architect-py 5.1.4b1__py3-none-any.whl → 5.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- architect_py/__init__.py +19 -1
- architect_py/async_client.py +158 -45
- architect_py/client.py +4 -0
- architect_py/client.pyi +33 -9
- architect_py/grpc/client.py +24 -9
- architect_py/grpc/models/AlgoHelper/AlgoParamTypes.py +31 -0
- architect_py/grpc/models/AlgoHelper/__init__.py +2 -0
- architect_py/grpc/models/Auth/AuthInfoRequest.py +37 -0
- architect_py/grpc/models/Auth/AuthInfoResponse.py +30 -0
- architect_py/grpc/models/Core/ConfigResponse.py +7 -2
- architect_py/grpc/models/Folio/AccountHistoryRequest.py +35 -4
- architect_py/grpc/models/Folio/AccountSummary.py +7 -1
- architect_py/grpc/models/Marketdata/Candle.py +6 -0
- architect_py/grpc/models/Marketdata/L1BookSnapshot.py +17 -3
- architect_py/grpc/models/Marketdata/L2BookSnapshot.py +6 -0
- architect_py/grpc/models/Marketdata/Liquidation.py +6 -0
- architect_py/grpc/models/Marketdata/Ticker.py +6 -0
- architect_py/grpc/models/Marketdata/Trade.py +6 -0
- architect_py/grpc/models/Oms/Order.py +38 -14
- architect_py/grpc/models/Oms/PlaceOrderRequest.py +38 -14
- architect_py/grpc/models/__init__.py +4 -1
- architect_py/grpc/models/definitions.py +173 -0
- architect_py/grpc/orderflow.py +138 -0
- architect_py/tests/test_order_entry.py +9 -6
- architect_py/tests/test_orderflow.py +116 -27
- architect_py/tests/test_positions.py +173 -0
- architect_py/tests/test_rounding.py +28 -28
- architect_py/utils/pandas.py +50 -1
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/METADATA +1 -1
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/RECORD +35 -29
- examples/funding_rate_mean_reversion_algo.py +23 -47
- scripts/postprocess_grpc.py +17 -2
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/WHEEL +0 -0
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/licenses/LICENSE +0 -0
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/top_level.txt +0 -0
architect_py/grpc/client.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from types import UnionType
|
2
2
|
from typing import Any, AsyncIterator, Sequence, Tuple
|
3
3
|
|
4
|
-
import grpc
|
4
|
+
import grpc.aio
|
5
5
|
import msgspec
|
6
6
|
|
7
7
|
from . import *
|
@@ -17,6 +17,8 @@ class GrpcClient:
|
|
17
17
|
endpoint: str
|
18
18
|
channel: grpc.aio.Channel
|
19
19
|
jwt: str | None = None
|
20
|
+
as_user: str | None = None
|
21
|
+
as_role: str | None = None
|
20
22
|
|
21
23
|
def __init__(
|
22
24
|
self,
|
@@ -25,9 +27,13 @@ class GrpcClient:
|
|
25
27
|
port: int,
|
26
28
|
use_ssl: bool = False,
|
27
29
|
options: Sequence[Tuple[str, Any]] | None = None,
|
30
|
+
as_user: str | None = None,
|
31
|
+
as_role: str | None = None,
|
28
32
|
):
|
29
33
|
scheme = "https" if use_ssl else "http"
|
30
34
|
self.endpoint = f"{scheme}://{host}:{port}"
|
35
|
+
self.as_user = as_user
|
36
|
+
self.as_role = as_role
|
31
37
|
if use_ssl:
|
32
38
|
credentials = grpc.ssl_channel_credentials()
|
33
39
|
self.channel = grpc.aio.secure_channel(
|
@@ -39,6 +45,16 @@ class GrpcClient:
|
|
39
45
|
def set_jwt(self, jwt: str | None):
|
40
46
|
self.jwt = jwt
|
41
47
|
|
48
|
+
def metadata(self) -> Sequence[Tuple[str, str]]:
|
49
|
+
metadata = []
|
50
|
+
if self.jwt is not None:
|
51
|
+
metadata.append(("authorization", f"Bearer {self.jwt}"))
|
52
|
+
if self.as_user is not None:
|
53
|
+
metadata.append(("x-architect-user", self.as_user))
|
54
|
+
if self.as_role is not None:
|
55
|
+
metadata.append(("x-architect-role", self.as_role))
|
56
|
+
return metadata
|
57
|
+
|
42
58
|
async def close(self):
|
43
59
|
await self.channel.close()
|
44
60
|
|
@@ -59,6 +75,8 @@ class GrpcClient:
|
|
59
75
|
async def unary_unary(
|
60
76
|
self,
|
61
77
|
request: RequestType[ResponseTypeGeneric],
|
78
|
+
*,
|
79
|
+
no_metadata: bool = False,
|
62
80
|
) -> ResponseTypeGeneric:
|
63
81
|
"""
|
64
82
|
Generic function for making a unary RPC call to the gRPC server.
|
@@ -75,10 +93,10 @@ class GrpcClient:
|
|
75
93
|
request_serializer=encoder.encode,
|
76
94
|
response_deserializer=decoder.decode,
|
77
95
|
)
|
78
|
-
if
|
79
|
-
metadata = (("authorization", f"Bearer {self.jwt}"),)
|
80
|
-
else:
|
96
|
+
if no_metadata:
|
81
97
|
metadata = ()
|
98
|
+
else:
|
99
|
+
metadata = self.metadata()
|
82
100
|
return await stub(request, metadata=metadata)
|
83
101
|
|
84
102
|
async def unary_stream(
|
@@ -100,11 +118,8 @@ class GrpcClient:
|
|
100
118
|
request_serializer=encoder.encode,
|
101
119
|
response_deserializer=decoder.decode,
|
102
120
|
)
|
103
|
-
|
104
|
-
|
105
|
-
else:
|
106
|
-
metadata = ()
|
107
|
-
call: grpc.aio._base_call.UnaryStreamCall[
|
121
|
+
metadata = self.metadata()
|
122
|
+
call: grpc.aio.UnaryStreamCall[
|
108
123
|
RequestType[ResponseTypeGeneric], ResponseTypeGeneric
|
109
124
|
] = stub(request, metadata=metadata)
|
110
125
|
async for update in call:
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# generated by datamodel-codegen:
|
2
|
+
# filename: AlgoHelper/AlgoParamTypes.json
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
from typing import List, Union
|
7
|
+
|
8
|
+
from msgspec import Struct
|
9
|
+
|
10
|
+
from .. import definitions
|
11
|
+
|
12
|
+
|
13
|
+
class AlgoParamTypes(Struct, omit_defaults=True):
|
14
|
+
"""
|
15
|
+
this is used to coerce creation of the params in the schema.json
|
16
|
+
"""
|
17
|
+
|
18
|
+
spreader: List[Union[definitions.SpreaderParams, definitions.SpreaderStatus]]
|
19
|
+
|
20
|
+
# Constructor that takes all field titles as arguments for convenience
|
21
|
+
@classmethod
|
22
|
+
def new(
|
23
|
+
cls,
|
24
|
+
spreader: List[Union[definitions.SpreaderParams, definitions.SpreaderStatus]],
|
25
|
+
):
|
26
|
+
return cls(
|
27
|
+
spreader,
|
28
|
+
)
|
29
|
+
|
30
|
+
def __str__(self) -> str:
|
31
|
+
return f"AlgoParamTypes(spreader={self.spreader})"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# generated by datamodel-codegen:
|
2
|
+
# filename: Auth/AuthInfoRequest.json
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
from architect_py.grpc.models.Auth.AuthInfoResponse import AuthInfoResponse
|
6
|
+
|
7
|
+
from msgspec import Struct
|
8
|
+
|
9
|
+
|
10
|
+
class AuthInfoRequest(Struct, omit_defaults=True):
|
11
|
+
pass
|
12
|
+
|
13
|
+
# Constructor that takes all field titles as arguments for convenience
|
14
|
+
@classmethod
|
15
|
+
def new(
|
16
|
+
cls,
|
17
|
+
):
|
18
|
+
return cls()
|
19
|
+
|
20
|
+
def __str__(self) -> str:
|
21
|
+
return f"AuthInfoRequest()"
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def get_response_type():
|
25
|
+
return AuthInfoResponse
|
26
|
+
|
27
|
+
@staticmethod
|
28
|
+
def get_unannotated_response_type():
|
29
|
+
return AuthInfoResponse
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def get_route() -> str:
|
33
|
+
return "/json.architect.Auth/AuthInfo"
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def get_rpc_method():
|
37
|
+
return "unary"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# generated by datamodel-codegen:
|
2
|
+
# filename: Auth/AuthInfoResponse.json
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
from msgspec import Struct
|
9
|
+
|
10
|
+
from .. import definitions
|
11
|
+
|
12
|
+
|
13
|
+
class AuthInfoResponse(Struct, omit_defaults=True):
|
14
|
+
original_user_id: Optional[definitions.UserId] = None
|
15
|
+
user_id: Optional[definitions.UserId] = None
|
16
|
+
|
17
|
+
# Constructor that takes all field titles as arguments for convenience
|
18
|
+
@classmethod
|
19
|
+
def new(
|
20
|
+
cls,
|
21
|
+
original_user_id: Optional[definitions.UserId] = None,
|
22
|
+
user_id: Optional[definitions.UserId] = None,
|
23
|
+
):
|
24
|
+
return cls(
|
25
|
+
original_user_id,
|
26
|
+
user_id,
|
27
|
+
)
|
28
|
+
|
29
|
+
def __str__(self) -> str:
|
30
|
+
return f"AuthInfoResponse(original_user_id={self.original_user_id},user_id={self.user_id})"
|
@@ -3,23 +3,28 @@
|
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
from typing import Dict
|
6
|
+
from typing import Dict, Optional
|
7
7
|
|
8
8
|
from msgspec import Struct
|
9
9
|
|
10
10
|
|
11
11
|
class ConfigResponse(Struct, omit_defaults=True):
|
12
12
|
marketdata: Dict[str, str]
|
13
|
+
symbology: Optional[str] = None
|
13
14
|
|
14
15
|
# Constructor that takes all field titles as arguments for convenience
|
15
16
|
@classmethod
|
16
17
|
def new(
|
17
18
|
cls,
|
18
19
|
marketdata: Dict[str, str],
|
20
|
+
symbology: Optional[str] = None,
|
19
21
|
):
|
20
22
|
return cls(
|
21
23
|
marketdata,
|
24
|
+
symbology,
|
22
25
|
)
|
23
26
|
|
24
27
|
def __str__(self) -> str:
|
25
|
-
return
|
28
|
+
return (
|
29
|
+
f"ConfigResponse(marketdata={self.marketdata},symbology={self.symbology})"
|
30
|
+
)
|
@@ -4,10 +4,10 @@
|
|
4
4
|
from __future__ import annotations
|
5
5
|
from architect_py.grpc.models.Folio.AccountHistoryResponse import AccountHistoryResponse
|
6
6
|
|
7
|
-
from datetime import datetime
|
8
|
-
from typing import Optional
|
7
|
+
from datetime import datetime, time
|
8
|
+
from typing import Annotated, Optional
|
9
9
|
|
10
|
-
from msgspec import Struct
|
10
|
+
from msgspec import Meta, Struct
|
11
11
|
|
12
12
|
from .. import definitions
|
13
13
|
|
@@ -15,6 +15,31 @@ from .. import definitions
|
|
15
15
|
class AccountHistoryRequest(Struct, omit_defaults=True):
|
16
16
|
account: definitions.AccountIdOrName
|
17
17
|
from_inclusive: Optional[datetime] = None
|
18
|
+
granularity: Optional[definitions.AccountHistoryGranularity] = None
|
19
|
+
limit: Optional[
|
20
|
+
Annotated[
|
21
|
+
Optional[int],
|
22
|
+
Meta(
|
23
|
+
description="Default maximum of 100 data points. If the number of data points between from_inclusive and to_exclusive exceeds the limit, the response will be truncated. Data is always returned in descending timestamp order."
|
24
|
+
),
|
25
|
+
]
|
26
|
+
] = None
|
27
|
+
"""
|
28
|
+
Default maximum of 100 data points. If the number of data points between from_inclusive and to_exclusive exceeds the limit, the response will be truncated. Data is always returned in descending timestamp order.
|
29
|
+
"""
|
30
|
+
time_of_day: Optional[
|
31
|
+
Annotated[
|
32
|
+
Optional[time],
|
33
|
+
Meta(
|
34
|
+
description="For daily granularity, the UTC time of day to use for each day.\n\nCurrently the seconds and subseconds parts are ignored."
|
35
|
+
),
|
36
|
+
]
|
37
|
+
] = None
|
38
|
+
"""
|
39
|
+
For daily granularity, the UTC time of day to use for each day.
|
40
|
+
|
41
|
+
Currently the seconds and subseconds parts are ignored.
|
42
|
+
"""
|
18
43
|
to_exclusive: Optional[datetime] = None
|
19
44
|
|
20
45
|
# Constructor that takes all field titles as arguments for convenience
|
@@ -23,16 +48,22 @@ class AccountHistoryRequest(Struct, omit_defaults=True):
|
|
23
48
|
cls,
|
24
49
|
account: definitions.AccountIdOrName,
|
25
50
|
from_inclusive: Optional[datetime] = None,
|
51
|
+
granularity: Optional[definitions.AccountHistoryGranularity] = None,
|
52
|
+
limit: Optional[int] = None,
|
53
|
+
time_of_day: Optional[time] = None,
|
26
54
|
to_exclusive: Optional[datetime] = None,
|
27
55
|
):
|
28
56
|
return cls(
|
29
57
|
account,
|
30
58
|
from_inclusive,
|
59
|
+
granularity,
|
60
|
+
limit,
|
61
|
+
time_of_day,
|
31
62
|
to_exclusive,
|
32
63
|
)
|
33
64
|
|
34
65
|
def __str__(self) -> str:
|
35
|
-
return f"AccountHistoryRequest(account={self.account},from_inclusive={self.from_inclusive},to_exclusive={self.to_exclusive})"
|
66
|
+
return f"AccountHistoryRequest(account={self.account},from_inclusive={self.from_inclusive},granularity={self.granularity},limit={self.limit},time_of_day={self.time_of_day},to_exclusive={self.to_exclusive})"
|
36
67
|
|
37
68
|
@staticmethod
|
38
69
|
def get_response_type():
|
@@ -15,7 +15,13 @@ from .. import definitions
|
|
15
15
|
class AccountSummary(Struct, omit_defaults=True):
|
16
16
|
account: str
|
17
17
|
balances: Dict[str, Decimal]
|
18
|
-
positions:
|
18
|
+
positions: Annotated[
|
19
|
+
Dict[str, List[definitions.AccountPosition]],
|
20
|
+
Meta(description="map from TradableProduct to a list of AccountPosition"),
|
21
|
+
]
|
22
|
+
"""
|
23
|
+
map from TradableProduct to a list of AccountPosition
|
24
|
+
"""
|
19
25
|
timestamp: datetime
|
20
26
|
cash_excess: Optional[
|
21
27
|
Annotated[Optional[Decimal], Meta(description="Cash available to withdraw.")]
|
@@ -136,10 +136,16 @@ class Candle(Struct, omit_defaults=True):
|
|
136
136
|
|
137
137
|
@property
|
138
138
|
def datetime(self) -> datetime:
|
139
|
+
"""
|
140
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
141
|
+
"""
|
139
142
|
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
140
143
|
|
141
144
|
@property
|
142
145
|
def datetime_local(self) -> datetime:
|
146
|
+
"""
|
147
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
148
|
+
"""
|
143
149
|
return datetime.fromtimestamp(self.ts)
|
144
150
|
|
145
151
|
@property
|
@@ -13,7 +13,15 @@ from msgspec import Meta, Struct
|
|
13
13
|
class L1BookSnapshot(Struct, omit_defaults=True):
|
14
14
|
s: Annotated[str, Meta(title="symbol")]
|
15
15
|
tn: Annotated[int, Meta(ge=0, title="timestamp_ns")]
|
16
|
-
ts: Annotated[
|
16
|
+
ts: Annotated[
|
17
|
+
int,
|
18
|
+
Meta(
|
19
|
+
description="Time that the exchange stamped the message", title="timestamp"
|
20
|
+
),
|
21
|
+
]
|
22
|
+
"""
|
23
|
+
Time that the exchange stamped the message
|
24
|
+
"""
|
17
25
|
a: Optional[
|
18
26
|
Annotated[
|
19
27
|
List[Decimal], Meta(description="(price, quantity)", title="best_ask")
|
@@ -34,13 +42,13 @@ class L1BookSnapshot(Struct, omit_defaults=True):
|
|
34
42
|
Annotated[
|
35
43
|
Optional[int],
|
36
44
|
Meta(
|
37
|
-
description="Time that Architect feed received the message
|
45
|
+
description="Time that Architect feed received the message that updated the BBO",
|
38
46
|
title="recv_time",
|
39
47
|
),
|
40
48
|
]
|
41
49
|
] = None
|
42
50
|
"""
|
43
|
-
Time that Architect feed received the message
|
51
|
+
Time that Architect feed received the message that updated the BBO
|
44
52
|
"""
|
45
53
|
rtn: Optional[Annotated[Optional[int], Meta(title="recv_time_ns")]] = None
|
46
54
|
|
@@ -95,10 +103,16 @@ class L1BookSnapshot(Struct, omit_defaults=True):
|
|
95
103
|
|
96
104
|
@property
|
97
105
|
def datetime(self) -> datetime:
|
106
|
+
"""
|
107
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
108
|
+
"""
|
98
109
|
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
99
110
|
|
100
111
|
@property
|
101
112
|
def datetime_local(self) -> datetime:
|
113
|
+
"""
|
114
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
115
|
+
"""
|
102
116
|
return datetime.fromtimestamp(self.ts)
|
103
117
|
|
104
118
|
@property
|
@@ -100,8 +100,14 @@ class L2BookSnapshot(Struct, omit_defaults=True):
|
|
100
100
|
|
101
101
|
@property
|
102
102
|
def datetime(self) -> datetime:
|
103
|
+
"""
|
104
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
105
|
+
"""
|
103
106
|
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
104
107
|
|
105
108
|
@property
|
106
109
|
def datetime_local(self) -> datetime:
|
110
|
+
"""
|
111
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
112
|
+
"""
|
107
113
|
return datetime.fromtimestamp(self.ts)
|
@@ -93,8 +93,14 @@ class Liquidation(Struct, omit_defaults=True):
|
|
93
93
|
|
94
94
|
@property
|
95
95
|
def datetime(self) -> datetime:
|
96
|
+
"""
|
97
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
98
|
+
"""
|
96
99
|
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
97
100
|
|
98
101
|
@property
|
99
102
|
def datetime_local(self) -> datetime:
|
103
|
+
"""
|
104
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
105
|
+
"""
|
100
106
|
return datetime.fromtimestamp(self.ts)
|
@@ -155,10 +155,16 @@ class Ticker(Struct, omit_defaults=True):
|
|
155
155
|
|
156
156
|
@property
|
157
157
|
def datetime(self) -> datetime:
|
158
|
+
"""
|
159
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
160
|
+
"""
|
158
161
|
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
159
162
|
|
160
163
|
@property
|
161
164
|
def datetime_local(self) -> datetime:
|
165
|
+
"""
|
166
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
167
|
+
"""
|
162
168
|
return datetime.fromtimestamp(self.ts)
|
163
169
|
|
164
170
|
@property
|
@@ -85,10 +85,16 @@ class Trade(Struct, omit_defaults=True):
|
|
85
85
|
|
86
86
|
@property
|
87
87
|
def datetime(self) -> datetime:
|
88
|
+
"""
|
89
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
90
|
+
"""
|
88
91
|
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
89
92
|
|
90
93
|
@property
|
91
94
|
def datetime_local(self) -> datetime:
|
95
|
+
"""
|
96
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
97
|
+
"""
|
92
98
|
return datetime.fromtimestamp(self.ts)
|
93
99
|
|
94
100
|
@property
|
@@ -51,6 +51,10 @@ class Order(Struct, omit_defaults=True):
|
|
51
51
|
p: Optional[Annotated[Decimal, Meta(title="limit_price")]] = None
|
52
52
|
po: Optional[Annotated[bool, Meta(title="post_only")]] = None
|
53
53
|
tp: Optional[Annotated[Decimal, Meta(title="trigger_price")]] = None
|
54
|
+
sl: Optional[
|
55
|
+
Annotated[Optional[definitions.TriggerLimitOrderType], Meta(title="stop_loss")]
|
56
|
+
] = None
|
57
|
+
tpp: Optional[Annotated[Optional[Decimal], Meta(title="take_profit_price")]] = None
|
54
58
|
|
55
59
|
# Constructor that takes all field titles as arguments for convenience
|
56
60
|
@classmethod
|
@@ -79,6 +83,8 @@ class Order(Struct, omit_defaults=True):
|
|
79
83
|
limit_price: Optional[Decimal] = None,
|
80
84
|
post_only: Optional[bool] = None,
|
81
85
|
trigger_price: Optional[Decimal] = None,
|
86
|
+
stop_loss: Optional[definitions.TriggerLimitOrderType] = None,
|
87
|
+
take_profit_price: Optional[Decimal] = None,
|
82
88
|
):
|
83
89
|
return cls(
|
84
90
|
account,
|
@@ -104,10 +110,12 @@ class Order(Struct, omit_defaults=True):
|
|
104
110
|
limit_price,
|
105
111
|
post_only,
|
106
112
|
trigger_price,
|
113
|
+
stop_loss,
|
114
|
+
take_profit_price,
|
107
115
|
)
|
108
116
|
|
109
117
|
def __str__(self) -> str:
|
110
|
-
return f"Order(account={self.a},dir={self.d},id={self.id},status={self.o},quantity={self.q},symbol={self.s},source={self.src},time_in_force={self.tif},recv_time_ns={self.tn},recv_time={self.ts},trader={self.u},execution_venue={self.ve},filled_quantity={self.xq},order_type={self.k},exchange_order_id={self.eid},parent_id={self.pid},reject_reason={self.r},reject_message={self.rm},is_short_sale={self.ss},average_fill_price={self.xp},limit_price={self.p},post_only={self.po},trigger_price={self.tp})"
|
118
|
+
return f"Order(account={self.a},dir={self.d},id={self.id},status={self.o},quantity={self.q},symbol={self.s},source={self.src},time_in_force={self.tif},recv_time_ns={self.tn},recv_time={self.ts},trader={self.u},execution_venue={self.ve},filled_quantity={self.xq},order_type={self.k},exchange_order_id={self.eid},parent_id={self.pid},reject_reason={self.r},reject_message={self.rm},is_short_sale={self.ss},average_fill_price={self.xp},limit_price={self.p},post_only={self.po},trigger_price={self.tp},stop_loss={self.sl},take_profit_price={self.tpp})"
|
111
119
|
|
112
120
|
@property
|
113
121
|
def account(self) -> str:
|
@@ -285,20 +293,27 @@ class Order(Struct, omit_defaults=True):
|
|
285
293
|
def trigger_price(self, value: Optional[Decimal]) -> None:
|
286
294
|
self.tp = value
|
287
295
|
|
296
|
+
@property
|
297
|
+
def stop_loss(self) -> Optional[definitions.TriggerLimitOrderType]:
|
298
|
+
return self.sl
|
299
|
+
|
300
|
+
@stop_loss.setter
|
301
|
+
def stop_loss(self, value: Optional[definitions.TriggerLimitOrderType]) -> None:
|
302
|
+
self.sl = value
|
303
|
+
|
304
|
+
@property
|
305
|
+
def take_profit_price(self) -> Optional[Decimal]:
|
306
|
+
return self.tpp
|
307
|
+
|
308
|
+
@take_profit_price.setter
|
309
|
+
def take_profit_price(self, value: Optional[Decimal]) -> None:
|
310
|
+
self.tpp = value
|
311
|
+
|
288
312
|
def __post_init__(self):
|
289
|
-
if self.k == "
|
290
|
-
if not all(getattr(self, key) is not None for key in []):
|
291
|
-
raise ValueError(
|
292
|
-
f"When field k (order_type) is of value MARKET, class Order requires fields []"
|
293
|
-
)
|
294
|
-
elif any(getattr(self, key) is not None for key in ["p", "po", "tp"]):
|
295
|
-
raise ValueError(
|
296
|
-
f"When field k (order_type) is of value MARKET, class Order should not have fields ['p', 'po', 'tp']"
|
297
|
-
)
|
298
|
-
elif self.k == "LIMIT":
|
313
|
+
if self.k == "LIMIT":
|
299
314
|
if not all(getattr(self, key) is not None for key in ["p", "po"]):
|
300
315
|
raise ValueError(
|
301
|
-
f"When field k (order_type) is of value LIMIT, class Order requires fields ['p', 'po']"
|
316
|
+
f"When field k (order_type) is of value LIMIT, class Order requires fields ['limit_price (p)', 'post_only (po)']"
|
302
317
|
)
|
303
318
|
elif any(getattr(self, key) is not None for key in ["tp"]):
|
304
319
|
raise ValueError(
|
@@ -307,7 +322,7 @@ class Order(Struct, omit_defaults=True):
|
|
307
322
|
elif self.k == "STOP_LOSS_LIMIT":
|
308
323
|
if not all(getattr(self, key) is not None for key in ["p", "tp"]):
|
309
324
|
raise ValueError(
|
310
|
-
f"When field k (order_type) is of value STOP_LOSS_LIMIT, class Order requires fields ['p', 'tp']"
|
325
|
+
f"When field k (order_type) is of value STOP_LOSS_LIMIT, class Order requires fields ['limit_price (p)', 'trigger_price (tp)']"
|
311
326
|
)
|
312
327
|
elif any(getattr(self, key) is not None for key in ["po"]):
|
313
328
|
raise ValueError(
|
@@ -316,9 +331,18 @@ class Order(Struct, omit_defaults=True):
|
|
316
331
|
elif self.k == "TAKE_PROFIT_LIMIT":
|
317
332
|
if not all(getattr(self, key) is not None for key in ["p", "tp"]):
|
318
333
|
raise ValueError(
|
319
|
-
f"When field k (order_type) is of value TAKE_PROFIT_LIMIT, class Order requires fields ['p', 'tp']"
|
334
|
+
f"When field k (order_type) is of value TAKE_PROFIT_LIMIT, class Order requires fields ['limit_price (p)', 'trigger_price (tp)']"
|
320
335
|
)
|
321
336
|
elif any(getattr(self, key) is not None for key in ["po"]):
|
322
337
|
raise ValueError(
|
323
338
|
f"When field k (order_type) is of value TAKE_PROFIT_LIMIT, class Order should not have fields ['po']"
|
324
339
|
)
|
340
|
+
elif self.k == "BRACKET":
|
341
|
+
if not all(getattr(self, key) is not None for key in ["p", "po"]):
|
342
|
+
raise ValueError(
|
343
|
+
f"When field k (order_type) is of value BRACKET, class Order requires fields ['limit_price (p)', 'post_only (po)']"
|
344
|
+
)
|
345
|
+
elif any(getattr(self, key) is not None for key in ["tp"]):
|
346
|
+
raise ValueError(
|
347
|
+
f"When field k (order_type) is of value BRACKET, class Order should not have fields ['tp']"
|
348
|
+
)
|
@@ -46,6 +46,10 @@ class PlaceOrderRequest(Struct, omit_defaults=True):
|
|
46
46
|
p: Optional[Annotated[Decimal, Meta(title="limit_price")]] = None
|
47
47
|
po: Optional[Annotated[bool, Meta(title="post_only")]] = None
|
48
48
|
tp: Optional[Annotated[Decimal, Meta(title="trigger_price")]] = None
|
49
|
+
sl: Optional[
|
50
|
+
Annotated[Optional[definitions.TriggerLimitOrderType], Meta(title="stop_loss")]
|
51
|
+
] = None
|
52
|
+
tpp: Optional[Annotated[Optional[Decimal], Meta(title="take_profit_price")]] = None
|
49
53
|
|
50
54
|
# Constructor that takes all field titles as arguments for convenience
|
51
55
|
@classmethod
|
@@ -65,6 +69,8 @@ class PlaceOrderRequest(Struct, omit_defaults=True):
|
|
65
69
|
limit_price: Optional[Decimal] = None,
|
66
70
|
post_only: Optional[bool] = None,
|
67
71
|
trigger_price: Optional[Decimal] = None,
|
72
|
+
stop_loss: Optional[definitions.TriggerLimitOrderType] = None,
|
73
|
+
take_profit_price: Optional[Decimal] = None,
|
68
74
|
):
|
69
75
|
return cls(
|
70
76
|
dir,
|
@@ -81,10 +87,12 @@ class PlaceOrderRequest(Struct, omit_defaults=True):
|
|
81
87
|
limit_price,
|
82
88
|
post_only,
|
83
89
|
trigger_price,
|
90
|
+
stop_loss,
|
91
|
+
take_profit_price,
|
84
92
|
)
|
85
93
|
|
86
94
|
def __str__(self) -> str:
|
87
|
-
return f"PlaceOrderRequest(dir={self.d},quantity={self.q},symbol={self.s},time_in_force={self.tif},order_type={self.k},account={self.a},id={self.id},parent_id={self.pid},source={self.src},trader={self.u},execution_venue={self.x},limit_price={self.p},post_only={self.po},trigger_price={self.tp})"
|
95
|
+
return f"PlaceOrderRequest(dir={self.d},quantity={self.q},symbol={self.s},time_in_force={self.tif},order_type={self.k},account={self.a},id={self.id},parent_id={self.pid},source={self.src},trader={self.u},execution_venue={self.x},limit_price={self.p},post_only={self.po},trigger_price={self.tp},stop_loss={self.sl},take_profit_price={self.tpp})"
|
88
96
|
|
89
97
|
@property
|
90
98
|
def dir(self) -> OrderDir:
|
@@ -190,6 +198,22 @@ class PlaceOrderRequest(Struct, omit_defaults=True):
|
|
190
198
|
def trigger_price(self, value: Optional[Decimal]) -> None:
|
191
199
|
self.tp = value
|
192
200
|
|
201
|
+
@property
|
202
|
+
def stop_loss(self) -> Optional[definitions.TriggerLimitOrderType]:
|
203
|
+
return self.sl
|
204
|
+
|
205
|
+
@stop_loss.setter
|
206
|
+
def stop_loss(self, value: Optional[definitions.TriggerLimitOrderType]) -> None:
|
207
|
+
self.sl = value
|
208
|
+
|
209
|
+
@property
|
210
|
+
def take_profit_price(self) -> Optional[Decimal]:
|
211
|
+
return self.tpp
|
212
|
+
|
213
|
+
@take_profit_price.setter
|
214
|
+
def take_profit_price(self, value: Optional[Decimal]) -> None:
|
215
|
+
self.tpp = value
|
216
|
+
|
193
217
|
@staticmethod
|
194
218
|
def get_response_type():
|
195
219
|
return Order
|
@@ -207,19 +231,10 @@ class PlaceOrderRequest(Struct, omit_defaults=True):
|
|
207
231
|
return "unary"
|
208
232
|
|
209
233
|
def __post_init__(self):
|
210
|
-
if self.k == "
|
211
|
-
if not all(getattr(self, key) is not None for key in []):
|
212
|
-
raise ValueError(
|
213
|
-
f"When field k (order_type) is of value MARKET, class PlaceOrderRequest requires fields []"
|
214
|
-
)
|
215
|
-
elif any(getattr(self, key) is not None for key in ["p", "po", "tp"]):
|
216
|
-
raise ValueError(
|
217
|
-
f"When field k (order_type) is of value MARKET, class PlaceOrderRequest should not have fields ['p', 'po', 'tp']"
|
218
|
-
)
|
219
|
-
elif self.k == "LIMIT":
|
234
|
+
if self.k == "LIMIT":
|
220
235
|
if not all(getattr(self, key) is not None for key in ["p", "po"]):
|
221
236
|
raise ValueError(
|
222
|
-
f"When field k (order_type) is of value LIMIT, class PlaceOrderRequest requires fields ['p', 'po']"
|
237
|
+
f"When field k (order_type) is of value LIMIT, class PlaceOrderRequest requires fields ['limit_price (p)', 'post_only (po)']"
|
223
238
|
)
|
224
239
|
elif any(getattr(self, key) is not None for key in ["tp"]):
|
225
240
|
raise ValueError(
|
@@ -228,7 +243,7 @@ class PlaceOrderRequest(Struct, omit_defaults=True):
|
|
228
243
|
elif self.k == "STOP_LOSS_LIMIT":
|
229
244
|
if not all(getattr(self, key) is not None for key in ["p", "tp"]):
|
230
245
|
raise ValueError(
|
231
|
-
f"When field k (order_type) is of value STOP_LOSS_LIMIT, class PlaceOrderRequest requires fields ['p', 'tp']"
|
246
|
+
f"When field k (order_type) is of value STOP_LOSS_LIMIT, class PlaceOrderRequest requires fields ['limit_price (p)', 'trigger_price (tp)']"
|
232
247
|
)
|
233
248
|
elif any(getattr(self, key) is not None for key in ["po"]):
|
234
249
|
raise ValueError(
|
@@ -237,9 +252,18 @@ class PlaceOrderRequest(Struct, omit_defaults=True):
|
|
237
252
|
elif self.k == "TAKE_PROFIT_LIMIT":
|
238
253
|
if not all(getattr(self, key) is not None for key in ["p", "tp"]):
|
239
254
|
raise ValueError(
|
240
|
-
f"When field k (order_type) is of value TAKE_PROFIT_LIMIT, class PlaceOrderRequest requires fields ['p', 'tp']"
|
255
|
+
f"When field k (order_type) is of value TAKE_PROFIT_LIMIT, class PlaceOrderRequest requires fields ['limit_price (p)', 'trigger_price (tp)']"
|
241
256
|
)
|
242
257
|
elif any(getattr(self, key) is not None for key in ["po"]):
|
243
258
|
raise ValueError(
|
244
259
|
f"When field k (order_type) is of value TAKE_PROFIT_LIMIT, class PlaceOrderRequest should not have fields ['po']"
|
245
260
|
)
|
261
|
+
elif self.k == "BRACKET":
|
262
|
+
if not all(getattr(self, key) is not None for key in ["p", "po"]):
|
263
|
+
raise ValueError(
|
264
|
+
f"When field k (order_type) is of value BRACKET, class PlaceOrderRequest requires fields ['limit_price (p)', 'post_only (po)']"
|
265
|
+
)
|
266
|
+
elif any(getattr(self, key) is not None for key in ["tp"]):
|
267
|
+
raise ValueError(
|
268
|
+
f"When field k (order_type) is of value BRACKET, class PlaceOrderRequest should not have fields ['tp']"
|
269
|
+
)
|