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,299 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from ctrader_api_client.enums import OrderSide, OrderType, StopTriggerMethod, TimeInForce
|
|
6
|
+
|
|
7
|
+
from .._internal.proto import (
|
|
8
|
+
ProtoOAAmendOrderReq,
|
|
9
|
+
ProtoOAAmendPositionSLTPReq,
|
|
10
|
+
ProtoOAClosePositionReq,
|
|
11
|
+
ProtoOANewOrderReq,
|
|
12
|
+
ProtoOAOrderTriggerMethod,
|
|
13
|
+
ProtoOAOrderType,
|
|
14
|
+
ProtoOAPayloadType,
|
|
15
|
+
ProtoOATimeInForce,
|
|
16
|
+
ProtoOATradeSide,
|
|
17
|
+
)
|
|
18
|
+
from ._base import FrozenModel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Reverse mappings for to_proto conversions
|
|
22
|
+
|
|
23
|
+
_ORDER_TYPE_TO_PROTO: dict[OrderType, int] = {
|
|
24
|
+
OrderType.MARKET: ProtoOAOrderType.MARKET,
|
|
25
|
+
OrderType.LIMIT: ProtoOAOrderType.LIMIT,
|
|
26
|
+
OrderType.STOP: ProtoOAOrderType.STOP,
|
|
27
|
+
OrderType.STOP_LOSS_TAKE_PROFIT: ProtoOAOrderType.STOP_LOSS_TAKE_PROFIT,
|
|
28
|
+
OrderType.MARKET_RANGE: ProtoOAOrderType.MARKET_RANGE,
|
|
29
|
+
OrderType.STOP_LIMIT: ProtoOAOrderType.STOP_LIMIT,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_TIME_IN_FORCE_TO_PROTO: dict[TimeInForce, int] = {
|
|
33
|
+
TimeInForce.GOOD_TILL_DATE: ProtoOATimeInForce.GOOD_TILL_DATE,
|
|
34
|
+
TimeInForce.GOOD_TILL_CANCEL: ProtoOATimeInForce.GOOD_TILL_CANCEL,
|
|
35
|
+
TimeInForce.IMMEDIATE_OR_CANCEL: ProtoOATimeInForce.IMMEDIATE_OR_CANCEL,
|
|
36
|
+
TimeInForce.FILL_OR_KILL: ProtoOATimeInForce.FILL_OR_KILL,
|
|
37
|
+
TimeInForce.MARKET_ON_OPEN: ProtoOATimeInForce.MARKET_ON_OPEN,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_TRIGGER_TO_PROTO: dict[StopTriggerMethod, int] = {
|
|
41
|
+
StopTriggerMethod.TRADE: ProtoOAOrderTriggerMethod.TRADE,
|
|
42
|
+
StopTriggerMethod.OPPOSITE: ProtoOAOrderTriggerMethod.OPPOSITE,
|
|
43
|
+
StopTriggerMethod.DOUBLE_TRADE: ProtoOAOrderTriggerMethod.DOUBLE_TRADE,
|
|
44
|
+
StopTriggerMethod.DOUBLE_OPPOSITE: ProtoOAOrderTriggerMethod.DOUBLE_OPPOSITE,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NewOrderRequest(FrozenModel):
|
|
49
|
+
"""Request to place a new order.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
symbol_id: Symbol to trade.
|
|
53
|
+
side: Order direction (BUY/SELL).
|
|
54
|
+
volume: Volume in cents (100 = 0.01 lots).
|
|
55
|
+
order_type: Type of order.
|
|
56
|
+
limit_price: Limit price for LIMIT/STOP_LIMIT orders.
|
|
57
|
+
stop_price: Stop trigger price for STOP/STOP_LIMIT orders.
|
|
58
|
+
stop_loss: Stop loss price.
|
|
59
|
+
take_profit: Take profit price.
|
|
60
|
+
time_in_force: Order duration type.
|
|
61
|
+
expiration_timestamp: Expiration for GTD orders.
|
|
62
|
+
position_id: Position ID to close (for closing orders).
|
|
63
|
+
client_order_id: User-defined order ID.
|
|
64
|
+
label: User-defined label (max 100 chars).
|
|
65
|
+
comment: User-defined comment (max 256 chars).
|
|
66
|
+
base_slippage_price: Base price for slippage calculation.
|
|
67
|
+
slippage_in_points: Max allowed slippage.
|
|
68
|
+
trailing_stop_loss: Enable trailing stop loss.
|
|
69
|
+
guaranteed_stop_loss: Enable guaranteed stop loss.
|
|
70
|
+
relative_stop_loss: Stop loss distance in points.
|
|
71
|
+
relative_take_profit: Take profit distance in points.
|
|
72
|
+
stop_trigger_method: How to trigger stop orders.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
symbol_id: int
|
|
76
|
+
side: OrderSide
|
|
77
|
+
volume: int
|
|
78
|
+
order_type: OrderType = OrderType.MARKET
|
|
79
|
+
|
|
80
|
+
# Prices (as float for convenience)
|
|
81
|
+
limit_price: float | None = None
|
|
82
|
+
stop_price: float | None = None
|
|
83
|
+
stop_loss: float | None = None
|
|
84
|
+
take_profit: float | None = None
|
|
85
|
+
|
|
86
|
+
# Duration
|
|
87
|
+
time_in_force: TimeInForce = TimeInForce.GOOD_TILL_CANCEL
|
|
88
|
+
expiration_timestamp: datetime | None = None
|
|
89
|
+
|
|
90
|
+
# Position reference
|
|
91
|
+
position_id: int | None = None
|
|
92
|
+
|
|
93
|
+
# Metadata
|
|
94
|
+
client_order_id: str = ""
|
|
95
|
+
label: str = ""
|
|
96
|
+
comment: str = ""
|
|
97
|
+
|
|
98
|
+
# Slippage
|
|
99
|
+
base_slippage_price: float | None = None
|
|
100
|
+
slippage_in_points: int | None = None
|
|
101
|
+
|
|
102
|
+
# Relative SL/TP
|
|
103
|
+
relative_stop_loss: int | None = None
|
|
104
|
+
relative_take_profit: int | None = None
|
|
105
|
+
|
|
106
|
+
# Flags
|
|
107
|
+
trailing_stop_loss: bool = False
|
|
108
|
+
guaranteed_stop_loss: bool = False
|
|
109
|
+
stop_trigger_method: StopTriggerMethod | None = None
|
|
110
|
+
|
|
111
|
+
def to_proto(self, account_id: int) -> ProtoOANewOrderReq:
|
|
112
|
+
"""Convert to proto message.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
account_id: The trading account ID.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Proto request message.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
# Map enums to proto values
|
|
122
|
+
order_type_value = ProtoOAOrderType(_ORDER_TYPE_TO_PROTO[self.order_type])
|
|
123
|
+
trade_side_value = ProtoOATradeSide.BUY if self.side == OrderSide.BUY else ProtoOATradeSide.SELL
|
|
124
|
+
time_in_force_value = ProtoOATimeInForce(_TIME_IN_FORCE_TO_PROTO[self.time_in_force])
|
|
125
|
+
|
|
126
|
+
trigger_method = None
|
|
127
|
+
if self.stop_trigger_method:
|
|
128
|
+
trigger_method = ProtoOAOrderTriggerMethod(_TRIGGER_TO_PROTO[self.stop_trigger_method])
|
|
129
|
+
|
|
130
|
+
return ProtoOANewOrderReq(
|
|
131
|
+
payload_type=ProtoOAPayloadType.PROTO_OA_NEW_ORDER_REQ,
|
|
132
|
+
ctid_trader_account_id=account_id,
|
|
133
|
+
symbol_id=self.symbol_id,
|
|
134
|
+
order_type=order_type_value,
|
|
135
|
+
trade_side=trade_side_value,
|
|
136
|
+
volume=self.volume,
|
|
137
|
+
limit_price=self.limit_price or 0.0,
|
|
138
|
+
stop_price=self.stop_price or 0.0,
|
|
139
|
+
time_in_force=time_in_force_value,
|
|
140
|
+
expiration_timestamp=int(self.expiration_timestamp.timestamp() * 1000) if self.expiration_timestamp else 0,
|
|
141
|
+
stop_loss=self.stop_loss or 0.0,
|
|
142
|
+
take_profit=self.take_profit or 0.0,
|
|
143
|
+
comment=self.comment,
|
|
144
|
+
base_slippage_price=self.base_slippage_price or 0.0,
|
|
145
|
+
slippage_in_points=self.slippage_in_points or 0,
|
|
146
|
+
label=self.label,
|
|
147
|
+
position_id=self.position_id or 0,
|
|
148
|
+
client_order_id=self.client_order_id,
|
|
149
|
+
relative_stop_loss=self.relative_stop_loss or 0,
|
|
150
|
+
relative_take_profit=self.relative_take_profit or 0,
|
|
151
|
+
guaranteed_stop_loss=self.guaranteed_stop_loss,
|
|
152
|
+
trailing_stop_loss=self.trailing_stop_loss,
|
|
153
|
+
stop_trigger_method=trigger_method, # ty: ignore[invalid-argument-type]
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class AmendOrderRequest(FrozenModel):
|
|
158
|
+
"""Request to modify a pending order.
|
|
159
|
+
|
|
160
|
+
Only include fields you want to change.
|
|
161
|
+
|
|
162
|
+
Attributes:
|
|
163
|
+
order_id: The order to modify.
|
|
164
|
+
volume: New volume in cents.
|
|
165
|
+
limit_price: New limit price.
|
|
166
|
+
stop_price: New stop trigger price.
|
|
167
|
+
stop_loss: New stop loss price.
|
|
168
|
+
take_profit: New take profit price.
|
|
169
|
+
expiration_timestamp: New expiration time.
|
|
170
|
+
slippage_in_points: New max slippage.
|
|
171
|
+
trailing_stop_loss: Enable/disable trailing stop.
|
|
172
|
+
guaranteed_stop_loss: Enable/disable guaranteed stop.
|
|
173
|
+
relative_stop_loss: New relative stop loss in points.
|
|
174
|
+
relative_take_profit: New relative take profit in points.
|
|
175
|
+
stop_trigger_method: New trigger method.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
order_id: int
|
|
179
|
+
|
|
180
|
+
# Optional updates
|
|
181
|
+
volume: int | None = None
|
|
182
|
+
limit_price: float | None = None
|
|
183
|
+
stop_price: float | None = None
|
|
184
|
+
stop_loss: float | None = None
|
|
185
|
+
take_profit: float | None = None
|
|
186
|
+
expiration_timestamp: datetime | None = None
|
|
187
|
+
slippage_in_points: int | None = None
|
|
188
|
+
trailing_stop_loss: bool | None = None
|
|
189
|
+
guaranteed_stop_loss: bool | None = None
|
|
190
|
+
relative_stop_loss: int | None = None
|
|
191
|
+
relative_take_profit: int | None = None
|
|
192
|
+
stop_trigger_method: StopTriggerMethod | None = None
|
|
193
|
+
|
|
194
|
+
def to_proto(self, account_id: int) -> ProtoOAAmendOrderReq:
|
|
195
|
+
"""Convert to proto message.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
account_id: The trading account ID.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Proto request message.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
trigger_method = ProtoOAOrderTriggerMethod.TRADE
|
|
205
|
+
if self.stop_trigger_method:
|
|
206
|
+
trigger_method = ProtoOAOrderTriggerMethod(_TRIGGER_TO_PROTO[self.stop_trigger_method])
|
|
207
|
+
|
|
208
|
+
return ProtoOAAmendOrderReq(
|
|
209
|
+
payload_type=ProtoOAPayloadType.PROTO_OA_AMEND_ORDER_REQ,
|
|
210
|
+
ctid_trader_account_id=account_id,
|
|
211
|
+
order_id=self.order_id,
|
|
212
|
+
volume=self.volume or 0,
|
|
213
|
+
limit_price=self.limit_price or 0.0,
|
|
214
|
+
stop_price=self.stop_price or 0.0,
|
|
215
|
+
expiration_timestamp=int(self.expiration_timestamp.timestamp() * 1000) if self.expiration_timestamp else 0,
|
|
216
|
+
stop_loss=self.stop_loss or 0.0,
|
|
217
|
+
take_profit=self.take_profit or 0.0,
|
|
218
|
+
slippage_in_points=self.slippage_in_points or 0,
|
|
219
|
+
relative_stop_loss=self.relative_stop_loss or 0,
|
|
220
|
+
relative_take_profit=self.relative_take_profit or 0,
|
|
221
|
+
guaranteed_stop_loss=self.guaranteed_stop_loss if self.guaranteed_stop_loss is not None else False,
|
|
222
|
+
trailing_stop_loss=self.trailing_stop_loss if self.trailing_stop_loss is not None else False,
|
|
223
|
+
stop_trigger_method=trigger_method,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class AmendPositionRequest(FrozenModel):
|
|
228
|
+
"""Request to modify a position's stop loss and take profit.
|
|
229
|
+
|
|
230
|
+
Attributes:
|
|
231
|
+
position_id: The position to modify.
|
|
232
|
+
stop_loss: New stop loss price, or None to remove.
|
|
233
|
+
take_profit: New take profit price, or None to remove.
|
|
234
|
+
trailing_stop_loss: Enable/disable trailing stop.
|
|
235
|
+
guaranteed_stop_loss: Enable/disable guaranteed stop.
|
|
236
|
+
stop_loss_trigger_method: Trigger method for stop loss.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
position_id: int
|
|
240
|
+
stop_loss: float | None = None
|
|
241
|
+
take_profit: float | None = None
|
|
242
|
+
trailing_stop_loss: bool = False
|
|
243
|
+
guaranteed_stop_loss: bool = False
|
|
244
|
+
stop_loss_trigger_method: StopTriggerMethod | None = None
|
|
245
|
+
|
|
246
|
+
def to_proto(self, account_id: int) -> ProtoOAAmendPositionSLTPReq:
|
|
247
|
+
"""Convert to proto message.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
account_id: The trading account ID.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Proto request message.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
trigger_method = ProtoOAOrderTriggerMethod.TRADE
|
|
257
|
+
if self.stop_loss_trigger_method:
|
|
258
|
+
trigger_method = ProtoOAOrderTriggerMethod(_TRIGGER_TO_PROTO[self.stop_loss_trigger_method])
|
|
259
|
+
|
|
260
|
+
return ProtoOAAmendPositionSLTPReq(
|
|
261
|
+
payload_type=ProtoOAPayloadType.PROTO_OA_AMEND_POSITION_SLTP_REQ,
|
|
262
|
+
ctid_trader_account_id=account_id,
|
|
263
|
+
position_id=self.position_id,
|
|
264
|
+
stop_loss=self.stop_loss or 0.0,
|
|
265
|
+
take_profit=self.take_profit or 0.0,
|
|
266
|
+
guaranteed_stop_loss=self.guaranteed_stop_loss,
|
|
267
|
+
trailing_stop_loss=self.trailing_stop_loss,
|
|
268
|
+
stop_loss_trigger_method=trigger_method,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class ClosePositionRequest(FrozenModel):
|
|
273
|
+
"""Request to close a position.
|
|
274
|
+
|
|
275
|
+
Attributes:
|
|
276
|
+
position_id: The position to close.
|
|
277
|
+
volume: Volume to close in cents. Use position's full volume
|
|
278
|
+
for complete close, or partial volume for partial close.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
position_id: int
|
|
282
|
+
volume: int
|
|
283
|
+
|
|
284
|
+
def to_proto(self, account_id: int) -> ProtoOAClosePositionReq:
|
|
285
|
+
"""Convert to proto message.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
account_id: The trading account ID.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Proto request message.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
return ProtoOAClosePositionReq(
|
|
295
|
+
payload_type=ProtoOAPayloadType.PROTO_OA_CLOSE_POSITION_REQ,
|
|
296
|
+
ctid_trader_account_id=account_id,
|
|
297
|
+
position_id=self.position_id,
|
|
298
|
+
volume=self.volume,
|
|
299
|
+
)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .._internal.proto import ProtoOATradingMode
|
|
7
|
+
from ..enums import TradingMode
|
|
8
|
+
from ._base import FrozenModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .._internal.proto import ProtoOALightSymbol, ProtoOASymbol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_TRADING_MODE_MAP: dict[int, TradingMode] = {
|
|
16
|
+
ProtoOATradingMode.ENABLED: TradingMode.ENABLED,
|
|
17
|
+
ProtoOATradingMode.DISABLED_WITHOUT_PENDINGS_EXECUTION: TradingMode.DISABLED_WITHOUT_PENDINGS_EXECUTION,
|
|
18
|
+
ProtoOATradingMode.DISABLED_WITH_PENDINGS_EXECUTION: TradingMode.DISABLED_WITH_PENDINGS_EXECUTION,
|
|
19
|
+
ProtoOATradingMode.CLOSE_ONLY_MODE: TradingMode.CLOSE_ONLY,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SymbolInfo(FrozenModel):
|
|
24
|
+
"""Basic symbol information from symbol list.
|
|
25
|
+
|
|
26
|
+
This is the lightweight representation returned when listing symbols.
|
|
27
|
+
Use Symbol for full trading parameters.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
symbol_id: The unique symbol identifier.
|
|
31
|
+
name: Symbol name (e.g., "EURUSD").
|
|
32
|
+
enabled: Whether the symbol is enabled for trading.
|
|
33
|
+
base_asset_id: Asset ID of the base currency.
|
|
34
|
+
quote_asset_id: Asset ID of the quote currency.
|
|
35
|
+
category_id: Symbol category ID.
|
|
36
|
+
description: Human-readable description.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
symbol_id: int
|
|
40
|
+
name: str
|
|
41
|
+
enabled: bool
|
|
42
|
+
base_asset_id: int
|
|
43
|
+
quote_asset_id: int
|
|
44
|
+
category_id: int | None = None
|
|
45
|
+
description: str = ""
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_proto(cls, proto: ProtoOALightSymbol) -> SymbolInfo:
|
|
49
|
+
"""Create SymbolInfo from proto message.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
proto: The proto message to convert.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A new SymbolInfo instance.
|
|
56
|
+
"""
|
|
57
|
+
return cls(
|
|
58
|
+
symbol_id=proto.symbol_id,
|
|
59
|
+
name=proto.symbol_name,
|
|
60
|
+
enabled=proto.enabled,
|
|
61
|
+
base_asset_id=proto.base_asset_id,
|
|
62
|
+
quote_asset_id=proto.quote_asset_id,
|
|
63
|
+
category_id=proto.symbol_category_id if proto.symbol_category_id else None,
|
|
64
|
+
description=proto.description or "",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Symbol(FrozenModel):
|
|
69
|
+
"""Full symbol trading parameters.
|
|
70
|
+
|
|
71
|
+
Contains all information needed for trading calculations including
|
|
72
|
+
pip position, lot size, volume limits, and swap rates.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
symbol_id: The unique symbol identifier.
|
|
76
|
+
digits: Price decimal places.
|
|
77
|
+
pip_position: Position of pip in price (e.g., 4 means 0.0001).
|
|
78
|
+
lot_size: Contract size in base units (e.g., 100000 for forex).
|
|
79
|
+
min_volume: Minimum order volume in cents.
|
|
80
|
+
max_volume: Maximum order volume in cents.
|
|
81
|
+
step_volume: Volume step in cents.
|
|
82
|
+
trading_mode: Current trading mode.
|
|
83
|
+
swap_long: Swap rate for long positions.
|
|
84
|
+
swap_short: Swap rate for short positions.
|
|
85
|
+
commission: Commission rate.
|
|
86
|
+
max_exposure: Maximum allowed exposure.
|
|
87
|
+
leverage_id: ID of dynamic leverage profile, if any.
|
|
88
|
+
enable_short_selling: Whether short selling is allowed.
|
|
89
|
+
guaranteed_stop_loss: Whether guaranteed stop loss is available.
|
|
90
|
+
sl_distance: Minimum stop loss distance.
|
|
91
|
+
tp_distance: Minimum take profit distance.
|
|
92
|
+
schedule_timezone: Timezone for trading schedule.
|
|
93
|
+
measurement_units: Measurement units (e.g., "oz" for gold).
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
symbol_id: int
|
|
97
|
+
digits: int
|
|
98
|
+
pip_position: int
|
|
99
|
+
lot_size: int
|
|
100
|
+
min_volume: int
|
|
101
|
+
max_volume: int
|
|
102
|
+
step_volume: int
|
|
103
|
+
trading_mode: TradingMode
|
|
104
|
+
swap_long: float
|
|
105
|
+
swap_short: float
|
|
106
|
+
|
|
107
|
+
# Optional fields
|
|
108
|
+
commission: int = 0
|
|
109
|
+
max_exposure: int | None = None
|
|
110
|
+
leverage_id: int | None = None
|
|
111
|
+
enable_short_selling: bool = True
|
|
112
|
+
guaranteed_stop_loss: bool = False
|
|
113
|
+
sl_distance: int = 0
|
|
114
|
+
tp_distance: int = 0
|
|
115
|
+
schedule_timezone: str = ""
|
|
116
|
+
measurement_units: str = ""
|
|
117
|
+
|
|
118
|
+
def price_to_decimal(self, raw_price: int) -> Decimal:
|
|
119
|
+
"""Convert raw price integer to Decimal.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
raw_price: Raw price from API.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Price as Decimal with correct precision.
|
|
126
|
+
"""
|
|
127
|
+
return Decimal(raw_price) / Decimal(10**self.digits)
|
|
128
|
+
|
|
129
|
+
def decimal_to_price(self, price: Decimal) -> int:
|
|
130
|
+
"""Convert Decimal price to raw integer.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
price: Price as Decimal.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Raw price integer for API.
|
|
137
|
+
"""
|
|
138
|
+
return int(price * Decimal(10**self.digits))
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def volume_to_lots(volume_cents: int) -> Decimal:
|
|
142
|
+
"""Convert volume in cents to lots.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
volume_cents: Volume in cents (100 = 0.01 lots).
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Volume in lots.
|
|
149
|
+
"""
|
|
150
|
+
return Decimal(volume_cents) / Decimal(100)
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def lots_to_volume(lots: Decimal) -> int:
|
|
154
|
+
"""Convert lots to volume in cents.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
lots: Volume in lots.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Volume in cents for API.
|
|
161
|
+
"""
|
|
162
|
+
return int(lots * 100)
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def from_proto(cls, proto: ProtoOASymbol) -> Symbol:
|
|
166
|
+
"""Create Symbol from proto message.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
proto: The proto message to convert.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
A new Symbol instance.
|
|
173
|
+
"""
|
|
174
|
+
return cls(
|
|
175
|
+
symbol_id=proto.symbol_id,
|
|
176
|
+
digits=proto.digits,
|
|
177
|
+
pip_position=proto.pip_position,
|
|
178
|
+
lot_size=proto.lot_size,
|
|
179
|
+
min_volume=proto.min_volume,
|
|
180
|
+
max_volume=proto.max_volume,
|
|
181
|
+
step_volume=proto.step_volume,
|
|
182
|
+
trading_mode=_TRADING_MODE_MAP.get(proto.trading_mode, TradingMode.ENABLED),
|
|
183
|
+
swap_long=proto.swap_long,
|
|
184
|
+
swap_short=proto.swap_short,
|
|
185
|
+
commission=proto.commission if proto.commission else 0,
|
|
186
|
+
max_exposure=proto.max_exposure if proto.max_exposure else None,
|
|
187
|
+
leverage_id=proto.leverage_id if proto.leverage_id else None,
|
|
188
|
+
enable_short_selling=proto.enable_short_selling,
|
|
189
|
+
guaranteed_stop_loss=proto.guaranteed_stop_loss,
|
|
190
|
+
sl_distance=proto.sl_distance if proto.sl_distance else 0,
|
|
191
|
+
tp_distance=proto.tp_distance if proto.tp_distance else 0,
|
|
192
|
+
schedule_timezone=proto.schedule_time_zone or "",
|
|
193
|
+
measurement_units=proto.measurement_units or "",
|
|
194
|
+
)
|
|
File without changes
|