Shrink public API surface and remove storage re-export module (#74)

This commit is contained in:
Daichi Narushima
2026-06-26 18:23:30 +09:00
committed by GitHub
parent 668f38d8aa
commit f435544f07
12 changed files with 137 additions and 496 deletions
+5 -6
View File
@@ -44,16 +44,14 @@ from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from mt5cli import ( from mt5cli import (
DataKind,
Dataset,
MT5Client, MT5Client,
build_config, build_config,
collect_history, collect_history,
export_dataframe,
mt5_session, mt5_session,
normalize_dataframe,
update_history_with_config, update_history_with_config,
) )
from mt5cli.schemas import DataKind, normalize_dataframe
from mt5cli.utils import Dataset, export_dataframe
# Persistent session for multiple calls # Persistent session for multiple calls
with mt5_session(build_config(login=12345, server="Broker-Demo")) as client: with mt5_session(build_config(login=12345, server="Broker-Demo")) as client:
@@ -89,7 +87,7 @@ update_history_with_config(
) )
``` ```
Schema contracts live in `mt5cli.schemas` (`DataKind`, `validate_schema`, `normalize_dataframe`). Storage helpers are re-exported from `mt5cli.storage` and the package root. Schema contracts live in `mt5cli.schemas` (`DataKind`, `validate_schema`, `normalize_dataframe`). Export and storage helpers are in `mt5cli.utils` (`Dataset`, `export_dataframe`) and `mt5cli.history`.
`MT5Client.order_send()` is a live execution primitive: it can place real trades on the connected account. mt5cli does not implement strategy logic, signal generation, backtesting, or optimization — downstream applications must gate live execution explicitly. `MT5Client.order_send()` is a live execution primitive: it can place real trades on the connected account. mt5cli does not implement strategy logic, signal generation, backtesting, or optimization — downstream applications must gate live execution explicitly.
@@ -212,7 +210,8 @@ For automated pipelines, use the importable incremental API instead of re-fetchi
```python ```python
from pdmt5 import Mt5Config, Mt5DataClient from pdmt5 import Mt5Config, Mt5DataClient
from mt5cli import Dataset, update_history, update_history_with_config from mt5cli import update_history, update_history_with_config
from mt5cli.utils import Dataset
# Reuse an already-connected pdmt5 client (does not open/close MT5) # Reuse an already-connected pdmt5 client (does not open/close MT5)
client = Mt5DataClient(config=Mt5Config(login=12345)) client = Mt5DataClient(config=Mt5Config(login=12345))
+5 -3
View File
@@ -182,12 +182,14 @@ targets without hard-coding view names:
from pathlib import Path from pathlib import Path
from mt5cli import ( from mt5cli import (
load_rate_data,
load_rate_series_by_granularity, load_rate_series_by_granularity,
load_rate_series_from_sqlite, load_rate_series_from_sqlite,
resolve_rate_table_name,
) )
from mt5cli.history import resolve_rate_view_name from mt5cli.history import (
load_rate_data,
resolve_rate_table_name,
resolve_rate_view_name,
)
view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1", require_existing=True) view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1", require_existing=True)
rates = load_rate_data(Path("history.db"), view, count=1000) rates = load_rate_data(Path("history.db"), view, count=1000)
+3 -5
View File
@@ -13,7 +13,6 @@ responsibilities.
| [Public API Contract](public-contract.md) | Stable downstream SDK exports, CLI boundary, and out-of-scope items | | [Public API Contract](public-contract.md) | Stable downstream SDK exports, CLI boundary, and out-of-scope items |
| [Client](client.md) | `MT5Client` session abstraction for data access and order primitives | | [Client](client.md) | `MT5Client` session abstraction for data access and order primitives |
| [Schemas](schemas.md) | Canonical DataFrame contracts and normalization helpers | | [Schemas](schemas.md) | Canonical DataFrame contracts and normalization helpers |
| [Storage](storage.md) | CSV/JSON/Parquet/SQLite export and history collection helpers |
| [Converters](converters.md) | Symbol, timeframe, timezone, and date-range utilities | | [Converters](converters.md) | Symbol, timeframe, timezone, and date-range utilities |
| [Exceptions](exceptions.md) | Stable mt5cli exception types and MT5 error normalization | | [Exceptions](exceptions.md) | Stable mt5cli exception types and MT5 error normalization |
| [SDK](sdk.md) | Module-level fetch helpers, multi-account collectors, incremental history | | [SDK](sdk.md) | Module-level fetch helpers, multi-account collectors, incremental history |
@@ -30,15 +29,14 @@ flowchart TD
CLI["mt5cli CLI"] --> Client CLI["mt5cli CLI"] --> Client
Client --> SDK["sdk / pdmt5"] Client --> SDK["sdk / pdmt5"]
Client --> Schemas["schemas"] Client --> Schemas["schemas"]
Storage["storage"] --> History["history SQLite"] History["history SQLite"] --> Utils["utils export"]
Storage --> Utils["utils export"]
SDK --> PDMT5["pdmt5.Mt5DataClient"] SDK --> PDMT5["pdmt5.Mt5DataClient"]
``` ```
Downstream packages should depend on the package root exports documented in the Downstream packages should depend on the package root exports documented in the
[Public API Contract](public-contract.md) (`MT5Client`, [Public API Contract](public-contract.md) (`MT5Client`,
`DataKind`, `normalize_dataframe`, `collect_history`, `load_rate_data`, `collect_history`, `load_rate_series_from_sqlite`, etc.) rather than private
`resolve_rate_view_name`, etc.) rather than private modules. modules. Lower-level helpers are accessible directly from their owning modules.
`MT5Client.order_send()` is a live execution primitive that can place real trades. mt5cli exposes minimal execution helpers only; strategy logic, signals, backtests, and optimization remain out of scope and must be implemented downstream with explicit execution gating. `MT5Client.order_send()` is a live execution primitive that can place real trades. mt5cli exposes minimal execution helpers only; strategy logic, signals, backtests, and optimization remain out of scope and must be implemented downstream with explicit execution gating.
+31 -84
View File
@@ -24,22 +24,15 @@ Note: the former `mt5cli` re-export `TICK_FLAG_MAP` corresponds to `COPY_TICKS_M
in pdmt5 — the name changed, it was not simply moved. in pdmt5 — the name changed, it was not simply moved.
Downstream packages should import from the package root (`from mt5cli import Downstream packages should import from the package root (`from mt5cli import
...`) and use the public tier sets in `mt5cli.contract` to distinguish API ...`). The contract set `STABLE_SDK_EXPORTS` in `mt5cli.contract` enumerates
stability. CLI commands mirror the same behavior but are not importable Python every package-root symbol. Lower-level helpers (schema utilities, export
APIs. functions, parser helpers, low-level MT5 wrappers) are available directly from
their owning modules (`mt5cli.schemas`, `mt5cli.utils`, `mt5cli.converters`,
## Public API tiers `mt5cli.sdk`, etc.) and are not part of the root SDK surface.
mt5cli classifies package-root imports by intended downstream use:
| Tier | Contract set | Meaning |
| ---------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable core | `STABLE_SDK_EXPORTS` | Preferred SDK surface for downstream MT5 infrastructure adapters. Changes require a deliberate compatibility path. |
| Secondary public | `SECONDARY_PUBLIC_EXPORTS` | Public helpers for CLI/export/schema integrations and lower-level MT5 wrappers. Importable, but less central to the downstream trading SDK. |
## Stable downstream SDK API ## Stable downstream SDK API
These names are exported from `mt5cli` and covered by the contract in These names are exported from `mt5cli` and enumerated in
`mt5cli.STABLE_SDK_EXPORTS` (defined in `mt5cli.contract`). `mt5cli.STABLE_SDK_EXPORTS` (defined in `mt5cli.contract`).
### Session lifecycle and configuration ### Session lifecycle and configuration
@@ -52,20 +45,6 @@ These names are exported from `mt5cli` and covered by the contract in
| `create_trading_client`, `mt5_trading_session` | Trading-capable `pdmt5.Mt5TradingClient` lifecycle | | `create_trading_client`, `mt5_trading_session` | Trading-capable `pdmt5.Mt5TradingClient` lifecycle |
| `AccountSpec` | Generic account group: symbols plus optional credentials | | `AccountSpec` | Generic account group: symbols plus optional credentials |
| `resolve_account_spec`, `resolve_account_specs` | Merge overrides and expand `${ENV_VAR}` placeholders; opt-in `allow_whole_dollar_env` for bare `$NAME` | | `resolve_account_spec`, `resolve_account_specs` | Merge overrides and expand `${ENV_VAR}` placeholders; opt-in `allow_whole_dollar_env` for bare `$NAME` |
| `substitute_env_placeholders` | Replace `${NAME}` substrings from the environment; opt-in `allow_whole_dollar_env` for whole-value `$NAME` |
| `substitute_mapping_values` | Recursively traverse a dict/list/scalar structure and substitute `${ENV_VAR}` placeholders for caller-selected mapping keys only; optionally normalise blank strings to `None` for a separate caller-selected key set; does not hard-code any application-specific key names |
Credential resolution is generic: any environment variable name may appear inside
`${...}`. mt5cli does not hard-code application-specific keys such as
`mt5_login` or `mt5_exe`.
Pass `allow_whole_dollar_env=True` to `substitute_env_placeholders()`,
`substitute_mapping_values()`, `resolve_account_spec()`, `resolve_account_specs()`,
and `build_config()` to additionally expand strings whose entire value is a bare
`$ENV_NAME` identifier.
Partial strings such as `"plan$pass"`, `"abc$ENV"`, or `"$ENV-suffix"` are
**never** expanded — only an exact `$IDENTIFIER` whole-string match qualifies.
Default is `False` to preserve backward compatibility.
### Closed-bar rate helpers ### Closed-bar rate helpers
@@ -85,20 +64,13 @@ timestamp normalization in downstream apps.
### SQLite history collection and rate loading ### SQLite history collection and rate loading
| Symbol | Role | | Symbol | Role |
| ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `collect_history` | One-shot date-range export into SQLite | | `collect_history` | One-shot date-range export into SQLite |
| `update_history`, `update_history_with_config` | Incremental append from `MAX(time)` cursors | | `update_history`, `update_history_with_config` | Incremental append from `MAX(time)` cursors |
| `ThrottledHistoryUpdater` | Minimum interval between successful incremental updates; optional `update_backend` injection | | `ThrottledHistoryUpdater` | Minimum interval between successful incremental updates; optional `update_backend` injection |
| `resolve_history_datasets`, `resolve_history_timeframes`, `resolve_history_tick_flags` | History pipeline configuration | | `RateTarget`, `build_rate_targets` | Neutral `(symbol, timeframe)` series descriptors |
| `build_rate_view_name`, `resolve_rate_table_name`, `resolve_rate_view_name`, `resolve_rate_view_names`, `resolve_rate_tables` | Map symbols/timeframes to mt5cli-managed table or view names | | `load_rate_series_from_sqlite`, `load_rate_series_by_granularity` | Load one or many series; fail clearly when managed views are missing |
| `RateTarget`, `build_rate_targets` | Neutral `(symbol, timeframe)` series descriptors |
| `load_rate_data`, `load_rate_data_from_connection` | Load one table/view into a time-indexed DataFrame |
| `load_rate_series_from_sqlite`, `load_rate_series_by_granularity` | Load one or many series; fail clearly when managed views are missing |
Pass `require_existing=True` to rate view resolution helpers when downstream
code must fail instead of receiving a best-guess view name. Multi-series loaders
require existing managed `rate_*__*` views unless `explicit_tables` is supplied.
See [History Collection (SQLite)](history.md) for schema, view naming, and ER See [History Collection (SQLite)](history.md) for schema, view naming, and ER
diagrams. diagrams.
@@ -151,48 +123,23 @@ and returned as `status="failed"` with normalized `request` / `response` details
### Errors ### Errors
| Symbol | Role | | Symbol | Role |
| ------------------------------------------------------------------------------------ | -------------------------------------------- | | -------------------------------------------------------------------------- | ----------------------------- |
| `Mt5CliError`, `Mt5ConnectionError`, `Mt5OperationError`, `Mt5SchemaError` | Stable mt5cli exception types | | `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 ## Module-scoped helpers
These names remain importable from `mt5cli` and are covered by Lower-level helpers are available from their owning modules and are not part
`SECONDARY_PUBLIC_EXPORTS`, but they are oriented toward CLI/export/schema of the package-root stable surface. Import them directly when needed:
integrations, parsing, and lower-level MT5 access rather than the stable core
SDK surface. Prefer the stable symbols above for downstream infrastructure
adapters.
### Read-only MT5 data wrappers | Module | Examples |
| ------------------- | ---------------------------------------------------------------------------------------------- |
Module-level helpers open a transient connection per call. Prefer `mt5_session` | `mt5cli.history` | `resolve_rate_view_name`, `resolve_rate_tables`, `load_rate_data`, `build_rate_view_name` |
or `MT5Client` when making many requests in one process. | `mt5cli.sdk` | `copy_rates_from`, `copy_ticks_from`, `account_info`, `symbols`, `mt5_summary`, `latest_rates` |
| `mt5cli.schemas` | `DataKind`, `normalize_dataframe`, `validate_schema`, `DEDUP_KEYS` |
| Area | Symbols | | `mt5cli.utils` | `Dataset`, `IfExists`, `detect_format`, `export_dataframe`, `export_dataframe_to_sqlite` |
| -------------------- | ---------------------------------------------------------------------------------------------------- | | `mt5cli.converters` | `normalize_symbol`, `ensure_utc`, `parse_date_range`, `granularity_name` |
| Rates | `copy_rates_from`, `copy_rates_from_pos`, `copy_rates_range`, `latest_rates`, `collect_latest_rates` | | `mt5cli.exceptions` | `normalize_mt5_exception`, `call_with_normalized_errors`, `is_recoverable_mt5_error` |
| Ticks | `copy_ticks_from`, `copy_ticks_range`, `recent_ticks` |
| Account / terminal | `account_info`, `terminal_info`, `mt5_version`, `last_error`, `mt5_summary`, `mt5_summary_as_df` |
| Symbols / market | `symbols`, `symbol_info`, `symbol_info_tick`, `market_book`, `minimum_margins` |
| Trading state (read) | `orders`, `positions`, `history_orders`, `history_deals`, `recent_history_deals` |
| Multi-account rates | `collect_latest_rates_for_accounts` |
Use `mt5_version` for MetaTrader 5 terminal version data. The name `version` at
the package root refers to `importlib.metadata.version` (package metadata), not
the MT5 SDK helper.
### Schema, export, and parser helpers
| Area | Symbols |
| -------------------- | ------------------------------------------------------------------------------------------------------------- |
| Dataset contracts | `DataKind`, `Dataset`, `IfExists`, `DEDUP_KEYS`, `REQUIRED_COLUMNS`, `TIME_COLUMNS`, `KNOWN_MT5_TIME_COLUMNS` |
| Schema normalization | `normalize_dataframe`, `normalize_time_columns`, `schema_columns`, `validate_schema` |
| 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` |
| Trading data shapes | `POSITION_COLUMNS` |
## CLI commands ## CLI commands
@@ -246,7 +193,7 @@ their own adapter layer.
## Contract verification ## Contract verification
`tests/test_contracts.py` asserts that every name in the stable and secondary `tests/test_contracts.py` asserts that every name in `STABLE_SDK_EXPORTS` is
tier sets is importable from `mt5cli`, documents key closed-bar, rate-view, importable from `mt5cli`, that all package-root exports are covered by the
SQLite loading, account-resolution, and trading-session behaviors, and keeps the stable set, and documents key closed-bar, SQLite loading, account-resolution,
tier sets aligned with `__all__`. and trading-session behaviors.
+2 -1
View File
@@ -117,7 +117,8 @@ call it every iteration without over-fetching.
```python ```python
from pdmt5 import Mt5Config, Mt5DataClient from pdmt5 import Mt5Config, Mt5DataClient
from mt5cli import Dataset, ThrottledHistoryUpdater from mt5cli import ThrottledHistoryUpdater
from mt5cli.utils import Dataset
updater = ThrottledHistoryUpdater( updater = ThrottledHistoryUpdater(
output="history.db", output="history.db",
-3
View File
@@ -1,3 +0,0 @@
# Storage
::: mt5cli.storage
+5 -9
View File
@@ -42,19 +42,15 @@ from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from mt5cli import ( from mt5cli import (
DataKind,
Dataset,
MT5Client, MT5Client,
build_config, build_config,
collect_history, collect_history,
export_dataframe,
load_rate_data,
minimum_margins,
mt5_session, mt5_session,
normalize_dataframe,
recent_ticks,
resolve_rate_view_name,
) )
from mt5cli.history import load_rate_data, resolve_rate_view_name
from mt5cli.schemas import DataKind, normalize_dataframe
from mt5cli.sdk import minimum_margins, recent_ticks
from mt5cli.utils import Dataset, export_dataframe
# Persistent session for multiple calls # Persistent session for multiple calls
with mt5_session(build_config(login=12345, server="Broker-Demo")) as client: with mt5_session(build_config(login=12345, server="Broker-Demo")) as client:
@@ -90,7 +86,7 @@ collect_history(
) )
``` ```
Schema contracts live in `mt5cli.schemas` (`DataKind`, `validate_schema`, `normalize_dataframe`). Storage helpers are re-exported from `mt5cli.storage` and the package root. Schema contracts live in `mt5cli.schemas` (`DataKind`, `validate_schema`, `normalize_dataframe`). Export and storage helpers are in `mt5cli.utils` (`Dataset`, `export_dataframe`) and `mt5cli.history`.
`MT5Client.order_send()` is a live execution primitive: it can place real trades on the connected account. mt5cli does not implement strategy logic, signal generation, backtesting, or optimization — downstream applications must gate live execution explicitly (the CLI requires `--yes` for `order-send`). `MT5Client.order_send()` is a live execution primitive: it can place real trades on the connected account. mt5cli does not implement strategy logic, signal generation, backtesting, or optimization — downstream applications must gate live execution explicitly (the CLI requires `--yes` for `order-send`).
-1
View File
@@ -59,7 +59,6 @@ nav:
- Public API Contract: api/public-contract.md - Public API Contract: api/public-contract.md
- Client: api/client.md - Client: api/client.md
- Schemas: api/schemas.md - Schemas: api/schemas.md
- Storage: api/storage.md
- Converters: api/converters.md - Converters: api/converters.md
- Exceptions: api/exceptions.md - Exceptions: api/exceptions.md
- CLI: api/cli.md - CLI: api/cli.md
+1 -145
View File
@@ -9,107 +9,34 @@ strategy responsibilities.
from importlib.metadata import version from importlib.metadata import version
from .client import MT5Client, build_config, mt5_session from .client import MT5Client, build_config, mt5_session
from .contract import ( from .contract import STABLE_SDK_EXPORTS
PUBLIC_EXPORT_TIERS,
SECONDARY_PUBLIC_EXPORTS,
STABLE_SDK_EXPORTS,
)
from .converters import (
ensure_utc,
granularity_name,
normalize_symbol,
normalize_symbols,
parse_date_range,
recent_window,
)
from .exceptions import ( from .exceptions import (
Mt5CliError, Mt5CliError,
Mt5ConnectionError, Mt5ConnectionError,
Mt5OperationError, Mt5OperationError,
Mt5SchemaError, Mt5SchemaError,
call_with_normalized_errors,
is_recoverable_mt5_error,
normalize_mt5_exception,
) )
from .history import ( from .history import (
RateTarget, RateTarget,
build_rate_targets, build_rate_targets,
build_rate_view_name,
drop_forming_rate_bar, drop_forming_rate_bar,
load_rate_data,
load_rate_data_from_connection,
load_rate_series_by_granularity, load_rate_series_by_granularity,
load_rate_series_from_sqlite, load_rate_series_from_sqlite,
resolve_history_datasets,
resolve_history_tick_flags,
resolve_history_timeframes,
resolve_rate_table_name,
resolve_rate_tables,
resolve_rate_view_name,
resolve_rate_view_names,
)
from .schemas import (
DEDUP_KEYS,
KNOWN_MT5_TIME_COLUMNS,
REQUIRED_COLUMNS,
TIME_COLUMNS,
DataKind,
normalize_dataframe,
normalize_time_columns,
schema_columns,
validate_schema,
) )
from .sdk import ( from .sdk import (
AccountSpec, AccountSpec,
ThrottledHistoryUpdater, ThrottledHistoryUpdater,
account_info,
collect_history, collect_history,
collect_latest_closed_rates_by_granularity, collect_latest_closed_rates_by_granularity,
collect_latest_closed_rates_for_accounts, collect_latest_closed_rates_for_accounts,
collect_latest_rates,
collect_latest_rates_for_accounts,
collect_latest_rates_for_accounts_with_retries, collect_latest_rates_for_accounts_with_retries,
copy_rates_from,
copy_rates_from_pos,
copy_rates_range,
copy_ticks_from,
copy_ticks_range,
fetch_latest_closed_rates, fetch_latest_closed_rates,
history_deals,
history_orders,
last_error,
latest_rates,
market_book,
minimum_margins,
mt5_summary,
mt5_summary_as_df,
orders,
positions,
recent_history_deals,
recent_ticks,
resolve_account_spec, resolve_account_spec,
resolve_account_specs, resolve_account_specs,
substitute_env_placeholders,
substitute_mapping_values,
symbol_info,
symbol_info_tick,
symbols,
terminal_info,
update_history, update_history,
update_history_with_config, update_history_with_config,
) )
from .sdk import (
version as mt5_version,
)
from .storage import (
Dataset,
IfExists,
detect_format,
export_dataframe,
export_dataframe_to_sqlite,
)
from .trading import ( from .trading import (
POSITION_COLUMNS,
ExecutionStatus, ExecutionStatus,
MarginVolume, MarginVolume,
OrderExecutionResult, OrderExecutionResult,
@@ -149,28 +76,13 @@ from .trading import (
update_sltp_for_open_positions, update_sltp_for_open_positions,
update_trailing_stop_loss_for_open_positions, update_trailing_stop_loss_for_open_positions,
) )
from .utils import (
parse_datetime,
parse_tick_flags,
parse_timeframe,
)
__version__ = version(__package__) if __package__ else None __version__ = version(__package__) if __package__ else None
__all__ = [ __all__ = [
"DEDUP_KEYS",
"KNOWN_MT5_TIME_COLUMNS",
"POSITION_COLUMNS",
"PUBLIC_EXPORT_TIERS",
"REQUIRED_COLUMNS",
"SECONDARY_PUBLIC_EXPORTS",
"STABLE_SDK_EXPORTS", "STABLE_SDK_EXPORTS",
"TIME_COLUMNS",
"AccountSpec", "AccountSpec",
"DataKind",
"Dataset",
"ExecutionStatus", "ExecutionStatus",
"IfExists",
"MT5Client", "MT5Client",
"MarginVolume", "MarginVolume",
"Mt5CliError", "Mt5CliError",
@@ -186,10 +98,8 @@ __all__ = [
"ProjectionMode", "ProjectionMode",
"RateTarget", "RateTarget",
"ThrottledHistoryUpdater", "ThrottledHistoryUpdater",
"account_info",
"build_config", "build_config",
"build_rate_targets", "build_rate_targets",
"build_rate_view_name",
"calculate_account_projected_margin_ratio", "calculate_account_projected_margin_ratio",
"calculate_margin_and_volume", "calculate_margin_and_volume",
"calculate_new_position_margin_ratio", "calculate_new_position_margin_ratio",
@@ -201,29 +111,17 @@ __all__ = [
"calculate_symbol_group_margin_ratio", "calculate_symbol_group_margin_ratio",
"calculate_trailing_stop_updates", "calculate_trailing_stop_updates",
"calculate_volume_by_margin", "calculate_volume_by_margin",
"call_with_normalized_errors",
"close_open_positions", "close_open_positions",
"collect_history", "collect_history",
"collect_latest_closed_rates_by_granularity", "collect_latest_closed_rates_by_granularity",
"collect_latest_closed_rates_for_accounts", "collect_latest_closed_rates_for_accounts",
"collect_latest_rates",
"collect_latest_rates_for_accounts",
"collect_latest_rates_for_accounts_with_retries", "collect_latest_rates_for_accounts_with_retries",
"copy_rates_from",
"copy_rates_from_pos",
"copy_rates_range",
"copy_ticks_from",
"copy_ticks_range",
"create_trading_client", "create_trading_client",
"detect_format",
"detect_position_side", "detect_position_side",
"determine_order_limits", "determine_order_limits",
"drop_forming_rate_bar", "drop_forming_rate_bar",
"ensure_symbol_selected", "ensure_symbol_selected",
"ensure_utc",
"estimate_order_margin", "estimate_order_margin",
"export_dataframe",
"export_dataframe_to_sqlite",
"extract_tick_price", "extract_tick_price",
"fetch_latest_closed_rates", "fetch_latest_closed_rates",
"fetch_latest_closed_rates_for_trading_client", "fetch_latest_closed_rates_for_trading_client",
@@ -232,58 +130,16 @@ __all__ = [
"get_positions_frame", "get_positions_frame",
"get_symbol_snapshot", "get_symbol_snapshot",
"get_tick_snapshot", "get_tick_snapshot",
"granularity_name",
"history_deals",
"history_orders",
"is_recoverable_mt5_error",
"last_error",
"latest_rates",
"load_rate_data",
"load_rate_data_from_connection",
"load_rate_series_by_granularity", "load_rate_series_by_granularity",
"load_rate_series_from_sqlite", "load_rate_series_from_sqlite",
"market_book",
"minimum_margins",
"mt5_session", "mt5_session",
"mt5_summary",
"mt5_summary_as_df",
"mt5_trading_session", "mt5_trading_session",
"mt5_version",
"normalize_dataframe",
"normalize_mt5_exception",
"normalize_order_volume", "normalize_order_volume",
"normalize_symbol",
"normalize_symbols",
"normalize_time_columns",
"orders",
"parse_date_range",
"parse_datetime",
"parse_tick_flags",
"parse_timeframe",
"place_market_order", "place_market_order",
"positions",
"recent_history_deals",
"recent_ticks",
"recent_window",
"resolve_account_spec", "resolve_account_spec",
"resolve_account_specs", "resolve_account_specs",
"resolve_history_datasets",
"resolve_history_tick_flags",
"resolve_history_timeframes",
"resolve_rate_table_name",
"resolve_rate_tables",
"resolve_rate_view_name",
"resolve_rate_view_names",
"schema_columns",
"substitute_env_placeholders",
"substitute_mapping_values",
"symbol_info",
"symbol_info_tick",
"symbols",
"terminal_info",
"update_history", "update_history",
"update_history_with_config", "update_history_with_config",
"update_sltp_for_open_positions", "update_sltp_for_open_positions",
"update_trailing_stop_loss_for_open_positions", "update_trailing_stop_loss_for_open_positions",
"validate_schema",
] ]
+2 -78
View File
@@ -1,4 +1,4 @@
"""Downstream SDK export tiers for mt5cli.""" """Downstream SDK export tier for mt5cli."""
from __future__ import annotations from __future__ import annotations
@@ -22,7 +22,6 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
"ThrottledHistoryUpdater", "ThrottledHistoryUpdater",
"build_config", "build_config",
"build_rate_targets", "build_rate_targets",
"build_rate_view_name",
"calculate_account_projected_margin_ratio", "calculate_account_projected_margin_ratio",
"calculate_margin_and_volume", "calculate_margin_and_volume",
"calculate_new_position_margin_ratio", "calculate_new_position_margin_ratio",
@@ -34,7 +33,6 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
"calculate_symbol_group_margin_ratio", "calculate_symbol_group_margin_ratio",
"calculate_trailing_stop_updates", "calculate_trailing_stop_updates",
"calculate_volume_by_margin", "calculate_volume_by_margin",
"call_with_normalized_errors",
"close_open_positions", "close_open_positions",
"collect_history", "collect_history",
"collect_latest_closed_rates_by_granularity", "collect_latest_closed_rates_by_granularity",
@@ -54,92 +52,18 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
"get_positions_frame", "get_positions_frame",
"get_symbol_snapshot", "get_symbol_snapshot",
"get_tick_snapshot", "get_tick_snapshot",
"is_recoverable_mt5_error",
"load_rate_data",
"load_rate_data_from_connection",
"load_rate_series_by_granularity", "load_rate_series_by_granularity",
"load_rate_series_from_sqlite", "load_rate_series_from_sqlite",
"mt5_session", "mt5_session",
"mt5_trading_session", "mt5_trading_session",
"normalize_mt5_exception",
"normalize_order_volume", "normalize_order_volume",
"place_market_order", "place_market_order",
"resolve_account_spec", "resolve_account_spec",
"resolve_account_specs", "resolve_account_specs",
"resolve_history_datasets",
"resolve_history_tick_flags",
"resolve_history_timeframes",
"resolve_rate_table_name",
"resolve_rate_tables",
"resolve_rate_view_name",
"resolve_rate_view_names",
"substitute_env_placeholders",
"substitute_mapping_values",
"update_history", "update_history",
"update_history_with_config", "update_history_with_config",
"update_sltp_for_open_positions", "update_sltp_for_open_positions",
"update_trailing_stop_loss_for_open_positions", "update_trailing_stop_loss_for_open_positions",
}) })
SECONDARY_PUBLIC_EXPORTS: frozenset[str] = frozenset({ __all__ = ["STABLE_SDK_EXPORTS"]
"DEDUP_KEYS",
"DataKind",
"Dataset",
"IfExists",
"KNOWN_MT5_TIME_COLUMNS",
"POSITION_COLUMNS",
"REQUIRED_COLUMNS",
"TIME_COLUMNS",
"account_info",
"collect_latest_rates",
"collect_latest_rates_for_accounts",
"copy_rates_from",
"copy_rates_from_pos",
"copy_rates_range",
"copy_ticks_from",
"copy_ticks_range",
"detect_format",
"ensure_utc",
"export_dataframe",
"export_dataframe_to_sqlite",
"granularity_name",
"history_deals",
"history_orders",
"last_error",
"latest_rates",
"market_book",
"minimum_margins",
"mt5_summary",
"mt5_summary_as_df",
"mt5_version",
"normalize_dataframe",
"normalize_symbol",
"normalize_symbols",
"normalize_time_columns",
"orders",
"parse_date_range",
"parse_datetime",
"parse_tick_flags",
"parse_timeframe",
"positions",
"recent_history_deals",
"recent_ticks",
"recent_window",
"schema_columns",
"symbol_info",
"symbol_info_tick",
"symbols",
"terminal_info",
"validate_schema",
})
PUBLIC_EXPORT_TIERS: dict[str, frozenset[str]] = {
"stable": STABLE_SDK_EXPORTS,
"secondary": SECONDARY_PUBLIC_EXPORTS,
}
__all__ = [
"PUBLIC_EXPORT_TIERS",
"SECONDARY_PUBLIC_EXPORTS",
"STABLE_SDK_EXPORTS",
]
-49
View File
@@ -1,49 +0,0 @@
"""Generic storage helpers for MT5 market and account history."""
from __future__ import annotations
from .history import (
RateTarget,
build_rate_targets,
build_rate_view_name,
drop_forming_rate_bar,
load_rate_data,
load_rate_data_from_connection,
load_rate_series_by_granularity,
load_rate_series_from_sqlite,
resolve_rate_tables,
resolve_rate_view_name,
resolve_rate_view_names,
)
from .sdk import collect_history, update_history, update_history_with_config
from .utils import (
Dataset,
IfExists,
OutputFormat,
detect_format,
export_dataframe,
export_dataframe_to_sqlite,
)
__all__ = [
"Dataset",
"IfExists",
"OutputFormat",
"RateTarget",
"build_rate_targets",
"build_rate_view_name",
"collect_history",
"detect_format",
"drop_forming_rate_bar",
"export_dataframe",
"export_dataframe_to_sqlite",
"load_rate_data",
"load_rate_data_from_connection",
"load_rate_series_by_granularity",
"load_rate_series_from_sqlite",
"resolve_rate_tables",
"resolve_rate_view_name",
"resolve_rate_view_names",
"update_history",
"update_history_with_config",
]
+83 -112
View File
@@ -2,14 +2,16 @@
from __future__ import annotations from __future__ import annotations
import re import importlib
import sqlite3 import sqlite3
from datetime import UTC, datetime from datetime import UTC, datetime
from importlib.metadata import requires from importlib.metadata import requires
from pathlib import Path from typing import TYPE_CHECKING, get_type_hints
from typing import get_type_hints
from unittest.mock import MagicMock from unittest.mock import MagicMock
if TYPE_CHECKING:
from pathlib import Path
import pandas as pd import pandas as pd
import pytest import pytest
from pdmt5 import Mt5RuntimeError, Mt5TradingError from pdmt5 import Mt5RuntimeError, Mt5TradingError
@@ -17,15 +19,8 @@ from pytest_mock import MockerFixture # noqa: TC002
import mt5cli import mt5cli
from mt5cli import ( from mt5cli import (
DEDUP_KEYS,
PUBLIC_EXPORT_TIERS,
REQUIRED_COLUMNS,
SECONDARY_PUBLIC_EXPORTS,
STABLE_SDK_EXPORTS, STABLE_SDK_EXPORTS,
TIME_COLUMNS,
AccountSpec, AccountSpec,
DataKind,
Dataset,
ExecutionStatus, ExecutionStatus,
MarginVolume, MarginVolume,
MT5Client, MT5Client,
@@ -44,40 +39,56 @@ from mt5cli import (
calculate_projected_margin_ratio, calculate_projected_margin_ratio,
calculate_symbol_group_margin_ratio, calculate_symbol_group_margin_ratio,
calculate_trailing_stop_updates, calculate_trailing_stop_updates,
call_with_normalized_errors,
detect_format,
drop_forming_rate_bar, drop_forming_rate_bar,
ensure_symbol_selected, ensure_symbol_selected,
ensure_utc,
export_dataframe,
export_dataframe_to_sqlite,
extract_tick_price, extract_tick_price,
fetch_latest_closed_rates, fetch_latest_closed_rates,
fetch_latest_closed_rates_for_trading_client, fetch_latest_closed_rates_for_trading_client,
fetch_latest_closed_rates_indexed, fetch_latest_closed_rates_indexed,
granularity_name,
is_recoverable_mt5_error,
load_rate_data,
load_rate_series_from_sqlite, load_rate_series_from_sqlite,
mt5_session, mt5_session,
mt5_trading_session, mt5_trading_session,
normalize_dataframe,
normalize_mt5_exception,
normalize_order_volume, normalize_order_volume,
place_market_order,
resolve_account_spec,
resolve_account_specs,
)
from mt5cli.converters import (
ensure_utc,
granularity_name,
normalize_symbol, normalize_symbol,
normalize_symbols, normalize_symbols,
parse_date_range, parse_date_range,
place_market_order,
recent_window, recent_window,
resolve_account_spec, )
resolve_account_specs, from mt5cli.exceptions import (
call_with_normalized_errors,
is_recoverable_mt5_error,
normalize_mt5_exception,
)
from mt5cli.history import (
create_rate_compatibility_views,
load_rate_data,
resolve_rate_view_name, resolve_rate_view_name,
)
from mt5cli.retry import retry_with_backoff
from mt5cli.schemas import (
DEDUP_KEYS,
REQUIRED_COLUMNS,
TIME_COLUMNS,
DataKind,
ensure_utc_columns,
normalize_dataframe,
normalize_time_columns,
schema_columns, schema_columns,
validate_schema, validate_schema,
) )
from mt5cli.history import create_rate_compatibility_views from mt5cli.utils import (
from mt5cli.retry import retry_with_backoff Dataset,
from mt5cli.schemas import ensure_utc_columns, normalize_time_columns detect_format,
export_dataframe,
export_dataframe_to_sqlite,
)
def _sample_frame(kind: DataKind) -> pd.DataFrame: def _sample_frame(kind: DataKind) -> pd.DataFrame:
@@ -540,6 +551,12 @@ def test_storage_export_round_trip_sqlite(tmp_path: Path) -> None:
assert count == 1 assert count == 1
def test_storage_module_does_not_exist() -> None:
"""mt5cli.storage re-export module has been removed."""
with pytest.raises(ModuleNotFoundError):
importlib.import_module("mt5cli.storage")
class TestStableSdkContract: class TestStableSdkContract:
"""Tests for the documented stable downstream SDK contract.""" """Tests for the documented stable downstream SDK contract."""
@@ -548,51 +565,19 @@ class TestStableSdkContract:
missing = sorted(STABLE_SDK_EXPORTS - set(mt5cli.__all__)) missing = sorted(STABLE_SDK_EXPORTS - set(mt5cli.__all__))
assert not missing, f"STABLE_SDK_EXPORTS missing from __all__: {missing}" assert not missing, f"STABLE_SDK_EXPORTS missing from __all__: {missing}"
def test_public_export_tiers_are_disjoint_and_complete(self) -> None: def test_stable_exports_cover_root_api(self) -> None:
"""Documented public tiers do not overlap and classify root exports.""" """STABLE_SDK_EXPORTS classifies every package-root symbol."""
assert PUBLIC_EXPORT_TIERS == { tier_metadata = {"STABLE_SDK_EXPORTS"}
"stable": STABLE_SDK_EXPORTS,
"secondary": SECONDARY_PUBLIC_EXPORTS,
}
assert not (STABLE_SDK_EXPORTS & SECONDARY_PUBLIC_EXPORTS)
tiered_exports = STABLE_SDK_EXPORTS | SECONDARY_PUBLIC_EXPORTS
root_exports = set(mt5cli.__all__) root_exports = set(mt5cli.__all__)
missing_from_root = sorted(tiered_exports - root_exports) missing_from_root = sorted(STABLE_SDK_EXPORTS - root_exports)
assert not missing_from_root, ( assert not missing_from_root, (
f"Tiered exports missing from __all__: {missing_from_root}" f"STABLE_SDK_EXPORTS missing from __all__: {missing_from_root}"
) )
tier_metadata_exports = { unclassified = sorted(root_exports - STABLE_SDK_EXPORTS - tier_metadata)
"PUBLIC_EXPORT_TIERS", assert not unclassified, (
"SECONDARY_PUBLIC_EXPORTS", f"Root exports not in STABLE_SDK_EXPORTS: {unclassified}"
"STABLE_SDK_EXPORTS",
}
unclassified_root_exports = sorted(
root_exports - tiered_exports - tier_metadata_exports,
)
assert not unclassified_root_exports, (
f"Root exports missing from public API tiers: {unclassified_root_exports}"
)
def test_stable_docs_do_not_document_nonstable_exports(self) -> None:
"""Stable docs do not promote secondary root exports."""
docs_path = Path("docs/api/public-contract.md")
docs = docs_path.read_text(encoding="utf-8")
stable_section = docs.split("## Stable downstream SDK API", maxsplit=1)[
1
].split(
"## Secondary public exports",
maxsplit=1,
)[0]
documented_symbols = set(
re.findall(r"`([A-Za-z_][A-Za-z0-9_]*)`", stable_section)
)
nonstable_exports = SECONDARY_PUBLIC_EXPORTS
wrongly_stable = sorted(documented_symbols & nonstable_exports)
assert not wrongly_stable, (
f"Non-stable exports documented in stable section: {wrongly_stable}"
) )
@pytest.mark.parametrize("name", sorted(STABLE_SDK_EXPORTS)) @pytest.mark.parametrize("name", sorted(STABLE_SDK_EXPORTS))
@@ -600,17 +585,6 @@ class TestStableSdkContract:
"""Stable SDK names resolve through ``from mt5cli import ...``.""" """Stable SDK names resolve through ``from mt5cli import ...``."""
assert hasattr(mt5cli, name), f"{name!r} missing from mt5cli package root" assert hasattr(mt5cli, name), f"{name!r} missing from mt5cli package root"
@pytest.mark.parametrize(
"name",
sorted(SECONDARY_PUBLIC_EXPORTS),
)
def test_secondary_exports_are_importable(
self,
name: str,
) -> None:
"""Non-stable public names remain available from the package root."""
assert hasattr(mt5cli, name), f"{name!r} missing from mt5cli package root"
def test_drop_forming_rate_bar_from_package_root(self) -> None: def test_drop_forming_rate_bar_from_package_root(self) -> None:
"""Closed-bar trimming is available from the stable package surface.""" """Closed-bar trimming is available from the stable package surface."""
frame = pd.DataFrame({"time": [1, 2, 3], "close": [1.0, 1.1, 1.2]}) frame = pd.DataFrame({"time": [1, 2, 3], "close": [1.0, 1.1, 1.2]})
@@ -684,38 +658,6 @@ class TestStableSdkContract:
assert callable(calculate_projected_margin_ratio) assert callable(calculate_projected_margin_ratio)
assert callable(calculate_symbol_group_margin_ratio) assert callable(calculate_symbol_group_margin_ratio)
def test_resolve_rate_view_name_from_package_root(self, tmp_path: Path) -> None:
"""Rate view resolution is importable and honors require_existing."""
db_path = tmp_path / "rates.db"
with sqlite3.connect(db_path) as conn:
conn.execute(
"CREATE TABLE rates("
" symbol TEXT, timeframe INTEGER, time TEXT, close REAL)",
)
conn.execute(
"INSERT INTO rates(symbol, timeframe, time, close) VALUES (?, ?, ?, ?)",
("EURUSD", 1, "2024-01-01T00:00:00+00:00", 1.0),
)
create_rate_compatibility_views(conn)
assert resolve_rate_view_name(db_path, "EURUSD", "M1") == "rate_EURUSD__1"
missing = tmp_path / "missing.db"
with pytest.raises(ValueError, match="SQLite database not found"):
resolve_rate_view_name(missing, "EURUSD", "M1", require_existing=True)
def test_load_rate_data_from_package_root(self, tmp_path: Path) -> None:
"""SQLite rate loading normalizes timestamps through the stable API."""
db_path = tmp_path / "view.db"
with sqlite3.connect(db_path) as conn:
conn.execute(
'CREATE VIEW "rate_EURUSD__1" AS'
" SELECT '2024-01-01T00:00:00+00:00' AS time, 1.1 AS close",
)
frame = load_rate_data(db_path, "rate_EURUSD__1")
assert frame.index.name == "time"
assert abs(float(frame.iloc[0]["close"]) - 1.1) < 1e-9
def test_load_rate_series_from_sqlite_requires_managed_views( def test_load_rate_series_from_sqlite_requires_managed_views(
self, self,
tmp_path: Path, tmp_path: Path,
@@ -834,6 +776,38 @@ class TestStableSdkContract:
assert "time" not in result.columns assert "time" not in result.columns
assert "close" in result.columns assert "close" in result.columns
def test_rate_view_helpers_in_history_module(self, tmp_path: Path) -> None:
"""Rate view helpers are available from mt5cli.history."""
db_path = tmp_path / "rates.db"
with sqlite3.connect(db_path) as conn:
conn.execute(
"CREATE TABLE rates("
" symbol TEXT, timeframe INTEGER, time TEXT, close REAL)",
)
conn.execute(
"INSERT INTO rates(symbol, timeframe, time, close) VALUES (?, ?, ?, ?)",
("EURUSD", 1, "2024-01-01T00:00:00+00:00", 1.0),
)
create_rate_compatibility_views(conn)
assert resolve_rate_view_name(db_path, "EURUSD", "M1") == "rate_EURUSD__1"
missing = tmp_path / "missing.db"
with pytest.raises(ValueError, match="SQLite database not found"):
resolve_rate_view_name(missing, "EURUSD", "M1", require_existing=True)
def test_load_rate_data_in_history_module(self, tmp_path: Path) -> None:
"""SQLite rate loading normalizes timestamps through mt5cli.history."""
db_path = tmp_path / "view.db"
with sqlite3.connect(db_path) as conn:
conn.execute(
'CREATE VIEW "rate_EURUSD__1" AS'
" SELECT '2024-01-01T00:00:00+00:00' AS time, 1.1 AS close",
)
frame = load_rate_data(db_path, "rate_EURUSD__1")
assert frame.index.name == "time"
assert abs(float(frame.iloc[0]["close"]) - 1.1) < 1e-9
@pytest.mark.parametrize( @pytest.mark.parametrize(
"name", "name",
@@ -851,9 +825,6 @@ def test_pdmt5_pass_through_names_removed_from_public_contract(name: str) -> Non
assert name not in STABLE_SDK_EXPORTS, ( assert name not in STABLE_SDK_EXPORTS, (
f"{name!r} should not be 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__" assert name not in mt5cli.__all__, f"{name!r} should not be in mt5cli.__all__"