mt5cli 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mt5cli-0.6.0 → mt5cli-0.7.0}/PKG-INFO +67 -3
- {mt5cli-0.6.0 → mt5cli-0.7.0}/README.md +65 -1
- {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/index.md +7 -2
- {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/sdk.md +14 -3
- mt5cli-0.7.0/docs/api/trading.md +70 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/index.md +6 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/mkdocs.yml +1 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/__init__.py +10 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/cli.py +2 -2
- {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/history.py +9 -9
- {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/sdk.py +86 -14
- mt5cli-0.7.0/mt5cli/trading.py +210 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/utils.py +31 -50
- {mt5cli-0.6.0 → mt5cli-0.7.0}/pyproject.toml +2 -2
- {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_cli.py +5 -5
- {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_history.py +17 -1
- {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_sdk.py +144 -7
- mt5cli-0.7.0/tests/test_trading.py +356 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/test_utils.py +51 -14
- {mt5cli-0.6.0 → mt5cli-0.7.0}/uv.lock +5 -5
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.agents/skills/mt5cli/SKILL.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.claude/agents/codex.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.claude/settings.json +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/FUNDING.yml +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/dependabot.yml +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/renovate.json +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/workflows/ci.yml +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/workflows/claude.yml +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.github/workflows/release.yml +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/.gitignore +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/AGENTS.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/CLAUDE.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/LICENSE +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/cli.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/history.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/docs/api/utils.md +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/mt5cli/__main__.py +0 -0
- {mt5cli-0.6.0 → mt5cli-0.7.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mt5cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Command-line tool for MetaTrader 5
|
|
5
5
|
Project-URL: Repository, https://github.com/dceoy/mt5cli.git
|
|
6
6
|
Author-email: dceoy <dceoy@users.noreply.github.com>
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
17
17
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
18
18
|
Requires-Python: <3.14,>=3.11
|
|
19
19
|
Requires-Dist: click>=8.1.0
|
|
20
|
-
Requires-Dist: pdmt5>=0.
|
|
20
|
+
Requires-Dist: pdmt5>=0.3.0
|
|
21
21
|
Requires-Dist: pyarrow>=19.0.0
|
|
22
22
|
Requires-Dist: typer>=0.15.0
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
@@ -30,6 +30,12 @@ Command-line tool for exporting MetaTrader 5 data to CSV, JSON, Parquet, and SQL
|
|
|
30
30
|
|
|
31
31
|
Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5.
|
|
32
32
|
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
- **pdmt5** — canonical MT5 client, DataFrame/trading primitives, and MT5 constant parsing (`TIMEFRAME_*`, `COPY_TICKS_*`, order types).
|
|
36
|
+
- **mt5cli** — CLI commands, CSV/JSON/Parquet/SQLite export, SQLite history collection, rate views, and local batch/automation SDK helpers built on pdmt5.
|
|
37
|
+
- **mt5api** — sibling HTTP adapter for remote MT5 access; not a dependency of mt5cli.
|
|
38
|
+
|
|
33
39
|
## Features
|
|
34
40
|
|
|
35
41
|
- **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats
|
|
@@ -176,7 +182,8 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
|
|
|
176
182
|
```
|
|
177
183
|
|
|
178
184
|
- **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code.
|
|
179
|
-
- **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error
|
|
185
|
+
- **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate).
|
|
186
|
+
- **Trading session helpers**: use `mt5_trading_session()` for a trading-capable `pdmt5.Mt5TradingClient` that initializes/logs in via `Mt5Config.path` and always shuts down safely. Pair with `detect_position_side()`, `calculate_margin_and_volume()`, and `determine_order_limits()` for generic position and sizing utilities. The read-only `mt5_session()` / `Mt5CliClient` SDK is unchanged.
|
|
180
187
|
- **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
|
|
181
188
|
- **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `Mt5CliClient` that shuts down on exit.
|
|
182
189
|
- **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
|
|
@@ -188,6 +195,63 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
|
|
|
188
195
|
- Windows OS (MetaTrader 5 requirement)
|
|
189
196
|
- MetaTrader 5 platform installed
|
|
190
197
|
|
|
198
|
+
### Migration note for mteor
|
|
199
|
+
|
|
200
|
+
Replace local MT5 lifecycle and trading helper code with mt5cli imports:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
# Before (local mteor helpers)
|
|
204
|
+
# with local_mt5_trading_session(config) as client:
|
|
205
|
+
# side = local_detect_position_side(client, symbol)
|
|
206
|
+
# sizing = local_calculate_margin_and_volume(client, symbol, unit_ratio, preserved_ratio)
|
|
207
|
+
# limits = local_determine_order_limits(client, symbol, side, sl_ratio, tp_ratio)
|
|
208
|
+
|
|
209
|
+
# After (mt5cli shared layer)
|
|
210
|
+
from pdmt5 import Mt5Config
|
|
211
|
+
from mt5cli import (
|
|
212
|
+
calculate_margin_and_volume,
|
|
213
|
+
detect_position_side,
|
|
214
|
+
determine_order_limits,
|
|
215
|
+
mt5_trading_session,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
with mt5_trading_session(
|
|
219
|
+
Mt5Config(path=terminal_path, login=login), retry_count=2
|
|
220
|
+
) as client:
|
|
221
|
+
side = detect_position_side(client, symbol)
|
|
222
|
+
sizing = calculate_margin_and_volume(
|
|
223
|
+
client, symbol, unit_margin_ratio=0.5, preserved_margin_ratio=0.2
|
|
224
|
+
)
|
|
225
|
+
if side is not None:
|
|
226
|
+
limits = determine_order_limits(
|
|
227
|
+
client,
|
|
228
|
+
symbol,
|
|
229
|
+
side,
|
|
230
|
+
stop_loss_limit_ratio=0.01,
|
|
231
|
+
take_profit_limit_ratio=0.02,
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Throttled history updates use a separate read-only session:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from pdmt5 import Mt5Config, Mt5DataClient
|
|
239
|
+
|
|
240
|
+
from mt5cli import ThrottledHistoryUpdater
|
|
241
|
+
|
|
242
|
+
updater = ThrottledHistoryUpdater(
|
|
243
|
+
output="history.db", interval_seconds=60, suppress_errors=True
|
|
244
|
+
)
|
|
245
|
+
client = Mt5DataClient(config=Mt5Config(login=login))
|
|
246
|
+
client.initialize_and_login_mt5()
|
|
247
|
+
try:
|
|
248
|
+
updater.update(client, ["EURUSD"])
|
|
249
|
+
finally:
|
|
250
|
+
client.shutdown()
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Read-only collectors can keep using `mt5_session()` and `Mt5CliClient` without changes.
|
|
254
|
+
|
|
191
255
|
## Development
|
|
192
256
|
|
|
193
257
|
```bash
|
|
@@ -6,6 +6,12 @@ Command-line tool for exporting MetaTrader 5 data to CSV, JSON, Parquet, and SQL
|
|
|
6
6
|
|
|
7
7
|
Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5.
|
|
8
8
|
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
- **pdmt5** — canonical MT5 client, DataFrame/trading primitives, and MT5 constant parsing (`TIMEFRAME_*`, `COPY_TICKS_*`, order types).
|
|
12
|
+
- **mt5cli** — CLI commands, CSV/JSON/Parquet/SQLite export, SQLite history collection, rate views, and local batch/automation SDK helpers built on pdmt5.
|
|
13
|
+
- **mt5api** — sibling HTTP adapter for remote MT5 access; not a dependency of mt5cli.
|
|
14
|
+
|
|
9
15
|
## Features
|
|
10
16
|
|
|
11
17
|
- **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats
|
|
@@ -152,7 +158,8 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
|
|
|
152
158
|
```
|
|
153
159
|
|
|
154
160
|
- **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code.
|
|
155
|
-
- **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error
|
|
161
|
+
- **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability errors for history API methods without advancing the throttle (other `AttributeError` / `TypeError` values always propagate).
|
|
162
|
+
- **Trading session helpers**: use `mt5_trading_session()` for a trading-capable `pdmt5.Mt5TradingClient` that initializes/logs in via `Mt5Config.path` and always shuts down safely. Pair with `detect_position_side()`, `calculate_margin_and_volume()`, and `determine_order_limits()` for generic position and sizing utilities. The read-only `mt5_session()` / `Mt5CliClient` SDK is unchanged.
|
|
156
163
|
- **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
|
|
157
164
|
- **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `Mt5CliClient` that shuts down on exit.
|
|
158
165
|
- **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
|
|
@@ -164,6 +171,63 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
|
|
|
164
171
|
- Windows OS (MetaTrader 5 requirement)
|
|
165
172
|
- MetaTrader 5 platform installed
|
|
166
173
|
|
|
174
|
+
### Migration note for mteor
|
|
175
|
+
|
|
176
|
+
Replace local MT5 lifecycle and trading helper code with mt5cli imports:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
# Before (local mteor helpers)
|
|
180
|
+
# with local_mt5_trading_session(config) as client:
|
|
181
|
+
# side = local_detect_position_side(client, symbol)
|
|
182
|
+
# sizing = local_calculate_margin_and_volume(client, symbol, unit_ratio, preserved_ratio)
|
|
183
|
+
# limits = local_determine_order_limits(client, symbol, side, sl_ratio, tp_ratio)
|
|
184
|
+
|
|
185
|
+
# After (mt5cli shared layer)
|
|
186
|
+
from pdmt5 import Mt5Config
|
|
187
|
+
from mt5cli import (
|
|
188
|
+
calculate_margin_and_volume,
|
|
189
|
+
detect_position_side,
|
|
190
|
+
determine_order_limits,
|
|
191
|
+
mt5_trading_session,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
with mt5_trading_session(
|
|
195
|
+
Mt5Config(path=terminal_path, login=login), retry_count=2
|
|
196
|
+
) as client:
|
|
197
|
+
side = detect_position_side(client, symbol)
|
|
198
|
+
sizing = calculate_margin_and_volume(
|
|
199
|
+
client, symbol, unit_margin_ratio=0.5, preserved_margin_ratio=0.2
|
|
200
|
+
)
|
|
201
|
+
if side is not None:
|
|
202
|
+
limits = determine_order_limits(
|
|
203
|
+
client,
|
|
204
|
+
symbol,
|
|
205
|
+
side,
|
|
206
|
+
stop_loss_limit_ratio=0.01,
|
|
207
|
+
take_profit_limit_ratio=0.02,
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Throttled history updates use a separate read-only session:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from pdmt5 import Mt5Config, Mt5DataClient
|
|
215
|
+
|
|
216
|
+
from mt5cli import ThrottledHistoryUpdater
|
|
217
|
+
|
|
218
|
+
updater = ThrottledHistoryUpdater(
|
|
219
|
+
output="history.db", interval_seconds=60, suppress_errors=True
|
|
220
|
+
)
|
|
221
|
+
client = Mt5DataClient(config=Mt5Config(login=login))
|
|
222
|
+
client.initialize_and_login_mt5()
|
|
223
|
+
try:
|
|
224
|
+
updater.update(client, ["EURUSD"])
|
|
225
|
+
finally:
|
|
226
|
+
client.shutdown()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Read-only collectors can keep using `mt5_session()` and `Mt5CliClient` without changes.
|
|
230
|
+
|
|
167
231
|
## Development
|
|
168
232
|
|
|
169
233
|
```bash
|
|
@@ -18,6 +18,10 @@ Utility module providing constants, enums, Click parameter types, and helper fun
|
|
|
18
18
|
|
|
19
19
|
Programmatic SDK for read-only MetaTrader 5 data collection. Returns pandas DataFrames and provides `collect_history` for SQLite bulk collection.
|
|
20
20
|
|
|
21
|
+
### [Trading](trading.md)
|
|
22
|
+
|
|
23
|
+
Trading-capable session management and operational helpers built on `pdmt5.Mt5TradingClient`. Complements the read-only SDK without changing existing `Mt5CliClient` behavior.
|
|
24
|
+
|
|
21
25
|
### [History Collection (SQLite)](history.md)
|
|
22
26
|
|
|
23
27
|
SQLite storage helpers for the `collect-history` command schema, incremental updates, deduplication, indexes, and optional views.
|
|
@@ -28,8 +32,9 @@ The package follows a simple architecture built on top of pdmt5:
|
|
|
28
32
|
|
|
29
33
|
1. **CLI Layer** (`cli.py`): Typer application with subcommands that delegate to the SDK and export results.
|
|
30
34
|
2. **SDK Layer** (`sdk.py`): Read-only data access functions, `Mt5CliClient`, and `collect_history` orchestration.
|
|
31
|
-
3. **
|
|
32
|
-
4. **
|
|
35
|
+
3. **Trading Layer** (`trading.py`): Trading-capable sessions and operational helpers on `Mt5TradingClient`.
|
|
36
|
+
4. **Utils Layer** (`utils.py`): Constants, enums, custom Click parameter types, parsing helpers, and format detection/export utilities.
|
|
37
|
+
5. **Data Layer** (via `pdmt5`): Uses `Mt5DataClient`, `Mt5TradingClient`, and `Mt5Config` from the pdmt5 package for MetaTrader 5 access.
|
|
33
38
|
|
|
34
39
|
## Usage Guidelines
|
|
35
40
|
|
|
@@ -98,6 +98,17 @@ finally:
|
|
|
98
98
|
client.shutdown()
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
By default `Mt5TradingError`, `Mt5RuntimeError`,
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
By default recoverable errors (`Mt5TradingError`, `Mt5RuntimeError`,
|
|
102
|
+
`sqlite3.Error`, `ValueError`, `OSError`, and MT5 client capability
|
|
103
|
+
`AttributeError` / `TypeError` for history API methods) propagate so the caller
|
|
104
|
+
controls logging; pass `suppress_errors=True` to swallow them and return
|
|
105
|
+
`False` without advancing the throttle. Other `AttributeError` / `TypeError`
|
|
106
|
+
values always propagate. Input validation (`_resolve_update_history_request`)
|
|
107
|
+
runs before any MT5 or SQLite calls, but when `suppress_errors=True` the
|
|
108
|
+
resulting `ValueError` is suppressed along with other recoverable errors.
|
|
109
|
+
|
|
110
|
+
## Trading-capable sessions
|
|
111
|
+
|
|
112
|
+
For order placement and trading calculations, use the dedicated
|
|
113
|
+
[Trading module](trading.md). The read-only `Mt5CliClient` and `mt5_session()`
|
|
114
|
+
helpers in this module are unchanged.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Trading Module
|
|
2
|
+
|
|
3
|
+
::: mt5cli.trading
|
|
4
|
+
|
|
5
|
+
## Trading-capable MT5 sessions
|
|
6
|
+
|
|
7
|
+
`mt5_trading_session()` complements the read-only `mt5_session()` helper in
|
|
8
|
+
`sdk.py`. It yields a connected `pdmt5.Mt5TradingClient`, uses
|
|
9
|
+
`Mt5Config.path` to launch the terminal when configured, and always calls
|
|
10
|
+
`shutdown()` on exit.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from pdmt5 import Mt5Config
|
|
14
|
+
|
|
15
|
+
from mt5cli import mt5_trading_session
|
|
16
|
+
|
|
17
|
+
with mt5_trading_session(
|
|
18
|
+
Mt5Config(path=r"C:\Program Files\MetaTrader 5\terminal64.exe", login=12345),
|
|
19
|
+
retry_count=2,
|
|
20
|
+
) as client:
|
|
21
|
+
positions = client.positions_get_as_df(symbol="EURUSD")
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The read-only `Mt5CliClient` / `mt5_session()` API is unchanged.
|
|
25
|
+
|
|
26
|
+
## Operational trading helpers
|
|
27
|
+
|
|
28
|
+
These helpers are strategy-agnostic and do not depend on signal detection,
|
|
29
|
+
betting logic, or scheduling code in downstream applications.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from mt5cli import (
|
|
33
|
+
calculate_margin_and_volume,
|
|
34
|
+
detect_position_side,
|
|
35
|
+
determine_order_limits,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
side = detect_position_side(client, "EURUSD")
|
|
39
|
+
sizing = calculate_margin_and_volume(
|
|
40
|
+
client,
|
|
41
|
+
"EURUSD",
|
|
42
|
+
unit_margin_ratio=0.5,
|
|
43
|
+
preserved_margin_ratio=0.2,
|
|
44
|
+
)
|
|
45
|
+
limits = determine_order_limits(
|
|
46
|
+
client,
|
|
47
|
+
"EURUSD",
|
|
48
|
+
side="long",
|
|
49
|
+
stop_loss_limit_ratio=0.01,
|
|
50
|
+
take_profit_limit_ratio=0.02,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Protective ratios must satisfy `0 <= ratio < 1`; `0` omits that level.
|
|
55
|
+
`calculate_margin_and_volume()` clamps negative `margin_free` to `0.0`
|
|
56
|
+
before sizing.
|
|
57
|
+
|
|
58
|
+
## Migration from mteor-local helpers
|
|
59
|
+
|
|
60
|
+
| mteor-local concern | mt5cli replacement |
|
|
61
|
+
| -------------------------------------------------------- | ----------------------------------------------- |
|
|
62
|
+
| Manual terminal spawn/kill around trading code | `mt5_trading_session()` |
|
|
63
|
+
| Local position-side detection | `detect_position_side()` |
|
|
64
|
+
| Local margin/volume sizing | `calculate_margin_and_volume()` |
|
|
65
|
+
| Local SL/TP price derivation | `determine_order_limits()` |
|
|
66
|
+
| Throttled SQLite history loop with ad-hoc error handling | `ThrottledHistoryUpdater(suppress_errors=True)` |
|
|
67
|
+
|
|
68
|
+
Keep read-only data collection on `mt5_session()` / `Mt5CliClient`; use
|
|
69
|
+
`mt5_trading_session()` only where order placement or trading calculations are
|
|
70
|
+
required.
|
|
@@ -6,6 +6,12 @@ Command-line tool for MetaTrader 5 data export.
|
|
|
6
6
|
|
|
7
7
|
mt5cli is a CLI application that exports MetaTrader 5 trading data to multiple file formats. It is built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5.
|
|
8
8
|
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
- **pdmt5** — canonical MT5 client, DataFrame/trading primitives, and MT5 constant parsing (`TIMEFRAME_*`, `COPY_TICKS_*`, order types).
|
|
12
|
+
- **mt5cli** — CLI commands, CSV/JSON/Parquet/SQLite export, SQLite history collection, rate views, and local batch/automation SDK helpers built on pdmt5.
|
|
13
|
+
- **mt5api** — sibling HTTP adapter for remote MT5 access; not a dependency of mt5cli.
|
|
14
|
+
|
|
9
15
|
## Features
|
|
10
16
|
|
|
11
17
|
- **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats
|
|
@@ -61,6 +61,12 @@ from .sdk import (
|
|
|
61
61
|
from .sdk import (
|
|
62
62
|
version as mt5_version,
|
|
63
63
|
)
|
|
64
|
+
from .trading import (
|
|
65
|
+
calculate_margin_and_volume,
|
|
66
|
+
detect_position_side,
|
|
67
|
+
determine_order_limits,
|
|
68
|
+
mt5_trading_session,
|
|
69
|
+
)
|
|
64
70
|
from .utils import (
|
|
65
71
|
TICK_FLAG_MAP,
|
|
66
72
|
TIMEFRAME_MAP,
|
|
@@ -89,6 +95,7 @@ __all__ = [
|
|
|
89
95
|
"build_config",
|
|
90
96
|
"build_rate_targets",
|
|
91
97
|
"build_rate_view_name",
|
|
98
|
+
"calculate_margin_and_volume",
|
|
92
99
|
"collect_history",
|
|
93
100
|
"collect_latest_closed_rates_by_granularity",
|
|
94
101
|
"collect_latest_closed_rates_for_accounts",
|
|
@@ -101,6 +108,8 @@ __all__ = [
|
|
|
101
108
|
"copy_ticks_from",
|
|
102
109
|
"copy_ticks_range",
|
|
103
110
|
"detect_format",
|
|
111
|
+
"detect_position_side",
|
|
112
|
+
"determine_order_limits",
|
|
104
113
|
"drop_forming_rate_bar",
|
|
105
114
|
"export_dataframe",
|
|
106
115
|
"export_dataframe_to_sqlite",
|
|
@@ -117,6 +126,7 @@ __all__ = [
|
|
|
117
126
|
"mt5_session",
|
|
118
127
|
"mt5_summary",
|
|
119
128
|
"mt5_summary_as_df",
|
|
129
|
+
"mt5_trading_session",
|
|
120
130
|
"mt5_version",
|
|
121
131
|
"orders",
|
|
122
132
|
"parse_datetime",
|
|
@@ -347,7 +347,7 @@ def ticks_recent(
|
|
|
347
347
|
click_type=TICK_FLAGS_TYPE,
|
|
348
348
|
help="Tick flags (ALL, INFO, TRADE, or integer).",
|
|
349
349
|
),
|
|
350
|
-
] =
|
|
350
|
+
] = "ALL", # pyright: ignore[reportArgumentType]
|
|
351
351
|
) -> None:
|
|
352
352
|
"""Export ticks from a recent time window."""
|
|
353
353
|
client = _sdk_client(ctx)
|
|
@@ -656,7 +656,7 @@ def collect_history(
|
|
|
656
656
|
click_type=TICK_FLAGS_TYPE,
|
|
657
657
|
help="Tick copy flags (ALL, INFO, TRADE, or integer).",
|
|
658
658
|
),
|
|
659
|
-
] =
|
|
659
|
+
] = "ALL", # pyright: ignore[reportArgumentType]
|
|
660
660
|
if_exists: Annotated[
|
|
661
661
|
IfExists,
|
|
662
662
|
typer.Option(
|
|
@@ -10,9 +10,10 @@ from pathlib import Path
|
|
|
10
10
|
from typing import TYPE_CHECKING, Literal, cast
|
|
11
11
|
|
|
12
12
|
import pandas as pd
|
|
13
|
+
from pdmt5 import get_timeframe_name as _get_timeframe_name
|
|
13
14
|
|
|
14
15
|
from .utils import (
|
|
15
|
-
|
|
16
|
+
TIMEFRAME_NAMES,
|
|
16
17
|
Dataset,
|
|
17
18
|
IfExists,
|
|
18
19
|
parse_datetime,
|
|
@@ -27,7 +28,7 @@ if TYPE_CHECKING:
|
|
|
27
28
|
|
|
28
29
|
logger = logging.getLogger(__name__)
|
|
29
30
|
|
|
30
|
-
DEFAULT_HISTORY_TIMEFRAMES: tuple[str, ...] =
|
|
31
|
+
DEFAULT_HISTORY_TIMEFRAMES: tuple[str, ...] = TIMEFRAME_NAMES
|
|
31
32
|
|
|
32
33
|
_HISTORY_DEDUP_KEYS: dict[Dataset, tuple[tuple[str, ...], ...]] = {
|
|
33
34
|
Dataset.rates: (("symbol", "timeframe", "time"), ("symbol", "time")),
|
|
@@ -80,7 +81,7 @@ def resolve_history_timeframes(
|
|
|
80
81
|
seen: set[int] = set()
|
|
81
82
|
resolved: list[int] = []
|
|
82
83
|
for value in raw:
|
|
83
|
-
tf =
|
|
84
|
+
tf = parse_timeframe(value)
|
|
84
85
|
if tf not in seen:
|
|
85
86
|
seen.add(tf)
|
|
86
87
|
resolved.append(tf)
|
|
@@ -93,17 +94,16 @@ def resolve_history_tick_flags(flags: int | str) -> int:
|
|
|
93
94
|
Returns:
|
|
94
95
|
Integer tick flag value.
|
|
95
96
|
"""
|
|
96
|
-
if isinstance(flags, int):
|
|
97
|
-
return flags
|
|
98
97
|
return parse_tick_flags(flags)
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
def resolve_granularity_name(timeframe: int) -> str:
|
|
102
101
|
"""Return a granularity name for a timeframe integer when known."""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
try:
|
|
103
|
+
name = _get_timeframe_name(timeframe)
|
|
104
|
+
except ValueError:
|
|
105
|
+
return str(timeframe)
|
|
106
|
+
return name.removeprefix("TIMEFRAME_")
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
def drop_forming_rate_bar(df_rate: pd.DataFrame) -> pd.DataFrame:
|
|
@@ -44,6 +44,62 @@ T = TypeVar("T")
|
|
|
44
44
|
|
|
45
45
|
logger = logging.getLogger(__name__)
|
|
46
46
|
|
|
47
|
+
_RECOVERABLE_HISTORY_UPDATE_ERRORS: tuple[type[BaseException], ...] = (
|
|
48
|
+
Mt5TradingError,
|
|
49
|
+
Mt5RuntimeError,
|
|
50
|
+
sqlite3.Error,
|
|
51
|
+
ValueError,
|
|
52
|
+
OSError,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_MT5_CLIENT_CAPABILITY_METHODS: frozenset[str] = frozenset({
|
|
56
|
+
"copy_rates_range_as_df",
|
|
57
|
+
"copy_ticks_range_as_df",
|
|
58
|
+
"history_deals_get_as_df",
|
|
59
|
+
"history_orders_get_as_df",
|
|
60
|
+
})
|
|
61
|
+
_MT5_HISTORY_MODULE = Path(__file__).with_name("history.py").resolve()
|
|
62
|
+
_MT5_HISTORY_CLIENT_CALL_FUNCTIONS: frozenset[str] = frozenset({
|
|
63
|
+
"write_rates_dataset",
|
|
64
|
+
"write_ticks_dataset",
|
|
65
|
+
"write_history_dataset",
|
|
66
|
+
"_write_incremental_history_deals",
|
|
67
|
+
})
|
|
68
|
+
_NON_CALLABLE_TYPE_ERROR = re.compile(r"^'[^']+' object is not callable$")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _is_non_callable_history_client_type_error(exc: TypeError) -> bool:
|
|
72
|
+
"""Return whether a TypeError came from calling a history client API attribute."""
|
|
73
|
+
if not _NON_CALLABLE_TYPE_ERROR.match(str(exc)):
|
|
74
|
+
return False
|
|
75
|
+
tb = exc.__traceback__
|
|
76
|
+
if tb is None:
|
|
77
|
+
return False
|
|
78
|
+
while tb.tb_next is not None:
|
|
79
|
+
tb = tb.tb_next
|
|
80
|
+
frame = tb.tb_frame
|
|
81
|
+
return (
|
|
82
|
+
frame.f_code.co_name in _MT5_HISTORY_CLIENT_CALL_FUNCTIONS
|
|
83
|
+
and Path(frame.f_code.co_filename).resolve() == _MT5_HISTORY_MODULE
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _is_mt5_client_capability_error(exc: BaseException) -> bool:
|
|
88
|
+
"""Return whether an error indicates an incompatible MT5 client API surface."""
|
|
89
|
+
if isinstance(exc, AttributeError):
|
|
90
|
+
msg = str(exc)
|
|
91
|
+
if msg.startswith("MT5 client is missing required method:"):
|
|
92
|
+
return True
|
|
93
|
+
name = getattr(exc, "name", None)
|
|
94
|
+
return isinstance(name, str) and name in _MT5_CLIENT_CAPABILITY_METHODS
|
|
95
|
+
if isinstance(exc, TypeError):
|
|
96
|
+
msg = str(exc)
|
|
97
|
+
if msg.startswith("MT5 client attribute is not callable:"):
|
|
98
|
+
return True
|
|
99
|
+
return _is_non_callable_history_client_type_error(exc)
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
47
103
|
__all__ = [
|
|
48
104
|
"AccountSpec",
|
|
49
105
|
"Mt5CliClient",
|
|
@@ -88,14 +144,10 @@ __all__ = [
|
|
|
88
144
|
|
|
89
145
|
|
|
90
146
|
def _coerce_timeframe(timeframe: int | str) -> int:
|
|
91
|
-
if isinstance(timeframe, int):
|
|
92
|
-
return timeframe
|
|
93
147
|
return parse_timeframe(timeframe)
|
|
94
148
|
|
|
95
149
|
|
|
96
150
|
def _coerce_tick_flags(flags: int | str) -> int:
|
|
97
|
-
if isinstance(flags, int):
|
|
98
|
-
return flags
|
|
99
151
|
return parse_tick_flags(flags)
|
|
100
152
|
|
|
101
153
|
|
|
@@ -1014,10 +1066,14 @@ class ThrottledHistoryUpdater:
|
|
|
1014
1066
|
include_account_events: Include account-level cash events.
|
|
1015
1067
|
interval_seconds: Minimum seconds between successful updates. Values
|
|
1016
1068
|
``<= 0`` update on every call.
|
|
1017
|
-
suppress_errors: When True, ``Mt5TradingError``,
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1069
|
+
suppress_errors: When True, recoverable errors (``Mt5TradingError``,
|
|
1070
|
+
``Mt5RuntimeError``, ``sqlite3.Error``, ``ValueError``,
|
|
1071
|
+
``OSError``, and MT5 client capability ``AttributeError`` /
|
|
1072
|
+
``TypeError`` for history API methods) raised during an update
|
|
1073
|
+
are swallowed and :meth:`update` returns False without advancing
|
|
1074
|
+
the throttle. Other ``AttributeError`` / ``TypeError`` values
|
|
1075
|
+
always propagate. When False (default), recoverable errors
|
|
1076
|
+
propagate so callers control logging.
|
|
1021
1077
|
"""
|
|
1022
1078
|
self.output = output
|
|
1023
1079
|
self.datasets = datasets
|
|
@@ -1057,16 +1113,27 @@ class ThrottledHistoryUpdater:
|
|
|
1057
1113
|
Returns:
|
|
1058
1114
|
True if an update ran successfully, False if it was throttled or
|
|
1059
1115
|
(when ``suppress_errors`` is True) failed with a recoverable error.
|
|
1116
|
+
When ``suppress_errors`` is False, recoverable update failures
|
|
1117
|
+
propagate to the caller.
|
|
1060
1118
|
|
|
1061
1119
|
Raises:
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
False.
|
|
1120
|
+
AttributeError: MT5 client capability mismatch when
|
|
1121
|
+
``suppress_errors`` is False, or any other attribute error.
|
|
1122
|
+
TypeError: MT5 client capability mismatch when ``suppress_errors``
|
|
1123
|
+
is False, or any other type error.
|
|
1066
1124
|
"""
|
|
1067
1125
|
if not self.should_update():
|
|
1068
1126
|
return False
|
|
1069
1127
|
try:
|
|
1128
|
+
_resolve_update_history_request(
|
|
1129
|
+
output=self.output,
|
|
1130
|
+
symbols=symbols,
|
|
1131
|
+
datasets=self.datasets,
|
|
1132
|
+
timeframes=self.timeframes,
|
|
1133
|
+
flags=self.flags,
|
|
1134
|
+
lookback_hours=self.lookback_hours,
|
|
1135
|
+
date_to=None,
|
|
1136
|
+
)
|
|
1070
1137
|
update_history(
|
|
1071
1138
|
client=client,
|
|
1072
1139
|
output=self.output,
|
|
@@ -1078,11 +1145,16 @@ class ThrottledHistoryUpdater:
|
|
|
1078
1145
|
with_views=self.with_views,
|
|
1079
1146
|
include_account_events=self.include_account_events,
|
|
1080
1147
|
)
|
|
1081
|
-
except
|
|
1148
|
+
except _RECOVERABLE_HISTORY_UPDATE_ERRORS:
|
|
1082
1149
|
if self.suppress_errors:
|
|
1083
1150
|
logger.warning("Suppressed history update error", exc_info=True)
|
|
1084
1151
|
return False
|
|
1085
1152
|
raise
|
|
1153
|
+
except (AttributeError, TypeError) as exc:
|
|
1154
|
+
if self.suppress_errors and _is_mt5_client_capability_error(exc):
|
|
1155
|
+
logger.warning("Suppressed history update error", exc_info=True)
|
|
1156
|
+
return False
|
|
1157
|
+
raise
|
|
1086
1158
|
self._last_update_monotonic = time.monotonic()
|
|
1087
1159
|
return True
|
|
1088
1160
|
|
|
@@ -1095,7 +1167,7 @@ def collect_history(
|
|
|
1095
1167
|
*,
|
|
1096
1168
|
datasets: set[Dataset] | None = None,
|
|
1097
1169
|
timeframe: int | str = 1,
|
|
1098
|
-
flags: int | str =
|
|
1170
|
+
flags: int | str = "ALL",
|
|
1099
1171
|
if_exists: IfExists = IfExists.FAIL,
|
|
1100
1172
|
with_views: bool = False,
|
|
1101
1173
|
config: Mt5Config | None = None,
|