ctrader-api-client 0.8.0__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.

Files changed (125) hide show
  1. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/PKG-INFO +14 -2
  2. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/README.md +11 -0
  3. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/models.md +20 -0
  4. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/symbols.md +58 -0
  5. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/pyproject.toml +4 -1
  6. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/__init__.py +10 -0
  7. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/exceptions.py +22 -0
  8. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/__init__.py +5 -0
  9. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/symbol.py +7 -0
  10. ctrader_api_client-0.8.1/src/ctrader_api_client/models/trading_hours.py +506 -0
  11. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/unit/test_symbol_conversion.py +19 -1
  12. ctrader_api_client-0.8.1/tests/unit/test_trading_hours.py +387 -0
  13. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/uv.lock +12 -1
  14. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/.claude/settings.local.json +0 -0
  15. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/.github/workflows/docs.yml +0 -0
  16. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/.gitignore +0 -0
  17. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/.pre-commit-config.yaml +0 -0
  18. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/.python-version +0 -0
  19. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/Justfile +0 -0
  20. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/LICENSE +0 -0
  21. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/accounts.md +0 -0
  22. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/client.md +0 -0
  23. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/enums.md +0 -0
  24. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/events.md +0 -0
  25. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/exceptions.md +0 -0
  26. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/market-data.md +0 -0
  27. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/api/trading.md +0 -0
  28. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/getting-started.md +0 -0
  29. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/docs/index.md +0 -0
  30. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/mkdocs.yml +0 -0
  31. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/protos/SOURCE +0 -0
  32. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/protos/VERSION +0 -0
  33. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/protos/update.sh +0 -0
  34. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiCommonMessages.proto +0 -0
  35. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
  36. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiMessages.proto +0 -0
  37. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/protos/vendor/OpenApiModelMessages.proto +0 -0
  38. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/scripts/fix_proto_imports.py +0 -0
  39. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/__init__.py +0 -0
  40. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/clock.py +0 -0
  41. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/conversions.py +0 -0
  42. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/messages.py +0 -0
  43. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
  44. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
  45. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +0 -0
  46. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +0 -0
  47. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/proto/__init__.py +0 -0
  48. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/_internal/serialization.py +0 -0
  49. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/__init__.py +0 -0
  50. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/_base.py +0 -0
  51. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/accounts.py +0 -0
  52. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/market_data.py +0 -0
  53. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/symbols.py +0 -0
  54. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/api/trading.py +0 -0
  55. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/__init__.py +0 -0
  56. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/_recovery.py +0 -0
  57. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/_refresh.py +0 -0
  58. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/_session.py +0 -0
  59. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/credentials.py +0 -0
  60. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/manager.py +0 -0
  61. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/policy.py +0 -0
  62. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/auth/store.py +0 -0
  63. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/client.py +0 -0
  64. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/composition.py +0 -0
  65. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/config.py +0 -0
  66. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/__init__.py +0 -0
  67. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/heartbeat.py +0 -0
  68. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/listener.py +0 -0
  69. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/protocol.py +0 -0
  70. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/supervisor.py +0 -0
  71. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/connection/transport.py +0 -0
  72. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/enums.py +0 -0
  73. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/__init__.py +0 -0
  74. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/_execution.py +0 -0
  75. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/emitter.py +0 -0
  76. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/publisher.py +0 -0
  77. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/router.py +0 -0
  78. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/events/types.py +0 -0
  79. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/_base.py +0 -0
  80. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/account.py +0 -0
  81. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/deal.py +0 -0
  82. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/market_data.py +0 -0
  83. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/order.py +0 -0
  84. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/position.py +0 -0
  85. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/models/requests.py +0 -0
  86. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/src/ctrader_api_client/py.typed +0 -0
  87. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/__init__.py +0 -0
  88. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/__init__.py +0 -0
  89. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/api/__init__.py +0 -0
  90. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/api/conftest.py +0 -0
  91. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/api/test_market_data.py +0 -0
  92. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/api/test_reference_data.py +0 -0
  93. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/api/test_subscription_restore.py +0 -0
  94. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/api/test_trading.py +0 -0
  95. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/auth/__init__.py +0 -0
  96. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/auth/conftest.py +0 -0
  97. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/auth/test_authentication.py +0 -0
  98. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/auth/test_token_lifecycle.py +0 -0
  99. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/connection/__init__.py +0 -0
  100. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/connection/conftest.py +0 -0
  101. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_event_dispatch.py +0 -0
  102. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_framing.py +0 -0
  103. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_heartbeat.py +0 -0
  104. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_reconnection.py +0 -0
  105. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/connection/test_request_response.py +0 -0
  106. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/events/__init__.py +0 -0
  107. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/events/conftest.py +0 -0
  108. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/events/test_routing.py +0 -0
  109. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/events/test_subscriptions.py +0 -0
  110. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/behavior/test_client.py +0 -0
  111. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/conftest.py +0 -0
  112. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/__init__.py +0 -0
  113. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/clock.py +0 -0
  114. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/factories.py +0 -0
  115. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/recorder.py +0 -0
  116. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/server.py +0 -0
  117. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/signals.py +0 -0
  118. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/stub_protocol.py +0 -0
  119. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/harness/wire.py +0 -0
  120. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/unit/__init__.py +0 -0
  121. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/unit/test_api_errors.py +0 -0
  122. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/unit/test_market_data_conversion.py +0 -0
  123. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/unit/test_request_conversion.py +0 -0
  124. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/unit/test_trading_records.py +0 -0
  125. {ctrader_api_client-0.8.0 → ctrader_api_client-0.8.1}/tests/unit/test_wire_format.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ctrader-api-client
3
- Version: 0.8.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
@@ -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
@@ -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`:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ctrader-api-client"
3
- version = "0.8.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]
@@ -99,6 +99,7 @@ from .exceptions import (
99
99
  TokenExpiredError,
100
100
  TokenRefreshError,
101
101
  UnknownPayloadTypeError,
102
+ UnknownTimezoneError,
102
103
  )
103
104
  from .models import (
104
105
  Account,
@@ -108,13 +109,17 @@ from .models import (
108
109
  CloseDetail,
109
110
  ClosePositionRequest,
110
111
  Deal,
112
+ Holiday,
111
113
  NewOrderRequest,
112
114
  Order,
113
115
  Position,
114
116
  PositionUnrealizedPnL,
117
+ ScheduleInterval,
115
118
  Symbol,
116
119
  SymbolInfo,
117
120
  TickData,
121
+ TradingHours,
122
+ TradingSession,
118
123
  Trendbar,
119
124
  )
120
125
 
@@ -154,6 +159,7 @@ __all__ = [
154
159
  "ExecutionEvent",
155
160
  "ExecutionType",
156
161
  "FramingError",
162
+ "Holiday",
157
163
  "MarginCallTriggerEvent",
158
164
  "MarginChangeEvent",
159
165
  "NewOrderRequest",
@@ -170,6 +176,7 @@ __all__ = [
170
176
  "ReauthPolicy",
171
177
  "ReconnectedEvent",
172
178
  "RefreshPolicy",
179
+ "ScheduleInterval",
173
180
  "SpotEvent",
174
181
  "StopTriggerMethod",
175
182
  "SubscriptionRestoreFailedEvent",
@@ -184,9 +191,12 @@ __all__ = [
184
191
  "TokenRefreshFailedEvent",
185
192
  "TokenStore",
186
193
  "TraderUpdateEvent",
194
+ "TradingHours",
187
195
  "TradingMode",
196
+ "TradingSession",
188
197
  "TrailingStopChangedEvent",
189
198
  "Trendbar",
190
199
  "TrendbarPeriod",
191
200
  "UnknownPayloadTypeError",
201
+ "UnknownTimezoneError",
192
202
  ]
@@ -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
  # =============================================================================
@@ -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
  ]
@@ -3,9 +3,12 @@ from __future__ import annotations
3
3
  from decimal import Decimal
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from pydantic import Field
7
+
6
8
  from .._internal.proto import ProtoOATradingMode
7
9
  from ..enums import TradingMode
8
10
  from ._base import FrozenModel
11
+ from .trading_hours import TradingHours
9
12
 
10
13
 
11
14
  if TYPE_CHECKING:
@@ -91,6 +94,8 @@ class Symbol(FrozenModel):
91
94
  tp_distance: Minimum take profit distance.
92
95
  schedule_timezone: Timezone for trading schedule.
93
96
  measurement_units: Measurement units (e.g., "oz" for gold).
97
+ trading_hours: Weekly schedule and holidays describing when the symbol is
98
+ open. Empty for a symbol the broker publishes without a schedule.
94
99
  """
95
100
 
96
101
  symbol_id: int
@@ -114,6 +119,7 @@ class Symbol(FrozenModel):
114
119
  tp_distance: int = 0
115
120
  schedule_timezone: str = ""
116
121
  measurement_units: str = ""
122
+ trading_hours: TradingHours = Field(default_factory=TradingHours)
117
123
 
118
124
  def volume_to_lots(self, volume_cents: int) -> Decimal:
119
125
  """Convert volume in cents to lots.
@@ -179,4 +185,5 @@ class Symbol(FrozenModel):
179
185
  tp_distance=proto.tp_distance if proto.tp_distance else 0,
180
186
  schedule_timezone=proto.schedule_time_zone or "",
181
187
  measurement_units=proto.measurement_units or "",
188
+ trading_hours=TradingHours.from_proto(proto),
182
189
  )