[codex] fix mt5 adapter APIs (#36)
* fix mt5 adapter APIs * address PR feedback * fix zero ratio minimum volume sizing * Bump version to v0.8.0
This commit is contained in:
+29
-3
@@ -167,19 +167,45 @@ Resolution rules:
|
||||
|
||||
### Rate data loading
|
||||
|
||||
Use `load_rate_data()` to load a table or view from a SQLite path, or
|
||||
`load_rate_data_from_connection()` when you already have a connection:
|
||||
The canonical normalized rate table is `rates`; compatibility views are named
|
||||
with `rate_<symbol>__<timeframe>` for single-timeframe symbols or
|
||||
`rate_<symbol>__<granularity>_<timeframe>` when a symbol has multiple stored
|
||||
timeframes. `resolve_rate_table_name()` returns `rates`, while
|
||||
`resolve_rate_view_name()` returns the per-symbol compatibility view name.
|
||||
|
||||
Use `load_rate_data()` or `load_rate_series_from_sqlite(..., table=...)` to load
|
||||
a single table or view from a SQLite path. Use
|
||||
`load_rate_series_by_granularity()` to load multiple instrument/granularity
|
||||
targets without hard-coding view names:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from mt5cli import load_rate_data
|
||||
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
|
||||
|
||||
view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1", require_existing=True)
|
||||
rates = load_rate_data(Path("history.db"), view, count=1000)
|
||||
same_rates = load_rate_series_from_sqlite(Path("history.db"), table=view, count=1000)
|
||||
|
||||
table = resolve_rate_table_name("EURUSD", "M1") # "rates"
|
||||
series = load_rate_series_by_granularity(
|
||||
Path("history.db"),
|
||||
symbols=["EURUSD", "GBPUSD"],
|
||||
granularities=["M1", "H1"],
|
||||
count=500,
|
||||
)
|
||||
```
|
||||
|
||||
`count` returns the latest rows while preserving chronological order. Missing
|
||||
tables/views and mismatched `explicit_tables` lengths raise `ValueError` with
|
||||
the requested database target in the message.
|
||||
|
||||
The loader accepts close-based OHLC rate data or tick-like bid/ask data. It
|
||||
validates that `time` exists, parses timestamps with pandas, and returns a
|
||||
DataFrame indexed by ascending `DatetimeIndex` named `time`.
|
||||
|
||||
+20
-5
@@ -31,13 +31,25 @@ rates = collect_latest_rates_for_accounts_with_retries(
|
||||
### Latest closed rate bars
|
||||
|
||||
MetaTrader 5 `start_pos=0` includes the still-forming current bar as the last
|
||||
row. `collect_latest_closed_rates_for_accounts()` fetches `count + 1` bars,
|
||||
drops that row with `drop_forming_rate_bar()`, and validates each series is
|
||||
non-empty. Use `collect_latest_closed_rates_by_granularity()` when callers
|
||||
prefer keys such as `("EURUSD", "M1")` instead of integer timeframes.
|
||||
row. `fetch_latest_closed_rates()` handles one connected client; multi-account
|
||||
helpers fetch `count + 1` bars, drop that row with `drop_forming_rate_bar()`,
|
||||
and validate each series is non-empty. Returned frames are ordered
|
||||
oldest-to-newest and may contain fewer than `count` rows only when MT5 returns
|
||||
fewer closed bars.
|
||||
|
||||
```python
|
||||
from mt5cli import AccountSpec, collect_latest_closed_rates_by_granularity
|
||||
from mt5cli import (
|
||||
AccountSpec,
|
||||
collect_latest_closed_rates_by_granularity,
|
||||
fetch_latest_closed_rates,
|
||||
)
|
||||
|
||||
closed = fetch_latest_closed_rates(
|
||||
client,
|
||||
symbol="EURUSD",
|
||||
granularity="M1",
|
||||
count=500,
|
||||
)
|
||||
|
||||
rates = collect_latest_closed_rates_by_granularity(
|
||||
[AccountSpec(symbols=["EURUSD"], login=12345)],
|
||||
@@ -48,6 +60,9 @@ rates = collect_latest_closed_rates_by_granularity(
|
||||
closed_m1 = rates["EURUSD", "M1"]
|
||||
```
|
||||
|
||||
Use `collect_latest_closed_rates_by_granularity()` when callers prefer keys such
|
||||
as `("EURUSD", "M1")` instead of integer timeframes.
|
||||
|
||||
### Resolving credentials and `${ENV_VAR}` placeholders
|
||||
|
||||
`resolve_account_spec()` / `resolve_account_specs()` merge explicit override
|
||||
|
||||
+56
-12
@@ -4,38 +4,60 @@
|
||||
|
||||
## Trading-capable MT5 sessions
|
||||
|
||||
`mt5_trading_session()` complements the read-only `mt5_session()` helper in
|
||||
`sdk.py`. It yields a connected `pdmt5.Mt5TradingClient`, uses
|
||||
`Mt5Config.path` to launch the terminal when configured, and always calls
|
||||
`shutdown()` on exit.
|
||||
`create_trading_client()` and `mt5_trading_session()` complement the read-only
|
||||
`mt5_session()` helper in `sdk.py`. They return or yield an initialized
|
||||
`pdmt5.Mt5TradingClient`, use `Mt5Config.path` to launch the terminal when
|
||||
configured, and `mt5_trading_session()` always calls `shutdown()` on exit.
|
||||
|
||||
```python
|
||||
from pdmt5 import Mt5Config
|
||||
|
||||
from mt5cli import mt5_trading_session
|
||||
from mt5cli import create_trading_client, mt5_trading_session
|
||||
|
||||
with mt5_trading_session(
|
||||
Mt5Config(path=r"C:\Program Files\MetaTrader 5\terminal64.exe", login=12345),
|
||||
path=r"C:\Program Files\MetaTrader 5\terminal64.exe",
|
||||
login="12345",
|
||||
password="secret",
|
||||
server="Broker-Demo",
|
||||
retry_count=2,
|
||||
) as client:
|
||||
positions = client.positions_get_as_df(symbol="EURUSD")
|
||||
|
||||
client = create_trading_client(login=12345, server="Broker-Demo")
|
||||
try:
|
||||
account = client.account_info_as_dict()
|
||||
finally:
|
||||
client.shutdown()
|
||||
```
|
||||
|
||||
`login` accepts `int`, numeric `str`, or an empty string; empty strings are
|
||||
treated as unset. `path`, `password`, `server`, and `timeout` are forwarded to
|
||||
`pdmt5.Mt5Config`, and omitted `timeout` values keep the lower-level default.
|
||||
The read-only `Mt5CliClient` / `mt5_session()` API is unchanged.
|
||||
|
||||
## Operational trading helpers
|
||||
## State and order helpers
|
||||
|
||||
These helpers are strategy-agnostic and do not depend on signal detection,
|
||||
betting logic, or scheduling code in downstream applications.
|
||||
|
||||
```python
|
||||
from mt5cli import (
|
||||
calculate_spread_ratio,
|
||||
calculate_margin_and_volume,
|
||||
close_open_positions,
|
||||
detect_position_side,
|
||||
determine_order_limits,
|
||||
get_account_snapshot,
|
||||
get_positions_frame,
|
||||
get_symbol_snapshot,
|
||||
get_tick_snapshot,
|
||||
place_market_order,
|
||||
)
|
||||
|
||||
account = get_account_snapshot(client)
|
||||
symbol = get_symbol_snapshot(client, "EURUSD")
|
||||
tick = get_tick_snapshot(client, "EURUSD")
|
||||
positions = get_positions_frame(client, "EURUSD")
|
||||
side = detect_position_side(client, "EURUSD")
|
||||
spread_ratio = calculate_spread_ratio(client, "EURUSD")
|
||||
sizing = calculate_margin_and_volume(
|
||||
client,
|
||||
"EURUSD",
|
||||
@@ -49,11 +71,33 @@ limits = determine_order_limits(
|
||||
stop_loss_limit_ratio=0.01,
|
||||
take_profit_limit_ratio=0.02,
|
||||
)
|
||||
preview = place_market_order(
|
||||
client,
|
||||
symbol="EURUSD",
|
||||
volume=sizing["buy_volume"],
|
||||
order_side="BUY",
|
||||
sl=limits["stop_loss"],
|
||||
tp=limits["take_profit"],
|
||||
dry_run=True,
|
||||
)
|
||||
closed = close_open_positions(client, symbols="EURUSD", dry_run=True)
|
||||
```
|
||||
|
||||
Protective ratios must satisfy `0 <= ratio < 1`; `0` omits that level.
|
||||
`calculate_margin_and_volume()` clamps negative `margin_free` to `0.0`
|
||||
before sizing.
|
||||
`detect_position_side()` returns `long` for buy-only exposure, `short` for
|
||||
sell-only exposure, and `None` for no positions or mixed long/short exposure.
|
||||
`calculate_spread_ratio()` uses `(ask - bid) / ((ask + bid) / 2)` and raises
|
||||
`Mt5TradingError` when bid or ask is missing or non-positive.
|
||||
|
||||
SL/TP ratios for `determine_order_limits()` must satisfy `0 <= ratio < 1`; `0`
|
||||
omits that level. SL/TP prices are rounded with symbol `digits` metadata when
|
||||
available. `unit_margin_ratio` and `preserved_margin_ratio` for
|
||||
`calculate_margin_and_volume()` accept `0 <= ratio <= 1`; `unit_margin_ratio=0`
|
||||
requests one minimum valid unit when the post-reserve margin can afford it.
|
||||
Negative `margin_free` is clamped to `0.0` before sizing. Execution helpers
|
||||
return normalized dictionaries containing the request, response, status,
|
||||
retcode, and `dry_run` flag; `dry_run=True` never sends an order. Market order
|
||||
helpers mark known non-success MT5 retcodes as `status="failed"` while keeping
|
||||
the normalized response for inspection.
|
||||
|
||||
## Migration from mteor-local helpers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user