Skip to main content

mt5cli

CI/CD

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 MT5Client API, 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,
    get_account_snapshot,
    mt5_session,
)

# Login from environment — numeric string is coerced to int automatically
config = build_config(login="$MT5_LOGIN", allow_whole_dollar_env=True)

with mt5_session(config) as client:
    account = get_account_snapshot(client)
    spread = calculate_spread_ratio(client, "EURUSD")
    positions = client.positions(symbol="EURUSD")

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 from env or placeholders
MT5_LOGIN=12345 MT5_PASSWORD=secret MT5_SERVER=MyBroker-Demo \
  mt5cli -o positions.csv positions
MT5_PATH="/path/to/terminal64.exe" mt5cli -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; optional --filling-mode / --deviation / --comment / --magic)
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)
history-gaps Export a SQLite-only one-row-per-gap report from managed rate compatibility views without connecting to MT5
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. CLI connection flags fall back to MT5_LOGIN, MT5_PASSWORD, MT5_SERVER, and MT5_PATH when unset, and explicit CLI values still win.

history-gaps

Inspect collected SQLite rate views offline and export one row per detected gap. For managed compatibility views, the command infers bar spacing from the view name. Use --granularity-seconds for custom tables or views.

mt5cli -o gaps.json history-gaps --sqlite3 history.db
mt5cli -o eurusd.csv history-gaps --sqlite3 history.db --table rate_EURUSD__M1_1

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 Mt5Config
from mt5cli import update_observability_with_config

# This wrapper owns the MT5 connection automatically.
update_observability_with_config(
    output="history.db",
    config=Mt5Config(login=12345),
    symbols=["EURUSD", "GBPUSD"],  # optional position/order filter
    include_account=True,
    include_positions=True,
    include_orders=True,
    include_terminal=True,
    with_grafana_schema=True,
)

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;

Grafana and telemetry API docs

The shipped Grafana helpers are documented in docs/api/grafana.md, including publish_grafana_copy() for creating a WAL-safe published SQLite copy for Grafana.

OpenTelemetry metrics are documented in docs/api/telemetry.md, including enable_otel_metrics(), configure_metrics(), the mt5cli[otel] extra, and OTEL_EXPORTER_OTLP_ENDPOINT.

Incremental history SDK

For automated pipelines, use the importable incremental API instead of re-fetching fixed date ranges:

from pdmt5 import Mt5Config
from mt5cli import update_history_with_config
from mt5cli.utils import Dataset

# This wrapper opens and closes MT5 for you.
update_history_with_config(
    output="history.db",
    symbols=["EURUSD", "GBPUSD"],
    config=Mt5Config(login=12345),
    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,
)
  • collect-history: explicit date-range export into SQLite.
  • update_history: incremental append based on existing SQLite MAX(time) per symbol (and timeframe for rates); account-level deals use a separate cursor when include_account_events=True.
  • rates table: normalized storage with symbol and timeframe columns.
  • Rate compatibility views: mt5cli manages all rate_* views. Naming is rate_<symbol>__<timeframe> when a symbol has one timeframe, otherwise rate_<symbol>__<granularity>_<timeframe> (for example rate_EURUSD__M1_1). Stale rate_* 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 accept None (or a missing path) and return deterministic default names unless require_existing=True.
  • Rate view loading: use load_rate_data() / load_rate_data_from_connection() to load a SQLite rate table or view into a DatetimeIndex DataFrame.
  • Multi-series rate loading: use build_rate_targets() to build neutral RateTarget(symbol, timeframe) pairs, resolve_rate_tables() to map them to table/view names (pass require_existing=True for strict resolution), and load_rate_series_from_sqlite() to load them into a mapping keyed by (symbol, integer timeframe). The loader requires existing managed views unless explicit_tables is supplied, and rejects duplicate (symbol, timeframe) targets.
  • Multi-account latest rates: use collect_latest_rates_for_accounts() with AccountSpec to 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 once retry_count is exhausted.
  • Latest closed bars: use collect_latest_closed_rates_for_accounts() when downstream logic must exclude the still-forming current bar. It fetches count + 1 bars at start_pos=0, drops the last row with drop_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 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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() and minimum_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_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_session,
)

with mt5_session(Mt5Config(path=terminal_path, login=login)) 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

from mt5cli import ThrottledHistoryUpdater, mt5_session

updater = ThrottledHistoryUpdater(
    output="history.db", interval_seconds=60, suppress_errors=True
)
with mt5_session(Mt5Config(login=login)) as client:
    updater.update(client, ["EURUSD"])

Read-only collectors can keep using mt5_session() and MT5Client.

Development

git clone https://github.com/dceoy/mt5cli.git
cd mt5cli
uv sync

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mt5cli-1.2.4.tar.gz (278.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mt5cli-1.2.4-py3-none-any.whl (89.8 kB view details)

Uploaded Python 3

File details

Details for the file mt5cli-1.2.4.tar.gz.

File metadata

  • Download URL: mt5cli-1.2.4.tar.gz
  • Upload date:
  • Size: 278.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for mt5cli-1.2.4.tar.gz
Algorithm Hash digest
SHA256 167160cb5752165b1bceb3d9f3360e9eda9d043c9f4482a22f1c73b112f7a960
MD5 055df599db046e0278b08e337b11fd4d
BLAKE2b-256 4ebb6814ec05f842721f3e4e84f32e9c295075561a51af0905d8bbd3d293caff

See more details on using hashes here.

Provenance

The following attestation bundles were made for mt5cli-1.2.4.tar.gz:

Publisher: release.yml on dceoy/mt5cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mt5cli-1.2.4-py3-none-any.whl.

File metadata

  • Download URL: mt5cli-1.2.4-py3-none-any.whl
  • Upload date:
  • Size: 89.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for mt5cli-1.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 487b18f860559ac25b20b22c049c78c4d84d6030165a99cb33dc0cf360dc043d
MD5 35555a1a27824a16824682cad5299d82
BLAKE2b-256 01068769d4200b4b29bfe174f2e5b7aeff1fecef8c0b9c7b605f829d3c941776

See more details on using hashes here.

Provenance

The following attestation bundles were made for mt5cli-1.2.4-py3-none-any.whl:

Publisher: release.yml on dceoy/mt5cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page