From f435544f07ea8b3dfa74767d6ee0fc79c73d3633 Mon Sep 17 00:00:00 2001 From: Daichi Narushima <1938249+dceoy@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:23:30 +0900 Subject: [PATCH] Shrink public API surface and remove storage re-export module (#74) --- README.md | 11 +- docs/api/history.md | 8 +- docs/api/index.md | 8 +- docs/api/public-contract.md | 115 ++++++--------------- docs/api/sdk.md | 3 +- docs/api/storage.md | 3 - docs/index.md | 14 +-- mkdocs.yml | 1 - mt5cli/__init__.py | 146 +-------------------------- mt5cli/contract.py | 80 +-------------- mt5cli/storage.py | 49 --------- tests/test_contracts.py | 195 +++++++++++++++--------------------- 12 files changed, 137 insertions(+), 496 deletions(-) delete mode 100644 docs/api/storage.md delete mode 100644 mt5cli/storage.py diff --git a/README.md b/README.md index 5bfbcd7..896edee 100644 --- a/README.md +++ b/README.md @@ -44,16 +44,14 @@ from datetime import UTC, datetime from pathlib import Path from mt5cli import ( - DataKind, - Dataset, MT5Client, build_config, collect_history, - export_dataframe, mt5_session, - normalize_dataframe, update_history_with_config, ) +from mt5cli.schemas import DataKind, normalize_dataframe +from mt5cli.utils import Dataset, export_dataframe # Persistent session for multiple calls 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. @@ -212,7 +210,8 @@ For automated pipelines, use the importable incremental API instead of re-fetchi ```python 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) client = Mt5DataClient(config=Mt5Config(login=12345)) diff --git a/docs/api/history.md b/docs/api/history.md index 75967ca..34c7a3f 100644 --- a/docs/api/history.md +++ b/docs/api/history.md @@ -182,12 +182,14 @@ targets without hard-coding view names: from pathlib import Path from mt5cli import ( - load_rate_data, load_rate_series_by_granularity, 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) rates = load_rate_data(Path("history.db"), view, count=1000) diff --git a/docs/api/index.md b/docs/api/index.md index 184ad52..5ab2183 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -13,7 +13,6 @@ responsibilities. | [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 | | [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 | | [Exceptions](exceptions.md) | Stable mt5cli exception types and MT5 error normalization | | [SDK](sdk.md) | Module-level fetch helpers, multi-account collectors, incremental history | @@ -30,15 +29,14 @@ flowchart TD CLI["mt5cli CLI"] --> Client Client --> SDK["sdk / pdmt5"] Client --> Schemas["schemas"] - Storage["storage"] --> History["history SQLite"] - Storage --> Utils["utils export"] + History["history SQLite"] --> Utils["utils export"] SDK --> PDMT5["pdmt5.Mt5DataClient"] ``` Downstream packages should depend on the package root exports documented in the [Public API Contract](public-contract.md) (`MT5Client`, -`DataKind`, `normalize_dataframe`, `collect_history`, `load_rate_data`, -`resolve_rate_view_name`, etc.) rather than private modules. +`collect_history`, `load_rate_series_from_sqlite`, etc.) rather than private +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. diff --git a/docs/api/public-contract.md b/docs/api/public-contract.md index 3b9fe67..d0d5686 100644 --- a/docs/api/public-contract.md +++ b/docs/api/public-contract.md @@ -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. 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 -APIs. - -## Public API tiers - -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. | +...`). The contract set `STABLE_SDK_EXPORTS` in `mt5cli.contract` enumerates +every package-root symbol. Lower-level helpers (schema utilities, export +functions, parser helpers, low-level MT5 wrappers) are available directly from +their owning modules (`mt5cli.schemas`, `mt5cli.utils`, `mt5cli.converters`, +`mt5cli.sdk`, etc.) and are not part of the root SDK surface. ## 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`). ### 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 | | `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` | -| `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 @@ -85,20 +64,13 @@ timestamp normalization in downstream apps. ### SQLite history collection and rate loading -| Symbol | Role | -| ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `collect_history` | One-shot date-range export into SQLite | -| `update_history`, `update_history_with_config` | Incremental append from `MAX(time)` cursors | -| `ThrottledHistoryUpdater` | Minimum interval between successful incremental updates; optional `update_backend` injection | -| `resolve_history_datasets`, `resolve_history_timeframes`, `resolve_history_tick_flags` | History pipeline configuration | -| `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 | -| `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. +| Symbol | Role | +| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `collect_history` | One-shot date-range export into SQLite | +| `update_history`, `update_history_with_config` | Incremental append from `MAX(time)` cursors | +| `ThrottledHistoryUpdater` | Minimum interval between successful incremental updates; optional `update_backend` injection | +| `RateTarget`, `build_rate_targets` | Neutral `(symbol, timeframe)` series descriptors | +| `load_rate_series_from_sqlite`, `load_rate_series_by_granularity` | Load one or many series; fail clearly when managed views are missing | See [History Collection (SQLite)](history.md) for schema, view naming, and ER diagrams. @@ -151,48 +123,23 @@ and returned as `status="failed"` with normalized `request` / `response` details ### 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 | +| Symbol | Role | +| -------------------------------------------------------------------------- | ----------------------------- | +| `Mt5CliError`, `Mt5ConnectionError`, `Mt5OperationError`, `Mt5SchemaError` | Stable mt5cli exception types | -## Secondary public exports +## Module-scoped helpers -These names remain importable from `mt5cli` and are covered by -`SECONDARY_PUBLIC_EXPORTS`, but they are oriented toward CLI/export/schema -integrations, parsing, and lower-level MT5 access rather than the stable core -SDK surface. Prefer the stable symbols above for downstream infrastructure -adapters. +Lower-level helpers are available from their owning modules and are not part +of the package-root stable surface. Import them directly when needed: -### Read-only MT5 data wrappers - -Module-level helpers open a transient connection per call. Prefer `mt5_session` -or `MT5Client` when making many requests in one process. - -| Area | Symbols | -| -------------------- | ---------------------------------------------------------------------------------------------------- | -| Rates | `copy_rates_from`, `copy_rates_from_pos`, `copy_rates_range`, `latest_rates`, `collect_latest_rates` | -| 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` | +| Module | Examples | +| ------------------- | ---------------------------------------------------------------------------------------------- | +| `mt5cli.history` | `resolve_rate_view_name`, `resolve_rate_tables`, `load_rate_data`, `build_rate_view_name` | +| `mt5cli.sdk` | `copy_rates_from`, `copy_ticks_from`, `account_info`, `symbols`, `mt5_summary`, `latest_rates` | +| `mt5cli.schemas` | `DataKind`, `normalize_dataframe`, `validate_schema`, `DEDUP_KEYS` | +| `mt5cli.utils` | `Dataset`, `IfExists`, `detect_format`, `export_dataframe`, `export_dataframe_to_sqlite` | +| `mt5cli.converters` | `normalize_symbol`, `ensure_utc`, `parse_date_range`, `granularity_name` | +| `mt5cli.exceptions` | `normalize_mt5_exception`, `call_with_normalized_errors`, `is_recoverable_mt5_error` | ## CLI commands @@ -246,7 +193,7 @@ their own adapter layer. ## Contract verification -`tests/test_contracts.py` asserts that every name in the stable and secondary -tier sets is importable from `mt5cli`, documents key closed-bar, rate-view, -SQLite loading, account-resolution, and trading-session behaviors, and keeps the -tier sets aligned with `__all__`. +`tests/test_contracts.py` asserts that every name in `STABLE_SDK_EXPORTS` is +importable from `mt5cli`, that all package-root exports are covered by the +stable set, and documents key closed-bar, SQLite loading, account-resolution, +and trading-session behaviors. diff --git a/docs/api/sdk.md b/docs/api/sdk.md index 9d975ef..d17934e 100644 --- a/docs/api/sdk.md +++ b/docs/api/sdk.md @@ -117,7 +117,8 @@ call it every iteration without over-fetching. ```python from pdmt5 import Mt5Config, Mt5DataClient -from mt5cli import Dataset, ThrottledHistoryUpdater +from mt5cli import ThrottledHistoryUpdater +from mt5cli.utils import Dataset updater = ThrottledHistoryUpdater( output="history.db", diff --git a/docs/api/storage.md b/docs/api/storage.md deleted file mode 100644 index dda244b..0000000 --- a/docs/api/storage.md +++ /dev/null @@ -1,3 +0,0 @@ -# Storage - -::: mt5cli.storage diff --git a/docs/index.md b/docs/index.md index 6abe618..415e72e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -42,19 +42,15 @@ from datetime import UTC, datetime from pathlib import Path from mt5cli import ( - DataKind, - Dataset, MT5Client, build_config, collect_history, - export_dataframe, - load_rate_data, - minimum_margins, 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 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`). diff --git a/mkdocs.yml b/mkdocs.yml index 125bba5..a9ce160 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -59,7 +59,6 @@ nav: - Public API Contract: api/public-contract.md - Client: api/client.md - Schemas: api/schemas.md - - Storage: api/storage.md - Converters: api/converters.md - Exceptions: api/exceptions.md - CLI: api/cli.md diff --git a/mt5cli/__init__.py b/mt5cli/__init__.py index 535588c..ff552fa 100644 --- a/mt5cli/__init__.py +++ b/mt5cli/__init__.py @@ -9,107 +9,34 @@ strategy responsibilities. from importlib.metadata import version from .client import MT5Client, build_config, mt5_session -from .contract import ( - 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 .contract import STABLE_SDK_EXPORTS from .exceptions import ( Mt5CliError, Mt5ConnectionError, Mt5OperationError, Mt5SchemaError, - call_with_normalized_errors, - is_recoverable_mt5_error, - normalize_mt5_exception, ) 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_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 ( AccountSpec, ThrottledHistoryUpdater, - account_info, 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, - 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_summary, - mt5_summary_as_df, - orders, - positions, - recent_history_deals, - recent_ticks, resolve_account_spec, resolve_account_specs, - substitute_env_placeholders, - substitute_mapping_values, - symbol_info, - symbol_info_tick, - symbols, - terminal_info, update_history, 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 ( - POSITION_COLUMNS, ExecutionStatus, MarginVolume, OrderExecutionResult, @@ -149,28 +76,13 @@ from .trading import ( update_sltp_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 __all__ = [ - "DEDUP_KEYS", - "KNOWN_MT5_TIME_COLUMNS", - "POSITION_COLUMNS", - "PUBLIC_EXPORT_TIERS", - "REQUIRED_COLUMNS", - "SECONDARY_PUBLIC_EXPORTS", "STABLE_SDK_EXPORTS", - "TIME_COLUMNS", "AccountSpec", - "DataKind", - "Dataset", "ExecutionStatus", - "IfExists", "MT5Client", "MarginVolume", "Mt5CliError", @@ -186,10 +98,8 @@ __all__ = [ "ProjectionMode", "RateTarget", "ThrottledHistoryUpdater", - "account_info", "build_config", "build_rate_targets", - "build_rate_view_name", "calculate_account_projected_margin_ratio", "calculate_margin_and_volume", "calculate_new_position_margin_ratio", @@ -201,29 +111,17 @@ __all__ = [ "calculate_symbol_group_margin_ratio", "calculate_trailing_stop_updates", "calculate_volume_by_margin", - "call_with_normalized_errors", "close_open_positions", "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", - "copy_rates_from", - "copy_rates_from_pos", - "copy_rates_range", - "copy_ticks_from", - "copy_ticks_range", "create_trading_client", - "detect_format", "detect_position_side", "determine_order_limits", "drop_forming_rate_bar", "ensure_symbol_selected", - "ensure_utc", "estimate_order_margin", - "export_dataframe", - "export_dataframe_to_sqlite", "extract_tick_price", "fetch_latest_closed_rates", "fetch_latest_closed_rates_for_trading_client", @@ -232,58 +130,16 @@ __all__ = [ "get_positions_frame", "get_symbol_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_from_sqlite", - "market_book", - "minimum_margins", "mt5_session", - "mt5_summary", - "mt5_summary_as_df", "mt5_trading_session", - "mt5_version", - "normalize_dataframe", - "normalize_mt5_exception", "normalize_order_volume", - "normalize_symbol", - "normalize_symbols", - "normalize_time_columns", - "orders", - "parse_date_range", - "parse_datetime", - "parse_tick_flags", - "parse_timeframe", "place_market_order", - "positions", - "recent_history_deals", - "recent_ticks", - "recent_window", "resolve_account_spec", "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_with_config", "update_sltp_for_open_positions", "update_trailing_stop_loss_for_open_positions", - "validate_schema", ] diff --git a/mt5cli/contract.py b/mt5cli/contract.py index 83d903d..3d8567e 100644 --- a/mt5cli/contract.py +++ b/mt5cli/contract.py @@ -1,4 +1,4 @@ -"""Downstream SDK export tiers for mt5cli.""" +"""Downstream SDK export tier for mt5cli.""" from __future__ import annotations @@ -22,7 +22,6 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({ "ThrottledHistoryUpdater", "build_config", "build_rate_targets", - "build_rate_view_name", "calculate_account_projected_margin_ratio", "calculate_margin_and_volume", "calculate_new_position_margin_ratio", @@ -34,7 +33,6 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({ "calculate_symbol_group_margin_ratio", "calculate_trailing_stop_updates", "calculate_volume_by_margin", - "call_with_normalized_errors", "close_open_positions", "collect_history", "collect_latest_closed_rates_by_granularity", @@ -54,92 +52,18 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({ "get_positions_frame", "get_symbol_snapshot", "get_tick_snapshot", - "is_recoverable_mt5_error", - "load_rate_data", - "load_rate_data_from_connection", "load_rate_series_by_granularity", "load_rate_series_from_sqlite", "mt5_session", "mt5_trading_session", - "normalize_mt5_exception", "normalize_order_volume", "place_market_order", "resolve_account_spec", "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_with_config", "update_sltp_for_open_positions", "update_trailing_stop_loss_for_open_positions", }) -SECONDARY_PUBLIC_EXPORTS: frozenset[str] = frozenset({ - "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", -] +__all__ = ["STABLE_SDK_EXPORTS"] diff --git a/mt5cli/storage.py b/mt5cli/storage.py deleted file mode 100644 index 3cf3d70..0000000 --- a/mt5cli/storage.py +++ /dev/null @@ -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", -] diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 1394f2f..7767b96 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -2,14 +2,16 @@ from __future__ import annotations -import re +import importlib import sqlite3 from datetime import UTC, datetime from importlib.metadata import requires -from pathlib import Path -from typing import get_type_hints +from typing import TYPE_CHECKING, get_type_hints from unittest.mock import MagicMock +if TYPE_CHECKING: + from pathlib import Path + import pandas as pd import pytest from pdmt5 import Mt5RuntimeError, Mt5TradingError @@ -17,15 +19,8 @@ from pytest_mock import MockerFixture # noqa: TC002 import mt5cli from mt5cli import ( - DEDUP_KEYS, - PUBLIC_EXPORT_TIERS, - REQUIRED_COLUMNS, - SECONDARY_PUBLIC_EXPORTS, STABLE_SDK_EXPORTS, - TIME_COLUMNS, AccountSpec, - DataKind, - Dataset, ExecutionStatus, MarginVolume, MT5Client, @@ -44,40 +39,56 @@ from mt5cli import ( calculate_projected_margin_ratio, calculate_symbol_group_margin_ratio, calculate_trailing_stop_updates, - call_with_normalized_errors, - detect_format, drop_forming_rate_bar, ensure_symbol_selected, - ensure_utc, - export_dataframe, - export_dataframe_to_sqlite, extract_tick_price, fetch_latest_closed_rates, fetch_latest_closed_rates_for_trading_client, fetch_latest_closed_rates_indexed, - granularity_name, - is_recoverable_mt5_error, - load_rate_data, load_rate_series_from_sqlite, mt5_session, mt5_trading_session, - normalize_dataframe, - normalize_mt5_exception, normalize_order_volume, + place_market_order, + resolve_account_spec, + resolve_account_specs, +) +from mt5cli.converters import ( + ensure_utc, + granularity_name, normalize_symbol, normalize_symbols, parse_date_range, - place_market_order, 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, +) +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, validate_schema, ) -from mt5cli.history import create_rate_compatibility_views -from mt5cli.retry import retry_with_backoff -from mt5cli.schemas import ensure_utc_columns, normalize_time_columns +from mt5cli.utils import ( + Dataset, + detect_format, + export_dataframe, + export_dataframe_to_sqlite, +) 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 +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: """Tests for the documented stable downstream SDK contract.""" @@ -548,51 +565,19 @@ class TestStableSdkContract: missing = sorted(STABLE_SDK_EXPORTS - set(mt5cli.__all__)) assert not missing, f"STABLE_SDK_EXPORTS missing from __all__: {missing}" - def test_public_export_tiers_are_disjoint_and_complete(self) -> None: - """Documented public tiers do not overlap and classify root exports.""" - assert PUBLIC_EXPORT_TIERS == { - "stable": STABLE_SDK_EXPORTS, - "secondary": SECONDARY_PUBLIC_EXPORTS, - } - assert not (STABLE_SDK_EXPORTS & SECONDARY_PUBLIC_EXPORTS) - tiered_exports = STABLE_SDK_EXPORTS | SECONDARY_PUBLIC_EXPORTS + def test_stable_exports_cover_root_api(self) -> None: + """STABLE_SDK_EXPORTS classifies every package-root symbol.""" + tier_metadata = {"STABLE_SDK_EXPORTS"} 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, ( - f"Tiered exports missing from __all__: {missing_from_root}" + f"STABLE_SDK_EXPORTS missing from __all__: {missing_from_root}" ) - tier_metadata_exports = { - "PUBLIC_EXPORT_TIERS", - "SECONDARY_PUBLIC_EXPORTS", - "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}" + unclassified = sorted(root_exports - STABLE_SDK_EXPORTS - tier_metadata) + assert not unclassified, ( + f"Root exports not in STABLE_SDK_EXPORTS: {unclassified}" ) @pytest.mark.parametrize("name", sorted(STABLE_SDK_EXPORTS)) @@ -600,17 +585,6 @@ class TestStableSdkContract: """Stable SDK names resolve through ``from mt5cli import ...``.""" 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: """Closed-bar trimming is available from the stable package surface.""" 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_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( self, tmp_path: Path, @@ -834,6 +776,38 @@ class TestStableSdkContract: assert "time" not 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( "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, ( 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__"