mt5cli 1.6.0__tar.gz → 1.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {mt5cli-1.6.0 → mt5cli-1.7.0}/.github/workflows/claude.yml +1 -1
  2. {mt5cli-1.6.0 → mt5cli-1.7.0}/PKG-INFO +9 -60
  3. {mt5cli-1.6.0 → mt5cli-1.7.0}/README.md +8 -59
  4. mt5cli-1.7.0/docs/api/analytics.md +99 -0
  5. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/history.md +36 -10
  6. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/index.md +1 -3
  7. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/observability.md +7 -10
  8. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/public-contract.md +30 -56
  9. {mt5cli-1.6.0 → mt5cli-1.7.0}/mkdocs.yml +1 -1
  10. mt5cli-1.7.0/mt5cli/analytics.py +399 -0
  11. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/cli.py +31 -51
  12. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/history.py +160 -14
  13. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/observability.py +245 -34
  14. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/sdk.py +1 -1
  15. {mt5cli-1.6.0 → mt5cli-1.7.0}/pyproject.toml +2 -2
  16. mt5cli-1.7.0/tests/architecture/__init__.py +1 -0
  17. mt5cli-1.7.0/tests/architecture/test_unit_module_correspondence.py +37 -0
  18. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/cli/test_cli.bats +2 -19
  19. mt5cli-1.7.0/tests/conftest.py +47 -0
  20. mt5cli-1.7.0/tests/contracts/__init__.py +1 -0
  21. mt5cli-1.7.0/tests/contracts/test_public_api.py +163 -0
  22. mt5cli-1.7.0/tests/docs/__init__.py +1 -0
  23. mt5cli-1.7.0/tests/docs/test_public_contract.py +21 -0
  24. mt5cli-1.7.0/tests/integration/__init__.py +1 -0
  25. mt5cli-1.6.0/tests/unit/test_packaging.py → mt5cli-1.7.0/tests/packaging/test_metadata.py +1 -0
  26. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/conftest.py +2 -38
  27. mt5cli-1.7.0/tests/unit/test_analytics.py +1016 -0
  28. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_cli.py +86 -85
  29. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_client.py +59 -0
  30. mt5cli-1.7.0/tests/unit/test_contract.py +13 -0
  31. mt5cli-1.7.0/tests/unit/test_converters.py +153 -0
  32. mt5cli-1.7.0/tests/unit/test_exceptions.py +56 -0
  33. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_history.py +641 -0
  34. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_observability.py +256 -25
  35. mt5cli-1.7.0/tests/unit/test_rates.py +885 -0
  36. mt5cli-1.7.0/tests/unit/test_retry.py +42 -0
  37. mt5cli-1.7.0/tests/unit/test_schemas.py +327 -0
  38. mt5cli-1.7.0/tests/unit/test_sdk.py +23 -0
  39. {mt5cli-1.6.0 → mt5cli-1.7.0}/uv.lock +7 -7
  40. mt5cli-1.6.0/docs/api/grafana.md +0 -25
  41. mt5cli-1.6.0/examples/grafana/README.md +0 -95
  42. mt5cli-1.6.0/examples/grafana/compose.yml +0 -26
  43. mt5cli-1.6.0/examples/grafana/dashboards/mt5cli-market.json +0 -98
  44. mt5cli-1.6.0/examples/grafana/dashboards/mt5cli-overview.json +0 -254
  45. mt5cli-1.6.0/examples/grafana/dashboards/mt5cli-trades.json +0 -167
  46. mt5cli-1.6.0/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -13
  47. mt5cli-1.6.0/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -16
  48. mt5cli-1.6.0/mt5cli/grafana.py +0 -633
  49. mt5cli-1.6.0/tests/unit/test_contracts.py +0 -768
  50. mt5cli-1.6.0/tests/unit/test_examples.py +0 -88
  51. mt5cli-1.6.0/tests/unit/test_grafana.py +0 -930
  52. mt5cli-1.6.0/tests/unit/test_history_contract.py +0 -508
  53. mt5cli-1.6.0/tests/unit/test_history_edge_contract.py +0 -122
  54. mt5cli-1.6.0/tests/unit/test_rate_timestamp_contract.py +0 -291
  55. mt5cli-1.6.0/tests/unit/test_rates.py +0 -408
  56. mt5cli-1.6.0/tests/unit/test_rates_contract.py +0 -220
  57. mt5cli-1.6.0/tests/unit/test_stable_sdk_cleanup.py +0 -52
  58. {mt5cli-1.6.0 → mt5cli-1.7.0}/.agents/skills/local-qa/SKILL.md +0 -0
  59. {mt5cli-1.6.0 → mt5cli-1.7.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  60. {mt5cli-1.6.0 → mt5cli-1.7.0}/.agents/skills/mt5cli/SKILL.md +0 -0
  61. {mt5cli-1.6.0 → mt5cli-1.7.0}/.claude/agents/codex.md +0 -0
  62. {mt5cli-1.6.0 → mt5cli-1.7.0}/.claude/settings.json +0 -0
  63. {mt5cli-1.6.0 → mt5cli-1.7.0}/.github/CODEOWNERS +0 -0
  64. {mt5cli-1.6.0 → mt5cli-1.7.0}/.github/FUNDING.yml +0 -0
  65. {mt5cli-1.6.0 → mt5cli-1.7.0}/.github/dependabot.yml +0 -0
  66. {mt5cli-1.6.0 → mt5cli-1.7.0}/.github/renovate.json +0 -0
  67. {mt5cli-1.6.0 → mt5cli-1.7.0}/.github/workflows/ci.yml +0 -0
  68. {mt5cli-1.6.0 → mt5cli-1.7.0}/.github/workflows/release.yml +0 -0
  69. {mt5cli-1.6.0 → mt5cli-1.7.0}/.gitignore +0 -0
  70. {mt5cli-1.6.0 → mt5cli-1.7.0}/LICENSE +0 -0
  71. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/cli.md +0 -0
  72. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/client.md +0 -0
  73. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/converters.md +0 -0
  74. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/exceptions.md +0 -0
  75. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/marketdata.md +0 -0
  76. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/schemas.md +0 -0
  77. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/telemetry.md +0 -0
  78. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/trading.md +0 -0
  79. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/api/utils.md +0 -0
  80. {mt5cli-1.6.0 → mt5cli-1.7.0}/docs/index.md +0 -0
  81. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/__init__.py +0 -0
  82. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/__main__.py +0 -0
  83. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/client.py +0 -0
  84. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/contract.py +0 -0
  85. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/converters.py +0 -0
  86. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/exceptions.py +0 -0
  87. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/lifecycle.py +0 -0
  88. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/marketdata.py +0 -0
  89. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/rates.py +0 -0
  90. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/retry.py +0 -0
  91. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/schemas.py +0 -0
  92. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/telemetry.py +0 -0
  93. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/trading.py +0 -0
  94. {mt5cli-1.6.0 → mt5cli-1.7.0}/mt5cli/utils.py +0 -0
  95. /mt5cli-1.6.0/tests/unit/test_docs_contract.py → /mt5cli-1.7.0/tests/docs/test_rate_storage_contract.py +0 -0
  96. {mt5cli-1.6.0/tests/unit → mt5cli-1.7.0/tests/integration}/test_history_capture_persist.py +0 -0
  97. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/__init__.py +0 -0
  98. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_lifecycle.py +0 -0
  99. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_marketdata.py +0 -0
  100. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_telemetry.py +0 -0
  101. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_trading.py +0 -0
  102. {mt5cli-1.6.0 → mt5cli-1.7.0}/tests/unit/test_utils.py +0 -0
@@ -34,7 +34,7 @@ jobs:
34
34
  issues: write
35
35
  id-token: write
36
36
  actions: read
37
- uses: dceoy/gha-for-devops/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses]
37
+ uses: dceoy/pr-loop/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses]
38
38
  secrets:
39
39
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env]
40
40
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mt5cli
3
- Version: 1.6.0
3
+ Version: 1.7.0
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>
@@ -212,8 +212,8 @@ python -m mt5cli -o account.csv account-info
212
212
  | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available; optional `--filling-mode` / `--deviation` / `--comment` / `--magic`) |
213
213
  | `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`) |
214
214
  | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
215
- | `grafana-schema` | Create or refresh Grafana-ready views and indexes in an existing SQLite database (idempotent, no MT5 connection) |
216
- | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite for live Grafana dashboards |
215
+ | `publish-dashboard` | Publish Parquet analytics datasets and a manifest from a SQLite history database for static dashboards (`--sqlite3`; `-o` is the manifest path; no MT5) |
216
+ | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite |
217
217
 
218
218
  Use `order-check` to validate a request payload before running `order-send --yes`.
219
219
  `close-positions` is the safer high-level alternative that builds correct close
@@ -246,25 +246,20 @@ mt5cli -o history.db collect-history \
246
246
 
247
247
  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 `rates` table records the requested `timeframe` so appended runs at different timeframes remain distinguishable. The `positions_reconstructed` view aggregates trade deals by `position_id`, excludes positions without closing-side entries, and uses volume-weighted open/close prices; reversal deals (`DEAL_ENTRY_INOUT`) are reported via `volume_reversal` / `reversal_count` columns.
248
248
 
249
- ### Grafana-ready SQLite dashboards
249
+ ### Static dashboard publication
250
250
 
251
- mt5cli can prepare a SQLite database for use as a Grafana datasource (via the [SQLite plugin](https://grafana.com/grafana/plugins/frser-sqlite-datasource/) or similar). Most `grafana_*` views expose an integer epoch-second `time` column for use in Grafana time-series panels. Two views (`grafana_realized_pnl`, `grafana_trade_stats`) are static symbol-level summaries with no `time` column — use them in table or stat panels.
251
+ `mt5cli -o dist/data/manifest.json publish-dashboard --sqlite3 history.db` (or `mt5cli.publish_dashboard("history.db", "dist/data")`) writes `trades`, `daily_pnl`, `strategy_stats`, `equity`, and `account_snapshots` Parquet files plus a `manifest.json` from canonical `analytics_*` views, ready for static hosting and DuckDB-Wasm. It never connects to MT5 and requires the `parquet` extra. See `docs/api/analytics.md`.
252
252
 
253
- #### Prepare the schema (idempotent, no MT5 connection needed)
253
+ ### Account snapshots (SQLite)
254
254
 
255
- ```bash
256
- mt5cli -o history.db grafana-schema
257
- ```
258
-
259
- This creates snapshot tables (`account_snapshots`, `position_snapshots`, `order_snapshots`, `terminal_snapshots`, `snapshot_runs`) and all `grafana_*` views and indexes in the SQLite database. Safe to run repeatedly — all operations are idempotent.
255
+ mt5cli can append timestamped account, position, order, and terminal snapshots to a SQLite database. Snapshot tables (`account_snapshots`, `position_snapshots`, `order_snapshots`, `terminal_snapshots`, `snapshot_runs`) are created automatically and idempotently.
260
256
 
261
257
  #### Snapshot current account state
262
258
 
263
259
  ```bash
264
260
  mt5cli -o history.db snapshot \
265
261
  --symbol JP225 --symbol HK50 --symbol NL25 \
266
- --with-account --with-positions --with-orders --with-terminal \
267
- --with-grafana-schema
262
+ --with-account --with-positions --with-orders --with-terminal
268
263
  ```
269
264
 
270
265
  Appends one timestamped row per data type. Never places orders or modifies trading state. Run periodically (e.g. from a cron job or a loop) to build a time-series account history.
@@ -284,56 +279,10 @@ update_observability_with_config(
284
279
  include_positions=True,
285
280
  include_orders=True,
286
281
  include_terminal=True,
287
- with_grafana_schema=True,
288
282
  )
289
283
  ```
290
284
 
291
- #### Available Grafana views
292
-
293
- **Time-series views** (integer epoch-second `time` column; snapshot views also expose `run_id`):
294
-
295
- | View | Source | Description |
296
- | ---------------------------- | -------------------- | ---------------------------------------------------------- |
297
- | `grafana_rates` | `rates` | OHLCV bars with integer epoch `time` |
298
- | `grafana_ticks` | `ticks` | Tick data with integer epoch `time` |
299
- | `grafana_history_deals` | `history_deals` | All deals with epoch `time` |
300
- | `grafana_history_orders` | `history_orders` | All historical orders; adds epoch `time` from `time_setup` |
301
- | `grafana_trade_deals` | `history_deals` | Trade deals only (`type IN (0,1)`) |
302
- | `grafana_cash_events` | `history_deals` | Non-trade deals (deposits, dividends, etc.) |
303
- | `grafana_symbol_pnl` | `history_deals` | Per-close-deal profit/loss per symbol |
304
- | `grafana_account_snapshots` | `account_snapshots` | Account balance/equity/margin time series |
305
- | `grafana_position_snapshots` | `position_snapshots` | Open position snapshots over time |
306
- | `grafana_order_snapshots` | `order_snapshots` | Active order snapshots over time |
307
- | `grafana_terminal_snapshots` | `terminal_snapshots` | Terminal connectivity snapshots |
308
-
309
- **Static summary views** (no `time` column; use in table or stat panels, not time-series):
310
-
311
- | View | Source | Description |
312
- | ---------------------- | --------------- | ------------------------------------- |
313
- | `grafana_realized_pnl` | `history_deals` | Cumulative realized PnL per symbol |
314
- | `grafana_trade_stats` | `history_deals` | Win/loss counts and profit per symbol |
315
-
316
- #### Example Grafana queries
317
-
318
- ```sql
319
- -- Equity curve over time
320
- SELECT time, equity FROM grafana_account_snapshots ORDER BY time;
321
-
322
- -- Rolling balance by account login
323
- SELECT time, login, balance FROM grafana_account_snapshots
324
- WHERE login = $login ORDER BY time;
325
-
326
- -- Open positions at latest successful snapshot
327
- SELECT symbol, volume, profit FROM grafana_position_snapshots
328
- WHERE run_id = (SELECT MAX(run_id) FROM snapshot_runs WHERE status = 'ok');
329
-
330
- -- Realized PnL by symbol
331
- SELECT symbol, total_profit FROM grafana_trade_stats ORDER BY total_profit DESC;
332
- ```
333
-
334
- #### Grafana and telemetry API docs
335
-
336
- The shipped Grafana helpers are documented in [`docs/api/grafana.md`](docs/api/grafana.md), including `publish_grafana_copy()` for creating a WAL-safe published SQLite copy for Grafana.
285
+ #### Telemetry API docs
337
286
 
338
287
  OpenTelemetry metrics are documented in [`docs/api/telemetry.md`](docs/api/telemetry.md), including `enable_otel_metrics()`, `configure_metrics()`, the `mt5cli[otel]` extra, and `OTEL_EXPORTER_OTLP_ENDPOINT`.
339
288
 
@@ -183,8 +183,8 @@ python -m mt5cli -o account.csv account-info
183
183
  | `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` available; optional `--filling-mode` / `--deviation` / `--comment` / `--magic`) |
184
184
  | `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`) |
185
185
  | `history-gaps` | Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5 |
186
- | `grafana-schema` | Create or refresh Grafana-ready views and indexes in an existing SQLite database (idempotent, no MT5 connection) |
187
- | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite for live Grafana dashboards |
186
+ | `publish-dashboard` | Publish Parquet analytics datasets and a manifest from a SQLite history database for static dashboards (`--sqlite3`; `-o` is the manifest path; no MT5) |
187
+ | `snapshot` | Snapshot current account, position, order, and terminal state into SQLite |
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
@@ -217,25 +217,20 @@ mt5cli -o history.db collect-history \
217
217
 
218
218
  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 `rates` table records the requested `timeframe` so appended runs at different timeframes remain distinguishable. The `positions_reconstructed` view aggregates trade deals by `position_id`, excludes positions without closing-side entries, and uses volume-weighted open/close prices; reversal deals (`DEAL_ENTRY_INOUT`) are reported via `volume_reversal` / `reversal_count` columns.
219
219
 
220
- ### Grafana-ready SQLite dashboards
220
+ ### Static dashboard publication
221
221
 
222
- mt5cli can prepare a SQLite database for use as a Grafana datasource (via the [SQLite plugin](https://grafana.com/grafana/plugins/frser-sqlite-datasource/) or similar). Most `grafana_*` views expose an integer epoch-second `time` column for use in Grafana time-series panels. Two views (`grafana_realized_pnl`, `grafana_trade_stats`) are static symbol-level summaries with no `time` column — use them in table or stat panels.
222
+ `mt5cli -o dist/data/manifest.json publish-dashboard --sqlite3 history.db` (or `mt5cli.publish_dashboard("history.db", "dist/data")`) writes `trades`, `daily_pnl`, `strategy_stats`, `equity`, and `account_snapshots` Parquet files plus a `manifest.json` from canonical `analytics_*` views, ready for static hosting and DuckDB-Wasm. It never connects to MT5 and requires the `parquet` extra. See `docs/api/analytics.md`.
223
223
 
224
- #### Prepare the schema (idempotent, no MT5 connection needed)
224
+ ### Account snapshots (SQLite)
225
225
 
226
- ```bash
227
- mt5cli -o history.db grafana-schema
228
- ```
229
-
230
- This creates snapshot tables (`account_snapshots`, `position_snapshots`, `order_snapshots`, `terminal_snapshots`, `snapshot_runs`) and all `grafana_*` views and indexes in the SQLite database. Safe to run repeatedly — all operations are idempotent.
226
+ mt5cli can append timestamped account, position, order, and terminal snapshots to a SQLite database. Snapshot tables (`account_snapshots`, `position_snapshots`, `order_snapshots`, `terminal_snapshots`, `snapshot_runs`) are created automatically and idempotently.
231
227
 
232
228
  #### Snapshot current account state
233
229
 
234
230
  ```bash
235
231
  mt5cli -o history.db snapshot \
236
232
  --symbol JP225 --symbol HK50 --symbol NL25 \
237
- --with-account --with-positions --with-orders --with-terminal \
238
- --with-grafana-schema
233
+ --with-account --with-positions --with-orders --with-terminal
239
234
  ```
240
235
 
241
236
  Appends one timestamped row per data type. Never places orders or modifies trading state. Run periodically (e.g. from a cron job or a loop) to build a time-series account history.
@@ -255,56 +250,10 @@ update_observability_with_config(
255
250
  include_positions=True,
256
251
  include_orders=True,
257
252
  include_terminal=True,
258
- with_grafana_schema=True,
259
253
  )
260
254
  ```
261
255
 
262
- #### Available Grafana views
263
-
264
- **Time-series views** (integer epoch-second `time` column; snapshot views also expose `run_id`):
265
-
266
- | View | Source | Description |
267
- | ---------------------------- | -------------------- | ---------------------------------------------------------- |
268
- | `grafana_rates` | `rates` | OHLCV bars with integer epoch `time` |
269
- | `grafana_ticks` | `ticks` | Tick data with integer epoch `time` |
270
- | `grafana_history_deals` | `history_deals` | All deals with epoch `time` |
271
- | `grafana_history_orders` | `history_orders` | All historical orders; adds epoch `time` from `time_setup` |
272
- | `grafana_trade_deals` | `history_deals` | Trade deals only (`type IN (0,1)`) |
273
- | `grafana_cash_events` | `history_deals` | Non-trade deals (deposits, dividends, etc.) |
274
- | `grafana_symbol_pnl` | `history_deals` | Per-close-deal profit/loss per symbol |
275
- | `grafana_account_snapshots` | `account_snapshots` | Account balance/equity/margin time series |
276
- | `grafana_position_snapshots` | `position_snapshots` | Open position snapshots over time |
277
- | `grafana_order_snapshots` | `order_snapshots` | Active order snapshots over time |
278
- | `grafana_terminal_snapshots` | `terminal_snapshots` | Terminal connectivity snapshots |
279
-
280
- **Static summary views** (no `time` column; use in table or stat panels, not time-series):
281
-
282
- | View | Source | Description |
283
- | ---------------------- | --------------- | ------------------------------------- |
284
- | `grafana_realized_pnl` | `history_deals` | Cumulative realized PnL per symbol |
285
- | `grafana_trade_stats` | `history_deals` | Win/loss counts and profit per symbol |
286
-
287
- #### Example Grafana queries
288
-
289
- ```sql
290
- -- Equity curve over time
291
- SELECT time, equity FROM grafana_account_snapshots ORDER BY time;
292
-
293
- -- Rolling balance by account login
294
- SELECT time, login, balance FROM grafana_account_snapshots
295
- WHERE login = $login ORDER BY time;
296
-
297
- -- Open positions at latest successful snapshot
298
- SELECT symbol, volume, profit FROM grafana_position_snapshots
299
- WHERE run_id = (SELECT MAX(run_id) FROM snapshot_runs WHERE status = 'ok');
300
-
301
- -- Realized PnL by symbol
302
- SELECT symbol, total_profit FROM grafana_trade_stats ORDER BY total_profit DESC;
303
- ```
304
-
305
- #### Grafana and telemetry API docs
306
-
307
- The shipped Grafana helpers are documented in [`docs/api/grafana.md`](docs/api/grafana.md), including `publish_grafana_copy()` for creating a WAL-safe published SQLite copy for Grafana.
256
+ #### Telemetry API docs
308
257
 
309
258
  OpenTelemetry metrics are documented in [`docs/api/telemetry.md`](docs/api/telemetry.md), including `enable_otel_metrics()`, `configure_metrics()`, the `mt5cli[otel]` extra, and `OTEL_EXPORTER_OTLP_ENDPOINT`.
310
259
 
@@ -0,0 +1,99 @@
1
+ # Analytics
2
+
3
+ ::: mt5cli.analytics
4
+
5
+ ## Static dashboard workflow
6
+
7
+ mt5cli keeps SQLite as the canonical local store and publishes browser-facing
8
+ Parquet datasets from it, so a static site (for example DuckDB-Wasm) can query
9
+ the data without a live API or direct SQLite access. Visualization stays
10
+ outside mt5cli.
11
+
12
+ ```bash
13
+ mt5cli -o dist/data/manifest.json publish-dashboard --sqlite3 history.db
14
+ ```
15
+
16
+ ```python
17
+ from mt5cli import publish_dashboard
18
+
19
+ manifest = publish_dashboard("history.db", "dist/data")
20
+ ```
21
+
22
+ Publication never connects to MetaTrader 5 and never modifies the source
23
+ database (analytics views are created as connection-local TEMP views). It
24
+ requires the `parquet` extra (`pip install "mt5cli[parquet]"`).
25
+
26
+ | File | Source view |
27
+ | --------------------------- | ----------------------------------------------------------------------------------------------------- |
28
+ | `trades.parquet` | `analytics_trades` |
29
+ | `daily_pnl.parquet` | `analytics_daily_pnl` |
30
+ | `strategy_stats.parquet` | `analytics_strategy_stats` |
31
+ | `equity.parquet` | `analytics_equity` |
32
+ | `account_snapshots.parquet` | `account_snapshots` joined to successful runs |
33
+ | `manifest.json` | `schema_version`, `generated_at`, `mt5cli_version`, and per dataset `name`, `file`, `rows`, `columns` |
34
+
35
+ Datasets whose source tables are missing are skipped and omitted from the
36
+ manifest. The manifest is written last. `manifest_name` must be a plain file name (no `/`, `\\`, drive prefix or `..`) that is not the name of a dataset file; otherwise `ValueError` is raised before anything is written (the CLI reports it as a usage error).
37
+
38
+ ## Analytics views
39
+
40
+ `create_analytics_views(conn)` creates the views idempotently in an existing
41
+ history database (`temporary=True` creates TEMP views for read-only databases).
42
+
43
+ - `analytics_trades`: one row per completed trade leg (built on the history layer's `deal_portions` view, the same source as `positions_reconstructed`). A leg is the stretch of
44
+ a `position_id` between reversals: a `DEAL_ENTRY_INOUT` deal closes the
45
+ current leg and opens the next one (`leg_index`). Its volume, commission and
46
+ fee are split pro rata between the two legs; its swap goes wholly to the
47
+ closing leg (MT5 reports `DEAL_SWAP` for the position being closed), and
48
+ profit is realized on the closed part only. A leg is complete once its closing volume covers its opening
49
+ volume (1e-9 lot tolerance), so partially closed legs and the still-open
50
+ leg after a reversal are excluded. For positions without reversals, leg 0
51
+ matches `positions_reconstructed`. Columns: `position_id`, `symbol`,
52
+ `leg_index`, `magic`, `magic_count`, `side`, `open_time`, `close_time`, `close_date`,
53
+ `holding_seconds`, `volume`, `entry_price`, `exit_price` (volume-weighted),
54
+ `reversal_count`, `deals_count`, `profit`, `commission`, `swap`, `fee`,
55
+ `net_profit`. Numeric timestamps stay numeric epoch values; textual
56
+ timestamps remain timezone-naive MT5 trade-server wall-clock values. No
57
+ implicit UTC conversion is performed.
58
+ - `analytics_realized_events`: every deal portion, each at its own timestamp
59
+ (cash basis: an entry's commission lands on the entry date and a partial
60
+ close's profit on the day it is realized), including partially closed and
61
+ still-open legs. Events of a completed leg sum to that leg's `net_profit`,
62
+ so cumulative equity also reflects entry costs and partial-close profit of
63
+ legs that have not completed yet.
64
+ Each event carries its own deal's `magic`. A leg needs a visible opening
65
+ volume to count as a trade, and its position must start inside the captured
66
+ history (its first visible deal is an entry-in, since the reversal split cannot
67
+ be reconstructed otherwise), so positions opened before the captured history
68
+ appear only in the events, daily P/L and equity. This check is best-effort: a
69
+ scale-in cut by the capture window looks like a position start and cannot be told
70
+ apart from one using the deals alone, so collect history from before the oldest
71
+ open position when exact trade legs matter.
72
+ - `analytics_daily_pnl`: per `date`, `symbol`, `magic` (the deal-level magic)
73
+ from the realized events: `event_count`, `profit`, `commission`, `swap`, `fee`, `net_profit`.
74
+ - `analytics_strategy_stats`: per `symbol`, `magic` from the trade legs (trade
75
+ count, wins, losses, win rate, gross profit/loss, profit factor), plus
76
+ `avg_holding_seconds`, `first_open_time`, `last_close_time`. A leg's `magic` is
77
+ its entry magic; legs whose entry deals mix magics have a NULL `magic` with
78
+ `magic_count > 1`, so for such legs daily P/L by magic and the strategy stats
79
+ by magic can differ.
80
+ - `analytics_equity`: one row per realized event in time order with
81
+ `cumulative_net_profit` (realized P&L only).
82
+
83
+ Net P/L is:
84
+
85
+ ```text
86
+ net_profit = COALESCE(profit, 0) + COALESCE(commission, 0)
87
+ + COALESCE(swap, 0) + COALESCE(fee, 0)
88
+ ```
89
+
90
+ Missing optional deal columns are treated as zero (or NULL for `magic`). A trade
91
+ is a win when `net_profit > 0` and a loss when `net_profit < 0`; break-even
92
+ trades count as neither. `gross_profit` and `gross_loss` sum positive and
93
+ negative trade `net_profit`, and `profit_factor = gross_profit / ABS(gross_loss)`
94
+ is NULL without losses. Metrics needing unavailable state (such as MAE/MFE) are
95
+ out of scope.
96
+
97
+ Parquet columns with a fixed meaning are written with stable types (`Int64`,
98
+ `float64`, `string`) even for empty tables; time columns keep the stored
99
+ representation.
@@ -123,6 +123,20 @@ erDiagram
123
123
  REAL profit
124
124
  }
125
125
 
126
+ deal_portions {
127
+ INTEGER ticket
128
+ INTEGER position_id
129
+ TEXT symbol
130
+ INTEGER leg
131
+ TEXT role
132
+ INTEGER position_start_known
133
+ REAL volume
134
+ REAL profit
135
+ REAL commission
136
+ REAL swap
137
+ REAL fee
138
+ }
139
+
126
140
  positions_reconstructed {
127
141
  INTEGER position_id
128
142
  TEXT symbol
@@ -144,20 +158,32 @@ erDiagram
144
158
  history_orders ||--o{ history_deals : "order ~ ticket (logical)"
145
159
  symbols ||--o{ rates : "symbol (logical)"
146
160
  history_deals ||--|| cash_events : "VIEW: type NOT IN (0,1)"
147
- history_deals ||--o{ positions_reconstructed : "VIEW: GROUP BY position_id"
161
+ history_deals ||--o{ deal_portions : "VIEW: deals split at reversals"
162
+ deal_portions ||--o{ positions_reconstructed : "VIEW: GROUP BY position_id"
148
163
  ```
149
164
 
150
165
  ### Tables and views
151
166
 
152
- | Object | Kind | Source | Notes |
153
- | ------------------------- | ----- | -------------------- | ------------------------------------------------------------------------------------------- |
154
- | `rates` | table | `copy_rates_range` | Indexed on `(symbol, timeframe, time)` when columns exist. |
155
- | `ticks` | table | `copy_ticks_range` | Indexed on `(symbol, time)` when columns exist. |
156
- | `history_orders` | table | `history_orders_get` | Fetched per `--symbol`, then concatenated. |
157
- | `history_deals` | table | `history_deals_get` | Fetched per `--symbol`, then concatenated. Indexed on `(position_id, symbol)` when present. |
158
- | `symbols` | table | `symbol_info` | Opt-in. One row per symbol per collection/update, snapshotted at `date_to` / update end. |
159
- | `cash_events` | view | `history_deals` | Non-trade deal types (deposits, balance ops, etc.). Requires `type` column. |
160
- | `positions_reconstructed` | view | `history_deals` | One row per closed `position_id`; volume-weighted prices and reversal stats. |
167
+ | Object | Kind | Source | Notes |
168
+ | ------------------------- | ----- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | `rates` | table | `copy_rates_range` | Indexed on `(symbol, timeframe, time)` when columns exist. |
170
+ | `ticks` | table | `copy_ticks_range` | Indexed on `(symbol, time)` when columns exist. |
171
+ | `history_orders` | table | `history_orders_get` | Fetched per `--symbol`, then concatenated. |
172
+ | `history_deals` | table | `history_deals_get` | Fetched per `--symbol`, then concatenated. Indexed on `(position_id, symbol)` when present. |
173
+ | `symbols` | table | `symbol_info` | Opt-in. One row per symbol per collection/update, snapshotted at `date_to` / update end. |
174
+ | `cash_events` | view | `history_deals` | Non-trade deal types (deposits, balance ops, etc.). Requires `type` column. |
175
+ | `deal_portions` | view | `history_deals` | Canonical leg-aware layer: trade deals, with each reversal (`DEAL_ENTRY_INOUT`) split into a closing and an opening portion (`leg`, `role`). |
176
+ | `positions_reconstructed` | view | `deal_portions` | One row per closed `position_id`; volume-weighted prices and reversal stats (reversal volume counted whole). |
177
+
178
+ The reversal split in `deal_portions` uses the balance of the visible deals, so it
179
+ is reliable only for positions whose first visible deal is an entry-in
180
+ (`position_start_known = 1`). When the captured history starts mid-position
181
+ (`position_start_known = 0`), the `leg` numbering and the closing/opening split of a
182
+ reversal are approximate, though every deal's cash totals are preserved and
183
+ `positions_reconstructed` is unaffected. The flag is best-effort: a scale-in is also
184
+ an entry-in deal, so if the capture window cuts a position and a later scale-in is
185
+ the first visible deal, the missing earlier exposure cannot be detected from the
186
+ deals alone.
161
187
 
162
188
  Column sets can vary with terminal and pdmt5 version. Views are skipped with a warning
163
189
  when required columns are missing.
@@ -18,9 +18,8 @@ responsibilities.
18
18
  | [Exceptions](exceptions.md) | Stable mt5cli exception types and MT5 error normalization |
19
19
  | [Trading](trading.md) | Trading-capable sessions and operational helpers |
20
20
  | [History Collection (SQLite)](history.md) | SQLite schema, incremental writes, dedup, and rate views |
21
- | [Observability](observability.md) | Account/position/order/terminal snapshot orchestration |
21
+ | [Observability](observability.md) | Account/position/order/terminal snapshot orchestration and SQLite snapshot tables |
22
22
  | [Telemetry](telemetry.md) | OpenTelemetry metrics setup, meters, and emitted metric names |
23
- | [Grafana](grafana.md) | Grafana-ready SQLite schema, views, snapshots, and published copies |
24
23
  | [CLI](cli.md) | Typer commands that delegate to the Python API |
25
24
  | [Utils](utils.md) | Generic parsing, conversion, and export helpers |
26
25
 
@@ -34,7 +33,6 @@ flowchart TD
34
33
  MarketData["marketdata"] --> Client
35
34
  History["history"] --> Client
36
35
  Observability["observability"] --> Client
37
- Observability --> Grafana["grafana"]
38
36
  History --> Utils["utils export"]
39
37
  ```
40
38
 
@@ -2,13 +2,11 @@
2
2
 
3
3
  ::: mt5cli.observability
4
4
 
5
- ## Grafana observability (SQLite read model)
5
+ ## Snapshot persistence (SQLite)
6
6
 
7
7
  These helpers append timestamped account/position/order/terminal snapshot
8
- rows into a SQLite database, preparing it as a Grafana datasource. All DDL is
9
- idempotent (`CREATE TABLE IF NOT EXISTS`, `DROP VIEW IF EXISTS` + `CREATE
10
- VIEW`, `CREATE INDEX IF NOT EXISTS`). Missing source tables are skipped with a
11
- warning rather than raising an error.
8
+ rows into a SQLite database. The snapshot tables are created on demand with
9
+ idempotent DDL (`CREATE TABLE IF NOT EXISTS`).
12
10
 
13
11
  | Symbol | Role |
14
12
  | ---------------------------------- | ------------------------------------------------------------------------------------------- |
@@ -17,9 +15,6 @@ warning rather than raising an error.
17
15
 
18
16
  Both functions write to the SQLite path given by `output=`. The optional
19
17
  `symbols` parameter filters `positions` / `orders` by symbol.
20
- `with_grafana_schema=False` (default) skips Grafana view/index setup; run
21
- `grafana-schema` once to set up the schema, then call `snapshot` repeatedly
22
- without this flag.
23
18
 
24
19
  Pass the `MT5Client` yielded by `mt5_session()` directly to
25
20
  `update_observability(client=...)`. This workflow calls the facade's canonical
@@ -34,5 +29,7 @@ with mt5_session() as client:
34
29
  update_observability(client=client, output="observability.db")
35
30
  ```
36
31
 
37
- Schema and persistence (table DDL, Grafana views, and row inserts) belong to
38
- [Grafana](grafana.md); this module owns _when_ and _what_ to snapshot.
32
+ The snapshot table DDL and row inserts (`create_snapshot_tables`,
33
+ `start_snapshot_run`, `insert_*_snapshot(s)`, `record_snapshot_run`) live in
34
+ this module alongside the orchestration that decides _when_ and _what_ to
35
+ snapshot.
@@ -25,7 +25,7 @@ in pdmt5 — the name changed, it was not simply moved.
25
25
  Downstream packages should import from the package root (`from mt5cli import
26
26
  ...`). The authoritative SDK declaration in `mt5cli.sdk` enumerates
27
27
  every package-root symbol. Helpers promoted for downstream use, including selected configuration and
28
- parsing utilities, history timeframe resolution, and Grafana schema setup, are
28
+ parsing utilities, history timeframe resolution, and dashboard publication, are
29
29
  part of that root contract. Other low-level
30
30
  schema, export, conversion, and implementation helpers remain available only
31
31
  from their owning modules.
@@ -43,8 +43,8 @@ operational capability still has exactly one owning module:
43
43
  | `mt5cli.marketdata` | Stateless one-off market-data reads and multi-account rate collection (`AccountSpec`, `collect_latest_rates_for_accounts`, etc.) |
44
44
  | `mt5cli.history` | Legacy history collection, incremental updates, and SQLite storage (`collect_history`, `update_history`, `write_*_dataset`) |
45
45
  | `mt5cli.rates` | Canonical rate persistence/loading and stable update wrappers (`load_rate_series_from_sqlite`, `update_history`, `ThrottledHistoryUpdater`) |
46
- | `mt5cli.observability` | Observability snapshot orchestration (`update_observability`, `update_observability_with_config`) |
47
- | `mt5cli.grafana` | Grafana schema, views, and snapshot persistence |
46
+ | `mt5cli.observability` | Observability snapshot orchestration and SQLite snapshot tables (`update_observability`, `update_observability_with_config`) |
47
+ | `mt5cli.analytics` | Canonical `analytics_*` views and static-dashboard Parquet publication (`publish_dashboard`) |
48
48
  | `mt5cli.trading` | Order preparation, broker-facing calculations, and normalized execution receipts |
49
49
  | `mt5cli.contract` | The internal `HistoryClient` / `ObservabilityClient` protocols |
50
50
  | `mt5cli.sdk` | Authoritative stable package-root export declaration; no separate connection lifecycle |
@@ -196,12 +196,11 @@ client type; `from mt5cli import MT5Client, mt5_session` remains the supported
196
196
  way to obtain a connected client. Downstream code should import stable names
197
197
  from the package root and capability-specific helpers from their owning modules.
198
198
 
199
- ### Grafana observability (SQLite read model)
199
+ ### Observability snapshots (SQLite)
200
200
 
201
- These helpers prepare a SQLite database as a Grafana datasource. All DDL is
202
- idempotent (`CREATE TABLE IF NOT EXISTS`, `DROP VIEW IF EXISTS` + `CREATE
203
- VIEW`, `CREATE INDEX IF NOT EXISTS`). Missing source tables are skipped with a
204
- warning rather than raising an error.
201
+ These helpers append timestamped account/position/order/terminal snapshot
202
+ rows to a SQLite database. The snapshot tables are created on demand with
203
+ idempotent DDL (`CREATE TABLE IF NOT EXISTS`).
205
204
 
206
205
  | Symbol | Role |
207
206
  | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
@@ -213,9 +212,7 @@ warning rather than raising an error.
213
212
 
214
213
  `update_observability` and `persist_observability_snapshot` write to the
215
214
  SQLite path given by `output=`. The optional `symbols` parameter filters
216
- `positions_get` / `orders_get` by symbol. `with_grafana_schema=False`
217
- (default) skips Grafana view/index setup; run `grafana-schema` once to set up
218
- the schema, then call `snapshot` repeatedly without this flag.
215
+ `positions_get` / `orders_get` by symbol.
219
216
 
220
217
  `update_observability(client=...)` is exactly
221
218
  `capture_observability_snapshot(client=...)` followed by
@@ -232,7 +229,7 @@ workflows call the facade's canonical data methods (`account_info`, `positions`,
232
229
  `orders`, `terminal_info`, `copy_rates_range`, `copy_ticks_range`,
233
230
  `history_orders`, and `history_deals`); callers never need a pdmt5 client.
234
231
 
235
- **Snapshot tables** (created by `create_snapshot_tables` in `mt5cli.grafana`):
232
+ **Snapshot tables** (created by `create_snapshot_tables` in `mt5cli.observability`):
236
233
 
237
234
  | Table | Content |
238
235
  | -------------------- | ----------------------------------------- |
@@ -242,35 +239,15 @@ workflows call the facade's canonical data methods (`account_info`, `positions`,
242
239
  | `terminal_snapshots` | Terminal connectivity and build info |
243
240
  | `snapshot_runs` | Per-run status (`ok` / `error`) timestamp |
244
241
 
245
- **Grafana time-series views** (integer epoch-second `time` column; snapshot views also expose `run_id`):
246
-
247
- | View | Source |
248
- | ---------------------------- | -------------------------------- |
249
- | `grafana_rates` | `rates` table |
250
- | `grafana_ticks` | `ticks` table |
251
- | `grafana_history_deals` | `history_deals` |
252
- | `grafana_history_orders` | `history_orders` |
253
- | `grafana_trade_deals` | `history_deals` trade types only |
254
- | `grafana_cash_events` | `history_deals` non-trade events |
255
- | `grafana_symbol_pnl` | Per-close-deal P&L per symbol |
256
- | `grafana_account_snapshots` | `account_snapshots` |
257
- | `grafana_position_snapshots` | `position_snapshots` |
258
- | `grafana_order_snapshots` | `order_snapshots` |
259
- | `grafana_terminal_snapshots` | `terminal_snapshots` |
260
-
261
- **Grafana static summary views** (no `time` column; use for table/stat panels, not time-series):
262
-
263
- | View | Source |
264
- | ---------------------- | ------------------------------------- |
265
- | `grafana_realized_pnl` | Cumulative realized PnL per symbol |
266
- | `grafana_trade_stats` | Win/loss counts and profit per symbol |
267
-
268
- `ensure_grafana_schema` is a stable package-root helper for downstream setup.
269
- Other lower-level helpers (`create_grafana_views`, `create_grafana_indexes`,
270
- `create_snapshot_tables`, `start_snapshot_run`,
242
+ `publish_dashboard(source, output_dir)` is a stable package-root helper that
243
+ publishes Parquet analytics datasets and `manifest.json` from an offline
244
+ SQLite history database without connecting to MT5 or modifying the source; see
245
+ [Analytics](analytics.md).
246
+
247
+ Lower-level snapshot helpers (`create_snapshot_tables`, `start_snapshot_run`,
271
248
  `insert_account_snapshot`, `insert_position_snapshots`, `insert_order_snapshots`,
272
249
  `insert_terminal_snapshot`, `record_snapshot_run`) are available directly from
273
- `mt5cli.grafana` and are not part of the package-root stable surface.
250
+ `mt5cli.observability` and are not part of the package-root stable surface.
274
251
 
275
252
  ### Errors
276
253
 
@@ -314,14 +291,14 @@ names.
314
291
  Lower-level helpers are available from their owning modules and are not part
315
292
  of the package-root stable surface. Import them directly when needed:
316
293
 
317
- | Module | Examples |
318
- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
319
- | `mt5cli.grafana` | `ensure_grafana_schema`, `create_grafana_views`, `create_grafana_indexes`, `create_snapshot_tables`, `start_snapshot_run`, `insert_account_snapshot`, `record_snapshot_run` |
320
- | `mt5cli.marketdata` | `copy_rates_from`, `copy_ticks_from`, `account_info`, `symbols`, `mt5_summary`, `latest_rates` |
321
- | `mt5cli.schemas` | `DataKind`, `normalize_dataframe`, `validate_schema`, `DEDUP_KEYS` |
322
- | `mt5cli.utils` | `Dataset`, `IfExists`, `detect_format`, `export_dataframe`, `export_dataframe_to_sqlite` |
323
- | `mt5cli.converters` | `normalize_symbol`, `ensure_utc`, `ensure_trade_server_time`, `parse_date_range`, `granularity_name` |
324
- | `mt5cli.exceptions` | `normalize_mt5_exception`, `call_with_normalized_errors`, `is_recoverable_mt5_error` |
294
+ | Module | Examples |
295
+ | ------------------- | ---------------------------------------------------------------------------------------------------- |
296
+ | `mt5cli.analytics` | `create_analytics_views` |
297
+ | `mt5cli.marketdata` | `copy_rates_from`, `copy_ticks_from`, `account_info`, `symbols`, `mt5_summary`, `latest_rates` |
298
+ | `mt5cli.schemas` | `DataKind`, `normalize_dataframe`, `validate_schema`, `DEDUP_KEYS` |
299
+ | `mt5cli.utils` | `Dataset`, `IfExists`, `detect_format`, `export_dataframe`, `export_dataframe_to_sqlite` |
300
+ | `mt5cli.converters` | `normalize_symbol`, `ensure_utc`, `ensure_trade_server_time`, `parse_date_range`, `granularity_name` |
301
+ | `mt5cli.exceptions` | `normalize_mt5_exception`, `call_with_normalized_errors`, `is_recoverable_mt5_error` |
325
302
 
326
303
  ## CLI commands
327
304
 
@@ -337,14 +314,10 @@ The Typer application in `mt5cli.cli` exposes file-export commands documented in
337
314
  - Delegate to the same Python APIs described here; they are not duplicated
338
315
  business logic.
339
316
 
340
- `grafana-schema` initializes Grafana views, indexes, and snapshot tables in the
341
- target SQLite database without connecting to MT5. It is idempotent and safe to
342
- run repeatedly.
343
-
344
317
  `snapshot` appends one timestamped row per enabled data type
345
318
  (`--with-account`, `--with-positions`, `--with-orders`, `--with-terminal`) and
346
- never places orders or modifies trading state. Both commands require
347
- `-o/--output` to point at a `.db` / SQLite file.
319
+ never places orders or modifies trading state. It requires `-o/--output` to
320
+ point at a `.db` / SQLite file.
348
321
 
349
322
  `order-send` is the expert raw-request path; it requires `--yes` and a fully
350
323
  constructed request payload. `close-positions` is the safer high-level helper
@@ -402,7 +375,8 @@ their own adapter layer.
402
375
 
403
376
  ## Contract verification
404
377
 
405
- `tests/unit/test_contracts.py` asserts that every name in `STABLE_SDK_EXPORTS` is
378
+ `tests/contracts/test_public_api.py` asserts that every name in `STABLE_SDK_EXPORTS` is
406
379
  importable from `mt5cli`, that all package-root exports are covered by the
407
- stable set, and documents key closed-bar, SQLite loading, account-resolution,
408
- and trading-session behaviors.
380
+ stable set, and that the package-root API exposes only the canonical public
381
+ surface. Module-specific unit tests cover closed-bar, SQLite loading,
382
+ account-resolution, and trading-session behaviors.
@@ -67,7 +67,7 @@ nav:
67
67
  - History Collection (SQLite): api/history.md
68
68
  - Observability: api/observability.md
69
69
  - Telemetry: api/telemetry.md
70
- - Grafana: api/grafana.md
70
+ - Analytics: api/analytics.md
71
71
  - Utils: api/utils.md
72
72
 
73
73
  markdown_extensions: