mt5cli 1.0.0__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {mt5cli-1.0.0 → mt5cli-1.0.2}/PKG-INFO +3 -3
  2. {mt5cli-1.0.0 → mt5cli-1.0.2}/README.md +2 -2
  3. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/index.md +38 -26
  4. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/cli.py +50 -36
  5. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/history.py +9 -3
  6. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/sdk.py +7 -4
  7. {mt5cli-1.0.0 → mt5cli-1.0.2}/pyproject.toml +1 -1
  8. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_cli.py +103 -6
  9. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_history.py +16 -2
  10. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_sdk.py +64 -3
  11. {mt5cli-1.0.0 → mt5cli-1.0.2}/uv.lock +2 -2
  12. {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/local-qa/SKILL.md +0 -0
  13. {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  14. {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/mt5cli/SKILL.md +0 -0
  15. {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  16. {mt5cli-1.0.0 → mt5cli-1.0.2}/.claude/agents/codex.md +0 -0
  17. {mt5cli-1.0.0 → mt5cli-1.0.2}/.claude/settings.json +0 -0
  18. {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/FUNDING.yml +0 -0
  19. {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/dependabot.yml +0 -0
  20. {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/renovate.json +0 -0
  21. {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/workflows/ci.yml +0 -0
  22. {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/workflows/claude.yml +0 -0
  23. {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/workflows/release.yml +0 -0
  24. {mt5cli-1.0.0 → mt5cli-1.0.2}/.gitignore +0 -0
  25. {mt5cli-1.0.0 → mt5cli-1.0.2}/AGENTS.md +0 -0
  26. {mt5cli-1.0.0 → mt5cli-1.0.2}/CLAUDE.md +0 -0
  27. {mt5cli-1.0.0 → mt5cli-1.0.2}/LICENSE +0 -0
  28. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/cli.md +0 -0
  29. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/client.md +0 -0
  30. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/converters.md +0 -0
  31. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/exceptions.md +0 -0
  32. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/history.md +0 -0
  33. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/index.md +0 -0
  34. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/public-contract.md +0 -0
  35. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/schemas.md +0 -0
  36. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/sdk.md +0 -0
  37. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/trading.md +0 -0
  38. {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/utils.md +0 -0
  39. {mt5cli-1.0.0 → mt5cli-1.0.2}/mkdocs.yml +0 -0
  40. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/__init__.py +0 -0
  41. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/__main__.py +0 -0
  42. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/client.py +0 -0
  43. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/contract.py +0 -0
  44. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/converters.py +0 -0
  45. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/exceptions.py +0 -0
  46. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/retry.py +0 -0
  47. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/schemas.py +0 -0
  48. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/trading.py +0 -0
  49. {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/utils.py +0 -0
  50. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/__init__.py +0 -0
  51. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/conftest.py +0 -0
  52. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_contracts.py +0 -0
  53. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_trading.py +0 -0
  54. {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Generic MT5 data and execution infrastructure for Python applications
5
5
  Project-URL: Repository, https://github.com/dceoy/mt5cli.git
6
6
  Author-email: dceoy <dceoy@users.noreply.github.com>
@@ -209,7 +209,7 @@ python -m mt5cli -o account.csv account-info
209
209
  | `order-check` | Check funds sufficiency for a trade request |
210
210
  | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
211
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 |
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 \
@@ -184,7 +184,7 @@ python -m mt5cli -o account.csv account-info
184
184
  | `order-check` | Check funds sufficiency for a trade request |
185
185
  | `order-send` | Send a raw trade request to the trade server (`--yes` required; expert path) |
186
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 |
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 \
@@ -147,26 +147,38 @@ mt5cli --login 12345 --password mypass --server MyBroker-Demo \
147
147
  | `minimum-margins` | Export minimum-volume margin summary |
148
148
  | `market-book` | Export market depth (order book) |
149
149
 
150
- ### Trading
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 |
161
- | `order-send` | Send a trade request to the trade server (`--yes` required) |
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`) |
162
161
 
163
- Use `order-check` to validate a request payload before running `order-send --yes`.
162
+ ### Execution (live / mutating)
163
+
164
+ These commands send requests to the live trade server and can place or close
165
+ real trades. Both require `--yes` for live execution.
166
+
167
+ | Command | Description |
168
+ | ----------------- | ---------------------------------------------------------------------------------------------------- |
169
+ | `order-send` | Send a **raw** trade request directly to MT5 (`--yes` required; expert path — no extra validation) |
170
+ | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` to preview) |
171
+
172
+ Use `order-check` (Trading State) to validate funds before running `order-send --yes`.
173
+ `close-positions` is the safer high-level alternative that builds correct close
174
+ requests automatically. `order-send` is the expert raw path — downstream
175
+ applications should prefer dedicated closing helpers or their own risk controls.
164
176
 
165
177
  ### Bulk Collection
166
178
 
167
- | Command | Description |
168
- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
169
- | `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) |
170
182
 
171
183
  ```bash
172
184
  mt5cli -o history.db collect-history \
@@ -178,16 +190,16 @@ mt5cli -o history.db collect-history \
178
190
 
179
191
  `collect-history` options:
180
192
 
181
- | Option | Default | Description |
182
- | -------------- | ---------- | --------------------------------------------------------------------------------------------- |
183
- | `--symbol/-s` | _required_ | Symbol to collect (repeat for multiple). |
184
- | `--date-from` | _required_ | Start date in ISO 8601. |
185
- | `--date-to` | _required_ | End date in ISO 8601. |
186
- | `--dataset` | all four | Repeatable: `rates`, `ticks`, `history-orders`, `history-deals`. |
187
- | `--timeframe` | `M1` | Rates timeframe; recorded in a `timeframe` column on the `rates` table. |
188
- | `--flags` | `ALL` | Tick copy flags forwarded to `copy_ticks_range`. |
189
- | `--if-exists` | `fail` | `append`, `replace`, or `fail` when a target table already exists. |
190
- | `--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). |
191
203
 
192
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`.
193
205
 
@@ -217,7 +229,7 @@ See the [History schema diagram](api/history.md#entity-relationship-diagram) for
217
229
 
218
230
  Browse the API documentation for detailed module information:
219
231
 
220
- - [CLI Module](api/cli.md) - CLI application with export commands
232
+ - [CLI Module](api/cli.md) - CLI application with data export and execution commands
221
233
  - [SDK Module](api/sdk.md) - Programmatic read-only data collection API
222
234
  - [Utils Module](api/utils.md) - Constants, parameter types, parsers, and export utilities
223
235
 
@@ -1,4 +1,4 @@
1
- """Command-line interface for MetaTrader 5 data export."""
1
+ """Command-line interface for MetaTrader 5 data and execution utilities."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -55,7 +55,12 @@ class _ExportContext:
55
55
 
56
56
  app = typer.Typer(
57
57
  name="mt5cli",
58
- help="Export MetaTrader5 data to CSV, JSON, Parquet, or SQLite3.",
58
+ help=(
59
+ "MT5 data and execution utilities — read market data, inspect account"
60
+ " state, and send trade requests. Data commands write to CSV, JSON,"
61
+ " Parquet, or SQLite3. Execution commands (order-send, close-positions)"
62
+ " require --yes for live mutations."
63
+ ),
59
64
  )
60
65
 
61
66
  _REQUEST_OPTION_HELP = (
@@ -151,7 +156,7 @@ def _callback( # pyright: ignore[reportUnusedFunction]
151
156
  typer.Option("--log-level", help="Logging level."),
152
157
  ] = LogLevel.WARNING,
153
158
  ) -> None:
154
- """Configure shared options for all export commands.
159
+ """Configure shared connection and output options.
155
160
 
156
161
  Raises:
157
162
  typer.BadParameter: If the output format cannot be determined.
@@ -183,7 +188,7 @@ def _callback( # pyright: ignore[reportUnusedFunction]
183
188
  # ---------------------------------------------------------------------------
184
189
 
185
190
 
186
- @app.command()
191
+ @app.command(rich_help_panel="Data / Export")
187
192
  def rates_from(
188
193
  ctx: typer.Context,
189
194
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -210,7 +215,7 @@ def rates_from(
210
215
  )
211
216
 
212
217
 
213
- @app.command()
218
+ @app.command(rich_help_panel="Data / Export")
214
219
  def rates_from_pos(
215
220
  ctx: typer.Context,
216
221
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -236,7 +241,7 @@ def rates_from_pos(
236
241
  )
237
242
 
238
243
 
239
- @app.command()
244
+ @app.command(rich_help_panel="Data / Export")
240
245
  def latest_rates(
241
246
  ctx: typer.Context,
242
247
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -265,7 +270,7 @@ def latest_rates(
265
270
  )
266
271
 
267
272
 
268
- @app.command()
273
+ @app.command(rich_help_panel="Data / Export")
269
274
  def rates_range(
270
275
  ctx: typer.Context,
271
276
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -292,7 +297,7 @@ def rates_range(
292
297
  )
293
298
 
294
299
 
295
- @app.command()
300
+ @app.command(rich_help_panel="Data / Export")
296
301
  def ticks_from(
297
302
  ctx: typer.Context,
298
303
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -316,7 +321,7 @@ def ticks_from(
316
321
  )
317
322
 
318
323
 
319
- @app.command()
324
+ @app.command(rich_help_panel="Data / Export")
320
325
  def ticks_range(
321
326
  ctx: typer.Context,
322
327
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -340,7 +345,7 @@ def ticks_range(
340
345
  )
341
346
 
342
347
 
343
- @app.command()
348
+ @app.command(rich_help_panel="Data / Export")
344
349
  def ticks_recent(
345
350
  ctx: typer.Context,
346
351
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -377,19 +382,19 @@ def ticks_recent(
377
382
  )
378
383
 
379
384
 
380
- @app.command()
385
+ @app.command(rich_help_panel="Data / Export")
381
386
  def account_info(ctx: typer.Context) -> None:
382
387
  """Export account information."""
383
388
  _export_command(ctx, lambda client: client.account_info())
384
389
 
385
390
 
386
- @app.command()
391
+ @app.command(rich_help_panel="Data / Export")
387
392
  def terminal_info(ctx: typer.Context) -> None:
388
393
  """Export terminal information."""
389
394
  _export_command(ctx, lambda client: client.terminal_info())
390
395
 
391
396
 
392
- @app.command()
397
+ @app.command(rich_help_panel="Data / Export")
393
398
  def symbols(
394
399
  ctx: typer.Context,
395
400
  group: Annotated[
@@ -401,7 +406,7 @@ def symbols(
401
406
  _export_command(ctx, lambda client: client.symbols(group=group))
402
407
 
403
408
 
404
- @app.command()
409
+ @app.command(rich_help_panel="Data / Export")
405
410
  def symbol_info(
406
411
  ctx: typer.Context,
407
412
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -410,7 +415,7 @@ def symbol_info(
410
415
  _export_command(ctx, lambda client: client.symbol_info(symbol))
411
416
 
412
417
 
413
- @app.command()
418
+ @app.command(rich_help_panel="Data / Export")
414
419
  def minimum_margins(
415
420
  ctx: typer.Context,
416
421
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -419,7 +424,7 @@ def minimum_margins(
419
424
  _export_command(ctx, lambda client: client.minimum_margins(symbol))
420
425
 
421
426
 
422
- @app.command()
427
+ @app.command(rich_help_panel="Data / Export")
423
428
  def orders(
424
429
  ctx: typer.Context,
425
430
  symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
@@ -433,7 +438,7 @@ def orders(
433
438
  )
434
439
 
435
440
 
436
- @app.command()
441
+ @app.command(rich_help_panel="Data / Export")
437
442
  def positions(
438
443
  ctx: typer.Context,
439
444
  symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
@@ -447,7 +452,7 @@ def positions(
447
452
  )
448
453
 
449
454
 
450
- @app.command()
455
+ @app.command(rich_help_panel="Data / Export")
451
456
  def history_orders(
452
457
  ctx: typer.Context,
453
458
  date_from: Annotated[
@@ -477,7 +482,7 @@ def history_orders(
477
482
  )
478
483
 
479
484
 
480
- @app.command()
485
+ @app.command(rich_help_panel="Data / Export")
481
486
  def history_deals(
482
487
  ctx: typer.Context,
483
488
  date_from: Annotated[
@@ -507,7 +512,7 @@ def history_deals(
507
512
  )
508
513
 
509
514
 
510
- @app.command()
515
+ @app.command(rich_help_panel="Data / Export")
511
516
  def recent_history_deals(
512
517
  ctx: typer.Context,
513
518
  hours: Annotated[float, typer.Option(help="Lookback window in hours.")],
@@ -530,25 +535,25 @@ def recent_history_deals(
530
535
  )
531
536
 
532
537
 
533
- @app.command()
538
+ @app.command(rich_help_panel="Data / Export")
534
539
  def mt5_summary(ctx: typer.Context) -> None:
535
540
  """Export a compact terminal/account status summary."""
536
541
  _export_command(ctx, lambda client: client.mt5_summary_as_df())
537
542
 
538
543
 
539
- @app.command()
544
+ @app.command(rich_help_panel="Data / Export")
540
545
  def version(ctx: typer.Context) -> None:
541
546
  """Export MetaTrader5 version information."""
542
547
  _export_command(ctx, lambda client: client.version())
543
548
 
544
549
 
545
- @app.command()
550
+ @app.command(rich_help_panel="Data / Export")
546
551
  def last_error(ctx: typer.Context) -> None:
547
552
  """Export the last error information."""
548
553
  _export_command(ctx, lambda client: client.last_error())
549
554
 
550
555
 
551
- @app.command()
556
+ @app.command(rich_help_panel="Data / Export")
552
557
  def symbol_info_tick(
553
558
  ctx: typer.Context,
554
559
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -557,7 +562,7 @@ def symbol_info_tick(
557
562
  _export_command(ctx, lambda client: client.symbol_info_tick(symbol))
558
563
 
559
564
 
560
- @app.command()
565
+ @app.command(rich_help_panel="Data / Export")
561
566
  def market_book(
562
567
  ctx: typer.Context,
563
568
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
@@ -566,7 +571,7 @@ def market_book(
566
571
  _export_command(ctx, lambda client: client.market_book(symbol))
567
572
 
568
573
 
569
- @app.command()
574
+ @app.command(rich_help_panel="Data / Export")
570
575
  def order_check(
571
576
  ctx: typer.Context,
572
577
  request: Annotated[
@@ -578,7 +583,7 @@ def order_check(
578
583
  _export_command(ctx, lambda client: client.order_check(request))
579
584
 
580
585
 
581
- @app.command()
586
+ @app.command(rich_help_panel="Execution")
582
587
  def order_send(
583
588
  ctx: typer.Context,
584
589
  request: Annotated[
@@ -590,7 +595,13 @@ def order_send(
590
595
  typer.Option("--yes", help="Confirm the live trade request."),
591
596
  ] = False,
592
597
  ) -> None:
593
- """Send a trading operation request to the trade server.
598
+ """Send a raw trade request to the trade server (expert path, live execution).
599
+
600
+ Passes the request JSON directly to MT5 ``order_send``. This is the
601
+ low-level expert path — it places real trades on the connected account
602
+ with no additional validation beyond what MT5 itself performs. Use
603
+ ``order-check`` first to validate funds sufficiency. Prefer
604
+ ``close-positions`` for closing open positions. ``--yes`` is required.
594
605
 
595
606
  Raises:
596
607
  typer.BadParameter: If --yes is not provided.
@@ -628,7 +639,7 @@ def _execution_results_to_df(results: list[OrderExecutionResult]) -> pd.DataFram
628
639
  return pd.DataFrame(rows)
629
640
 
630
641
 
631
- @app.command()
642
+ @app.command(rich_help_panel="Execution")
632
643
  def close_positions(
633
644
  ctx: typer.Context,
634
645
  symbol: Annotated[
@@ -691,7 +702,7 @@ def close_positions(
691
702
  _execute_export(ctx, lambda: df)
692
703
 
693
704
 
694
- @app.command()
705
+ @app.command(rich_help_panel="Collection")
695
706
  def collect_history(
696
707
  ctx: typer.Context,
697
708
  symbol: Annotated[
@@ -716,7 +727,8 @@ def collect_history(
716
727
  "--dataset",
717
728
  help=(
718
729
  "Dataset to include (repeat for multiple)."
719
- " 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."
720
732
  ),
721
733
  ),
722
734
  ] = None,
@@ -754,10 +766,12 @@ def collect_history(
754
766
  ) -> None:
755
767
  """Collect historical datasets into a single SQLite database.
756
768
 
757
- Tables written depend on ``--dataset``: ``rates``, ``ticks``,
758
- ``history_orders``, ``history_deals``. History datasets are fetched per
759
- symbol and concatenated. Rates rows carry the requested ``timeframe`` so
760
- 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.
761
775
 
762
776
  With ``--with-views`` (requires the ``history-deals`` dataset), optional
763
777
  views ``cash_events`` and ``positions_reconstructed`` are derived from
@@ -773,7 +787,7 @@ def collect_history(
773
787
  " Use a .db/.sqlite/.sqlite3 extension or --format sqlite3."
774
788
  )
775
789
  raise typer.BadParameter(msg)
776
- datasets = set(dataset) if dataset else set(Dataset)
790
+ datasets = set(dataset) if dataset is not None else None
777
791
  sdk.collect_history(
778
792
  output=export_ctx.output,
779
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()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mt5cli"
3
- version = "1.0.0"
3
+ version = "1.0.2"
4
4
  description = "Generic MT5 data and execution infrastructure for Python applications"
5
5
  authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
6
6
  maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
@@ -740,6 +740,66 @@ class TestCommands:
740
740
  assert "must be a JSON object" in normalize_cli_output(result.output)
741
741
 
742
742
 
743
+ # ---------------------------------------------------------------------------
744
+ # Help text / scope tests
745
+ # ---------------------------------------------------------------------------
746
+
747
+
748
+ class TestHelpText:
749
+ """Tests verifying CLI help text matches the documented scope."""
750
+
751
+ def test_top_level_help_mentions_execution(self) -> None:
752
+ """Top-level help must describe execution utilities, not export only."""
753
+ result = runner.invoke(app, ["--help"])
754
+ assert result.exit_code == 0
755
+ output = normalize_cli_output(result.output)
756
+ assert "execution" in output.lower()
757
+
758
+ def test_top_level_help_has_execution_panel(self) -> None:
759
+ """Top-level help must show an Execution command group."""
760
+ result = runner.invoke(app, ["--help"])
761
+ assert result.exit_code == 0
762
+ assert "Execution" in result.output
763
+
764
+ def test_top_level_help_has_data_export_panel(self) -> None:
765
+ """Top-level help must show a Data / Export command group."""
766
+ result = runner.invoke(app, ["--help"])
767
+ assert result.exit_code == 0
768
+ assert "Data / Export" in result.output
769
+
770
+ def test_order_send_help_mentions_expert_and_raw(self) -> None:
771
+ """order-send help must communicate it is the expert raw-request path."""
772
+ result2 = runner.invoke(
773
+ app,
774
+ ["-o", "out.csv", "order-send", "--help"],
775
+ )
776
+ assert result2.exit_code == 0
777
+ output = normalize_cli_output(result2.output)
778
+ assert "raw" in output.lower()
779
+ assert "expert" in output.lower()
780
+
781
+ def test_order_send_help_mentions_live_execution(self) -> None:
782
+ """order-send help must warn about live execution."""
783
+ result = runner.invoke(
784
+ app,
785
+ ["-o", "out.csv", "order-send", "--help"],
786
+ )
787
+ assert result.exit_code == 0
788
+ output = normalize_cli_output(result.output)
789
+ assert "live" in output.lower()
790
+
791
+ def test_close_positions_help_mentions_dry_run_and_yes(self) -> None:
792
+ """close-positions help must document both safety gates."""
793
+ result = runner.invoke(
794
+ app,
795
+ ["-o", "out.csv", "close-positions", "--help"],
796
+ )
797
+ assert result.exit_code == 0
798
+ output = normalize_cli_output(result.output)
799
+ assert "--dry-run" in output
800
+ assert "--yes" in output
801
+
802
+
743
803
  # ---------------------------------------------------------------------------
744
804
  # close-positions command
745
805
  # ---------------------------------------------------------------------------
@@ -1238,12 +1298,12 @@ class TestCollectHistory:
1238
1298
  """Create a mocked Mt5DataClient with history-style DataFrames."""
1239
1299
  return _build_history_client(mocker)
1240
1300
 
1241
- def test_collect_history_writes_all_tables(
1301
+ def test_collect_history_writes_default_tables(
1242
1302
  self,
1243
1303
  tmp_path: Path,
1244
1304
  history_client: MagicMock,
1245
1305
  ) -> None:
1246
- """Test that collect-history writes rates, ticks, and history tables."""
1306
+ """Test that collect-history default excludes ticks."""
1247
1307
  output = tmp_path / "history.db"
1248
1308
  result = runner.invoke(
1249
1309
  app,
@@ -1263,8 +1323,42 @@ class TestCollectHistory:
1263
1323
  )
1264
1324
  assert result.exit_code == 0, result.output
1265
1325
  assert history_client.copy_rates_range_as_df.call_count == 2
1266
- assert history_client.copy_ticks_range_as_df.call_count == 2
1267
- 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(
1268
1362
  symbol="EURUSD",
1269
1363
  date_from=datetime(2024, 1, 1, tzinfo=UTC),
1270
1364
  date_to=datetime(2024, 2, 1, tzinfo=UTC),
@@ -1277,7 +1371,8 @@ class TestCollectHistory:
1277
1371
  "SELECT name FROM sqlite_master WHERE type='table'",
1278
1372
  ).fetchall()
1279
1373
  }
1280
- assert {"rates", "ticks", "history_orders", "history_deals"} <= tables
1374
+ assert "ticks" in tables
1375
+ assert "rates" not in tables
1281
1376
 
1282
1377
  def test_collect_history_history_fetched_per_symbol(
1283
1378
  self,
@@ -1460,7 +1555,7 @@ class TestCollectHistory:
1460
1555
  tmp_path: Path,
1461
1556
  history_client: MagicMock,
1462
1557
  ) -> None:
1463
- """Test that --flags defaults to ALL for ticks."""
1558
+ """Test that --flags defaults to ALL when --dataset ticks is explicit."""
1464
1559
  output = tmp_path / "history.db"
1465
1560
  result = runner.invoke(
1466
1561
  app,
@@ -1474,6 +1569,8 @@ class TestCollectHistory:
1474
1569
  "2024-01-01",
1475
1570
  "--date-to",
1476
1571
  "2024-02-01",
1572
+ "--dataset",
1573
+ "ticks",
1477
1574
  ],
1478
1575
  )
1479
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."""
@@ -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.0"
490
+ version = "1.0.2"
491
491
  source = { editable = "." }
492
492
  dependencies = [
493
493
  { name = "click" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes