Shrink public API surface and remove storage re-export module (#74)
This commit is contained in:
@@ -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))
|
||||
|
||||
+5
-3
@@ -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)
|
||||
|
||||
+3
-5
@@ -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.
|
||||
|
||||
|
||||
+31
-84
@@ -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.
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Storage
|
||||
|
||||
::: mt5cli.storage
|
||||
+5
-9
@@ -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`).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-145
@@ -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",
|
||||
]
|
||||
|
||||
+2
-78
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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__"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user