ctrader-api-client 0.2.1__tar.gz → 0.2.3__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.
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/.pre-commit-config.yaml +8 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/Justfile +1 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/PKG-INFO +1 -1
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/pyproject.toml +1 -1
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/market_data.py +1 -1
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/config.py +2 -2
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/heartbeat.py +12 -10
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/protocol.py +10 -7
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/market_data.py +11 -11
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/connection/test_heartbeat.py +40 -14
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/connection/test_protocol.py +23 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/models/test_market_data.py +2 -2
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/uv.lock +1 -1
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/.claude/settings.local.json +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/.github/workflows/docs.yml +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/.gitignore +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/.python-version +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/LICENSE +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/README.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/accounts.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/client.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/enums.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/events.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/market-data.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/models.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/symbols.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/api/trading.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/getting-started.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/docs/index.md +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/mkdocs.yml +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/SOURCE +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/VERSION +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/update.sh +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/.gitkeep +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/OpenApiCommonMessages.proto +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/OpenApiMessages.proto +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/OpenApiModelMessages.proto +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/scripts/fix_proto_imports.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/messages.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/proto/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/serialization.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/accounts.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/symbols.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/trading.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/auth/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/auth/credentials.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/auth/manager.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/client.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/transport.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/enums.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/emitter.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/router.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/types.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/exceptions.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/__init__.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/_base.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/account.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/deal.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/order.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/position.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/requests.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/symbol.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/py.typed +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/_internal/test_messages.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/_internal/test_serialization.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/api/conftest.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/api/test_accounts.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/api/test_market_data_api.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/api/test_symbols.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/api/test_trading.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/auth/test_credentials.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/auth/test_manager.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/connection/test_transport.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/events/test_emitter.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/events/test_router.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/events/test_types.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/models/test_account.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/models/test_deal.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/models/test_order.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/models/test_position.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/models/test_requests.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/models/test_symbol.py +0 -0
- {ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/test_client.py +0 -0
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/market_data.py
RENAMED
|
@@ -372,7 +372,7 @@ class MarketDataAPI:
|
|
|
372
372
|
description=f"Expected ProtoOAGetTrendbarsRes, got {type(response).__name__}",
|
|
373
373
|
)
|
|
374
374
|
|
|
375
|
-
return [Trendbar.from_proto(t) for t in response.trendbar]
|
|
375
|
+
return [Trendbar.from_proto(t, historical=True) for t in response.trendbar]
|
|
376
376
|
|
|
377
377
|
async def get_tick_data(
|
|
378
378
|
self,
|
|
@@ -13,7 +13,7 @@ class ClientConfig(BaseModel):
|
|
|
13
13
|
client_id: OAuth application client ID.
|
|
14
14
|
client_secret: OAuth application client secret.
|
|
15
15
|
heartbeat_interval: Seconds between heartbeat sends.
|
|
16
|
-
heartbeat_timeout: Seconds without server
|
|
16
|
+
heartbeat_timeout: Seconds without server-initiated messages before disconnect. Set to 0 to disable.
|
|
17
17
|
request_timeout: Default timeout for API requests in seconds.
|
|
18
18
|
reconnect_attempts: Max reconnection attempts (0 to disable).
|
|
19
19
|
reconnect_min_wait: Initial wait between reconnection attempts.
|
|
@@ -43,7 +43,7 @@ class ClientConfig(BaseModel):
|
|
|
43
43
|
|
|
44
44
|
# Heartbeat settings
|
|
45
45
|
heartbeat_interval: float = Field(default=10.0, gt=0)
|
|
46
|
-
heartbeat_timeout: float = Field(default=0, ge=0)
|
|
46
|
+
heartbeat_timeout: float = Field(default=60.0, ge=0)
|
|
47
47
|
|
|
48
48
|
# Request settings
|
|
49
49
|
request_timeout: float = Field(default=30.0, gt=0)
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/heartbeat.py
RENAMED
|
@@ -5,6 +5,7 @@ import time
|
|
|
5
5
|
|
|
6
6
|
import anyio
|
|
7
7
|
import anyio.abc
|
|
8
|
+
import betterproto
|
|
8
9
|
|
|
9
10
|
from .._internal.proto import ProtoHeartbeatEvent
|
|
10
11
|
from .protocol import Protocol
|
|
@@ -43,10 +44,11 @@ class HeartbeatManager:
|
|
|
43
44
|
async def start(self) -> None:
|
|
44
45
|
"""Start heartbeat monitoring.
|
|
45
46
|
|
|
46
|
-
Registers
|
|
47
|
-
the heartbeat send loop.
|
|
47
|
+
Registers event handlers and starts the heartbeat send loop.
|
|
48
48
|
"""
|
|
49
|
-
#
|
|
49
|
+
# Track activity on any server message, not just heartbeats
|
|
50
|
+
self._protocol.on_event(betterproto.Message, self._record_activity)
|
|
51
|
+
# Keep heartbeat handler for debug logging
|
|
50
52
|
self._protocol.on_event(ProtoHeartbeatEvent, self._on_heartbeat)
|
|
51
53
|
self._last_received = time.monotonic()
|
|
52
54
|
|
|
@@ -58,7 +60,7 @@ class HeartbeatManager:
|
|
|
58
60
|
async def stop(self) -> None:
|
|
59
61
|
"""Stop heartbeat monitoring.
|
|
60
62
|
|
|
61
|
-
Cancels the heartbeat loop and removes
|
|
63
|
+
Cancels the heartbeat loop and removes event handlers.
|
|
62
64
|
"""
|
|
63
65
|
if self._task_scope is not None:
|
|
64
66
|
self._task_scope.cancel()
|
|
@@ -71,6 +73,7 @@ class HeartbeatManager:
|
|
|
71
73
|
pass
|
|
72
74
|
self._task_group = None
|
|
73
75
|
|
|
76
|
+
self._protocol.remove_handler(betterproto.Message, self._record_activity)
|
|
74
77
|
self._protocol.remove_handler(ProtoHeartbeatEvent, self._on_heartbeat)
|
|
75
78
|
|
|
76
79
|
async def restart(self) -> None:
|
|
@@ -83,13 +86,12 @@ class HeartbeatManager:
|
|
|
83
86
|
if self._task_group is not None:
|
|
84
87
|
self._task_group.start_soon(self._heartbeat_loop)
|
|
85
88
|
|
|
86
|
-
async def
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
_event: The heartbeat event from the server (unused).
|
|
91
|
-
"""
|
|
89
|
+
async def _record_activity(self, _message: betterproto.Message) -> None:
|
|
90
|
+
"""Reset the inactivity timer on any received server message."""
|
|
92
91
|
self._last_received = time.monotonic()
|
|
92
|
+
|
|
93
|
+
async def _on_heartbeat(self, _event: ProtoHeartbeatEvent) -> None:
|
|
94
|
+
"""Handler called when an explicit heartbeat is received from the server."""
|
|
93
95
|
logger.debug("Heartbeat received from server")
|
|
94
96
|
|
|
95
97
|
async def _heartbeat_loop(self) -> None:
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/protocol.py
RENAMED
|
@@ -276,17 +276,20 @@ class Protocol:
|
|
|
276
276
|
|
|
277
277
|
Handlers are spawned as concurrent tasks to prevent deadlocks if
|
|
278
278
|
handlers perform some blocking I/O calls that require responses from the reader loop.
|
|
279
|
+
Walks the MRO so handlers registered for a base class (e.g. betterproto.Message)
|
|
280
|
+
are also called for all subclass messages.
|
|
279
281
|
|
|
280
282
|
Args:
|
|
281
283
|
message: The event message to dispatch.
|
|
282
284
|
"""
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
self._task_group
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
285
|
+
for cls in type(message).__mro__:
|
|
286
|
+
handlers = self._event_handlers.get(cls, [])
|
|
287
|
+
for handler in handlers:
|
|
288
|
+
if self._task_group is not None:
|
|
289
|
+
self._task_group.start_soon(self._call_handler_safe, handler, message)
|
|
290
|
+
else:
|
|
291
|
+
# Fallback if task group not available (shouldn't happen in normal operation)
|
|
292
|
+
await self._call_handler_safe(handler, message)
|
|
290
293
|
|
|
291
294
|
@staticmethod
|
|
292
295
|
async def _call_handler_safe(
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/market_data.py
RENAMED
|
@@ -56,17 +56,15 @@ class Trendbar(FrozenModel):
|
|
|
56
56
|
volume: int
|
|
57
57
|
|
|
58
58
|
@classmethod
|
|
59
|
-
def from_proto(cls, proto: ProtoOATrendbar, bid_price: float | None = None) -> Trendbar:
|
|
60
|
-
"""Create Trendbar from proto message.
|
|
59
|
+
def from_proto(cls, proto: ProtoOATrendbar, bid_price: float | None = None, historical: bool = False) -> Trendbar:
|
|
60
|
+
"""Create a Trendbar from a proto message.
|
|
61
61
|
|
|
62
62
|
Args:
|
|
63
|
-
proto:
|
|
64
|
-
bid_price:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
A new Trendbar instance.
|
|
63
|
+
proto: Source proto message.
|
|
64
|
+
bid_price: Bid price used as close when the API omits delta_close on live updates.
|
|
65
|
+
historical: If False, raises on delta_close == 0 instead of silently returning low == close.
|
|
69
66
|
"""
|
|
67
|
+
|
|
70
68
|
# Calculate timestamp from utc_timestamp_in_minutes
|
|
71
69
|
ts = datetime.now(UTC)
|
|
72
70
|
if proto.utc_timestamp_in_minutes:
|
|
@@ -80,12 +78,14 @@ class Trendbar(FrozenModel):
|
|
|
80
78
|
if bid_price is not None:
|
|
81
79
|
close = bid_price
|
|
82
80
|
else:
|
|
83
|
-
if proto.delta_close == 0:
|
|
84
|
-
# If delta_close is 0, it means the API did not provide a close price
|
|
81
|
+
if not historical and proto.delta_close == 0:
|
|
82
|
+
# If delta_close is 0 on a live bar, it means the API did not provide a close price.
|
|
85
83
|
# In this case, we must raise instead of silently returning an incorrect close price.
|
|
84
|
+
# If historical is True, we allow delta_close to be 0 since historical bars can have a close price equal
|
|
85
|
+
# to the low price.
|
|
86
86
|
raise RuntimeError(
|
|
87
87
|
"delta_close missing from Trendbar proto and no bid_price was provided to use as fallback."
|
|
88
|
-
"Returning error as this is safer than silently returning an incorrect close price of low + 0."
|
|
88
|
+
" Returning error as this is safer than silently returning an incorrect close price of low + 0."
|
|
89
89
|
)
|
|
90
90
|
close = low + proto.delta_close
|
|
91
91
|
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/connection/test_heartbeat.py
RENAMED
|
@@ -6,9 +6,10 @@ import time
|
|
|
6
6
|
from unittest.mock import AsyncMock, MagicMock
|
|
7
7
|
|
|
8
8
|
import anyio
|
|
9
|
+
import betterproto
|
|
9
10
|
import pytest
|
|
10
11
|
|
|
11
|
-
from ctrader_api_client._internal.proto import ProtoHeartbeatEvent
|
|
12
|
+
from ctrader_api_client._internal.proto import ProtoHeartbeatEvent, ProtoOAVersionRes
|
|
12
13
|
from ctrader_api_client.connection.heartbeat import HeartbeatManager
|
|
13
14
|
from ctrader_api_client.connection.protocol import Protocol
|
|
14
15
|
|
|
@@ -55,10 +56,10 @@ class TestHeartbeatStart:
|
|
|
55
56
|
await heartbeat.start()
|
|
56
57
|
|
|
57
58
|
try:
|
|
58
|
-
|
|
59
|
-
mock_protocol.on_event.
|
|
60
|
-
|
|
61
|
-
assert
|
|
59
|
+
assert mock_protocol.on_event.call_count == 2
|
|
60
|
+
registered_types = [call[0][0] for call in mock_protocol.on_event.call_args_list]
|
|
61
|
+
assert betterproto.Message in registered_types
|
|
62
|
+
assert ProtoHeartbeatEvent in registered_types
|
|
62
63
|
finally:
|
|
63
64
|
await heartbeat.stop()
|
|
64
65
|
|
|
@@ -86,9 +87,10 @@ class TestHeartbeatStop:
|
|
|
86
87
|
await heartbeat.start()
|
|
87
88
|
await heartbeat.stop()
|
|
88
89
|
|
|
89
|
-
mock_protocol.remove_handler.
|
|
90
|
-
|
|
91
|
-
assert
|
|
90
|
+
assert mock_protocol.remove_handler.call_count == 2
|
|
91
|
+
removed_types = [call[0][0] for call in mock_protocol.remove_handler.call_args_list]
|
|
92
|
+
assert betterproto.Message in removed_types
|
|
93
|
+
assert ProtoHeartbeatEvent in removed_types
|
|
92
94
|
|
|
93
95
|
@pytest.mark.anyio
|
|
94
96
|
async def test_stop_cancels_loop(self, mock_protocol: MagicMock) -> None:
|
|
@@ -105,16 +107,27 @@ class TestHeartbeatReceived:
|
|
|
105
107
|
"""Tests for heartbeat event handling."""
|
|
106
108
|
|
|
107
109
|
@pytest.mark.anyio
|
|
108
|
-
async def
|
|
110
|
+
async def test_record_activity_updates_last_received(self, mock_protocol: MagicMock) -> None:
|
|
109
111
|
heartbeat = HeartbeatManager(mock_protocol)
|
|
110
112
|
|
|
111
113
|
initial_time = time.monotonic() - 100 # Set to past
|
|
112
114
|
heartbeat._last_received = initial_time
|
|
113
115
|
|
|
114
|
-
await heartbeat.
|
|
116
|
+
await heartbeat._record_activity(ProtoHeartbeatEvent())
|
|
115
117
|
|
|
116
118
|
assert heartbeat._last_received > initial_time
|
|
117
119
|
|
|
120
|
+
@pytest.mark.anyio
|
|
121
|
+
async def test_on_heartbeat_does_not_update_last_received(self, mock_protocol: MagicMock) -> None:
|
|
122
|
+
heartbeat = HeartbeatManager(mock_protocol)
|
|
123
|
+
|
|
124
|
+
initial_time = time.monotonic() - 100
|
|
125
|
+
heartbeat._last_received = initial_time
|
|
126
|
+
|
|
127
|
+
await heartbeat._on_heartbeat(ProtoHeartbeatEvent())
|
|
128
|
+
|
|
129
|
+
assert heartbeat._last_received == initial_time
|
|
130
|
+
|
|
118
131
|
|
|
119
132
|
class TestHeartbeatSend:
|
|
120
133
|
"""Tests for heartbeat sending."""
|
|
@@ -163,15 +176,28 @@ class TestHeartbeatTimeout:
|
|
|
163
176
|
await heartbeat.start()
|
|
164
177
|
|
|
165
178
|
try:
|
|
166
|
-
# Simulate receiving heartbeats
|
|
167
179
|
for _ in range(3):
|
|
168
180
|
await anyio.sleep(0.03)
|
|
169
|
-
await heartbeat.
|
|
181
|
+
await heartbeat._record_activity(ProtoHeartbeatEvent())
|
|
182
|
+
finally:
|
|
183
|
+
await heartbeat.stop()
|
|
184
|
+
|
|
185
|
+
mock_protocol.handle_disconnect.assert_not_called()
|
|
186
|
+
|
|
187
|
+
@pytest.mark.anyio
|
|
188
|
+
async def test_no_timeout_when_non_heartbeat_messages_received(self, mock_protocol: MagicMock) -> None:
|
|
189
|
+
heartbeat = HeartbeatManager(mock_protocol, interval=0.02, timeout=0.1)
|
|
190
|
+
|
|
191
|
+
await heartbeat.start()
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
for _ in range(3):
|
|
195
|
+
await anyio.sleep(0.03)
|
|
196
|
+
await heartbeat._record_activity(ProtoOAVersionRes())
|
|
170
197
|
finally:
|
|
171
198
|
await heartbeat.stop()
|
|
172
199
|
|
|
173
|
-
|
|
174
|
-
mock_protocol._handle_disconnect.assert_not_called()
|
|
200
|
+
mock_protocol.handle_disconnect.assert_not_called()
|
|
175
201
|
|
|
176
202
|
|
|
177
203
|
class TestHeartbeatSendFailure:
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/connection/test_protocol.py
RENAMED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from unittest.mock import AsyncMock, MagicMock
|
|
6
6
|
|
|
7
7
|
import anyio
|
|
8
|
+
import betterproto
|
|
8
9
|
import pytest
|
|
9
10
|
|
|
10
11
|
from ctrader_api_client._internal.proto import (
|
|
@@ -348,6 +349,28 @@ class TestMessageDispatch:
|
|
|
348
349
|
|
|
349
350
|
assert len(received) == 1
|
|
350
351
|
|
|
352
|
+
@pytest.mark.anyio
|
|
353
|
+
async def test_dispatch_triggers_base_class_handler(self, mock_transport: MagicMock) -> None:
|
|
354
|
+
protocol = Protocol(mock_transport)
|
|
355
|
+
|
|
356
|
+
received: list[betterproto.Message] = []
|
|
357
|
+
|
|
358
|
+
async def catch_all(message: betterproto.Message) -> None:
|
|
359
|
+
received.append(message)
|
|
360
|
+
|
|
361
|
+
protocol.on_event(betterproto.Message, catch_all)
|
|
362
|
+
|
|
363
|
+
proto_msg = ProtoMessage(
|
|
364
|
+
payload_type=ProtoPayloadType.HEARTBEAT_EVENT,
|
|
365
|
+
client_msg_id="",
|
|
366
|
+
)
|
|
367
|
+
inner = ProtoHeartbeatEvent()
|
|
368
|
+
|
|
369
|
+
await protocol._dispatch_message(proto_msg, inner)
|
|
370
|
+
|
|
371
|
+
assert len(received) == 1
|
|
372
|
+
assert isinstance(received[0], ProtoHeartbeatEvent)
|
|
373
|
+
|
|
351
374
|
|
|
352
375
|
class TestReconnect:
|
|
353
376
|
"""Tests for reconnection logic."""
|
|
@@ -62,7 +62,7 @@ class TestTrendbarFromProto:
|
|
|
62
62
|
|
|
63
63
|
for proto_value, expected_period in test_cases:
|
|
64
64
|
proto.period = proto_value
|
|
65
|
-
bar = Trendbar.from_proto(proto)
|
|
65
|
+
bar = Trendbar.from_proto(proto, bid_price=10)
|
|
66
66
|
assert bar.period == expected_period
|
|
67
67
|
|
|
68
68
|
def test_from_proto_converts_timestamp(self) -> None:
|
|
@@ -77,7 +77,7 @@ class TestTrendbarFromProto:
|
|
|
77
77
|
proto.delta_close = 0
|
|
78
78
|
proto.volume = 1000
|
|
79
79
|
|
|
80
|
-
bar = Trendbar.from_proto(proto)
|
|
80
|
+
bar = Trendbar.from_proto(proto, bid_price=10)
|
|
81
81
|
|
|
82
82
|
assert bar.timestamp == datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC)
|
|
83
83
|
|
|
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
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/OpenApiCommonMessages.proto
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/OpenApiCommonModelMessages.proto
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/protos/vendor/OpenApiModelMessages.proto
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/_internal/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/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.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/api/accounts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/auth/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/auth/credentials.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/auth/manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/connection/transport.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/emitter.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/router.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/events/types.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/__init__.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/_base.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/account.py
RENAMED
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/order.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/position.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/requests.py
RENAMED
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/src/ctrader_api_client/models/symbol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/_internal/test_serialization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/api/test_market_data_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctrader_api_client-0.2.1 → ctrader_api_client-0.2.3}/tests/unit/connection/test_transport.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
|