ctrader-api-client 0.1.0__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.
- ctrader_api_client/__init__.py +64 -0
- ctrader_api_client/_internal/__init__.py +26 -0
- ctrader_api_client/_internal/messages.py +348 -0
- ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +42 -0
- ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +30 -0
- ctrader_api_client/_internal/proto/OpenApiMessages.py +1112 -0
- ctrader_api_client/_internal/proto/OpenApiModelMessages.py +802 -0
- ctrader_api_client/_internal/proto/__init__.py +320 -0
- ctrader_api_client/_internal/serialization.py +84 -0
- ctrader_api_client/api/__init__.py +21 -0
- ctrader_api_client/api/accounts.py +71 -0
- ctrader_api_client/api/market_data.py +424 -0
- ctrader_api_client/api/symbols.py +171 -0
- ctrader_api_client/api/trading.py +506 -0
- ctrader_api_client/auth/__init__.py +14 -0
- ctrader_api_client/auth/credentials.py +72 -0
- ctrader_api_client/auth/manager.py +511 -0
- ctrader_api_client/client.py +475 -0
- ctrader_api_client/config.py +56 -0
- ctrader_api_client/connection/__init__.py +16 -0
- ctrader_api_client/connection/heartbeat.py +120 -0
- ctrader_api_client/connection/protocol.py +366 -0
- ctrader_api_client/connection/transport.py +123 -0
- ctrader_api_client/enums.py +138 -0
- ctrader_api_client/events/__init__.py +65 -0
- ctrader_api_client/events/emitter.py +254 -0
- ctrader_api_client/events/router.py +400 -0
- ctrader_api_client/events/types.py +340 -0
- ctrader_api_client/exceptions.py +231 -0
- ctrader_api_client/models/__init__.py +50 -0
- ctrader_api_client/models/_base.py +19 -0
- ctrader_api_client/models/account.py +177 -0
- ctrader_api_client/models/deal.py +242 -0
- ctrader_api_client/models/market_data.py +192 -0
- ctrader_api_client/models/order.py +262 -0
- ctrader_api_client/models/position.py +209 -0
- ctrader_api_client/models/requests.py +299 -0
- ctrader_api_client/models/symbol.py +194 -0
- ctrader_api_client/py.typed +0 -0
- ctrader_api_client-0.1.0.dist-info/METADATA +252 -0
- ctrader_api_client-0.1.0.dist-info/RECORD +43 -0
- ctrader_api_client-0.1.0.dist-info/WHEEL +4 -0
- ctrader_api_client-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""cTrader Open API Python client.
|
|
2
|
+
|
|
3
|
+
A high-level async client for the cTrader trading platform.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
```python
|
|
7
|
+
import asyncio
|
|
8
|
+
from ctrader_api_client import CTraderClient, ClientConfig
|
|
9
|
+
from ctrader_api_client.events import SpotEvent
|
|
10
|
+
|
|
11
|
+
config = ClientConfig(
|
|
12
|
+
client_id="your_client_id",
|
|
13
|
+
client_secret="your_client_secret",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
client = CTraderClient(config)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@client.on(SpotEvent, symbol_id=270)
|
|
20
|
+
async def on_spot(event: SpotEvent) -> None:
|
|
21
|
+
print(f"{event.bid}/{event.ask}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
async with client:
|
|
26
|
+
await client.auth.authenticate_app()
|
|
27
|
+
creds = await client.auth.authenticate_by_trader_login(
|
|
28
|
+
trader_login=17091452,
|
|
29
|
+
access_token="...",
|
|
30
|
+
refresh_token="...",
|
|
31
|
+
expires_at=1778617423,
|
|
32
|
+
)
|
|
33
|
+
await client.market_data.subscribe_spots(creds.account_id, [270])
|
|
34
|
+
await asyncio.Event().wait()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
38
|
+
```
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from .client import CTraderClient
|
|
42
|
+
from .config import ClientConfig
|
|
43
|
+
|
|
44
|
+
# Re-export enums for easier access
|
|
45
|
+
from .enums import (
|
|
46
|
+
AccountType,
|
|
47
|
+
ExecutionType,
|
|
48
|
+
OrderSide,
|
|
49
|
+
OrderType,
|
|
50
|
+
PositionStatus,
|
|
51
|
+
TrendbarPeriod,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"AccountType",
|
|
57
|
+
"CTraderClient",
|
|
58
|
+
"ClientConfig",
|
|
59
|
+
"ExecutionType",
|
|
60
|
+
"OrderSide",
|
|
61
|
+
"OrderType",
|
|
62
|
+
"PositionStatus",
|
|
63
|
+
"TrendbarPeriod",
|
|
64
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .messages import (
|
|
2
|
+
ClientMessageIdGenerator,
|
|
3
|
+
MessageRegistry,
|
|
4
|
+
deserialize_proto_message,
|
|
5
|
+
get_registry,
|
|
6
|
+
unwrap_message,
|
|
7
|
+
wrap_message,
|
|
8
|
+
)
|
|
9
|
+
from .serialization import (
|
|
10
|
+
encode_with_length_prefix,
|
|
11
|
+
read_exact,
|
|
12
|
+
read_framed_message,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ClientMessageIdGenerator",
|
|
18
|
+
"MessageRegistry",
|
|
19
|
+
"deserialize_proto_message",
|
|
20
|
+
"encode_with_length_prefix",
|
|
21
|
+
"get_registry",
|
|
22
|
+
"read_exact",
|
|
23
|
+
"read_framed_message",
|
|
24
|
+
"unwrap_message",
|
|
25
|
+
"wrap_message",
|
|
26
|
+
]
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""Message routing and request correlation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
import betterproto
|
|
9
|
+
|
|
10
|
+
from ..exceptions import DeserializationError, UnknownPayloadTypeError
|
|
11
|
+
from .proto import (
|
|
12
|
+
ProtoErrorRes,
|
|
13
|
+
ProtoHeartbeatEvent,
|
|
14
|
+
ProtoMessage,
|
|
15
|
+
ProtoOAAccountAuthReq,
|
|
16
|
+
ProtoOAAccountAuthRes,
|
|
17
|
+
ProtoOAAccountDisconnectEvent,
|
|
18
|
+
ProtoOAAccountLogoutReq,
|
|
19
|
+
ProtoOAAccountLogoutRes,
|
|
20
|
+
ProtoOAAccountsTokenInvalidatedEvent,
|
|
21
|
+
ProtoOAAmendOrderReq,
|
|
22
|
+
ProtoOAAmendPositionSLTPReq,
|
|
23
|
+
ProtoOAApplicationAuthReq,
|
|
24
|
+
ProtoOAApplicationAuthRes,
|
|
25
|
+
ProtoOAAssetClassListReq,
|
|
26
|
+
ProtoOAAssetClassListRes,
|
|
27
|
+
ProtoOAAssetListReq,
|
|
28
|
+
ProtoOAAssetListRes,
|
|
29
|
+
ProtoOACancelOrderReq,
|
|
30
|
+
ProtoOACashFlowHistoryListReq,
|
|
31
|
+
ProtoOACashFlowHistoryListRes,
|
|
32
|
+
ProtoOAClientDisconnectEvent,
|
|
33
|
+
ProtoOAClosePositionReq,
|
|
34
|
+
ProtoOADealListByPositionIdReq,
|
|
35
|
+
ProtoOADealListByPositionIdRes,
|
|
36
|
+
ProtoOADealListReq,
|
|
37
|
+
ProtoOADealListRes,
|
|
38
|
+
ProtoOADealOffsetListReq,
|
|
39
|
+
ProtoOADealOffsetListRes,
|
|
40
|
+
ProtoOADepthEvent,
|
|
41
|
+
ProtoOAErrorRes,
|
|
42
|
+
ProtoOAExecutionEvent,
|
|
43
|
+
ProtoOAExpectedMarginReq,
|
|
44
|
+
ProtoOAExpectedMarginRes,
|
|
45
|
+
ProtoOAGetAccountListByAccessTokenReq,
|
|
46
|
+
ProtoOAGetAccountListByAccessTokenRes,
|
|
47
|
+
ProtoOAGetCtidProfileByTokenReq,
|
|
48
|
+
ProtoOAGetCtidProfileByTokenRes,
|
|
49
|
+
ProtoOAGetDynamicLeverageByIDReq,
|
|
50
|
+
ProtoOAGetDynamicLeverageByIDRes,
|
|
51
|
+
ProtoOAGetPositionUnrealizedPnLReq,
|
|
52
|
+
ProtoOAGetPositionUnrealizedPnLRes,
|
|
53
|
+
ProtoOAGetTickDataReq,
|
|
54
|
+
ProtoOAGetTickDataRes,
|
|
55
|
+
ProtoOAGetTrendbarsReq,
|
|
56
|
+
ProtoOAGetTrendbarsRes,
|
|
57
|
+
ProtoOAMarginCallListReq,
|
|
58
|
+
ProtoOAMarginCallListRes,
|
|
59
|
+
ProtoOAMarginCallTriggerEvent,
|
|
60
|
+
ProtoOAMarginCallUpdateEvent,
|
|
61
|
+
ProtoOAMarginCallUpdateReq,
|
|
62
|
+
ProtoOAMarginCallUpdateRes,
|
|
63
|
+
ProtoOAMarginChangedEvent,
|
|
64
|
+
ProtoOANewOrderReq,
|
|
65
|
+
ProtoOAOrderDetailsReq,
|
|
66
|
+
ProtoOAOrderDetailsRes,
|
|
67
|
+
ProtoOAOrderErrorEvent,
|
|
68
|
+
ProtoOAOrderListByPositionIdReq,
|
|
69
|
+
ProtoOAOrderListByPositionIdRes,
|
|
70
|
+
ProtoOAOrderListReq,
|
|
71
|
+
ProtoOAOrderListRes,
|
|
72
|
+
ProtoOAPayloadType,
|
|
73
|
+
ProtoOAReconcileReq,
|
|
74
|
+
ProtoOAReconcileRes,
|
|
75
|
+
ProtoOARefreshTokenReq,
|
|
76
|
+
ProtoOARefreshTokenRes,
|
|
77
|
+
ProtoOASpotEvent,
|
|
78
|
+
ProtoOASubscribeDepthQuotesReq,
|
|
79
|
+
ProtoOASubscribeDepthQuotesRes,
|
|
80
|
+
ProtoOASubscribeLiveTrendbarReq,
|
|
81
|
+
ProtoOASubscribeLiveTrendbarRes,
|
|
82
|
+
ProtoOASubscribeSpotsReq,
|
|
83
|
+
ProtoOASubscribeSpotsRes,
|
|
84
|
+
ProtoOASymbolByIdReq,
|
|
85
|
+
ProtoOASymbolByIdRes,
|
|
86
|
+
ProtoOASymbolCategoryListReq,
|
|
87
|
+
ProtoOASymbolCategoryListRes,
|
|
88
|
+
ProtoOASymbolChangedEvent,
|
|
89
|
+
ProtoOASymbolsForConversionReq,
|
|
90
|
+
ProtoOASymbolsForConversionRes,
|
|
91
|
+
ProtoOASymbolsListReq,
|
|
92
|
+
ProtoOASymbolsListRes,
|
|
93
|
+
ProtoOATraderReq,
|
|
94
|
+
ProtoOATraderRes,
|
|
95
|
+
ProtoOATraderUpdatedEvent,
|
|
96
|
+
ProtoOATrailingSLChangedEvent,
|
|
97
|
+
ProtoOAUnsubscribeDepthQuotesReq,
|
|
98
|
+
ProtoOAUnsubscribeDepthQuotesRes,
|
|
99
|
+
ProtoOAUnsubscribeLiveTrendbarReq,
|
|
100
|
+
ProtoOAUnsubscribeLiveTrendbarRes,
|
|
101
|
+
ProtoOAUnsubscribeSpotsReq,
|
|
102
|
+
ProtoOAUnsubscribeSpotsRes,
|
|
103
|
+
ProtoOAv1PnLChangeEvent,
|
|
104
|
+
ProtoOAv1PnLChangeSubscribeReq,
|
|
105
|
+
ProtoOAv1PnLChangeSubscribeRes,
|
|
106
|
+
ProtoOAv1PnLChangeUnSubscribeReq,
|
|
107
|
+
ProtoOAv1PnLChangeUnSubscribeRes,
|
|
108
|
+
ProtoOAVersionReq,
|
|
109
|
+
ProtoOAVersionRes,
|
|
110
|
+
ProtoPayloadType,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Explicit mapping from payload type to message class.
|
|
115
|
+
# This is intentionally manual to catch any mismatches at import time.
|
|
116
|
+
_PAYLOAD_TYPE_TO_CLASS: dict[int, type[betterproto.Message]] = {
|
|
117
|
+
# Common messages
|
|
118
|
+
ProtoPayloadType.ERROR_RES: ProtoErrorRes,
|
|
119
|
+
ProtoPayloadType.HEARTBEAT_EVENT: ProtoHeartbeatEvent,
|
|
120
|
+
# OpenAPI messages
|
|
121
|
+
ProtoOAPayloadType.PROTO_OA_APPLICATION_AUTH_REQ: ProtoOAApplicationAuthReq,
|
|
122
|
+
ProtoOAPayloadType.PROTO_OA_APPLICATION_AUTH_RES: ProtoOAApplicationAuthRes,
|
|
123
|
+
ProtoOAPayloadType.PROTO_OA_ACCOUNT_AUTH_REQ: ProtoOAAccountAuthReq,
|
|
124
|
+
ProtoOAPayloadType.PROTO_OA_ACCOUNT_AUTH_RES: ProtoOAAccountAuthRes,
|
|
125
|
+
ProtoOAPayloadType.PROTO_OA_VERSION_REQ: ProtoOAVersionReq,
|
|
126
|
+
ProtoOAPayloadType.PROTO_OA_VERSION_RES: ProtoOAVersionRes,
|
|
127
|
+
ProtoOAPayloadType.PROTO_OA_NEW_ORDER_REQ: ProtoOANewOrderReq,
|
|
128
|
+
ProtoOAPayloadType.PROTO_OA_TRAILING_SL_CHANGED_EVENT: ProtoOATrailingSLChangedEvent,
|
|
129
|
+
ProtoOAPayloadType.PROTO_OA_CANCEL_ORDER_REQ: ProtoOACancelOrderReq,
|
|
130
|
+
ProtoOAPayloadType.PROTO_OA_AMEND_ORDER_REQ: ProtoOAAmendOrderReq,
|
|
131
|
+
ProtoOAPayloadType.PROTO_OA_AMEND_POSITION_SLTP_REQ: ProtoOAAmendPositionSLTPReq,
|
|
132
|
+
ProtoOAPayloadType.PROTO_OA_CLOSE_POSITION_REQ: ProtoOAClosePositionReq,
|
|
133
|
+
ProtoOAPayloadType.PROTO_OA_ASSET_LIST_REQ: ProtoOAAssetListReq,
|
|
134
|
+
ProtoOAPayloadType.PROTO_OA_ASSET_LIST_RES: ProtoOAAssetListRes,
|
|
135
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOLS_LIST_REQ: ProtoOASymbolsListReq,
|
|
136
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOLS_LIST_RES: ProtoOASymbolsListRes,
|
|
137
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOL_BY_ID_REQ: ProtoOASymbolByIdReq,
|
|
138
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOL_BY_ID_RES: ProtoOASymbolByIdRes,
|
|
139
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOLS_FOR_CONVERSION_REQ: ProtoOASymbolsForConversionReq,
|
|
140
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOLS_FOR_CONVERSION_RES: ProtoOASymbolsForConversionRes,
|
|
141
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOL_CHANGED_EVENT: ProtoOASymbolChangedEvent,
|
|
142
|
+
ProtoOAPayloadType.PROTO_OA_TRADER_REQ: ProtoOATraderReq,
|
|
143
|
+
ProtoOAPayloadType.PROTO_OA_TRADER_RES: ProtoOATraderRes,
|
|
144
|
+
ProtoOAPayloadType.PROTO_OA_TRADER_UPDATE_EVENT: ProtoOATraderUpdatedEvent,
|
|
145
|
+
ProtoOAPayloadType.PROTO_OA_RECONCILE_REQ: ProtoOAReconcileReq,
|
|
146
|
+
ProtoOAPayloadType.PROTO_OA_RECONCILE_RES: ProtoOAReconcileRes,
|
|
147
|
+
ProtoOAPayloadType.PROTO_OA_EXECUTION_EVENT: ProtoOAExecutionEvent,
|
|
148
|
+
ProtoOAPayloadType.PROTO_OA_SUBSCRIBE_SPOTS_REQ: ProtoOASubscribeSpotsReq,
|
|
149
|
+
ProtoOAPayloadType.PROTO_OA_SUBSCRIBE_SPOTS_RES: ProtoOASubscribeSpotsRes,
|
|
150
|
+
ProtoOAPayloadType.PROTO_OA_UNSUBSCRIBE_SPOTS_REQ: ProtoOAUnsubscribeSpotsReq,
|
|
151
|
+
ProtoOAPayloadType.PROTO_OA_UNSUBSCRIBE_SPOTS_RES: ProtoOAUnsubscribeSpotsRes,
|
|
152
|
+
ProtoOAPayloadType.PROTO_OA_SPOT_EVENT: ProtoOASpotEvent,
|
|
153
|
+
ProtoOAPayloadType.PROTO_OA_ORDER_ERROR_EVENT: ProtoOAOrderErrorEvent,
|
|
154
|
+
ProtoOAPayloadType.PROTO_OA_DEAL_LIST_REQ: ProtoOADealListReq,
|
|
155
|
+
ProtoOAPayloadType.PROTO_OA_DEAL_LIST_RES: ProtoOADealListRes,
|
|
156
|
+
ProtoOAPayloadType.PROTO_OA_SUBSCRIBE_LIVE_TRENDBAR_REQ: ProtoOASubscribeLiveTrendbarReq,
|
|
157
|
+
ProtoOAPayloadType.PROTO_OA_UNSUBSCRIBE_LIVE_TRENDBAR_REQ: ProtoOAUnsubscribeLiveTrendbarReq,
|
|
158
|
+
ProtoOAPayloadType.PROTO_OA_GET_TRENDBARS_REQ: ProtoOAGetTrendbarsReq,
|
|
159
|
+
ProtoOAPayloadType.PROTO_OA_GET_TRENDBARS_RES: ProtoOAGetTrendbarsRes,
|
|
160
|
+
ProtoOAPayloadType.PROTO_OA_EXPECTED_MARGIN_REQ: ProtoOAExpectedMarginReq,
|
|
161
|
+
ProtoOAPayloadType.PROTO_OA_EXPECTED_MARGIN_RES: ProtoOAExpectedMarginRes,
|
|
162
|
+
ProtoOAPayloadType.PROTO_OA_MARGIN_CHANGED_EVENT: ProtoOAMarginChangedEvent,
|
|
163
|
+
ProtoOAPayloadType.PROTO_OA_ERROR_RES: ProtoOAErrorRes,
|
|
164
|
+
ProtoOAPayloadType.PROTO_OA_CASH_FLOW_HISTORY_LIST_REQ: ProtoOACashFlowHistoryListReq,
|
|
165
|
+
ProtoOAPayloadType.PROTO_OA_CASH_FLOW_HISTORY_LIST_RES: ProtoOACashFlowHistoryListRes,
|
|
166
|
+
ProtoOAPayloadType.PROTO_OA_GET_TICKDATA_REQ: ProtoOAGetTickDataReq,
|
|
167
|
+
ProtoOAPayloadType.PROTO_OA_GET_TICKDATA_RES: ProtoOAGetTickDataRes,
|
|
168
|
+
ProtoOAPayloadType.PROTO_OA_ACCOUNTS_TOKEN_INVALIDATED_EVENT: ProtoOAAccountsTokenInvalidatedEvent,
|
|
169
|
+
ProtoOAPayloadType.PROTO_OA_CLIENT_DISCONNECT_EVENT: ProtoOAClientDisconnectEvent,
|
|
170
|
+
ProtoOAPayloadType.PROTO_OA_GET_ACCOUNTS_BY_ACCESS_TOKEN_REQ: ProtoOAGetAccountListByAccessTokenReq,
|
|
171
|
+
ProtoOAPayloadType.PROTO_OA_GET_ACCOUNTS_BY_ACCESS_TOKEN_RES: ProtoOAGetAccountListByAccessTokenRes,
|
|
172
|
+
ProtoOAPayloadType.PROTO_OA_GET_CTID_PROFILE_BY_TOKEN_REQ: ProtoOAGetCtidProfileByTokenReq,
|
|
173
|
+
ProtoOAPayloadType.PROTO_OA_GET_CTID_PROFILE_BY_TOKEN_RES: ProtoOAGetCtidProfileByTokenRes,
|
|
174
|
+
ProtoOAPayloadType.PROTO_OA_ASSET_CLASS_LIST_REQ: ProtoOAAssetClassListReq,
|
|
175
|
+
ProtoOAPayloadType.PROTO_OA_ASSET_CLASS_LIST_RES: ProtoOAAssetClassListRes,
|
|
176
|
+
ProtoOAPayloadType.PROTO_OA_DEPTH_EVENT: ProtoOADepthEvent,
|
|
177
|
+
ProtoOAPayloadType.PROTO_OA_SUBSCRIBE_DEPTH_QUOTES_REQ: ProtoOASubscribeDepthQuotesReq,
|
|
178
|
+
ProtoOAPayloadType.PROTO_OA_SUBSCRIBE_DEPTH_QUOTES_RES: ProtoOASubscribeDepthQuotesRes,
|
|
179
|
+
ProtoOAPayloadType.PROTO_OA_UNSUBSCRIBE_DEPTH_QUOTES_REQ: ProtoOAUnsubscribeDepthQuotesReq,
|
|
180
|
+
ProtoOAPayloadType.PROTO_OA_UNSUBSCRIBE_DEPTH_QUOTES_RES: ProtoOAUnsubscribeDepthQuotesRes,
|
|
181
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOL_CATEGORY_REQ: ProtoOASymbolCategoryListReq,
|
|
182
|
+
ProtoOAPayloadType.PROTO_OA_SYMBOL_CATEGORY_RES: ProtoOASymbolCategoryListRes,
|
|
183
|
+
ProtoOAPayloadType.PROTO_OA_ACCOUNT_LOGOUT_REQ: ProtoOAAccountLogoutReq,
|
|
184
|
+
ProtoOAPayloadType.PROTO_OA_ACCOUNT_LOGOUT_RES: ProtoOAAccountLogoutRes,
|
|
185
|
+
ProtoOAPayloadType.PROTO_OA_ACCOUNT_DISCONNECT_EVENT: ProtoOAAccountDisconnectEvent,
|
|
186
|
+
ProtoOAPayloadType.PROTO_OA_SUBSCRIBE_LIVE_TRENDBAR_RES: ProtoOASubscribeLiveTrendbarRes,
|
|
187
|
+
ProtoOAPayloadType.PROTO_OA_UNSUBSCRIBE_LIVE_TRENDBAR_RES: ProtoOAUnsubscribeLiveTrendbarRes,
|
|
188
|
+
ProtoOAPayloadType.PROTO_OA_MARGIN_CALL_LIST_REQ: ProtoOAMarginCallListReq,
|
|
189
|
+
ProtoOAPayloadType.PROTO_OA_MARGIN_CALL_LIST_RES: ProtoOAMarginCallListRes,
|
|
190
|
+
ProtoOAPayloadType.PROTO_OA_MARGIN_CALL_UPDATE_REQ: ProtoOAMarginCallUpdateReq,
|
|
191
|
+
ProtoOAPayloadType.PROTO_OA_MARGIN_CALL_UPDATE_RES: ProtoOAMarginCallUpdateRes,
|
|
192
|
+
ProtoOAPayloadType.PROTO_OA_MARGIN_CALL_UPDATE_EVENT: ProtoOAMarginCallUpdateEvent,
|
|
193
|
+
ProtoOAPayloadType.PROTO_OA_MARGIN_CALL_TRIGGER_EVENT: ProtoOAMarginCallTriggerEvent,
|
|
194
|
+
ProtoOAPayloadType.PROTO_OA_REFRESH_TOKEN_REQ: ProtoOARefreshTokenReq,
|
|
195
|
+
ProtoOAPayloadType.PROTO_OA_REFRESH_TOKEN_RES: ProtoOARefreshTokenRes,
|
|
196
|
+
ProtoOAPayloadType.PROTO_OA_ORDER_LIST_REQ: ProtoOAOrderListReq,
|
|
197
|
+
ProtoOAPayloadType.PROTO_OA_ORDER_LIST_RES: ProtoOAOrderListRes,
|
|
198
|
+
ProtoOAPayloadType.PROTO_OA_GET_DYNAMIC_LEVERAGE_REQ: ProtoOAGetDynamicLeverageByIDReq,
|
|
199
|
+
ProtoOAPayloadType.PROTO_OA_GET_DYNAMIC_LEVERAGE_RES: ProtoOAGetDynamicLeverageByIDRes,
|
|
200
|
+
ProtoOAPayloadType.PROTO_OA_DEAL_LIST_BY_POSITION_ID_REQ: ProtoOADealListByPositionIdReq,
|
|
201
|
+
ProtoOAPayloadType.PROTO_OA_DEAL_LIST_BY_POSITION_ID_RES: ProtoOADealListByPositionIdRes,
|
|
202
|
+
ProtoOAPayloadType.PROTO_OA_ORDER_DETAILS_REQ: ProtoOAOrderDetailsReq,
|
|
203
|
+
ProtoOAPayloadType.PROTO_OA_ORDER_DETAILS_RES: ProtoOAOrderDetailsRes,
|
|
204
|
+
ProtoOAPayloadType.PROTO_OA_ORDER_LIST_BY_POSITION_ID_REQ: ProtoOAOrderListByPositionIdReq,
|
|
205
|
+
ProtoOAPayloadType.PROTO_OA_ORDER_LIST_BY_POSITION_ID_RES: ProtoOAOrderListByPositionIdRes,
|
|
206
|
+
ProtoOAPayloadType.PROTO_OA_DEAL_OFFSET_LIST_REQ: ProtoOADealOffsetListReq,
|
|
207
|
+
ProtoOAPayloadType.PROTO_OA_DEAL_OFFSET_LIST_RES: ProtoOADealOffsetListRes,
|
|
208
|
+
ProtoOAPayloadType.PROTO_OA_GET_POSITION_UNREALIZED_PNL_REQ: ProtoOAGetPositionUnrealizedPnLReq,
|
|
209
|
+
ProtoOAPayloadType.PROTO_OA_GET_POSITION_UNREALIZED_PNL_RES: ProtoOAGetPositionUnrealizedPnLRes,
|
|
210
|
+
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_EVENT: ProtoOAv1PnLChangeEvent,
|
|
211
|
+
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_REQ: ProtoOAv1PnLChangeSubscribeReq,
|
|
212
|
+
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_RES: ProtoOAv1PnLChangeSubscribeRes,
|
|
213
|
+
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_REQ: ProtoOAv1PnLChangeUnSubscribeReq,
|
|
214
|
+
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_RES: ProtoOAv1PnLChangeUnSubscribeRes,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass
|
|
219
|
+
class MessageRegistry:
|
|
220
|
+
"""Bidirectional mapping between payload_type and message class."""
|
|
221
|
+
|
|
222
|
+
payload_type_to_class: dict[int, type[betterproto.Message]] = field(default_factory=dict)
|
|
223
|
+
class_to_payload_type: dict[type[betterproto.Message], int] = field(default_factory=dict)
|
|
224
|
+
|
|
225
|
+
def get_class(self, payload_type: int) -> type[betterproto.Message] | None:
|
|
226
|
+
"""Get the message class for a payload type."""
|
|
227
|
+
return self.payload_type_to_class.get(payload_type)
|
|
228
|
+
|
|
229
|
+
def get_payload_type(self, cls: type[betterproto.Message]) -> int | None:
|
|
230
|
+
"""Get the payload type for a message class."""
|
|
231
|
+
return self.class_to_payload_type.get(cls)
|
|
232
|
+
|
|
233
|
+
def register(self, payload_type: int, cls: type[betterproto.Message]) -> None:
|
|
234
|
+
"""Register a bidirectional mapping."""
|
|
235
|
+
self.payload_type_to_class[payload_type] = cls
|
|
236
|
+
self.class_to_payload_type[cls] = payload_type
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class ClientMessageIdGenerator:
|
|
240
|
+
"""Thread-safe incrementing ID generator for request correlation."""
|
|
241
|
+
|
|
242
|
+
def __init__(self) -> None:
|
|
243
|
+
self._counter = 0
|
|
244
|
+
self._lock = threading.Lock()
|
|
245
|
+
|
|
246
|
+
def next_id(self) -> str:
|
|
247
|
+
"""Generate the next unique client message ID."""
|
|
248
|
+
with self._lock:
|
|
249
|
+
self._counter += 1
|
|
250
|
+
return str(self._counter)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Module-level singleton
|
|
254
|
+
_registry: MessageRegistry | None = None
|
|
255
|
+
_registry_lock = threading.Lock()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _build_registry() -> MessageRegistry:
|
|
259
|
+
"""Build the message registry from the explicit mapping."""
|
|
260
|
+
registry = MessageRegistry()
|
|
261
|
+
for payload_type, cls in _PAYLOAD_TYPE_TO_CLASS.items():
|
|
262
|
+
registry.register(payload_type, cls)
|
|
263
|
+
return registry
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def get_registry() -> MessageRegistry:
|
|
267
|
+
"""Get the lazily-initialized singleton registry."""
|
|
268
|
+
global _registry
|
|
269
|
+
if _registry is None:
|
|
270
|
+
with _registry_lock:
|
|
271
|
+
if _registry is None:
|
|
272
|
+
_registry = _build_registry()
|
|
273
|
+
return _registry
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def wrap_message(inner: betterproto.Message, client_msg_id: str | None = None) -> ProtoMessage:
|
|
277
|
+
"""Wrap an inner message into a ProtoMessage for transmission.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
inner: The inner protobuf message to wrap.
|
|
281
|
+
client_msg_id: Optional client message ID for request correlation.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
A ProtoMessage wrapper containing the serialized inner message.
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
UnknownPayloadTypeError: If the inner message type is not registered.
|
|
288
|
+
"""
|
|
289
|
+
registry = get_registry()
|
|
290
|
+
payload_type = registry.get_payload_type(type(inner))
|
|
291
|
+
|
|
292
|
+
if payload_type is None:
|
|
293
|
+
raise UnknownPayloadTypeError(payload_type=-1)
|
|
294
|
+
|
|
295
|
+
return ProtoMessage(
|
|
296
|
+
payload_type=payload_type,
|
|
297
|
+
payload=bytes(inner),
|
|
298
|
+
client_msg_id=client_msg_id or "",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def unwrap_message(proto_message: ProtoMessage) -> betterproto.Message:
|
|
303
|
+
"""Deserialize the inner payload from a ProtoMessage.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
proto_message: The ProtoMessage wrapper to unwrap.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
The deserialized inner message.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
UnknownPayloadTypeError: If the payload type is not registered.
|
|
313
|
+
DeserializationError: If deserialization fails.
|
|
314
|
+
"""
|
|
315
|
+
registry = get_registry()
|
|
316
|
+
cls = registry.get_class(proto_message.payload_type)
|
|
317
|
+
|
|
318
|
+
if cls is None:
|
|
319
|
+
raise UnknownPayloadTypeError(proto_message.payload_type)
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
return cls().parse(proto_message.payload)
|
|
323
|
+
except Exception as e:
|
|
324
|
+
raise DeserializationError(
|
|
325
|
+
payload_type=proto_message.payload_type,
|
|
326
|
+
raw_data=proto_message.payload,
|
|
327
|
+
) from e
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def deserialize_proto_message(data: bytes) -> ProtoMessage:
|
|
331
|
+
"""Deserialize raw bytes to a ProtoMessage wrapper.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
data: Raw bytes to deserialize.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
The deserialized ProtoMessage.
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
DeserializationError: If deserialization fails.
|
|
341
|
+
"""
|
|
342
|
+
try:
|
|
343
|
+
return ProtoMessage().parse(data)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
raise DeserializationError(
|
|
346
|
+
payload_type=-1,
|
|
347
|
+
raw_data=data,
|
|
348
|
+
) from e
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
2
|
+
# sources: OpenApiCommonMessages.proto
|
|
3
|
+
# plugin: python-betterproto
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import betterproto
|
|
7
|
+
|
|
8
|
+
from .OpenApiCommonModelMessages import ProtoPayloadType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ProtoMessage(betterproto.Message):
|
|
13
|
+
"""
|
|
14
|
+
* Base message that is used for all messages that are sent to/from Open API
|
|
15
|
+
proxy of cTrader platform.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
payload_type: int = betterproto.uint32_field(1)
|
|
19
|
+
payload: bytes = betterproto.bytes_field(2)
|
|
20
|
+
client_msg_id: str = betterproto.string_field(3)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ProtoErrorRes(betterproto.Message):
|
|
25
|
+
"""* Error response that is sent from Open API proxy when error occurs."""
|
|
26
|
+
|
|
27
|
+
payload_type: "ProtoPayloadType" = betterproto.enum_field(1)
|
|
28
|
+
error_code: str = betterproto.string_field(2)
|
|
29
|
+
description: str = betterproto.string_field(3)
|
|
30
|
+
maintenance_end_timestamp: int = betterproto.uint64_field(4)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ProtoHeartbeatEvent(betterproto.Message):
|
|
35
|
+
"""
|
|
36
|
+
* Event that is sent from Open API proxy and can be used as criteria that
|
|
37
|
+
connection is healthy when no other messages are sent by cTrader platform.
|
|
38
|
+
Open API client can send this message when he needs to keep the connection
|
|
39
|
+
open for a period without other messages longer than 30 seconds.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
payload_type: "ProtoPayloadType" = betterproto.enum_field(1)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
2
|
+
# sources: OpenApiCommonModelMessages.proto
|
|
3
|
+
# plugin: python-betterproto
|
|
4
|
+
|
|
5
|
+
import betterproto
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProtoPayloadType(betterproto.Enum):
|
|
9
|
+
"""--- INTENSIVE COMMANDS 1 - 49 --- COMMON API 50 - 69"""
|
|
10
|
+
|
|
11
|
+
# common intensive
|
|
12
|
+
PROTO_MESSAGE = 5
|
|
13
|
+
# common commands
|
|
14
|
+
ERROR_RES = 50
|
|
15
|
+
HEARTBEAT_EVENT = 51
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProtoErrorCode(betterproto.Enum):
|
|
19
|
+
"""COMMON error codes 1 - 99"""
|
|
20
|
+
|
|
21
|
+
UNKNOWN_ERROR = 1
|
|
22
|
+
UNSUPPORTED_MESSAGE = 2
|
|
23
|
+
INVALID_REQUEST = 3
|
|
24
|
+
TIMEOUT_ERROR = 5
|
|
25
|
+
ENTITY_NOT_FOUND = 6
|
|
26
|
+
CANT_ROUTE_REQUEST = 7
|
|
27
|
+
FRAME_TOO_LONG = 8
|
|
28
|
+
MARKET_CLOSED = 9
|
|
29
|
+
CONCURRENT_MODIFICATION = 10
|
|
30
|
+
BLOCKED_PAYLOAD_TYPE = 11
|