mt5cli 1.1.6__tar.gz → 1.2.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 (79) hide show
  1. {mt5cli-1.1.6 → mt5cli-1.2.0}/PKG-INFO +31 -71
  2. {mt5cli-1.1.6 → mt5cli-1.2.0}/README.md +30 -70
  3. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/history.md +58 -0
  4. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/index.md +22 -19
  5. mt5cli-1.1.6/docs/api/sdk.md → mt5cli-1.2.0/docs/api/marketdata.md +6 -67
  6. mt5cli-1.2.0/docs/api/observability.md +38 -0
  7. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/public-contract.md +104 -24
  8. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/trading.md +55 -113
  9. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/index.md +4 -3
  10. {mt5cli-1.1.6 → mt5cli-1.2.0}/mkdocs.yml +2 -1
  11. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/__init__.py +6 -15
  12. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/cli.py +146 -20
  13. mt5cli-1.2.0/mt5cli/client.py +1035 -0
  14. mt5cli-1.2.0/mt5cli/contract.py +169 -0
  15. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/exceptions.py +14 -7
  16. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/history.py +494 -28
  17. mt5cli-1.2.0/mt5cli/marketdata.py +818 -0
  18. mt5cli-1.2.0/mt5cli/observability.py +258 -0
  19. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/retry.py +3 -2
  20. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/trading.py +426 -499
  21. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/utils.py +5 -122
  22. {mt5cli-1.1.6 → mt5cli-1.2.0}/pyproject.toml +1 -1
  23. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/conftest.py +1 -1
  24. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_cli.py +165 -43
  25. mt5cli-1.2.0/tests/test_client.py +1430 -0
  26. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_contracts.py +172 -3
  27. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_history.py +1022 -30
  28. mt5cli-1.2.0/tests/test_marketdata.py +729 -0
  29. mt5cli-1.2.0/tests/test_observability.py +458 -0
  30. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_trading.py +490 -868
  31. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_utils.py +0 -99
  32. {mt5cli-1.1.6 → mt5cli-1.2.0}/uv.lock +2 -2
  33. mt5cli-1.1.6/mt5cli/client.py +0 -86
  34. mt5cli-1.1.6/mt5cli/contract.py +0 -75
  35. mt5cli-1.1.6/mt5cli/sdk.py +0 -2408
  36. mt5cli-1.1.6/tests/test_client.py +0 -44
  37. mt5cli-1.1.6/tests/test_sdk.py +0 -3386
  38. {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/local-qa/SKILL.md +0 -0
  39. {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  40. {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/mt5cli/SKILL.md +0 -0
  41. {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  42. {mt5cli-1.1.6 → mt5cli-1.2.0}/.claude/agents/codex.md +0 -0
  43. {mt5cli-1.1.6 → mt5cli-1.2.0}/.claude/settings.json +0 -0
  44. {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/FUNDING.yml +0 -0
  45. {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/dependabot.yml +0 -0
  46. {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/renovate.json +0 -0
  47. {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/ci.yml +0 -0
  48. {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/claude.yml +0 -0
  49. {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/opencode.yml +0 -0
  50. {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/release.yml +0 -0
  51. {mt5cli-1.1.6 → mt5cli-1.2.0}/.gitignore +0 -0
  52. {mt5cli-1.1.6 → mt5cli-1.2.0}/AGENTS.md +0 -0
  53. {mt5cli-1.1.6 → mt5cli-1.2.0}/CLAUDE.md +0 -0
  54. {mt5cli-1.1.6 → mt5cli-1.2.0}/LICENSE +0 -0
  55. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/cli.md +0 -0
  56. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/client.md +0 -0
  57. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/converters.md +0 -0
  58. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/exceptions.md +0 -0
  59. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/grafana.md +0 -0
  60. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/schemas.md +0 -0
  61. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/telemetry.md +0 -0
  62. {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/utils.md +0 -0
  63. {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/README.md +0 -0
  64. {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/compose.yml +0 -0
  65. {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/dashboards/mt5cli-market.json +0 -0
  66. {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
  67. {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
  68. {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
  69. {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
  70. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/__main__.py +0 -0
  71. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/converters.py +0 -0
  72. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/grafana.py +0 -0
  73. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/schemas.py +0 -0
  74. {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/telemetry.py +0 -0
  75. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/__init__.py +0 -0
  76. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_examples.py +0 -0
  77. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_grafana.py +0 -0
  78. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_packaging.py +0 -0
  79. {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_telemetry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 1.1.6
3
+ Version: 1.2.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>
@@ -134,28 +134,17 @@ unset. Pass `allow_whole_dollar_env=True` to expand `${ENV_VAR}` and bare
134
134
  from mt5cli import (
135
135
  build_config,
136
136
  calculate_spread_ratio,
137
- create_trading_client,
138
137
  get_account_snapshot,
139
- mt5_trading_session,
138
+ mt5_session,
140
139
  )
141
140
 
142
141
  # Login from environment — numeric string is coerced to int automatically
143
142
  config = build_config(login="$MT5_LOGIN", allow_whole_dollar_env=True)
144
143
 
145
- with mt5_trading_session(
146
- path=r"C:\Program Files\MetaTrader 5\terminal64.exe",
147
- login="12345",
148
- password="from-env-or-secret-store",
149
- server="Broker-Demo",
150
- ) as client:
144
+ with mt5_session(config) as client:
151
145
  account = get_account_snapshot(client)
152
146
  spread = calculate_spread_ratio(client, "EURUSD")
153
-
154
- client = create_trading_client(login=12345, server="Broker-Demo")
155
- try:
156
- positions = client.positions_get_as_df(symbol="EURUSD")
157
- finally:
158
- client.shutdown()
147
+ positions = client.positions(symbol="EURUSD")
159
148
  ```
160
149
 
161
150
  ## CLI usage
@@ -278,30 +267,19 @@ Appends one timestamped row per data type. Never places orders or modifies tradi
278
267
  #### SDK usage
279
268
 
280
269
  ```python
281
- from pdmt5 import Mt5DataClient, Mt5Config
282
- from mt5cli import update_observability, update_observability_with_config
283
-
284
- # Reuse an already-connected client
285
- client = Mt5DataClient(config=Mt5Config(login=12345))
286
- client.initialize_and_login_mt5()
287
- try:
288
- update_observability(
289
- client=client,
290
- output="history.db",
291
- symbols=["EURUSD", "GBPUSD"], # optional position/order filter
292
- include_account=True,
293
- include_positions=True,
294
- include_orders=True,
295
- include_terminal=True,
296
- with_grafana_schema=True,
297
- )
298
- finally:
299
- client.shutdown()
270
+ from pdmt5 import Mt5Config
271
+ from mt5cli import update_observability_with_config
300
272
 
301
- # Standalone wrapper that opens/closes MT5 automatically
273
+ # This wrapper owns the MT5 connection automatically.
302
274
  update_observability_with_config(
303
275
  output="history.db",
304
276
  config=Mt5Config(login=12345),
277
+ symbols=["EURUSD", "GBPUSD"], # optional position/order filter
278
+ include_account=True,
279
+ include_positions=True,
280
+ include_orders=True,
281
+ include_terminal=True,
282
+ with_grafana_schema=True,
305
283
  )
306
284
  ```
307
285
 
@@ -359,33 +337,21 @@ OpenTelemetry metrics are documented in [`docs/api/telemetry.md`](docs/api/telem
359
337
  For automated pipelines, use the importable incremental API instead of re-fetching fixed date ranges:
360
338
 
361
339
  ```python
362
- from pdmt5 import Mt5Config, Mt5DataClient
363
- from mt5cli import update_history, update_history_with_config
340
+ from pdmt5 import Mt5Config
341
+ from mt5cli import update_history_with_config
364
342
  from mt5cli.utils import Dataset
365
343
 
366
- # Reuse an already-connected pdmt5 client (does not open/close MT5)
367
- client = Mt5DataClient(config=Mt5Config(login=12345))
368
- client.initialize_and_login_mt5()
369
- try:
370
- update_history(
371
- client=client,
372
- output="history.db",
373
- symbols=["EURUSD", "GBPUSD"],
374
- datasets={Dataset.rates, Dataset.history_deals},
375
- timeframes=["M1", "H1"], # default: all fixed MT5 timeframes
376
- lookback_hours=24,
377
- create_rate_views=True,
378
- with_views=True,
379
- include_account_events=True,
380
- )
381
- finally:
382
- client.shutdown()
383
-
384
- # Standalone wrapper that opens and closes MT5 for you
344
+ # This wrapper opens and closes MT5 for you.
385
345
  update_history_with_config(
386
346
  output="history.db",
387
- symbols=["EURUSD"],
347
+ symbols=["EURUSD", "GBPUSD"],
388
348
  config=Mt5Config(login=12345),
349
+ datasets={Dataset.rates, Dataset.history_deals},
350
+ timeframes=["M1", "H1"], # default: all fixed MT5 timeframes
351
+ lookback_hours=24,
352
+ create_rate_views=True,
353
+ with_views=True,
354
+ include_account_events=True,
389
355
  )
390
356
  ```
391
357
 
@@ -412,8 +378,8 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
412
378
  ```
413
379
 
414
380
  - **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code. For config dicts or nested structures loaded from YAML/TOML, use `substitute_mapping_values(data, keys={"login", "password"})` to expand placeholders only for caller-specified keys — key names are never hard-coded in mt5cli.
415
- - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.sdk.update_history`.
416
- - **Trading session helpers**: use `mt5_trading_session()` for a trading-capable client that initializes/logs in via `Mt5Config.path` and always shuts down safely. Pair with `detect_position_side()`, `calculate_margin_and_volume()`, and `determine_order_limits()` for generic position and sizing utilities. Keep read-only collection on `mt5_session()` / `MT5Client`.
381
+ - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, and `OSError` without advancing the throttle (`AttributeError` / `TypeError` always propagate, since the client passed to `update()` must implement the canonical `HistoryClient` method names). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.history.update_history`.
382
+ - **Trading helpers**: use `mt5_session()` for both market data and generic execution helpers. It owns initialization and shutdown for sessions it creates; caller-supplied clients remain caller-owned.
417
383
  - **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
418
384
  - **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `MT5Client` that shuts down on exit.
419
385
  - **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
@@ -431,7 +397,7 @@ Replace local MT5 lifecycle and trading helper code with mt5cli imports:
431
397
 
432
398
  ```python
433
399
  # Before (local application helpers)
434
- # with local_mt5_trading_session(config) as client:
400
+ # with local_mt5_session(config) as client:
435
401
  # side = local_detect_position_side(client, symbol)
436
402
  # sizing = local_calculate_margin_and_volume(client, symbol, unit_ratio, preserved_ratio)
437
403
  # limits = local_determine_order_limits(client, symbol, side, sl_ratio, tp_ratio)
@@ -442,12 +408,10 @@ from mt5cli import (
442
408
  calculate_margin_and_volume,
443
409
  detect_position_side,
444
410
  determine_order_limits,
445
- mt5_trading_session,
411
+ mt5_session,
446
412
  )
447
413
 
448
- with mt5_trading_session(
449
- Mt5Config(path=terminal_path, login=login), retry_count=2
450
- ) as client:
414
+ with mt5_session(Mt5Config(path=terminal_path, login=login)) as client:
451
415
  side = detect_position_side(client, symbol)
452
416
  sizing = calculate_margin_and_volume(
453
417
  client, symbol, unit_margin_ratio=0.5, preserved_margin_ratio=0.2
@@ -465,19 +429,15 @@ with mt5_trading_session(
465
429
  Throttled history updates use a separate read-only session:
466
430
 
467
431
  ```python
468
- from pdmt5 import Mt5Config, Mt5DataClient
432
+ from pdmt5 import Mt5Config
469
433
 
470
- from mt5cli import ThrottledHistoryUpdater
434
+ from mt5cli import ThrottledHistoryUpdater, mt5_session
471
435
 
472
436
  updater = ThrottledHistoryUpdater(
473
437
  output="history.db", interval_seconds=60, suppress_errors=True
474
438
  )
475
- client = Mt5DataClient(config=Mt5Config(login=login))
476
- client.initialize_and_login_mt5()
477
- try:
439
+ with mt5_session(Mt5Config(login=login)) as client:
478
440
  updater.update(client, ["EURUSD"])
479
- finally:
480
- client.shutdown()
481
441
  ```
482
442
 
483
443
  Read-only collectors can keep using `mt5_session()` and `MT5Client`.
@@ -103,28 +103,17 @@ unset. Pass `allow_whole_dollar_env=True` to expand `${ENV_VAR}` and bare
103
103
  from mt5cli import (
104
104
  build_config,
105
105
  calculate_spread_ratio,
106
- create_trading_client,
107
106
  get_account_snapshot,
108
- mt5_trading_session,
107
+ mt5_session,
109
108
  )
110
109
 
111
110
  # Login from environment — numeric string is coerced to int automatically
112
111
  config = build_config(login="$MT5_LOGIN", allow_whole_dollar_env=True)
113
112
 
114
- with mt5_trading_session(
115
- path=r"C:\Program Files\MetaTrader 5\terminal64.exe",
116
- login="12345",
117
- password="from-env-or-secret-store",
118
- server="Broker-Demo",
119
- ) as client:
113
+ with mt5_session(config) as client:
120
114
  account = get_account_snapshot(client)
121
115
  spread = calculate_spread_ratio(client, "EURUSD")
122
-
123
- client = create_trading_client(login=12345, server="Broker-Demo")
124
- try:
125
- positions = client.positions_get_as_df(symbol="EURUSD")
126
- finally:
127
- client.shutdown()
116
+ positions = client.positions(symbol="EURUSD")
128
117
  ```
129
118
 
130
119
  ## CLI usage
@@ -247,30 +236,19 @@ Appends one timestamped row per data type. Never places orders or modifies tradi
247
236
  #### SDK usage
248
237
 
249
238
  ```python
250
- from pdmt5 import Mt5DataClient, Mt5Config
251
- from mt5cli import update_observability, update_observability_with_config
252
-
253
- # Reuse an already-connected client
254
- client = Mt5DataClient(config=Mt5Config(login=12345))
255
- client.initialize_and_login_mt5()
256
- try:
257
- update_observability(
258
- client=client,
259
- output="history.db",
260
- symbols=["EURUSD", "GBPUSD"], # optional position/order filter
261
- include_account=True,
262
- include_positions=True,
263
- include_orders=True,
264
- include_terminal=True,
265
- with_grafana_schema=True,
266
- )
267
- finally:
268
- client.shutdown()
239
+ from pdmt5 import Mt5Config
240
+ from mt5cli import update_observability_with_config
269
241
 
270
- # Standalone wrapper that opens/closes MT5 automatically
242
+ # This wrapper owns the MT5 connection automatically.
271
243
  update_observability_with_config(
272
244
  output="history.db",
273
245
  config=Mt5Config(login=12345),
246
+ symbols=["EURUSD", "GBPUSD"], # optional position/order filter
247
+ include_account=True,
248
+ include_positions=True,
249
+ include_orders=True,
250
+ include_terminal=True,
251
+ with_grafana_schema=True,
274
252
  )
275
253
  ```
276
254
 
@@ -328,33 +306,21 @@ OpenTelemetry metrics are documented in [`docs/api/telemetry.md`](docs/api/telem
328
306
  For automated pipelines, use the importable incremental API instead of re-fetching fixed date ranges:
329
307
 
330
308
  ```python
331
- from pdmt5 import Mt5Config, Mt5DataClient
332
- from mt5cli import update_history, update_history_with_config
309
+ from pdmt5 import Mt5Config
310
+ from mt5cli import update_history_with_config
333
311
  from mt5cli.utils import Dataset
334
312
 
335
- # Reuse an already-connected pdmt5 client (does not open/close MT5)
336
- client = Mt5DataClient(config=Mt5Config(login=12345))
337
- client.initialize_and_login_mt5()
338
- try:
339
- update_history(
340
- client=client,
341
- output="history.db",
342
- symbols=["EURUSD", "GBPUSD"],
343
- datasets={Dataset.rates, Dataset.history_deals},
344
- timeframes=["M1", "H1"], # default: all fixed MT5 timeframes
345
- lookback_hours=24,
346
- create_rate_views=True,
347
- with_views=True,
348
- include_account_events=True,
349
- )
350
- finally:
351
- client.shutdown()
352
-
353
- # Standalone wrapper that opens and closes MT5 for you
313
+ # This wrapper opens and closes MT5 for you.
354
314
  update_history_with_config(
355
315
  output="history.db",
356
- symbols=["EURUSD"],
316
+ symbols=["EURUSD", "GBPUSD"],
357
317
  config=Mt5Config(login=12345),
318
+ datasets={Dataset.rates, Dataset.history_deals},
319
+ timeframes=["M1", "H1"], # default: all fixed MT5 timeframes
320
+ lookback_hours=24,
321
+ create_rate_views=True,
322
+ with_views=True,
323
+ include_account_events=True,
358
324
  )
359
325
  ```
360
326
 
@@ -381,8 +347,8 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
381
347
  ```
382
348
 
383
349
  - **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code. For config dicts or nested structures loaded from YAML/TOML, use `substitute_mapping_values(data, keys={"login", "password"})` to expand placeholders only for caller-specified keys — key names are never hard-coded in mt5cli.
384
- - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.sdk.update_history`.
385
- - **Trading session helpers**: use `mt5_trading_session()` for a trading-capable client that initializes/logs in via `Mt5Config.path` and always shuts down safely. Pair with `detect_position_side()`, `calculate_margin_and_volume()`, and `determine_order_limits()` for generic position and sizing utilities. Keep read-only collection on `mt5_session()` / `MT5Client`.
350
+ - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, and `OSError` without advancing the throttle (`AttributeError` / `TypeError` always propagate, since the client passed to `update()` must implement the canonical `HistoryClient` method names). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.history.update_history`.
351
+ - **Trading helpers**: use `mt5_session()` for both market data and generic execution helpers. It owns initialization and shutdown for sessions it creates; caller-supplied clients remain caller-owned.
386
352
  - **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
387
353
  - **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `MT5Client` that shuts down on exit.
388
354
  - **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
@@ -400,7 +366,7 @@ Replace local MT5 lifecycle and trading helper code with mt5cli imports:
400
366
 
401
367
  ```python
402
368
  # Before (local application helpers)
403
- # with local_mt5_trading_session(config) as client:
369
+ # with local_mt5_session(config) as client:
404
370
  # side = local_detect_position_side(client, symbol)
405
371
  # sizing = local_calculate_margin_and_volume(client, symbol, unit_ratio, preserved_ratio)
406
372
  # limits = local_determine_order_limits(client, symbol, side, sl_ratio, tp_ratio)
@@ -411,12 +377,10 @@ from mt5cli import (
411
377
  calculate_margin_and_volume,
412
378
  detect_position_side,
413
379
  determine_order_limits,
414
- mt5_trading_session,
380
+ mt5_session,
415
381
  )
416
382
 
417
- with mt5_trading_session(
418
- Mt5Config(path=terminal_path, login=login), retry_count=2
419
- ) as client:
383
+ with mt5_session(Mt5Config(path=terminal_path, login=login)) as client:
420
384
  side = detect_position_side(client, symbol)
421
385
  sizing = calculate_margin_and_volume(
422
386
  client, symbol, unit_margin_ratio=0.5, preserved_margin_ratio=0.2
@@ -434,19 +398,15 @@ with mt5_trading_session(
434
398
  Throttled history updates use a separate read-only session:
435
399
 
436
400
  ```python
437
- from pdmt5 import Mt5Config, Mt5DataClient
401
+ from pdmt5 import Mt5Config
438
402
 
439
- from mt5cli import ThrottledHistoryUpdater
403
+ from mt5cli import ThrottledHistoryUpdater, mt5_session
440
404
 
441
405
  updater = ThrottledHistoryUpdater(
442
406
  output="history.db", interval_seconds=60, suppress_errors=True
443
407
  )
444
- client = Mt5DataClient(config=Mt5Config(login=login))
445
- client.initialize_and_login_mt5()
446
- try:
408
+ with mt5_session(Mt5Config(login=login)) as client:
447
409
  updater.update(client, ["EURUSD"])
448
- finally:
449
- client.shutdown()
450
410
  ```
451
411
 
452
412
  Read-only collectors can keep using `mt5_session()` and `MT5Client`.
@@ -286,3 +286,61 @@ frame = series["EURUSD", 1] # keyed by (symbol, integer timeframe)
286
286
  )
287
287
  frame = series["EURUSD", "M1"] # keyed by (symbol | None, granularity_name)
288
288
  ```
289
+
290
+ ## Throttled incremental history updates
291
+
292
+ `ThrottledHistoryUpdater` wraps `update_history()` with a minimum interval
293
+ between successful runs (using a monotonic clock), so an application loop can
294
+ call it every iteration without over-fetching.
295
+
296
+ ```python
297
+ from pdmt5 import Mt5Config
298
+
299
+ from mt5cli import ThrottledHistoryUpdater, mt5_session
300
+ from mt5cli.utils import Dataset
301
+
302
+ updater = ThrottledHistoryUpdater(
303
+ output="history.db",
304
+ datasets={Dataset.rates},
305
+ timeframes=["M1"],
306
+ interval_seconds=60, # <= 0 updates on every call
307
+ )
308
+
309
+ with mt5_session(Mt5Config(login=12345)) as client:
310
+ while True:
311
+ updater.update(client, ["EURUSD", "GBPUSD"]) # no-op until 60s elapse
312
+ # ... do other work; break when shutting down ...
313
+ ```
314
+
315
+ Pass `update_backend` to substitute the default `update_history` implementation
316
+ without monkey-patching `mt5cli.history.update_history`. The callable receives the
317
+ same keyword arguments as `update_history` (`client`, `output`, `symbols`,
318
+ `datasets`, `timeframes`, `flags`, `lookback_hours`, `with_views`,
319
+ `include_account_events`). The resolved backend is stored on
320
+ `updater.update_backend` for inspection or subclassing.
321
+
322
+ ```python
323
+ from mt5cli import ThrottledHistoryUpdater, update_history
324
+
325
+
326
+ def app_update_history(**kwargs) -> None:
327
+ update_history(**kwargs) # or delegate to application-specific logic
328
+
329
+
330
+ updater = ThrottledHistoryUpdater(
331
+ output="history.db",
332
+ interval_seconds=60,
333
+ update_backend=app_update_history,
334
+ )
335
+ ```
336
+
337
+ By default recoverable errors (`Mt5RuntimeError`, `sqlite3.Error`,
338
+ `ValueError`, `OSError`) propagate so the caller controls logging; pass
339
+ `suppress_errors=True` to swallow them and return `False` without advancing the
340
+ throttle. `AttributeError` and `TypeError` are treated as caller programming
341
+ errors and always propagate, even when `suppress_errors=True` — the client
342
+ passed to `update()` must implement the canonical `HistoryClient` method names
343
+ (`copy_rates_range`, `copy_ticks_range`, `history_orders`, `history_deals`,
344
+ `symbol_info_as_dict`). Input validation (`_resolve_update_history_request`)
345
+ runs before any MT5 or SQLite calls, but when `suppress_errors=True` the
346
+ resulting `ValueError` is suppressed along with other recoverable errors.
@@ -8,31 +8,34 @@ responsibilities.
8
8
 
9
9
  ## Public API layers
10
10
 
11
- | Module | Purpose |
12
- | ----------------------------------------- | ------------------------------------------------------------------------- |
13
- | [Public API Contract](public-contract.md) | Stable downstream SDK exports, CLI boundary, and out-of-scope items |
14
- | [Client](client.md) | `MT5Client` session abstraction for data access and order primitives |
15
- | [Schemas](schemas.md) | Canonical DataFrame contracts and normalization helpers |
16
- | [Converters](converters.md) | Symbol, timeframe, timezone, and date-range utilities |
17
- | [Exceptions](exceptions.md) | Stable mt5cli exception types and MT5 error normalization |
18
- | [SDK](sdk.md) | Module-level fetch helpers, multi-account collectors, incremental history |
19
- | [Trading](trading.md) | Trading-capable sessions and operational helpers |
20
- | [History Collection (SQLite)](history.md) | SQLite schema, incremental writes, dedup, and rate views |
21
- | [Telemetry](telemetry.md) | OpenTelemetry metrics setup, meters, and emitted metric names |
22
- | [Grafana](grafana.md) | Grafana-ready SQLite schema, views, snapshots, and published copies |
23
- | [CLI](cli.md) | Typer commands that delegate to the Python API |
24
- | [Utils](utils.md) | Parsing helpers and Click parameter types |
11
+ | Module | Purpose |
12
+ | ----------------------------------------- | ------------------------------------------------------------------------------------ |
13
+ | [Public API Contract](public-contract.md) | Stable downstream SDK exports, CLI boundary, and out-of-scope items |
14
+ | [Client](client.md) | `MT5Client` / `mt5_session()` — the sole connection lifecycle and client abstraction |
15
+ | [Market Data](marketdata.md) | Stateless one-off market-data reads and multi-account rate collection |
16
+ | [Schemas](schemas.md) | Canonical DataFrame contracts and normalization helpers |
17
+ | [Converters](converters.md) | Symbol, timeframe, timezone, and date-range utilities |
18
+ | [Exceptions](exceptions.md) | Stable mt5cli exception types and MT5 error normalization |
19
+ | [Trading](trading.md) | Trading-capable sessions and operational helpers |
20
+ | [History Collection (SQLite)](history.md) | SQLite schema, incremental writes, dedup, and rate views |
21
+ | [Observability](observability.md) | Account/position/order/terminal snapshot orchestration |
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
+ | [CLI](cli.md) | Typer commands that delegate to the Python API |
25
+ | [Utils](utils.md) | Generic parsing, conversion, and export helpers |
25
26
 
26
27
  ## Architecture overview
27
28
 
28
29
  ```mermaid
29
30
  flowchart TD
30
- App["Downstream application"] --> Client["MT5Client"]
31
+ App["Downstream application"] --> Client["MT5Client / mt5_session"]
31
32
  CLI["mt5cli CLI"] --> Client
32
- Client --> SDK["sdk / pdmt5"]
33
- Client --> Schemas["schemas"]
34
- History["history SQLite"] --> Utils["utils export"]
35
- SDK --> PDMT5["pdmt5.Mt5DataClient"]
33
+ Client --> PDMT5["pdmt5.Mt5DataClient"]
34
+ MarketData["marketdata"] --> Client
35
+ History["history"] --> Client
36
+ Observability["observability"] --> Client
37
+ Observability --> Grafana["grafana"]
38
+ History --> Utils["utils export"]
36
39
  ```
37
40
 
38
41
  Downstream packages should depend on the package root exports documented in the
@@ -1,11 +1,11 @@
1
- # SDK Module
1
+ # Market Data Module
2
2
 
3
- ::: mt5cli.sdk
3
+ ::: mt5cli.marketdata
4
4
 
5
5
  ## Resilient multi-account orchestration
6
6
 
7
- The SDK ships strategy-agnostic helpers for building long-running collectors on
8
- top of the read-only client. None of them depend on a particular trading
7
+ This module ships strategy-agnostic helpers for building long-running
8
+ collectors on top of `MT5Client`. None of them depend on a particular trading
9
9
  application.
10
10
 
11
11
  ### Retrying transient rate collection
@@ -31,9 +31,8 @@ rates = collect_latest_rates_for_accounts_with_retries(
31
31
  ### Latest closed rate bars
32
32
 
33
33
  MetaTrader 5 `start_pos=0` includes the still-forming current bar as the last
34
- row. `fetch_latest_closed_rates()` handles one connected `MT5Client`; use
35
- `fetch_latest_closed_rates_for_trading_client()` from an active trading
36
- session created by `create_trading_client()`. Multi-account helpers fetch
34
+ row. `fetch_latest_closed_rates()` handles one connected `MT5Client`.
35
+ Multi-account helpers fetch
37
36
  `count + 1` bars, drop
38
37
  that row with `drop_forming_rate_bar()`, and validate each series is non-empty. Returned frames are ordered
39
38
  oldest-to-newest and may contain fewer than `count` rows only when MT5 returns
@@ -109,66 +108,6 @@ resolved = resolve_account_specs(accounts, allow_whole_dollar_env=True)
109
108
  # resolved[0].password == "secret"
110
109
  ```
111
110
 
112
- ### Throttled incremental history updates
113
-
114
- `ThrottledHistoryUpdater` wraps `update_history()` with a minimum interval
115
- between successful runs (using a monotonic clock), so an application loop can
116
- call it every iteration without over-fetching.
117
-
118
- ```python
119
- from pdmt5 import Mt5Config, Mt5DataClient
120
-
121
- from mt5cli import ThrottledHistoryUpdater
122
- from mt5cli.utils import Dataset
123
-
124
- updater = ThrottledHistoryUpdater(
125
- output="history.db",
126
- datasets={Dataset.rates},
127
- timeframes=["M1"],
128
- interval_seconds=60, # <= 0 updates on every call
129
- )
130
-
131
- client = Mt5DataClient(config=Mt5Config(login=12345))
132
- client.initialize_and_login_mt5()
133
- try:
134
- while True:
135
- updater.update(client, ["EURUSD", "GBPUSD"]) # no-op until 60s elapse
136
- # ... do other work; break when shutting down ...
137
- finally:
138
- client.shutdown()
139
- ```
140
-
141
- Pass `update_backend` to substitute the default `update_history` implementation
142
- without monkey-patching `mt5cli.sdk.update_history`. The callable receives the
143
- same keyword arguments as `update_history` (`client`, `output`, `symbols`,
144
- `datasets`, `timeframes`, `flags`, `lookback_hours`, `with_views`,
145
- `include_account_events`). The resolved backend is stored on
146
- `updater.update_backend` for inspection or subclassing.
147
-
148
- ```python
149
- from mt5cli import ThrottledHistoryUpdater, update_history
150
-
151
-
152
- def app_update_history(**kwargs) -> None:
153
- update_history(**kwargs) # or delegate to application-specific logic
154
-
155
-
156
- updater = ThrottledHistoryUpdater(
157
- output="history.db",
158
- interval_seconds=60,
159
- update_backend=app_update_history,
160
- )
161
- ```
162
-
163
- By default recoverable errors (`Mt5RuntimeError`, `sqlite3.Error`,
164
- `ValueError`, `OSError`, and MT5 client capability
165
- `AttributeError` / `TypeError` for history API methods) propagate so the caller
166
- controls logging; pass `suppress_errors=True` to swallow them and return
167
- `False` without advancing the throttle. Other `AttributeError` / `TypeError`
168
- values always propagate. Input validation (`_resolve_update_history_request`)
169
- runs before any MT5 or SQLite calls, but when `suppress_errors=True` the
170
- resulting `ValueError` is suppressed along with other recoverable errors.
171
-
172
111
  ## Trading-capable sessions
173
112
 
174
113
  For order placement and trading calculations, use the dedicated
@@ -0,0 +1,38 @@
1
+ # Observability Module
2
+
3
+ ::: mt5cli.observability
4
+
5
+ ## Grafana observability (SQLite read model)
6
+
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.
12
+
13
+ | Symbol | Role |
14
+ | ---------------------------------- | ------------------------------------------------------------------------------------------- |
15
+ | `update_observability` | Append one timestamped snapshot row per data type from a connected client implementation |
16
+ | `update_observability_with_config` | Standalone wrapper: opens/closes MT5 connection automatically around `update_observability` |
17
+
18
+ Both functions write to the SQLite path given by `output=`. The optional
19
+ `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
+
24
+ Pass the `MT5Client` yielded by `mt5_session()` directly to
25
+ `update_observability(client=...)`. This workflow calls the facade's canonical
26
+ data methods (`account_info`, `positions`, `orders`, `terminal_info`); callers
27
+ never need a pdmt5 client, and no raw pdmt5 method-name fallback logic is
28
+ involved.
29
+
30
+ ```python
31
+ from mt5cli import mt5_session, update_observability
32
+
33
+ with mt5_session() as client:
34
+ update_observability(client=client, output="observability.db")
35
+ ```
36
+
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.