mt5cli 0.5.2__tar.gz → 0.6.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.5.2 → mt5cli-0.6.0}/PKG-INFO +19 -2
- {mt5cli-0.5.2 → mt5cli-0.6.0}/README.md +18 -1
- {mt5cli-0.5.2 → mt5cli-0.6.0}/docs/api/history.md +12 -0
- mt5cli-0.6.0/docs/api/sdk.md +103 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/mt5cli/__init__.py +18 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/mt5cli/history.py +66 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/mt5cli/sdk.py +442 -2
- {mt5cli-0.5.2 → mt5cli-0.6.0}/pyproject.toml +1 -1
- {mt5cli-0.5.2 → mt5cli-0.6.0}/tests/test_history.py +92 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/tests/test_sdk.py +507 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/uv.lock +4 -4
- mt5cli-0.5.2/docs/api/sdk.md +0 -3
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.agents/skills/mt5cli/SKILL.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.claude/agents/codex.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.claude/settings.json +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.github/FUNDING.yml +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.github/dependabot.yml +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.github/renovate.json +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.github/workflows/ci.yml +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.github/workflows/claude.yml +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.github/workflows/release.yml +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/.gitignore +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/AGENTS.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/CLAUDE.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/LICENSE +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/docs/api/cli.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/docs/api/index.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/docs/api/utils.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/docs/index.md +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/mkdocs.yml +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/mt5cli/__main__.py +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/mt5cli/cli.py +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/mt5cli/utils.py +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/tests/__init__.py +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/tests/test_cli.py +0 -0
- {mt5cli-0.5.2 → mt5cli-0.6.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mt5cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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>
|
|
@@ -160,7 +160,24 @@ update_history_with_config(
|
|
|
160
160
|
- **Rate view resolution**: use `resolve_rate_view_name()` / `resolve_rate_view_names()` to map symbols and granularities to existing SQLite compatibility views without creating databases. Both accept `None` (or a missing path) and return deterministic default names unless `require_existing=True`.
|
|
161
161
|
- **Rate view loading**: use `load_rate_data()` / `load_rate_data_from_connection()` to load a SQLite rate table or view into a `DatetimeIndex` DataFrame.
|
|
162
162
|
- **Multi-series rate loading**: use `build_rate_targets()` to build neutral `RateTarget(symbol, timeframe)` pairs, `resolve_rate_tables()` to map them to table/view names (pass `require_existing=True` for strict resolution), and `load_rate_series_from_sqlite()` to load them into a mapping keyed by `(symbol, integer timeframe)`. The loader requires existing managed views unless `explicit_tables` is supplied, and rejects duplicate `(symbol, timeframe)` targets.
|
|
163
|
-
- **Multi-account latest rates**: use `collect_latest_rates_for_accounts()` with `AccountSpec` to read the latest bars for several account groups, merged into a `(symbol, integer timeframe)` mapping.
|
|
163
|
+
- **Multi-account latest rates**: use `collect_latest_rates_for_accounts()` with `AccountSpec` to read the latest bars for several account groups, merged into a `(symbol, integer timeframe)` mapping. For long-running pollers, `collect_latest_rates_for_accounts_with_retries()` adds bounded exponential backoff that retries only `pdmt5.Mt5TradingError` / `pdmt5.Mt5RuntimeError` and re-raises once `retry_count` is exhausted.
|
|
164
|
+
- **Latest closed bars**: use `collect_latest_closed_rates_for_accounts()` when downstream logic must exclude the still-forming current bar. It fetches `count + 1` bars at `start_pos=0`, drops the last row with `drop_forming_rate_bar()`, and validates each series is non-empty. `collect_latest_closed_rates_by_granularity()` returns the same data keyed by `(symbol, granularity_name)` such as `("EURUSD", "M1")`.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from mt5cli import AccountSpec, collect_latest_closed_rates_by_granularity
|
|
168
|
+
|
|
169
|
+
rates = collect_latest_closed_rates_by_granularity(
|
|
170
|
+
[AccountSpec(symbols=["EURUSD", "GBPUSD"], login=12345)],
|
|
171
|
+
["M1", "H1"],
|
|
172
|
+
count=500,
|
|
173
|
+
retry_count=3,
|
|
174
|
+
)
|
|
175
|
+
eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **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`/`sqlite3.Error` and let the caller decide logging.
|
|
180
|
+
- **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.
|
|
164
181
|
- **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.
|
|
165
182
|
- **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
|
|
166
183
|
- **Recent ticks and margins**: `recent_ticks()` and `minimum_margins()` SDK helpers (and matching CLI commands) cover common downstream read-only queries.
|
|
@@ -136,7 +136,24 @@ update_history_with_config(
|
|
|
136
136
|
- **Rate view resolution**: use `resolve_rate_view_name()` / `resolve_rate_view_names()` to map symbols and granularities to existing SQLite compatibility views without creating databases. Both accept `None` (or a missing path) and return deterministic default names unless `require_existing=True`.
|
|
137
137
|
- **Rate view loading**: use `load_rate_data()` / `load_rate_data_from_connection()` to load a SQLite rate table or view into a `DatetimeIndex` DataFrame.
|
|
138
138
|
- **Multi-series rate loading**: use `build_rate_targets()` to build neutral `RateTarget(symbol, timeframe)` pairs, `resolve_rate_tables()` to map them to table/view names (pass `require_existing=True` for strict resolution), and `load_rate_series_from_sqlite()` to load them into a mapping keyed by `(symbol, integer timeframe)`. The loader requires existing managed views unless `explicit_tables` is supplied, and rejects duplicate `(symbol, timeframe)` targets.
|
|
139
|
-
- **Multi-account latest rates**: use `collect_latest_rates_for_accounts()` with `AccountSpec` to read the latest bars for several account groups, merged into a `(symbol, integer timeframe)` mapping.
|
|
139
|
+
- **Multi-account latest rates**: use `collect_latest_rates_for_accounts()` with `AccountSpec` to read the latest bars for several account groups, merged into a `(symbol, integer timeframe)` mapping. For long-running pollers, `collect_latest_rates_for_accounts_with_retries()` adds bounded exponential backoff that retries only `pdmt5.Mt5TradingError` / `pdmt5.Mt5RuntimeError` and re-raises once `retry_count` is exhausted.
|
|
140
|
+
- **Latest closed bars**: use `collect_latest_closed_rates_for_accounts()` when downstream logic must exclude the still-forming current bar. It fetches `count + 1` bars at `start_pos=0`, drops the last row with `drop_forming_rate_bar()`, and validates each series is non-empty. `collect_latest_closed_rates_by_granularity()` returns the same data keyed by `(symbol, granularity_name)` such as `("EURUSD", "M1")`.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from mt5cli import AccountSpec, collect_latest_closed_rates_by_granularity
|
|
144
|
+
|
|
145
|
+
rates = collect_latest_closed_rates_by_granularity(
|
|
146
|
+
[AccountSpec(symbols=["EURUSD", "GBPUSD"], login=12345)],
|
|
147
|
+
["M1", "H1"],
|
|
148
|
+
count=500,
|
|
149
|
+
retry_count=3,
|
|
150
|
+
)
|
|
151
|
+
eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- **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`/`sqlite3.Error` and let the caller decide logging.
|
|
156
|
+
- **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.
|
|
140
157
|
- **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.
|
|
141
158
|
- **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
|
|
142
159
|
- **Recent ticks and margins**: `recent_ticks()` and `minimum_margins()` SDK helpers (and matching CLI commands) cover common downstream read-only queries.
|
|
@@ -215,3 +215,15 @@ frame = series["EURUSD", 1] # keyed by (symbol, integer timeframe)
|
|
|
215
215
|
requires existing managed `rate_*` compatibility views and raises
|
|
216
216
|
`ValueError` when they are missing. Duplicate `(symbol, timeframe)` targets
|
|
217
217
|
are rejected.
|
|
218
|
+
- `load_rate_series_by_granularity()` is a thin wrapper that builds the targets,
|
|
219
|
+
loads the series, and rekeys the result by granularity name to avoid
|
|
220
|
+
converting integer timeframes downstream:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from mt5cli import load_rate_series_by_granularity
|
|
224
|
+
|
|
225
|
+
series = load_rate_series_by_granularity(
|
|
226
|
+
"history.db", ["EURUSD"], ["M1", "H1"], count=1000
|
|
227
|
+
)
|
|
228
|
+
frame = series["EURUSD", "M1"] # keyed by (symbol | None, granularity_name)
|
|
229
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# SDK Module
|
|
2
|
+
|
|
3
|
+
::: mt5cli.sdk
|
|
4
|
+
|
|
5
|
+
## Resilient multi-account orchestration
|
|
6
|
+
|
|
7
|
+
The SDK ships strategy-agnostic helpers for building long-running collectors on
|
|
8
|
+
top of the read-only client. None of them depend on a particular trading
|
|
9
|
+
application.
|
|
10
|
+
|
|
11
|
+
### Retrying transient rate collection
|
|
12
|
+
|
|
13
|
+
`collect_latest_rates_for_accounts_with_retries()` wraps
|
|
14
|
+
`collect_latest_rates_for_accounts()` with bounded exponential backoff. Only
|
|
15
|
+
`pdmt5.Mt5TradingError` and `pdmt5.Mt5RuntimeError` are retried; the final
|
|
16
|
+
failure is re-raised once `retry_count` is exhausted.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from mt5cli import AccountSpec, collect_latest_rates_for_accounts_with_retries
|
|
20
|
+
|
|
21
|
+
accounts = [AccountSpec(symbols=["EURUSD"], login=12345)]
|
|
22
|
+
rates = collect_latest_rates_for_accounts_with_retries(
|
|
23
|
+
accounts,
|
|
24
|
+
["M1", "H1"],
|
|
25
|
+
count=500,
|
|
26
|
+
retry_count=3,
|
|
27
|
+
backoff_base=2, # sleeps 2s, 4s, 8s between attempts
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Latest closed rate bars
|
|
32
|
+
|
|
33
|
+
MetaTrader 5 `start_pos=0` includes the still-forming current bar as the last
|
|
34
|
+
row. `collect_latest_closed_rates_for_accounts()` fetches `count + 1` bars,
|
|
35
|
+
drops that row with `drop_forming_rate_bar()`, and validates each series is
|
|
36
|
+
non-empty. Use `collect_latest_closed_rates_by_granularity()` when callers
|
|
37
|
+
prefer keys such as `("EURUSD", "M1")` instead of integer timeframes.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from mt5cli import AccountSpec, collect_latest_closed_rates_by_granularity
|
|
41
|
+
|
|
42
|
+
rates = collect_latest_closed_rates_by_granularity(
|
|
43
|
+
[AccountSpec(symbols=["EURUSD"], login=12345)],
|
|
44
|
+
["M1", "H1"],
|
|
45
|
+
count=500,
|
|
46
|
+
retry_count=3,
|
|
47
|
+
)
|
|
48
|
+
closed_m1 = rates["EURUSD", "M1"]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Resolving credentials and `${ENV_VAR}` placeholders
|
|
52
|
+
|
|
53
|
+
`resolve_account_spec()` / `resolve_account_specs()` merge explicit override
|
|
54
|
+
values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders, keeping
|
|
55
|
+
secrets out of plan/config files. A missing environment variable raises
|
|
56
|
+
`ValueError`.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import os
|
|
60
|
+
|
|
61
|
+
from mt5cli import AccountSpec, resolve_account_specs
|
|
62
|
+
|
|
63
|
+
os.environ["MT5_LOGIN"] = "12345"
|
|
64
|
+
os.environ["MT5_PASSWORD"] = "secret"
|
|
65
|
+
accounts = [
|
|
66
|
+
AccountSpec(symbols=["EURUSD"], login="${MT5_LOGIN}", password="${MT5_PASSWORD}")
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
resolved = resolve_account_specs(accounts, server="Broker-Demo")
|
|
70
|
+
# resolved[0].login == "12345", resolved[0].server == "Broker-Demo"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Throttled incremental history updates
|
|
74
|
+
|
|
75
|
+
`ThrottledHistoryUpdater` wraps `update_history()` with a minimum interval
|
|
76
|
+
between successful runs (using a monotonic clock), so an application loop can
|
|
77
|
+
call it every iteration without over-fetching.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from pdmt5 import Mt5Config, Mt5DataClient
|
|
81
|
+
|
|
82
|
+
from mt5cli import Dataset, ThrottledHistoryUpdater
|
|
83
|
+
|
|
84
|
+
updater = ThrottledHistoryUpdater(
|
|
85
|
+
output="history.db",
|
|
86
|
+
datasets={Dataset.rates},
|
|
87
|
+
timeframes=["M1"],
|
|
88
|
+
interval_seconds=60, # <= 0 updates on every call
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
client = Mt5DataClient(config=Mt5Config(login=12345))
|
|
92
|
+
client.initialize_and_login_mt5()
|
|
93
|
+
try:
|
|
94
|
+
while True:
|
|
95
|
+
updater.update(client, ["EURUSD", "GBPUSD"]) # no-op until 60s elapse
|
|
96
|
+
# ... do other work; break when shutting down ...
|
|
97
|
+
finally:
|
|
98
|
+
client.shutdown()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
By default `Mt5TradingError`, `Mt5RuntimeError`, and `sqlite3.Error` propagate so
|
|
102
|
+
the caller controls logging; pass `suppress_errors=True` to swallow them and
|
|
103
|
+
return `False` without advancing the throttle.
|
|
@@ -6,8 +6,10 @@ from .history import (
|
|
|
6
6
|
RateTarget,
|
|
7
7
|
build_rate_targets,
|
|
8
8
|
build_rate_view_name,
|
|
9
|
+
drop_forming_rate_bar,
|
|
9
10
|
load_rate_data,
|
|
10
11
|
load_rate_data_from_connection,
|
|
12
|
+
load_rate_series_by_granularity,
|
|
11
13
|
load_rate_series_from_sqlite,
|
|
12
14
|
resolve_history_datasets,
|
|
13
15
|
resolve_history_tick_flags,
|
|
@@ -19,11 +21,15 @@ from .history import (
|
|
|
19
21
|
from .sdk import (
|
|
20
22
|
AccountSpec,
|
|
21
23
|
Mt5CliClient,
|
|
24
|
+
ThrottledHistoryUpdater,
|
|
22
25
|
account_info,
|
|
23
26
|
build_config,
|
|
24
27
|
collect_history,
|
|
28
|
+
collect_latest_closed_rates_by_granularity,
|
|
29
|
+
collect_latest_closed_rates_for_accounts,
|
|
25
30
|
collect_latest_rates,
|
|
26
31
|
collect_latest_rates_for_accounts,
|
|
32
|
+
collect_latest_rates_for_accounts_with_retries,
|
|
27
33
|
copy_rates_from,
|
|
28
34
|
copy_rates_from_pos,
|
|
29
35
|
copy_rates_range,
|
|
@@ -42,6 +48,9 @@ from .sdk import (
|
|
|
42
48
|
positions,
|
|
43
49
|
recent_history_deals,
|
|
44
50
|
recent_ticks,
|
|
51
|
+
resolve_account_spec,
|
|
52
|
+
resolve_account_specs,
|
|
53
|
+
substitute_env_placeholders,
|
|
45
54
|
symbol_info,
|
|
46
55
|
symbol_info_tick,
|
|
47
56
|
symbols,
|
|
@@ -75,19 +84,24 @@ __all__ = [
|
|
|
75
84
|
"IfExists",
|
|
76
85
|
"Mt5CliClient",
|
|
77
86
|
"RateTarget",
|
|
87
|
+
"ThrottledHistoryUpdater",
|
|
78
88
|
"account_info",
|
|
79
89
|
"build_config",
|
|
80
90
|
"build_rate_targets",
|
|
81
91
|
"build_rate_view_name",
|
|
82
92
|
"collect_history",
|
|
93
|
+
"collect_latest_closed_rates_by_granularity",
|
|
94
|
+
"collect_latest_closed_rates_for_accounts",
|
|
83
95
|
"collect_latest_rates",
|
|
84
96
|
"collect_latest_rates_for_accounts",
|
|
97
|
+
"collect_latest_rates_for_accounts_with_retries",
|
|
85
98
|
"copy_rates_from",
|
|
86
99
|
"copy_rates_from_pos",
|
|
87
100
|
"copy_rates_range",
|
|
88
101
|
"copy_ticks_from",
|
|
89
102
|
"copy_ticks_range",
|
|
90
103
|
"detect_format",
|
|
104
|
+
"drop_forming_rate_bar",
|
|
91
105
|
"export_dataframe",
|
|
92
106
|
"export_dataframe_to_sqlite",
|
|
93
107
|
"history_deals",
|
|
@@ -96,6 +110,7 @@ __all__ = [
|
|
|
96
110
|
"latest_rates",
|
|
97
111
|
"load_rate_data",
|
|
98
112
|
"load_rate_data_from_connection",
|
|
113
|
+
"load_rate_series_by_granularity",
|
|
99
114
|
"load_rate_series_from_sqlite",
|
|
100
115
|
"market_book",
|
|
101
116
|
"minimum_margins",
|
|
@@ -110,12 +125,15 @@ __all__ = [
|
|
|
110
125
|
"positions",
|
|
111
126
|
"recent_history_deals",
|
|
112
127
|
"recent_ticks",
|
|
128
|
+
"resolve_account_spec",
|
|
129
|
+
"resolve_account_specs",
|
|
113
130
|
"resolve_history_datasets",
|
|
114
131
|
"resolve_history_tick_flags",
|
|
115
132
|
"resolve_history_timeframes",
|
|
116
133
|
"resolve_rate_tables",
|
|
117
134
|
"resolve_rate_view_name",
|
|
118
135
|
"resolve_rate_view_names",
|
|
136
|
+
"substitute_env_placeholders",
|
|
119
137
|
"symbol_info",
|
|
120
138
|
"symbol_info_tick",
|
|
121
139
|
"symbols",
|
|
@@ -106,6 +106,23 @@ def resolve_granularity_name(timeframe: int) -> str:
|
|
|
106
106
|
return str(timeframe)
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def drop_forming_rate_bar(df_rate: pd.DataFrame) -> pd.DataFrame:
|
|
110
|
+
"""Return closed bars from chronologically ordered MT5 rate data.
|
|
111
|
+
|
|
112
|
+
MetaTrader 5 ``copy_rates_from_pos(start_pos=0)`` includes the still-forming
|
|
113
|
+
current bar as the last row. Slice it off so downstream logic only sees
|
|
114
|
+
completed bars. Empty frames and single-row frames return empty results.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
df_rate: Rate data ordered oldest-to-newest with the forming bar last.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A new DataFrame with all rows except the last. Index and columns are
|
|
121
|
+
preserved. The input frame is not modified.
|
|
122
|
+
"""
|
|
123
|
+
return df_rate.iloc[:-1].copy()
|
|
124
|
+
|
|
125
|
+
|
|
109
126
|
def build_rate_view_name(
|
|
110
127
|
*,
|
|
111
128
|
symbol: str,
|
|
@@ -706,6 +723,55 @@ def load_rate_series_from_sqlite(
|
|
|
706
723
|
conn.close()
|
|
707
724
|
|
|
708
725
|
|
|
726
|
+
def load_rate_series_by_granularity(
|
|
727
|
+
conn_or_path: SqliteConnOrPath,
|
|
728
|
+
symbols: Sequence[str],
|
|
729
|
+
granularities: Sequence[int | str],
|
|
730
|
+
count: int,
|
|
731
|
+
*,
|
|
732
|
+
explicit_tables: Sequence[str] | None = None,
|
|
733
|
+
allow_missing_symbol: bool = False,
|
|
734
|
+
) -> dict[tuple[str | None, str], pd.DataFrame]:
|
|
735
|
+
"""Load rate series keyed by symbol and string granularity name.
|
|
736
|
+
|
|
737
|
+
Builds targets with :func:`build_rate_targets` and loads them with
|
|
738
|
+
:func:`load_rate_series_from_sqlite`, then rekeys the result by granularity
|
|
739
|
+
name (for example ``M1``) instead of the integer timeframe to reduce
|
|
740
|
+
downstream boilerplate.
|
|
741
|
+
|
|
742
|
+
Args:
|
|
743
|
+
conn_or_path: SQLite database path or open connection.
|
|
744
|
+
symbols: MT5 symbol names. May be empty when ``allow_missing_symbol``.
|
|
745
|
+
granularities: MT5 timeframes as integers or names (for example ``M1``).
|
|
746
|
+
count: Number of most recent rows to load per series.
|
|
747
|
+
explicit_tables: Optional explicit table or view names matching the
|
|
748
|
+
built targets in row-major order. Required when symbols are omitted.
|
|
749
|
+
allow_missing_symbol: When True and ``symbols`` is empty, build targets
|
|
750
|
+
with ``symbol=None`` for each granularity instead of raising.
|
|
751
|
+
|
|
752
|
+
Returns:
|
|
753
|
+
Mapping keyed by ``(symbol | None, granularity_name)`` to each rate
|
|
754
|
+
DataFrame. Propagates ``ValueError`` (via :func:`build_rate_targets` and
|
|
755
|
+
:func:`load_rate_series_from_sqlite`) when inputs are empty or invalid,
|
|
756
|
+
table resolution fails, or duplicate targets are present.
|
|
757
|
+
"""
|
|
758
|
+
targets = build_rate_targets(
|
|
759
|
+
symbols,
|
|
760
|
+
granularities,
|
|
761
|
+
allow_missing_symbol=allow_missing_symbol,
|
|
762
|
+
)
|
|
763
|
+
series = load_rate_series_from_sqlite(
|
|
764
|
+
conn_or_path,
|
|
765
|
+
targets,
|
|
766
|
+
count,
|
|
767
|
+
explicit_tables=explicit_tables,
|
|
768
|
+
)
|
|
769
|
+
return {
|
|
770
|
+
(symbol, resolve_granularity_name(timeframe)): frame
|
|
771
|
+
for (symbol, timeframe), frame in series.items()
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
|
|
709
775
|
def get_table_columns(conn: sqlite3.Connection, table: str) -> set[str]:
|
|
710
776
|
"""Return existing SQLite columns for a table."""
|
|
711
777
|
quoted_table = quote_sqlite_identifier(table)
|