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] = {
    "ALL": 1,
    "INFO": 2,
    "TRADE": 4,
}

TIMEFRAME_MAP module-attribute

TIMEFRAME_MAP: dict[str, int] = {
    "M1": 1,
    "M2": 2,
    "M3": 3,
    "M4": 4,
    "M5": 5,
    "M6": 6,
    "M10": 10,
    "M12": 12,
    "M15": 15,
    "M20": 20,
    "M30": 30,
    "H1": 16385,
    "H2": 16386,
    "H3": 16387,
    "H4": 16388,
    "H6": 16390,
    "H8": 16392,
    "H12": 16396,
    "D1": 16408,
    "W1": 32769,
    "MN1": 49153,
}

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'

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: str) -> int

Parse tick flags string or integer value.

Parameters:

Name Type Description Default
value str

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

required

Returns:

Type Description
int

Integer tick flag value.

Raises:

Type Description
ValueError

If the flag is invalid.

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

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

    Returns:
        Integer tick flag value.

    Raises:
        ValueError: If the flag is invalid.
    """
    upper = value.upper()
    if upper in TICK_FLAG_MAP:
        return TICK_FLAG_MAP[upper]
    try:
        return int(value)
    except ValueError:
        valid = ", ".join(TICK_FLAG_MAP)
        msg = f"Invalid tick flags: '{value}'. Use one of: {valid}, or an integer."
        raise ValueError(msg) from None

parse_timeframe

parse_timeframe(value: str) -> int

Parse a timeframe string or integer value.

Parameters:

Name Type Description Default
value str

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: str) -> 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.
    """
    upper = value.upper()
    if upper in TIMEFRAME_MAP:
        return TIMEFRAME_MAP[upper]
    try:
        return int(value)
    except ValueError:
        valid = ", ".join(TIMEFRAME_MAP)
        msg = f"Invalid timeframe: '{value}'. Use one of: {valid}, or an integer."
        raise ValueError(msg) from None