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.
Files changed (43) hide show
  1. ctrader_api_client/__init__.py +64 -0
  2. ctrader_api_client/_internal/__init__.py +26 -0
  3. ctrader_api_client/_internal/messages.py +348 -0
  4. ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +42 -0
  5. ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +30 -0
  6. ctrader_api_client/_internal/proto/OpenApiMessages.py +1112 -0
  7. ctrader_api_client/_internal/proto/OpenApiModelMessages.py +802 -0
  8. ctrader_api_client/_internal/proto/__init__.py +320 -0
  9. ctrader_api_client/_internal/serialization.py +84 -0
  10. ctrader_api_client/api/__init__.py +21 -0
  11. ctrader_api_client/api/accounts.py +71 -0
  12. ctrader_api_client/api/market_data.py +424 -0
  13. ctrader_api_client/api/symbols.py +171 -0
  14. ctrader_api_client/api/trading.py +506 -0
  15. ctrader_api_client/auth/__init__.py +14 -0
  16. ctrader_api_client/auth/credentials.py +72 -0
  17. ctrader_api_client/auth/manager.py +511 -0
  18. ctrader_api_client/client.py +475 -0
  19. ctrader_api_client/config.py +56 -0
  20. ctrader_api_client/connection/__init__.py +16 -0
  21. ctrader_api_client/connection/heartbeat.py +120 -0
  22. ctrader_api_client/connection/protocol.py +366 -0
  23. ctrader_api_client/connection/transport.py +123 -0
  24. ctrader_api_client/enums.py +138 -0
  25. ctrader_api_client/events/__init__.py +65 -0
  26. ctrader_api_client/events/emitter.py +254 -0
  27. ctrader_api_client/events/router.py +400 -0
  28. ctrader_api_client/events/types.py +340 -0
  29. ctrader_api_client/exceptions.py +231 -0
  30. ctrader_api_client/models/__init__.py +50 -0
  31. ctrader_api_client/models/_base.py +19 -0
  32. ctrader_api_client/models/account.py +177 -0
  33. ctrader_api_client/models/deal.py +242 -0
  34. ctrader_api_client/models/market_data.py +192 -0
  35. ctrader_api_client/models/order.py +262 -0
  36. ctrader_api_client/models/position.py +209 -0
  37. ctrader_api_client/models/requests.py +299 -0
  38. ctrader_api_client/models/symbol.py +194 -0
  39. ctrader_api_client/py.typed +0 -0
  40. ctrader_api_client-0.1.0.dist-info/METADATA +252 -0
  41. ctrader_api_client-0.1.0.dist-info/RECORD +43 -0
  42. ctrader_api_client-0.1.0.dist-info/WHEEL +4 -0
  43. 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