SDK Module

mt5cli.sdk

Programmatic SDK for MetaTrader 5 data collection.

T module-attribute

T = TypeVar('T')

UpdateHistoryBackend module-attribute

UpdateHistoryBackend = Callable[..., None]

__all__ module-attribute

__all__ = [
    "AccountSpec",
    "Mt5CliClient",
    "ThrottledHistoryUpdater",
    "account_info",
    "build_config",
    "collect_history",
    "collect_latest_closed_rates_by_granularity",
    "collect_latest_closed_rates_for_accounts",
    "collect_latest_rates",
    "collect_latest_rates_for_accounts",
    "collect_latest_rates_for_accounts_with_retries",
    "connected_client",
    "copy_rates_from",
    "copy_rates_from_pos",
    "copy_rates_range",
    "copy_ticks_from",
    "copy_ticks_range",
    "fetch_latest_closed_rates",
    "history_deals",
    "history_orders",
    "last_error",
    "latest_rates",
    "market_book",
    "minimum_margins",
    "mt5_session",
    "mt5_summary",
    "mt5_summary_as_df",
    "orders",
    "positions",
    "recent_history_deals",
    "recent_ticks",
    "resolve_account_spec",
    "resolve_account_specs",
    "substitute_env_placeholders",
    "symbol_info",
    "symbol_info_tick",
    "symbols",
    "terminal_info",
    "update_history",
    "update_history_with_config",
    "version",
]

logger module-attribute

logger = getLogger(__name__)

AccountSpec dataclass

AccountSpec(
    symbols: Sequence[str],
    login: int | str | None = None,
    password: str | None = None,
    server: str | None = None,
    path: str | None = None,
    timeout: int | None = None,
)

Connection parameters and symbols for one MT5 account group.

Attributes:

Name Type Description
symbols Sequence[str]

Symbols to load latest rates for under this account.

login int | str | None

Trading account login. String values are coerced to int when non-empty.

password str | None

Trading account password.

server str | None

Trading server name.

path str | None

Path to the MetaTrader5 terminal EXE file.

timeout int | None

Connection timeout in milliseconds.

login class-attribute instance-attribute

login: int | str | None = field(default=None, repr=False)

password class-attribute instance-attribute

password: str | None = field(default=None, repr=False)

path class-attribute instance-attribute

path: str | None = None

server class-attribute instance-attribute

server: str | None = None

symbols instance-attribute

symbols: Sequence[str]

timeout class-attribute instance-attribute

timeout: int | None = None

Mt5CliClient

Mt5CliClient(
    *,
    path: str | None = None,
    login: int | None = None,
    password: str | None = None,
    server: str | None = None,
    timeout: int | None = None,
    retry_count: int = 3,
    config: Mt5Config | None = None,
    client: Mt5DataClient | None = None,
)

Programmatic client for read-only MetaTrader 5 data access.

Initialize the SDK client.

Parameters:

Name Type Description Default
path str | None

Path to MetaTrader5 terminal EXE file.

None
login int | None

Trading account login.

None
password str | None

Trading account password.

None
server str | None

Trading server name.

None
timeout int | None

Connection timeout in milliseconds.

None
retry_count int

Number of MT5 initialization retries for sessions opened by this client.

3
config Mt5Config | None

Optional pre-built Mt5Config (overrides other args).

None
client Mt5DataClient | None

Optional already-connected Mt5DataClient. Injected clients are reused as-is and are not initialized or shut down.

None
Source code in mt5cli/sdk.py
def __init__(
    self,
    *,
    path: str | None = None,
    login: int | None = None,
    password: str | None = None,
    server: str | None = None,
    timeout: int | None = None,
    retry_count: int = 3,
    config: Mt5Config | None = None,
    client: Mt5DataClient | None = None,
) -> None:
    """Initialize the SDK client.

    Args:
        path: Path to MetaTrader5 terminal EXE file.
        login: Trading account login.
        password: Trading account password.
        server: Trading server name.
        timeout: Connection timeout in milliseconds.
        retry_count: Number of MT5 initialization retries for sessions
            opened by this client.
        config: Optional pre-built ``Mt5Config`` (overrides other args).
        client: Optional already-connected ``Mt5DataClient``. Injected
            clients are reused as-is and are not initialized or shut down.
    """
    self._config = config or build_config(
        path=path,
        login=login,
        password=password,
        server=server,
        timeout=timeout,
    )
    self._retry_count = retry_count
    self._client = client
    self._owns_client = client is None

config property

config: Mt5Config

Return the underlying MT5 configuration.

__enter__

__enter__() -> Self

Open a persistent MT5 connection for multiple calls.

Returns:

Type Description
Self

This client instance.

Source code in mt5cli/sdk.py
def __enter__(self) -> Self:
    """Open a persistent MT5 connection for multiple calls.

    Returns:
        This client instance.
    """
    if self._client is not None:
        return self
    client = Mt5DataClient(config=self._config, retry_count=self._retry_count)
    try:
        client.initialize_and_login_mt5()
    except Exception:
        client.shutdown()
        raise
    self._client = client
    self._owns_client = True  # only set when this method created the client
    return self

__exit__

__exit__(
    exc_type: type[BaseException] | None,
    exc: BaseException | None,
    tb: object,
) -> None

Shut down the persistent MT5 connection.

Source code in mt5cli/sdk.py
def __exit__(
    self,
    exc_type: type[BaseException] | None,
    exc: BaseException | None,
    tb: object,
) -> None:
    """Shut down the persistent MT5 connection."""
    if self._client is not None and self._owns_client:
        self._client.shutdown()
        self._client = None

account_info

account_info() -> DataFrame

Return account information.

Source code in mt5cli/sdk.py
def account_info(self) -> pd.DataFrame:
    """Return account information."""
    return self._fetch(lambda c: c.account_info_as_df())

collect_latest_rates

collect_latest_rates(
    symbols: Sequence[str],
    timeframes: Sequence[int | str],
    *,
    count: int,
    start_pos: int = 0,
) -> dict[tuple[str, int], DataFrame]

Return latest rates for each symbol/timeframe pair.

Returns:

Type Description
dict[tuple[str, int], DataFrame]

Mapping keyed by (symbol, timeframe_int).

Raises:

Type Description
ValueError

If count is not positive or inputs are empty.

Source code in mt5cli/sdk.py
def collect_latest_rates(
    self,
    symbols: Sequence[str],
    timeframes: Sequence[int | str],
    *,
    count: int,
    start_pos: int = 0,
) -> dict[tuple[str, int], pd.DataFrame]:
    """Return latest rates for each symbol/timeframe pair.

    Returns:
        Mapping keyed by ``(symbol, timeframe_int)``.

    Raises:
        ValueError: If ``count`` is not positive or inputs are empty.
    """
    _require_positive(count, "count")
    if not symbols:
        msg = "At least one symbol is required."
        raise ValueError(msg)
    if not timeframes:
        msg = "At least one timeframe is required."
        raise ValueError(msg)
    resolved_timeframes = [_coerce_timeframe(timeframe) for timeframe in timeframes]
    return self._fetch_value(
        lambda c: {
            (symbol, timeframe): c.copy_rates_from_pos_as_df(
                symbol=symbol,
                timeframe=timeframe,
                start_pos=start_pos,
                count=count,
            )
            for symbol in symbols
            for timeframe in resolved_timeframes
        },
    )

copy_rates_from

copy_rates_from(
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    count: int,
) -> DataFrame

Return rates starting from a date.

Source code in mt5cli/sdk.py
def copy_rates_from(
    self,
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    count: int,
) -> pd.DataFrame:
    """Return rates starting from a date."""
    tf = _coerce_timeframe(timeframe)
    start = _require_datetime(date_from)
    return self._fetch(
        lambda c: c.copy_rates_from_as_df(
            symbol=symbol,
            timeframe=tf,
            date_from=start,
            count=count,
        ),
    )

copy_rates_from_pos

copy_rates_from_pos(
    symbol: str,
    timeframe: int | str,
    start_pos: int,
    count: int,
) -> DataFrame

Return rates starting from a bar position.

Source code in mt5cli/sdk.py
def copy_rates_from_pos(
    self,
    symbol: str,
    timeframe: int | str,
    start_pos: int,
    count: int,
) -> pd.DataFrame:
    """Return rates starting from a bar position."""
    tf = _coerce_timeframe(timeframe)
    return self._fetch(
        lambda c: c.copy_rates_from_pos_as_df(
            symbol=symbol,
            timeframe=tf,
            start_pos=start_pos,
            count=count,
        ),
    )

copy_rates_range

copy_rates_range(
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    date_to: datetime | str,
) -> DataFrame

Return rates for a date range.

Source code in mt5cli/sdk.py
def copy_rates_range(
    self,
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    date_to: datetime | str,
) -> pd.DataFrame:
    """Return rates for a date range."""
    tf = _coerce_timeframe(timeframe)
    start = _require_datetime(date_from)
    end = _require_datetime(date_to)
    return self._fetch(
        lambda c: c.copy_rates_range_as_df(
            symbol=symbol,
            timeframe=tf,
            date_from=start,
            date_to=end,
        ),
    )

copy_ticks_from

copy_ticks_from(
    symbol: str,
    date_from: datetime | str,
    count: int,
    flags: int | str,
) -> DataFrame

Return ticks starting from a date.

Source code in mt5cli/sdk.py
def copy_ticks_from(
    self,
    symbol: str,
    date_from: datetime | str,
    count: int,
    flags: int | str,
) -> pd.DataFrame:
    """Return ticks starting from a date."""
    start = _require_datetime(date_from)
    tick_flags = _coerce_tick_flags(flags)
    return self._fetch(
        lambda c: c.copy_ticks_from_as_df(
            symbol=symbol,
            date_from=start,
            count=count,
            flags=tick_flags,
        ),
    )

copy_ticks_range

copy_ticks_range(
    symbol: str,
    date_from: datetime | str,
    date_to: datetime | str,
    flags: int | str,
) -> DataFrame

Return ticks for a date range.

Source code in mt5cli/sdk.py
def copy_ticks_range(
    self,
    symbol: str,
    date_from: datetime | str,
    date_to: datetime | str,
    flags: int | str,
) -> pd.DataFrame:
    """Return ticks for a date range."""
    start = _require_datetime(date_from)
    end = _require_datetime(date_to)
    tick_flags = _coerce_tick_flags(flags)
    return self._fetch(
        lambda c: c.copy_ticks_range_as_df(
            symbol=symbol,
            date_from=start,
            date_to=end,
            flags=tick_flags,
        ),
    )

from_connected_client classmethod

from_connected_client(client: Mt5DataClient) -> Self

Bind to an already-connected Mt5DataClient without owning it.

The returned Mt5CliClient never initializes or shuts down the injected client, including when used as a context manager.

Returns:

Type Description
Self

Client wrapper bound to the injected connection.

Source code in mt5cli/sdk.py
@classmethod
def from_connected_client(cls, client: Mt5DataClient) -> Self:
    """Bind to an already-connected ``Mt5DataClient`` without owning it.

    The returned ``Mt5CliClient`` never initializes or shuts down the
    injected client, including when used as a context manager.

    Returns:
        Client wrapper bound to the injected connection.
    """
    return cls(client=client)

history_deals

history_deals(
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
) -> DataFrame

Return historical deals.

Source code in mt5cli/sdk.py
def history_deals(
    self,
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
) -> pd.DataFrame:
    """Return historical deals."""
    start = _coerce_datetime(date_from)
    end = _coerce_datetime(date_to)
    return self._fetch(
        lambda c: c.history_deals_get_as_df(
            date_from=start,
            date_to=end,
            group=group,
            symbol=symbol,
            ticket=ticket,
            position=position,
        ),
    )

history_orders

history_orders(
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
) -> DataFrame

Return historical orders.

Source code in mt5cli/sdk.py
def history_orders(
    self,
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
) -> pd.DataFrame:
    """Return historical orders."""
    start = _coerce_datetime(date_from)
    end = _coerce_datetime(date_to)
    return self._fetch(
        lambda c: c.history_orders_get_as_df(
            date_from=start,
            date_to=end,
            group=group,
            symbol=symbol,
            ticket=ticket,
            position=position,
        ),
    )

last_error

last_error() -> DataFrame

Return the last error information.

Source code in mt5cli/sdk.py
def last_error(self) -> pd.DataFrame:
    """Return the last error information."""
    return self._fetch(lambda c: c.last_error_as_df())

latest_rates

latest_rates(
    symbol: str,
    timeframe: int | str,
    count: int,
    start_pos: int = 0,
) -> DataFrame

Return the latest rates from a bar position.

Source code in mt5cli/sdk.py
def latest_rates(
    self,
    symbol: str,
    timeframe: int | str,
    count: int,
    start_pos: int = 0,
) -> pd.DataFrame:
    """Return the latest rates from a bar position."""
    _require_positive(count, "count")
    return self.copy_rates_from_pos(symbol, timeframe, start_pos, count)

market_book

market_book(symbol: str) -> DataFrame

Return market depth for a symbol.

Source code in mt5cli/sdk.py
def market_book(self, symbol: str) -> pd.DataFrame:
    """Return market depth for a symbol."""
    return self._fetch(lambda c: c.market_book_get_as_df(symbol=symbol))

minimum_margins

minimum_margins(symbol: str) -> DataFrame

Return minimum-volume buy and sell margin requirements.

Parameters:

Name Type Description Default
symbol str

Symbol name.

required

Returns:

Type Description
DataFrame

One-row DataFrame with columns symbol, account_currency,

DataFrame

volume_min, buy_margin, and sell_margin.

Source code in mt5cli/sdk.py
def minimum_margins(self, symbol: str) -> pd.DataFrame:
    """Return minimum-volume buy and sell margin requirements.

    Args:
        symbol: Symbol name.

    Returns:
        One-row DataFrame with columns ``symbol``, ``account_currency``,
        ``volume_min``, ``buy_margin``, and ``sell_margin``.
    """
    return self._fetch(lambda c: _fetch_minimum_margins(c, symbol))

mt5_summary

mt5_summary() -> dict[str, object]

Return a compact terminal/account status summary.

Source code in mt5cli/sdk.py
def mt5_summary(self) -> dict[str, object]:
    """Return a compact terminal/account status summary."""

    def _summary(client: Mt5DataClient) -> dict[str, object]:
        return {
            "version": _plain_mt5_value(
                _call_required_client_method(client, "version"),
            ),
            "terminal_info": _plain_mt5_value(
                _call_required_client_method(client, "terminal_info"),
            ),
            "account_info": _plain_mt5_value(
                _call_required_client_method(client, "account_info"),
            ),
            "symbols_total": _plain_mt5_value(
                _call_required_client_method(client, "symbols_total"),
            ),
        }

    return self._fetch_value(_summary)

mt5_summary_as_df

mt5_summary_as_df() -> DataFrame

Return an export-safe one-row terminal/account summary DataFrame.

Source code in mt5cli/sdk.py
def mt5_summary_as_df(self) -> pd.DataFrame:
    """Return an export-safe one-row terminal/account summary DataFrame."""
    summary = self.mt5_summary()
    return pd.DataFrame(
        [
            {
                key: _mt5_summary_export_value(value)
                for key, value in summary.items()
            },
        ],
    )

orders

orders(
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
) -> DataFrame

Return active orders.

Source code in mt5cli/sdk.py
def orders(
    self,
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
) -> pd.DataFrame:
    """Return active orders."""
    return self._fetch(
        lambda c: c.orders_get_as_df(
            symbol=symbol,
            group=group,
            ticket=ticket,
        ),
    )

positions

positions(
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
) -> DataFrame

Return open positions.

Source code in mt5cli/sdk.py
def positions(
    self,
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
) -> pd.DataFrame:
    """Return open positions."""
    return self._fetch(
        lambda c: c.positions_get_as_df(
            symbol=symbol,
            group=group,
            ticket=ticket,
        ),
    )

recent_history_deals

recent_history_deals(
    hours: float,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
) -> DataFrame

Return historical deals from a recent trailing window.

Source code in mt5cli/sdk.py
def recent_history_deals(
    self,
    hours: float,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
) -> pd.DataFrame:
    """Return historical deals from a recent trailing window."""
    _require_positive(hours, "hours")
    end = _require_datetime(date_to) if date_to is not None else datetime.now(UTC)
    start = end - timedelta(hours=hours)
    return self.history_deals(
        date_from=start,
        date_to=end,
        group=group,
        symbol=symbol,
    )

recent_ticks

recent_ticks(
    symbol: str,
    seconds: float,
    *,
    date_to: datetime | str | None = None,
    count: int = 10000,
    flags: int | str = "ALL",
) -> DataFrame

Return ticks from a recent time window.

Parameters:

Name Type Description Default
symbol str

Symbol name.

required
seconds float

Lookback window in seconds ending at date_to.

required
date_to datetime | str | None

Window end time. When None, uses the latest symbol_info_tick().time rather than wall-clock now.

None
count int

Maximum ticks to return. Values <= 0 return the full window without trimming. Positive values keep the most recent ticks; when the window is sparse, copy_ticks_from avoids fetching the entire range.

10000
flags int | str

Tick flags as ALL, INFO, TRADE, or an integer.

'ALL'

Returns:

Type Description
DataFrame

Tick DataFrame with MT5 tick columns such as time, bid,

DataFrame

ask, last, and volume.

Source code in mt5cli/sdk.py
def recent_ticks(
    self,
    symbol: str,
    seconds: float,
    *,
    date_to: datetime | str | None = None,
    count: int = 10000,
    flags: int | str = "ALL",
) -> pd.DataFrame:
    """Return ticks from a recent time window.

    Args:
        symbol: Symbol name.
        seconds: Lookback window in seconds ending at ``date_to``.
        date_to: Window end time. When ``None``, uses the latest
            ``symbol_info_tick().time`` rather than wall-clock now.
        count: Maximum ticks to return. Values ``<= 0`` return the full
            window without trimming. Positive values keep the most recent
            ticks; when the window is sparse, ``copy_ticks_from`` avoids
            fetching the entire range.
        flags: Tick flags as ``ALL``, ``INFO``, ``TRADE``, or an integer.

    Returns:
        Tick DataFrame with MT5 tick columns such as ``time``, ``bid``,
        ``ask``, ``last``, and ``volume``.
    """
    tick_flags = _coerce_tick_flags(flags)
    end = _coerce_datetime(date_to)
    return self._fetch(
        lambda c: _fetch_recent_ticks(
            c,
            symbol,
            seconds,
            end,
            count,
            tick_flags,
        ),
    )

symbol_info

symbol_info(symbol: str) -> DataFrame

Return details for one symbol.

Source code in mt5cli/sdk.py
def symbol_info(self, symbol: str) -> pd.DataFrame:
    """Return details for one symbol."""
    return self._fetch(lambda c: c.symbol_info_as_df(symbol=symbol))

symbol_info_tick

symbol_info_tick(symbol: str) -> DataFrame

Return the last tick for a symbol.

Source code in mt5cli/sdk.py
def symbol_info_tick(self, symbol: str) -> pd.DataFrame:
    """Return the last tick for a symbol."""
    return self._fetch(lambda c: c.symbol_info_tick_as_df(symbol=symbol))

symbols

symbols(group: str | None = None) -> DataFrame

Return the symbol list.

Source code in mt5cli/sdk.py
def symbols(self, group: str | None = None) -> pd.DataFrame:
    """Return the symbol list."""
    return self._fetch(lambda c: c.symbols_get_as_df(group=group))

terminal_info

terminal_info() -> DataFrame

Return terminal information.

Source code in mt5cli/sdk.py
def terminal_info(self) -> pd.DataFrame:
    """Return terminal information."""
    return self._fetch(lambda c: c.terminal_info_as_df())

version

version() -> DataFrame

Return MetaTrader5 version information.

Source code in mt5cli/sdk.py
def version(self) -> pd.DataFrame:
    """Return MetaTrader5 version information."""
    return self._fetch(lambda c: c.version_as_df())

ThrottledHistoryUpdater

ThrottledHistoryUpdater(
    *,
    output: Path | str,
    datasets: set[Dataset] | None = None,
    timeframes: Sequence[int | str] | None = None,
    flags: int | str = "ALL",
    lookback_hours: float = 24.0,
    with_views: bool = False,
    include_account_events: bool = True,
    interval_seconds: float = 0.0,
    suppress_errors: bool = False,
    update_backend: UpdateHistoryBackend | None = None,
)

Throttled incremental SQLite history updater for long-running apps.

Wraps :func:update_history (or a custom update_backend) with a minimum interval between successful updates, so a tight application loop can call :meth:update every iteration without re-fetching MT5 history more often than desired. Timing uses a monotonic clock, so it is unaffected by wall-clock changes.

Downstream applications may pass update_backend to substitute the default :func:update_history implementation—for example to add application-specific logging, metrics, or test doubles—without monkey- patching mt5cli.sdk.update_history.

Initialize the throttled updater.

Parameters:

Name Type Description Default
output Path | str

SQLite database path.

required
datasets set[Dataset] | None

Datasets to include (defaults to all).

None
timeframes Sequence[int | str] | None

Rate timeframes to update (defaults to all fixed MT5 timeframes).

None
flags int | str

Tick copy flags as integer or name (e.g. ALL).

'ALL'
lookback_hours float

First-run lookback when a table has no prior rows.

24.0
with_views bool

Create cash_events and positions_reconstructed views.

False
include_account_events bool

Include account-level cash events.

True
interval_seconds float

Minimum seconds between successful updates. Values <= 0 update on every call.

0.0
suppress_errors bool

When True, recoverable errors (Mt5TradingError, Mt5RuntimeError, sqlite3.Error, ValueError, OSError, and MT5 client capability AttributeError / TypeError for history API methods) raised during an update are swallowed and :meth:update returns False without advancing the throttle. Other AttributeError / TypeError values always propagate. When False (default), recoverable errors propagate so callers control logging.

False
update_backend UpdateHistoryBackend | None

Callable invoked instead of :func:update_history when :meth:update runs. Receives the same keyword arguments as :func:update_history (client, output, symbols, datasets, timeframes, flags, lookback_hours, with_views, include_account_events). Defaults to :func:update_history.

None
Source code in mt5cli/sdk.py
def __init__(
    self,
    *,
    output: Path | str,
    datasets: set[Dataset] | None = None,
    timeframes: Sequence[int | str] | None = None,
    flags: int | str = "ALL",
    lookback_hours: float = 24.0,
    with_views: bool = False,
    include_account_events: bool = True,
    interval_seconds: float = 0.0,
    suppress_errors: bool = False,
    update_backend: UpdateHistoryBackend | None = None,
) -> None:
    """Initialize the throttled updater.

    Args:
        output: SQLite database path.
        datasets: Datasets to include (defaults to all).
        timeframes: Rate timeframes to update (defaults to all fixed MT5
            timeframes).
        flags: Tick copy flags as integer or name (e.g. ``ALL``).
        lookback_hours: First-run lookback when a table has no prior rows.
        with_views: Create ``cash_events`` and ``positions_reconstructed``
            views.
        include_account_events: Include account-level cash events.
        interval_seconds: Minimum seconds between successful updates. Values
            ``<= 0`` update on every call.
        suppress_errors: When True, recoverable errors (``Mt5TradingError``,
            ``Mt5RuntimeError``, ``sqlite3.Error``, ``ValueError``,
            ``OSError``, and MT5 client capability ``AttributeError`` /
            ``TypeError`` for history API methods) raised during an update
            are swallowed and :meth:`update` returns False without advancing
            the throttle. Other ``AttributeError`` / ``TypeError`` values
            always propagate. When False (default), recoverable errors
            propagate so callers control logging.
        update_backend: Callable invoked instead of :func:`update_history`
            when :meth:`update` runs. Receives the same keyword arguments as
            :func:`update_history` (``client``, ``output``, ``symbols``,
            ``datasets``, ``timeframes``, ``flags``, ``lookback_hours``,
            ``with_views``, ``include_account_events``). Defaults to
            :func:`update_history`.
    """
    self.output = output
    self.datasets = datasets
    self.timeframes = timeframes
    self.flags = flags
    self.lookback_hours = lookback_hours
    self.with_views = with_views
    self.include_account_events = include_account_events
    self.interval_seconds = interval_seconds
    self.suppress_errors = suppress_errors
    self.update_backend = (
        update_history if update_backend is None else update_backend
    )
    self._last_update_monotonic: float | None = None

datasets instance-attribute

datasets = datasets

flags instance-attribute

flags = flags

include_account_events instance-attribute

include_account_events = include_account_events

interval_seconds instance-attribute

interval_seconds = interval_seconds

last_update_monotonic property

last_update_monotonic: float | None

Return the monotonic timestamp of the last successful update.

lookback_hours instance-attribute

lookback_hours = lookback_hours

output instance-attribute

output = output

suppress_errors instance-attribute

suppress_errors = suppress_errors

timeframes instance-attribute

timeframes = timeframes

update_backend instance-attribute

update_backend = (
    update_history
    if update_backend is None
    else update_backend
)

with_views instance-attribute

with_views = with_views

should_update

should_update() -> bool

Return whether enough time has elapsed to run another update.

Returns:

Type Description
bool

True when interval_seconds <= 0, when no update has succeeded

bool

yet, or when at least interval_seconds have elapsed since the

bool

last successful update.

Source code in mt5cli/sdk.py
def should_update(self) -> bool:
    """Return whether enough time has elapsed to run another update.

    Returns:
        True when ``interval_seconds <= 0``, when no update has succeeded
        yet, or when at least ``interval_seconds`` have elapsed since the
        last successful update.
    """
    if self.interval_seconds <= 0 or self._last_update_monotonic is None:
        return True
    return (time.monotonic() - self._last_update_monotonic) >= self.interval_seconds

update

update(
    client: Mt5DataClient, symbols: Sequence[str]
) -> bool

Run a throttled incremental history update.

Parameters:

Name Type Description Default
client Mt5DataClient

Connected MT5 data client.

required
symbols Sequence[str]

Symbols to update.

required

Returns:

Type Description
bool

True if an update ran successfully, False if it was throttled or

bool

(when suppress_errors is True) failed with a recoverable error.

bool

When suppress_errors is False, recoverable update failures

bool

propagate to the caller.

Raises:

Type Description
AttributeError

MT5 client capability mismatch when suppress_errors is False, or any other attribute error.

TypeError

MT5 client capability mismatch when suppress_errors is False, or any other type error.

Source code in mt5cli/sdk.py
def update(self, client: Mt5DataClient, symbols: Sequence[str]) -> bool:
    """Run a throttled incremental history update.

    Args:
        client: Connected MT5 data client.
        symbols: Symbols to update.

    Returns:
        True if an update ran successfully, False if it was throttled or
        (when ``suppress_errors`` is True) failed with a recoverable error.
        When ``suppress_errors`` is False, recoverable update failures
        propagate to the caller.

    Raises:
        AttributeError: MT5 client capability mismatch when
            ``suppress_errors`` is False, or any other attribute error.
        TypeError: MT5 client capability mismatch when ``suppress_errors``
            is False, or any other type error.
    """
    if not self.should_update():
        return False
    try:
        _resolve_update_history_request(
            output=self.output,
            symbols=symbols,
            datasets=self.datasets,
            timeframes=self.timeframes,
            flags=self.flags,
            lookback_hours=self.lookback_hours,
            date_to=None,
        )
        self.update_backend(
            client=client,
            output=self.output,
            symbols=symbols,
            datasets=self.datasets,
            timeframes=self.timeframes,
            flags=self.flags,
            lookback_hours=self.lookback_hours,
            with_views=self.with_views,
            include_account_events=self.include_account_events,
        )
    except _RECOVERABLE_HISTORY_UPDATE_ERRORS:
        if self.suppress_errors:
            logger.warning("Suppressed history update error", exc_info=True)
            return False
        raise
    except (AttributeError, TypeError) as exc:
        if self.suppress_errors and _is_mt5_client_capability_error(exc):
            logger.warning("Suppressed history update error", exc_info=True)
            return False
        raise
    self._last_update_monotonic = time.monotonic()
    return True

account_info

account_info(
    *, config: Mt5Config | None = None
) -> DataFrame

Return account information.

Source code in mt5cli/sdk.py
def account_info(*, config: Mt5Config | None = None) -> pd.DataFrame:
    """Return account information."""
    return _make_client(config=config).account_info()

build_config

build_config(
    *,
    path: str | None = None,
    login: int | None = None,
    password: str | None = None,
    server: str | None = None,
    timeout: int | None = None,
) -> Mt5Config

Build an Mt5Config from optional connection parameters.

Returns:

Type Description
Mt5Config

Configured Mt5Config instance.

Source code in mt5cli/sdk.py
def build_config(
    *,
    path: str | None = None,
    login: int | None = None,
    password: str | None = None,
    server: str | None = None,
    timeout: int | None = None,
) -> Mt5Config:
    """Build an ``Mt5Config`` from optional connection parameters.

    Returns:
        Configured ``Mt5Config`` instance.
    """
    return Mt5Config(
        path=path,
        login=login,
        password=password,
        server=server,
        timeout=timeout,
    )

collect_history

collect_history(
    output: Path,
    symbols: list[str],
    date_from: datetime | str,
    date_to: datetime | str,
    *,
    datasets: set[Dataset] | None = None,
    timeframe: int | str = 1,
    flags: int | str = "ALL",
    if_exists: IfExists = FAIL,
    with_views: bool = False,
    config: Mt5Config | None = None,
) -> None

Collect historical datasets into a single SQLite database.

Parameters:

Name Type Description Default
output Path

SQLite database path.

required
symbols list[str]

Symbols to collect.

required
date_from datetime | str

Start date.

required
date_to datetime | str

End date.

required
datasets set[Dataset] | None

Datasets to include (defaults to all).

None
timeframe int | str

Rates timeframe as integer or name (e.g. M1).

1
flags int | str

Tick copy flags as integer or name (e.g. ALL).

'ALL'
if_exists IfExists

Behavior when a target table already exists.

FAIL
with_views bool

Create cash_events and positions_reconstructed views.

False
config Mt5Config | None

MT5 connection configuration.

None
Source code in mt5cli/sdk.py
def collect_history(
    output: Path,
    symbols: list[str],
    date_from: datetime | str,
    date_to: datetime | str,
    *,
    datasets: set[Dataset] | None = None,
    timeframe: int | str = 1,
    flags: int | str = "ALL",
    if_exists: IfExists = IfExists.FAIL,
    with_views: bool = False,
    config: Mt5Config | None = None,
) -> None:
    """Collect historical datasets into a single SQLite database.

    Args:
        output: SQLite database path.
        symbols: Symbols to collect.
        date_from: Start date.
        date_to: End date.
        datasets: Datasets to include (defaults to all).
        timeframe: Rates timeframe as integer or name (e.g. ``M1``).
        flags: Tick copy flags as integer or name (e.g. ``ALL``).
        if_exists: Behavior when a target table already exists.
        with_views: Create ``cash_events`` and ``positions_reconstructed`` views.
        config: MT5 connection configuration.
    """
    start = _require_datetime(date_from)
    end = _require_datetime(date_to)
    selected = datasets if datasets is not None else set(Dataset)
    tf = _coerce_timeframe(timeframe)
    tick_flags = _coerce_tick_flags(flags)
    mt5_config = config or build_config()
    with connected_client(mt5_config) as client, sqlite3.connect(output) as conn:
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA synchronous=NORMAL")
        written_tables, written_columns = write_collected_datasets(
            conn,
            client,
            symbols,
            selected,
            tf,
            tick_flags,
            start,
            end,
            if_exists,
        )
        create_history_indexes(conn, written_columns)
        if with_views and Dataset.history_deals in written_tables:
            create_cash_events_view(conn, written_columns[Dataset.history_deals])
            create_positions_reconstructed_view(
                conn,
                written_columns[Dataset.history_deals],
            )
        elif with_views:
            logger.warning(
                "--with-views ignored: history_deals table was not written",
            )
    logger.info(
        "Collected %s for %d symbol(s) into %s",
        ", ".join(sorted(ds.value for ds in selected)),
        len(symbols),
        output,
    )

collect_latest_closed_rates_by_granularity

collect_latest_closed_rates_by_granularity(
    accounts: Sequence[AccountSpec],
    granularities: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
    retry_count: int = 0,
    backoff_base: float = 2.0,
) -> dict[tuple[str, str], DataFrame]

Collect latest closed rate bars keyed by symbol and granularity name.

Thin wrapper around :func:collect_latest_closed_rates_for_accounts that rekeys the result by granularity name (for example M1) instead of the integer timeframe.

Parameters:

Name Type Description Default
accounts Sequence[AccountSpec]

Account groups to read. Each must define at least one symbol.

required
granularities Sequence[int | str]

MT5 timeframes as integers or names (for example M1).

required
count int

Number of closed bars to return per symbol/timeframe.

required
start_pos int

Initial bar position offset passed to the underlying collector.

0
base_config Mt5Config | None

Optional base configuration whose fields fill any value not set on an individual account.

None
retry_count int

Maximum number of retries after the first attempt. 0 disables retries.

0
backoff_base float

Base for exponential backoff between retry attempts.

2.0

Returns:

Type Description
dict[tuple[str, str], DataFrame]

Mapping keyed by (symbol, granularity_name). Propagates

dict[tuple[str, str], DataFrame]

ValueError from :func:collect_latest_closed_rates_for_accounts.

Source code in mt5cli/sdk.py
def collect_latest_closed_rates_by_granularity(
    accounts: Sequence[AccountSpec],
    granularities: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
    retry_count: int = 0,
    backoff_base: float = 2.0,
) -> dict[tuple[str, str], pd.DataFrame]:
    """Collect latest closed rate bars keyed by symbol and granularity name.

    Thin wrapper around :func:`collect_latest_closed_rates_for_accounts` that
    rekeys the result by granularity name (for example ``M1``) instead of the
    integer timeframe.

    Args:
        accounts: Account groups to read. Each must define at least one symbol.
        granularities: MT5 timeframes as integers or names (for example ``M1``).
        count: Number of closed bars to return per symbol/timeframe.
        start_pos: Initial bar position offset passed to the underlying collector.
        base_config: Optional base configuration whose fields fill any value not
            set on an individual account.
        retry_count: Maximum number of retries after the first attempt. ``0``
            disables retries.
        backoff_base: Base for exponential backoff between retry attempts.

    Returns:
        Mapping keyed by ``(symbol, granularity_name)``. Propagates
        ``ValueError`` from :func:`collect_latest_closed_rates_for_accounts`.
    """
    loaded = collect_latest_closed_rates_for_accounts(
        accounts,
        granularities,
        count,
        start_pos=start_pos,
        base_config=base_config,
        retry_count=retry_count,
        backoff_base=backoff_base,
    )
    return {
        (symbol, resolve_granularity_name(timeframe)): frame
        for (symbol, timeframe), frame in loaded.items()
    }

collect_latest_closed_rates_for_accounts

collect_latest_closed_rates_for_accounts(
    accounts: Sequence[AccountSpec],
    timeframes: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
    retry_count: int = 0,
    backoff_base: float = 2.0,
) -> dict[tuple[str, int], DataFrame]

Collect latest closed rate bars across multiple MT5 account groups.

When start_pos is 0 (the default), MetaTrader 5 includes the still-forming current bar as the last row. This helper fetches count + 1 bars, drops that bar with :func:drop_forming_rate_bar, and validates that each resulting frame is non-empty. When start_pos is greater than zero the forming bar is not in range, so only count bars are fetched and no row is dropped.

Wraps :func:collect_latest_rates_for_accounts_with_retries for transient MT5 error handling.

Parameters:

Name Type Description Default
accounts Sequence[AccountSpec]

Account groups to read. Each must define at least one symbol.

required
timeframes Sequence[int | str]

MT5 timeframes as integers or names (for example M1).

required
count int

Number of closed bars to return per symbol/timeframe.

required
start_pos int

Initial bar position offset passed to the underlying collector.

0
base_config Mt5Config | None

Optional base configuration whose fields fill any value not set on an individual account.

None
retry_count int

Maximum number of retries after the first attempt. 0 disables retries.

0
backoff_base float

Base for exponential backoff between retry attempts.

2.0

Returns:

Type Description
dict[tuple[str, int], DataFrame]

Mapping keyed by (symbol, timeframe_int).

Raises:

Type Description
ValueError

If inputs are invalid, or any series is empty (after dropping the still-forming bar when start_pos is 0).

Source code in mt5cli/sdk.py
def collect_latest_closed_rates_for_accounts(
    accounts: Sequence[AccountSpec],
    timeframes: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
    retry_count: int = 0,
    backoff_base: float = 2.0,
) -> dict[tuple[str, int], pd.DataFrame]:
    """Collect latest closed rate bars across multiple MT5 account groups.

    When ``start_pos`` is ``0`` (the default), MetaTrader 5 includes the
    still-forming current bar as the last row. This helper fetches
    ``count + 1`` bars, drops that bar with :func:`drop_forming_rate_bar`, and
    validates that each resulting frame is non-empty. When ``start_pos`` is
    greater than zero the forming bar is not in range, so only ``count`` bars
    are fetched and no row is dropped.

    Wraps :func:`collect_latest_rates_for_accounts_with_retries` for transient
    MT5 error handling.

    Args:
        accounts: Account groups to read. Each must define at least one symbol.
        timeframes: MT5 timeframes as integers or names (for example ``M1``).
        count: Number of closed bars to return per symbol/timeframe.
        start_pos: Initial bar position offset passed to the underlying collector.
        base_config: Optional base configuration whose fields fill any value not
            set on an individual account.
        retry_count: Maximum number of retries after the first attempt. ``0``
            disables retries.
        backoff_base: Base for exponential backoff between retry attempts.

    Returns:
        Mapping keyed by ``(symbol, timeframe_int)``.

    Raises:
        ValueError: If inputs are invalid, or any series is empty (after
            dropping the still-forming bar when ``start_pos`` is ``0``).
    """
    _require_positive(count, "count")
    _require_non_negative(start_pos, "start_pos")
    fetch_count = count + 1 if start_pos == 0 else count
    loaded = collect_latest_rates_for_accounts_with_retries(
        accounts,
        timeframes,
        fetch_count,
        start_pos=start_pos,
        base_config=base_config,
        retry_count=retry_count,
        backoff_base=backoff_base,
    )
    result: dict[tuple[str, int], pd.DataFrame] = {}
    for key, df_rate in loaded.items():
        closed = drop_forming_rate_bar(df_rate) if start_pos == 0 else df_rate
        if closed.empty:
            symbol, timeframe = key
            msg = f"Rate data is empty for {symbol!r} at timeframe {timeframe}."
            raise ValueError(msg)
        result[key] = closed
    return result

collect_latest_rates

collect_latest_rates(
    symbols: Sequence[str],
    timeframes: Sequence[int | str],
    *,
    count: int,
    start_pos: int = 0,
    config: Mt5Config | None = None,
) -> dict[tuple[str, int], DataFrame]

Return latest rates for each symbol/timeframe pair.

Source code in mt5cli/sdk.py
def collect_latest_rates(
    symbols: Sequence[str],
    timeframes: Sequence[int | str],
    *,
    count: int,
    start_pos: int = 0,
    config: Mt5Config | None = None,
) -> dict[tuple[str, int], pd.DataFrame]:
    """Return latest rates for each symbol/timeframe pair."""
    return _make_client(config=config).collect_latest_rates(
        symbols,
        timeframes,
        count=count,
        start_pos=start_pos,
    )

collect_latest_rates_for_accounts

collect_latest_rates_for_accounts(
    accounts: Sequence[AccountSpec],
    timeframes: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
) -> dict[tuple[str, int], DataFrame]

Collect latest rates across multiple MT5 account groups.

Each account is connected in turn, its symbols are read for every timeframe, and the resulting frames are merged into a single mapping.

Parameters:

Name Type Description Default
accounts Sequence[AccountSpec]

Account groups to read. Each must define at least one symbol.

required
timeframes Sequence[int | str]

MT5 timeframes as integers or names (for example M1).

required
count int

Number of most recent bars to read per symbol/timeframe.

required
start_pos int

Initial bar position offset.

0
base_config Mt5Config | None

Optional base configuration whose fields fill any value not set on an individual account.

None

Returns:

Type Description
dict[tuple[str, int], DataFrame]

Mapping keyed by (symbol, timeframe_int). When accounts share a

dict[tuple[str, int], DataFrame]

symbol/timeframe pair, the last account processed wins.

Raises:

Type Description
ValueError

If accounts, timeframes, or any account's symbols are empty, or count is not positive.

Source code in mt5cli/sdk.py
def collect_latest_rates_for_accounts(
    accounts: Sequence[AccountSpec],
    timeframes: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
) -> dict[tuple[str, int], pd.DataFrame]:
    """Collect latest rates across multiple MT5 account groups.

    Each account is connected in turn, its symbols are read for every
    timeframe, and the resulting frames are merged into a single mapping.

    Args:
        accounts: Account groups to read. Each must define at least one symbol.
        timeframes: MT5 timeframes as integers or names (for example ``M1``).
        count: Number of most recent bars to read per symbol/timeframe.
        start_pos: Initial bar position offset.
        base_config: Optional base configuration whose fields fill any value not
            set on an individual account.

    Returns:
        Mapping keyed by ``(symbol, timeframe_int)``. When accounts share a
        symbol/timeframe pair, the last account processed wins.

    Raises:
        ValueError: If ``accounts``, ``timeframes``, or any account's symbols are
            empty, or ``count`` is not positive.
    """
    account_list = list(accounts)
    if not account_list:
        msg = "At least one account is required."
        raise ValueError(msg)
    if not timeframes:
        msg = "At least one timeframe is required."
        raise ValueError(msg)
    if any(not account.symbols for account in account_list):
        msg = "Each account requires at least one symbol."
        raise ValueError(msg)
    _require_positive(count, "count")
    result: dict[tuple[str, int], pd.DataFrame] = {}
    for account in account_list:
        config = _build_account_config(account, base_config)
        with Mt5CliClient(config=config) as client:
            result.update(
                client.collect_latest_rates(
                    account.symbols,
                    timeframes,
                    count=count,
                    start_pos=start_pos,
                ),
            )
    return result

collect_latest_rates_for_accounts_with_retries

collect_latest_rates_for_accounts_with_retries(
    accounts: Sequence[AccountSpec],
    timeframes: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
    retry_count: int = 0,
    backoff_base: float = 2.0,
) -> dict[tuple[str, int], DataFrame]

Collect latest rates across accounts, retrying transient MT5 failures.

Wraps :func:collect_latest_rates_for_accounts with bounded exponential backoff. Only pdmt5.Mt5TradingError and pdmt5.Mt5RuntimeError are retried; other exceptions propagate immediately. The final failure is re-raised once retries are exhausted.

Parameters:

Name Type Description Default
accounts Sequence[AccountSpec]

Account groups to read. Each must define at least one symbol.

required
timeframes Sequence[int | str]

MT5 timeframes as integers or names (for example M1).

required
count int

Number of most recent bars to read per symbol/timeframe.

required
start_pos int

Initial bar position offset.

0
base_config Mt5Config | None

Optional base configuration whose fields fill any value not set on an individual account.

None
retry_count int

Maximum number of retries after the first attempt. 0 disables retries.

0
backoff_base float

Base for exponential backoff. The delay before retry attempt n (1-indexed) is backoff_base ** n seconds.

2.0

Returns:

Type Description
dict[tuple[str, int], DataFrame]

Mapping keyed by (symbol, timeframe_int). Propagates ValueError

dict[tuple[str, int], DataFrame]

for invalid inputs (see :func:collect_latest_rates_for_accounts) and

dict[tuple[str, int], DataFrame]

re-raises the last pdmt5.Mt5TradingError or pdmt5.Mt5RuntimeError

dict[tuple[str, int], DataFrame]

once retries are exhausted.

Source code in mt5cli/sdk.py
def collect_latest_rates_for_accounts_with_retries(
    accounts: Sequence[AccountSpec],
    timeframes: Sequence[int | str],
    count: int,
    *,
    start_pos: int = 0,
    base_config: Mt5Config | None = None,
    retry_count: int = 0,
    backoff_base: float = 2.0,
) -> dict[tuple[str, int], pd.DataFrame]:
    """Collect latest rates across accounts, retrying transient MT5 failures.

    Wraps :func:`collect_latest_rates_for_accounts` with bounded exponential
    backoff. Only ``pdmt5.Mt5TradingError`` and ``pdmt5.Mt5RuntimeError`` are
    retried; other exceptions propagate immediately. The final failure is
    re-raised once retries are exhausted.

    Args:
        accounts: Account groups to read. Each must define at least one symbol.
        timeframes: MT5 timeframes as integers or names (for example ``M1``).
        count: Number of most recent bars to read per symbol/timeframe.
        start_pos: Initial bar position offset.
        base_config: Optional base configuration whose fields fill any value not
            set on an individual account.
        retry_count: Maximum number of retries after the first attempt. ``0``
            disables retries.
        backoff_base: Base for exponential backoff. The delay before retry
            attempt ``n`` (1-indexed) is ``backoff_base ** n`` seconds.

    Returns:
        Mapping keyed by ``(symbol, timeframe_int)``. Propagates ``ValueError``
        for invalid inputs (see :func:`collect_latest_rates_for_accounts`) and
        re-raises the last ``pdmt5.Mt5TradingError`` or ``pdmt5.Mt5RuntimeError``
        once retries are exhausted.
    """

    def _collect() -> dict[tuple[str, int], pd.DataFrame]:
        return collect_latest_rates_for_accounts(
            accounts,
            timeframes,
            count,
            start_pos=start_pos,
            base_config=base_config,
        )

    return retry_with_backoff(
        _collect,
        retry_count=retry_count,
        backoff_base=backoff_base,
        operation="Rate collection",
    )

connected_client

connected_client(
    config: Mt5Config,
) -> Iterator[Mt5DataClient]

Initialize MT5, yield a connected client, and always shut down.

Parameters:

Name Type Description Default
config Mt5Config

MT5 connection configuration.

required

Yields:

Type Description
Mt5DataClient

Connected Mt5DataClient instance.

Source code in mt5cli/sdk.py
@contextmanager
def connected_client(config: Mt5Config) -> Iterator[Mt5DataClient]:
    """Initialize MT5, yield a connected client, and always shut down.

    Args:
        config: MT5 connection configuration.

    Yields:
        Connected ``Mt5DataClient`` instance.
    """
    client = Mt5DataClient(config=config)
    try:
        client.initialize_and_login_mt5()
        yield client
    finally:
        client.shutdown()

copy_rates_from

copy_rates_from(
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    count: int,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return rates starting from a date.

Source code in mt5cli/sdk.py
def copy_rates_from(
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    count: int,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return rates starting from a date."""
    return _make_client(config=config).copy_rates_from(
        symbol,
        timeframe,
        date_from,
        count,
    )

copy_rates_from_pos

copy_rates_from_pos(
    symbol: str,
    timeframe: int | str,
    start_pos: int,
    count: int,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return rates starting from a bar position.

Source code in mt5cli/sdk.py
def copy_rates_from_pos(
    symbol: str,
    timeframe: int | str,
    start_pos: int,
    count: int,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return rates starting from a bar position."""
    return _make_client(config=config).copy_rates_from_pos(
        symbol,
        timeframe,
        start_pos,
        count,
    )

copy_rates_range

copy_rates_range(
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    date_to: datetime | str,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return rates for a date range.

Source code in mt5cli/sdk.py
def copy_rates_range(
    symbol: str,
    timeframe: int | str,
    date_from: datetime | str,
    date_to: datetime | str,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return rates for a date range."""
    return _make_client(config=config).copy_rates_range(
        symbol,
        timeframe,
        date_from,
        date_to,
    )

copy_ticks_from

copy_ticks_from(
    symbol: str,
    date_from: datetime | str,
    count: int,
    flags: int | str,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return ticks starting from a date.

Source code in mt5cli/sdk.py
def copy_ticks_from(
    symbol: str,
    date_from: datetime | str,
    count: int,
    flags: int | str,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return ticks starting from a date."""
    return _make_client(config=config).copy_ticks_from(
        symbol,
        date_from,
        count,
        flags,
    )

copy_ticks_range

copy_ticks_range(
    symbol: str,
    date_from: datetime | str,
    date_to: datetime | str,
    flags: int | str,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return ticks for a date range.

Source code in mt5cli/sdk.py
def copy_ticks_range(
    symbol: str,
    date_from: datetime | str,
    date_to: datetime | str,
    flags: int | str,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return ticks for a date range."""
    return _make_client(config=config).copy_ticks_range(
        symbol,
        date_from,
        date_to,
        flags,
    )

fetch_latest_closed_rates

fetch_latest_closed_rates(
    client: Mt5CliClient,
    *,
    symbol: str,
    granularity: str,
    count: int,
) -> DataFrame

Fetch up to count most recent closed bars, oldest to newest.

Returns:

Type Description
DataFrame

Closed rate bars ordered oldest to newest.

Raises:

Type Description
ValueError

If count is not positive or no closed bars are returned.

Source code in mt5cli/sdk.py
def fetch_latest_closed_rates(
    client: Mt5CliClient,
    *,
    symbol: str,
    granularity: str,
    count: int,
) -> pd.DataFrame:
    """Fetch up to ``count`` most recent closed bars, oldest to newest.

    Returns:
        Closed rate bars ordered oldest to newest.

    Raises:
        ValueError: If ``count`` is not positive or no closed bars are returned.
    """
    _require_positive(count, "count")
    frame = client.latest_rates(symbol, granularity, count + 1, start_pos=0)
    closed = drop_forming_rate_bar(frame)
    if closed.empty:
        msg = (
            f"Rate data is empty for {symbol!r} at granularity {granularity!r} "
            f"with count {count}."
        )
        raise ValueError(msg)
    return closed.tail(count).reset_index(drop=True)

history_deals

history_deals(
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return historical deals.

Source code in mt5cli/sdk.py
def history_deals(
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return historical deals."""
    return _make_client(config=config).history_deals(
        date_from=date_from,
        date_to=date_to,
        group=group,
        symbol=symbol,
        ticket=ticket,
        position=position,
    )

history_orders

history_orders(
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return historical orders.

Source code in mt5cli/sdk.py
def history_orders(
    date_from: datetime | str | None = None,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    ticket: int | None = None,
    position: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return historical orders."""
    return _make_client(config=config).history_orders(
        date_from=date_from,
        date_to=date_to,
        group=group,
        symbol=symbol,
        ticket=ticket,
        position=position,
    )

last_error

last_error(*, config: Mt5Config | None = None) -> DataFrame

Return the last error information.

Source code in mt5cli/sdk.py
def last_error(*, config: Mt5Config | None = None) -> pd.DataFrame:
    """Return the last error information."""
    return _make_client(config=config).last_error()

latest_rates

latest_rates(
    symbol: str,
    timeframe: int | str,
    count: int,
    start_pos: int = 0,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return the latest rates from a bar position.

Source code in mt5cli/sdk.py
def latest_rates(
    symbol: str,
    timeframe: int | str,
    count: int,
    start_pos: int = 0,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return the latest rates from a bar position."""
    return _make_client(config=config).latest_rates(
        symbol,
        timeframe,
        count,
        start_pos=start_pos,
    )

market_book

market_book(
    symbol: str, *, config: Mt5Config | None = None
) -> DataFrame

Return market depth for a symbol.

Source code in mt5cli/sdk.py
def market_book(
    symbol: str,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return market depth for a symbol."""
    return _make_client(config=config).market_book(symbol)

minimum_margins

minimum_margins(
    symbol: str, *, config: Mt5Config | None = None
) -> DataFrame

Return minimum-volume buy and sell margin requirements.

See Mt5CliClient.minimum_margins for return details.

Source code in mt5cli/sdk.py
def minimum_margins(
    symbol: str,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return minimum-volume buy and sell margin requirements.

    See ``Mt5CliClient.minimum_margins`` for return details.
    """
    return _make_client(config=config).minimum_margins(symbol)

mt5_session

mt5_session(
    config: Mt5Config | None = None,
) -> Iterator[Mt5CliClient]

Open an MT5 terminal session and yield a connected client.

Launches the MetaTrader 5 terminal using Mt5Config.path (when set), logs in, yields a connected :class:Mt5CliClient, and always shuts the terminal down on exit.

Parameters:

Name Type Description Default
config Mt5Config | None

MT5 connection configuration. Defaults to an empty config that attaches to a running terminal.

None

Yields:

Type Description
Mt5CliClient

Connected Mt5CliClient bound to the session.

Source code in mt5cli/sdk.py
@contextmanager
def mt5_session(config: Mt5Config | None = None) -> Iterator[Mt5CliClient]:
    """Open an MT5 terminal session and yield a connected client.

    Launches the MetaTrader 5 terminal using ``Mt5Config.path`` (when set),
    logs in, yields a connected :class:`Mt5CliClient`, and always shuts the
    terminal down on exit.

    Args:
        config: MT5 connection configuration. Defaults to an empty config that
            attaches to a running terminal.

    Yields:
        Connected ``Mt5CliClient`` bound to the session.
    """
    mt5_config = config or build_config()
    with connected_client(mt5_config) as client:
        yield Mt5CliClient.from_connected_client(client)

mt5_summary

mt5_summary(
    *, config: Mt5Config | None = None
) -> dict[str, object]

Return a compact terminal/account status summary.

Source code in mt5cli/sdk.py
def mt5_summary(*, config: Mt5Config | None = None) -> dict[str, object]:
    """Return a compact terminal/account status summary."""
    return _make_client(config=config).mt5_summary()

mt5_summary_as_df

mt5_summary_as_df(
    *, config: Mt5Config | None = None
) -> DataFrame

Return an export-safe terminal/account status summary DataFrame.

Source code in mt5cli/sdk.py
def mt5_summary_as_df(*, config: Mt5Config | None = None) -> pd.DataFrame:
    """Return an export-safe terminal/account status summary DataFrame."""
    return _make_client(config=config).mt5_summary_as_df()

orders

orders(
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return active orders.

Source code in mt5cli/sdk.py
def orders(
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return active orders."""
    return _make_client(config=config).orders(
        symbol=symbol,
        group=group,
        ticket=ticket,
    )

positions

positions(
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return open positions.

Source code in mt5cli/sdk.py
def positions(
    symbol: str | None = None,
    group: str | None = None,
    ticket: int | None = None,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return open positions."""
    return _make_client(config=config).positions(
        symbol=symbol,
        group=group,
        ticket=ticket,
    )

recent_history_deals

recent_history_deals(
    hours: float,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return historical deals from a recent trailing window.

Source code in mt5cli/sdk.py
def recent_history_deals(
    hours: float,
    date_to: datetime | str | None = None,
    group: str | None = None,
    symbol: str | None = None,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return historical deals from a recent trailing window."""
    return _make_client(config=config).recent_history_deals(
        hours,
        date_to=date_to,
        group=group,
        symbol=symbol,
    )

recent_ticks

recent_ticks(
    symbol: str,
    seconds: float,
    *,
    date_to: datetime | str | None = None,
    count: int = 10000,
    flags: int | str = "ALL",
    config: Mt5Config | None = None,
) -> DataFrame

Return ticks from a recent time window ending at date_to or now.

See Mt5CliClient.recent_ticks for parameter and return details.

Source code in mt5cli/sdk.py
def recent_ticks(
    symbol: str,
    seconds: float,
    *,
    date_to: datetime | str | None = None,
    count: int = 10000,
    flags: int | str = "ALL",
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return ticks from a recent time window ending at ``date_to`` or now.

    See ``Mt5CliClient.recent_ticks`` for parameter and return details.
    """
    return _make_client(config=config).recent_ticks(
        symbol,
        seconds,
        date_to=date_to,
        count=count,
        flags=flags,
    )

resolve_account_spec

resolve_account_spec(
    account: AccountSpec,
    *,
    login: int | str | None = None,
    password: str | None = None,
    server: str | None = None,
    path: str | None = None,
    timeout: int | None = None,
) -> AccountSpec

Resolve an account's credentials from overrides and ${ENV_VAR} values.

Explicit override arguments take precedence over the corresponding :class:AccountSpec fields. The resolved string fields (login, password, server, path) have any ${ENV_VAR} placeholders substituted from the environment.

Parameters:

Name Type Description Default
account AccountSpec

Source account specification.

required
login int | str | None

Optional explicit login override.

None
password str | None

Optional explicit password override.

None
server str | None

Optional explicit server override.

None
path str | None

Optional explicit terminal path override.

None
timeout int | None

Optional explicit connection timeout override.

None

Returns:

Type Description
AccountSpec

A new :class:AccountSpec with resolved credentials and the original

AccountSpec

symbols preserved. Raises ValueError (via

AccountSpec

func:substitute_env_placeholders) if a referenced environment

AccountSpec

variable is not set.

Source code in mt5cli/sdk.py
def resolve_account_spec(
    account: AccountSpec,
    *,
    login: int | str | None = None,
    password: str | None = None,
    server: str | None = None,
    path: str | None = None,
    timeout: int | None = None,
) -> AccountSpec:
    """Resolve an account's credentials from overrides and ``${ENV_VAR}`` values.

    Explicit override arguments take precedence over the corresponding
    :class:`AccountSpec` fields. The resolved string fields (``login``,
    ``password``, ``server``, ``path``) have any ``${ENV_VAR}`` placeholders
    substituted from the environment.

    Args:
        account: Source account specification.
        login: Optional explicit login override.
        password: Optional explicit password override.
        server: Optional explicit server override.
        path: Optional explicit terminal path override.
        timeout: Optional explicit connection timeout override.

    Returns:
        A new :class:`AccountSpec` with resolved credentials and the original
        symbols preserved. Raises ``ValueError`` (via
        :func:`substitute_env_placeholders`) if a referenced environment
        variable is not set.
    """
    return AccountSpec(
        symbols=account.symbols,
        login=_resolve_login(login, account.login),
        password=_resolve_field(password, account.password),
        server=_resolve_field(server, account.server),
        path=_resolve_field(path, account.path),
        timeout=timeout if timeout is not None else account.timeout,
    )

resolve_account_specs

resolve_account_specs(
    accounts: Sequence[AccountSpec],
    *,
    login: int | str | None = None,
    password: str | None = None,
    server: str | None = None,
    path: str | None = None,
    timeout: int | None = None,
) -> list[AccountSpec]

Resolve credentials for multiple accounts.

Applies the same overrides and ${ENV_VAR} substitution as :func:resolve_account_spec to every account.

Parameters:

Name Type Description Default
accounts Sequence[AccountSpec]

Source account specifications.

required
login int | str | None

Optional explicit login override applied to each account.

None
password str | None

Optional explicit password override applied to each account.

None
server str | None

Optional explicit server override applied to each account.

None
path str | None

Optional explicit terminal path override applied to each account.

None
timeout int | None

Optional explicit timeout override applied to each account.

None

Returns:

Type Description
list[AccountSpec]

Resolved account specifications in the original order. Raises

list[AccountSpec]

ValueError (via :func:substitute_env_placeholders) if a referenced

list[AccountSpec]

environment variable is not set.

Source code in mt5cli/sdk.py
def resolve_account_specs(
    accounts: Sequence[AccountSpec],
    *,
    login: int | str | None = None,
    password: str | None = None,
    server: str | None = None,
    path: str | None = None,
    timeout: int | None = None,
) -> list[AccountSpec]:
    """Resolve credentials for multiple accounts.

    Applies the same overrides and ``${ENV_VAR}`` substitution as
    :func:`resolve_account_spec` to every account.

    Args:
        accounts: Source account specifications.
        login: Optional explicit login override applied to each account.
        password: Optional explicit password override applied to each account.
        server: Optional explicit server override applied to each account.
        path: Optional explicit terminal path override applied to each account.
        timeout: Optional explicit timeout override applied to each account.

    Returns:
        Resolved account specifications in the original order. Raises
        ``ValueError`` (via :func:`substitute_env_placeholders`) if a referenced
        environment variable is not set.
    """
    return [
        resolve_account_spec(
            account,
            login=login,
            password=password,
            server=server,
            path=path,
            timeout=timeout,
        )
        for account in accounts
    ]

substitute_env_placeholders

substitute_env_placeholders(value: str) -> str

Replace ${ENV_VAR} placeholders in a string with environment values.

Parameters:

Name Type Description Default
value str

String that may contain one or more ${ENV_VAR} placeholders.

required

Returns:

Type Description
str

The string with every placeholder replaced by its environment value.

Raises:

Type Description
ValueError

If a referenced environment variable is not set.

Source code in mt5cli/sdk.py
def substitute_env_placeholders(value: str) -> str:
    """Replace ``${ENV_VAR}`` placeholders in a string with environment values.

    Args:
        value: String that may contain one or more ``${ENV_VAR}`` placeholders.

    Returns:
        The string with every placeholder replaced by its environment value.

    Raises:
        ValueError: If a referenced environment variable is not set.
    """
    parts: list[str] = []
    last_end = 0
    for match in _ENV_PLACEHOLDER_PATTERN.finditer(value):
        parts.append(value[last_end : match.start()])
        name = match.group("name")
        if name not in os.environ:
            msg = f"Environment variable {name!r} is not set."
            raise ValueError(msg)
        parts.append(os.environ[name])
        last_end = match.end()
    parts.append(value[last_end:])
    return "".join(parts)

symbol_info

symbol_info(
    symbol: str, *, config: Mt5Config | None = None
) -> DataFrame

Return details for one symbol.

Source code in mt5cli/sdk.py
def symbol_info(
    symbol: str,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return details for one symbol."""
    return _make_client(config=config).symbol_info(symbol)

symbol_info_tick

symbol_info_tick(
    symbol: str, *, config: Mt5Config | None = None
) -> DataFrame

Return the last tick for a symbol.

Source code in mt5cli/sdk.py
def symbol_info_tick(
    symbol: str,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return the last tick for a symbol."""
    return _make_client(config=config).symbol_info_tick(symbol)

symbols

symbols(
    group: str | None = None,
    *,
    config: Mt5Config | None = None,
) -> DataFrame

Return the symbol list.

Source code in mt5cli/sdk.py
def symbols(
    group: str | None = None,
    *,
    config: Mt5Config | None = None,
) -> pd.DataFrame:
    """Return the symbol list."""
    return _make_client(config=config).symbols(group=group)

terminal_info

terminal_info(
    *, config: Mt5Config | None = None
) -> DataFrame

Return terminal information.

Source code in mt5cli/sdk.py
def terminal_info(*, config: Mt5Config | None = None) -> pd.DataFrame:
    """Return terminal information."""
    return _make_client(config=config).terminal_info()

update_history

update_history(
    *,
    client: Mt5DataClient,
    output: Path | str,
    symbols: Sequence[str],
    datasets: set[Dataset] | None = None,
    timeframes: Sequence[int | str] | None = None,
    flags: int | str = "ALL",
    lookback_hours: float = 24.0,
    date_to: datetime | str | None = None,
    deduplicate: bool = True,
    create_rate_views: bool = True,
    with_views: bool = False,
    include_account_events: bool = True,
) -> None

Incrementally append MT5 history into a SQLite database.

Uses an already-connected Mt5DataClient and does not create or close the MT5 connection. For first-time tables, data is fetched from date_to - lookback_hours. Subsequent runs resume from existing MAX(time) per symbol (and timeframe for rates); when include_account_events=True, account-level deals use a separate cursor over type NOT IN (0, 1) / empty-symbol rows.

Parameters:

Name Type Description Default
client Mt5DataClient

Connected MT5 data client.

required
output Path | str

SQLite database path.

required
symbols Sequence[str]

Symbols to update.

required
datasets set[Dataset] | None

Datasets to include (defaults to all).

None
timeframes Sequence[int | str] | None

Rate timeframes to update (defaults to all fixed MT5 timeframes when None).

None
flags int | str

Tick copy flags as integer or name (e.g. ALL).

'ALL'
lookback_hours float

First-run lookback when a table has no prior rows.

24.0
date_to datetime | str | None

Optional update end datetime. Defaults to now (UTC).

None
deduplicate bool

Remove duplicate rows after append, keeping latest ROWID.

True
create_rate_views bool

Create rate_<symbol>__<timeframe> views.

True
with_views bool

Create cash_events and positions_reconstructed views.

False
include_account_events bool

Include account-level cash events in history_deals when True.

True
Source code in mt5cli/sdk.py
def update_history(  # noqa: PLR0913
    *,
    client: Mt5DataClient,
    output: Path | str,
    symbols: Sequence[str],
    datasets: set[Dataset] | None = None,
    timeframes: Sequence[int | str] | None = None,
    flags: int | str = "ALL",
    lookback_hours: float = 24.0,
    date_to: datetime | str | None = None,
    deduplicate: bool = True,
    create_rate_views: bool = True,
    with_views: bool = False,
    include_account_events: bool = True,
) -> None:
    """Incrementally append MT5 history into a SQLite database.

    Uses an already-connected ``Mt5DataClient`` and does not create or close
    the MT5 connection. For first-time tables, data is fetched from
    ``date_to - lookback_hours``. Subsequent runs resume from existing
    ``MAX(time)`` per symbol (and timeframe for rates); when
    ``include_account_events=True``, account-level deals use a separate cursor
    over ``type NOT IN (0, 1)`` / empty-symbol rows.

    Args:
        client: Connected MT5 data client.
        output: SQLite database path.
        symbols: Symbols to update.
        datasets: Datasets to include (defaults to all).
        timeframes: Rate timeframes to update (defaults to all fixed MT5
            timeframes when None).
        flags: Tick copy flags as integer or name (e.g. ``ALL``).
        lookback_hours: First-run lookback when a table has no prior rows.
        date_to: Optional update end datetime. Defaults to now (UTC).
        deduplicate: Remove duplicate rows after append, keeping latest ROWID.
        create_rate_views: Create ``rate_<symbol>__<timeframe>`` views.
        with_views: Create ``cash_events`` and ``positions_reconstructed`` views.
        include_account_events: Include account-level cash events in
            ``history_deals`` when True.
    """
    request = _resolve_update_history_request(
        output=output,
        symbols=symbols,
        datasets=datasets,
        timeframes=timeframes,
        flags=flags,
        lookback_hours=lookback_hours,
        date_to=date_to,
    )
    if request is None:
        return
    logger.info(
        "Updating history in SQLite: symbols=%s, datasets=%s, path=%s",
        list(symbols),
        sorted(dataset.value for dataset in request.selected),
        request.output_path,
    )
    with sqlite3.connect(request.output_path) as conn:
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA synchronous=NORMAL")
        write_incremental_datasets(
            conn,
            client,
            symbols,
            request.selected,
            request.resolved_timeframes,
            request.resolved_tick_flags,
            request.fallback_start,
            request.end,
            deduplicate=deduplicate,
            create_rate_views=create_rate_views,
            with_views=with_views,
            include_account_events=include_account_events,
        )

update_history_with_config

update_history_with_config(
    *,
    output: Path | str,
    symbols: Sequence[str],
    config: Mt5Config | None = None,
    datasets: set[Dataset] | None = None,
    timeframes: Sequence[int | str] | None = None,
    flags: int | str = "ALL",
    lookback_hours: float = 24.0,
    date_to: datetime | str | None = None,
    deduplicate: bool = True,
    create_rate_views: bool = True,
    with_views: bool = False,
    include_account_events: bool = True,
) -> None

Incrementally append MT5 history, opening and closing the MT5 connection.

Convenience wrapper around :func:update_history for standalone use.

Source code in mt5cli/sdk.py
def update_history_with_config(  # noqa: PLR0913
    *,
    output: Path | str,
    symbols: Sequence[str],
    config: Mt5Config | None = None,
    datasets: set[Dataset] | None = None,
    timeframes: Sequence[int | str] | None = None,
    flags: int | str = "ALL",
    lookback_hours: float = 24.0,
    date_to: datetime | str | None = None,
    deduplicate: bool = True,
    create_rate_views: bool = True,
    with_views: bool = False,
    include_account_events: bool = True,
) -> None:
    """Incrementally append MT5 history, opening and closing the MT5 connection.

    Convenience wrapper around :func:`update_history` for standalone use.
    """
    request = _resolve_update_history_request(
        output=output,
        symbols=symbols,
        datasets=datasets,
        timeframes=timeframes,
        flags=flags,
        lookback_hours=lookback_hours,
        date_to=date_to,
    )
    if request is None:
        return
    mt5_config = config or build_config()
    with connected_client(mt5_config) as client:
        update_history(
            client=client,
            output=output,
            symbols=symbols,
            datasets=datasets,
            timeframes=timeframes,
            flags=flags,
            lookback_hours=lookback_hours,
            date_to=date_to,
            deduplicate=deduplicate,
            create_rate_views=create_rate_views,
            with_views=with_views,
            include_account_events=include_account_events,
        )

version

version(*, config: Mt5Config | None = None) -> DataFrame

Return MetaTrader5 version information.

Source code in mt5cli/sdk.py
def version(*, config: Mt5Config | None = None) -> pd.DataFrame:
    """Return MetaTrader5 version information."""
    return _make_client(config=config).version()

Resilient multi-account orchestration

The SDK ships strategy-agnostic helpers for building long-running collectors on top of the read-only client. None of them depend on a particular trading application.

Retrying transient rate collection

collect_latest_rates_for_accounts_with_retries() wraps collect_latest_rates_for_accounts() with bounded exponential backoff. Only pdmt5.Mt5TradingError and pdmt5.Mt5RuntimeError are retried; the final failure is re-raised once retry_count is exhausted.

from mt5cli import AccountSpec, collect_latest_rates_for_accounts_with_retries

accounts = [AccountSpec(symbols=["EURUSD"], login=12345)]
rates = collect_latest_rates_for_accounts_with_retries(
    accounts,
    ["M1", "H1"],
    count=500,
    retry_count=3,
    backoff_base=2,  # sleeps 2s, 4s, 8s between attempts
)

Latest closed rate bars

MetaTrader 5 start_pos=0 includes the still-forming current bar as the last row. fetch_latest_closed_rates() handles one connected Mt5CliClient; use fetch_latest_closed_rates_for_trading_client() from an active Mt5TradingClient session. Multi-account helpers fetch count + 1 bars, drop that row with drop_forming_rate_bar(), and validate each series is non-empty. Returned frames are ordered oldest-to-newest and may contain fewer than count rows only when MT5 returns fewer closed bars.

from mt5cli import (
    AccountSpec,
    collect_latest_closed_rates_by_granularity,
    fetch_latest_closed_rates,
)

closed = fetch_latest_closed_rates(
    client,
    symbol="EURUSD",
    granularity="M1",
    count=500,
)

rates = collect_latest_closed_rates_by_granularity(
    [AccountSpec(symbols=["EURUSD"], login=12345)],
    ["M1", "H1"],
    count=500,
    retry_count=3,
)
closed_m1 = rates["EURUSD", "M1"]

Use collect_latest_closed_rates_by_granularity() when callers prefer keys such as ("EURUSD", "M1") instead of integer timeframes.

Resolving credentials and ${ENV_VAR} placeholders

resolve_account_spec() / resolve_account_specs() merge explicit override values over AccountSpec fields and expand ${ENV_VAR} placeholders, keeping secrets out of plan/config files. A missing environment variable raises ValueError.

import os

from mt5cli import AccountSpec, resolve_account_specs

os.environ["MT5_LOGIN"] = "12345"
os.environ["MT5_PASSWORD"] = "secret"
accounts = [
    AccountSpec(symbols=["EURUSD"], login="${MT5_LOGIN}", password="${MT5_PASSWORD}")
]

resolved = resolve_account_specs(accounts, server="Broker-Demo")
# resolved[0].login == "12345", resolved[0].server == "Broker-Demo"

Throttled incremental history updates

ThrottledHistoryUpdater wraps update_history() with a minimum interval between successful runs (using a monotonic clock), so an application loop can call it every iteration without over-fetching.

from pdmt5 import Mt5Config, Mt5DataClient

from mt5cli import Dataset, ThrottledHistoryUpdater

updater = ThrottledHistoryUpdater(
    output="history.db",
    datasets={Dataset.rates},
    timeframes=["M1"],
    interval_seconds=60,  # <= 0 updates on every call
)

client = Mt5DataClient(config=Mt5Config(login=12345))
client.initialize_and_login_mt5()
try:
    while True:
        updater.update(client, ["EURUSD", "GBPUSD"])  # no-op until 60s elapse
        # ... do other work; break when shutting down ...
finally:
    client.shutdown()

Pass update_backend to substitute the default update_history implementation without monkey-patching mt5cli.sdk.update_history. The callable receives the same keyword arguments as update_history (client, output, symbols, datasets, timeframes, flags, lookback_hours, with_views, include_account_events). The resolved backend is stored on updater.update_backend for inspection or subclassing.

from mt5cli import ThrottledHistoryUpdater, update_history


def app_update_history(**kwargs) -> None:
    update_history(**kwargs)  # or delegate to application-specific logic


updater = ThrottledHistoryUpdater(
    output="history.db",
    interval_seconds=60,
    update_backend=app_update_history,
)

By default recoverable errors (Mt5TradingError, Mt5RuntimeError, sqlite3.Error, ValueError, OSError, and MT5 client capability AttributeError / TypeError for history API methods) propagate so the caller controls logging; pass suppress_errors=True to swallow them and return False without advancing the throttle. Other AttributeError / TypeError values always propagate. Input validation (_resolve_update_history_request) runs before any MT5 or SQLite calls, but when suppress_errors=True the resulting ValueError is suppressed along with other recoverable errors.

Trading-capable sessions

For order placement and trading calculations, use the dedicated Trading module. The read-only Mt5CliClient and mt5_session() helpers in this module are unchanged.