ctrader-api-client 0.4.0__tar.gz → 0.4.2__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.
Files changed (92) hide show
  1. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/PKG-INFO +1 -1
  2. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/pyproject.toml +1 -1
  3. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/events/router.py +7 -3
  4. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/events/types.py +1 -1
  5. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/symbol.py +12 -0
  6. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/events/test_emitter.py +8 -8
  7. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/events/test_router.py +2 -2
  8. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/events/test_types.py +4 -4
  9. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/models/test_symbol.py +19 -0
  10. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/uv.lock +1 -1
  11. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/.claude/settings.local.json +0 -0
  12. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/.github/workflows/docs.yml +0 -0
  13. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/.gitignore +0 -0
  14. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/.pre-commit-config.yaml +0 -0
  15. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/.python-version +0 -0
  16. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/Justfile +0 -0
  17. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/LICENSE +0 -0
  18. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/README.md +0 -0
  19. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/accounts.md +0 -0
  20. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/client.md +0 -0
  21. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/enums.md +0 -0
  22. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/events.md +0 -0
  23. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/market-data.md +0 -0
  24. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/models.md +0 -0
  25. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/symbols.md +0 -0
  26. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/api/trading.md +0 -0
  27. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/getting-started.md +0 -0
  28. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/docs/index.md +0 -0
  29. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/mkdocs.yml +0 -0
  30. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/protos/SOURCE +0 -0
  31. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/protos/VERSION +0 -0
  32. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/protos/update.sh +0 -0
  33. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/protos/vendor/OpenApiCommonMessages.proto +0 -0
  34. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
  35. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/protos/vendor/OpenApiMessages.proto +0 -0
  36. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/protos/vendor/OpenApiModelMessages.proto +0 -0
  37. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/scripts/fix_proto_imports.py +0 -0
  38. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/__init__.py +0 -0
  39. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/__init__.py +0 -0
  40. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/messages.py +0 -0
  41. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
  42. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
  43. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +0 -0
  44. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +0 -0
  45. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/proto/__init__.py +0 -0
  46. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/_internal/serialization.py +0 -0
  47. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/api/__init__.py +0 -0
  48. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/api/accounts.py +0 -0
  49. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/api/market_data.py +0 -0
  50. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/api/symbols.py +0 -0
  51. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/api/trading.py +0 -0
  52. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/auth/__init__.py +0 -0
  53. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/auth/credentials.py +0 -0
  54. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/auth/manager.py +0 -0
  55. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/client.py +0 -0
  56. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/config.py +0 -0
  57. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/connection/__init__.py +0 -0
  58. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/connection/heartbeat.py +0 -0
  59. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/connection/protocol.py +0 -0
  60. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/connection/transport.py +0 -0
  61. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/enums.py +0 -0
  62. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/events/__init__.py +0 -0
  63. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/events/emitter.py +0 -0
  64. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/exceptions.py +0 -0
  65. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/__init__.py +0 -0
  66. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/_base.py +0 -0
  67. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/account.py +0 -0
  68. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/deal.py +0 -0
  69. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/market_data.py +0 -0
  70. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/order.py +0 -0
  71. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/position.py +0 -0
  72. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/models/requests.py +0 -0
  73. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/src/ctrader_api_client/py.typed +0 -0
  74. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/_internal/test_messages.py +0 -0
  75. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/_internal/test_serialization.py +0 -0
  76. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/api/conftest.py +0 -0
  77. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/api/test_accounts.py +0 -0
  78. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/api/test_market_data_api.py +0 -0
  79. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/api/test_symbols.py +0 -0
  80. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/api/test_trading.py +0 -0
  81. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/auth/test_credentials.py +0 -0
  82. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/auth/test_manager.py +0 -0
  83. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/connection/test_heartbeat.py +0 -0
  84. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/connection/test_protocol.py +0 -0
  85. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/connection/test_transport.py +0 -0
  86. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/models/test_account.py +0 -0
  87. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/models/test_deal.py +0 -0
  88. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/models/test_market_data.py +0 -0
  89. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/models/test_order.py +0 -0
  90. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/models/test_position.py +0 -0
  91. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/models/test_requests.py +0 -0
  92. {ctrader_api_client-0.4.0 → ctrader_api_client-0.4.2}/tests/unit/test_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctrader-api-client
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ctrader-api-client"
3
- version = "0.4.0"
3
+ version = "0.4.2"
4
4
  description = "API Client to interact with the cTrader Open API spec"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -180,15 +180,19 @@ class EventRouter:
180
180
 
181
181
  async def _handle_spot(self, proto: ProtoOASpotEvent) -> None:
182
182
  """Convert ProtoOASpotEvent to SpotEvent."""
183
+
184
+ # I don't trust the API
185
+ if proto.trendbar is None:
186
+ trendbars = [] # type: ignore[unreachable]
187
+ else:
188
+ trendbars = [Trendbar.from_proto(tb, bid_price=Decimal(proto.bid)) for tb in proto.trendbar]
183
189
  event = SpotEvent(
184
190
  account_id=proto.ctid_trader_account_id,
185
191
  symbol_id=proto.symbol_id,
186
192
  bid=proto.bid / Decimal(100000) if proto.bid else None,
187
193
  ask=proto.ask / Decimal(100000) if proto.ask else None,
188
194
  timestamp=self._timestamp_to_datetime(proto.timestamp) if proto.timestamp else datetime.now(UTC),
189
- trendbar=(
190
- Trendbar.from_proto(proto.trendbar[0], bid_price=Decimal(str(proto.bid))) if proto.trendbar else None
191
- ),
195
+ trendbar=trendbars,
192
196
  )
193
197
  await self._emitter.emit(event)
194
198
 
@@ -32,7 +32,7 @@ class SpotEvent:
32
32
  symbol_id: int
33
33
  bid: Decimal | None
34
34
  ask: Decimal | None
35
- trendbar: Trendbar | None
35
+ trendbar: list[Trendbar]
36
36
  timestamp: datetime
37
37
 
38
38
 
@@ -137,6 +137,18 @@ class Symbol(FrozenModel):
137
137
  """
138
138
  return int(lots * self.lot_size)
139
139
 
140
+ def quantize_price(self, price: Decimal) -> Decimal:
141
+ """Quantize price to symbol's allowed decimal places
142
+
143
+ Args:
144
+ price: The price to quantize.
145
+
146
+ Returns:
147
+ Quantized price.
148
+ """
149
+ quantize_exp = Decimal(10) ** (-self.digits)
150
+ return price.quantize(quantize_exp)
151
+
140
152
  @classmethod
141
153
  def from_proto(cls, proto: ProtoOASymbol) -> Symbol:
142
154
  """Create Symbol from proto message.
@@ -143,7 +143,7 @@ class TestEventEmitterEmit:
143
143
  symbol_id=1,
144
144
  bid=Decimal("1.23000"),
145
145
  ask=Decimal("1.23050"),
146
- trendbar=None,
146
+ trendbar=[],
147
147
  timestamp=datetime.now(UTC),
148
148
  )
149
149
  await emitter.emit(event)
@@ -164,7 +164,7 @@ class TestEventEmitterEmit:
164
164
  symbol_id=1,
165
165
  bid=Decimal("1.23000"),
166
166
  ask=Decimal("1.23050"),
167
- trendbar=None,
167
+ trendbar=[],
168
168
  timestamp=datetime.now(UTC),
169
169
  )
170
170
  await emitter.emit(event)
@@ -185,7 +185,7 @@ class TestEventEmitterEmit:
185
185
  symbol_id=2,
186
186
  bid=Decimal("1.23000"),
187
187
  ask=Decimal("1.23050"),
188
- trendbar=None,
188
+ trendbar=[],
189
189
  timestamp=datetime.now(UTC),
190
190
  )
191
191
  await emitter.emit(event)
@@ -205,7 +205,7 @@ class TestEventEmitterEmit:
205
205
  symbol_id=1,
206
206
  bid=Decimal("1.23000"),
207
207
  ask=Decimal("1.23050"),
208
- trendbar=None,
208
+ trendbar=[],
209
209
  timestamp=datetime.now(UTC),
210
210
  )
211
211
  await emitter.emit(event)
@@ -236,7 +236,7 @@ class TestEventEmitterEmit:
236
236
  symbol_id=1,
237
237
  bid=Decimal("1.23000"),
238
238
  ask=Decimal("1.23050"),
239
- trendbar=None,
239
+ trendbar=[],
240
240
  timestamp=datetime.now(UTC),
241
241
  )
242
242
  await emitter.emit(event)
@@ -262,7 +262,7 @@ class TestEventEmitterErrorHandling:
262
262
  symbol_id=1,
263
263
  bid=Decimal("1.23000"),
264
264
  ask=Decimal("1.23050"),
265
- trendbar=None,
265
+ trendbar=[],
266
266
  timestamp=datetime.now(UTC),
267
267
  )
268
268
  await emitter.emit(event)
@@ -286,7 +286,7 @@ class TestEventEmitterErrorHandling:
286
286
  symbol_id=1,
287
287
  bid=Decimal("1.23000"),
288
288
  ask=Decimal("1.23050"),
289
- trendbar=None,
289
+ trendbar=[],
290
290
  timestamp=datetime.now(UTC),
291
291
  )
292
292
  await emitter.emit(event)
@@ -311,7 +311,7 @@ class TestEventEmitterErrorHandling:
311
311
  symbol_id=1,
312
312
  bid=Decimal("1.23000"),
313
313
  ask=Decimal("1.23050"),
314
- trendbar=None,
314
+ trendbar=[],
315
315
  timestamp=datetime.now(UTC),
316
316
  )
317
317
 
@@ -113,7 +113,7 @@ class TestSpotEventConversion:
113
113
  assert event.symbol_id == 1
114
114
  assert event.bid == Decimal("1.23") # 123000 / 1e5
115
115
  assert event.ask == Decimal("1.2305") # 123050 / 1e5
116
- assert event.trendbar is None
116
+ assert len(event.trendbar) == 0
117
117
  assert event.timestamp == datetime(2021, 1, 1, 0, 0, 0, tzinfo=UTC)
118
118
 
119
119
  @pytest.mark.anyio
@@ -140,7 +140,7 @@ class TestSpotEventConversion:
140
140
  assert len(received_events) == 1
141
141
  assert received_events[0].bid is None
142
142
  assert received_events[0].ask is None
143
- assert received_events[0].trendbar is None
143
+ assert len(received_events[0].trendbar) == 0
144
144
 
145
145
 
146
146
  class TestExecutionEventConversion:
@@ -34,7 +34,7 @@ class TestSpotEvent:
34
34
  symbol_id=1,
35
35
  bid=Decimal("1.23000"),
36
36
  ask=Decimal("1.23050"),
37
- trendbar=None,
37
+ trendbar=[],
38
38
  timestamp=timestamp,
39
39
  )
40
40
 
@@ -42,7 +42,7 @@ class TestSpotEvent:
42
42
  assert event.symbol_id == 1
43
43
  assert event.bid == Decimal("1.23000")
44
44
  assert event.ask == Decimal("1.23050")
45
- assert event.trendbar is None
45
+ assert len(event.trendbar) == 0
46
46
  assert event.timestamp == timestamp
47
47
 
48
48
  def test_spot_event_is_frozen(self) -> None:
@@ -52,7 +52,7 @@ class TestSpotEvent:
52
52
  symbol_id=1,
53
53
  bid=Decimal("1.23000"),
54
54
  ask=Decimal("1.23050"),
55
- trendbar=None,
55
+ trendbar=[],
56
56
  timestamp=datetime.now(UTC),
57
57
  )
58
58
 
@@ -66,7 +66,7 @@ class TestSpotEvent:
66
66
  symbol_id=1,
67
67
  bid=None,
68
68
  ask=None,
69
- trendbar=None,
69
+ trendbar=[],
70
70
  timestamp=datetime.now(UTC),
71
71
  )
72
72
 
@@ -215,3 +215,22 @@ class TestSymbol:
215
215
  volume = symbol.lots_to_volume(original_lots)
216
216
  result = symbol.volume_to_lots(volume)
217
217
  assert result == original_lots
218
+
219
+ def test_quantize(self):
220
+ """Test that quantize correctly rounds to the symbol's digits."""
221
+ symbol = Symbol(
222
+ symbol_id=1,
223
+ digits=5,
224
+ pip_position=4,
225
+ lot_size=100000,
226
+ min_volume=1000,
227
+ max_volume=10000000,
228
+ step_volume=1000,
229
+ trading_mode=TradingMode.ENABLED,
230
+ swap_long=Decimal("0.0"),
231
+ swap_short=Decimal("0.0"),
232
+ )
233
+
234
+ assert symbol.quantize_price(Decimal("1.23456789")) == Decimal("1.23457")
235
+ assert symbol.quantize_price(Decimal("1.23456123")) == Decimal("1.23456")
236
+ assert symbol.quantize_price(Decimal("1.23456499")) == Decimal("1.23456")
@@ -201,7 +201,7 @@ wheels = [
201
201
 
202
202
  [[package]]
203
203
  name = "ctrader-api-client"
204
- version = "0.4.0"
204
+ version = "0.4.2"
205
205
  source = { editable = "." }
206
206
  dependencies = [
207
207
  { name = "anyio" },