ctrader-api-client 0.3.0__tar.gz → 0.4.0__tar.gz
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-0.3.0 → ctrader_api_client-0.4.0}/PKG-INFO +1 -1
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/events.md +1 -1
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/market-data.md +3 -3
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/models.md +6 -6
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/index.md +1 -1
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/pyproject.toml +3 -1
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/api/trading.py +2 -2
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/client.py +59 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/events/router.py +5 -3
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/events/types.py +2 -2
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/account.py +11 -8
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/deal.py +25 -22
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/market_data.py +16 -14
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/order.py +19 -18
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/position.py +20 -19
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/requests.py +36 -34
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/symbol.py +8 -7
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/api/test_market_data_api.py +2 -1
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/events/test_emitter.py +17 -16
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/events/test_router.py +2 -2
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/events/test_types.py +7 -7
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/models/test_account.py +10 -9
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/models/test_deal.py +23 -22
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/models/test_market_data.py +21 -21
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/models/test_order.py +5 -4
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/models/test_position.py +8 -7
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/models/test_requests.py +20 -19
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/models/test_symbol.py +16 -15
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/uv.lock +1 -1
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/.claude/settings.local.json +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/.github/workflows/docs.yml +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/.gitignore +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/.pre-commit-config.yaml +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/.python-version +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/Justfile +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/LICENSE +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/README.md +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/accounts.md +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/client.md +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/enums.md +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/symbols.md +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/api/trading.md +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/docs/getting-started.md +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/mkdocs.yml +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/protos/SOURCE +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/protos/VERSION +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/protos/update.sh +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/protos/vendor/OpenApiCommonMessages.proto +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/protos/vendor/OpenApiMessages.proto +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/protos/vendor/OpenApiModelMessages.proto +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/scripts/fix_proto_imports.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/messages.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/proto/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/_internal/serialization.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/api/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/api/accounts.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/api/market_data.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/api/symbols.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/auth/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/auth/credentials.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/auth/manager.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/config.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/connection/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/connection/heartbeat.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/connection/protocol.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/connection/transport.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/enums.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/events/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/events/emitter.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/exceptions.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/__init__.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/_base.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/py.typed +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/_internal/test_messages.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/_internal/test_serialization.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/api/conftest.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/api/test_accounts.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/api/test_symbols.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/api/test_trading.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/auth/test_credentials.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/auth/test_manager.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/connection/test_heartbeat.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/connection/test_protocol.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/connection/test_transport.py +0 -0
- {ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/tests/unit/test_client.py +0 -0
|
@@ -52,7 +52,7 @@ await client.market_data.subscribe_trendbars(account_id, 270, TrendbarPeriod.M1)
|
|
|
52
52
|
|
|
53
53
|
@client.on(SpotEvent, symbol_id=270)
|
|
54
54
|
async def on_spot(event: SpotEvent):
|
|
55
|
-
# Prices are
|
|
55
|
+
# Prices are Decimals
|
|
56
56
|
print(f"Bid: {event.bid}, Ask: {event.ask}")
|
|
57
57
|
|
|
58
58
|
# Trendbar is included when subscribed
|
|
@@ -30,7 +30,7 @@ from ctrader_api_client.events import SpotEvent
|
|
|
30
30
|
await client.market_data.subscribe_spots(account_id, [270, 271])
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
# Handle price updates - bid/ask are
|
|
33
|
+
# Handle price updates - bid/ask are Decimals
|
|
34
34
|
@client.on(SpotEvent, symbol_id=270)
|
|
35
35
|
async def on_price(event: SpotEvent):
|
|
36
36
|
print(f"Bid: {event.bid}, Ask: {event.ask}")
|
|
@@ -86,7 +86,7 @@ trendbars = await client.market_data.get_trendbars(
|
|
|
86
86
|
to_timestamp=datetime.now(UTC),
|
|
87
87
|
)
|
|
88
88
|
|
|
89
|
-
# OHLC values are already
|
|
89
|
+
# OHLC values are already Decimals (converted from raw integers)
|
|
90
90
|
for bar in trendbars:
|
|
91
91
|
print(f"{bar.timestamp}: O={bar.open} H={bar.high} L={bar.low} C={bar.close} V={bar.volume}")
|
|
92
92
|
```
|
|
@@ -104,7 +104,7 @@ ticks = await client.market_data.get_tick_data(
|
|
|
104
104
|
quote_type="BID", # or "ASK"
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
# Price is already a
|
|
107
|
+
# Price is already a Decimal
|
|
108
108
|
for tick in ticks:
|
|
109
109
|
print(f"{tick.timestamp}: {tick.price}")
|
|
110
110
|
```
|
|
@@ -166,30 +166,30 @@ symbol = await client.symbols.get_by_id(account_id, 270)
|
|
|
166
166
|
volume = symbol.lots_to_volume(0.1) # Returns volume in cents
|
|
167
167
|
|
|
168
168
|
# Convert volume to lots for display
|
|
169
|
-
lots = symbol.volume_to_lots(position.volume) # Returns lots as
|
|
169
|
+
lots = symbol.volume_to_lots(position.volume) # Returns lots as Decimal
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
## Price Values
|
|
173
173
|
|
|
174
|
-
Prices in events and models (bid, ask, OHLC, execution prices, etc.) are returned as **
|
|
174
|
+
Prices in events and models (bid, ask, OHLC, execution prices, etc.) are returned as **Decimals** - no conversion needed:
|
|
175
175
|
|
|
176
176
|
```python
|
|
177
177
|
@client.on(SpotEvent)
|
|
178
178
|
async def on_price(event: SpotEvent):
|
|
179
|
-
# bid and ask are already
|
|
179
|
+
# bid and ask are already Decimals
|
|
180
180
|
spread = event.ask - event.bid
|
|
181
181
|
print(f"Spread: {spread}")
|
|
182
182
|
|
|
183
|
-
# Trendbar OHLC are
|
|
183
|
+
# Trendbar OHLC are Decimals
|
|
184
184
|
for bar in trendbars:
|
|
185
185
|
range_size = bar.high - bar.low
|
|
186
186
|
print(f"Range: {range_size}")
|
|
187
187
|
|
|
188
|
-
# Account balance is a
|
|
188
|
+
# Account balance is a Decimal
|
|
189
189
|
account = await client.accounts.get_trader(account_id)
|
|
190
190
|
print(f"Balance: {account.balance}")
|
|
191
191
|
|
|
192
|
-
# Position values are
|
|
192
|
+
# Position values are Decimals
|
|
193
193
|
for pos in positions:
|
|
194
194
|
print(f"Swap: {pos.swap}, Commission: {pos.commission}")
|
|
195
195
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ctrader-api-client"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "API Client to interact with the cTrader Open API spec"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -51,6 +51,8 @@ select = [
|
|
|
51
51
|
"C4", # comprehensions
|
|
52
52
|
"ARG", # Flags unused function arguments
|
|
53
53
|
"RUF022", # Unsorted __all__ blocks
|
|
54
|
+
"FURB157", # Check unnecessary casts in Decimal constructors
|
|
55
|
+
"RUF032", # Check for Decimal calls passing a float literal
|
|
54
56
|
]
|
|
55
57
|
|
|
56
58
|
[tool.ruff.lint.per-file-ignores]
|
|
@@ -194,8 +194,8 @@ class TradingAPI:
|
|
|
194
194
|
return [
|
|
195
195
|
PositionUnrealizedPnL(
|
|
196
196
|
position_id=p.position_id,
|
|
197
|
-
gross_unrealized_pnl=p.gross_unrealized_pn_l / divisor,
|
|
198
|
-
net_unrealized_pnl=p.net_unrealized_pn_l / divisor,
|
|
197
|
+
gross_unrealized_pnl=Decimal(p.gross_unrealized_pn_l) / divisor,
|
|
198
|
+
net_unrealized_pnl=Decimal(p.net_unrealized_pn_l) / divisor,
|
|
199
199
|
)
|
|
200
200
|
for p in response.position_unrealized_pn_l
|
|
201
201
|
]
|
|
@@ -454,6 +454,65 @@ class CTraderClient:
|
|
|
454
454
|
|
|
455
455
|
return decorator
|
|
456
456
|
|
|
457
|
+
@overload
|
|
458
|
+
def register_handler(
|
|
459
|
+
self,
|
|
460
|
+
event_type: type[T_BothFilters],
|
|
461
|
+
handler: EventHandler[T_BothFilters],
|
|
462
|
+
*,
|
|
463
|
+
account_id: int | None = ...,
|
|
464
|
+
symbol_id: int | None = ...,
|
|
465
|
+
) -> None: ...
|
|
466
|
+
|
|
467
|
+
@overload
|
|
468
|
+
def register_handler(
|
|
469
|
+
self,
|
|
470
|
+
event_type: type[T_AccountIdOnly],
|
|
471
|
+
handler: EventHandler[T_AccountIdOnly],
|
|
472
|
+
*,
|
|
473
|
+
account_id: int | None = ...,
|
|
474
|
+
) -> None: ...
|
|
475
|
+
|
|
476
|
+
@overload
|
|
477
|
+
def register_handler(
|
|
478
|
+
self,
|
|
479
|
+
event_type: type[T_NoFilters],
|
|
480
|
+
handler: EventHandler[T_NoFilters],
|
|
481
|
+
) -> None: ...
|
|
482
|
+
|
|
483
|
+
def register_handler(
|
|
484
|
+
self,
|
|
485
|
+
event_type: type[T],
|
|
486
|
+
handler: EventHandler[T],
|
|
487
|
+
*,
|
|
488
|
+
account_id: int | None = None,
|
|
489
|
+
symbol_id: int | None = None,
|
|
490
|
+
) -> None:
|
|
491
|
+
"""Register an event handler.
|
|
492
|
+
|
|
493
|
+
Same as the on() decorator but as a regular method for dynamic registration.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
event_type: The event class to listen for.
|
|
497
|
+
handler: The async function to call when the event arrives.
|
|
498
|
+
account_id: Only receive events for this account (optional).
|
|
499
|
+
symbol_id: Only receive events for this symbol (optional).
|
|
500
|
+
Example:
|
|
501
|
+
```python
|
|
502
|
+
async def on_eurusd(event: SpotEvent) -> None:
|
|
503
|
+
print(f"EURUSD: {event.bid}/{event.ask}")
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
client.register_handler(SpotEvent, on_eurusd, symbol_id=270)
|
|
507
|
+
```
|
|
508
|
+
"""
|
|
509
|
+
self._emitter.subscribe(
|
|
510
|
+
event_type,
|
|
511
|
+
handler,
|
|
512
|
+
account_id=account_id,
|
|
513
|
+
symbol_id=symbol_id,
|
|
514
|
+
)
|
|
515
|
+
|
|
457
516
|
def off(
|
|
458
517
|
self,
|
|
459
518
|
event_type: type[T],
|
{ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/events/router.py
RENAMED
|
@@ -183,10 +183,12 @@ class EventRouter:
|
|
|
183
183
|
event = SpotEvent(
|
|
184
184
|
account_id=proto.ctid_trader_account_id,
|
|
185
185
|
symbol_id=proto.symbol_id,
|
|
186
|
-
bid=proto.bid /
|
|
187
|
-
ask=proto.ask /
|
|
186
|
+
bid=proto.bid / Decimal(100000) if proto.bid else None,
|
|
187
|
+
ask=proto.ask / Decimal(100000) if proto.ask else None,
|
|
188
188
|
timestamp=self._timestamp_to_datetime(proto.timestamp) if proto.timestamp else datetime.now(UTC),
|
|
189
|
-
trendbar=
|
|
189
|
+
trendbar=(
|
|
190
|
+
Trendbar.from_proto(proto.trendbar[0], bid_price=Decimal(str(proto.bid))) if proto.trendbar else None
|
|
191
|
+
),
|
|
190
192
|
)
|
|
191
193
|
await self._emitter.emit(event)
|
|
192
194
|
|
{ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/account.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import UTC, datetime
|
|
4
|
+
from decimal import Decimal
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from .._internal.proto import ProtoOAAccessRights, ProtoOAAccountType
|
|
@@ -107,7 +108,7 @@ class Account(FrozenModel):
|
|
|
107
108
|
|
|
108
109
|
account_id: int
|
|
109
110
|
trader_login: int
|
|
110
|
-
balance:
|
|
111
|
+
balance: Decimal
|
|
111
112
|
leverage_in_cents: int
|
|
112
113
|
account_type: AccountType
|
|
113
114
|
access_rights: AccessRights
|
|
@@ -120,9 +121,9 @@ class Account(FrozenModel):
|
|
|
120
121
|
# Optional fields
|
|
121
122
|
max_leverage: int | None = None
|
|
122
123
|
balance_version: int | None = None
|
|
123
|
-
manager_bonus:
|
|
124
|
-
ib_bonus:
|
|
125
|
-
non_withdrawable_bonus:
|
|
124
|
+
manager_bonus: Decimal | None = None
|
|
125
|
+
ib_bonus: Decimal | None = None
|
|
126
|
+
non_withdrawable_bonus: Decimal | None = None
|
|
126
127
|
|
|
127
128
|
def get_leverage(self) -> str:
|
|
128
129
|
"""Get leverage as human-readable string.
|
|
@@ -148,7 +149,7 @@ class Account(FrozenModel):
|
|
|
148
149
|
return cls(
|
|
149
150
|
account_id=proto.ctid_trader_account_id,
|
|
150
151
|
trader_login=proto.trader_login,
|
|
151
|
-
balance=proto.balance / divisor,
|
|
152
|
+
balance=Decimal(proto.balance) / divisor,
|
|
152
153
|
leverage_in_cents=proto.leverage_in_cents,
|
|
153
154
|
account_type=_ACCOUNT_TYPE_MAP.get(proto.account_type, AccountType.HEDGED),
|
|
154
155
|
access_rights=_ACCESS_RIGHTS_MAP.get(proto.access_rights, AccessRights.FULL_ACCESS),
|
|
@@ -161,7 +162,9 @@ class Account(FrozenModel):
|
|
|
161
162
|
),
|
|
162
163
|
max_leverage=proto.max_leverage if proto.max_leverage else None,
|
|
163
164
|
balance_version=proto.balance_version if proto.balance_version else None,
|
|
164
|
-
manager_bonus=proto.manager_bonus / divisor if proto.manager_bonus else None,
|
|
165
|
-
ib_bonus=proto.ib_bonus / divisor if proto.ib_bonus else None,
|
|
166
|
-
non_withdrawable_bonus=proto.non_withdrawable_bonus / divisor
|
|
165
|
+
manager_bonus=Decimal(proto.manager_bonus) / divisor if proto.manager_bonus else None,
|
|
166
|
+
ib_bonus=Decimal(proto.ib_bonus) / divisor if proto.ib_bonus else None,
|
|
167
|
+
non_withdrawable_bonus=Decimal(proto.non_withdrawable_bonus) / divisor
|
|
168
|
+
if proto.non_withdrawable_bonus
|
|
169
|
+
else None,
|
|
167
170
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import UTC, datetime
|
|
4
|
+
from decimal import Decimal
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from .._internal.proto import ProtoOADealStatus
|
|
@@ -45,14 +46,14 @@ class CloseDetail(FrozenModel):
|
|
|
45
46
|
balance_version: Version number for balance updates.
|
|
46
47
|
"""
|
|
47
48
|
|
|
48
|
-
entry_price:
|
|
49
|
+
entry_price: Decimal
|
|
49
50
|
closed_volume: int
|
|
50
|
-
gross_profit:
|
|
51
|
-
swap:
|
|
52
|
-
commission:
|
|
53
|
-
balance:
|
|
54
|
-
pnl_conversion_fee:
|
|
55
|
-
quote_to_deposit_rate:
|
|
51
|
+
gross_profit: Decimal
|
|
52
|
+
swap: Decimal
|
|
53
|
+
commission: Decimal
|
|
54
|
+
balance: Decimal
|
|
55
|
+
pnl_conversion_fee: Decimal = Decimal(0)
|
|
56
|
+
quote_to_deposit_rate: Decimal | None = None
|
|
56
57
|
balance_version: int | None = None
|
|
57
58
|
|
|
58
59
|
@classmethod
|
|
@@ -68,14 +69,14 @@ class CloseDetail(FrozenModel):
|
|
|
68
69
|
money_digits = proto.money_digits if proto.money_digits else 2
|
|
69
70
|
divisor = 10**money_digits
|
|
70
71
|
return cls(
|
|
71
|
-
entry_price=proto.entry_price,
|
|
72
|
+
entry_price=Decimal(str(proto.entry_price)),
|
|
72
73
|
closed_volume=proto.closed_volume,
|
|
73
|
-
gross_profit=proto.gross_profit / divisor,
|
|
74
|
-
swap=proto.swap / divisor,
|
|
75
|
-
commission=proto.commission / divisor,
|
|
76
|
-
balance=proto.balance / divisor,
|
|
77
|
-
pnl_conversion_fee=proto.pnl_conversion_fee / divisor if proto.pnl_conversion_fee else 0,
|
|
78
|
-
quote_to_deposit_rate=proto.quote_to_deposit_conversion_rate
|
|
74
|
+
gross_profit=Decimal(proto.gross_profit) / divisor,
|
|
75
|
+
swap=Decimal(proto.swap) / divisor,
|
|
76
|
+
commission=Decimal(proto.commission) / divisor,
|
|
77
|
+
balance=Decimal(proto.balance) / divisor,
|
|
78
|
+
pnl_conversion_fee=Decimal(proto.pnl_conversion_fee) / divisor if proto.pnl_conversion_fee else Decimal(0),
|
|
79
|
+
quote_to_deposit_rate=Decimal(str(proto.quote_to_deposit_conversion_rate))
|
|
79
80
|
if proto.quote_to_deposit_conversion_rate
|
|
80
81
|
else None,
|
|
81
82
|
balance_version=proto.balance_version if proto.balance_version else None,
|
|
@@ -114,16 +115,16 @@ class Deal(FrozenModel):
|
|
|
114
115
|
side: OrderSide
|
|
115
116
|
volume: int
|
|
116
117
|
filled_volume: int
|
|
117
|
-
execution_price:
|
|
118
|
+
execution_price: Decimal
|
|
118
119
|
execution_timestamp: datetime
|
|
119
120
|
status: DealStatus
|
|
120
|
-
commission:
|
|
121
|
+
commission: Decimal
|
|
121
122
|
|
|
122
123
|
# Optional
|
|
123
124
|
create_timestamp: datetime | None = None
|
|
124
125
|
last_update_timestamp: datetime | None = None
|
|
125
|
-
margin_rate:
|
|
126
|
-
base_to_usd_rate:
|
|
126
|
+
margin_rate: Decimal | None = None
|
|
127
|
+
base_to_usd_rate: Decimal | None = None
|
|
127
128
|
close_detail: CloseDetail | None = None
|
|
128
129
|
|
|
129
130
|
@property
|
|
@@ -163,15 +164,17 @@ class Deal(FrozenModel):
|
|
|
163
164
|
side=side,
|
|
164
165
|
volume=proto.volume,
|
|
165
166
|
filled_volume=proto.filled_volume,
|
|
166
|
-
execution_price=proto.execution_price,
|
|
167
|
+
execution_price=Decimal(str(proto.execution_price)),
|
|
167
168
|
execution_timestamp=_timestamp_to_datetime(proto.execution_timestamp),
|
|
168
169
|
status=_DEAL_STATUS_MAP.get(proto.deal_status, DealStatus.FILLED),
|
|
169
|
-
commission=proto.commission / divisor if proto.commission else 0,
|
|
170
|
+
commission=Decimal(proto.commission) / divisor if proto.commission else Decimal(0),
|
|
170
171
|
create_timestamp=_timestamp_to_datetime(proto.create_timestamp) if proto.create_timestamp else None,
|
|
171
172
|
last_update_timestamp=(
|
|
172
173
|
_timestamp_to_datetime(proto.utc_last_update_timestamp) if proto.utc_last_update_timestamp else None
|
|
173
174
|
),
|
|
174
|
-
margin_rate=proto.margin_rate if proto.margin_rate else None,
|
|
175
|
-
base_to_usd_rate=
|
|
175
|
+
margin_rate=Decimal(str(proto.margin_rate)) if proto.margin_rate else None,
|
|
176
|
+
base_to_usd_rate=(
|
|
177
|
+
Decimal(str(proto.base_to_usd_conversion_rate)) if proto.base_to_usd_conversion_rate else None
|
|
178
|
+
),
|
|
176
179
|
close_detail=close_detail,
|
|
177
180
|
)
|
{ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/market_data.py
RENAMED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from datetime import UTC, datetime
|
|
5
|
+
from decimal import Decimal
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
6
7
|
|
|
7
8
|
from .._internal.proto import ProtoOATrendbarPeriod
|
|
@@ -49,14 +50,14 @@ class Trendbar(FrozenModel):
|
|
|
49
50
|
|
|
50
51
|
timestamp: datetime
|
|
51
52
|
period: TrendbarPeriod
|
|
52
|
-
open:
|
|
53
|
-
high:
|
|
54
|
-
low:
|
|
55
|
-
close:
|
|
53
|
+
open: Decimal
|
|
54
|
+
high: Decimal
|
|
55
|
+
low: Decimal
|
|
56
|
+
close: Decimal
|
|
56
57
|
volume: int
|
|
57
58
|
|
|
58
59
|
@classmethod
|
|
59
|
-
def from_proto(cls, proto: ProtoOATrendbar, bid_price:
|
|
60
|
+
def from_proto(cls, proto: ProtoOATrendbar, bid_price: Decimal | None = None, historical: bool = False) -> Trendbar:
|
|
60
61
|
"""Create a Trendbar from a proto message.
|
|
61
62
|
|
|
62
63
|
Args:
|
|
@@ -89,13 +90,14 @@ class Trendbar(FrozenModel):
|
|
|
89
90
|
)
|
|
90
91
|
close = low + proto.delta_close
|
|
91
92
|
|
|
93
|
+
divisor = Decimal(100000)
|
|
92
94
|
return cls(
|
|
93
95
|
timestamp=ts,
|
|
94
96
|
period=_PERIOD_MAP.get(proto.period, TrendbarPeriod.M1),
|
|
95
|
-
low=low /
|
|
96
|
-
open=open_price /
|
|
97
|
-
high=high /
|
|
98
|
-
close=close /
|
|
97
|
+
low=Decimal(low) / divisor,
|
|
98
|
+
open=Decimal(open_price) / divisor,
|
|
99
|
+
high=Decimal(high) / divisor,
|
|
100
|
+
close=Decimal(close) / divisor,
|
|
99
101
|
volume=proto.volume,
|
|
100
102
|
)
|
|
101
103
|
|
|
@@ -111,13 +113,13 @@ class TickData(FrozenModel):
|
|
|
111
113
|
"""
|
|
112
114
|
|
|
113
115
|
timestamp: datetime
|
|
114
|
-
price:
|
|
116
|
+
price: Decimal
|
|
115
117
|
|
|
116
118
|
@classmethod
|
|
117
119
|
def from_proto(cls, proto: ProtoOATickData) -> TickData:
|
|
118
120
|
"""Create TickData from proto message.
|
|
119
121
|
|
|
120
|
-
Note that price needs to be converted from a raw integer to a
|
|
122
|
+
Note that price needs to be converted from a raw integer to a Decimal by dividing by 100000.
|
|
121
123
|
|
|
122
124
|
Args:
|
|
123
125
|
proto: The proto message.
|
|
@@ -128,7 +130,7 @@ class TickData(FrozenModel):
|
|
|
128
130
|
# Proto timestamp is delta from base in milliseconds
|
|
129
131
|
return cls(
|
|
130
132
|
timestamp=datetime.fromtimestamp(proto.timestamp / 1000, tz=UTC),
|
|
131
|
-
price=proto.tick /
|
|
133
|
+
price=Decimal(proto.tick) / Decimal(100000),
|
|
132
134
|
)
|
|
133
135
|
|
|
134
136
|
@classmethod
|
|
@@ -139,7 +141,7 @@ class TickData(FrozenModel):
|
|
|
139
141
|
the first data point being an absolute value.
|
|
140
142
|
This method converts them to absolute values.
|
|
141
143
|
|
|
142
|
-
Additionally, price needs to be converted from a raw integer to a
|
|
144
|
+
Additionally, price needs to be converted from a raw integer to a Decimal by dividing by 100000.
|
|
143
145
|
|
|
144
146
|
Args:
|
|
145
147
|
protos: List of proto tick data messages.
|
|
@@ -161,7 +163,7 @@ class TickData(FrozenModel):
|
|
|
161
163
|
ticks.append(
|
|
162
164
|
cls(
|
|
163
165
|
timestamp=datetime.fromtimestamp(current_timestamp / 1000, tz=UTC),
|
|
164
|
-
price=current_price /
|
|
166
|
+
price=Decimal(current_price) / Decimal(100000),
|
|
165
167
|
)
|
|
166
168
|
)
|
|
167
169
|
|
{ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/order.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import UTC, datetime
|
|
4
|
+
from decimal import Decimal
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from .._internal.proto import ProtoOAOrderStatus, ProtoOAOrderTriggerMethod, ProtoOAOrderType, ProtoOATimeInForce
|
|
@@ -65,11 +66,11 @@ class Order(FrozenModel):
|
|
|
65
66
|
volume: Order volume in cents.
|
|
66
67
|
time_in_force: Order duration type.
|
|
67
68
|
open_timestamp: When the order was created.
|
|
68
|
-
limit_price: Limit price
|
|
69
|
-
stop_price: Stop trigger price
|
|
70
|
-
stop_loss: Stop loss price
|
|
71
|
-
take_profit: Take profit price
|
|
72
|
-
execution_price: Average fill price
|
|
69
|
+
limit_price: Limit price, or None.
|
|
70
|
+
stop_price: Stop trigger price, or None.
|
|
71
|
+
stop_loss: Stop loss price, or None.
|
|
72
|
+
take_profit: Take profit price, or None.
|
|
73
|
+
execution_price: Average fill price, or None.
|
|
73
74
|
executed_volume: Volume that has been filled, in cents.
|
|
74
75
|
expiration_timestamp: When the order expires, or None.
|
|
75
76
|
position_id: Associated position ID, or None.
|
|
@@ -97,12 +98,12 @@ class Order(FrozenModel):
|
|
|
97
98
|
time_in_force: TimeInForce
|
|
98
99
|
open_timestamp: datetime
|
|
99
100
|
|
|
100
|
-
# Prices
|
|
101
|
-
limit_price:
|
|
102
|
-
stop_price:
|
|
103
|
-
stop_loss:
|
|
104
|
-
take_profit:
|
|
105
|
-
execution_price:
|
|
101
|
+
# Prices
|
|
102
|
+
limit_price: Decimal | None = None
|
|
103
|
+
stop_price: Decimal | None = None
|
|
104
|
+
stop_loss: Decimal | None = None
|
|
105
|
+
take_profit: Decimal | None = None
|
|
106
|
+
execution_price: Decimal | None = None
|
|
106
107
|
|
|
107
108
|
# Execution
|
|
108
109
|
executed_volume: int = 0
|
|
@@ -110,7 +111,7 @@ class Order(FrozenModel):
|
|
|
110
111
|
position_id: int | None = None
|
|
111
112
|
|
|
112
113
|
# Slippage
|
|
113
|
-
base_slippage_price:
|
|
114
|
+
base_slippage_price: Decimal | None = None
|
|
114
115
|
slippage_in_points: int | None = None
|
|
115
116
|
|
|
116
117
|
# Relative SL/TP (in points)
|
|
@@ -171,17 +172,17 @@ class Order(FrozenModel):
|
|
|
171
172
|
volume=trade_data.volume if trade_data else 0,
|
|
172
173
|
time_in_force=_TIME_IN_FORCE_MAP.get(proto.time_in_force, TimeInForce.GOOD_TILL_CANCEL),
|
|
173
174
|
open_timestamp=open_ts,
|
|
174
|
-
limit_price=proto.limit_price if proto.limit_price else None,
|
|
175
|
-
stop_price=proto.stop_price if proto.stop_price else None,
|
|
176
|
-
stop_loss=proto.stop_loss if proto.stop_loss else None,
|
|
177
|
-
take_profit=proto.take_profit if proto.take_profit else None,
|
|
178
|
-
execution_price=proto.execution_price if proto.execution_price else None,
|
|
175
|
+
limit_price=Decimal(str(proto.limit_price)) if proto.limit_price else None,
|
|
176
|
+
stop_price=Decimal(str(proto.stop_price)) if proto.stop_price else None,
|
|
177
|
+
stop_loss=Decimal(str(proto.stop_loss)) if proto.stop_loss else None,
|
|
178
|
+
take_profit=Decimal(str(proto.take_profit)) if proto.take_profit else None,
|
|
179
|
+
execution_price=Decimal(str(proto.execution_price)) if proto.execution_price else None,
|
|
179
180
|
executed_volume=proto.executed_volume if proto.executed_volume else 0,
|
|
180
181
|
expiration_timestamp=(
|
|
181
182
|
_timestamp_to_datetime(proto.expiration_timestamp) if proto.expiration_timestamp else None
|
|
182
183
|
),
|
|
183
184
|
position_id=proto.position_id if proto.position_id else None,
|
|
184
|
-
base_slippage_price=proto.base_slippage_price if proto.base_slippage_price else None,
|
|
185
|
+
base_slippage_price=Decimal(str(proto.base_slippage_price)) if proto.base_slippage_price else None,
|
|
185
186
|
slippage_in_points=proto.slippage_in_points if proto.slippage_in_points else None,
|
|
186
187
|
relative_stop_loss=proto.relative_stop_loss if proto.relative_stop_loss else None,
|
|
187
188
|
relative_take_profit=proto.relative_take_profit if proto.relative_take_profit else None,
|
{ctrader_api_client-0.3.0 → ctrader_api_client-0.4.0}/src/ctrader_api_client/models/position.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import UTC, datetime
|
|
4
|
+
from decimal import Decimal
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from .._internal.proto import ProtoOAOrderTriggerMethod, ProtoOAPositionStatus
|
|
@@ -43,12 +44,12 @@ class Position(FrozenModel):
|
|
|
43
44
|
symbol_id: The symbol being traded.
|
|
44
45
|
side: Position direction (BUY/SELL).
|
|
45
46
|
volume: Position volume in cents.
|
|
46
|
-
entry_price: Entry price as
|
|
47
|
+
entry_price: Entry price as Decimal from API.
|
|
47
48
|
status: Current position status.
|
|
48
49
|
open_timestamp: When the position was opened.
|
|
49
50
|
money_digits: Decimal places for monetary values.
|
|
50
|
-
stop_loss: Stop loss price as
|
|
51
|
-
take_profit: Take profit price as
|
|
51
|
+
stop_loss: Stop loss price as Decimal, or None if not set.
|
|
52
|
+
take_profit: Take profit price as Decimal, or None if not set.
|
|
52
53
|
trailing_stop_loss: Whether trailing stop is enabled.
|
|
53
54
|
guaranteed_stop_loss: Whether guaranteed stop loss is enabled.
|
|
54
55
|
stop_loss_trigger_method: Method for triggering stop loss.
|
|
@@ -66,23 +67,23 @@ class Position(FrozenModel):
|
|
|
66
67
|
symbol_id: int
|
|
67
68
|
side: OrderSide
|
|
68
69
|
volume: int
|
|
69
|
-
entry_price:
|
|
70
|
+
entry_price: Decimal
|
|
70
71
|
status: PositionStatus
|
|
71
72
|
open_timestamp: datetime
|
|
72
73
|
money_digits: int
|
|
73
74
|
|
|
74
75
|
# Protection orders
|
|
75
|
-
stop_loss:
|
|
76
|
-
take_profit:
|
|
76
|
+
stop_loss: Decimal | None = None
|
|
77
|
+
take_profit: Decimal | None = None
|
|
77
78
|
trailing_stop_loss: bool = False
|
|
78
79
|
guaranteed_stop_loss: bool = False
|
|
79
80
|
stop_loss_trigger_method: StopTriggerMethod = StopTriggerMethod.TRADE
|
|
80
81
|
|
|
81
82
|
# Financial
|
|
82
|
-
swap:
|
|
83
|
-
commission:
|
|
84
|
-
used_margin:
|
|
85
|
-
margin_rate:
|
|
83
|
+
swap: Decimal = Decimal(0)
|
|
84
|
+
commission: Decimal = Decimal(0)
|
|
85
|
+
used_margin: Decimal = Decimal(0)
|
|
86
|
+
margin_rate: Decimal | None = None
|
|
86
87
|
|
|
87
88
|
# Metadata
|
|
88
89
|
label: str = ""
|
|
@@ -120,19 +121,19 @@ class Position(FrozenModel):
|
|
|
120
121
|
symbol_id=trade_data.symbol_id if trade_data else 0,
|
|
121
122
|
side=side,
|
|
122
123
|
volume=trade_data.volume if trade_data else 0,
|
|
123
|
-
entry_price=proto.price if proto.price else 0
|
|
124
|
+
entry_price=Decimal(str(proto.price)) if proto.price else Decimal(0),
|
|
124
125
|
status=_POSITION_STATUS_MAP.get(proto.position_status, PositionStatus.OPEN),
|
|
125
126
|
open_timestamp=open_ts,
|
|
126
127
|
money_digits=proto.money_digits if proto.money_digits else 2,
|
|
127
|
-
stop_loss=proto.stop_loss if proto.stop_loss else None,
|
|
128
|
-
take_profit=proto.take_profit if proto.take_profit else None,
|
|
128
|
+
stop_loss=Decimal(str(proto.stop_loss)) if proto.stop_loss else None,
|
|
129
|
+
take_profit=Decimal(str(proto.take_profit)) if proto.take_profit else None,
|
|
129
130
|
trailing_stop_loss=proto.trailing_stop_loss,
|
|
130
131
|
guaranteed_stop_loss=proto.guaranteed_stop_loss,
|
|
131
132
|
stop_loss_trigger_method=_TRIGGER_METHOD_MAP.get(proto.stop_loss_trigger_method, StopTriggerMethod.TRADE),
|
|
132
|
-
swap=proto.swap / divisor if proto.swap else 0,
|
|
133
|
-
commission=proto.commission / divisor if proto.commission else 0,
|
|
134
|
-
used_margin=proto.used_margin / divisor if proto.used_margin else 0,
|
|
135
|
-
margin_rate=proto.margin_rate if proto.margin_rate else None,
|
|
133
|
+
swap=Decimal(proto.swap) / divisor if proto.swap else Decimal(0),
|
|
134
|
+
commission=Decimal(proto.commission) / divisor if proto.commission else Decimal(0),
|
|
135
|
+
used_margin=Decimal(proto.used_margin) / divisor if proto.used_margin else Decimal(0),
|
|
136
|
+
margin_rate=Decimal(str(proto.margin_rate)) if proto.margin_rate else None,
|
|
136
137
|
label=trade_data.label if trade_data else "",
|
|
137
138
|
comment=trade_data.comment if trade_data else "",
|
|
138
139
|
last_update_timestamp=(
|
|
@@ -157,5 +158,5 @@ class PositionUnrealizedPnL(FrozenModel):
|
|
|
157
158
|
"""
|
|
158
159
|
|
|
159
160
|
position_id: int
|
|
160
|
-
gross_unrealized_pnl:
|
|
161
|
-
net_unrealized_pnl:
|
|
161
|
+
gross_unrealized_pnl: Decimal
|
|
162
|
+
net_unrealized_pnl: Decimal
|