ctrader-api-client 0.8.0__tar.gz → 0.9.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.
Potentially problematic release.
This version of ctrader-api-client might be problematic. Click here for more details.
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/PKG-INFO +18 -2
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/README.md +15 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/enums.md +10 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/models.md +80 -0
- ctrader_api_client-0.9.0/docs/api/symbols.md +172 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/pyproject.toml +4 -1
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/__init__.py +16 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/enums.py +29 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/exceptions.py +22 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/__init__.py +7 -0
- ctrader_api_client-0.9.0/src/ctrader_api_client/models/commission.py +148 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/symbol.py +13 -4
- ctrader_api_client-0.9.0/src/ctrader_api_client/models/trading_hours.py +506 -0
- ctrader_api_client-0.9.0/tests/unit/test_commission_conversion.py +187 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/unit/test_symbol_conversion.py +19 -1
- ctrader_api_client-0.9.0/tests/unit/test_trading_hours.py +387 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/uv.lock +12 -1
- ctrader_api_client-0.8.0/docs/api/symbols.md +0 -85
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/.claude/settings.local.json +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/.github/workflows/docs.yml +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/.gitignore +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/.pre-commit-config.yaml +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/.python-version +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/Justfile +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/LICENSE +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/accounts.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/client.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/events.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/exceptions.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/market-data.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/api/trading.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/getting-started.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/docs/index.md +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/mkdocs.yml +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/protos/SOURCE +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/protos/VERSION +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/protos/update.sh +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiCommonMessages.proto +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiMessages.proto +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiModelMessages.proto +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/scripts/fix_proto_imports.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/clock.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/conversions.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/messages.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/serialization.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/_base.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/accounts.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/market_data.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/symbols.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/trading.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_recovery.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_refresh.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_session.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/credentials.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/manager.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/policy.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/store.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/client.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/composition.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/config.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/heartbeat.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/listener.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/protocol.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/supervisor.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/transport.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/_execution.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/emitter.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/publisher.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/router.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/types.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/_base.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/account.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/deal.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/market_data.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/order.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/position.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/requests.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/py.typed +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/api/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/api/conftest.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/api/test_market_data.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/api/test_reference_data.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/api/test_subscription_restore.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/api/test_trading.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/auth/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/auth/conftest.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/auth/test_authentication.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/auth/test_token_lifecycle.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/connection/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/connection/conftest.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_event_dispatch.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_framing.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_heartbeat.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_reconnection.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_request_response.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/events/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/events/conftest.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/events/test_routing.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/events/test_subscriptions.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/behavior/test_client.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/conftest.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/clock.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/factories.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/recorder.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/server.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/signals.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/stub_protocol.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/harness/wire.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/unit/__init__.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/unit/test_api_errors.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/unit/test_market_data_conversion.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/unit/test_request_conversion.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/unit/test_trading_records.py +0 -0
- {ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/tests/unit/test_wire_format.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: ctrader-api-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: API Client to interact with the cTrader Open API spec
|
|
5
5
|
Author-email: Elio <elioachukri@pm.me>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -15,6 +15,7 @@ Requires-Dist: anyio>=4.13.0
|
|
|
15
15
|
Requires-Dist: betterproto[compiler]>=1.2.5
|
|
16
16
|
Requires-Dist: pydantic>=2.12.5
|
|
17
17
|
Requires-Dist: tenacity>=9.1.4
|
|
18
|
+
Requires-Dist: tzdata>=2025.2
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
|
|
20
21
|
# cTrader API Client
|
|
@@ -301,6 +302,21 @@ results = await client.symbols.search(account_id, "EUR")
|
|
|
301
302
|
|
|
302
303
|
# Get specific symbol
|
|
303
304
|
symbol = await client.symbols.get_by_id(account_id, symbol_id)
|
|
305
|
+
|
|
306
|
+
# The commission rate comes with the type that says what the rate counts
|
|
307
|
+
commission = symbol.commission
|
|
308
|
+
print(f"{commission.rate} ({commission.type.value}), min {commission.minimum}")
|
|
309
|
+
|
|
310
|
+
# Trading hours arrive with the symbol, so this costs no extra request
|
|
311
|
+
hours = symbol.trading_hours
|
|
312
|
+
if hours.is_open():
|
|
313
|
+
print(f"Open, closes at {hours.next_close()}")
|
|
314
|
+
else:
|
|
315
|
+
print(f"Closed, opens at {hours.next_open()}")
|
|
316
|
+
|
|
317
|
+
# Sessions for a given day, with broker holidays already applied
|
|
318
|
+
for session in hours.sessions_on(date(2026, 6, 19)):
|
|
319
|
+
print(session.start, "->", session.end)
|
|
304
320
|
```
|
|
305
321
|
|
|
306
322
|
### Account Information
|
|
@@ -282,6 +282,21 @@ results = await client.symbols.search(account_id, "EUR")
|
|
|
282
282
|
|
|
283
283
|
# Get specific symbol
|
|
284
284
|
symbol = await client.symbols.get_by_id(account_id, symbol_id)
|
|
285
|
+
|
|
286
|
+
# The commission rate comes with the type that says what the rate counts
|
|
287
|
+
commission = symbol.commission
|
|
288
|
+
print(f"{commission.rate} ({commission.type.value}), min {commission.minimum}")
|
|
289
|
+
|
|
290
|
+
# Trading hours arrive with the symbol, so this costs no extra request
|
|
291
|
+
hours = symbol.trading_hours
|
|
292
|
+
if hours.is_open():
|
|
293
|
+
print(f"Open, closes at {hours.next_close()}")
|
|
294
|
+
else:
|
|
295
|
+
print(f"Closed, opens at {hours.next_open()}")
|
|
296
|
+
|
|
297
|
+
# Sessions for a given day, with broker holidays already applied
|
|
298
|
+
for session in hours.sessions_on(date(2026, 6, 19)):
|
|
299
|
+
print(session.start, "->", session.end)
|
|
285
300
|
```
|
|
286
301
|
|
|
287
302
|
### Account Information
|
|
@@ -69,6 +69,16 @@ from ctrader_api_client import ExecutionType, OrderSide, OrderType
|
|
|
69
69
|
show_source: false
|
|
70
70
|
members: true
|
|
71
71
|
|
|
72
|
+
::: ctrader_api_client.enums.CommissionType
|
|
73
|
+
options:
|
|
74
|
+
show_source: false
|
|
75
|
+
members: true
|
|
76
|
+
|
|
77
|
+
::: ctrader_api_client.enums.MinCommissionType
|
|
78
|
+
options:
|
|
79
|
+
show_source: false
|
|
80
|
+
members: true
|
|
81
|
+
|
|
72
82
|
## Market Data Enums
|
|
73
83
|
|
|
74
84
|
::: ctrader_api_client.enums.TrendbarPeriod
|
|
@@ -130,6 +130,86 @@ partial_close = ClosePositionRequest(
|
|
|
130
130
|
show_source: false
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
::: ctrader_api_client.models.Commission
|
|
134
|
+
options:
|
|
135
|
+
show_source: false
|
|
136
|
+
|
|
137
|
+
**Example:**
|
|
138
|
+
|
|
139
|
+
A broker states a commission as a rate and a type. The type says what the rate counts, so
|
|
140
|
+
you must read the two together. The rate alone is meaningless: `2.5` is 2.50 USD per lot
|
|
141
|
+
under one type and 2.5% of the traded value under another.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from decimal import Decimal
|
|
145
|
+
|
|
146
|
+
from ctrader_api_client import CommissionType, Symbol
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def commission_per_side(symbol: Symbol, lots: Decimal, price: Decimal) -> Decimal:
|
|
150
|
+
"""Commission for one side of a trade.
|
|
151
|
+
|
|
152
|
+
The result is in USD for the two USD types, and in the symbol's quote currency
|
|
153
|
+
for QUOTE_CCY_PER_LOT.
|
|
154
|
+
"""
|
|
155
|
+
# lot_size counts hundredths of a base unit, so 100 gives the contract size.
|
|
156
|
+
notional = lots * symbol.lot_size / 100 * price
|
|
157
|
+
|
|
158
|
+
match symbol.commission.type:
|
|
159
|
+
case CommissionType.USD_PER_MILLION_USD:
|
|
160
|
+
return symbol.commission.rate * notional / 1_000_000
|
|
161
|
+
case CommissionType.USD_PER_LOT | CommissionType.QUOTE_CCY_PER_LOT:
|
|
162
|
+
return symbol.commission.rate * lots
|
|
163
|
+
case CommissionType.PERCENTAGE_OF_VALUE:
|
|
164
|
+
return symbol.commission.rate / 100 * notional
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Symbol IDs belong to the broker's server, so look yours up with symbols.search().
|
|
168
|
+
gold = await client.symbols.get_by_id(account_id, 41)
|
|
169
|
+
|
|
170
|
+
# 0.0007 (PERCENTAGE_OF_VALUE), a lot of 100 ounces
|
|
171
|
+
print(gold.commission.rate, gold.commission.type.value)
|
|
172
|
+
|
|
173
|
+
# 3.07689900 USD for 1 lot at 4395.57, which the broker charges as 3.08
|
|
174
|
+
print(commission_per_side(gold, Decimal(1), Decimal("4395.57")))
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
These rates come from one live broker, and show how far the types differ:
|
|
178
|
+
|
|
179
|
+
| Symbol | Type | Rate | Charge per side, for one lot |
|
|
180
|
+
|--------|------|------|------------------------------|
|
|
181
|
+
| EURUSD | `USD_PER_LOT` | 2.5 | 2.50 USD, at any price |
|
|
182
|
+
| XAUUSD | `PERCENTAGE_OF_VALUE` | 0.0007 | 3.08 USD, at a price of 4395.57 |
|
|
183
|
+
| BTCUSD | `PERCENTAGE_OF_VALUE` | 0.0325 | 20.89 USD, at a price of 64286.15 |
|
|
184
|
+
|
|
185
|
+
The broker charges the commission per side, so a round turn costs twice as much.
|
|
186
|
+
|
|
187
|
+
`minimum` is the smallest commission the broker charges for one trade. Apply it only when
|
|
188
|
+
its currency matches your charge: `minimum_type` tells you whether `minimum` is in the
|
|
189
|
+
symbol's quote currency, or in the currency that `minimum_asset` names. A `minimum` of 0
|
|
190
|
+
means the broker sets no minimum.
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
::: ctrader_api_client.models.TradingHours
|
|
194
|
+
options:
|
|
195
|
+
show_source: false
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
::: ctrader_api_client.models.TradingSession
|
|
199
|
+
options:
|
|
200
|
+
show_source: false
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
::: ctrader_api_client.models.ScheduleInterval
|
|
204
|
+
options:
|
|
205
|
+
show_source: false
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
::: ctrader_api_client.models.Holiday
|
|
209
|
+
options:
|
|
210
|
+
show_source: false
|
|
211
|
+
|
|
212
|
+
|
|
133
213
|
::: ctrader_api_client.models.Account
|
|
134
214
|
options:
|
|
135
215
|
show_source: false
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Symbols API
|
|
2
|
+
|
|
3
|
+
Symbol information lookup and search operations.
|
|
4
|
+
|
|
5
|
+
Access via `client.symbols`.
|
|
6
|
+
|
|
7
|
+
## SymbolsAPI
|
|
8
|
+
|
|
9
|
+
::: ctrader_api_client.api.SymbolsAPI
|
|
10
|
+
options:
|
|
11
|
+
show_source: false
|
|
12
|
+
members:
|
|
13
|
+
- list_all
|
|
14
|
+
- get_by_ids
|
|
15
|
+
- get_by_id
|
|
16
|
+
- search
|
|
17
|
+
|
|
18
|
+
## Usage Examples
|
|
19
|
+
|
|
20
|
+
### List All Symbols
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
# Get lightweight info for all symbols
|
|
24
|
+
symbols = await client.symbols.list_all(account_id)
|
|
25
|
+
|
|
26
|
+
for sym in symbols:
|
|
27
|
+
print(f"{sym.symbol_id}: {sym.name}")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Get Symbol by ID
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# Get full symbol details
|
|
34
|
+
symbol = await client.symbols.get_by_id(account_id, 270)
|
|
35
|
+
|
|
36
|
+
print(f"Digits: {symbol.digits}")
|
|
37
|
+
print(f"Lot size: {symbol.lot_size}")
|
|
38
|
+
print(f"Min volume: {symbol.min_volume}")
|
|
39
|
+
print(f"Max volume: {symbol.max_volume}")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Get Multiple Symbols
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
symbols = await client.symbols.get_by_ids(account_id, [270, 271, 272])
|
|
46
|
+
|
|
47
|
+
for sym in symbols:
|
|
48
|
+
print(f"{sym.name}: {sym.digits} digits, lot_size={sym.lot_size}")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Search Symbols
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# Find all EUR pairs
|
|
55
|
+
eur_pairs = await client.symbols.search(account_id, "EUR")
|
|
56
|
+
|
|
57
|
+
for sym in eur_pairs:
|
|
58
|
+
print(sym.name)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Commission
|
|
62
|
+
|
|
63
|
+
A broker states a commission as a rate and a type. The type says what the rate counts, so
|
|
64
|
+
you must read the two together. The same rate of `35` is 35 USD per million USD of volume
|
|
65
|
+
for one symbol, and 35 USD per lot for another.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from ctrader_api_client import CommissionType
|
|
69
|
+
|
|
70
|
+
symbol = await client.symbols.get_by_id(account_id, 270)
|
|
71
|
+
commission = symbol.commission
|
|
72
|
+
|
|
73
|
+
print(f"{commission.rate} ({commission.type.value})")
|
|
74
|
+
print(f"Minimum: {commission.minimum} {commission.minimum_asset}")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Type | Meaning of the rate |
|
|
78
|
+
|------|---------------------|
|
|
79
|
+
| `USD_PER_MILLION_USD` | USD per million USD of traded volume. Usual for forex. |
|
|
80
|
+
| `USD_PER_LOT` | USD per lot. Usual for commodity and index CFDs and futures. |
|
|
81
|
+
| `PERCENTAGE_OF_VALUE` | Percent of the traded value. Usual for equities. |
|
|
82
|
+
| `QUOTE_CCY_PER_LOT` | The symbol's quote currency per lot. |
|
|
83
|
+
|
|
84
|
+
The broker charges the commission per side, so a round turn costs twice the rate. The
|
|
85
|
+
charge lands in USD for the first two types, and in the symbol's quote currency for
|
|
86
|
+
`QUOTE_CCY_PER_LOT`. Convert to your deposit currency yourself. See
|
|
87
|
+
[Models](models.md#ctrader_api_client.models.Commission) for the arithmetic of each type,
|
|
88
|
+
and for how to apply `minimum`.
|
|
89
|
+
|
|
90
|
+
## Trading Hours
|
|
91
|
+
|
|
92
|
+
Every `Symbol` arrives with its trading calendar attached, so asking when a symbol is open
|
|
93
|
+
costs no extra request:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from datetime import date
|
|
97
|
+
|
|
98
|
+
symbol = await client.symbols.get_by_id(account_id, 270)
|
|
99
|
+
hours = symbol.trading_hours
|
|
100
|
+
|
|
101
|
+
if hours.is_open():
|
|
102
|
+
print("open now, closes at", hours.next_close())
|
|
103
|
+
else:
|
|
104
|
+
print("closed, opens at", hours.next_open())
|
|
105
|
+
|
|
106
|
+
for session in hours.sessions_on(date(2026, 6, 19)):
|
|
107
|
+
print(session.start, "->", session.end, f"({session.duration})")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Sessions are reported in the symbol's own `schedule_timezone` and are never merged, so a
|
|
111
|
+
schedule with a nightly maintenance break reports one session either side of it.
|
|
112
|
+
|
|
113
|
+
### Holidays
|
|
114
|
+
|
|
115
|
+
Holidays are closed windows that cut into the weekly schedule rather than replacing a whole
|
|
116
|
+
day. A broker publishing an "early close" sends a window covering the rest of the day, which
|
|
117
|
+
brings the close forward:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# Regular Friday: 01:05 - 23:50. With a 20:00 early close holiday:
|
|
121
|
+
hours.sessions_on(date(2026, 6, 19)) # -> one session, 01:05 - 20:00
|
|
122
|
+
|
|
123
|
+
hours.holidays_on(date(2026, 6, 19)) # -> the holiday responsible
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A holiday carries **its own** timezone, which is not always the symbol's. A closure defined in
|
|
127
|
+
a timezone an hour behind the symbol's straddles two of the symbol's calendar days, and both
|
|
128
|
+
are shortened accordingly.
|
|
129
|
+
|
|
130
|
+
A holiday whose window is empty (`start_second >= end_second`, which brokers send as `0`/`0`)
|
|
131
|
+
closes the entire day.
|
|
132
|
+
|
|
133
|
+
### Trading mode is separate
|
|
134
|
+
|
|
135
|
+
`is_open()` answers the calendar question only. A symbol can be closed to trading by its
|
|
136
|
+
`trading_mode` while the schedule says it is open, so check both:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
tradable = hours.is_open() and symbol.trading_mode == TradingMode.ENABLED
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Staleness
|
|
143
|
+
|
|
144
|
+
Schedules arrive only with a symbol fetch, so a long-lived `Symbol` can drift. Subscribe to
|
|
145
|
+
`SymbolChangedEvent` and re-fetch when the server reports a change. The API exposes no
|
|
146
|
+
historical schedules — what you receive is the current configuration.
|
|
147
|
+
|
|
148
|
+
## Volume Conversion
|
|
149
|
+
|
|
150
|
+
The cTrader API uses volume in "cents" (smallest volume units). The relationship between lots and volume depends on the symbol's `lot_size`:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
symbol = await client.symbols.get_by_id(account_id, 270)
|
|
154
|
+
|
|
155
|
+
# Lots to volume (for placing orders)
|
|
156
|
+
volume = symbol.lots_to_volume(1.0) # e.g., 10000000 for standard forex
|
|
157
|
+
|
|
158
|
+
# Volume to lots (for display)
|
|
159
|
+
lots = symbol.volume_to_lots(10000000) # e.g., 1.0 for standard forex
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Note:** Different instruments have different lot sizes. Always use the symbol's methods for conversion:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# Standard forex (lot_size=100000)
|
|
166
|
+
forex_symbol = await client.symbols.get_by_id(account_id, 1) # EURUSD (lot_size=10000000)
|
|
167
|
+
forex_volume = forex_symbol.lots_to_volume(0.1) # 1000000
|
|
168
|
+
|
|
169
|
+
# Index CFD might have different lot_size
|
|
170
|
+
index_symbol = await client.symbols.get_by_id(account_id, 270) # US500 (lot_size=100)
|
|
171
|
+
index_volume = index_symbol.lots_to_volume(0.1) # 10
|
|
172
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ctrader-api-client"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.9.0"
|
|
4
4
|
description = "API Client to interact with the cTrader Open API spec"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -20,6 +20,9 @@ dependencies = [
|
|
|
20
20
|
"betterproto[compiler]>=1.2.5",
|
|
21
21
|
"pydantic>=2.12.5",
|
|
22
22
|
"tenacity>=9.1.4",
|
|
23
|
+
# Windows ships no system IANA database, which ZoneInfo needs to resolve the
|
|
24
|
+
# broker-supplied timezones that trading schedules are expressed in.
|
|
25
|
+
"tzdata>=2025.2",
|
|
23
26
|
]
|
|
24
27
|
|
|
25
28
|
[build-system]
|
|
@@ -50,8 +50,10 @@ from .enums import (
|
|
|
50
50
|
AccessRights,
|
|
51
51
|
AccountType,
|
|
52
52
|
AuthTrigger,
|
|
53
|
+
CommissionType,
|
|
53
54
|
DealStatus,
|
|
54
55
|
ExecutionType,
|
|
56
|
+
MinCommissionType,
|
|
55
57
|
OrderSide,
|
|
56
58
|
OrderStatus,
|
|
57
59
|
OrderType,
|
|
@@ -99,6 +101,7 @@ from .exceptions import (
|
|
|
99
101
|
TokenExpiredError,
|
|
100
102
|
TokenRefreshError,
|
|
101
103
|
UnknownPayloadTypeError,
|
|
104
|
+
UnknownTimezoneError,
|
|
102
105
|
)
|
|
103
106
|
from .models import (
|
|
104
107
|
Account,
|
|
@@ -107,14 +110,19 @@ from .models import (
|
|
|
107
110
|
AmendPositionRequest,
|
|
108
111
|
CloseDetail,
|
|
109
112
|
ClosePositionRequest,
|
|
113
|
+
Commission,
|
|
110
114
|
Deal,
|
|
115
|
+
Holiday,
|
|
111
116
|
NewOrderRequest,
|
|
112
117
|
Order,
|
|
113
118
|
Position,
|
|
114
119
|
PositionUnrealizedPnL,
|
|
120
|
+
ScheduleInterval,
|
|
115
121
|
Symbol,
|
|
116
122
|
SymbolInfo,
|
|
117
123
|
TickData,
|
|
124
|
+
TradingHours,
|
|
125
|
+
TradingSession,
|
|
118
126
|
Trendbar,
|
|
119
127
|
)
|
|
120
128
|
|
|
@@ -145,6 +153,8 @@ __all__ = [
|
|
|
145
153
|
"ClientDisconnectEvent",
|
|
146
154
|
"CloseDetail",
|
|
147
155
|
"ClosePositionRequest",
|
|
156
|
+
"Commission",
|
|
157
|
+
"CommissionType",
|
|
148
158
|
"Deal",
|
|
149
159
|
"DealStatus",
|
|
150
160
|
"DepthEvent",
|
|
@@ -154,8 +164,10 @@ __all__ = [
|
|
|
154
164
|
"ExecutionEvent",
|
|
155
165
|
"ExecutionType",
|
|
156
166
|
"FramingError",
|
|
167
|
+
"Holiday",
|
|
157
168
|
"MarginCallTriggerEvent",
|
|
158
169
|
"MarginChangeEvent",
|
|
170
|
+
"MinCommissionType",
|
|
159
171
|
"NewOrderRequest",
|
|
160
172
|
"Order",
|
|
161
173
|
"OrderErrorEvent",
|
|
@@ -170,6 +182,7 @@ __all__ = [
|
|
|
170
182
|
"ReauthPolicy",
|
|
171
183
|
"ReconnectedEvent",
|
|
172
184
|
"RefreshPolicy",
|
|
185
|
+
"ScheduleInterval",
|
|
173
186
|
"SpotEvent",
|
|
174
187
|
"StopTriggerMethod",
|
|
175
188
|
"SubscriptionRestoreFailedEvent",
|
|
@@ -184,9 +197,12 @@ __all__ = [
|
|
|
184
197
|
"TokenRefreshFailedEvent",
|
|
185
198
|
"TokenStore",
|
|
186
199
|
"TraderUpdateEvent",
|
|
200
|
+
"TradingHours",
|
|
187
201
|
"TradingMode",
|
|
202
|
+
"TradingSession",
|
|
188
203
|
"TrailingStopChangedEvent",
|
|
189
204
|
"Trendbar",
|
|
190
205
|
"TrendbarPeriod",
|
|
191
206
|
"UnknownPayloadTypeError",
|
|
207
|
+
"UnknownTimezoneError",
|
|
192
208
|
]
|
|
@@ -117,6 +117,35 @@ class TradingMode(Enum):
|
|
|
117
117
|
CLOSE_ONLY = "CLOSE_ONLY"
|
|
118
118
|
|
|
119
119
|
|
|
120
|
+
class CommissionType(Enum):
|
|
121
|
+
"""Unit a symbol's commission rate is expressed in.
|
|
122
|
+
|
|
123
|
+
The rate is a plain number. This type tells you what the number counts.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
USD_PER_MILLION_USD = "USD_PER_MILLION_USD"
|
|
127
|
+
"""USD per million USD of traded volume. Usual for forex."""
|
|
128
|
+
|
|
129
|
+
USD_PER_LOT = "USD_PER_LOT"
|
|
130
|
+
"""USD per lot. Usual for commodity and index CFDs and futures."""
|
|
131
|
+
|
|
132
|
+
PERCENTAGE_OF_VALUE = "PERCENTAGE_OF_VALUE"
|
|
133
|
+
"""Percent of the traded value. Usual for equities."""
|
|
134
|
+
|
|
135
|
+
QUOTE_CCY_PER_LOT = "QUOTE_CCY_PER_LOT"
|
|
136
|
+
"""The symbol's quote currency per lot."""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class MinCommissionType(Enum):
|
|
140
|
+
"""Currency a symbol's minimum commission is expressed in."""
|
|
141
|
+
|
|
142
|
+
CURRENCY = "CURRENCY"
|
|
143
|
+
"""The currency named by the minimum commission asset."""
|
|
144
|
+
|
|
145
|
+
QUOTE_CURRENCY = "QUOTE_CURRENCY"
|
|
146
|
+
"""The symbol's quote currency."""
|
|
147
|
+
|
|
148
|
+
|
|
120
149
|
class TrendbarPeriod(Enum):
|
|
121
150
|
"""Trendbar/candle period."""
|
|
122
151
|
|
|
@@ -255,6 +255,28 @@ class APIError(CTraderError):
|
|
|
255
255
|
)
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
# =============================================================================
|
|
259
|
+
# Data Errors
|
|
260
|
+
# =============================================================================
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class UnknownTimezoneError(CTraderError):
|
|
264
|
+
"""A timezone named by the server is not in the IANA database.
|
|
265
|
+
|
|
266
|
+
Trading schedules are broker-supplied wall clock time, so a timezone that
|
|
267
|
+
cannot be resolved leaves every session boundary meaningless. This should
|
|
268
|
+
never happen: if it does, the broker has published a symbol whose schedule
|
|
269
|
+
timezone is misconfigured, and the schedule cannot be interpreted at all.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(self, name: str) -> None:
|
|
273
|
+
self.name = name
|
|
274
|
+
super().__init__(
|
|
275
|
+
f"Unknown schedule timezone {name!r}. This should never happen; it means the broker "
|
|
276
|
+
f"published a symbol whose schedule timezone is not a valid IANA name."
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
258
280
|
# =============================================================================
|
|
259
281
|
# Protocol Errors
|
|
260
282
|
# =============================================================================
|
{ctrader_api_client-0.8.0 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/__init__.py
RENAMED
|
@@ -24,12 +24,14 @@ Example:
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
from .account import Account, AccountSummary
|
|
27
|
+
from .commission import Commission
|
|
27
28
|
from .deal import CloseDetail, Deal
|
|
28
29
|
from .market_data import TickData, Trendbar
|
|
29
30
|
from .order import Order
|
|
30
31
|
from .position import Position, PositionUnrealizedPnL
|
|
31
32
|
from .requests import AmendOrderRequest, AmendPositionRequest, ClosePositionRequest, NewOrderRequest
|
|
32
33
|
from .symbol import Symbol, SymbolInfo
|
|
34
|
+
from .trading_hours import Holiday, ScheduleInterval, TradingHours, TradingSession
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
__all__ = [
|
|
@@ -39,13 +41,18 @@ __all__ = [
|
|
|
39
41
|
"AmendPositionRequest",
|
|
40
42
|
"CloseDetail",
|
|
41
43
|
"ClosePositionRequest",
|
|
44
|
+
"Commission",
|
|
42
45
|
"Deal",
|
|
46
|
+
"Holiday",
|
|
43
47
|
"NewOrderRequest",
|
|
44
48
|
"Order",
|
|
45
49
|
"Position",
|
|
46
50
|
"PositionUnrealizedPnL",
|
|
51
|
+
"ScheduleInterval",
|
|
47
52
|
"Symbol",
|
|
48
53
|
"SymbolInfo",
|
|
49
54
|
"TickData",
|
|
55
|
+
"TradingHours",
|
|
56
|
+
"TradingSession",
|
|
50
57
|
"Trendbar",
|
|
51
58
|
]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""What a broker charges to trade a symbol.
|
|
2
|
+
|
|
3
|
+
The server sends a commission as a number and a type, and the type decides what
|
|
4
|
+
the number means. The same rate of ``35`` is 35 USD per million USD of volume for
|
|
5
|
+
one symbol and 35 USD per lot for another. A rate read without its type is
|
|
6
|
+
meaningless, so both travel together here.
|
|
7
|
+
|
|
8
|
+
The server sends each amount twice: once in a deprecated field, and once in a
|
|
9
|
+
scaled ``precise`` field that carries more decimal places. The precise field wins
|
|
10
|
+
where the broker sets it. Brokers that still publish only the deprecated fields
|
|
11
|
+
keep working.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from decimal import Decimal
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
from .._internal.proto import ProtoOACommissionType, ProtoOAMinCommissionType
|
|
20
|
+
from ..enums import CommissionType, MinCommissionType
|
|
21
|
+
from ._base import FrozenModel
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from .._internal.proto import ProtoOASymbol
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_COMMISSION_TYPE_MAP: dict[int, CommissionType] = {
|
|
29
|
+
ProtoOACommissionType.USD_PER_MILLION_USD: CommissionType.USD_PER_MILLION_USD,
|
|
30
|
+
ProtoOACommissionType.USD_PER_LOT: CommissionType.USD_PER_LOT,
|
|
31
|
+
ProtoOACommissionType.PERCENTAGE_OF_VALUE: CommissionType.PERCENTAGE_OF_VALUE,
|
|
32
|
+
ProtoOACommissionType.QUOTE_CCY_PER_LOT: CommissionType.QUOTE_CCY_PER_LOT,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_MIN_COMMISSION_TYPE_MAP: dict[int, MinCommissionType] = {
|
|
36
|
+
ProtoOAMinCommissionType.CURRENCY: MinCommissionType.CURRENCY,
|
|
37
|
+
ProtoOAMinCommissionType.QUOTE_CURRENCY: MinCommissionType.QUOTE_CURRENCY,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Every amount is an integer scaled by a fixed power of ten. A money amount is
|
|
41
|
+
# scaled by 10^8 in the precise fields and by 10^2 in the deprecated ones, which is
|
|
42
|
+
# why the two differ by 10^6 on the wire. A percentage rate keeps one scale of 10^5
|
|
43
|
+
# in both fields.
|
|
44
|
+
_PRECISE_SCALE = Decimal(10) ** 8
|
|
45
|
+
_DEPRECATED_SCALE = Decimal(10) ** 2
|
|
46
|
+
_PERCENTAGE_SCALE = Decimal(10) ** 5
|
|
47
|
+
|
|
48
|
+
# The wire format leaves an unset field at 0, and no commission enum has a member
|
|
49
|
+
# there, so 0 means "the broker said nothing" rather than a value.
|
|
50
|
+
_UNSET = 0
|
|
51
|
+
|
|
52
|
+
# What the server applies when the broker omits the field.
|
|
53
|
+
_DEFAULT_MIN_COMMISSION_ASSET = "USD"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Commission(FrozenModel):
|
|
57
|
+
"""The commission a broker charges to trade one symbol.
|
|
58
|
+
|
|
59
|
+
A commission is charged per side, so a round turn costs twice ``rate`` unless
|
|
60
|
+
the broker states otherwise.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
type: What ``rate`` counts. See CommissionType.
|
|
64
|
+
rate: The commission rate, in the unit its type names. A
|
|
65
|
+
PERCENTAGE_OF_VALUE rate is a percent, so ``Decimal("0.005")`` means
|
|
66
|
+
0.005% of the traded value.
|
|
67
|
+
minimum: The smallest commission the broker charges for one trade, in the
|
|
68
|
+
currency its type names. 0 means the broker sets no minimum.
|
|
69
|
+
minimum_type: The currency ``minimum`` is expressed in. See
|
|
70
|
+
MinCommissionType.
|
|
71
|
+
minimum_asset: The currency ``minimum`` is expressed in when
|
|
72
|
+
``minimum_type`` is CURRENCY.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
```python
|
|
76
|
+
from ctrader_api_client import CommissionType
|
|
77
|
+
|
|
78
|
+
symbol = await client.symbols.get_by_id(account_id, 270)
|
|
79
|
+
commission = symbol.commission
|
|
80
|
+
|
|
81
|
+
if commission.type is CommissionType.USD_PER_MILLION_USD:
|
|
82
|
+
charge = commission.rate * notional_usd / 1_000_000
|
|
83
|
+
print(f"{max(charge, commission.minimum)} USD per side")
|
|
84
|
+
```
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
type: CommissionType = CommissionType.USD_PER_MILLION_USD
|
|
88
|
+
rate: Decimal = Decimal(0)
|
|
89
|
+
minimum: Decimal = Decimal(0)
|
|
90
|
+
minimum_type: MinCommissionType = MinCommissionType.CURRENCY
|
|
91
|
+
minimum_asset: str = _DEFAULT_MIN_COMMISSION_ASSET
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def from_proto(cls, proto: ProtoOASymbol) -> Commission:
|
|
95
|
+
"""Create a Commission from a symbol proto message.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
proto: The proto message to read the commission fields from.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
A new Commission instance.
|
|
102
|
+
"""
|
|
103
|
+
commission_type = _COMMISSION_TYPE_MAP.get(proto.commission_type, CommissionType.USD_PER_MILLION_USD)
|
|
104
|
+
return cls(
|
|
105
|
+
type=commission_type,
|
|
106
|
+
rate=_rate(proto, commission_type),
|
|
107
|
+
minimum=_minimum(proto),
|
|
108
|
+
minimum_type=_MIN_COMMISSION_TYPE_MAP.get(proto.min_commission_type, MinCommissionType.CURRENCY),
|
|
109
|
+
minimum_asset=proto.min_commission_asset or _DEFAULT_MIN_COMMISSION_ASSET,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _rate(proto: ProtoOASymbol, commission_type: CommissionType) -> Decimal:
|
|
114
|
+
"""Descale the commission rate, preferring the precise field over the deprecated one.
|
|
115
|
+
|
|
116
|
+
A percentage rate is scaled by 10^5 in both fields. Every other rate is money,
|
|
117
|
+
so it is scaled by 10^8 in the precise field and by 10^2 in the deprecated one.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
proto: The proto message to read the rate from.
|
|
121
|
+
commission_type: The type the rate is expressed in.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The rate in the unit its type names.
|
|
125
|
+
"""
|
|
126
|
+
if commission_type is CommissionType.PERCENTAGE_OF_VALUE:
|
|
127
|
+
scaled = proto.precise_trading_commission_rate or proto.commission
|
|
128
|
+
return Decimal(scaled) / _PERCENTAGE_SCALE
|
|
129
|
+
if proto.precise_trading_commission_rate != _UNSET:
|
|
130
|
+
return Decimal(proto.precise_trading_commission_rate) / _PRECISE_SCALE
|
|
131
|
+
return Decimal(proto.commission) / _DEPRECATED_SCALE
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _minimum(proto: ProtoOASymbol) -> Decimal:
|
|
135
|
+
"""Descale the minimum commission, preferring the precise field over the deprecated one.
|
|
136
|
+
|
|
137
|
+
A minimum is always money, so it carries the money scales: 10^8 in the precise
|
|
138
|
+
field and 10^2 in the deprecated one.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
proto: The proto message to read the minimum from.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
The minimum commission per trade.
|
|
145
|
+
"""
|
|
146
|
+
if proto.precise_min_commission != _UNSET:
|
|
147
|
+
return Decimal(proto.precise_min_commission) / _PRECISE_SCALE
|
|
148
|
+
return Decimal(proto.min_commission) / _DEPRECATED_SCALE
|