mt5cli 0.8.1__tar.gz → 0.8.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 (56) hide show
  1. {mt5cli-0.8.1 → mt5cli-0.8.2}/PKG-INFO +2 -2
  2. {mt5cli-0.8.1 → mt5cli-0.8.2}/README.md +1 -1
  3. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/public-contract.md +10 -10
  4. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/sdk.md +22 -0
  5. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/sdk.py +23 -5
  6. {mt5cli-0.8.1 → mt5cli-0.8.2}/pyproject.toml +1 -1
  7. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/test_sdk.py +151 -0
  8. {mt5cli-0.8.1 → mt5cli-0.8.2}/uv.lock +1 -1
  9. {mt5cli-0.8.1 → mt5cli-0.8.2}/.agents/skills/local-qa/SKILL.md +0 -0
  10. {mt5cli-0.8.1 → mt5cli-0.8.2}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  11. {mt5cli-0.8.1 → mt5cli-0.8.2}/.agents/skills/mt5cli/SKILL.md +0 -0
  12. {mt5cli-0.8.1 → mt5cli-0.8.2}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  13. {mt5cli-0.8.1 → mt5cli-0.8.2}/.claude/agents/codex.md +0 -0
  14. {mt5cli-0.8.1 → mt5cli-0.8.2}/.claude/settings.json +0 -0
  15. {mt5cli-0.8.1 → mt5cli-0.8.2}/.github/FUNDING.yml +0 -0
  16. {mt5cli-0.8.1 → mt5cli-0.8.2}/.github/dependabot.yml +0 -0
  17. {mt5cli-0.8.1 → mt5cli-0.8.2}/.github/renovate.json +0 -0
  18. {mt5cli-0.8.1 → mt5cli-0.8.2}/.github/workflows/ci.yml +0 -0
  19. {mt5cli-0.8.1 → mt5cli-0.8.2}/.github/workflows/claude.yml +0 -0
  20. {mt5cli-0.8.1 → mt5cli-0.8.2}/.github/workflows/release.yml +0 -0
  21. {mt5cli-0.8.1 → mt5cli-0.8.2}/.gitignore +0 -0
  22. {mt5cli-0.8.1 → mt5cli-0.8.2}/AGENTS.md +0 -0
  23. {mt5cli-0.8.1 → mt5cli-0.8.2}/CLAUDE.md +0 -0
  24. {mt5cli-0.8.1 → mt5cli-0.8.2}/LICENSE +0 -0
  25. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/cli.md +0 -0
  26. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/client.md +0 -0
  27. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/converters.md +0 -0
  28. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/exceptions.md +0 -0
  29. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/history.md +0 -0
  30. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/index.md +0 -0
  31. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/schemas.md +0 -0
  32. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/storage.md +0 -0
  33. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/trading.md +0 -0
  34. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/api/utils.md +0 -0
  35. {mt5cli-0.8.1 → mt5cli-0.8.2}/docs/index.md +0 -0
  36. {mt5cli-0.8.1 → mt5cli-0.8.2}/mkdocs.yml +0 -0
  37. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/__init__.py +0 -0
  38. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/__main__.py +0 -0
  39. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/cli.py +0 -0
  40. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/client.py +0 -0
  41. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/contract.py +0 -0
  42. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/converters.py +0 -0
  43. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/exceptions.py +0 -0
  44. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/history.py +0 -0
  45. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/retry.py +0 -0
  46. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/schemas.py +0 -0
  47. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/storage.py +0 -0
  48. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/trading.py +0 -0
  49. {mt5cli-0.8.1 → mt5cli-0.8.2}/mt5cli/utils.py +0 -0
  50. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/__init__.py +0 -0
  51. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/conftest.py +0 -0
  52. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/test_cli.py +0 -0
  53. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/test_contracts.py +0 -0
  54. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/test_history.py +0 -0
  55. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/test_trading.py +0 -0
  56. {mt5cli-0.8.1 → mt5cli-0.8.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Generic MT5 data and execution infrastructure for Python applications
5
5
  Project-URL: Repository, https://github.com/dceoy/mt5cli.git
6
6
  Author-email: dceoy <dceoy@users.noreply.github.com>
@@ -273,7 +273,7 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
273
273
  ```
274
274
 
275
275
  - **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code.
276
- - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate).
276
+ - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.sdk.update_history`.
277
277
  - **Trading session helpers**: use `mt5_trading_session()` for a trading-capable `pdmt5.Mt5TradingClient` that initializes/logs in via `Mt5Config.path` and always shuts down safely. Pair with `detect_position_side()`, `calculate_margin_and_volume()`, and `determine_order_limits()` for generic position and sizing utilities. The read-only `mt5_session()` / `Mt5CliClient` SDK is unchanged.
278
278
  - **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
279
279
  - **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `MT5Client` that shuts down on exit.
@@ -249,7 +249,7 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
249
249
  ```
250
250
 
251
251
  - **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code.
252
- - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate).
252
+ - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.sdk.update_history`.
253
253
  - **Trading session helpers**: use `mt5_trading_session()` for a trading-capable `pdmt5.Mt5TradingClient` that initializes/logs in via `Mt5Config.path` and always shuts down safely. Pair with `detect_position_side()`, `calculate_margin_and_volume()`, and `determine_order_limits()` for generic position and sizing utilities. The read-only `mt5_session()` / `Mt5CliClient` SDK is unchanged.
254
254
  - **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
255
255
  - **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `MT5Client` that shuts down on exit.
@@ -67,16 +67,16 @@ timestamp normalization in downstream apps.
67
67
 
68
68
  ### SQLite history collection and rate loading
69
69
 
70
- | Symbol | Role |
71
- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
72
- | `collect_history` | One-shot date-range export into SQLite |
73
- | `update_history`, `update_history_with_config` | Incremental append from `MAX(time)` cursors |
74
- | `ThrottledHistoryUpdater` | Minimum interval between successful incremental updates |
75
- | `resolve_history_datasets`, `resolve_history_timeframes`, `resolve_history_tick_flags` | History pipeline configuration |
76
- | `build_rate_view_name`, `resolve_rate_table_name`, `resolve_rate_view_name`, `resolve_rate_view_names`, `resolve_rate_tables` | Map symbols/timeframes to mt5cli-managed table or view names |
77
- | `RateTarget`, `build_rate_targets` | Neutral `(symbol, timeframe)` series descriptors |
78
- | `load_rate_data`, `load_rate_data_from_connection` | Load one table/view into a time-indexed DataFrame |
79
- | `load_rate_series_from_sqlite`, `load_rate_series_by_granularity` | Load one or many series; fail clearly when managed views are missing |
70
+ | Symbol | Role |
71
+ | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
72
+ | `collect_history` | One-shot date-range export into SQLite |
73
+ | `update_history`, `update_history_with_config` | Incremental append from `MAX(time)` cursors |
74
+ | `ThrottledHistoryUpdater` | Minimum interval between successful incremental updates; optional `update_backend` injection |
75
+ | `resolve_history_datasets`, `resolve_history_timeframes`, `resolve_history_tick_flags` | History pipeline configuration |
76
+ | `build_rate_view_name`, `resolve_rate_table_name`, `resolve_rate_view_name`, `resolve_rate_view_names`, `resolve_rate_tables` | Map symbols/timeframes to mt5cli-managed table or view names |
77
+ | `RateTarget`, `build_rate_targets` | Neutral `(symbol, timeframe)` series descriptors |
78
+ | `load_rate_data`, `load_rate_data_from_connection` | Load one table/view into a time-indexed DataFrame |
79
+ | `load_rate_series_from_sqlite`, `load_rate_series_by_granularity` | Load one or many series; fail clearly when managed views are missing |
80
80
 
81
81
  Pass `require_existing=True` to rate view resolution helpers when downstream
82
82
  code must fail instead of receiving a best-guess view name. Multi-series loaders
@@ -113,6 +113,28 @@ finally:
113
113
  client.shutdown()
114
114
  ```
115
115
 
116
+ Pass `update_backend` to substitute the default `update_history` implementation
117
+ without monkey-patching `mt5cli.sdk.update_history`. The callable receives the
118
+ same keyword arguments as `update_history` (`client`, `output`, `symbols`,
119
+ `datasets`, `timeframes`, `flags`, `lookback_hours`, `with_views`,
120
+ `include_account_events`). The resolved backend is stored on
121
+ `updater.update_backend` for inspection or subclassing.
122
+
123
+ ```python
124
+ from mt5cli import ThrottledHistoryUpdater, update_history
125
+
126
+
127
+ def app_update_history(**kwargs) -> None:
128
+ update_history(**kwargs) # or delegate to application-specific logic
129
+
130
+
131
+ updater = ThrottledHistoryUpdater(
132
+ output="history.db",
133
+ interval_seconds=60,
134
+ update_backend=app_update_history,
135
+ )
136
+ ```
137
+
116
138
  By default recoverable errors (`Mt5TradingError`, `Mt5RuntimeError`,
117
139
  `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability
118
140
  `AttributeError` / `TypeError` for history API methods) propagate so the caller
@@ -42,6 +42,8 @@ from .utils import coerce_login as _coerce_login
42
42
  if TYPE_CHECKING:
43
43
  from collections.abc import Callable, Iterator, Sequence
44
44
 
45
+ UpdateHistoryBackend = Callable[..., None]
46
+
45
47
  T = TypeVar("T")
46
48
 
47
49
  logger = logging.getLogger(__name__)
@@ -1044,10 +1046,16 @@ def update_history_with_config( # noqa: PLR0913
1044
1046
  class ThrottledHistoryUpdater:
1045
1047
  """Throttled incremental SQLite history updater for long-running apps.
1046
1048
 
1047
- Wraps :func:`update_history` with a minimum interval between successful
1048
- updates, so a tight application loop can call :meth:`update` every
1049
- iteration without re-fetching MT5 history more often than desired. Timing
1050
- uses a monotonic clock, so it is unaffected by wall-clock changes.
1049
+ Wraps :func:`update_history` (or a custom ``update_backend``) with a minimum
1050
+ interval between successful updates, so a tight application loop can call
1051
+ :meth:`update` every iteration without re-fetching MT5 history more often
1052
+ than desired. Timing uses a monotonic clock, so it is unaffected by
1053
+ wall-clock changes.
1054
+
1055
+ Downstream applications may pass ``update_backend`` to substitute the
1056
+ default :func:`update_history` implementation—for example to add
1057
+ application-specific logging, metrics, or test doubles—without monkey-
1058
+ patching ``mt5cli.sdk.update_history``.
1051
1059
  """
1052
1060
 
1053
1061
  def __init__(
@@ -1062,6 +1070,7 @@ class ThrottledHistoryUpdater:
1062
1070
  include_account_events: bool = True,
1063
1071
  interval_seconds: float = 0.0,
1064
1072
  suppress_errors: bool = False,
1073
+ update_backend: UpdateHistoryBackend | None = None,
1065
1074
  ) -> None:
1066
1075
  """Initialize the throttled updater.
1067
1076
 
@@ -1085,6 +1094,12 @@ class ThrottledHistoryUpdater:
1085
1094
  the throttle. Other ``AttributeError`` / ``TypeError`` values
1086
1095
  always propagate. When False (default), recoverable errors
1087
1096
  propagate so callers control logging.
1097
+ update_backend: Callable invoked instead of :func:`update_history`
1098
+ when :meth:`update` runs. Receives the same keyword arguments as
1099
+ :func:`update_history` (``client``, ``output``, ``symbols``,
1100
+ ``datasets``, ``timeframes``, ``flags``, ``lookback_hours``,
1101
+ ``with_views``, ``include_account_events``). Defaults to
1102
+ :func:`update_history`.
1088
1103
  """
1089
1104
  self.output = output
1090
1105
  self.datasets = datasets
@@ -1095,6 +1110,9 @@ class ThrottledHistoryUpdater:
1095
1110
  self.include_account_events = include_account_events
1096
1111
  self.interval_seconds = interval_seconds
1097
1112
  self.suppress_errors = suppress_errors
1113
+ self.update_backend = (
1114
+ update_history if update_backend is None else update_backend
1115
+ )
1098
1116
  self._last_update_monotonic: float | None = None
1099
1117
 
1100
1118
  @property
@@ -1145,7 +1163,7 @@ class ThrottledHistoryUpdater:
1145
1163
  lookback_hours=self.lookback_hours,
1146
1164
  date_to=None,
1147
1165
  )
1148
- update_history(
1166
+ self.update_backend(
1149
1167
  client=client,
1150
1168
  output=self.output,
1151
1169
  symbols=symbols,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mt5cli"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  description = "Generic MT5 data and execution infrastructure for Python applications"
5
5
  authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
6
6
  maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
@@ -2100,3 +2100,154 @@ class TestThrottledHistoryUpdater:
2100
2100
  assert updater.update(MagicMock(), []) is False
2101
2101
  update.assert_not_called()
2102
2102
  assert updater.last_update_monotonic is None
2103
+
2104
+ def test_default_update_backend_is_update_history(self) -> None:
2105
+ """Test the default backend resolves to update_history."""
2106
+ updater = ThrottledHistoryUpdater(output="history.db")
2107
+ assert updater.update_backend is update_history
2108
+
2109
+ def test_falsy_callable_update_backend_is_preserved(
2110
+ self,
2111
+ mocker: MockerFixture,
2112
+ ) -> None:
2113
+ """Test only None selects the default backend, not falsy callables."""
2114
+
2115
+ class FalsyCallable:
2116
+ def __init__(self) -> None:
2117
+ self.calls: list[dict[str, object]] = []
2118
+
2119
+ def __bool__(self) -> bool:
2120
+ return False
2121
+
2122
+ def __call__(self, **kwargs: object) -> None:
2123
+ self.calls.append(kwargs)
2124
+
2125
+ falsy_backend = FalsyCallable()
2126
+ default_backend = mocker.patch("mt5cli.sdk.update_history")
2127
+ updater = ThrottledHistoryUpdater(
2128
+ output="history.db",
2129
+ update_backend=falsy_backend,
2130
+ )
2131
+
2132
+ assert updater.update_backend is falsy_backend
2133
+ client = MagicMock()
2134
+ assert updater.update(client, ["EURUSD"]) is True
2135
+ assert len(falsy_backend.calls) == 1
2136
+ assert falsy_backend.calls[0]["client"] is client
2137
+ assert falsy_backend.calls[0]["symbols"] == ["EURUSD"]
2138
+ default_backend.assert_not_called()
2139
+
2140
+ def test_custom_update_backend_receives_expected_kwargs(
2141
+ self,
2142
+ mocker: MockerFixture,
2143
+ ) -> None:
2144
+ """Test a custom backend receives update_history keyword arguments."""
2145
+ backend = mocker.Mock()
2146
+ client = MagicMock()
2147
+ updater = ThrottledHistoryUpdater(
2148
+ output="history.db",
2149
+ datasets={Dataset.rates},
2150
+ timeframes=["M1", "H1"],
2151
+ flags="INFO",
2152
+ lookback_hours=12.0,
2153
+ with_views=True,
2154
+ include_account_events=False,
2155
+ update_backend=backend,
2156
+ )
2157
+
2158
+ updater.update(client, ["EURUSD", "GBPUSD"])
2159
+
2160
+ backend.assert_called_once_with(
2161
+ client=client,
2162
+ output="history.db",
2163
+ symbols=["EURUSD", "GBPUSD"],
2164
+ datasets={Dataset.rates},
2165
+ timeframes=["M1", "H1"],
2166
+ flags="INFO",
2167
+ lookback_hours=12.0,
2168
+ with_views=True,
2169
+ include_account_events=False,
2170
+ )
2171
+
2172
+ def test_throttled_calls_do_not_invoke_custom_backend(
2173
+ self,
2174
+ mocker: MockerFixture,
2175
+ ) -> None:
2176
+ """Test throttled update cycles skip the injected backend."""
2177
+ backend = mocker.Mock()
2178
+ monotonic = mocker.patch("mt5cli.sdk.time.monotonic")
2179
+ monotonic.side_effect = [100.0, 105.0, 200.0, 200.0]
2180
+ client = MagicMock()
2181
+ updater = ThrottledHistoryUpdater(
2182
+ output="history.db",
2183
+ interval_seconds=60,
2184
+ update_backend=backend,
2185
+ )
2186
+
2187
+ assert updater.update(client, ["EURUSD"]) is True
2188
+ assert updater.update(client, ["EURUSD"]) is False
2189
+ assert updater.update(client, ["EURUSD"]) is True
2190
+ assert backend.call_count == 2
2191
+
2192
+ def test_successful_custom_backend_advances_throttle(
2193
+ self,
2194
+ mocker: MockerFixture,
2195
+ ) -> None:
2196
+ """Test a successful custom backend updates _last_update_monotonic."""
2197
+ backend = mocker.Mock()
2198
+ monotonic = mocker.patch("mt5cli.sdk.time.monotonic", return_value=42.0)
2199
+ updater = ThrottledHistoryUpdater(
2200
+ output="history.db",
2201
+ update_backend=backend,
2202
+ )
2203
+
2204
+ assert updater.update(MagicMock(), ["EURUSD"]) is True
2205
+ assert updater.last_update_monotonic is monotonic.return_value
2206
+ monotonic.assert_called_once()
2207
+
2208
+ def test_failed_custom_backend_does_not_advance_throttle(
2209
+ self,
2210
+ mocker: MockerFixture,
2211
+ ) -> None:
2212
+ """Test a failing custom backend leaves _last_update_monotonic unchanged."""
2213
+ backend = mocker.Mock(side_effect=Mt5RuntimeError("boom"))
2214
+ updater = ThrottledHistoryUpdater(
2215
+ output="history.db",
2216
+ update_backend=backend,
2217
+ )
2218
+
2219
+ with pytest.raises(Mt5RuntimeError, match="boom"):
2220
+ updater.update(MagicMock(), ["EURUSD"])
2221
+
2222
+ assert updater.last_update_monotonic is None
2223
+
2224
+ def test_custom_backend_suppresses_recoverable_errors_when_requested(
2225
+ self,
2226
+ mocker: MockerFixture,
2227
+ ) -> None:
2228
+ """Test suppress_errors swallows recoverable custom backend errors."""
2229
+ backend = mocker.Mock(side_effect=Mt5RuntimeError("boom"))
2230
+ updater = ThrottledHistoryUpdater(
2231
+ output="history.db",
2232
+ suppress_errors=True,
2233
+ update_backend=backend,
2234
+ )
2235
+
2236
+ assert updater.update(MagicMock(), ["EURUSD"]) is False
2237
+ assert updater.last_update_monotonic is None
2238
+
2239
+ def test_custom_backend_propagates_errors_when_not_suppressed(
2240
+ self,
2241
+ mocker: MockerFixture,
2242
+ ) -> None:
2243
+ """Test recoverable custom backend errors propagate by default."""
2244
+ backend = mocker.Mock(side_effect=Mt5RuntimeError("boom"))
2245
+ updater = ThrottledHistoryUpdater(
2246
+ output="history.db",
2247
+ update_backend=backend,
2248
+ )
2249
+
2250
+ with pytest.raises(Mt5RuntimeError, match="boom"):
2251
+ updater.update(MagicMock(), ["EURUSD"])
2252
+
2253
+ assert updater.last_update_monotonic is None
@@ -487,7 +487,7 @@ wheels = [
487
487
 
488
488
  [[package]]
489
489
  name = "mt5cli"
490
- version = "0.8.1"
490
+ version = "0.8.2"
491
491
  source = { editable = "." }
492
492
  dependencies = [
493
493
  { name = "click" },
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
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