Utils Module

mt5cli.utils

Utility constants, types, and functions for the mt5cli package.

DATETIME_TYPE module-attribute

DATETIME_TYPE = _DateTimeType()

REQUEST_TYPE module-attribute

REQUEST_TYPE = _RequestType()

TICK_FLAGS_TYPE module-attribute

TICK_FLAGS_TYPE = _TickFlagsType()

TICK_FLAG_MAP module-attribute

TICK_FLAG_MAP: dict[str, int] = dict(COPY_TICKS_MAP)

TIMEFRAME_NAMES module-attribute

TIMEFRAME_NAMES: tuple[str, ...] = tuple(
    name
    for name in TIMEFRAME_MAP
    if not startswith("TIMEFRAME_")
)

TIMEFRAME_TYPE module-attribute

TIMEFRAME_TYPE = _TimeframeType()

Dataset

Bases: StrEnum

Datasets supported by the collect-history command.

history_deals class-attribute instance-attribute

history_deals = 'history-deals'

history_orders class-attribute instance-attribute

history_orders = 'history-orders'

rates class-attribute instance-attribute

rates = 'rates'

table_name property

table_name: str

Return the SQLite table name for this dataset.

ticks class-attribute instance-attribute

ticks = 'ticks'

IfExists

Bases: StrEnum

SQLite table conflict behavior for the collect-history command.

APPEND class-attribute instance-attribute

APPEND = 'append'

FAIL class-attribute instance-attribute

FAIL = 'fail'

REPLACE class-attribute instance-attribute

REPLACE = 'replace'

LogLevel

Bases: StrEnum

Logging verbosity levels.

DEBUG class-attribute instance-attribute

DEBUG = 'DEBUG'

ERROR class-attribute instance-attribute

ERROR = 'ERROR'

INFO class-attribute instance-attribute

INFO = 'INFO'

WARNING class-attribute instance-attribute

WARNING = 'WARNING'

OutputFormat

Bases: StrEnum

Supported output file formats.

csv class-attribute instance-attribute

csv = 'csv'

json class-attribute instance-attribute

json = 'json'

parquet class-attribute instance-attribute

parquet = 'parquet'

sqlite3 class-attribute instance-attribute

sqlite3 = 'sqlite3'

coerce_login

coerce_login(login: int | str | None) -> int | None

Coerce a login value to int, treating empty strings as unset.

Returns:

Type Description
int | None

Integer login, or None when unset or an empty string.

Source code in mt5cli/utils.py
def coerce_login(login: int | str | None) -> int | None:
    """Coerce a login value to int, treating empty strings as unset.

    Returns:
        Integer login, or None when unset or an empty string.
    """
    if login is None or isinstance(login, int):
        return login
    text = login.strip()
    if not text:
        return None
    return int(text)

detect_format

detect_format(
    output_path: Path, explicit_format: str | None = None
) -> str

Detect the output format from a file extension or explicit format string.

Parameters:

Name Type Description Default
output_path Path

Path to the output file.

required
explicit_format str | None

Explicitly specified format, if any.

None

Returns:

Type Description
str

The detected format string.

Raises:

Type Description
ValueError

If the format cannot be determined.

Source code in mt5cli/utils.py
def detect_format(
    output_path: Path,
    explicit_format: str | None = None,
) -> str:
    """Detect the output format from a file extension or explicit format string.

    Args:
        output_path: Path to the output file.
        explicit_format: Explicitly specified format, if any.

    Returns:
        The detected format string.

    Raises:
        ValueError: If the format cannot be determined.
    """
    if explicit_format is not None:
        return explicit_format
    suffix = output_path.suffix.lower()
    if suffix in _FORMAT_EXTENSIONS:
        return _FORMAT_EXTENSIONS[suffix]
    msg = (
        f"Cannot detect format from extension '{suffix}'."
        " Use --format to specify the output format."
    )
    raise ValueError(msg)

export_dataframe

export_dataframe(
    df: DataFrame,
    output_path: Path,
    output_format: str,
    table_name: str = "data",
) -> None

Export a pandas DataFrame to the specified file format.

Parameters:

Name Type Description Default
df DataFrame

DataFrame to export.

required
output_path Path

Path to the output file.

required
output_format str

Output format (csv, json, parquet, or sqlite3).

required
table_name str

Table name for SQLite3 output.

'data'

Raises:

Type Description
ValueError

If the output format is not supported.

Source code in mt5cli/utils.py
def export_dataframe(
    df: pd.DataFrame,
    output_path: Path,
    output_format: str,
    table_name: str = "data",
) -> None:
    """Export a pandas DataFrame to the specified file format.

    Args:
        df: DataFrame to export.
        output_path: Path to the output file.
        output_format: Output format (csv, json, parquet, or sqlite3).
        table_name: Table name for SQLite3 output.

    Raises:
        ValueError: If the output format is not supported.
    """
    if output_format == "csv":
        df.to_csv(output_path, index=False)
    elif output_format == "json":
        df.to_json(
            output_path,
            orient="records",
            date_format="iso",
            indent=2,
        )
    elif output_format == "parquet":
        df.to_parquet(output_path, index=False)
    elif output_format == "sqlite3":
        export_dataframe_to_sqlite(
            df,
            output_path,
            table_name,
            if_exists=IfExists.REPLACE,
            index=False,
        )
    else:
        msg = f"Unsupported output format: {output_format}"
        raise ValueError(msg)

export_dataframe_to_sqlite

export_dataframe_to_sqlite(
    df: DataFrame,
    output_path: Path,
    table_name: str = "data",
    *,
    if_exists: IfExists = APPEND,
    index: bool = False,
    index_label: str | None = None,
    deduplicate_on: Sequence[str] | None = None,
) -> None

Write a DataFrame to SQLite with configurable append and deduplication.

Parameters:

Name Type Description Default
df DataFrame

DataFrame to export.

required
output_path Path

SQLite database path.

required
table_name str

Target table name.

'data'
if_exists IfExists

Conflict behavior when the table already exists.

APPEND
index bool

Whether to write the DataFrame index as a column.

False
index_label str | None

Column name for the index when index=True.

None
deduplicate_on Sequence[str] | None

Optional key columns to deduplicate after writing, keeping the latest ROWID per key group. Deduplication scans the full table, so repeated appends cost O(table size); index the key columns when appending frequently.

None
Source code in mt5cli/utils.py
def export_dataframe_to_sqlite(
    df: pd.DataFrame,
    output_path: Path,
    table_name: str = "data",
    *,
    if_exists: IfExists = IfExists.APPEND,
    index: bool = False,
    index_label: str | None = None,
    deduplicate_on: Sequence[str] | None = None,
) -> None:
    """Write a DataFrame to SQLite with configurable append and deduplication.

    Args:
        df: DataFrame to export.
        output_path: SQLite database path.
        table_name: Target table name.
        if_exists: Conflict behavior when the table already exists.
        index: Whether to write the DataFrame index as a column.
        index_label: Column name for the index when ``index=True``.
        deduplicate_on: Optional key columns to deduplicate after writing,
            keeping the latest ``ROWID`` per key group. Deduplication scans the
            full table, so repeated appends cost O(table size); index the key
            columns when appending frequently.
    """
    with sqlite3.connect(output_path) as conn:
        df.to_sql(  # type: ignore[reportUnknownMemberType]
            table_name,
            conn,
            if_exists=if_exists.value,
            index=index,
            index_label=index_label,
        )
        if deduplicate_on:
            from .history import drop_duplicates_in_table  # noqa: PLC0415

            drop_duplicates_in_table(
                conn.cursor(),
                table_name,
                list(deduplicate_on),
                keep="last",
            )
            conn.commit()

parse_datetime

parse_datetime(value: str) -> datetime

Parse an ISO 8601 datetime string to a timezone-aware datetime.

Parameters:

Name Type Description Default
value str

ISO 8601 datetime string (e.g., '2024-01-01' or '2024-01-01T12:00:00+00:00').

required

Returns:

Type Description
datetime

Parsed datetime with UTC timezone if no timezone is specified.

Raises:

Type Description
ValueError

If the string cannot be parsed.

Source code in mt5cli/utils.py
def parse_datetime(value: str) -> datetime:
    """Parse an ISO 8601 datetime string to a timezone-aware datetime.

    Args:
        value: ISO 8601 datetime string (e.g., '2024-01-01' or
            '2024-01-01T12:00:00+00:00').

    Returns:
        Parsed datetime with UTC timezone if no timezone is specified.

    Raises:
        ValueError: If the string cannot be parsed.
    """
    try:
        dt = datetime.fromisoformat(value)
    except ValueError:
        msg = f"Invalid datetime format: '{value}'. Use ISO 8601 format."
        raise ValueError(msg) from None
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=UTC)
    return dt

parse_request

parse_request(value: str) -> dict[str, Any]

Parse a JSON-formatted order request string or file reference.

Parameters:

Name Type Description Default
value str

JSON object string, or '@path' to read JSON from a file.

required

Returns:

Type Description
dict[str, Any]

Parsed request dictionary.

Raises:

Type Description
ValueError

If the request file cannot be read or the value is not a JSON object.

Source code in mt5cli/utils.py
def parse_request(value: str) -> dict[str, Any]:
    """Parse a JSON-formatted order request string or file reference.

    Args:
        value: JSON object string, or '@path' to read JSON from a file.

    Returns:
        Parsed request dictionary.

    Raises:
        ValueError: If the request file cannot be read or the value is not a
            JSON object.
    """
    if value.startswith("@"):
        path = Path(value[1:])
        try:
            text = path.read_text(encoding="utf-8")
        except (OSError, UnicodeDecodeError) as exc:
            msg = f"Failed to read JSON request file '{path}': {exc}"
            raise ValueError(msg) from exc
    else:
        text = value
    try:
        parsed: object = json.loads(text)
    except json.JSONDecodeError as exc:
        msg = f"Invalid JSON request: {exc}"
        raise ValueError(msg) from exc
    if not _is_request_dict(parsed):
        msg = "Order request must be a JSON object."
        raise ValueError(msg)
    return parsed

parse_tick_flags

parse_tick_flags(value: object) -> int

Parse tick flags string or integer value.

Parameters:

Name Type Description Default
value object

Tick flag name (ALL, INFO, TRADE, COPY_TICKS_*) or integer value.

required

Returns:

Type Description
int

Integer tick flag value compatible with MetaTrader 5 COPY_TICKS_*.

Raises:

Type Description
ValueError

If the flag is invalid.

Source code in mt5cli/utils.py
def parse_tick_flags(value: object) -> int:
    """Parse tick flags string or integer value.

    Args:
        value: Tick flag name (ALL, INFO, TRADE, COPY_TICKS_*) or integer value.

    Returns:
        Integer tick flag value compatible with MetaTrader 5 ``COPY_TICKS_*``.

    Raises:
        ValueError: If the flag is invalid.
    """
    try:
        return _parse_copy_ticks(value)
    except ValueError:
        display = value if isinstance(value, str) else repr(value)
        valid = ", ".join(_TICK_FLAG_NAMES)
        msg = (
            f"Invalid tick flags: '{display}'. "
            f"Use one of: {valid}, or a supported integer."
        )
        raise ValueError(msg) from None

parse_timeframe

parse_timeframe(value: object) -> int

Parse a timeframe string or integer value.

Parameters:

Name Type Description Default
value object

Timeframe name (e.g., 'M1', 'H1', 'D1') or integer value.

required

Returns:

Type Description
int

Integer timeframe value.

Raises:

Type Description
ValueError

If the timeframe is invalid.

Source code in mt5cli/utils.py
def parse_timeframe(value: object) -> int:
    """Parse a timeframe string or integer value.

    Args:
        value: Timeframe name (e.g., 'M1', 'H1', 'D1') or integer value.

    Returns:
        Integer timeframe value.

    Raises:
        ValueError: If the timeframe is invalid.
    """
    try:
        return _parse_timeframe(value)
    except ValueError:
        display = value if isinstance(value, str) else repr(value)
        valid = ", ".join(TIMEFRAME_NAMES)
        msg = (
            f"Invalid timeframe: '{display}'. "
            f"Use one of: {valid}, or a supported integer."
        )
        raise ValueError(msg) from None