mt5cli 0.6.0__tar.gz → 0.7.0__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 (40) hide show
  1. {mt5cli-0.6.0 → mt5cli-0.7.0}/PKG-INFO +67 -3
  2. {mt5cli-0.6.0 → mt5cli-0.7.0}/README.md +65 -1
  3. {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/index.md +7 -2
  4. {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/sdk.md +14 -3
  5. mt5cli-0.7.0/docs/api/trading.md +70 -0
  6. {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/index.md +6 -0
  7. {mt5cli-0.6.0 → mt5cli-0.7.0}/mkdocs.yml +1 -0
  8. {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/__init__.py +10 -0
  9. {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/cli.py +2 -2
  10. {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/history.py +9 -9
  11. {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/sdk.py +86 -14
  12. mt5cli-0.7.0/mt5cli/trading.py +210 -0
  13. {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/utils.py +31 -50
  14. {mt5cli-0.6.0 → mt5cli-0.7.0}/pyproject.toml +2 -2
  15. {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_cli.py +5 -5
  16. {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_history.py +17 -1
  17. {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_sdk.py +144 -7
  18. mt5cli-0.7.0/tests/test_trading.py +356 -0
  19. {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_utils.py +51 -14
  20. {mt5cli-0.6.0 → mt5cli-0.7.0}/uv.lock +5 -5
  21. {mt5cli-0.6.0 → mt5cli-0.7.0}/.agents/skills/local-qa/SKILL.md +0 -0
  22. {mt5cli-0.6.0 → mt5cli-0.7.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  23. {mt5cli-0.6.0 → mt5cli-0.7.0}/.agents/skills/mt5cli/SKILL.md +0 -0
  24. {mt5cli-0.6.0 → mt5cli-0.7.0}/.claude/agents/codex.md +0 -0
  25. {mt5cli-0.6.0 → mt5cli-0.7.0}/.claude/settings.json +0 -0
  26. {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/FUNDING.yml +0 -0
  27. {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/dependabot.yml +0 -0
  28. {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/renovate.json +0 -0
  29. {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/workflows/ci.yml +0 -0
  30. {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/workflows/claude.yml +0 -0
  31. {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/workflows/release.yml +0 -0
  32. {mt5cli-0.6.0 → mt5cli-0.7.0}/.gitignore +0 -0
  33. {mt5cli-0.6.0 → mt5cli-0.7.0}/AGENTS.md +0 -0
  34. {mt5cli-0.6.0 → mt5cli-0.7.0}/CLAUDE.md +0 -0
  35. {mt5cli-0.6.0 → mt5cli-0.7.0}/LICENSE +0 -0
  36. {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/cli.md +0 -0
  37. {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/history.md +0 -0
  38. {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/utils.md +0 -0
  39. {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/__main__.py +0 -0
  40. {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Command-line tool for MetaTrader 5
5
5
  Project-URL: Repository, https://github.com/dceoy/mt5cli.git
6
6
  Author-email: dceoy <dceoy@users.noreply.github.com>
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Topic :: Office/Business :: Financial :: Investment
18
18
  Requires-Python: <3.14,>=3.11
19
19
  Requires-Dist: click>=8.1.0
20
- Requires-Dist: pdmt5>=0.2.3
20
+ Requires-Dist: pdmt5>=0.3.0
21
21
  Requires-Dist: pyarrow>=19.0.0
22
22
  Requires-Dist: typer>=0.15.0
23
23
  Description-Content-Type: text/markdown
@@ -30,6 +30,12 @@ Command-line tool for exporting MetaTrader 5 data to CSV, JSON, Parquet, and SQL
30
30
 
31
31
  Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5.
32
32
 
33
+ ## Architecture
34
+
35
+ - **pdmt5** — canonical MT5 client, DataFrame/trading primitives, and MT5 constant parsing (`TIMEFRAME_*`, `COPY_TICKS_*`, order types).
36
+ - **mt5cli** — CLI commands, CSV/JSON/Parquet/SQLite export, SQLite history collection, rate views, and local batch/automation SDK helpers built on pdmt5.
37
+ - **mt5api** — sibling HTTP adapter for remote MT5 access; not a dependency of mt5cli.
38
+
33
39
  ## Features
34
40
 
35
41
  - **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats
@@ -176,7 +182,8 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
176
182
  ```
177
183
 
178
184
  - **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.
179
- - **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` and let the caller decide logging.
185
+ - **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).
186
+ - **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.
180
187
  - **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.
181
188
  - **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 `Mt5CliClient` that shuts down on exit.
182
189
  - **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
@@ -188,6 +195,63 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
188
195
  - Windows OS (MetaTrader 5 requirement)
189
196
  - MetaTrader 5 platform installed
190
197
 
198
+ ### Migration note for mteor
199
+
200
+ Replace local MT5 lifecycle and trading helper code with mt5cli imports:
201
+
202
+ ```python
203
+ # Before (local mteor helpers)
204
+ # with local_mt5_trading_session(config) as client:
205
+ # side = local_detect_position_side(client, symbol)
206
+ # sizing = local_calculate_margin_and_volume(client, symbol, unit_ratio, preserved_ratio)
207
+ # limits = local_determine_order_limits(client, symbol, side, sl_ratio, tp_ratio)
208
+
209
+ # After (mt5cli shared layer)
210
+ from pdmt5 import Mt5Config
211
+ from mt5cli import (
212
+ calculate_margin_and_volume,
213
+ detect_position_side,
214
+ determine_order_limits,
215
+ mt5_trading_session,
216
+ )
217
+
218
+ with mt5_trading_session(
219
+ Mt5Config(path=terminal_path, login=login), retry_count=2
220
+ ) as client:
221
+ side = detect_position_side(client, symbol)
222
+ sizing = calculate_margin_and_volume(
223
+ client, symbol, unit_margin_ratio=0.5, preserved_margin_ratio=0.2
224
+ )
225
+ if side is not None:
226
+ limits = determine_order_limits(
227
+ client,
228
+ symbol,
229
+ side,
230
+ stop_loss_limit_ratio=0.01,
231
+ take_profit_limit_ratio=0.02,
232
+ )
233
+ ```
234
+
235
+ Throttled history updates use a separate read-only session:
236
+
237
+ ```python
238
+ from pdmt5 import Mt5Config, Mt5DataClient
239
+
240
+ from mt5cli import ThrottledHistoryUpdater
241
+
242
+ updater = ThrottledHistoryUpdater(
243
+ output="history.db", interval_seconds=60, suppress_errors=True
244
+ )
245
+ client = Mt5DataClient(config=Mt5Config(login=login))
246
+ client.initialize_and_login_mt5()
247
+ try:
248
+ updater.update(client, ["EURUSD"])
249
+ finally:
250
+ client.shutdown()
251
+ ```
252
+
253
+ Read-only collectors can keep using `mt5_session()` and `Mt5CliClient` without changes.
254
+
191
255
  ## Development
192
256
 
193
257
  ```bash
@@ -6,6 +6,12 @@ Command-line tool for exporting MetaTrader 5 data to CSV, JSON, Parquet, and SQL
6
6
 
7
7
  Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5.
8
8
 
9
+ ## Architecture
10
+
11
+ - **pdmt5** — canonical MT5 client, DataFrame/trading primitives, and MT5 constant parsing (`TIMEFRAME_*`, `COPY_TICKS_*`, order types).
12
+ - **mt5cli** — CLI commands, CSV/JSON/Parquet/SQLite export, SQLite history collection, rate views, and local batch/automation SDK helpers built on pdmt5.
13
+ - **mt5api** — sibling HTTP adapter for remote MT5 access; not a dependency of mt5cli.
14
+
9
15
  ## Features
10
16
 
11
17
  - **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats
@@ -152,7 +158,8 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
152
158
  ```
153
159
 
154
160
  - **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.
155
- - **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` and let the caller decide logging.
161
+ - **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).
162
+ - **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.
156
163
  - **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.
157
164
  - **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 `Mt5CliClient` that shuts down on exit.
158
165
  - **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
@@ -164,6 +171,63 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
164
171
  - Windows OS (MetaTrader 5 requirement)
165
172
  - MetaTrader 5 platform installed
166
173
 
174
+ ### Migration note for mteor
175
+
176
+ Replace local MT5 lifecycle and trading helper code with mt5cli imports:
177
+
178
+ ```python
179
+ # Before (local mteor helpers)
180
+ # with local_mt5_trading_session(config) as client:
181
+ # side = local_detect_position_side(client, symbol)
182
+ # sizing = local_calculate_margin_and_volume(client, symbol, unit_ratio, preserved_ratio)
183
+ # limits = local_determine_order_limits(client, symbol, side, sl_ratio, tp_ratio)
184
+
185
+ # After (mt5cli shared layer)
186
+ from pdmt5 import Mt5Config
187
+ from mt5cli import (
188
+ calculate_margin_and_volume,
189
+ detect_position_side,
190
+ determine_order_limits,
191
+ mt5_trading_session,
192
+ )
193
+
194
+ with mt5_trading_session(
195
+ Mt5Config(path=terminal_path, login=login), retry_count=2
196
+ ) as client:
197
+ side = detect_position_side(client, symbol)
198
+ sizing = calculate_margin_and_volume(
199
+ client, symbol, unit_margin_ratio=0.5, preserved_margin_ratio=0.2
200
+ )
201
+ if side is not None:
202
+ limits = determine_order_limits(
203
+ client,
204
+ symbol,
205
+ side,
206
+ stop_loss_limit_ratio=0.01,
207
+ take_profit_limit_ratio=0.02,
208
+ )
209
+ ```
210
+
211
+ Throttled history updates use a separate read-only session:
212
+
213
+ ```python
214
+ from pdmt5 import Mt5Config, Mt5DataClient
215
+
216
+ from mt5cli import ThrottledHistoryUpdater
217
+
218
+ updater = ThrottledHistoryUpdater(
219
+ output="history.db", interval_seconds=60, suppress_errors=True
220
+ )
221
+ client = Mt5DataClient(config=Mt5Config(login=login))
222
+ client.initialize_and_login_mt5()
223
+ try:
224
+ updater.update(client, ["EURUSD"])
225
+ finally:
226
+ client.shutdown()
227
+ ```
228
+
229
+ Read-only collectors can keep using `mt5_session()` and `Mt5CliClient` without changes.
230
+
167
231
  ## Development
168
232
 
169
233
  ```bash
@@ -18,6 +18,10 @@ Utility module providing constants, enums, Click parameter types, and helper fun
18
18
 
19
19
  Programmatic SDK for read-only MetaTrader 5 data collection. Returns pandas DataFrames and provides `collect_history` for SQLite bulk collection.
20
20
 
21
+ ### [Trading](trading.md)
22
+
23
+ Trading-capable session management and operational helpers built on `pdmt5.Mt5TradingClient`. Complements the read-only SDK without changing existing `Mt5CliClient` behavior.
24
+
21
25
  ### [History Collection (SQLite)](history.md)
22
26
 
23
27
  SQLite storage helpers for the `collect-history` command schema, incremental updates, deduplication, indexes, and optional views.
@@ -28,8 +32,9 @@ The package follows a simple architecture built on top of pdmt5:
28
32
 
29
33
  1. **CLI Layer** (`cli.py`): Typer application with subcommands that delegate to the SDK and export results.
30
34
  2. **SDK Layer** (`sdk.py`): Read-only data access functions, `Mt5CliClient`, and `collect_history` orchestration.
31
- 3. **Utils Layer** (`utils.py`): Constants, enums, custom Click parameter types, parsing helpers, and format detection/export utilities.
32
- 4. **Data Layer** (via `pdmt5`): Uses `Mt5DataClient` and `Mt5Config` from the pdmt5 package for all MetaTrader 5 data access.
35
+ 3. **Trading Layer** (`trading.py`): Trading-capable sessions and operational helpers on `Mt5TradingClient`.
36
+ 4. **Utils Layer** (`utils.py`): Constants, enums, custom Click parameter types, parsing helpers, and format detection/export utilities.
37
+ 5. **Data Layer** (via `pdmt5`): Uses `Mt5DataClient`, `Mt5TradingClient`, and `Mt5Config` from the pdmt5 package for MetaTrader 5 access.
33
38
 
34
39
  ## Usage Guidelines
35
40
 
@@ -98,6 +98,17 @@ finally:
98
98
  client.shutdown()
99
99
  ```
100
100
 
101
- By default `Mt5TradingError`, `Mt5RuntimeError`, and `sqlite3.Error` propagate so
102
- the caller controls logging; pass `suppress_errors=True` to swallow them and
103
- return `False` without advancing the throttle.
101
+ By default recoverable errors (`Mt5TradingError`, `Mt5RuntimeError`,
102
+ `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability
103
+ `AttributeError` / `TypeError` for history API methods) propagate so the caller
104
+ controls logging; pass `suppress_errors=True` to swallow them and return
105
+ `False` without advancing the throttle. Other `AttributeError` / `TypeError`
106
+ values always propagate. Input validation (`_resolve_update_history_request`)
107
+ runs before any MT5 or SQLite calls, but when `suppress_errors=True` the
108
+ resulting `ValueError` is suppressed along with other recoverable errors.
109
+
110
+ ## Trading-capable sessions
111
+
112
+ For order placement and trading calculations, use the dedicated
113
+ [Trading module](trading.md). The read-only `Mt5CliClient` and `mt5_session()`
114
+ helpers in this module are unchanged.
@@ -0,0 +1,70 @@
1
+ # Trading Module
2
+
3
+ ::: mt5cli.trading
4
+
5
+ ## Trading-capable MT5 sessions
6
+
7
+ `mt5_trading_session()` complements the read-only `mt5_session()` helper in
8
+ `sdk.py`. It yields a connected `pdmt5.Mt5TradingClient`, uses
9
+ `Mt5Config.path` to launch the terminal when configured, and always calls
10
+ `shutdown()` on exit.
11
+
12
+ ```python
13
+ from pdmt5 import Mt5Config
14
+
15
+ from mt5cli import mt5_trading_session
16
+
17
+ with mt5_trading_session(
18
+ Mt5Config(path=r"C:\Program Files\MetaTrader 5\terminal64.exe", login=12345),
19
+ retry_count=2,
20
+ ) as client:
21
+ positions = client.positions_get_as_df(symbol="EURUSD")
22
+ ```
23
+
24
+ The read-only `Mt5CliClient` / `mt5_session()` API is unchanged.
25
+
26
+ ## Operational trading helpers
27
+
28
+ These helpers are strategy-agnostic and do not depend on signal detection,
29
+ betting logic, or scheduling code in downstream applications.
30
+
31
+ ```python
32
+ from mt5cli import (
33
+ calculate_margin_and_volume,
34
+ detect_position_side,
35
+ determine_order_limits,
36
+ )
37
+
38
+ side = detect_position_side(client, "EURUSD")
39
+ sizing = calculate_margin_and_volume(
40
+ client,
41
+ "EURUSD",
42
+ unit_margin_ratio=0.5,
43
+ preserved_margin_ratio=0.2,
44
+ )
45
+ limits = determine_order_limits(
46
+ client,
47
+ "EURUSD",
48
+ side="long",
49
+ stop_loss_limit_ratio=0.01,
50
+ take_profit_limit_ratio=0.02,
51
+ )
52
+ ```
53
+
54
+ Protective ratios must satisfy `0 <= ratio < 1`; `0` omits that level.
55
+ `calculate_margin_and_volume()` clamps negative `margin_free` to `0.0`
56
+ before sizing.
57
+
58
+ ## Migration from mteor-local helpers
59
+
60
+ | mteor-local concern | mt5cli replacement |
61
+ | -------------------------------------------------------- | ----------------------------------------------- |
62
+ | Manual terminal spawn/kill around trading code | `mt5_trading_session()` |
63
+ | Local position-side detection | `detect_position_side()` |
64
+ | Local margin/volume sizing | `calculate_margin_and_volume()` |
65
+ | Local SL/TP price derivation | `determine_order_limits()` |
66
+ | Throttled SQLite history loop with ad-hoc error handling | `ThrottledHistoryUpdater(suppress_errors=True)` |
67
+
68
+ Keep read-only data collection on `mt5_session()` / `Mt5CliClient`; use
69
+ `mt5_trading_session()` only where order placement or trading calculations are
70
+ required.
@@ -6,6 +6,12 @@ Command-line tool for MetaTrader 5 data export.
6
6
 
7
7
  mt5cli is a CLI application that exports MetaTrader 5 trading data to multiple file formats. It is built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5.
8
8
 
9
+ ## Architecture
10
+
11
+ - **pdmt5** — canonical MT5 client, DataFrame/trading primitives, and MT5 constant parsing (`TIMEFRAME_*`, `COPY_TICKS_*`, order types).
12
+ - **mt5cli** — CLI commands, CSV/JSON/Parquet/SQLite export, SQLite history collection, rate views, and local batch/automation SDK helpers built on pdmt5.
13
+ - **mt5api** — sibling HTTP adapter for remote MT5 access; not a dependency of mt5cli.
14
+
9
15
  ## Features
10
16
 
11
17
  - **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats
@@ -58,6 +58,7 @@ nav:
58
58
  - Overview: api/index.md
59
59
  - CLI: api/cli.md
60
60
  - SDK: api/sdk.md
61
+ - Trading: api/trading.md
61
62
  - History Collection (SQLite): api/history.md
62
63
  - Utils: api/utils.md
63
64
 
@@ -61,6 +61,12 @@ from .sdk import (
61
61
  from .sdk import (
62
62
  version as mt5_version,
63
63
  )
64
+ from .trading import (
65
+ calculate_margin_and_volume,
66
+ detect_position_side,
67
+ determine_order_limits,
68
+ mt5_trading_session,
69
+ )
64
70
  from .utils import (
65
71
  TICK_FLAG_MAP,
66
72
  TIMEFRAME_MAP,
@@ -89,6 +95,7 @@ __all__ = [
89
95
  "build_config",
90
96
  "build_rate_targets",
91
97
  "build_rate_view_name",
98
+ "calculate_margin_and_volume",
92
99
  "collect_history",
93
100
  "collect_latest_closed_rates_by_granularity",
94
101
  "collect_latest_closed_rates_for_accounts",
@@ -101,6 +108,8 @@ __all__ = [
101
108
  "copy_ticks_from",
102
109
  "copy_ticks_range",
103
110
  "detect_format",
111
+ "detect_position_side",
112
+ "determine_order_limits",
104
113
  "drop_forming_rate_bar",
105
114
  "export_dataframe",
106
115
  "export_dataframe_to_sqlite",
@@ -117,6 +126,7 @@ __all__ = [
117
126
  "mt5_session",
118
127
  "mt5_summary",
119
128
  "mt5_summary_as_df",
129
+ "mt5_trading_session",
120
130
  "mt5_version",
121
131
  "orders",
122
132
  "parse_datetime",
@@ -347,7 +347,7 @@ def ticks_recent(
347
347
  click_type=TICK_FLAGS_TYPE,
348
348
  help="Tick flags (ALL, INFO, TRADE, or integer).",
349
349
  ),
350
- ] = 1,
350
+ ] = "ALL", # pyright: ignore[reportArgumentType]
351
351
  ) -> None:
352
352
  """Export ticks from a recent time window."""
353
353
  client = _sdk_client(ctx)
@@ -656,7 +656,7 @@ def collect_history(
656
656
  click_type=TICK_FLAGS_TYPE,
657
657
  help="Tick copy flags (ALL, INFO, TRADE, or integer).",
658
658
  ),
659
- ] = 1,
659
+ ] = "ALL", # pyright: ignore[reportArgumentType]
660
660
  if_exists: Annotated[
661
661
  IfExists,
662
662
  typer.Option(
@@ -10,9 +10,10 @@ from pathlib import Path
10
10
  from typing import TYPE_CHECKING, Literal, cast
11
11
 
12
12
  import pandas as pd
13
+ from pdmt5 import get_timeframe_name as _get_timeframe_name
13
14
 
14
15
  from .utils import (
15
- TIMEFRAME_MAP,
16
+ TIMEFRAME_NAMES,
16
17
  Dataset,
17
18
  IfExists,
18
19
  parse_datetime,
@@ -27,7 +28,7 @@ if TYPE_CHECKING:
27
28
 
28
29
  logger = logging.getLogger(__name__)
29
30
 
30
- DEFAULT_HISTORY_TIMEFRAMES: tuple[str, ...] = tuple(TIMEFRAME_MAP)
31
+ DEFAULT_HISTORY_TIMEFRAMES: tuple[str, ...] = TIMEFRAME_NAMES
31
32
 
32
33
  _HISTORY_DEDUP_KEYS: dict[Dataset, tuple[tuple[str, ...], ...]] = {
33
34
  Dataset.rates: (("symbol", "timeframe", "time"), ("symbol", "time")),
@@ -80,7 +81,7 @@ def resolve_history_timeframes(
80
81
  seen: set[int] = set()
81
82
  resolved: list[int] = []
82
83
  for value in raw:
83
- tf = value if isinstance(value, int) else parse_timeframe(str(value))
84
+ tf = parse_timeframe(value)
84
85
  if tf not in seen:
85
86
  seen.add(tf)
86
87
  resolved.append(tf)
@@ -93,17 +94,16 @@ def resolve_history_tick_flags(flags: int | str) -> int:
93
94
  Returns:
94
95
  Integer tick flag value.
95
96
  """
96
- if isinstance(flags, int):
97
- return flags
98
97
  return parse_tick_flags(flags)
99
98
 
100
99
 
101
100
  def resolve_granularity_name(timeframe: int) -> str:
102
101
  """Return a granularity name for a timeframe integer when known."""
103
- for name, value in TIMEFRAME_MAP.items():
104
- if value == timeframe:
105
- return name
106
- return str(timeframe)
102
+ try:
103
+ name = _get_timeframe_name(timeframe)
104
+ except ValueError:
105
+ return str(timeframe)
106
+ return name.removeprefix("TIMEFRAME_")
107
107
 
108
108
 
109
109
  def drop_forming_rate_bar(df_rate: pd.DataFrame) -> pd.DataFrame:
@@ -44,6 +44,62 @@ T = TypeVar("T")
44
44
 
45
45
  logger = logging.getLogger(__name__)
46
46
 
47
+ _RECOVERABLE_HISTORY_UPDATE_ERRORS: tuple[type[BaseException], ...] = (
48
+ Mt5TradingError,
49
+ Mt5RuntimeError,
50
+ sqlite3.Error,
51
+ ValueError,
52
+ OSError,
53
+ )
54
+
55
+ _MT5_CLIENT_CAPABILITY_METHODS: frozenset[str] = frozenset({
56
+ "copy_rates_range_as_df",
57
+ "copy_ticks_range_as_df",
58
+ "history_deals_get_as_df",
59
+ "history_orders_get_as_df",
60
+ })
61
+ _MT5_HISTORY_MODULE = Path(__file__).with_name("history.py").resolve()
62
+ _MT5_HISTORY_CLIENT_CALL_FUNCTIONS: frozenset[str] = frozenset({
63
+ "write_rates_dataset",
64
+ "write_ticks_dataset",
65
+ "write_history_dataset",
66
+ "_write_incremental_history_deals",
67
+ })
68
+ _NON_CALLABLE_TYPE_ERROR = re.compile(r"^'[^']+' object is not callable$")
69
+
70
+
71
+ def _is_non_callable_history_client_type_error(exc: TypeError) -> bool:
72
+ """Return whether a TypeError came from calling a history client API attribute."""
73
+ if not _NON_CALLABLE_TYPE_ERROR.match(str(exc)):
74
+ return False
75
+ tb = exc.__traceback__
76
+ if tb is None:
77
+ return False
78
+ while tb.tb_next is not None:
79
+ tb = tb.tb_next
80
+ frame = tb.tb_frame
81
+ return (
82
+ frame.f_code.co_name in _MT5_HISTORY_CLIENT_CALL_FUNCTIONS
83
+ and Path(frame.f_code.co_filename).resolve() == _MT5_HISTORY_MODULE
84
+ )
85
+
86
+
87
+ def _is_mt5_client_capability_error(exc: BaseException) -> bool:
88
+ """Return whether an error indicates an incompatible MT5 client API surface."""
89
+ if isinstance(exc, AttributeError):
90
+ msg = str(exc)
91
+ if msg.startswith("MT5 client is missing required method:"):
92
+ return True
93
+ name = getattr(exc, "name", None)
94
+ return isinstance(name, str) and name in _MT5_CLIENT_CAPABILITY_METHODS
95
+ if isinstance(exc, TypeError):
96
+ msg = str(exc)
97
+ if msg.startswith("MT5 client attribute is not callable:"):
98
+ return True
99
+ return _is_non_callable_history_client_type_error(exc)
100
+ return False
101
+
102
+
47
103
  __all__ = [
48
104
  "AccountSpec",
49
105
  "Mt5CliClient",
@@ -88,14 +144,10 @@ __all__ = [
88
144
 
89
145
 
90
146
  def _coerce_timeframe(timeframe: int | str) -> int:
91
- if isinstance(timeframe, int):
92
- return timeframe
93
147
  return parse_timeframe(timeframe)
94
148
 
95
149
 
96
150
  def _coerce_tick_flags(flags: int | str) -> int:
97
- if isinstance(flags, int):
98
- return flags
99
151
  return parse_tick_flags(flags)
100
152
 
101
153
 
@@ -1014,10 +1066,14 @@ class ThrottledHistoryUpdater:
1014
1066
  include_account_events: Include account-level cash events.
1015
1067
  interval_seconds: Minimum seconds between successful updates. Values
1016
1068
  ``<= 0`` update on every call.
1017
- suppress_errors: When True, ``Mt5TradingError``, ``Mt5RuntimeError``,
1018
- and ``sqlite3.Error`` raised during an update are swallowed and
1019
- :meth:`update` returns False without advancing the throttle. When
1020
- False (default), such errors propagate so callers control logging.
1069
+ suppress_errors: When True, recoverable errors (``Mt5TradingError``,
1070
+ ``Mt5RuntimeError``, ``sqlite3.Error``, ``ValueError``,
1071
+ ``OSError``, and MT5 client capability ``AttributeError`` /
1072
+ ``TypeError`` for history API methods) raised during an update
1073
+ are swallowed and :meth:`update` returns False without advancing
1074
+ the throttle. Other ``AttributeError`` / ``TypeError`` values
1075
+ always propagate. When False (default), recoverable errors
1076
+ propagate so callers control logging.
1021
1077
  """
1022
1078
  self.output = output
1023
1079
  self.datasets = datasets
@@ -1057,16 +1113,27 @@ class ThrottledHistoryUpdater:
1057
1113
  Returns:
1058
1114
  True if an update ran successfully, False if it was throttled or
1059
1115
  (when ``suppress_errors`` is True) failed with a recoverable error.
1116
+ When ``suppress_errors`` is False, recoverable update failures
1117
+ propagate to the caller.
1060
1118
 
1061
1119
  Raises:
1062
- Mt5TradingError: If the update fails and ``suppress_errors`` is False.
1063
- Mt5RuntimeError: If the update fails and ``suppress_errors`` is False.
1064
- sqlite3.Error: If the SQLite write fails and ``suppress_errors`` is
1065
- False.
1120
+ AttributeError: MT5 client capability mismatch when
1121
+ ``suppress_errors`` is False, or any other attribute error.
1122
+ TypeError: MT5 client capability mismatch when ``suppress_errors``
1123
+ is False, or any other type error.
1066
1124
  """
1067
1125
  if not self.should_update():
1068
1126
  return False
1069
1127
  try:
1128
+ _resolve_update_history_request(
1129
+ output=self.output,
1130
+ symbols=symbols,
1131
+ datasets=self.datasets,
1132
+ timeframes=self.timeframes,
1133
+ flags=self.flags,
1134
+ lookback_hours=self.lookback_hours,
1135
+ date_to=None,
1136
+ )
1070
1137
  update_history(
1071
1138
  client=client,
1072
1139
  output=self.output,
@@ -1078,11 +1145,16 @@ class ThrottledHistoryUpdater:
1078
1145
  with_views=self.with_views,
1079
1146
  include_account_events=self.include_account_events,
1080
1147
  )
1081
- except (Mt5TradingError, Mt5RuntimeError, sqlite3.Error):
1148
+ except _RECOVERABLE_HISTORY_UPDATE_ERRORS:
1082
1149
  if self.suppress_errors:
1083
1150
  logger.warning("Suppressed history update error", exc_info=True)
1084
1151
  return False
1085
1152
  raise
1153
+ except (AttributeError, TypeError) as exc:
1154
+ if self.suppress_errors and _is_mt5_client_capability_error(exc):
1155
+ logger.warning("Suppressed history update error", exc_info=True)
1156
+ return False
1157
+ raise
1086
1158
  self._last_update_monotonic = time.monotonic()
1087
1159
  return True
1088
1160
 
@@ -1095,7 +1167,7 @@ def collect_history(
1095
1167
  *,
1096
1168
  datasets: set[Dataset] | None = None,
1097
1169
  timeframe: int | str = 1,
1098
- flags: int | str = 1,
1170
+ flags: int | str = "ALL",
1099
1171
  if_exists: IfExists = IfExists.FAIL,
1100
1172
  with_views: bool = False,
1101
1173
  config: Mt5Config | None = None,