This commit is contained in:
@@ -1,12 +1,28 @@
|
||||
# Public API Contract
|
||||
|
||||
mt5cli is the generic MT5 data and execution infrastructure layer for downstream
|
||||
Python applications. The intended dependency direction is:
|
||||
mt5cli is the canonical operational trading SDK and CLI/batch layer over pdmt5.
|
||||
The intended dependency direction is:
|
||||
|
||||
```text
|
||||
downstream app -> mt5cli -> pdmt5 -> MetaTrader 5
|
||||
```
|
||||
|
||||
## Responsibility boundary
|
||||
|
||||
| Layer | Owns |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **pdmt5** | MT5 core wrapper; DataFrame/dict conversion; canonical MT5 constants and parsers; direct low-level order primitives |
|
||||
| **mt5cli** | CLI/batch workflows; SQLite history collection; normalized datasets; closed-bar helpers; small downstream operational SDK; generic broker-facing margin/volume/order orchestration |
|
||||
| **downstream** | Strategy logic; signals; risk policy; backtesting; optimization; YAML/application semantics |
|
||||
|
||||
Downstream code should import raw pdmt5 types and constants (such as
|
||||
`Mt5Config`, `Mt5TradingClient`, `Mt5RuntimeError`, `Mt5TradingError`,
|
||||
`TIMEFRAME_MAP`, `COPY_TICKS_MAP`) directly from `pdmt5` when needed.
|
||||
mt5cli does not serve as a pass-through compatibility namespace for pdmt5.
|
||||
|
||||
Note: the former `mt5cli` re-export `TICK_FLAG_MAP` corresponds to `COPY_TICKS_MAP`
|
||||
in pdmt5 — the name changed, it was not simply moved.
|
||||
|
||||
Downstream packages should import from the package root (`from mt5cli import
|
||||
...`) and use the public tier sets in `mt5cli.contract` to distinguish API
|
||||
stability. CLI commands mirror the same behavior but are not importable Python
|
||||
@@ -133,13 +149,12 @@ sending requests. Failed, malformed, or unknown broker retcodes are fail-closed
|
||||
and returned as `status="failed"` with normalized `request` / `response` details;
|
||||
`dry_run=True` never calls `ensure_symbol_selected()` or `order_send()`.
|
||||
|
||||
### Errors and MT5 type re-exports
|
||||
### Errors
|
||||
|
||||
| Symbol | Role |
|
||||
| ------------------------------------------------------------------------------------ | ----------------------------------------------- |
|
||||
| `Mt5CliError`, `Mt5ConnectionError`, `Mt5OperationError`, `Mt5SchemaError` | Stable mt5cli exception types |
|
||||
| `normalize_mt5_exception`, `call_with_normalized_errors`, `is_recoverable_mt5_error` | Error normalization and retry classification |
|
||||
| `Mt5Config`, `Mt5RuntimeError`, `Mt5TradingClient`, `Mt5TradingError` | Re-exported pdmt5 types for adapter convenience |
|
||||
| Symbol | Role |
|
||||
| ------------------------------------------------------------------------------------ | -------------------------------------------- |
|
||||
| `Mt5CliError`, `Mt5ConnectionError`, `Mt5OperationError`, `Mt5SchemaError` | Stable mt5cli exception types |
|
||||
| `normalize_mt5_exception`, `call_with_normalized_errors`, `is_recoverable_mt5_error` | Error normalization and retry classification |
|
||||
|
||||
## Secondary public exports
|
||||
|
||||
@@ -176,7 +191,7 @@ the MT5 SDK helper.
|
||||
| Export helpers | `detect_format`, `export_dataframe`, `export_dataframe_to_sqlite` |
|
||||
| Symbol parsing | `normalize_symbol`, `normalize_symbols` |
|
||||
| Time parsing | `ensure_utc`, `parse_date_range`, `parse_datetime`, `recent_window` |
|
||||
| MT5 parsing maps | `granularity_name`, `parse_tick_flags`, `parse_timeframe`, `TICK_FLAG_MAP`, `TIMEFRAME_MAP` |
|
||||
| MT5 parsing maps | `granularity_name`, `parse_tick_flags`, `parse_timeframe` |
|
||||
| Trading data shapes | `POSITION_COLUMNS` |
|
||||
|
||||
## CLI commands
|
||||
|
||||
@@ -8,8 +8,6 @@ strategy responsibilities.
|
||||
|
||||
from importlib.metadata import version
|
||||
|
||||
from pdmt5 import Mt5Config, Mt5RuntimeError, Mt5TradingClient, Mt5TradingError
|
||||
|
||||
from .client import MT5Client, build_config, mt5_session
|
||||
from .contract import (
|
||||
PUBLIC_EXPORT_TIERS,
|
||||
@@ -152,8 +150,6 @@ from .trading import (
|
||||
update_trailing_stop_loss_for_open_positions,
|
||||
)
|
||||
from .utils import (
|
||||
TICK_FLAG_MAP,
|
||||
TIMEFRAME_MAP,
|
||||
parse_datetime,
|
||||
parse_tick_flags,
|
||||
parse_timeframe,
|
||||
@@ -169,8 +165,6 @@ __all__ = [
|
||||
"REQUIRED_COLUMNS",
|
||||
"SECONDARY_PUBLIC_EXPORTS",
|
||||
"STABLE_SDK_EXPORTS",
|
||||
"TICK_FLAG_MAP",
|
||||
"TIMEFRAME_MAP",
|
||||
"TIME_COLUMNS",
|
||||
"AccountSpec",
|
||||
"DataKind",
|
||||
@@ -180,13 +174,9 @@ __all__ = [
|
||||
"MT5Client",
|
||||
"MarginVolume",
|
||||
"Mt5CliError",
|
||||
"Mt5Config",
|
||||
"Mt5ConnectionError",
|
||||
"Mt5OperationError",
|
||||
"Mt5RuntimeError",
|
||||
"Mt5SchemaError",
|
||||
"Mt5TradingClient",
|
||||
"Mt5TradingError",
|
||||
"OrderExecutionResult",
|
||||
"OrderFillingMode",
|
||||
"OrderLimits",
|
||||
|
||||
@@ -6,13 +6,9 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
|
||||
"AccountSpec",
|
||||
"MT5Client",
|
||||
"Mt5CliError",
|
||||
"Mt5Config",
|
||||
"Mt5ConnectionError",
|
||||
"Mt5OperationError",
|
||||
"Mt5RuntimeError",
|
||||
"Mt5SchemaError",
|
||||
"Mt5TradingClient",
|
||||
"Mt5TradingError",
|
||||
"OrderFillingMode",
|
||||
"OrderSide",
|
||||
"OrderTimeMode",
|
||||
@@ -93,8 +89,6 @@ SECONDARY_PUBLIC_EXPORTS: frozenset[str] = frozenset({
|
||||
"KNOWN_MT5_TIME_COLUMNS",
|
||||
"POSITION_COLUMNS",
|
||||
"REQUIRED_COLUMNS",
|
||||
"TICK_FLAG_MAP",
|
||||
"TIMEFRAME_MAP",
|
||||
"TIME_COLUMNS",
|
||||
"account_info",
|
||||
"collect_latest_rates",
|
||||
|
||||
+4
-6
@@ -10,7 +10,8 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, TypeGuard
|
||||
|
||||
import click
|
||||
from pdmt5 import COPY_TICKS_MAP, TIMEFRAME_MAP
|
||||
from pdmt5 import COPY_TICKS_MAP as _COPY_TICKS_MAP
|
||||
from pdmt5 import TIMEFRAME_MAP as _TIMEFRAME_MAP
|
||||
from pdmt5 import parse_copy_ticks as _parse_copy_ticks
|
||||
from pdmt5 import parse_timeframe as _parse_timeframe
|
||||
|
||||
@@ -23,14 +24,11 @@ if TYPE_CHECKING:
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Backward-compatible snapshot; prefer ``COPY_TICKS_MAP`` from pdmt5 directly.
|
||||
TICK_FLAG_MAP: dict[str, int] = dict(COPY_TICKS_MAP)
|
||||
|
||||
TIMEFRAME_NAMES: tuple[str, ...] = tuple(
|
||||
name for name in TIMEFRAME_MAP if not name.startswith("TIMEFRAME_")
|
||||
name for name in _TIMEFRAME_MAP if not name.startswith("TIMEFRAME_")
|
||||
)
|
||||
_TICK_FLAG_NAMES: tuple[str, ...] = tuple(
|
||||
name for name in COPY_TICKS_MAP if not name.startswith("COPY_TICKS_")
|
||||
name for name in _COPY_TICKS_MAP if not name.startswith("COPY_TICKS_")
|
||||
)
|
||||
|
||||
_FORMAT_EXTENSIONS: dict[str, str] = {
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ license-files = ["LICENSE"]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11, < 3.14"
|
||||
dependencies = [
|
||||
"pdmt5>=0.3.0",
|
||||
"pdmt5>=1.0.0",
|
||||
"click >= 8.1.0",
|
||||
"typer >= 0.15.0",
|
||||
]
|
||||
|
||||
@@ -835,6 +835,28 @@ class TestStableSdkContract:
|
||||
assert "close" in result.columns
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"Mt5Config",
|
||||
"Mt5RuntimeError",
|
||||
"Mt5TradingClient",
|
||||
"Mt5TradingError",
|
||||
"TICK_FLAG_MAP",
|
||||
"TIMEFRAME_MAP",
|
||||
],
|
||||
)
|
||||
def test_pdmt5_pass_through_names_removed_from_public_contract(name: str) -> None:
|
||||
"""Removed pdmt5 pass-through names are not part of the public contract."""
|
||||
assert name not in STABLE_SDK_EXPORTS, (
|
||||
f"{name!r} should not be in STABLE_SDK_EXPORTS"
|
||||
)
|
||||
assert name not in SECONDARY_PUBLIC_EXPORTS, (
|
||||
f"{name!r} should not be in SECONDARY_PUBLIC_EXPORTS"
|
||||
)
|
||||
assert name not in mt5cli.__all__, f"{name!r} should not be in mt5cli.__all__"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Packaging metadata
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,8 @@ from pytest_mock import MockerFixture # noqa: TC002
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from pdmt5 import TIMEFRAME_MAP
|
||||
|
||||
from mt5cli import history
|
||||
from mt5cli.history import (
|
||||
DEFAULT_HISTORY_TIMEFRAMES,
|
||||
@@ -58,7 +60,7 @@ from mt5cli.history import (
|
||||
write_rates_dataset,
|
||||
write_streamed_frame,
|
||||
)
|
||||
from mt5cli.utils import TIMEFRAME_MAP, Dataset, IfExists
|
||||
from mt5cli.utils import Dataset, IfExists
|
||||
|
||||
|
||||
class TestResolveRateViewName:
|
||||
|
||||
+8
-12
@@ -11,15 +11,15 @@ from typing import TYPE_CHECKING
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
import mt5cli.utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from mt5cli.utils import (
|
||||
DATETIME_TYPE,
|
||||
REQUEST_TYPE,
|
||||
TICK_FLAG_MAP,
|
||||
TICK_FLAGS_TYPE,
|
||||
TIMEFRAME_MAP,
|
||||
TIMEFRAME_TYPE,
|
||||
Dataset,
|
||||
IfExists,
|
||||
@@ -373,17 +373,13 @@ class TestParseRequest:
|
||||
class TestConstants:
|
||||
"""Tests for module constants."""
|
||||
|
||||
def test_timeframe_map_has_expected_keys(self) -> None:
|
||||
"""Test that TIMEFRAME_MAP contains standard timeframes."""
|
||||
for key in ("M1", "M5", "M15", "M30", "H1", "H4", "D1", "W1", "MN1"):
|
||||
assert key in TIMEFRAME_MAP
|
||||
def test_timeframe_map_is_private_in_utils(self) -> None:
|
||||
"""TIMEFRAME_MAP is a private implementation detail; not a public attribute."""
|
||||
assert not hasattr(mt5cli.utils, "TIMEFRAME_MAP")
|
||||
|
||||
def test_tick_flag_map_has_expected_keys(self) -> None:
|
||||
"""Test that TICK_FLAG_MAP contains standard flags with MT5 values."""
|
||||
assert {"ALL", "INFO", "TRADE"} <= set(TICK_FLAG_MAP)
|
||||
assert TICK_FLAG_MAP["ALL"] == -1
|
||||
assert TICK_FLAG_MAP["INFO"] == 1
|
||||
assert TICK_FLAG_MAP["TRADE"] == 2
|
||||
def test_tick_flag_map_absent_from_utils(self) -> None:
|
||||
"""TICK_FLAG_MAP is not exposed by mt5cli.utils."""
|
||||
assert not hasattr(mt5cli.utils, "TICK_FLAG_MAP")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("dataset", "expected"),
|
||||
|
||||
@@ -518,7 +518,7 @@ dev = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.1.0" },
|
||||
{ name = "pdmt5", specifier = ">=0.3.0" },
|
||||
{ name = "pdmt5", specifier = ">=1.0.0" },
|
||||
{ name = "pyarrow", marker = "extra == 'parquet'", specifier = ">=19.0.0" },
|
||||
{ name = "typer", specifier = ">=0.15.0" },
|
||||
]
|
||||
@@ -691,16 +691,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pdmt5"
|
||||
version = "0.3.0"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "metatrader5", marker = "sys_platform == 'win32'" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/cc/c8fa3a01e0e34178fec8527992f7bb8eda5881477ce23aaacaa9b2ef7bec/pdmt5-0.3.0.tar.gz", hash = "sha256:bb612d5c2695eafac9b2a7b74756e13bd383d7e5517bd90c9a2efa92492c484c", size = 215100, upload-time = "2026-06-11T13:26:46.976Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/6d/b51d2d0ec4636e914210be03a7da20e5078bc8cd7a351edd13bc30b7d2b1/pdmt5-1.0.0.tar.gz", hash = "sha256:ba53a1a5db41fdf4c022ef55353f5a4f6884f625c9310de2b0ddb20457956716", size = 123155, upload-time = "2026-06-25T23:41:50.503Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/03/b12cc4c9db983d971c9172b3765161b6d91136d0624e6718a04dd815e7a1/pdmt5-0.3.0-py3-none-any.whl", hash = "sha256:5388b406cc583202600cfe22c9d781679b1d931b1ed5a2b5dcf37c566149b49f", size = 26250, upload-time = "2026-06-11T13:26:45.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/38/27b712c572d8146efddf571a0e4e7b6d2314a335eb0f47dec1f499ab2189/pdmt5-1.0.0-py3-none-any.whl", hash = "sha256:f969c17902f9ffcbf56d7ccf9285aab39ececd676fcca50c094abcf9d728d517", size = 23992, upload-time = "2026-06-25T23:41:49.067Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user