ctrader-api-client 0.8.1__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.1 → ctrader_api_client-0.9.0}/PKG-INFO +5 -1
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/README.md +4 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/enums.md +10 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/models.md +60 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/symbols.md +29 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/pyproject.toml +1 -1
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/__init__.py +6 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/enums.py +29 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/__init__.py +2 -0
- ctrader_api_client-0.9.0/src/ctrader_api_client/models/commission.py +148 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/symbol.py +6 -4
- ctrader_api_client-0.9.0/tests/unit/test_commission_conversion.py +187 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/uv.lock +1 -1
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/.claude/settings.local.json +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/.github/workflows/docs.yml +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/.gitignore +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/.pre-commit-config.yaml +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/.python-version +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/Justfile +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/LICENSE +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/accounts.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/client.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/events.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/exceptions.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/market-data.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/api/trading.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/getting-started.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/docs/index.md +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/mkdocs.yml +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/SOURCE +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/VERSION +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/update.sh +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiCommonMessages.proto +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiMessages.proto +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiModelMessages.proto +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/scripts/fix_proto_imports.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/clock.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/conversions.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/messages.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/proto/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/serialization.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/_base.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/accounts.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/market_data.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/symbols.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/trading.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_recovery.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_refresh.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_session.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/credentials.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/manager.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/policy.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/store.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/client.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/composition.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/config.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/heartbeat.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/listener.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/protocol.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/supervisor.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/transport.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/_execution.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/emitter.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/publisher.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/router.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/types.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/exceptions.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/_base.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/account.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/deal.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/market_data.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/order.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/position.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/requests.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/trading_hours.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/py.typed +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/conftest.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/test_market_data.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/test_reference_data.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/test_subscription_restore.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/test_trading.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/auth/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/auth/conftest.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/auth/test_authentication.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/auth/test_token_lifecycle.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/conftest.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_event_dispatch.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_framing.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_heartbeat.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_reconnection.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_request_response.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/events/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/events/conftest.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/events/test_routing.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/events/test_subscriptions.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/test_client.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/conftest.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/clock.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/factories.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/recorder.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/server.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/signals.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/stub_protocol.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/harness/wire.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/__init__.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_api_errors.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_market_data_conversion.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_request_conversion.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_symbol_conversion.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_trading_hours.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_trading_records.py +0 -0
- {ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_wire_format.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
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
|
|
@@ -303,6 +303,10 @@ results = await client.symbols.search(account_id, "EUR")
|
|
|
303
303
|
# Get specific symbol
|
|
304
304
|
symbol = await client.symbols.get_by_id(account_id, symbol_id)
|
|
305
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
|
+
|
|
306
310
|
# Trading hours arrive with the symbol, so this costs no extra request
|
|
307
311
|
hours = symbol.trading_hours
|
|
308
312
|
if hours.is_open():
|
|
@@ -283,6 +283,10 @@ results = await client.symbols.search(account_id, "EUR")
|
|
|
283
283
|
# Get specific symbol
|
|
284
284
|
symbol = await client.symbols.get_by_id(account_id, symbol_id)
|
|
285
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
|
+
|
|
286
290
|
# Trading hours arrive with the symbol, so this costs no extra request
|
|
287
291
|
hours = symbol.trading_hours
|
|
288
292
|
if hours.is_open():
|
|
@@ -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,66 @@ 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
|
+
|
|
133
193
|
::: ctrader_api_client.models.TradingHours
|
|
134
194
|
options:
|
|
135
195
|
show_source: false
|
|
@@ -58,6 +58,35 @@ for sym in eur_pairs:
|
|
|
58
58
|
print(sym.name)
|
|
59
59
|
```
|
|
60
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
|
+
|
|
61
90
|
## Trading Hours
|
|
62
91
|
|
|
63
92
|
Every `Symbol` arrives with its trading calendar attached, so asking when a symbol is open
|
|
@@ -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,
|
|
@@ -108,6 +110,7 @@ from .models import (
|
|
|
108
110
|
AmendPositionRequest,
|
|
109
111
|
CloseDetail,
|
|
110
112
|
ClosePositionRequest,
|
|
113
|
+
Commission,
|
|
111
114
|
Deal,
|
|
112
115
|
Holiday,
|
|
113
116
|
NewOrderRequest,
|
|
@@ -150,6 +153,8 @@ __all__ = [
|
|
|
150
153
|
"ClientDisconnectEvent",
|
|
151
154
|
"CloseDetail",
|
|
152
155
|
"ClosePositionRequest",
|
|
156
|
+
"Commission",
|
|
157
|
+
"CommissionType",
|
|
153
158
|
"Deal",
|
|
154
159
|
"DealStatus",
|
|
155
160
|
"DepthEvent",
|
|
@@ -162,6 +167,7 @@ __all__ = [
|
|
|
162
167
|
"Holiday",
|
|
163
168
|
"MarginCallTriggerEvent",
|
|
164
169
|
"MarginChangeEvent",
|
|
170
|
+
"MinCommissionType",
|
|
165
171
|
"NewOrderRequest",
|
|
166
172
|
"Order",
|
|
167
173
|
"OrderErrorEvent",
|
|
@@ -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
|
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/__init__.py
RENAMED
|
@@ -24,6 +24,7 @@ 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
|
|
@@ -40,6 +41,7 @@ __all__ = [
|
|
|
40
41
|
"AmendPositionRequest",
|
|
41
42
|
"CloseDetail",
|
|
42
43
|
"ClosePositionRequest",
|
|
44
|
+
"Commission",
|
|
43
45
|
"Deal",
|
|
44
46
|
"Holiday",
|
|
45
47
|
"NewOrderRequest",
|
|
@@ -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
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/symbol.py
RENAMED
|
@@ -8,6 +8,7 @@ from pydantic import Field
|
|
|
8
8
|
from .._internal.proto import ProtoOATradingMode
|
|
9
9
|
from ..enums import TradingMode
|
|
10
10
|
from ._base import FrozenModel
|
|
11
|
+
from .commission import Commission
|
|
11
12
|
from .trading_hours import TradingHours
|
|
12
13
|
|
|
13
14
|
|
|
@@ -78,14 +79,15 @@ class Symbol(FrozenModel):
|
|
|
78
79
|
symbol_id: The unique symbol identifier.
|
|
79
80
|
digits: Price decimal places.
|
|
80
81
|
pip_position: Position of pip in price (e.g., 4 means 0.0001).
|
|
81
|
-
lot_size: Contract size in base
|
|
82
|
+
lot_size: Contract size in hundredths of a base unit. A forex lot of
|
|
83
|
+
100000 units reads as 10000000, and a 100 ounce gold lot as 10000.
|
|
82
84
|
min_volume: Minimum order volume in cents.
|
|
83
85
|
max_volume: Maximum order volume in cents.
|
|
84
86
|
step_volume: Volume step in cents.
|
|
85
87
|
trading_mode: Current trading mode.
|
|
86
88
|
swap_long: Swap rate for long positions.
|
|
87
89
|
swap_short: Swap rate for short positions.
|
|
88
|
-
commission:
|
|
90
|
+
commission: What the broker charges to trade the symbol.
|
|
89
91
|
max_exposure: Maximum allowed exposure.
|
|
90
92
|
leverage_id: ID of dynamic leverage profile, if any.
|
|
91
93
|
enable_short_selling: Whether short selling is allowed.
|
|
@@ -108,9 +110,9 @@ class Symbol(FrozenModel):
|
|
|
108
110
|
trading_mode: TradingMode
|
|
109
111
|
swap_long: Decimal
|
|
110
112
|
swap_short: Decimal
|
|
113
|
+
commission: Commission = Field(default_factory=Commission)
|
|
111
114
|
|
|
112
115
|
# Optional fields
|
|
113
|
-
commission: int = 0
|
|
114
116
|
max_exposure: int | None = None
|
|
115
117
|
leverage_id: int | None = None
|
|
116
118
|
enable_short_selling: bool = True
|
|
@@ -176,7 +178,7 @@ class Symbol(FrozenModel):
|
|
|
176
178
|
trading_mode=_TRADING_MODE_MAP.get(proto.trading_mode, TradingMode.ENABLED),
|
|
177
179
|
swap_long=Decimal(str(proto.swap_long)),
|
|
178
180
|
swap_short=Decimal(str(proto.swap_short)),
|
|
179
|
-
commission=
|
|
181
|
+
commission=Commission.from_proto(proto),
|
|
180
182
|
max_exposure=proto.max_exposure if proto.max_exposure else None,
|
|
181
183
|
leverage_id=proto.leverage_id if proto.leverage_id else None,
|
|
182
184
|
enable_short_selling=proto.enable_short_selling,
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""The translation of the commission a broker charges to trade a symbol."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from ctrader_api_client._internal.proto import (
|
|
10
|
+
ProtoOACommissionType,
|
|
11
|
+
ProtoOAMinCommissionType,
|
|
12
|
+
ProtoOASymbol,
|
|
13
|
+
)
|
|
14
|
+
from ctrader_api_client.enums import CommissionType, MinCommissionType
|
|
15
|
+
from ctrader_api_client.models import Symbol
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize(
|
|
19
|
+
("proto_type", "expected"),
|
|
20
|
+
[
|
|
21
|
+
(ProtoOACommissionType.USD_PER_MILLION_USD, CommissionType.USD_PER_MILLION_USD),
|
|
22
|
+
(ProtoOACommissionType.USD_PER_LOT, CommissionType.USD_PER_LOT),
|
|
23
|
+
(ProtoOACommissionType.PERCENTAGE_OF_VALUE, CommissionType.PERCENTAGE_OF_VALUE),
|
|
24
|
+
(ProtoOACommissionType.QUOTE_CCY_PER_LOT, CommissionType.QUOTE_CCY_PER_LOT),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_the_commission_type_is_translated(proto_type: ProtoOACommissionType, expected: CommissionType) -> None:
|
|
28
|
+
symbol = Symbol.from_proto(ProtoOASymbol(symbol_id=1, commission_type=proto_type))
|
|
29
|
+
|
|
30
|
+
assert symbol.commission.type == expected
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_a_commission_type_the_broker_omits_falls_back_to_the_server_default() -> None:
|
|
34
|
+
"""The wire format leaves an unset enum at 0, which no commission type uses."""
|
|
35
|
+
symbol = Symbol.from_proto(ProtoOASymbol(symbol_id=1))
|
|
36
|
+
|
|
37
|
+
assert symbol.commission.type == CommissionType.USD_PER_MILLION_USD
|
|
38
|
+
assert symbol.commission.minimum_type == MinCommissionType.CURRENCY
|
|
39
|
+
assert symbol.commission.minimum_asset == "USD"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_a_symbol_without_a_commission_reports_no_charge() -> None:
|
|
43
|
+
symbol = Symbol.from_proto(ProtoOASymbol(symbol_id=1))
|
|
44
|
+
|
|
45
|
+
assert symbol.commission.rate == Decimal(0)
|
|
46
|
+
assert symbol.commission.minimum == Decimal(0)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_a_rate_per_million_is_descaled() -> None:
|
|
50
|
+
symbol = Symbol.from_proto(
|
|
51
|
+
ProtoOASymbol(
|
|
52
|
+
symbol_id=270,
|
|
53
|
+
commission_type=ProtoOACommissionType.USD_PER_MILLION_USD,
|
|
54
|
+
precise_trading_commission_rate=3_500_000_000,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
assert symbol.commission.rate == Decimal(35)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_a_rate_per_lot_keeps_its_fractional_part() -> None:
|
|
62
|
+
"""The precise field exists to carry the decimals the deprecated one cannot."""
|
|
63
|
+
symbol = Symbol.from_proto(
|
|
64
|
+
ProtoOASymbol(
|
|
65
|
+
symbol_id=270,
|
|
66
|
+
commission_type=ProtoOACommissionType.USD_PER_LOT,
|
|
67
|
+
precise_trading_commission_rate=350_000_000,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
assert symbol.commission.rate == Decimal("3.5")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_a_percentage_rate_is_reported_as_a_percent() -> None:
|
|
75
|
+
"""A percentage rate is scaled by 10^5, not by the 10^8 the other types use."""
|
|
76
|
+
symbol = Symbol.from_proto(
|
|
77
|
+
ProtoOASymbol(
|
|
78
|
+
symbol_id=270,
|
|
79
|
+
commission_type=ProtoOACommissionType.PERCENTAGE_OF_VALUE,
|
|
80
|
+
precise_trading_commission_rate=500,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
assert symbol.commission.rate == Decimal("0.005")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_a_broker_that_sends_only_the_deprecated_rate_is_read() -> None:
|
|
88
|
+
"""A deprecated money rate counts hundredths, so 250 is 2.50 USD per lot."""
|
|
89
|
+
symbol = Symbol.from_proto(
|
|
90
|
+
ProtoOASymbol(
|
|
91
|
+
symbol_id=270,
|
|
92
|
+
commission_type=ProtoOACommissionType.USD_PER_LOT,
|
|
93
|
+
commission=250,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
assert symbol.commission.rate == Decimal("2.5")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_a_deprecated_percentage_rate_carries_the_percentage_scale() -> None:
|
|
101
|
+
symbol = Symbol.from_proto(
|
|
102
|
+
ProtoOASymbol(
|
|
103
|
+
symbol_id=270,
|
|
104
|
+
commission_type=ProtoOACommissionType.PERCENTAGE_OF_VALUE,
|
|
105
|
+
commission=500,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
assert symbol.commission.rate == Decimal("0.005")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_the_precise_rate_wins_over_the_deprecated_one() -> None:
|
|
113
|
+
"""Brokers send both, and only the precise field carries the smaller decimals."""
|
|
114
|
+
symbol = Symbol.from_proto(
|
|
115
|
+
ProtoOASymbol(
|
|
116
|
+
symbol_id=270,
|
|
117
|
+
commission_type=ProtoOACommissionType.USD_PER_LOT,
|
|
118
|
+
commission=355,
|
|
119
|
+
precise_trading_commission_rate=355_500_000,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert symbol.commission.rate == Decimal("3.555")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_the_two_rate_fields_agree_on_what_a_live_broker_sends() -> None:
|
|
127
|
+
"""A live broker publishes 2.50 USD per lot as 250 and as 250000000.
|
|
128
|
+
|
|
129
|
+
The two scales differ by 10^6, and both readings must give the same money.
|
|
130
|
+
"""
|
|
131
|
+
symbol = Symbol.from_proto(
|
|
132
|
+
ProtoOASymbol(
|
|
133
|
+
symbol_id=270,
|
|
134
|
+
commission_type=ProtoOACommissionType.USD_PER_LOT,
|
|
135
|
+
commission=250,
|
|
136
|
+
precise_trading_commission_rate=250_000_000,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
assert symbol.commission.rate == Decimal("2.5")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_a_live_percentage_rate_reads_as_a_fraction_of_a_percent() -> None:
|
|
144
|
+
"""A live broker publishes 0.0007% on gold as 70 in both rate fields."""
|
|
145
|
+
symbol = Symbol.from_proto(
|
|
146
|
+
ProtoOASymbol(
|
|
147
|
+
symbol_id=41,
|
|
148
|
+
commission_type=ProtoOACommissionType.PERCENTAGE_OF_VALUE,
|
|
149
|
+
commission=70,
|
|
150
|
+
precise_trading_commission_rate=70,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
assert symbol.commission.rate == Decimal("0.0007")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_the_minimum_commission_is_descaled() -> None:
|
|
158
|
+
symbol = Symbol.from_proto(ProtoOASymbol(symbol_id=270, precise_min_commission=250_000_000))
|
|
159
|
+
|
|
160
|
+
assert symbol.commission.minimum == Decimal("2.5")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_a_broker_that_sends_only_the_deprecated_minimum_is_read() -> None:
|
|
164
|
+
"""A minimum is money, so the deprecated field counts hundredths as the rate does."""
|
|
165
|
+
symbol = Symbol.from_proto(ProtoOASymbol(symbol_id=270, min_commission=250))
|
|
166
|
+
|
|
167
|
+
assert symbol.commission.minimum == Decimal("2.5")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_the_precise_minimum_wins_over_the_deprecated_one() -> None:
|
|
171
|
+
symbol = Symbol.from_proto(ProtoOASymbol(symbol_id=270, min_commission=200, precise_min_commission=250_000_000))
|
|
172
|
+
|
|
173
|
+
assert symbol.commission.minimum == Decimal("2.5")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_the_minimum_commission_keeps_the_currency_it_is_charged_in() -> None:
|
|
177
|
+
symbol = Symbol.from_proto(
|
|
178
|
+
ProtoOASymbol(
|
|
179
|
+
symbol_id=270,
|
|
180
|
+
precise_min_commission=250_000_000,
|
|
181
|
+
min_commission_type=ProtoOAMinCommissionType.QUOTE_CURRENCY,
|
|
182
|
+
min_commission_asset="EUR",
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
assert symbol.commission.minimum_type == MinCommissionType.QUOTE_CURRENCY
|
|
187
|
+
assert symbol.commission.minimum_asset == "EUR"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiCommonMessages.proto
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiCommonModelMessages.proto
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/protos/vendor/OpenApiModelMessages.proto
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/clock.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/_internal/messages.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/accounts.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/api/market_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_recovery.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_refresh.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/_session.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/credentials.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/auth/manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/heartbeat.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/listener.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/connection/transport.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/_execution.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/emitter.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/publisher.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/router.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/events/types.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/_base.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/account.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/market_data.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/order.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/position.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/requests.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/src/ctrader_api_client/models/trading_hours.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/test_market_data.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/api/test_reference_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/auth/test_authentication.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/auth/test_token_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_framing.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_heartbeat.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/connection/test_reconnection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/behavior/events/test_subscriptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.8.1 → ctrader_api_client-0.9.0}/tests/unit/test_market_data_conversion.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|