mt5cli 1.0.1__tar.gz → 1.0.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.
Files changed (54) hide show
  1. {mt5cli-1.0.1 → mt5cli-1.0.3}/PKG-INFO +30 -30
  2. {mt5cli-1.0.1 → mt5cli-1.0.3}/README.md +29 -29
  3. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/index.md +22 -22
  4. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/cli.py +9 -6
  5. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/history.py +9 -3
  6. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/sdk.py +7 -4
  7. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/trading.py +10 -2
  8. {mt5cli-1.0.1 → mt5cli-1.0.3}/pyproject.toml +1 -1
  9. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/test_cli.py +43 -6
  10. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/test_history.py +16 -2
  11. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/test_sdk.py +64 -3
  12. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/test_trading.py +88 -0
  13. {mt5cli-1.0.1 → mt5cli-1.0.3}/uv.lock +2 -2
  14. {mt5cli-1.0.1 → mt5cli-1.0.3}/.agents/skills/local-qa/SKILL.md +0 -0
  15. {mt5cli-1.0.1 → mt5cli-1.0.3}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  16. {mt5cli-1.0.1 → mt5cli-1.0.3}/.agents/skills/mt5cli/SKILL.md +0 -0
  17. {mt5cli-1.0.1 → mt5cli-1.0.3}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  18. {mt5cli-1.0.1 → mt5cli-1.0.3}/.claude/agents/codex.md +0 -0
  19. {mt5cli-1.0.1 → mt5cli-1.0.3}/.claude/settings.json +0 -0
  20. {mt5cli-1.0.1 → mt5cli-1.0.3}/.github/FUNDING.yml +0 -0
  21. {mt5cli-1.0.1 → mt5cli-1.0.3}/.github/dependabot.yml +0 -0
  22. {mt5cli-1.0.1 → mt5cli-1.0.3}/.github/renovate.json +0 -0
  23. {mt5cli-1.0.1 → mt5cli-1.0.3}/.github/workflows/ci.yml +0 -0
  24. {mt5cli-1.0.1 → mt5cli-1.0.3}/.github/workflows/claude.yml +0 -0
  25. {mt5cli-1.0.1 → mt5cli-1.0.3}/.github/workflows/release.yml +0 -0
  26. {mt5cli-1.0.1 → mt5cli-1.0.3}/.gitignore +0 -0
  27. {mt5cli-1.0.1 → mt5cli-1.0.3}/AGENTS.md +0 -0
  28. {mt5cli-1.0.1 → mt5cli-1.0.3}/CLAUDE.md +0 -0
  29. {mt5cli-1.0.1 → mt5cli-1.0.3}/LICENSE +0 -0
  30. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/cli.md +0 -0
  31. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/client.md +0 -0
  32. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/converters.md +0 -0
  33. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/exceptions.md +0 -0
  34. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/history.md +0 -0
  35. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/index.md +0 -0
  36. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/public-contract.md +0 -0
  37. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/schemas.md +0 -0
  38. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/sdk.md +0 -0
  39. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/trading.md +0 -0
  40. {mt5cli-1.0.1 → mt5cli-1.0.3}/docs/api/utils.md +0 -0
  41. {mt5cli-1.0.1 → mt5cli-1.0.3}/mkdocs.yml +0 -0
  42. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/__init__.py +0 -0
  43. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/__main__.py +0 -0
  44. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/client.py +0 -0
  45. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/contract.py +0 -0
  46. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/converters.py +0 -0
  47. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/exceptions.py +0 -0
  48. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/retry.py +0 -0
  49. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/schemas.py +0 -0
  50. {mt5cli-1.0.1 → mt5cli-1.0.3}/mt5cli/utils.py +0 -0
  51. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/__init__.py +0 -0
  52. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/conftest.py +0 -0
  53. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/test_contracts.py +0 -0
  54. {mt5cli-1.0.1 → mt5cli-1.0.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 1.0.1
3
+ Version: 1.0.3
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>
@@ -182,34 +182,34 @@ python -m mt5cli -o account.csv account-info
182
182
 
183
183
  ## Commands
184
184
 
185
- | Command | Description |
186
- | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
187
- | `rates-from` | Export rates from a start date |
188
- | `rates-from-pos` | Export rates from a start position |
189
- | `latest-rates` | Export latest rates from a start position |
190
- | `rates-range` | Export rates for a date range |
191
- | `ticks-from` | Export ticks from a start date |
192
- | `ticks-range` | Export ticks for a date range |
193
- | `ticks-recent` | Export ticks from a recent trailing window |
194
- | `account-info` | Export account information |
195
- | `terminal-info` | Export terminal information |
196
- | `version` | Export MetaTrader 5 version information |
197
- | `last-error` | Export the last error information |
198
- | `symbols` | Export symbol list |
199
- | `symbol-info` | Export symbol details |
200
- | `symbol-info-tick` | Export the last tick for a symbol |
201
- | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
202
- | `market-book` | Export market depth (order book) |
203
- | `orders` | Export active orders |
204
- | `positions` | Export open positions |
205
- | `history-orders` | Export historical orders |
206
- | `history-deals` | Export historical deals |
207
- | `recent-history-deals` | Export historical deals from a recent trailing window |
208
- | `mt5-summary` | Export terminal/account status summary |
209
- | `order-check` | Check funds sufficiency for a trade request |
210
- | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
211
- | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available) |
212
- | `collect-history` | Bundle rates, ticks, history-orders, and history-deals for one or more symbols into a single SQLite database |
185
+ | Command | Description |
186
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
187
+ | `rates-from` | Export rates from a start date |
188
+ | `rates-from-pos` | Export rates from a start position |
189
+ | `latest-rates` | Export latest rates from a start position |
190
+ | `rates-range` | Export rates for a date range |
191
+ | `ticks-from` | Export ticks from a start date |
192
+ | `ticks-range` | Export ticks for a date range |
193
+ | `ticks-recent` | Export ticks from a recent trailing window |
194
+ | `account-info` | Export account information |
195
+ | `terminal-info` | Export terminal information |
196
+ | `version` | Export MetaTrader 5 version information |
197
+ | `last-error` | Export the last error information |
198
+ | `symbols` | Export symbol list |
199
+ | `symbol-info` | Export symbol details |
200
+ | `symbol-info-tick` | Export the last tick for a symbol |
201
+ | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
202
+ | `market-book` | Export market depth (order book) |
203
+ | `orders` | Export active orders |
204
+ | `positions` | Export open positions |
205
+ | `history-orders` | Export historical orders |
206
+ | `history-deals` | Export historical deals |
207
+ | `recent-history-deals` | Export historical deals from a recent trailing window |
208
+ | `mt5-summary` | Export terminal/account status summary |
209
+ | `order-check` | Check funds sufficiency for a trade request |
210
+ | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
211
+ | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available) |
212
+ | `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`) |
213
213
 
214
214
  Use `order-check` to validate a request payload before running `order-send --yes`.
215
215
  `close-positions` is the safer high-level alternative that builds correct close
@@ -217,7 +217,7 @@ requests automatically. At least one `--symbol` or `--ticket` must be provided.
217
217
 
218
218
  ### `collect-history`
219
219
 
220
- Collect several historical datasets per symbol into one SQLite database in a single MT5 session. Pick datasets with repeatable `--dataset` (default: all four), choose conflict behavior with `--if-exists append|replace|fail` (default: `fail`), and optionally derive `cash_events` / `positions_reconstructed` views from `history_deals` via `--with-views`.
220
+ Collect several historical datasets per symbol into one SQLite database in a single MT5 session. Pick datasets with repeatable `--dataset` (default: `rates`, `history-orders`, `history-deals`; add `--dataset ticks` when tick-level history is required — tick data can grow the SQLite database quickly), choose conflict behavior with `--if-exists append|replace|fail` (default: `fail`), and optionally derive `cash_events` / `positions_reconstructed` views from `history_deals` via `--with-views`.
221
221
 
222
222
  ```bash
223
223
  mt5cli -o history.db collect-history \
@@ -157,34 +157,34 @@ python -m mt5cli -o account.csv account-info
157
157
 
158
158
  ## Commands
159
159
 
160
- | Command | Description |
161
- | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
162
- | `rates-from` | Export rates from a start date |
163
- | `rates-from-pos` | Export rates from a start position |
164
- | `latest-rates` | Export latest rates from a start position |
165
- | `rates-range` | Export rates for a date range |
166
- | `ticks-from` | Export ticks from a start date |
167
- | `ticks-range` | Export ticks for a date range |
168
- | `ticks-recent` | Export ticks from a recent trailing window |
169
- | `account-info` | Export account information |
170
- | `terminal-info` | Export terminal information |
171
- | `version` | Export MetaTrader 5 version information |
172
- | `last-error` | Export the last error information |
173
- | `symbols` | Export symbol list |
174
- | `symbol-info` | Export symbol details |
175
- | `symbol-info-tick` | Export the last tick for a symbol |
176
- | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
177
- | `market-book` | Export market depth (order book) |
178
- | `orders` | Export active orders |
179
- | `positions` | Export open positions |
180
- | `history-orders` | Export historical orders |
181
- | `history-deals` | Export historical deals |
182
- | `recent-history-deals` | Export historical deals from a recent trailing window |
183
- | `mt5-summary` | Export terminal/account status summary |
184
- | `order-check` | Check funds sufficiency for a trade request |
185
- | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
186
- | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available) |
187
- | `collect-history` | Bundle rates, ticks, history-orders, and history-deals for one or more symbols into a single SQLite database |
160
+ | Command | Description |
161
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
162
+ | `rates-from` | Export rates from a start date |
163
+ | `rates-from-pos` | Export rates from a start position |
164
+ | `latest-rates` | Export latest rates from a start position |
165
+ | `rates-range` | Export rates for a date range |
166
+ | `ticks-from` | Export ticks from a start date |
167
+ | `ticks-range` | Export ticks for a date range |
168
+ | `ticks-recent` | Export ticks from a recent trailing window |
169
+ | `account-info` | Export account information |
170
+ | `terminal-info` | Export terminal information |
171
+ | `version` | Export MetaTrader 5 version information |
172
+ | `last-error` | Export the last error information |
173
+ | `symbols` | Export symbol list |
174
+ | `symbol-info` | Export symbol details |
175
+ | `symbol-info-tick` | Export the last tick for a symbol |
176
+ | `minimum-margins` | Export minimum-volume buy and sell margin requirements |
177
+ | `market-book` | Export market depth (order book) |
178
+ | `orders` | Export active orders |
179
+ | `positions` | Export open positions |
180
+ | `history-orders` | Export historical orders |
181
+ | `history-deals` | Export historical deals |
182
+ | `recent-history-deals` | Export historical deals from a recent trailing window |
183
+ | `mt5-summary` | Export terminal/account status summary |
184
+ | `order-check` | Check funds sufficiency for a trade request |
185
+ | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
186
+ | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available) |
187
+ | `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`) |
188
188
 
189
189
  Use `order-check` to validate a request payload before running `order-send --yes`.
190
190
  `close-positions` is the safer high-level alternative that builds correct close
@@ -192,7 +192,7 @@ requests automatically. At least one `--symbol` or `--ticket` must be provided.
192
192
 
193
193
  ### `collect-history`
194
194
 
195
- Collect several historical datasets per symbol into one SQLite database in a single MT5 session. Pick datasets with repeatable `--dataset` (default: all four), choose conflict behavior with `--if-exists append|replace|fail` (default: `fail`), and optionally derive `cash_events` / `positions_reconstructed` views from `history_deals` via `--with-views`.
195
+ Collect several historical datasets per symbol into one SQLite database in a single MT5 session. Pick datasets with repeatable `--dataset` (default: `rates`, `history-orders`, `history-deals`; add `--dataset ticks` when tick-level history is required — tick data can grow the SQLite database quickly), choose conflict behavior with `--if-exists append|replace|fail` (default: `fail`), and optionally derive `cash_events` / `positions_reconstructed` views from `history_deals` via `--with-views`.
196
196
 
197
197
  ```bash
198
198
  mt5cli -o history.db collect-history \
@@ -149,15 +149,15 @@ mt5cli --login 12345 --password mypass --server MyBroker-Demo \
149
149
 
150
150
  ### Trading State
151
151
 
152
- | Command | Description |
153
- | ---------------------- | --------------------------------------------------------------------- |
154
- | `orders` | Export active orders |
155
- | `positions` | Export open positions |
156
- | `history-orders` | Export historical orders |
157
- | `history-deals` | Export historical deals |
158
- | `recent-history-deals` | Export historical deals from a trailing window |
159
- | `mt5-summary` | Export terminal/account status summary |
160
- | `order-check` | Check funds sufficiency for a trade request (read-only, no `--yes`) |
152
+ | Command | Description |
153
+ | ---------------------- | ------------------------------------------------------------------- |
154
+ | `orders` | Export active orders |
155
+ | `positions` | Export open positions |
156
+ | `history-orders` | Export historical orders |
157
+ | `history-deals` | Export historical deals |
158
+ | `recent-history-deals` | Export historical deals from a trailing window |
159
+ | `mt5-summary` | Export terminal/account status summary |
160
+ | `order-check` | Check funds sufficiency for a trade request (read-only, no `--yes`) |
161
161
 
162
162
  ### Execution (live / mutating)
163
163
 
@@ -176,9 +176,9 @@ applications should prefer dedicated closing helpers or their own risk controls.
176
176
 
177
177
  ### Bulk Collection
178
178
 
179
- | Command | Description |
180
- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
181
- | `collect-history` | Collect rates, ticks, history-orders, and history-deals for one or more symbols into a single SQLite database (optional cash-event/position views) |
179
+ | Command | Description |
180
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181
+ | `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) |
182
182
 
183
183
  ```bash
184
184
  mt5cli -o history.db collect-history \
@@ -190,16 +190,16 @@ mt5cli -o history.db collect-history \
190
190
 
191
191
  `collect-history` options:
192
192
 
193
- | Option | Default | Description |
194
- | -------------- | ---------- | --------------------------------------------------------------------------------------------- |
195
- | `--symbol/-s` | _required_ | Symbol to collect (repeat for multiple). |
196
- | `--date-from` | _required_ | Start date in ISO 8601. |
197
- | `--date-to` | _required_ | End date in ISO 8601. |
198
- | `--dataset` | all four | Repeatable: `rates`, `ticks`, `history-orders`, `history-deals`. |
199
- | `--timeframe` | `M1` | Rates timeframe; recorded in a `timeframe` column on the `rates` table. |
200
- | `--flags` | `ALL` | Tick copy flags forwarded to `copy_ticks_range`. |
201
- | `--if-exists` | `fail` | `append`, `replace`, or `fail` when a target table already exists. |
202
- | `--with-views` | off | Add `cash_events` and `positions_reconstructed` views (requires the `history-deals` dataset). |
193
+ | Option | Default | Description |
194
+ | -------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
195
+ | `--symbol/-s` | _required_ | Symbol to collect (repeat for multiple). |
196
+ | `--date-from` | _required_ | Start date in ISO 8601. |
197
+ | `--date-to` | _required_ | End date in ISO 8601. |
198
+ | `--dataset` | rates, history-orders, history-deals | Repeatable: `rates`, `ticks`, `history-orders`, `history-deals`. Ticks are opt-in: pass `--dataset ticks` to include them. |
199
+ | `--timeframe` | `M1` | Rates timeframe; recorded in a `timeframe` column on the `rates` table. |
200
+ | `--flags` | `ALL` | Tick copy flags forwarded to `copy_ticks_range`. |
201
+ | `--if-exists` | `fail` | `append`, `replace`, or `fail` when a target table already exists. |
202
+ | `--with-views` | off | Add `cash_events` and `positions_reconstructed` views (requires the `history-deals` dataset). |
203
203
 
204
204
  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`.
205
205
 
@@ -727,7 +727,8 @@ def collect_history(
727
727
  "--dataset",
728
728
  help=(
729
729
  "Dataset to include (repeat for multiple)."
730
- " Defaults to all: rates, ticks, history-orders, history-deals."
730
+ " Defaults to rates, history-orders, history-deals."
731
+ " Ticks are opt-in: pass --dataset ticks to include them."
731
732
  ),
732
733
  ),
733
734
  ] = None,
@@ -765,10 +766,12 @@ def collect_history(
765
766
  ) -> None:
766
767
  """Collect historical datasets into a single SQLite database.
767
768
 
768
- Tables written depend on ``--dataset``: ``rates``, ``ticks``,
769
- ``history_orders``, ``history_deals``. History datasets are fetched per
770
- symbol and concatenated. Rates rows carry the requested ``timeframe`` so
771
- appended runs at different timeframes remain distinguishable.
769
+ Tables written depend on ``--dataset``: ``rates``, ``history_orders``,
770
+ ``history_deals`` by default. ``ticks`` are opt-in: pass
771
+ ``--dataset ticks`` to include them (tick data grows the database quickly).
772
+ History datasets are fetched per symbol and concatenated. Rates rows carry
773
+ the requested ``timeframe`` so appended runs at different timeframes remain
774
+ distinguishable.
772
775
 
773
776
  With ``--with-views`` (requires the ``history-deals`` dataset), optional
774
777
  views ``cash_events`` and ``positions_reconstructed`` are derived from
@@ -784,7 +787,7 @@ def collect_history(
784
787
  " Use a .db/.sqlite/.sqlite3 extension or --format sqlite3."
785
788
  )
786
789
  raise typer.BadParameter(msg)
787
- datasets = set(dataset) if dataset else set(Dataset)
790
+ datasets = set(dataset) if dataset is not None else None
788
791
  sdk.collect_history(
789
792
  output=export_ctx.output,
790
793
  symbols=symbol,
@@ -30,6 +30,11 @@ if TYPE_CHECKING:
30
30
  logger = logging.getLogger(__name__)
31
31
 
32
32
  DEFAULT_HISTORY_TIMEFRAMES: tuple[str, ...] = TIMEFRAME_NAMES
33
+ DEFAULT_HISTORY_DATASETS: frozenset[Dataset] = frozenset({
34
+ Dataset.rates,
35
+ Dataset.history_orders,
36
+ Dataset.history_deals,
37
+ })
33
38
 
34
39
  _HISTORY_DEDUP_KEYS: dict[Dataset, tuple[tuple[str, ...], ...]] = {
35
40
  Dataset.rates: DEDUP_KEYS[DataKind.rates],
@@ -62,11 +67,12 @@ def resolve_history_datasets(datasets: set[Dataset] | None) -> set[Dataset]:
62
67
  """Resolve configured history datasets.
63
68
 
64
69
  Returns:
65
- All supported datasets when ``datasets`` is None, otherwise the
66
- configured selection (which may be empty).
70
+ ``DEFAULT_HISTORY_DATASETS`` (rates, history-orders, history-deals)
71
+ when ``datasets`` is None, otherwise the configured selection (which
72
+ may be empty or explicitly include ``Dataset.ticks``).
67
73
  """
68
74
  if datasets is None:
69
- return set(Dataset)
75
+ return set(DEFAULT_HISTORY_DATASETS)
70
76
  return set(datasets)
71
77
 
72
78
 
@@ -981,7 +981,8 @@ def update_history( # noqa: PLR0913
981
981
  client: Connected MT5 data client.
982
982
  output: SQLite database path.
983
983
  symbols: Symbols to update.
984
- datasets: Datasets to include (defaults to all).
984
+ datasets: Datasets to include (defaults to rates, history-orders,
985
+ history-deals; pass ``{Dataset.ticks}`` to opt in to ticks).
985
986
  timeframes: Rate timeframes to update (defaults to all fixed MT5
986
987
  timeframes when None).
987
988
  flags: Tick copy flags as integer or name (e.g. ``ALL``).
@@ -1110,7 +1111,8 @@ class ThrottledHistoryUpdater:
1110
1111
 
1111
1112
  Args:
1112
1113
  output: SQLite database path.
1113
- datasets: Datasets to include (defaults to all).
1114
+ datasets: Datasets to include (defaults to rates, history-orders,
1115
+ history-deals; pass ``{Dataset.ticks}`` to opt in to ticks).
1114
1116
  timeframes: Rate timeframes to update (defaults to all fixed MT5
1115
1117
  timeframes).
1116
1118
  flags: Tick copy flags as integer or name (e.g. ``ALL``).
@@ -1242,7 +1244,8 @@ def collect_history(
1242
1244
  symbols: Symbols to collect.
1243
1245
  date_from: Start date.
1244
1246
  date_to: End date.
1245
- datasets: Datasets to include (defaults to all).
1247
+ datasets: Datasets to include (defaults to rates, history-orders,
1248
+ history-deals; pass ``{Dataset.ticks}`` to opt in to ticks).
1246
1249
  timeframe: Rates timeframe as integer or name (e.g. ``M1``).
1247
1250
  flags: Tick copy flags as integer or name (e.g. ``ALL``).
1248
1251
  if_exists: Behavior when a target table already exists.
@@ -1251,7 +1254,7 @@ def collect_history(
1251
1254
  """
1252
1255
  start = _require_datetime(date_from)
1253
1256
  end = _require_datetime(date_to)
1254
- selected = datasets if datasets is not None else set(Dataset)
1257
+ selected = resolve_history_datasets(datasets)
1255
1258
  tf = _coerce_timeframe(timeframe)
1256
1259
  tick_flags = _coerce_tick_flags(flags)
1257
1260
  mt5_config = config or build_config()
@@ -15,6 +15,7 @@ from .exceptions import Mt5OperationError
15
15
  from .history import drop_forming_rate_bar
16
16
  from .sdk import build_config
17
17
  from .utils import coerce_login as _coerce_login
18
+ from .utils import parse_timeframe
18
19
 
19
20
  if TYPE_CHECKING:
20
21
  from collections.abc import Iterator, Mapping, Sequence
@@ -1624,11 +1625,18 @@ def fetch_latest_closed_rates_for_trading_client(
1624
1625
  if count <= 0:
1625
1626
  msg = "count must be positive."
1626
1627
  raise ValueError(msg)
1628
+ timeframe = parse_timeframe(granularity)
1627
1629
  fetch_method = getattr(client, "fetch_latest_rates_as_df", None)
1628
- if not callable(fetch_method):
1630
+ copy_method = getattr(client, "copy_rates_from_pos_as_df", None)
1631
+ if callable(fetch_method):
1632
+ fetched = fetch_method(symbol, granularity, count + 1)
1633
+ elif callable(copy_method):
1634
+ fetched = copy_method(
1635
+ symbol=symbol, timeframe=timeframe, start_pos=0, count=count + 1
1636
+ )
1637
+ else:
1629
1638
  msg = "MT5 trading client cannot fetch rate data."
1630
1639
  raise Mt5OperationError(msg)
1631
- fetched = fetch_method(symbol, granularity, count + 1)
1632
1640
  if not isinstance(fetched, pd.DataFrame):
1633
1641
  msg = (
1634
1642
  f"Malformed rate data for {symbol!r} at granularity {granularity!r}: "
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mt5cli"
3
- version = "1.0.1"
3
+ version = "1.0.3"
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"}]
@@ -1298,12 +1298,12 @@ class TestCollectHistory:
1298
1298
  """Create a mocked Mt5DataClient with history-style DataFrames."""
1299
1299
  return _build_history_client(mocker)
1300
1300
 
1301
- def test_collect_history_writes_all_tables(
1301
+ def test_collect_history_writes_default_tables(
1302
1302
  self,
1303
1303
  tmp_path: Path,
1304
1304
  history_client: MagicMock,
1305
1305
  ) -> None:
1306
- """Test that collect-history writes rates, ticks, and history tables."""
1306
+ """Test that collect-history default excludes ticks."""
1307
1307
  output = tmp_path / "history.db"
1308
1308
  result = runner.invoke(
1309
1309
  app,
@@ -1323,8 +1323,42 @@ class TestCollectHistory:
1323
1323
  )
1324
1324
  assert result.exit_code == 0, result.output
1325
1325
  assert history_client.copy_rates_range_as_df.call_count == 2
1326
- assert history_client.copy_ticks_range_as_df.call_count == 2
1327
- history_client.copy_ticks_range_as_df.assert_any_call(
1326
+ assert history_client.copy_ticks_range_as_df.call_count == 0
1327
+ with sqlite3.connect(output) as conn:
1328
+ tables = {
1329
+ row[0]
1330
+ for row in conn.execute(
1331
+ "SELECT name FROM sqlite_master WHERE type='table'",
1332
+ ).fetchall()
1333
+ }
1334
+ assert {"rates", "history_orders", "history_deals"} <= tables
1335
+ assert "ticks" not in tables
1336
+
1337
+ def test_collect_history_explicit_ticks_dataset(
1338
+ self,
1339
+ tmp_path: Path,
1340
+ history_client: MagicMock,
1341
+ ) -> None:
1342
+ """Test that --dataset ticks writes the ticks table with the correct flags."""
1343
+ output = tmp_path / "history.db"
1344
+ result = runner.invoke(
1345
+ app,
1346
+ [
1347
+ "-o",
1348
+ str(output),
1349
+ "collect-history",
1350
+ "--symbol",
1351
+ "EURUSD",
1352
+ "--date-from",
1353
+ "2024-01-01",
1354
+ "--date-to",
1355
+ "2024-02-01",
1356
+ "--dataset",
1357
+ "ticks",
1358
+ ],
1359
+ )
1360
+ assert result.exit_code == 0, result.output
1361
+ history_client.copy_ticks_range_as_df.assert_called_once_with(
1328
1362
  symbol="EURUSD",
1329
1363
  date_from=datetime(2024, 1, 1, tzinfo=UTC),
1330
1364
  date_to=datetime(2024, 2, 1, tzinfo=UTC),
@@ -1337,7 +1371,8 @@ class TestCollectHistory:
1337
1371
  "SELECT name FROM sqlite_master WHERE type='table'",
1338
1372
  ).fetchall()
1339
1373
  }
1340
- assert {"rates", "ticks", "history_orders", "history_deals"} <= tables
1374
+ assert "ticks" in tables
1375
+ assert "rates" not in tables
1341
1376
 
1342
1377
  def test_collect_history_history_fetched_per_symbol(
1343
1378
  self,
@@ -1520,7 +1555,7 @@ class TestCollectHistory:
1520
1555
  tmp_path: Path,
1521
1556
  history_client: MagicMock,
1522
1557
  ) -> None:
1523
- """Test that --flags defaults to ALL for ticks."""
1558
+ """Test that --flags defaults to ALL when --dataset ticks is explicit."""
1524
1559
  output = tmp_path / "history.db"
1525
1560
  result = runner.invoke(
1526
1561
  app,
@@ -1534,6 +1569,8 @@ class TestCollectHistory:
1534
1569
  "2024-01-01",
1535
1570
  "--date-to",
1536
1571
  "2024-02-01",
1572
+ "--dataset",
1573
+ "ticks",
1537
1574
  ],
1538
1575
  )
1539
1576
  assert result.exit_code == 0, result.output
@@ -19,6 +19,7 @@ from pdmt5 import TIMEFRAME_MAP
19
19
 
20
20
  from mt5cli import history
21
21
  from mt5cli.history import (
22
+ DEFAULT_HISTORY_DATASETS,
22
23
  DEFAULT_HISTORY_TIMEFRAMES,
23
24
  DedupScope,
24
25
  RateTarget,
@@ -547,10 +548,23 @@ class TestResolveHistorySettings:
547
548
  """Tests for history dataset and timeframe resolution."""
548
549
 
549
550
  def test_resolve_history_datasets_defaults_and_empty(self) -> None:
550
- """Test dataset resolution distinguishes None from empty selection."""
551
- assert resolve_history_datasets(None) == set(Dataset)
551
+ """Test dataset resolution excludes ticks by default."""
552
+ resolved = resolve_history_datasets(None)
553
+ assert resolved == set(DEFAULT_HISTORY_DATASETS)
554
+ assert Dataset.ticks not in resolved
555
+ assert {
556
+ Dataset.rates,
557
+ Dataset.history_orders,
558
+ Dataset.history_deals,
559
+ } == resolved
552
560
  assert resolve_history_datasets(set()) == set()
553
561
 
562
+ def test_resolve_history_datasets_explicit_ticks(self) -> None:
563
+ """Test that explicit ticks selection is honored."""
564
+ assert resolve_history_datasets({Dataset.ticks}) == {Dataset.ticks}
565
+ all_ds = resolve_history_datasets(set(Dataset))
566
+ assert Dataset.ticks in all_ds
567
+
554
568
  def test_resolve_history_timeframes_defaults(self) -> None:
555
569
  """Test default timeframes include all fixed MT5 values."""
556
570
  resolved = resolve_history_timeframes(None)
@@ -618,12 +618,12 @@ class TestCollectHistory:
618
618
  """Create a mocked Mt5DataClient with history-style DataFrames."""
619
619
  return _build_history_client(mocker)
620
620
 
621
- def test_collect_history_writes_all_tables(
621
+ def test_collect_history_writes_default_tables(
622
622
  self,
623
623
  tmp_path: Path,
624
624
  history_client: MagicMock,
625
625
  ) -> None:
626
- """Test that collect_history writes rates, ticks, and history tables."""
626
+ """Test that collect_history default excludes ticks."""
627
627
  output = tmp_path / "history.db"
628
628
  collect_history(
629
629
  output,
@@ -632,7 +632,33 @@ class TestCollectHistory:
632
632
  "2024-02-01",
633
633
  )
634
634
  assert history_client.copy_rates_range_as_df.call_count == 2
635
+ assert history_client.copy_ticks_range_as_df.call_count == 0
636
+ with sqlite3.connect(output) as conn:
637
+ tables = {
638
+ row[0]
639
+ for row in conn.execute(
640
+ "SELECT name FROM sqlite_master WHERE type='table'",
641
+ ).fetchall()
642
+ }
643
+ assert {"rates", "history_orders", "history_deals"} <= tables
644
+ assert "ticks" not in tables
645
+
646
+ def test_collect_history_explicit_ticks_dataset(
647
+ self,
648
+ tmp_path: Path,
649
+ history_client: MagicMock,
650
+ ) -> None:
651
+ """Test that explicit datasets={Dataset.ticks} writes the ticks table."""
652
+ output = tmp_path / "history.db"
653
+ collect_history(
654
+ output,
655
+ ["EURUSD", "GBPUSD"],
656
+ "2024-01-01",
657
+ "2024-02-01",
658
+ datasets={Dataset.ticks},
659
+ )
635
660
  assert history_client.copy_ticks_range_as_df.call_count == 2
661
+ assert history_client.copy_rates_range_as_df.call_count == 0
636
662
  with sqlite3.connect(output) as conn:
637
663
  tables = {
638
664
  row[0]
@@ -640,7 +666,8 @@ class TestCollectHistory:
640
666
  "SELECT name FROM sqlite_master WHERE type='table'",
641
667
  ).fetchall()
642
668
  }
643
- assert {"rates", "ticks", "history_orders", "history_deals"} <= tables
669
+ assert "ticks" in tables
670
+ assert "rates" not in tables
644
671
 
645
672
  def test_collect_history_with_views(
646
673
  self,
@@ -1066,6 +1093,40 @@ class TestUpdateHistory:
1066
1093
  after = datetime.now(UTC)
1067
1094
  assert before <= captured["end"] <= after
1068
1095
 
1096
+ def test_update_history_default_datasets_exclude_ticks(
1097
+ self,
1098
+ connected_client: MagicMock,
1099
+ mocker: MockerFixture,
1100
+ tmp_path: Path,
1101
+ ) -> None:
1102
+ """Test update_history with datasets=None does not collect ticks."""
1103
+ datasets_written: list[set[Dataset]] = []
1104
+
1105
+ def capture(
1106
+ *args: object,
1107
+ **_kwargs: object,
1108
+ ) -> tuple[set[Dataset], dict[Dataset, set[str]]]:
1109
+ datasets_written.append(args[3]) # type: ignore[arg-type]
1110
+ return set(), {}
1111
+
1112
+ mocker.patch("mt5cli.sdk.write_incremental_datasets", side_effect=capture)
1113
+ update_history(
1114
+ client=connected_client,
1115
+ output=tmp_path / "default-datasets.db",
1116
+ symbols=["EURUSD"],
1117
+ datasets=None,
1118
+ timeframes=["M1"],
1119
+ lookback_hours=1,
1120
+ date_to=datetime(2024, 1, 1, tzinfo=UTC),
1121
+ )
1122
+ assert len(datasets_written) == 1
1123
+ assert Dataset.ticks not in datasets_written[0]
1124
+ assert {
1125
+ Dataset.rates,
1126
+ Dataset.history_orders,
1127
+ Dataset.history_deals,
1128
+ } == datasets_written[0]
1129
+
1069
1130
 
1070
1131
  class TestRecentTicks:
1071
1132
  """Tests for recent_ticks helper."""
@@ -3198,6 +3198,71 @@ class TestFetchLatestClosedRatesForTradingClient:
3198
3198
  count=2,
3199
3199
  )
3200
3200
 
3201
+ def test_copy_rates_from_pos_fallback_drops_forming_bar(self) -> None:
3202
+ """Regression: client with only copy_rates_from_pos_as_df works end-to-end."""
3203
+ client = MagicMock(spec=["copy_rates_from_pos_as_df"])
3204
+ client.copy_rates_from_pos_as_df.return_value = pd.DataFrame(
3205
+ {
3206
+ "time": [1700000000, 1700000060, 1700000120],
3207
+ "close": [1.0, 1.1, 1.2],
3208
+ },
3209
+ )
3210
+
3211
+ result = fetch_latest_closed_rates_for_trading_client(
3212
+ client,
3213
+ symbol="EURUSD",
3214
+ granularity="M1",
3215
+ count=2,
3216
+ )
3217
+
3218
+ client.copy_rates_from_pos_as_df.assert_called_once_with(
3219
+ symbol="EURUSD", timeframe=1, start_pos=0, count=3
3220
+ )
3221
+ assert list(result["close"]) == [1.0, 1.1]
3222
+ assert list(result["time"]) == [1700000000, 1700000060]
3223
+
3224
+ def test_copy_rates_from_pos_fallback_resolves_granularity(self) -> None:
3225
+ """Fallback path resolves granularity string to integer timeframe."""
3226
+ client = MagicMock(spec=["copy_rates_from_pos_as_df"])
3227
+ client.copy_rates_from_pos_as_df.return_value = pd.DataFrame(
3228
+ {"time": [1, 2, 3, 4], "close": [1.0, 1.1, 1.2, 1.3]},
3229
+ )
3230
+
3231
+ fetch_latest_closed_rates_for_trading_client(
3232
+ client, symbol="USDJPY", granularity="H1", count=3
3233
+ )
3234
+
3235
+ call_kwargs = client.copy_rates_from_pos_as_df.call_args.kwargs
3236
+ assert call_kwargs["symbol"] == "USDJPY"
3237
+ assert call_kwargs["timeframe"] == 16385
3238
+ assert call_kwargs["start_pos"] == 0
3239
+ assert call_kwargs["count"] == 4
3240
+
3241
+ def test_copy_rates_from_pos_fallback_returns_count_closed_rows(self) -> None:
3242
+ """Fallback path trims to exactly count closed rows after forming-bar drop."""
3243
+ client = MagicMock(spec=["copy_rates_from_pos_as_df"])
3244
+ client.copy_rates_from_pos_as_df.return_value = pd.DataFrame(
3245
+ {"time": list(range(6)), "close": [float(i) for i in range(6)]},
3246
+ )
3247
+
3248
+ result = fetch_latest_closed_rates_for_trading_client(
3249
+ client, symbol="EURUSD", granularity="M1", count=4
3250
+ )
3251
+
3252
+ assert len(result) == 4
3253
+ assert list(result["close"]) == [1.0, 2.0, 3.0, 4.0]
3254
+
3255
+ def test_copy_rates_from_pos_fallback_raises_on_invalid_granularity(self) -> None:
3256
+ """Invalid granularity raises ValueError before calling the fallback method."""
3257
+ client = MagicMock(spec=["copy_rates_from_pos_as_df"])
3258
+
3259
+ with pytest.raises(ValueError, match="Invalid timeframe"):
3260
+ fetch_latest_closed_rates_for_trading_client(
3261
+ client, symbol="EURUSD", granularity="BADGRAN", count=1
3262
+ )
3263
+
3264
+ client.copy_rates_from_pos_as_df.assert_not_called()
3265
+
3201
3266
  def test_raises_when_trading_client_cannot_fetch_rates(self) -> None:
3202
3267
  """Test missing rate-fetch methods raise Mt5TradingError."""
3203
3268
  client = MagicMock(spec=[])
@@ -3528,6 +3593,29 @@ class TestFetchLatestClosedRatesIndexed:
3528
3593
  assert "close" in result.columns
3529
3594
  assert isinstance(result.index, pd.DatetimeIndex)
3530
3595
 
3596
+ def test_copy_rates_from_pos_fallback_produces_utc_datetime_index(self) -> None:
3597
+ """Fallback path via copy_rates_from_pos_as_df produces a UTC DatetimeIndex."""
3598
+ client = MagicMock(spec=["copy_rates_from_pos_as_df"])
3599
+ client.copy_rates_from_pos_as_df.return_value = pd.DataFrame(
3600
+ {
3601
+ "time": [1700000000, 1700003600, 1700007200],
3602
+ "close": [1.1, 1.2, 1.3],
3603
+ },
3604
+ )
3605
+
3606
+ result = fetch_latest_closed_rates_indexed(
3607
+ client,
3608
+ symbol="EURUSD",
3609
+ granularity="M1",
3610
+ count=2,
3611
+ )
3612
+
3613
+ assert isinstance(result.index, pd.DatetimeIndex)
3614
+ assert result.index.name == "time"
3615
+ assert str(result.index.tz) == "UTC"
3616
+ assert "time" not in result.columns
3617
+ assert list(result["close"]) == [1.1, 1.2]
3618
+
3531
3619
 
3532
3620
  class TestExtractTickPrice:
3533
3621
  """Tests for the public extract_tick_price helper."""
@@ -358,7 +358,7 @@ name = "metatrader5"
358
358
  version = "5.0.5640"
359
359
  source = { registry = "https://pypi.org/simple" }
360
360
  dependencies = [
361
- { name = "numpy", marker = "sys_platform == 'win32'" },
361
+ { name = "numpy" },
362
362
  ]
363
363
  wheels = [
364
364
  { url = "https://files.pythonhosted.org/packages/ef/a0/3b764c6743ef601ff12f7d8d62ca5768eb25e90d3758ac7e1d08e667af85/metatrader5-5.0.5640-cp311-cp311-win_amd64.whl", hash = "sha256:4057255f2d63138a3ea1a5d492715038a71d3177cad793fca97a5c58771b4eb1", size = 48091, upload-time = "2026-02-20T23:31:12.289Z" },
@@ -487,7 +487,7 @@ wheels = [
487
487
 
488
488
  [[package]]
489
489
  name = "mt5cli"
490
- version = "1.0.1"
490
+ version = "1.0.3"
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