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.
- {mt5cli-1.0.0 → mt5cli-1.0.2}/PKG-INFO +3 -3
- {mt5cli-1.0.0 → mt5cli-1.0.2}/README.md +2 -2
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/index.md +38 -26
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/cli.py +50 -36
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/history.py +9 -3
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/sdk.py +7 -4
- {mt5cli-1.0.0 → mt5cli-1.0.2}/pyproject.toml +1 -1
- {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_cli.py +103 -6
- {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_history.py +16 -2
- {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_sdk.py +64 -3
- {mt5cli-1.0.0 → mt5cli-1.0.2}/uv.lock +2 -2
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/mt5cli/SKILL.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.claude/agents/codex.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.claude/settings.json +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/FUNDING.yml +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/dependabot.yml +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/renovate.json +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/workflows/ci.yml +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/workflows/claude.yml +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.github/workflows/release.yml +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/.gitignore +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/AGENTS.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/CLAUDE.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/LICENSE +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/cli.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/client.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/converters.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/exceptions.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/history.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/index.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/public-contract.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/schemas.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/sdk.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/trading.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/docs/api/utils.md +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mkdocs.yml +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/__init__.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/__main__.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/client.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/contract.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/converters.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/exceptions.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/retry.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/schemas.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/trading.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/mt5cli/utils.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/__init__.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/conftest.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_contracts.py +0 -0
- {mt5cli-1.0.0 → mt5cli-1.0.2}/tests/test_trading.py +0 -0
- {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.
|
|
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` |
|
|
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:
|
|
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` |
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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
|
|
182
|
-
| -------------- |
|
|
183
|
-
| `--symbol/-s` | _required_
|
|
184
|
-
| `--date-from` | _required_
|
|
185
|
-
| `--date-to` | _required_
|
|
186
|
-
| `--dataset` |
|
|
187
|
-
| `--timeframe` | `M1`
|
|
188
|
-
| `--flags` | `ALL`
|
|
189
|
-
| `--if-exists` | `fail`
|
|
190
|
-
| `--with-views` | off
|
|
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
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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``, ``
|
|
758
|
-
``
|
|
759
|
-
|
|
760
|
-
|
|
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
|
|
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
|
-
|
|
66
|
-
configured selection (which
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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 ==
|
|
1267
|
-
|
|
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
|
|
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
|
|
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
|
|
551
|
-
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|