* feat: Grafana copy publishing, dashboard examples, and optional OTel metrics Implements three observability improvements: #82 — publish_grafana_copy(): Uses SQLite online backup API (WAL-safe) to atomically publish a consistent read-only copy beside the target. Adds --publish-copy option to grafana-schema and snapshot CLI commands. #83 — examples/grafana/: Minimal working Grafana setup with docker-compose, provisioning datasource/dashboard YAML, and three dashboard JSON files (mt5cli-overview, mt5cli-trades, mt5cli-market). All queries use grafana_* views; no credentials or private paths included. #84 — mt5cli/telemetry.py: Optional OTel metrics behind mt5cli[otel] extra. Base install is unaffected. Adds _Mt5Metrics singleton (no-op until configure_metrics() is called), wraps update_history() and update_observability() with record_history_update / record_snapshot_update context managers, and emits account/position gauges from snapshots. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: replace ambiguous multiplication sign in comment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: normalize markdown formatting in grafana README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: preserve file mode on Grafana copy and fix unsupported time macro - publish_grafana_copy: chmod temp file to match the existing target's permissions (or 0o644 when no prior target exists) before atomic replace, so Grafana running as a different OS user (e.g. UID 472 in Docker) can read the published database - mt5cli-market.json: replace unsupported \$__timeFilter(time) with the epoch-based filter supported by frser-sqlite-datasource: "time" >= \$__from / 1000 AND "time" < \$__to / 1000 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: skip Windows-incompatible mode test, rename compose file to compose.yaml - Skip test_overwrite_preserves_existing_target_mode on win32 since Windows chmod does not preserve Unix group/other permission bits - Simplify test_fresh_target_has_readable_permissions to check owner read bit only (portable across platforms) - Rename docker-compose.yml -> compose.yaml (modern Compose convention) - Update README and test reference to match new filename Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: rename *.yaml to *.yml in examples/grafana Renames compose.yaml, mt5cli-sqlite.yaml, and mt5cli.yaml to .yml; updates README and test references accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: format Grafana dashboards and expand qa script to include JSON - Update qa.sh prettier pattern to format JSON files alongside markdown - Reformat Grafana dashboard JSONs with consistent spacing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address owner review comments before merge - qa.sh: fix Prettier glob from `{,d,json}` to `{md,json}` so Markdown files are actually formatted by local QA (P2) - compose.yml: add GF_INSTALL_PLUGINS env var so the frser-sqlite-datasource plugin is installed at container start (P1) - telemetry.py: replace no-op get_meter() call with a real SDK MeterProvider pipeline; add optional `readers` kwarg so callers can inject custom readers (e.g. InMemoryMetricReader in tests) without needing the OTLP package (P1) - sdk.py: aggregate profit and volume by symbol before emitting gauge values so hedging accounts with multiple same-symbol positions emit one point per symbol instead of overwriting with each row (P2) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: emit mt5_history_update_rows_total via conn.total_changes delta The counter was registered but never incremented, making the advertised history-update throughput metric permanently zero. Add add_history_rows() to _Mt5Metrics and call it in update_history() using the SQLite total_changes delta measured around write_incremental_datasets(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address three owner review comments - compose.yml: replace soft fallback with :? error expansion so Compose refuses to start when MT5CLI_DB_PATH is unset or empty (P1) - README.md: tell native Windows users to copy only the datasource provisioning file; the dashboards yml contains a Docker-specific path that is invalid on Windows (P2) - telemetry.py / sdk.py: emit mt5_terminal_connected, mt5_terminal_trade_allowed, and mt5_terminal_trade_expert gauges via a new record_terminal_state() method called from _snapshot_terminal(), completing the connection-status metric surface from issue #84 (P2) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add snapshot freshness panel and win-rate column to dashboards - mt5cli-overview.json: add a full-width "Last Snapshot" stat panel (dateTimeFromNow unit) below the account stats, querying MAX(time)*1000 from grafana_account_snapshots so users can tell whether Grafana is reading a current published copy (#83) - mt5cli-trades.json: add win_rate_pct computed column to the Trade Statistics by Symbol table via 100.0 * winning_deals / NULLIF( total_deals, 0), with a percent unit override and "Win Rate (%)" display label (#83) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: reject same source and target path in publish_grafana_copy Adds an early same-path guard to publish_grafana_copy: resolves both paths before any I/O and raises ValueError if they are identical, preventing the function from overwriting the live source database with its own backup copy. Also adds a unit test for the rejected case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address ruff EM102/TRY003/E501 in same-path guard Assigns the ValueError message to a variable before raising and shortens the test docstring to stay within the 88-char line limit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: apply ruff format to publish_grafana_copy error message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove grafana_ticks panel from default market dashboard The Tick Bid/Ask panel queried grafana_ticks which only exists when users collect tick data (opt-in). Users following the default OHLCV-only setup path hit "no such table: grafana_ticks" on dashboard load. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: close SQLite connections before atomic replace in publish_grafana_copy Wrap both src and dst connections with contextlib.closing() so they are explicitly closed before tmp_path.replace(target_path) runs. Without this, sqlite3.Connection's context manager only commits/rolls back but leaves the file handle open, which can cause PermissionError on Windows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: rename history.grafana.db to history.mt5cli.db in Grafana examples frser-sqlite-datasource blocks paths containing "grafana.db" via its internal blocklist. Rename the recommended published filename in the README, compose comment, and datasource provisioning comment to avoid a blocked/denied datasource for native Windows users. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: update Docker Compose quick-start to pass MT5CLI_DB_PATH The compose.yml already required MT5CLI_DB_PATH via ${MT5CLI_DB_PATH:?...}, but the README still showed bare `docker compose up -d`. Update the section to show the env-var-prefixed invocation and document the .env file alternative. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: agent <agent@localhost> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
mt5cli
Generic MT5 data and execution infrastructure for Python applications. Export from the CLI or import a small, stable Python API in downstream packages.
The Public API Contract lists stable SDK exports (mt5cli.STABLE_SDK_EXPORTS), CLI commands, internal helpers, and responsibilities that remain out of scope (strategy logic, backtests, optimization).
Built on top of pdmt5, a pandas-based data handler for MetaTrader 5.
Architecture
- pdmt5 — canonical MT5 client, DataFrame/trading primitives, and MT5 constant parsing (
TIMEFRAME_*,COPY_TICKS_*, order types). - mt5cli — public
MT5ClientAPI, standardized dataset schemas, storage helpers, CLI commands, and SQLite history collection built on pdmt5. - mt5api — sibling HTTP adapter for remote MT5 access; not a dependency of mt5cli.
Features
- Multi-format export: CSV, JSON, Parquet, and SQLite3 output formats
- Auto-detection: Format detection from file extensions
- Comprehensive data access: Rates, ticks, account info, symbols, orders, positions, and trading history
- Flexible timeframes: Named timeframes (M1, H1, D1, etc.) and numeric values
- Connection management: Optional credentials, server, and timeout configuration
- SQLite rate loading: Load mt5cli-managed rate tables/views for offline workflows
Installation
pip install -U mt5cli MetaTrader5
Parquet export is not included by default. To enable it, install the parquet extra:
pip install -U "mt5cli[parquet]" MetaTrader5
Python API (downstream packages)
Import MT5Client for generic MT5 data access, schema normalization, and optional order primitives.
from datetime import UTC, datetime
from pathlib import Path
from mt5cli import (
MT5Client,
build_config,
collect_history,
mt5_session,
update_history_with_config,
)
from mt5cli.schemas import DataKind, normalize_dataframe
from mt5cli.utils import Dataset, export_dataframe
# Persistent session for multiple calls
with mt5_session(build_config(login=12345, server="Broker-Demo")) as client:
rates = client.copy_rates_range(
"EURUSD",
timeframe="H1",
date_from="2024-01-01",
date_to="2024-02-01",
)
positions = client.positions()
check = client.order_check({"action": 1, "symbol": "EURUSD", "volume": 0.1})
# Normalize MT5 frames to the public schema contract before storage
closed_rates = normalize_dataframe(
rates, DataKind.rates, symbol="EURUSD", timeframe="H1"
)
export_dataframe(closed_rates, Path("rates.csv"), "csv")
# Bulk SQLite history (same behavior as collect-history CLI command)
collect_history(
Path("history.db"),
symbols=["EURUSD"],
date_from=datetime(2024, 1, 1, tzinfo=UTC),
date_to=datetime(2024, 2, 1, tzinfo=UTC),
datasets={Dataset.rates, Dataset.history_deals},
)
# Incremental append for automated pipelines
update_history_with_config(
output="history.db",
symbols=["EURUSD"],
config=build_config(login=12345),
)
Schema contracts live in mt5cli.schemas (DataKind, validate_schema, normalize_dataframe). Export and storage helpers are in mt5cli.utils (Dataset, export_dataframe) and mt5cli.history.
MT5Client.order_send() is a live execution primitive: it can place real trades on the connected account. mt5cli does not implement strategy logic, signal generation, backtesting, or optimization — downstream applications must gate live execution explicitly.
Trading lifecycle and state helpers
Trading applications can depend on mt5cli imports only; terminal path,
credentials, server, and timeout are forwarded to pdmt5.Mt5Config, numeric
login strings are coerced to integers, and empty login strings are treated as
unset. Pass allow_whole_dollar_env=True to expand ${ENV_VAR} and bare
$ENV_NAME placeholders in connection string parameters before coercion.
from mt5cli import (
build_config,
calculate_spread_ratio,
create_trading_client,
get_account_snapshot,
mt5_trading_session,
)
# Login from environment — numeric string is coerced to int automatically
config = build_config(login="$MT5_LOGIN", allow_whole_dollar_env=True)
with mt5_trading_session(
path=r"C:\Program Files\MetaTrader 5\terminal64.exe",
login="12345",
password="from-env-or-secret-store",
server="Broker-Demo",
) as client:
account = get_account_snapshot(client)
spread = calculate_spread_ratio(client, "EURUSD")
client = create_trading_client(login=12345, server="Broker-Demo")
try:
positions = client.positions_get_as_df(symbol="EURUSD")
finally:
client.shutdown()
CLI usage
# Export account information to CSV
mt5cli -o account.csv account-info
# Export EURUSD M1 rates to Parquet
mt5cli -o rates.parquet rates-from --symbol EURUSD --timeframe M1 \
--date-from 2024-01-01 --count 1000
# Export ticks to JSON
mt5cli -o ticks.json ticks-from --symbol EURUSD \
--date-from 2024-01-01 --count 500 --flags ALL
# Export symbols to SQLite3 with custom table name
mt5cli -o data.db --table symbols symbols --group "*USD*"
# Export with connection credentials
mt5cli --login 12345 --password mypass --server MyBroker-Demo \
-o positions.csv positions
Run as a Python module:
python -m mt5cli -o account.csv account-info
Commands
| Command | Description |
|---|---|
rates-from |
Export rates from a start date |
rates-from-pos |
Export rates from a start position |
latest-rates |
Export latest rates from a start position |
rates-range |
Export rates for a date range |
ticks-from |
Export ticks from a start date |
ticks-range |
Export ticks for a date range |
ticks-recent |
Export ticks from a recent trailing window |
account-info |
Export account information |
terminal-info |
Export terminal information |
version |
Export MetaTrader 5 version information |
last-error |
Export the last error information |
symbols |
Export symbol list |
symbol-info |
Export symbol details |
symbol-info-tick |
Export the last tick for a symbol |
minimum-margins |
Export minimum-volume buy and sell margin requirements |
market-book |
Export market depth (order book) |
orders |
Export active orders |
positions |
Export open positions |
history-orders |
Export historical orders |
history-deals |
Export historical deals |
recent-history-deals |
Export historical deals from a recent trailing window |
mt5-summary |
Export terminal/account status summary |
order-check |
Check funds sufficiency for a trade request |
order-send |
Send a raw trade request to the trade server (--yes required; expert path) |
close-positions |
Close open positions by --symbol or --ticket (--yes required for live; --dry-run available) |
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) |
grafana-schema |
Create or refresh Grafana-ready views and indexes in an existing SQLite database (idempotent, no MT5 connection) |
snapshot |
Snapshot current account, position, order, and terminal state into SQLite for live Grafana dashboards |
Use order-check to validate a request payload before running order-send --yes.
close-positions is the safer high-level alternative that builds correct close
requests automatically. At least one --symbol or --ticket must be provided.
collect-history
Collect several historical datasets per symbol into one SQLite database in a single MT5 session. Pick datasets with repeatable --dataset (default: rates, history-orders, history-deals; add --dataset ticks when tick-level history is required — tick data can grow the SQLite database quickly), choose conflict behavior with --if-exists append|replace|fail (default: fail), and optionally derive cash_events / positions_reconstructed views from history_deals via --with-views.
mt5cli -o history.db collect-history \
--symbol EURUSD --symbol GBPUSD \
--date-from 2024-01-01 --date-to 2024-02-01 \
--dataset rates --dataset history-deals \
--timeframe M1 --flags ALL --if-exists append --with-views
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.
Grafana-ready SQLite dashboards
mt5cli can prepare a SQLite database for use as a Grafana datasource (via the SQLite plugin 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.
Prepare the schema (idempotent, no MT5 connection needed)
mt5cli -o history.db grafana-schema
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.
Snapshot current account state
mt5cli -o history.db snapshot \
--symbol JP225 --symbol HK50 --symbol NL25 \
--with-account --with-positions --with-orders --with-terminal \
--with-grafana-schema
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.
SDK usage
from pdmt5 import Mt5DataClient, Mt5Config
from mt5cli import update_observability, update_observability_with_config
# Reuse an already-connected client
client = Mt5DataClient(config=Mt5Config(login=12345))
client.initialize_and_login_mt5()
try:
update_observability(
client=client,
output="history.db",
symbols=["EURUSD", "GBPUSD"], # optional position/order filter
include_account=True,
include_positions=True,
include_orders=True,
include_terminal=True,
with_grafana_schema=True,
)
finally:
client.shutdown()
# Standalone wrapper that opens/closes MT5 automatically
update_observability_with_config(
output="history.db",
config=Mt5Config(login=12345),
)
Available Grafana views
Time-series views (integer epoch-second time column; snapshot views also expose run_id):
| View | Source | Description |
|---|---|---|
grafana_rates |
rates |
OHLCV bars with integer epoch time |
grafana_ticks |
ticks |
Tick data with integer epoch time |
grafana_history_deals |
history_deals |
All deals with epoch time |
grafana_history_orders |
history_orders |
All historical orders; adds epoch time from time_setup |
grafana_trade_deals |
history_deals |
Trade deals only (type IN (0,1)) |
grafana_cash_events |
history_deals |
Non-trade deals (deposits, dividends, etc.) |
grafana_symbol_pnl |
history_deals |
Per-close-deal profit/loss per symbol |
grafana_account_snapshots |
account_snapshots |
Account balance/equity/margin time series |
grafana_position_snapshots |
position_snapshots |
Open position snapshots over time |
grafana_order_snapshots |
order_snapshots |
Active order snapshots over time |
grafana_terminal_snapshots |
terminal_snapshots |
Terminal connectivity snapshots |
Static summary views (no time column; use in table or stat panels, not time-series):
| View | Source | Description |
|---|---|---|
grafana_realized_pnl |
history_deals |
Cumulative realized PnL per symbol |
grafana_trade_stats |
history_deals |
Win/loss counts and profit per symbol |
Example Grafana queries
-- Equity curve over time
SELECT time, equity FROM grafana_account_snapshots ORDER BY time;
-- Rolling balance by account login
SELECT time, login, balance FROM grafana_account_snapshots
WHERE login = $login ORDER BY time;
-- Open positions at latest successful snapshot
SELECT symbol, volume, profit FROM grafana_position_snapshots
WHERE run_id = (SELECT MAX(run_id) FROM snapshot_runs WHERE status = 'ok');
-- Realized PnL by symbol
SELECT symbol, total_profit FROM grafana_trade_stats ORDER BY total_profit DESC;
Note
: OpenTelemetry integration is intentionally not part of this release and is tracked separately.
Incremental history SDK
For automated pipelines, use the importable incremental API instead of re-fetching fixed date ranges:
from pdmt5 import Mt5Config, Mt5DataClient
from mt5cli import update_history, update_history_with_config
from mt5cli.utils import Dataset
# Reuse an already-connected pdmt5 client (does not open/close MT5)
client = Mt5DataClient(config=Mt5Config(login=12345))
client.initialize_and_login_mt5()
try:
update_history(
client=client,
output="history.db",
symbols=["EURUSD", "GBPUSD"],
datasets={Dataset.rates, Dataset.history_deals},
timeframes=["M1", "H1"], # default: all fixed MT5 timeframes
lookback_hours=24,
create_rate_views=True,
with_views=True,
include_account_events=True,
)
finally:
client.shutdown()
# Standalone wrapper that opens and closes MT5 for you
update_history_with_config(
output="history.db",
symbols=["EURUSD"],
config=Mt5Config(login=12345),
)
collect-history: explicit date-range export into SQLite.update_history: incremental append based on existing SQLiteMAX(time)per symbol (and timeframe for rates); account-level deals use a separate cursor wheninclude_account_events=True.ratestable: normalized storage withsymbolandtimeframecolumns.- Rate compatibility views: mt5cli manages all
rate_*views. Naming israte_<symbol>__<timeframe>when a symbol has one timeframe, otherwiserate_<symbol>__<granularity>_<timeframe>(for examplerate_EURUSD__M1_1). Stalerate_*views are dropped and recreated when rates change for offline downstream tools. - Rate view resolution: use
resolve_rate_view_name()/resolve_rate_view_names()to map symbols and granularities to existing SQLite compatibility views without creating databases. Both acceptNone(or a missing path) and return deterministic default names unlessrequire_existing=True. - Rate view loading: use
load_rate_data()/load_rate_data_from_connection()to load a SQLite rate table or view into aDatetimeIndexDataFrame. - Multi-series rate loading: use
build_rate_targets()to build neutralRateTarget(symbol, timeframe)pairs,resolve_rate_tables()to map them to table/view names (passrequire_existing=Truefor strict resolution), andload_rate_series_from_sqlite()to load them into a mapping keyed by(symbol, integer timeframe). The loader requires existing managed views unlessexplicit_tablesis supplied, and rejects duplicate(symbol, timeframe)targets. - Multi-account latest rates: use
collect_latest_rates_for_accounts()withAccountSpecto read the latest bars for several account groups, merged into a(symbol, integer timeframe)mapping. For long-running pollers,collect_latest_rates_for_accounts_with_retries()adds bounded exponential backoff that retries only recoverable MT5 errors and re-raises onceretry_countis exhausted. - Latest closed bars: use
collect_latest_closed_rates_for_accounts()when downstream logic must exclude the still-forming current bar. It fetchescount + 1bars atstart_pos=0, drops the last row withdrop_forming_rate_bar(), and validates each series is non-empty.collect_latest_closed_rates_by_granularity()returns the same data keyed by(symbol, granularity_name)such as("EURUSD", "M1").
from mt5cli import AccountSpec, collect_latest_closed_rates_by_granularity
rates = collect_latest_closed_rates_by_granularity(
[AccountSpec(symbols=["EURUSD", "GBPUSD"], login=12345)],
["M1", "H1"],
count=500,
retry_count=3,
)
eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
- Credential resolution: use
resolve_account_spec()/resolve_account_specs()to merge explicit override values overAccountSpecfields and expand${ENV_VAR}placeholders (viasubstitute_env_placeholders()), raisingValueErrorfor 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, usesubstitute_mapping_values(data, keys={"login", "password"})to expand placeholders only for caller-specified keys — key names are never hard-coded in mt5cli. - Throttled history updates: use
ThrottledHistoryUpdaterto wrapupdate_history()with a minimuminterval_secondsbetween successful runs (monotonic clock). Callshould_update()/update(client, symbols)from an application loop; errors propagate by default, or passsuppress_errors=Trueto swallow recoverableMt5*Error,sqlite3.Error,ValueError,OSError, and MT5 client capability errors for history API methods without advancing the throttle (otherAttributeError/TypeErrorvalues always propagate). Passupdate_backendto inject a custom history update callable (same keyword arguments asupdate_history) instead of monkey-patchingmt5cli.sdk.update_history. - Trading session helpers: use
mt5_trading_session()for a trading-capable client that initializes/logs in viaMt5Config.pathand always shuts down safely. Pair withdetect_position_side(),calculate_margin_and_volume(), anddetermine_order_limits()for generic position and sizing utilities. Keep read-only collection onmt5_session()/MT5Client. - Granularity-keyed rate loading:
load_rate_series_by_granularity()builds targets withbuild_rate_targets(), loads them withload_rate_series_from_sqlite(), and returns a mapping keyed by(symbol | None, granularity_name)such as("EURUSD", "M1")to reduce downstream boilerplate. - MT5 session helper: use the
mt5_session()context manager to attach to (or, whenMt5Config.pathis set, launch) an MT5 terminal, log in, and yield a connectedMT5Clientthat shuts down on exit. - SQLite export helpers: use
export_dataframe_to_sqlite()for append mode, optional index export, and post-write deduplication by key columns. - Recent ticks and margins:
recent_ticks()andminimum_margins()SDK helpers (and matching CLI commands) cover common downstream read-only queries.
Requirements
- Python 3.11+
- Windows OS (MetaTrader 5 requirement)
- MetaTrader 5 platform installed
Migration note for downstream trading apps
Replace local MT5 lifecycle and trading helper code with mt5cli imports:
# Before (local application helpers)
# with local_mt5_trading_session(config) as client:
# side = local_detect_position_side(client, symbol)
# sizing = local_calculate_margin_and_volume(client, symbol, unit_ratio, preserved_ratio)
# limits = local_determine_order_limits(client, symbol, side, sl_ratio, tp_ratio)
# After (mt5cli shared layer)
from pdmt5 import Mt5Config
from mt5cli import (
calculate_margin_and_volume,
detect_position_side,
determine_order_limits,
mt5_trading_session,
)
with mt5_trading_session(
Mt5Config(path=terminal_path, login=login), retry_count=2
) as client:
side = detect_position_side(client, symbol)
sizing = calculate_margin_and_volume(
client, symbol, unit_margin_ratio=0.5, preserved_margin_ratio=0.2
)
if side is not None:
limits = determine_order_limits(
client,
symbol,
side,
stop_loss_limit_ratio=0.01,
take_profit_limit_ratio=0.02,
)
Throttled history updates use a separate read-only session:
from pdmt5 import Mt5Config, Mt5DataClient
from mt5cli import ThrottledHistoryUpdater
updater = ThrottledHistoryUpdater(
output="history.db", interval_seconds=60, suppress_errors=True
)
client = Mt5DataClient(config=Mt5Config(login=login))
client.initialize_and_login_mt5()
try:
updater.update(client, ["EURUSD"])
finally:
client.shutdown()
Read-only collectors can keep using mt5_session() and MT5Client.
Development
git clone https://github.com/dceoy/mt5cli.git
cd mt5cli
uv sync