mt5cli 1.1.3__tar.gz → 1.1.5__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 (69) hide show
  1. {mt5cli-1.1.3 → mt5cli-1.1.5}/AGENTS.md +1 -1
  2. {mt5cli-1.1.3 → mt5cli-1.1.5}/PKG-INFO +33 -33
  3. {mt5cli-1.1.3 → mt5cli-1.1.5}/README.md +31 -31
  4. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/history.md +32 -1
  5. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/public-contract.md +7 -2
  6. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/sdk.md +2 -2
  7. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/trading.md +40 -0
  8. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/index.md +18 -18
  9. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/__init__.py +2 -0
  10. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/cli.py +42 -7
  11. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/contract.py +1 -0
  12. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/history.py +116 -10
  13. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/schemas.py +15 -0
  14. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/sdk.py +27 -7
  15. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/trading.py +126 -2
  16. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/utils.py +1 -0
  17. {mt5cli-1.1.3 → mt5cli-1.1.5}/pyproject.toml +2 -2
  18. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_cli.py +173 -0
  19. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_contracts.py +69 -57
  20. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_history.py +265 -4
  21. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_sdk.py +101 -0
  22. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_trading.py +344 -0
  23. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_utils.py +1 -0
  24. {mt5cli-1.1.3 → mt5cli-1.1.5}/uv.lock +5 -5
  25. {mt5cli-1.1.3 → mt5cli-1.1.5}/.agents/skills/local-qa/SKILL.md +0 -0
  26. {mt5cli-1.1.3 → mt5cli-1.1.5}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  27. {mt5cli-1.1.3 → mt5cli-1.1.5}/.agents/skills/mt5cli/SKILL.md +0 -0
  28. {mt5cli-1.1.3 → mt5cli-1.1.5}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  29. {mt5cli-1.1.3 → mt5cli-1.1.5}/.claude/agents/codex.md +0 -0
  30. {mt5cli-1.1.3 → mt5cli-1.1.5}/.claude/settings.json +0 -0
  31. {mt5cli-1.1.3 → mt5cli-1.1.5}/.github/FUNDING.yml +0 -0
  32. {mt5cli-1.1.3 → mt5cli-1.1.5}/.github/dependabot.yml +0 -0
  33. {mt5cli-1.1.3 → mt5cli-1.1.5}/.github/renovate.json +0 -0
  34. {mt5cli-1.1.3 → mt5cli-1.1.5}/.github/workflows/ci.yml +0 -0
  35. {mt5cli-1.1.3 → mt5cli-1.1.5}/.github/workflows/claude.yml +0 -0
  36. {mt5cli-1.1.3 → mt5cli-1.1.5}/.github/workflows/opencode.yml +0 -0
  37. {mt5cli-1.1.3 → mt5cli-1.1.5}/.github/workflows/release.yml +0 -0
  38. {mt5cli-1.1.3 → mt5cli-1.1.5}/.gitignore +0 -0
  39. {mt5cli-1.1.3 → mt5cli-1.1.5}/CLAUDE.md +0 -0
  40. {mt5cli-1.1.3 → mt5cli-1.1.5}/LICENSE +0 -0
  41. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/cli.md +0 -0
  42. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/client.md +0 -0
  43. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/converters.md +0 -0
  44. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/exceptions.md +0 -0
  45. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/grafana.md +0 -0
  46. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/index.md +0 -0
  47. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/schemas.md +0 -0
  48. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/telemetry.md +0 -0
  49. {mt5cli-1.1.3 → mt5cli-1.1.5}/docs/api/utils.md +0 -0
  50. {mt5cli-1.1.3 → mt5cli-1.1.5}/examples/grafana/README.md +0 -0
  51. {mt5cli-1.1.3 → mt5cli-1.1.5}/examples/grafana/compose.yml +0 -0
  52. {mt5cli-1.1.3 → mt5cli-1.1.5}/examples/grafana/dashboards/mt5cli-market.json +0 -0
  53. {mt5cli-1.1.3 → mt5cli-1.1.5}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
  54. {mt5cli-1.1.3 → mt5cli-1.1.5}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
  55. {mt5cli-1.1.3 → mt5cli-1.1.5}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
  56. {mt5cli-1.1.3 → mt5cli-1.1.5}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
  57. {mt5cli-1.1.3 → mt5cli-1.1.5}/mkdocs.yml +0 -0
  58. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/__main__.py +0 -0
  59. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/client.py +0 -0
  60. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/converters.py +0 -0
  61. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/exceptions.py +0 -0
  62. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/grafana.py +0 -0
  63. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/retry.py +0 -0
  64. {mt5cli-1.1.3 → mt5cli-1.1.5}/mt5cli/telemetry.py +0 -0
  65. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/__init__.py +0 -0
  66. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/conftest.py +0 -0
  67. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_examples.py +0 -0
  68. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_grafana.py +0 -0
  69. {mt5cli-1.1.3 → mt5cli-1.1.5}/tests/test_telemetry.py +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Project Structure & Module Organization
4
4
 
5
- `mt5cli/` contains the package source. Important modules include `cli.py` for the Typer command-line app, `client.py` and `sdk.py` for public MT5 client/session APIs, `history.py` for SQLite history collection, `storage.py` and `converters.py` for export behavior, and `schemas.py` for normalized dataset contracts. `tests/` holds pytest coverage for CLI behavior, SDK contracts, trading helpers, history, and utilities. `docs/` and `mkdocs.yml` define the MkDocs site and API reference. `skills/mt5cli/SKILL.md` documents the mt5cli agent skill.
5
+ `mt5cli/` contains the package source. Important modules include `cli.py` for the Typer command-line app, `client.py` and `sdk.py` for public MT5 client/session APIs, `history.py` for SQLite history collection, `trading.py` for trading helpers, `grafana.py` for Grafana schema support, `telemetry.py` for observability, `utils.py` and `converters.py` for export behavior, and `schemas.py` for normalized dataset contracts. `tests/` holds pytest coverage for CLI behavior, SDK contracts, trading helpers, history, and utilities. `docs/` and `mkdocs.yml` define the MkDocs site and API reference. `skills/mt5cli/SKILL.md` documents the mt5cli agent skill.
6
6
 
7
7
  ## Build, Test, and Development Commands
8
8
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 1.1.3
3
+ Version: 1.1.5
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>
@@ -18,7 +18,7 @@ Classifier: Topic :: Office/Business :: Financial :: Investment
18
18
  Requires-Python: <3.14,>=3.11
19
19
  Requires-Dist: click>=8.1.0
20
20
  Requires-Dist: pandas>=2.2.2
21
- Requires-Dist: pdmt5>=1.1.0
21
+ Requires-Dist: pdmt5>=1.1.1
22
22
  Requires-Dist: pydantic>=2.13.4
23
23
  Requires-Dist: typer>=0.15.0
24
24
  Provides-Extra: otel
@@ -189,37 +189,37 @@ python -m mt5cli -o account.csv account-info
189
189
 
190
190
  ## Commands
191
191
 
192
- | Command | Description |
193
- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
194
- | `rates-from` | Export rates from a start date |
195
- | `rates-from-pos` | Export rates from a start position |
196
- | `latest-rates` | Export latest rates from a start position |
197
- | `rates-range` | Export rates for a date range |
198
- | `ticks-from` | Export ticks from a start date |
199
- | `ticks-range` | Export ticks for a date range |
200
- | `ticks-recent` | Export ticks from a recent trailing window |
201
- | `account-info` | Export account information |
202
- | `terminal-info` | Export terminal information |
203
- | `version` | Export MetaTrader 5 version information |
204
- | `last-error` | Export the last error information |
205
- | `symbols` | Export symbol list |
206
- | `symbol-info` | Export symbol details |
207
- | `symbol-info-tick` | Export the last tick for a symbol |
208
- | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
209
- | `market-book` | Export market depth (order book) |
210
- | `orders` | Export active orders |
211
- | `positions` | Export open positions |
212
- | `history-orders` | Export historical orders |
213
- | `history-deals` | Export historical deals |
214
- | `recent-history-deals` | Export historical deals from a recent trailing window |
215
- | `mt5-summary` | Export terminal/account status summary |
216
- | `order-check` | Check funds sufficiency for a trade request |
217
- | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
218
- | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available; optional `--deviation` / `--comment` / `--magic`) |
219
- | `collect-history` | Collect rates, history-orders, and history-deals for one or more symbols into a single SQLite database (ticks opt-in via `--dataset ticks`) |
220
- | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
221
- | `grafana-schema` | Create or refresh Grafana-ready views and indexes in an existing SQLite database (idempotent, no MT5 connection) |
222
- | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite for live Grafana dashboards |
192
+ | Command | Description |
193
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
194
+ | `rates-from` | Export rates from a start date |
195
+ | `rates-from-pos` | Export rates from a start position |
196
+ | `latest-rates` | Export latest rates from a start position |
197
+ | `rates-range` | Export rates for a date range |
198
+ | `ticks-from` | Export ticks from a start date |
199
+ | `ticks-range` | Export ticks for a date range |
200
+ | `ticks-recent` | Export ticks from a recent trailing window |
201
+ | `account-info` | Export account information |
202
+ | `terminal-info` | Export terminal information |
203
+ | `version` | Export MetaTrader 5 version information |
204
+ | `last-error` | Export the last error information |
205
+ | `symbols` | Export symbol list |
206
+ | `symbol-info` | Export symbol details |
207
+ | `symbol-info-tick` | Export the last tick for a symbol |
208
+ | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
209
+ | `market-book` | Export market depth (order book) |
210
+ | `orders` | Export active orders |
211
+ | `positions` | Export open positions |
212
+ | `history-orders` | Export historical orders |
213
+ | `history-deals` | Export historical deals |
214
+ | `recent-history-deals` | Export historical deals from a recent trailing window |
215
+ | `mt5-summary` | Export terminal/account status summary |
216
+ | `order-check` | Check funds sufficiency for a trade request |
217
+ | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
218
+ | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available; optional `--filling-mode` / `--deviation` / `--comment` / `--magic`) |
219
+ | `collect-history` | Collect rates, history-orders, and history-deals for one or more symbols into a single SQLite database (ticks opt-in via `--dataset ticks`) |
220
+ | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
221
+ | `grafana-schema` | Create or refresh Grafana-ready views and indexes in an existing SQLite database (idempotent, no MT5 connection) |
222
+ | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite for live Grafana dashboards |
223
223
 
224
224
  Use `order-check` to validate a request payload before running `order-send --yes`.
225
225
  `close-positions` is the safer high-level alternative that builds correct close
@@ -158,37 +158,37 @@ python -m mt5cli -o account.csv account-info
158
158
 
159
159
  ## Commands
160
160
 
161
- | Command | Description |
162
- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
163
- | `rates-from` | Export rates from a start date |
164
- | `rates-from-pos` | Export rates from a start position |
165
- | `latest-rates` | Export latest rates from a start position |
166
- | `rates-range` | Export rates for a date range |
167
- | `ticks-from` | Export ticks from a start date |
168
- | `ticks-range` | Export ticks for a date range |
169
- | `ticks-recent` | Export ticks from a recent trailing window |
170
- | `account-info` | Export account information |
171
- | `terminal-info` | Export terminal information |
172
- | `version` | Export MetaTrader 5 version information |
173
- | `last-error` | Export the last error information |
174
- | `symbols` | Export symbol list |
175
- | `symbol-info` | Export symbol details |
176
- | `symbol-info-tick` | Export the last tick for a symbol |
177
- | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
178
- | `market-book` | Export market depth (order book) |
179
- | `orders` | Export active orders |
180
- | `positions` | Export open positions |
181
- | `history-orders` | Export historical orders |
182
- | `history-deals` | Export historical deals |
183
- | `recent-history-deals` | Export historical deals from a recent trailing window |
184
- | `mt5-summary` | Export terminal/account status summary |
185
- | `order-check` | Check funds sufficiency for a trade request |
186
- | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
187
- | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available; optional `--deviation` / `--comment` / `--magic`) |
188
- | `collect-history` | Collect rates, history-orders, and history-deals for one or more symbols into a single SQLite database (ticks opt-in via `--dataset ticks`) |
189
- | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
190
- | `grafana-schema` | Create or refresh Grafana-ready views and indexes in an existing SQLite database (idempotent, no MT5 connection) |
191
- | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite for live Grafana dashboards |
161
+ | Command | Description |
162
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
163
+ | `rates-from` | Export rates from a start date |
164
+ | `rates-from-pos` | Export rates from a start position |
165
+ | `latest-rates` | Export latest rates from a start position |
166
+ | `rates-range` | Export rates for a date range |
167
+ | `ticks-from` | Export ticks from a start date |
168
+ | `ticks-range` | Export ticks for a date range |
169
+ | `ticks-recent` | Export ticks from a recent trailing window |
170
+ | `account-info` | Export account information |
171
+ | `terminal-info` | Export terminal information |
172
+ | `version` | Export MetaTrader 5 version information |
173
+ | `last-error` | Export the last error information |
174
+ | `symbols` | Export symbol list |
175
+ | `symbol-info` | Export symbol details |
176
+ | `symbol-info-tick` | Export the last tick for a symbol |
177
+ | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
178
+ | `market-book` | Export market depth (order book) |
179
+ | `orders` | Export active orders |
180
+ | `positions` | Export open positions |
181
+ | `history-orders` | Export historical orders |
182
+ | `history-deals` | Export historical deals |
183
+ | `recent-history-deals` | Export historical deals from a recent trailing window |
184
+ | `mt5-summary` | Export terminal/account status summary |
185
+ | `order-check` | Check funds sufficiency for a trade request |
186
+ | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
187
+ | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available; optional `--filling-mode` / `--deviation` / `--comment` / `--magic`) |
188
+ | `collect-history` | Collect rates, history-orders, and history-deals for one or more symbols into a single SQLite database (ticks opt-in via `--dataset ticks`) |
189
+ | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
190
+ | `grafana-schema` | Create or refresh Grafana-ready views and indexes in an existing SQLite database (idempotent, no MT5 connection) |
191
+ | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite for live Grafana dashboards |
192
192
 
193
193
  Use `order-check` to validate a request payload before running `order-send --yes`.
194
194
  `close-positions` is the safer high-level alternative that builds correct close
@@ -20,6 +20,21 @@ append using dataset-specific keys (for example `ticket` on history tables, or
20
20
  Optional views are created when `--with-views` is set and the `history-deals` dataset
21
21
  was written.
22
22
 
23
+ `ticks` and `symbols` are opt-in datasets (pass `--dataset ticks` /
24
+ `--dataset symbols`); they are excluded from the default `rates`,
25
+ `history-orders`, `history-deals` selection. Unlike the other datasets,
26
+ `symbols` is not a time-windowed history table: each collection or update
27
+ writes one row per requested symbol, timestamped with the collection's
28
+ `date_to` (one-shot `collect-history`) or the update's end time (incremental
29
+ `update_history`). It snapshots broker-reported symbol metadata (`point`,
30
+ `digits`, `trade_contract_size`, `volume_min`, `volume_max`, `volume_step`,
31
+ `trade_tick_size`, `trade_tick_value`, `currency_profit`) so downstream
32
+ consumers can convert `rates.spread` (points) into a relative spread without a
33
+ live terminal connection. Metadata is only valid for the account/broker that
34
+ produced the snapshot. When a symbol's `point` is missing or zero, the row is
35
+ still written with NULL metadata and a warning is logged — a bad symbol never
36
+ aborts the rest of the sync.
37
+
23
38
  ### Entity-relationship diagram
24
39
 
25
40
  Sample layout for a full collection with `--with-views`:
@@ -79,6 +94,20 @@ erDiagram
79
94
  REAL fee
80
95
  }
81
96
 
97
+ symbols {
98
+ TEXT symbol "dedup key"
99
+ TEXT time "dedup key, snapshot moment"
100
+ REAL point
101
+ INTEGER digits
102
+ REAL trade_contract_size
103
+ REAL volume_min
104
+ REAL volume_max
105
+ REAL volume_step
106
+ REAL trade_tick_size
107
+ REAL trade_tick_value
108
+ TEXT currency_profit
109
+ }
110
+
82
111
  cash_events {
83
112
  INTEGER ticket
84
113
  TEXT symbol
@@ -106,6 +135,7 @@ erDiagram
106
135
  rates ||--o{ history_deals : "symbol (logical)"
107
136
  ticks ||--o{ history_deals : "symbol (logical)"
108
137
  history_orders ||--o{ history_deals : "order ~ ticket (logical)"
138
+ symbols ||--o{ rates : "symbol (logical)"
109
139
  history_deals ||--|| cash_events : "VIEW: type NOT IN (0,1)"
110
140
  history_deals ||--o{ positions_reconstructed : "VIEW: GROUP BY position_id"
111
141
  ```
@@ -118,6 +148,7 @@ erDiagram
118
148
  | `ticks` | table | `copy_ticks_range` | Indexed on `(symbol, time)` when columns exist. |
119
149
  | `history_orders` | table | `history_orders_get` | Fetched per `--symbol`, then concatenated. |
120
150
  | `history_deals` | table | `history_deals_get` | Fetched per `--symbol`, then concatenated. Indexed on `(position_id, symbol)` when present. |
151
+ | `symbols` | table | `symbol_info` | Opt-in. One row per symbol per collection/update, snapshotted at `date_to` / update end. |
121
152
  | `cash_events` | view | `history_deals` | Non-trade deal types (deposits, balance ops, etc.). Requires `type` column. |
122
153
  | `positions_reconstructed` | view | `history_deals` | One row per closed `position_id`; volume-weighted prices and reversal stats. |
123
154
 
@@ -240,7 +271,7 @@ frame = series["EURUSD", 1] # keyed by (symbol, integer timeframe)
240
271
  `require_existing` is ignored.
241
272
  - `load_rate_series_from_sqlite()` returns a mapping keyed by
242
273
  `(symbol, integer timeframe)`. Unless `explicit_tables` is supplied, it
243
- requires existing managed `rate_*` compatibility views and raises
274
+ requires existing managed `rate_*__*` compatibility views and raises
244
275
  `ValueError` when they are missing. Duplicate `(symbol, timeframe)` targets
245
276
  are rejected.
246
277
  - `load_rate_series_by_granularity()` is a thin wrapper that builds the targets,
@@ -88,6 +88,7 @@ strategy entries, exits, Kelly sizing, or signal logic.
88
88
  | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
89
89
  | `get_account_snapshot`, `get_symbol_snapshot`, `get_tick_snapshot`, `get_positions_frame` | Normalized account/symbol/tick/position views |
90
90
  | `extract_tick_price` | Positive finite bid/ask extraction from tick mappings |
91
+ | `estimate_server_clock_offset_seconds` | Broker server clock offset from true UTC, via the latest tick |
91
92
  | `detect_position_side` | Net long / short / flat from open positions |
92
93
  | `calculate_spread_ratio` | Relative bid-ask spread |
93
94
  | `calculate_margin_and_volume`, `calculate_volume_by_margin`, `calculate_new_position_margin_ratio` | Margin budget and volume sizing |
@@ -234,8 +235,12 @@ that closes open positions by `--symbol` or `--ticket` using
234
235
  `close_open_positions()`. Both `order-send --yes` and `close-positions --yes`
235
236
  are live execution paths. `close-positions --dry-run` previews close orders
236
237
  without placing them and does not require `--yes`. `close-positions` also
237
- accepts optional `--deviation`, `--comment`, and `--magic`; `--magic` scopes
238
- the selected open positions fail-closed when position magic metadata is absent.
238
+ accepts optional `--filling-mode`, `--deviation`, `--comment`, and `--magic`;
239
+ `--magic` scopes the selected open positions fail-closed when position magic
240
+ metadata is absent. When `--filling-mode` (or the `order_filling_mode`
241
+ parameter of `close_open_positions()`) is omitted, the filling mode is
242
+ resolved per symbol with `resolve_broker_filling_mode()` so closes are not
243
+ rejected on brokers whose symbols do not support IOC.
239
244
 
240
245
  `history-gaps` reads an existing SQLite history database and exports one row
241
246
  per detected gap from managed rate compatibility views. It never initializes
@@ -160,8 +160,8 @@ updater = ThrottledHistoryUpdater(
160
160
  )
161
161
  ```
162
162
 
163
- By default recoverable errors (`Mt5TradingError`, `Mt5RuntimeError`,
164
- `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability
163
+ By default recoverable errors (`Mt5RuntimeError`, `sqlite3.Error`,
164
+ `ValueError`, `OSError`, and MT5 client capability
165
165
  `AttributeError` / `TypeError` for history API methods) propagate so the caller
166
166
  controls logging; pass `suppress_errors=True` to swallow them and return
167
167
  `False` without advancing the throttle. Other `AttributeError` / `TypeError`
@@ -236,6 +236,45 @@ or `None` result from the underlying client is normalized to an empty DataFrame.
236
236
  Downstream packages own all strategy-specific transformations. mt5cli does not
237
237
  provide entry-deal classification, Kelly sizing, or any betting-specific helpers.
238
238
 
239
+ ## Broker server clock offset
240
+
241
+ MT5 tick, bar, and deal timestamps are epoch values labeled in the broker
242
+ server's wall clock, which is commonly UTC+2 or UTC+3 rather than true UTC.
243
+ Any code that mixes those timestamps with true UTC (freshness checks,
244
+ trailing history windows) silently inherits the broker's offset as a bias
245
+ unless it is measured and applied explicitly.
246
+
247
+ `estimate_server_clock_offset_seconds()` reads the latest tick for a symbol
248
+ and returns the broker's clock offset from true UTC, rounded to the nearest
249
+ half hour, or `None` when no valid tick time is available. Pass the result to
250
+ `fetch_recent_history_deals_for_trading_client()` via
251
+ `server_clock_offset_seconds` to shift the trailing window so it covers the
252
+ true most-recent deals:
253
+
254
+ ```python
255
+ from mt5cli import (
256
+ create_trading_client,
257
+ estimate_server_clock_offset_seconds,
258
+ fetch_recent_history_deals_for_trading_client,
259
+ )
260
+
261
+ client = create_trading_client(login=12345, server="Broker-Demo")
262
+ try:
263
+ offset = estimate_server_clock_offset_seconds(client, "JP225")
264
+ deals_df = fetch_recent_history_deals_for_trading_client(
265
+ client,
266
+ symbol="JP225",
267
+ hours=24,
268
+ server_clock_offset_seconds=offset,
269
+ )
270
+ finally:
271
+ client.shutdown()
272
+ ```
273
+
274
+ No offset is applied automatically anywhere; omitting
275
+ `server_clock_offset_seconds` keeps the trailing window anchored to true UTC
276
+ (current behavior, byte-identical to prior releases).
277
+
239
278
  ## Migration from application-local helpers
240
279
 
241
280
  | Application-local concern | mt5cli replacement |
@@ -247,6 +286,7 @@ provide entry-deal classification, Kelly sizing, or any betting-specific helpers
247
286
  | Local order or position margin estimation | `estimate_order_margin()`, `calculate_positions_margin()` |
248
287
  | Local closed-bar fetch from a trading session | `fetch_latest_closed_rates_for_trading_client()`, `fetch_latest_closed_rates_indexed()` |
249
288
  | Local recent deal history fetch from a trading session | `fetch_recent_history_deals_for_trading_client()` |
289
+ | Local broker server clock offset measurement | `estimate_server_clock_offset_seconds()` |
250
290
  | Local SL/TP price derivation | `determine_order_limits()` |
251
291
  | Throttled SQLite history loop with ad-hoc error handling | `ThrottledHistoryUpdater(suppress_errors=True)` |
252
292
 
@@ -166,10 +166,10 @@ mt5cli --login '${MT5_LOGIN}' --password '${MT5_PASSWORD}' --server '${MT5_SERVE
166
166
  These commands send requests to the live trade server and can place or close
167
167
  real trades. Both require `--yes` for live execution.
168
168
 
169
- | Command | Description |
170
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
171
- | `order-send` | Send a **raw** trade request directly to MT5 (`--yes` required; expert path — no extra validation) |
172
- | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` to preview; optional `--deviation` / `--comment` / `--magic`) |
169
+ | Command | Description |
170
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
171
+ | `order-send` | Send a **raw** trade request directly to MT5 (`--yes` required; expert path — no extra validation) |
172
+ | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` to preview; optional `--filling-mode` / `--deviation` / `--comment` / `--magic`) |
173
173
 
174
174
  Use `order-check` (Trading State) to validate funds before running `order-send --yes`.
175
175
  `close-positions` is the safer high-level alternative that builds correct close
@@ -178,10 +178,10 @@ applications should prefer dedicated closing helpers or their own risk controls.
178
178
 
179
179
  ### Bulk Collection
180
180
 
181
- | Command | Description |
182
- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
183
- | `collect-history` | Collect rates, history-orders, and history-deals (ticks opt-in via `--dataset ticks`) for one or more symbols into a single SQLite database (optional cash-event/position views) |
184
- | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
181
+ | Command | Description |
182
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
183
+ | `collect-history` | Collect rates, history-orders, and history-deals (ticks and symbol metadata opt-in via `--dataset ticks` / `--dataset symbols`) for one or more symbols into a single SQLite database (optional cash-event/position views) |
184
+ | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
185
185
 
186
186
  ```bash
187
187
  mt5cli -o history.db collect-history \
@@ -193,16 +193,16 @@ mt5cli -o history.db collect-history \
193
193
 
194
194
  `collect-history` options:
195
195
 
196
- | Option | Default | Description |
197
- | -------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
198
- | `--symbol/-s` | _required_ | Symbol to collect (repeat for multiple). |
199
- | `--date-from` | _required_ | Start date in ISO 8601. |
200
- | `--date-to` | _required_ | End date in ISO 8601. |
201
- | `--dataset` | rates, history-orders, history-deals | Repeatable: `rates`, `ticks`, `history-orders`, `history-deals`. Ticks are opt-in: pass `--dataset ticks` to include them. |
202
- | `--timeframe` | `M1` | Rates timeframe; recorded in a `timeframe` column on the `rates` table. |
203
- | `--flags` | `ALL` | Tick copy flags forwarded to `copy_ticks_range`. |
204
- | `--if-exists` | `fail` | `append`, `replace`, or `fail` when a target table already exists. |
205
- | `--with-views` | off | Add `cash_events` and `positions_reconstructed` views (requires the `history-deals` dataset). |
196
+ | Option | Default | Description |
197
+ | -------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
198
+ | `--symbol/-s` | _required_ | Symbol to collect (repeat for multiple). |
199
+ | `--date-from` | _required_ | Start date in ISO 8601. |
200
+ | `--date-to` | _required_ | End date in ISO 8601. |
201
+ | `--dataset` | rates, history-orders, history-deals | Repeatable: `rates`, `ticks`, `history-orders`, `history-deals`, `symbols`. Ticks and symbol metadata are opt-in: pass `--dataset ticks` / `--dataset symbols` to include them. |
202
+ | `--timeframe` | `M1` | Rates timeframe; recorded in a `timeframe` column on the `rates` table. |
203
+ | `--flags` | `ALL` | Tick copy flags forwarded to `copy_ticks_range`. |
204
+ | `--if-exists` | `fail` | `append`, `replace`, or `fail` when a target table already exists. |
205
+ | `--with-views` | off | Add `cash_events` and `positions_reconstructed` views (requires the `history-deals` dataset). |
206
206
 
207
207
  History orders and deals are fetched per symbol and concatenated, so the symbol filter is applied consistently across all datasets. The `cash_events` view is derived from symbol-filtered `history_deals`, so account-level cash events with empty or non-matching symbols may be excluded. The `positions_reconstructed` view excludes positions with no closing deal, uses volume-weighted open/close prices, and reports reversal deals (`DEAL_ENTRY_INOUT`) via `volume_reversal` / `reversal_count`.
208
208
 
@@ -66,6 +66,7 @@ from .trading import (
66
66
  determine_order_limits,
67
67
  ensure_symbol_selected,
68
68
  estimate_order_margin,
69
+ estimate_server_clock_offset_seconds,
69
70
  extract_tick_price,
70
71
  fetch_latest_closed_rates_for_trading_client,
71
72
  fetch_latest_closed_rates_indexed,
@@ -127,6 +128,7 @@ __all__ = [
127
128
  "drop_forming_rate_bar",
128
129
  "ensure_symbol_selected",
129
130
  "estimate_order_margin",
131
+ "estimate_server_clock_offset_seconds",
130
132
  "extract_tick_price",
131
133
  "fetch_latest_closed_rates",
132
134
  "fetch_latest_closed_rates_for_trading_client",
@@ -6,7 +6,7 @@ import json
6
6
  import logging
7
7
  import os
8
8
  import re
9
- import sqlite3
9
+ from contextlib import closing
10
10
  from dataclasses import dataclass
11
11
  from datetime import datetime # noqa: TC003
12
12
  from pathlib import Path # noqa: TC003
@@ -17,7 +17,11 @@ import typer
17
17
 
18
18
  from . import sdk
19
19
  from .client import MT5Client
20
- from .history import report_rate_gaps, resolve_granularity_name
20
+ from .history import (
21
+ open_existing_sqlite_database,
22
+ report_rate_gaps,
23
+ resolve_granularity_name,
24
+ )
21
25
  from .trading import OrderExecutionResult, close_open_positions, create_trading_client
22
26
  from .utils import (
23
27
  DATETIME_TYPE,
@@ -33,10 +37,13 @@ from .utils import (
33
37
  )
34
38
 
35
39
  if TYPE_CHECKING:
40
+ import sqlite3
36
41
  from collections.abc import Callable
37
42
 
38
43
  from pdmt5 import Mt5Config
39
44
 
45
+ from .trading import OrderFillingMode
46
+
40
47
  logger = logging.getLogger(__name__)
41
48
 
42
49
  # ---------------------------------------------------------------------------
@@ -727,6 +734,16 @@ def close_positions(
727
734
  help="Position ticket to close (repeat for multiple tickets).",
728
735
  ),
729
736
  ] = None,
737
+ filling_mode: Annotated[
738
+ str | None,
739
+ typer.Option(
740
+ "--filling-mode",
741
+ help=(
742
+ "Order filling mode for close requests (IOC, FOK, or RETURN)."
743
+ " Defaults to per-symbol broker resolution."
744
+ ),
745
+ ),
746
+ ] = None,
730
747
  deviation: Annotated[
731
748
  int | None,
732
749
  typer.Option(help="Optional slippage/deviation for each close request."),
@@ -762,11 +779,19 @@ def close_positions(
762
779
 
763
780
  Raises:
764
781
  typer.BadParameter: If neither ``--symbol`` nor ``--ticket`` is given,
765
- or if ``--yes`` is missing for a live (non-dry-run) run.
782
+ if ``--filling-mode`` is not IOC, FOK, or RETURN, or if ``--yes``
783
+ is missing for a live (non-dry-run) run.
766
784
  """
767
785
  if not symbol and not ticket:
768
786
  msg = "Provide at least one --symbol or --ticket to close positions."
769
787
  raise typer.BadParameter(msg)
788
+ order_filling_mode: OrderFillingMode | None = None
789
+ if filling_mode is not None:
790
+ normalized_filling_mode = filling_mode.upper()
791
+ if normalized_filling_mode not in {"IOC", "FOK", "RETURN"}:
792
+ msg = f"Unsupported filling mode: {filling_mode!r}."
793
+ raise typer.BadParameter(msg, param_hint="--filling-mode")
794
+ order_filling_mode = cast("OrderFillingMode", normalized_filling_mode)
770
795
  if not dry_run and not yes:
771
796
  msg = "Pass --yes to close live positions."
772
797
  raise typer.BadParameter(msg, param_hint="--yes")
@@ -777,6 +802,7 @@ def close_positions(
777
802
  client,
778
803
  symbols=list(symbol) if symbol else None,
779
804
  tickets=list(ticket) if ticket else None,
805
+ order_filling_mode=order_filling_mode,
780
806
  deviation=deviation,
781
807
  comment=comment,
782
808
  magic=magic,
@@ -817,10 +843,15 @@ def history_gaps(
817
843
  """Export SQLite rate gaps without connecting to MT5.
818
844
 
819
845
  Raises:
820
- typer.BadParameter: If no compatible rate view is available and no
821
- explicit table is provided, or if granularity inference fails.
846
+ typer.BadParameter: If the source database does not exist, if no
847
+ compatible rate view is available and no explicit table is
848
+ provided, or if granularity inference fails.
822
849
  """
823
- with sqlite3.connect(sqlite3_path) as conn:
850
+ try:
851
+ conn, _ = open_existing_sqlite_database(sqlite3_path)
852
+ except ValueError as exc:
853
+ raise typer.BadParameter(str(exc), param_hint="--sqlite3") from exc
854
+ with closing(conn):
824
855
  tables = list(table) if table else _default_gap_tables(conn)
825
856
  if not tables:
826
857
  msg = (
@@ -881,7 +912,8 @@ def collect_history(
881
912
  help=(
882
913
  "Dataset to include (repeat for multiple)."
883
914
  " Defaults to rates, history-orders, history-deals."
884
- " Ticks are opt-in: pass --dataset ticks to include them."
915
+ " Ticks and symbols metadata are opt-in:"
916
+ " pass --dataset ticks or --dataset symbols to include them."
885
917
  ),
886
918
  ),
887
919
  ] = None,
@@ -922,6 +954,9 @@ def collect_history(
922
954
  Tables written depend on ``--dataset``: ``rates``, ``history_orders``,
923
955
  ``history_deals`` by default. ``ticks`` are opt-in: pass
924
956
  ``--dataset ticks`` to include them (tick data grows the database quickly).
957
+ ``symbols`` is also opt-in: pass ``--dataset symbols`` to snapshot
958
+ per-symbol metadata (point, digits, contract size, volume limits) at
959
+ ``date_to``, one row per symbol per collection.
925
960
  History datasets are fetched per symbol and concatenated. Rates rows carry
926
961
  the requested ``timeframe`` so appended runs at different timeframes remain
927
962
  distinguishable.
@@ -44,6 +44,7 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
44
44
  "drop_forming_rate_bar",
45
45
  "ensure_symbol_selected",
46
46
  "estimate_order_margin",
47
+ "estimate_server_clock_offset_seconds",
47
48
  "extract_tick_price",
48
49
  "fetch_latest_closed_rates",
49
50
  "fetch_latest_closed_rates_for_trading_client",