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.
- {mt5cli-1.1.6 → mt5cli-1.2.0}/PKG-INFO +31 -71
- {mt5cli-1.1.6 → mt5cli-1.2.0}/README.md +30 -70
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/history.md +58 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/index.md +22 -19
- mt5cli-1.1.6/docs/api/sdk.md → mt5cli-1.2.0/docs/api/marketdata.md +6 -67
- mt5cli-1.2.0/docs/api/observability.md +38 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/public-contract.md +104 -24
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/trading.md +55 -113
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/index.md +4 -3
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mkdocs.yml +2 -1
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/__init__.py +6 -15
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/cli.py +146 -20
- mt5cli-1.2.0/mt5cli/client.py +1035 -0
- mt5cli-1.2.0/mt5cli/contract.py +169 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/exceptions.py +14 -7
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/history.py +494 -28
- mt5cli-1.2.0/mt5cli/marketdata.py +818 -0
- mt5cli-1.2.0/mt5cli/observability.py +258 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/retry.py +3 -2
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/trading.py +426 -499
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/utils.py +5 -122
- {mt5cli-1.1.6 → mt5cli-1.2.0}/pyproject.toml +1 -1
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/conftest.py +1 -1
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_cli.py +165 -43
- mt5cli-1.2.0/tests/test_client.py +1430 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_contracts.py +172 -3
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_history.py +1022 -30
- mt5cli-1.2.0/tests/test_marketdata.py +729 -0
- mt5cli-1.2.0/tests/test_observability.py +458 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_trading.py +490 -868
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_utils.py +0 -99
- {mt5cli-1.1.6 → mt5cli-1.2.0}/uv.lock +2 -2
- mt5cli-1.1.6/mt5cli/client.py +0 -86
- mt5cli-1.1.6/mt5cli/contract.py +0 -75
- mt5cli-1.1.6/mt5cli/sdk.py +0 -2408
- mt5cli-1.1.6/tests/test_client.py +0 -44
- mt5cli-1.1.6/tests/test_sdk.py +0 -3386
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/mt5cli/SKILL.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.claude/agents/codex.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.claude/settings.json +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/FUNDING.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/dependabot.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/renovate.json +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/ci.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/claude.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/opencode.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.github/workflows/release.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/.gitignore +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/AGENTS.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/CLAUDE.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/LICENSE +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/cli.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/client.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/converters.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/exceptions.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/grafana.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/schemas.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/telemetry.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/docs/api/utils.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/README.md +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/compose.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/dashboards/mt5cli-market.json +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/__main__.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/converters.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/grafana.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/schemas.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/mt5cli/telemetry.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/__init__.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_examples.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_grafana.py +0 -0
- {mt5cli-1.1.6 → mt5cli-1.2.0}/tests/test_packaging.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
|
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
|
|
282
|
-
from mt5cli import
|
|
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
|
-
#
|
|
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
|
|
363
|
-
from mt5cli import
|
|
340
|
+
from pdmt5 import Mt5Config
|
|
341
|
+
from mt5cli import update_history_with_config
|
|
364
342
|
from mt5cli.utils import Dataset
|
|
365
343
|
|
|
366
|
-
#
|
|
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
|
|
416
|
-
- **Trading
|
|
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
|
|
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
|
-
|
|
411
|
+
mt5_session,
|
|
446
412
|
)
|
|
447
413
|
|
|
448
|
-
with
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
251
|
-
from mt5cli import
|
|
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
|
-
#
|
|
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
|
|
332
|
-
from mt5cli import
|
|
309
|
+
from pdmt5 import Mt5Config
|
|
310
|
+
from mt5cli import update_history_with_config
|
|
333
311
|
from mt5cli.utils import Dataset
|
|
334
312
|
|
|
335
|
-
#
|
|
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
|
|
385
|
-
- **Trading
|
|
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
|
|
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
|
-
|
|
380
|
+
mt5_session,
|
|
415
381
|
)
|
|
416
382
|
|
|
417
|
-
with
|
|
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
|
|
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
|
-
|
|
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`
|
|
15
|
-
| [
|
|
16
|
-
| [
|
|
17
|
-
| [
|
|
18
|
-
| [
|
|
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
|
-
| [
|
|
22
|
-
| [
|
|
23
|
-
| [
|
|
24
|
-
| [
|
|
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 -->
|
|
33
|
-
|
|
34
|
-
History["history
|
|
35
|
-
|
|
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
|
-
#
|
|
1
|
+
# Market Data Module
|
|
2
2
|
|
|
3
|
-
::: mt5cli.
|
|
3
|
+
::: mt5cli.marketdata
|
|
4
4
|
|
|
5
5
|
## Resilient multi-account orchestration
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
top of
|
|
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
|
|
35
|
-
|
|
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.
|