ctrader-api-client 0.7.3__tar.gz → 0.8.1__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.7.3 → ctrader_api_client-0.8.1}/PKG-INFO +20 -4
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/README.md +17 -2
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/exceptions.md +26 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/models.md +20 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/symbols.md +58 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/getting-started.md +11 -2
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/pyproject.toml +4 -1
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/__init__.py +12 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/composition.py +1 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/config.py +8 -2
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/protocol.py +61 -15
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/transport.py +30 -12
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/exceptions.py +55 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/__init__.py +5 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/symbol.py +7 -0
- ctrader_api_client-0.8.1/src/ctrader_api_client/models/trading_hours.py +506 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_reconnection.py +219 -1
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/unit/test_symbol_conversion.py +19 -1
- ctrader_api_client-0.8.1/tests/unit/test_trading_hours.py +387 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/uv.lock +12 -1
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/.claude/settings.local.json +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/.github/workflows/docs.yml +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/.gitignore +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/.pre-commit-config.yaml +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/.python-version +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/Justfile +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/LICENSE +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/accounts.md +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/client.md +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/enums.md +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/events.md +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/market-data.md +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/api/trading.md +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/docs/index.md +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/mkdocs.yml +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/protos/SOURCE +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/protos/VERSION +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/protos/update.sh +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiCommonMessages.proto +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiMessages.proto +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiModelMessages.proto +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/scripts/fix_proto_imports.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/clock.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/conversions.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/messages.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/serialization.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/_base.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/accounts.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/market_data.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/symbols.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/trading.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/_recovery.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/_refresh.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/_session.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/credentials.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/manager.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/policy.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/store.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/client.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/heartbeat.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/listener.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/supervisor.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/enums.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/_execution.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/emitter.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/publisher.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/router.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/types.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/_base.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/account.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/deal.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/market_data.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/order.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/position.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/requests.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/py.typed +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/api/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/api/conftest.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/api/test_market_data.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/api/test_reference_data.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/api/test_subscription_restore.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/api/test_trading.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/auth/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/auth/conftest.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/auth/test_authentication.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/auth/test_token_lifecycle.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/connection/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/connection/conftest.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_event_dispatch.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_framing.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_heartbeat.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_request_response.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/events/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/events/conftest.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/events/test_routing.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/events/test_subscriptions.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/behavior/test_client.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/conftest.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/clock.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/factories.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/recorder.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/server.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/signals.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/stub_protocol.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/harness/wire.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/unit/__init__.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/unit/test_api_errors.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/unit/test_market_data_conversion.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/unit/test_request_conversion.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/tests/unit/test_trading_records.py +0 -0
- {ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/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.8.1
|
|
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,17 @@ 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
|
+
# Trading hours arrive with the symbol, so this costs no extra request
|
|
307
|
+
hours = symbol.trading_hours
|
|
308
|
+
if hours.is_open():
|
|
309
|
+
print(f"Open, closes at {hours.next_close()}")
|
|
310
|
+
else:
|
|
311
|
+
print(f"Closed, opens at {hours.next_open()}")
|
|
312
|
+
|
|
313
|
+
# Sessions for a given day, with broker holidays already applied
|
|
314
|
+
for session in hours.sessions_on(date(2026, 6, 19)):
|
|
315
|
+
print(session.start, "->", session.end)
|
|
304
316
|
```
|
|
305
317
|
|
|
306
318
|
### Account Information
|
|
@@ -354,10 +366,14 @@ config = ClientConfig(
|
|
|
354
366
|
heartbeat_timeout=30.0, # Or 0 to disable server heartbeat checks
|
|
355
367
|
request_timeout=30.0,
|
|
356
368
|
|
|
357
|
-
# Reconnection
|
|
358
|
-
|
|
369
|
+
# Reconnection: None retries for as long as the client is open (the
|
|
370
|
+
# default), an integer caps the attempts, 0 disables reconnection. A
|
|
371
|
+
# finite budget that runs out raises CTraderReconnectAbandonedError out
|
|
372
|
+
# of the `async with client:` block.
|
|
373
|
+
reconnect_attempts=None,
|
|
359
374
|
reconnect_min_wait=1.0,
|
|
360
375
|
reconnect_max_wait=60.0,
|
|
376
|
+
connect_timeout=30.0,
|
|
361
377
|
|
|
362
378
|
# Token refresh: when to refresh access tokens and how hard to retry
|
|
363
379
|
refresh_policy=RefreshPolicy(
|
|
@@ -282,6 +282,17 @@ 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
|
+
# Trading hours arrive with the symbol, so this costs no extra request
|
|
287
|
+
hours = symbol.trading_hours
|
|
288
|
+
if hours.is_open():
|
|
289
|
+
print(f"Open, closes at {hours.next_close()}")
|
|
290
|
+
else:
|
|
291
|
+
print(f"Closed, opens at {hours.next_open()}")
|
|
292
|
+
|
|
293
|
+
# Sessions for a given day, with broker holidays already applied
|
|
294
|
+
for session in hours.sessions_on(date(2026, 6, 19)):
|
|
295
|
+
print(session.start, "->", session.end)
|
|
285
296
|
```
|
|
286
297
|
|
|
287
298
|
### Account Information
|
|
@@ -335,10 +346,14 @@ config = ClientConfig(
|
|
|
335
346
|
heartbeat_timeout=30.0, # Or 0 to disable server heartbeat checks
|
|
336
347
|
request_timeout=30.0,
|
|
337
348
|
|
|
338
|
-
# Reconnection
|
|
339
|
-
|
|
349
|
+
# Reconnection: None retries for as long as the client is open (the
|
|
350
|
+
# default), an integer caps the attempts, 0 disables reconnection. A
|
|
351
|
+
# finite budget that runs out raises CTraderReconnectAbandonedError out
|
|
352
|
+
# of the `async with client:` block.
|
|
353
|
+
reconnect_attempts=None,
|
|
340
354
|
reconnect_min_wait=1.0,
|
|
341
355
|
reconnect_max_wait=60.0,
|
|
356
|
+
connect_timeout=30.0,
|
|
342
357
|
|
|
343
358
|
# Token refresh: when to refresh access tokens and how hard to retry
|
|
344
359
|
refresh_policy=RefreshPolicy(
|
|
@@ -153,6 +153,32 @@ other handlers for that event.
|
|
|
153
153
|
options:
|
|
154
154
|
show_source: false
|
|
155
155
|
|
|
156
|
+
::: ctrader_api_client.CTraderReconnectAbandonedError
|
|
157
|
+
options:
|
|
158
|
+
show_source: false
|
|
159
|
+
|
|
160
|
+
This is the one connection exception that reaches you without your having called
|
|
161
|
+
anything: it is raised out of the `async with client:` block itself. Reconnection
|
|
162
|
+
is unbounded by default, so seeing it means either that `reconnect_attempts` was
|
|
163
|
+
set to a finite number and every one was spent, or that something failed which
|
|
164
|
+
the reconnection logic did not anticipate.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
while True:
|
|
168
|
+
try:
|
|
169
|
+
async with client:
|
|
170
|
+
await asyncio.Event().wait()
|
|
171
|
+
except CTraderReconnectAbandonedError:
|
|
172
|
+
logger.exception("Client abandoned its connection; starting a new one")
|
|
173
|
+
await asyncio.sleep(30)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Catching it is only worthwhile if you intend to build a fresh client, since the
|
|
177
|
+
one that raised it is finished. Letting it propagate and having a process
|
|
178
|
+
supervisor restart you is an equally good answer, and the reason it is raised
|
|
179
|
+
rather than logged: a client that has stopped reconnecting has no reader and no
|
|
180
|
+
heartbeat left to notice anything, so nothing short of a restart brings it back.
|
|
181
|
+
|
|
156
182
|
### Authentication
|
|
157
183
|
|
|
158
184
|
::: ctrader_api_client.AuthenticationError
|
|
@@ -130,6 +130,26 @@ partial_close = ClosePositionRequest(
|
|
|
130
130
|
show_source: false
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
::: ctrader_api_client.models.TradingHours
|
|
134
|
+
options:
|
|
135
|
+
show_source: false
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
::: ctrader_api_client.models.TradingSession
|
|
139
|
+
options:
|
|
140
|
+
show_source: false
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
::: ctrader_api_client.models.ScheduleInterval
|
|
144
|
+
options:
|
|
145
|
+
show_source: false
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
::: ctrader_api_client.models.Holiday
|
|
149
|
+
options:
|
|
150
|
+
show_source: false
|
|
151
|
+
|
|
152
|
+
|
|
133
153
|
::: ctrader_api_client.models.Account
|
|
134
154
|
options:
|
|
135
155
|
show_source: false
|
|
@@ -58,6 +58,64 @@ for sym in eur_pairs:
|
|
|
58
58
|
print(sym.name)
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
## Trading Hours
|
|
62
|
+
|
|
63
|
+
Every `Symbol` arrives with its trading calendar attached, so asking when a symbol is open
|
|
64
|
+
costs no extra request:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from datetime import date
|
|
68
|
+
|
|
69
|
+
symbol = await client.symbols.get_by_id(account_id, 270)
|
|
70
|
+
hours = symbol.trading_hours
|
|
71
|
+
|
|
72
|
+
if hours.is_open():
|
|
73
|
+
print("open now, closes at", hours.next_close())
|
|
74
|
+
else:
|
|
75
|
+
print("closed, opens at", hours.next_open())
|
|
76
|
+
|
|
77
|
+
for session in hours.sessions_on(date(2026, 6, 19)):
|
|
78
|
+
print(session.start, "->", session.end, f"({session.duration})")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Sessions are reported in the symbol's own `schedule_timezone` and are never merged, so a
|
|
82
|
+
schedule with a nightly maintenance break reports one session either side of it.
|
|
83
|
+
|
|
84
|
+
### Holidays
|
|
85
|
+
|
|
86
|
+
Holidays are closed windows that cut into the weekly schedule rather than replacing a whole
|
|
87
|
+
day. A broker publishing an "early close" sends a window covering the rest of the day, which
|
|
88
|
+
brings the close forward:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# Regular Friday: 01:05 - 23:50. With a 20:00 early close holiday:
|
|
92
|
+
hours.sessions_on(date(2026, 6, 19)) # -> one session, 01:05 - 20:00
|
|
93
|
+
|
|
94
|
+
hours.holidays_on(date(2026, 6, 19)) # -> the holiday responsible
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A holiday carries **its own** timezone, which is not always the symbol's. A closure defined in
|
|
98
|
+
a timezone an hour behind the symbol's straddles two of the symbol's calendar days, and both
|
|
99
|
+
are shortened accordingly.
|
|
100
|
+
|
|
101
|
+
A holiday whose window is empty (`start_second >= end_second`, which brokers send as `0`/`0`)
|
|
102
|
+
closes the entire day.
|
|
103
|
+
|
|
104
|
+
### Trading mode is separate
|
|
105
|
+
|
|
106
|
+
`is_open()` answers the calendar question only. A symbol can be closed to trading by its
|
|
107
|
+
`trading_mode` while the schedule says it is open, so check both:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
tradable = hours.is_open() and symbol.trading_mode == TradingMode.ENABLED
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Staleness
|
|
114
|
+
|
|
115
|
+
Schedules arrive only with a symbol fetch, so a long-lived `Symbol` can drift. Subscribe to
|
|
116
|
+
`SymbolChangedEvent` and re-fetch when the server reports a change. The API exposes no
|
|
117
|
+
historical schedules — what you receive is the current configuration.
|
|
118
|
+
|
|
61
119
|
## Volume Conversion
|
|
62
120
|
|
|
63
121
|
The cTrader API uses volume in "cents" (smallest volume units). The relationship between lots and volume depends on the symbol's `lot_size`:
|
|
@@ -312,13 +312,22 @@ config = ClientConfig(
|
|
|
312
312
|
heartbeat_timeout=0, # 0 to disable server heartbeat checks (default)
|
|
313
313
|
request_timeout=30.0,
|
|
314
314
|
|
|
315
|
-
# Reconnection
|
|
316
|
-
|
|
315
|
+
# Reconnection: None retries for as long as the client is open (the
|
|
316
|
+
# default), an integer caps the attempts, 0 disables reconnection.
|
|
317
|
+
reconnect_attempts=None,
|
|
317
318
|
reconnect_min_wait=1.0,
|
|
318
319
|
reconnect_max_wait=60.0,
|
|
320
|
+
connect_timeout=30.0,
|
|
319
321
|
)
|
|
320
322
|
```
|
|
321
323
|
|
|
324
|
+
Reconnection is unbounded by default because a finite budget that runs out
|
|
325
|
+
leaves a client that can never recover: there is no reader and no heartbeat left
|
|
326
|
+
to notice anything, so it stays offline until the process restarts. If you do
|
|
327
|
+
set a finite `reconnect_attempts`, spending it raises
|
|
328
|
+
`CTraderReconnectAbandonedError` out of the `async with client:` block rather
|
|
329
|
+
than leaving the client alive and permanently disconnected.
|
|
330
|
+
|
|
322
331
|
## Next Steps
|
|
323
332
|
|
|
324
333
|
- [API Reference - Client](api/client.md) - Full client documentation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ctrader-api-client"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.1"
|
|
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]
|
|
@@ -92,12 +92,14 @@ from .exceptions import (
|
|
|
92
92
|
CTraderConnectionFailedError,
|
|
93
93
|
CTraderConnectionTimeoutError,
|
|
94
94
|
CTraderError,
|
|
95
|
+
CTraderReconnectAbandonedError,
|
|
95
96
|
DeserializationError,
|
|
96
97
|
FramingError,
|
|
97
98
|
ProtocolError,
|
|
98
99
|
TokenExpiredError,
|
|
99
100
|
TokenRefreshError,
|
|
100
101
|
UnknownPayloadTypeError,
|
|
102
|
+
UnknownTimezoneError,
|
|
101
103
|
)
|
|
102
104
|
from .models import (
|
|
103
105
|
Account,
|
|
@@ -107,13 +109,17 @@ from .models import (
|
|
|
107
109
|
CloseDetail,
|
|
108
110
|
ClosePositionRequest,
|
|
109
111
|
Deal,
|
|
112
|
+
Holiday,
|
|
110
113
|
NewOrderRequest,
|
|
111
114
|
Order,
|
|
112
115
|
Position,
|
|
113
116
|
PositionUnrealizedPnL,
|
|
117
|
+
ScheduleInterval,
|
|
114
118
|
Symbol,
|
|
115
119
|
SymbolInfo,
|
|
116
120
|
TickData,
|
|
121
|
+
TradingHours,
|
|
122
|
+
TradingSession,
|
|
117
123
|
Trendbar,
|
|
118
124
|
)
|
|
119
125
|
|
|
@@ -139,6 +145,7 @@ __all__ = [
|
|
|
139
145
|
"CTraderConnectionFailedError",
|
|
140
146
|
"CTraderConnectionTimeoutError",
|
|
141
147
|
"CTraderError",
|
|
148
|
+
"CTraderReconnectAbandonedError",
|
|
142
149
|
"ClientConfig",
|
|
143
150
|
"ClientDisconnectEvent",
|
|
144
151
|
"CloseDetail",
|
|
@@ -152,6 +159,7 @@ __all__ = [
|
|
|
152
159
|
"ExecutionEvent",
|
|
153
160
|
"ExecutionType",
|
|
154
161
|
"FramingError",
|
|
162
|
+
"Holiday",
|
|
155
163
|
"MarginCallTriggerEvent",
|
|
156
164
|
"MarginChangeEvent",
|
|
157
165
|
"NewOrderRequest",
|
|
@@ -168,6 +176,7 @@ __all__ = [
|
|
|
168
176
|
"ReauthPolicy",
|
|
169
177
|
"ReconnectedEvent",
|
|
170
178
|
"RefreshPolicy",
|
|
179
|
+
"ScheduleInterval",
|
|
171
180
|
"SpotEvent",
|
|
172
181
|
"StopTriggerMethod",
|
|
173
182
|
"SubscriptionRestoreFailedEvent",
|
|
@@ -182,9 +191,12 @@ __all__ = [
|
|
|
182
191
|
"TokenRefreshFailedEvent",
|
|
183
192
|
"TokenStore",
|
|
184
193
|
"TraderUpdateEvent",
|
|
194
|
+
"TradingHours",
|
|
185
195
|
"TradingMode",
|
|
196
|
+
"TradingSession",
|
|
186
197
|
"TrailingStopChangedEvent",
|
|
187
198
|
"Trendbar",
|
|
188
199
|
"TrendbarPeriod",
|
|
189
200
|
"UnknownPayloadTypeError",
|
|
201
|
+
"UnknownTimezoneError",
|
|
190
202
|
]
|
|
@@ -17,7 +17,12 @@ class ClientConfig(BaseModel):
|
|
|
17
17
|
heartbeat_interval: Seconds between heartbeat sends.
|
|
18
18
|
heartbeat_timeout: Seconds without server-initiated messages before disconnect. Set to 0 to disable.
|
|
19
19
|
request_timeout: Default timeout for API requests in seconds.
|
|
20
|
-
|
|
20
|
+
connect_timeout: Seconds allowed for the TCP connection and TLS
|
|
21
|
+
handshake together, after which the attempt is failed and retried.
|
|
22
|
+
reconnect_attempts: Max reconnection attempts. None (the default)
|
|
23
|
+
retries for as long as the client is open. 0 disables reconnection.
|
|
24
|
+
A finite budget that runs out abandons the connection and raises
|
|
25
|
+
CTraderReconnectAbandonedError out of the `async with` block.
|
|
21
26
|
reconnect_min_wait: Initial wait between reconnection attempts.
|
|
22
27
|
reconnect_max_wait: Maximum wait between reconnection attempts.
|
|
23
28
|
refresh_policy: When to refresh access tokens and how hard to try.
|
|
@@ -51,9 +56,10 @@ class ClientConfig(BaseModel):
|
|
|
51
56
|
|
|
52
57
|
# Request settings
|
|
53
58
|
request_timeout: float = Field(default=30.0, gt=0)
|
|
59
|
+
connect_timeout: float = Field(default=30.0, gt=0)
|
|
54
60
|
|
|
55
61
|
# Reconnection settings
|
|
56
|
-
reconnect_attempts: int = Field(default=
|
|
62
|
+
reconnect_attempts: int | None = Field(default=None, ge=0)
|
|
57
63
|
reconnect_min_wait: float = Field(default=1.0, gt=0)
|
|
58
64
|
reconnect_max_wait: float = Field(default=60.0, gt=0)
|
|
59
65
|
|
{ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/protocol.py
RENAMED
|
@@ -12,6 +12,7 @@ from tenacity import (
|
|
|
12
12
|
AsyncRetrying,
|
|
13
13
|
retry_if_exception_type,
|
|
14
14
|
stop_after_attempt,
|
|
15
|
+
stop_never,
|
|
15
16
|
wait_exponential,
|
|
16
17
|
)
|
|
17
18
|
|
|
@@ -29,6 +30,7 @@ from ..exceptions import (
|
|
|
29
30
|
CTraderConnectionClosedError,
|
|
30
31
|
CTraderConnectionFailedError,
|
|
31
32
|
CTraderConnectionTimeoutError,
|
|
33
|
+
CTraderReconnectAbandonedError,
|
|
32
34
|
FramingError,
|
|
33
35
|
)
|
|
34
36
|
from .listener import ConnectionListener
|
|
@@ -73,7 +75,7 @@ class Protocol:
|
|
|
73
75
|
def __init__(
|
|
74
76
|
self,
|
|
75
77
|
transport: Transport,
|
|
76
|
-
reconnect_attempts: int =
|
|
78
|
+
reconnect_attempts: int | None = None,
|
|
77
79
|
reconnect_min_wait: float = 1.0,
|
|
78
80
|
reconnect_max_wait: float = 60.0,
|
|
79
81
|
) -> None:
|
|
@@ -81,7 +83,10 @@ class Protocol:
|
|
|
81
83
|
|
|
82
84
|
Args:
|
|
83
85
|
transport: The underlying transport for sending/receiving data.
|
|
84
|
-
reconnect_attempts: Maximum reconnection attempts
|
|
86
|
+
reconnect_attempts: Maximum reconnection attempts. None retries for
|
|
87
|
+
as long as the client is open, which is the default: an outage
|
|
88
|
+
that outlasts a finite budget otherwise leaves a client that
|
|
89
|
+
can never recover. 0 disables reconnection entirely.
|
|
85
90
|
reconnect_min_wait: Initial wait between attempts (seconds).
|
|
86
91
|
reconnect_max_wait: Maximum wait between attempts (seconds).
|
|
87
92
|
"""
|
|
@@ -500,6 +505,11 @@ class Protocol:
|
|
|
500
505
|
working — reported through `handle_disconnect` as `_redo_requested`, the
|
|
501
506
|
only route left once the reader has exited — is reconnected too instead
|
|
502
507
|
of being stranded.
|
|
508
|
+
|
|
509
|
+
Raises:
|
|
510
|
+
CTraderReconnectAbandonedError: If reconnection is given up on. This
|
|
511
|
+
is deliberately allowed to escape into the protocol task group
|
|
512
|
+
and tear the client down: see :meth:`_abandon`.
|
|
503
513
|
"""
|
|
504
514
|
try:
|
|
505
515
|
while True:
|
|
@@ -535,18 +545,46 @@ class Protocol:
|
|
|
535
545
|
await self._notify_connection_lost()
|
|
536
546
|
except (CTraderConnectionFailedError, CTraderConnectionClosedError) as e:
|
|
537
547
|
logger.error("Reconnection failed, giving up: %s", e)
|
|
538
|
-
self.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
#
|
|
548
|
+
self._abandon("connection lost and reconnection failed", e)
|
|
549
|
+
except Exception as e:
|
|
550
|
+
# Nothing here is expected to fail this way — the retry loop below
|
|
551
|
+
# absorbs every failure connecting — so treat it as a defect rather
|
|
552
|
+
# than a state to sit quietly in.
|
|
543
553
|
logger.exception("Unexpected error while reconnecting")
|
|
544
|
-
self.
|
|
545
|
-
self._fail_pending("Reconnection error")
|
|
554
|
+
self._abandon("unexpected error while reconnecting", e)
|
|
546
555
|
finally:
|
|
547
556
|
self._reconnecting = False
|
|
548
557
|
self._redo_requested = False
|
|
549
558
|
|
|
559
|
+
def _abandon(self, reason: str, cause: BaseException) -> None:
|
|
560
|
+
"""Stop the protocol and make giving up impossible to miss.
|
|
561
|
+
|
|
562
|
+
A client that has stopped reconnecting cannot recover on its own: the
|
|
563
|
+
reader has exited, the heartbeat loop returns as soon as its next write
|
|
564
|
+
fails, and `handle_disconnect` refuses everything once `_running` is
|
|
565
|
+
False. Nothing is left that could try again. Staying alive in that state
|
|
566
|
+
only means a consumer polling `is_connected` sees a link that is down
|
|
567
|
+
and assumes, reasonably and wrongly, that something is working on it.
|
|
568
|
+
|
|
569
|
+
So the failure is raised instead of recorded. It escapes into the
|
|
570
|
+
protocol task group, through the supervisor's, and out of the
|
|
571
|
+
`async with client:` block, where a process supervisor will see it.
|
|
572
|
+
|
|
573
|
+
Raises:
|
|
574
|
+
CTraderReconnectAbandonedError: Always, unless reconnection was
|
|
575
|
+
disabled by configuration, which is a choice rather than a
|
|
576
|
+
failure and keeps the previous behaviour of a client that
|
|
577
|
+
rejects further requests.
|
|
578
|
+
"""
|
|
579
|
+
self._running = False
|
|
580
|
+
# Wake all pending requests so callers fail fast instead of hanging.
|
|
581
|
+
self._fail_pending(reason)
|
|
582
|
+
|
|
583
|
+
if self._reconnect_attempts == 0:
|
|
584
|
+
return
|
|
585
|
+
|
|
586
|
+
raise CTraderReconnectAbandonedError(reason, cause) from cause
|
|
587
|
+
|
|
550
588
|
def _fail_pending(self, reason: str) -> None:
|
|
551
589
|
"""Resolve every in-flight request with a connection error."""
|
|
552
590
|
for pending in self._pending.values():
|
|
@@ -556,27 +594,35 @@ class Protocol:
|
|
|
556
594
|
async def _reconnect(self) -> None:
|
|
557
595
|
"""Attempt reconnection with exponential backoff.
|
|
558
596
|
|
|
597
|
+
Retries on *any* failure rather than on an enumerated set of exception
|
|
598
|
+
types. The budget exists to outlast a server that cannot be reached,
|
|
599
|
+
and an unrecognised exception is no evidence that it can be: a
|
|
600
|
+
whitelist here meant an exception nobody had thought of skipped the
|
|
601
|
+
retries entirely and went straight to being fatal.
|
|
602
|
+
|
|
559
603
|
Raises:
|
|
560
|
-
CTraderConnectionClosedError: If reconnection is disabled
|
|
561
|
-
CTraderConnectionFailedError: If
|
|
604
|
+
CTraderConnectionClosedError: If reconnection is disabled.
|
|
605
|
+
CTraderConnectionFailedError: If every attempt in the budget failed.
|
|
562
606
|
"""
|
|
563
607
|
if self._reconnect_attempts == 0:
|
|
564
608
|
raise CTraderConnectionClosedError("Connection lost and reconnection disabled")
|
|
565
609
|
|
|
610
|
+
budget = "unlimited" if self._reconnect_attempts is None else self._reconnect_attempts
|
|
611
|
+
|
|
566
612
|
async for attempt in AsyncRetrying(
|
|
567
|
-
stop=stop_after_attempt(self._reconnect_attempts),
|
|
613
|
+
stop=stop_never if self._reconnect_attempts is None else stop_after_attempt(self._reconnect_attempts),
|
|
568
614
|
wait=wait_exponential(
|
|
569
615
|
min=self._reconnect_min_wait,
|
|
570
616
|
max=self._reconnect_max_wait,
|
|
571
617
|
),
|
|
572
|
-
retry=retry_if_exception_type(
|
|
618
|
+
retry=retry_if_exception_type(Exception),
|
|
573
619
|
reraise=True,
|
|
574
620
|
):
|
|
575
621
|
with attempt:
|
|
576
622
|
logger.debug(
|
|
577
|
-
"Reconnection attempt %d/%
|
|
623
|
+
"Reconnection attempt %d/%s",
|
|
578
624
|
attempt.retry_state.attempt_number,
|
|
579
|
-
|
|
625
|
+
budget,
|
|
580
626
|
)
|
|
581
627
|
await self._transport.connect()
|
|
582
628
|
|
{ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/transport.py
RENAMED
|
@@ -18,17 +18,21 @@ class Transport:
|
|
|
18
18
|
Handles raw socket connections without knowledge of protobuf or message semantics.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
def __init__(self, host: str, port: int, use_ssl: bool = True) -> None:
|
|
21
|
+
def __init__(self, host: str, port: int, use_ssl: bool = True, connect_timeout: float = 30.0) -> None:
|
|
22
22
|
"""Initialize transport configuration.
|
|
23
23
|
|
|
24
24
|
Args:
|
|
25
25
|
host: The server hostname to connect to.
|
|
26
26
|
port: The server port to connect to.
|
|
27
27
|
use_ssl: Whether to use SSL/TLS encryption. Defaults to True.
|
|
28
|
+
connect_timeout: Seconds to allow for the TCP connection and the
|
|
29
|
+
TLS handshake together. Bounds the one failure mode a caller
|
|
30
|
+
cannot retry: a handshake that neither completes nor fails.
|
|
28
31
|
"""
|
|
29
32
|
self._host = host
|
|
30
33
|
self._port = port
|
|
31
34
|
self._ssl = use_ssl
|
|
35
|
+
self._connect_timeout = connect_timeout
|
|
32
36
|
self._stream: ByteStream | None = None
|
|
33
37
|
|
|
34
38
|
@property
|
|
@@ -62,6 +66,12 @@ class Transport:
|
|
|
62
66
|
|
|
63
67
|
If already connected, this method returns immediately.
|
|
64
68
|
|
|
69
|
+
Either returns with a usable stream or raises
|
|
70
|
+
`CTraderConnectionFailedError`. There is deliberately no third outcome:
|
|
71
|
+
anything that goes wrong on the way to a connection *is* a failure to
|
|
72
|
+
connect, whatever its type, so callers can retry on one exception
|
|
73
|
+
rather than on a list of them that has to be kept complete.
|
|
74
|
+
|
|
65
75
|
Raises:
|
|
66
76
|
CTraderConnectionFailedError: If connection cannot be established.
|
|
67
77
|
"""
|
|
@@ -69,17 +79,25 @@ class Transport:
|
|
|
69
79
|
return # Already connected
|
|
70
80
|
|
|
71
81
|
try:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
# Every failure is translated, not an enumerated set of them. A TLS
|
|
83
|
+
# handshake cut short by the peer surfaces as anyio's
|
|
84
|
+
# BrokenResourceError, which is not an OSError and so used to
|
|
85
|
+
# escape untranslated — past the retry loop that only knew
|
|
86
|
+
# CTraderConnectionFailedError, and into the handler that reads an
|
|
87
|
+
# unrecognised exception as fatal. Cancellation still passes
|
|
88
|
+
# through: anyio raises it from BaseException, not Exception.
|
|
89
|
+
with anyio.fail_after(self._connect_timeout):
|
|
90
|
+
if self._ssl:
|
|
91
|
+
ssl_context = ssl.create_default_context()
|
|
92
|
+
self._stream = await anyio.connect_tcp(
|
|
93
|
+
self._host,
|
|
94
|
+
self._port,
|
|
95
|
+
ssl_context=ssl_context,
|
|
96
|
+
tls_standard_compatible=True,
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
self._stream = await anyio.connect_tcp(self._host, self._port)
|
|
100
|
+
except Exception as e:
|
|
83
101
|
raise CTraderConnectionFailedError(self._host, self._port, e) from e
|
|
84
102
|
|
|
85
103
|
if self._ssl:
|
|
@@ -68,6 +68,39 @@ class CTraderConnectionTimeoutError(CTraderConnectionError):
|
|
|
68
68
|
super().__init__(f"{operation} timed out after {timeout_seconds}s")
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
class CTraderReconnectAbandonedError(CTraderConnectionError):
|
|
72
|
+
"""The client tried to restore a dropped link and gave up.
|
|
73
|
+
|
|
74
|
+
Raised out of the `async with client:` block, tearing the client down
|
|
75
|
+
rather than leaving it alive but permanently offline. A client that has
|
|
76
|
+
stopped reconnecting can never recover on its own — there is no reader and
|
|
77
|
+
no heartbeat left to notice anything — so the only useful thing it can do
|
|
78
|
+
is say so where a supervisor will see it.
|
|
79
|
+
|
|
80
|
+
Reaching this is not expected. Reconnection is unbounded by default, so a
|
|
81
|
+
server that stays unreachable produces retries rather than this error. It
|
|
82
|
+
means either that `reconnect_attempts` was set to a finite number and every
|
|
83
|
+
one was used, or that something went wrong that the reconnection logic did
|
|
84
|
+
not anticipate.
|
|
85
|
+
|
|
86
|
+
Not raised when reconnection is disabled outright with
|
|
87
|
+
`reconnect_attempts=0`: not reconnecting is the documented outcome there,
|
|
88
|
+
not a failure.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
reason: What was being attempted when it was abandoned.
|
|
92
|
+
cause: The final underlying failure, also set as `__cause__`.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, reason: str, cause: BaseException | None = None) -> None:
|
|
96
|
+
self.reason = reason
|
|
97
|
+
self.cause = cause
|
|
98
|
+
message = f"Reconnection abandoned: {reason}"
|
|
99
|
+
if cause:
|
|
100
|
+
message += f" ({cause})"
|
|
101
|
+
super().__init__(message)
|
|
102
|
+
|
|
103
|
+
|
|
71
104
|
# =============================================================================
|
|
72
105
|
# Authentication Errors
|
|
73
106
|
# =============================================================================
|
|
@@ -222,6 +255,28 @@ class APIError(CTraderError):
|
|
|
222
255
|
)
|
|
223
256
|
|
|
224
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
|
+
|
|
225
280
|
# =============================================================================
|
|
226
281
|
# Protocol Errors
|
|
227
282
|
# =============================================================================
|
{ctrader_api_client-0.7.3 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/__init__.py
RENAMED
|
@@ -30,6 +30,7 @@ from .order import Order
|
|
|
30
30
|
from .position import Position, PositionUnrealizedPnL
|
|
31
31
|
from .requests import AmendOrderRequest, AmendPositionRequest, ClosePositionRequest, NewOrderRequest
|
|
32
32
|
from .symbol import Symbol, SymbolInfo
|
|
33
|
+
from .trading_hours import Holiday, ScheduleInterval, TradingHours, TradingSession
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
__all__ = [
|
|
@@ -40,12 +41,16 @@ __all__ = [
|
|
|
40
41
|
"CloseDetail",
|
|
41
42
|
"ClosePositionRequest",
|
|
42
43
|
"Deal",
|
|
44
|
+
"Holiday",
|
|
43
45
|
"NewOrderRequest",
|
|
44
46
|
"Order",
|
|
45
47
|
"Position",
|
|
46
48
|
"PositionUnrealizedPnL",
|
|
49
|
+
"ScheduleInterval",
|
|
47
50
|
"Symbol",
|
|
48
51
|
"SymbolInfo",
|
|
49
52
|
"TickData",
|
|
53
|
+
"TradingHours",
|
|
54
|
+
"TradingSession",
|
|
50
55
|
"Trendbar",
|
|
51
56
|
]
|