mt5cli 0.4.3__tar.gz → 0.5.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.4.3 → mt5cli-0.5.0}/PKG-INFO +30 -25
- {mt5cli-0.4.3 → mt5cli-0.5.0}/README.md +29 -24
- {mt5cli-0.4.3 → mt5cli-0.5.0}/docs/api/history.md +19 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/docs/index.md +20 -9
- {mt5cli-0.4.3 → mt5cli-0.5.0}/mt5cli/__init__.py +13 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/mt5cli/cli.py +56 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/mt5cli/history.py +138 -2
- {mt5cli-0.4.3 → mt5cli-0.5.0}/mt5cli/sdk.py +233 -4
- {mt5cli-0.4.3 → mt5cli-0.5.0}/pyproject.toml +1 -1
- {mt5cli-0.4.3 → mt5cli-0.5.0}/tests/test_cli.py +113 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/tests/test_history.py +143 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/tests/test_sdk.py +270 -2
- {mt5cli-0.4.3 → mt5cli-0.5.0}/uv.lock +1 -1
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.agents/skills/mt5cli/SKILL.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.claude/agents/codex.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.claude/settings.json +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.github/FUNDING.yml +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.github/dependabot.yml +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.github/renovate.json +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.github/workflows/ci.yml +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.github/workflows/claude.yml +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.github/workflows/release.yml +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/.gitignore +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/AGENTS.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/CLAUDE.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/LICENSE +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/docs/api/cli.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/docs/api/index.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/docs/api/sdk.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/docs/api/utils.md +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/mkdocs.yml +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/mt5cli/__main__.py +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/mt5cli/utils.py +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.0}/tests/__init__.py +0 -0
- {mt5cli-0.4.3 → mt5cli-0.5.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.5.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>
|
|
@@ -37,6 +37,7 @@ Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data han
|
|
|
37
37
|
- **Comprehensive data access**: Rates, ticks, account info, symbols, orders, positions, and trading history
|
|
38
38
|
- **Flexible timeframes**: Named timeframes (M1, H1, D1, etc.) and numeric values
|
|
39
39
|
- **Connection management**: Optional credentials, server, and timeout configuration
|
|
40
|
+
- **SQLite rate loading**: Load mt5cli-managed rate tables/views for offline workflows
|
|
40
41
|
|
|
41
42
|
## Installation
|
|
42
43
|
|
|
@@ -74,30 +75,33 @@ python -m mt5cli -o account.csv account-info
|
|
|
74
75
|
|
|
75
76
|
## Commands
|
|
76
77
|
|
|
77
|
-
| Command
|
|
78
|
-
|
|
|
79
|
-
| `rates-from`
|
|
80
|
-
| `rates-from-pos`
|
|
81
|
-
| `rates
|
|
82
|
-
| `
|
|
83
|
-
| `ticks-
|
|
84
|
-
| `ticks-
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `symbol-info
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `history-
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
78
|
+
| Command | Description |
|
|
79
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
80
|
+
| `rates-from` | Export rates from a start date |
|
|
81
|
+
| `rates-from-pos` | Export rates from a start position |
|
|
82
|
+
| `latest-rates` | Export latest rates from a start position |
|
|
83
|
+
| `rates-range` | Export rates for a date range |
|
|
84
|
+
| `ticks-from` | Export ticks from a start date |
|
|
85
|
+
| `ticks-range` | Export ticks for a date range |
|
|
86
|
+
| `ticks-recent` | Export ticks from a recent trailing window |
|
|
87
|
+
| `account-info` | Export account information |
|
|
88
|
+
| `terminal-info` | Export terminal information |
|
|
89
|
+
| `version` | Export MetaTrader 5 version information |
|
|
90
|
+
| `last-error` | Export the last error information |
|
|
91
|
+
| `symbols` | Export symbol list |
|
|
92
|
+
| `symbol-info` | Export symbol details |
|
|
93
|
+
| `symbol-info-tick` | Export the last tick for a symbol |
|
|
94
|
+
| `minimum-margins` | Export minimum-volume buy and sell margin requirements |
|
|
95
|
+
| `market-book` | Export market depth (order book) |
|
|
96
|
+
| `orders` | Export active orders |
|
|
97
|
+
| `positions` | Export open positions |
|
|
98
|
+
| `history-orders` | Export historical orders |
|
|
99
|
+
| `history-deals` | Export historical deals |
|
|
100
|
+
| `recent-history-deals` | Export historical deals from a recent trailing window |
|
|
101
|
+
| `mt5-summary` | Export terminal/account status summary |
|
|
102
|
+
| `order-check` | Check funds sufficiency for a trade request |
|
|
103
|
+
| `order-send` | Send a trade request to the trade server (`--yes` required) |
|
|
104
|
+
| `collect-history` | Bundle rates, ticks, history-orders, and history-deals for one or more symbols into a single SQLite database |
|
|
101
105
|
|
|
102
106
|
Use `order-check` to validate a request payload before running `order-send --yes`.
|
|
103
107
|
|
|
@@ -154,6 +158,7 @@ update_history_with_config(
|
|
|
154
158
|
- **`rates` table**: normalized storage with `symbol` and `timeframe` columns.
|
|
155
159
|
- **Rate compatibility views**: mt5cli manages all `rate_*` views. Naming is `rate_<symbol>__<timeframe>` when a symbol has one timeframe, otherwise `rate_<symbol>__<granularity>_<timeframe>` (for example `rate_EURUSD__M1_1`). Stale `rate_*` views are dropped and recreated when rates change for offline tools such as mteor optimize.
|
|
156
160
|
- **Rate view resolution**: use `mt5cli.history.resolve_rate_view_name()` / `resolve_rate_view_names()` to map symbols and granularities to existing SQLite compatibility views without creating databases.
|
|
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.
|
|
157
162
|
- **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
|
|
158
163
|
- **Recent ticks and margins**: `recent_ticks()` and `minimum_margins()` SDK helpers (and matching CLI commands) cover common downstream read-only queries.
|
|
159
164
|
|
|
@@ -13,6 +13,7 @@ Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data han
|
|
|
13
13
|
- **Comprehensive data access**: Rates, ticks, account info, symbols, orders, positions, and trading history
|
|
14
14
|
- **Flexible timeframes**: Named timeframes (M1, H1, D1, etc.) and numeric values
|
|
15
15
|
- **Connection management**: Optional credentials, server, and timeout configuration
|
|
16
|
+
- **SQLite rate loading**: Load mt5cli-managed rate tables/views for offline workflows
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
@@ -50,30 +51,33 @@ python -m mt5cli -o account.csv account-info
|
|
|
50
51
|
|
|
51
52
|
## Commands
|
|
52
53
|
|
|
53
|
-
| Command
|
|
54
|
-
|
|
|
55
|
-
| `rates-from`
|
|
56
|
-
| `rates-from-pos`
|
|
57
|
-
| `rates
|
|
58
|
-
| `
|
|
59
|
-
| `ticks-
|
|
60
|
-
| `ticks-
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `symbol-info
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `history-
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
54
|
+
| Command | Description |
|
|
55
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
56
|
+
| `rates-from` | Export rates from a start date |
|
|
57
|
+
| `rates-from-pos` | Export rates from a start position |
|
|
58
|
+
| `latest-rates` | Export latest rates from a start position |
|
|
59
|
+
| `rates-range` | Export rates for a date range |
|
|
60
|
+
| `ticks-from` | Export ticks from a start date |
|
|
61
|
+
| `ticks-range` | Export ticks for a date range |
|
|
62
|
+
| `ticks-recent` | Export ticks from a recent trailing window |
|
|
63
|
+
| `account-info` | Export account information |
|
|
64
|
+
| `terminal-info` | Export terminal information |
|
|
65
|
+
| `version` | Export MetaTrader 5 version information |
|
|
66
|
+
| `last-error` | Export the last error information |
|
|
67
|
+
| `symbols` | Export symbol list |
|
|
68
|
+
| `symbol-info` | Export symbol details |
|
|
69
|
+
| `symbol-info-tick` | Export the last tick for a symbol |
|
|
70
|
+
| `minimum-margins` | Export minimum-volume buy and sell margin requirements |
|
|
71
|
+
| `market-book` | Export market depth (order book) |
|
|
72
|
+
| `orders` | Export active orders |
|
|
73
|
+
| `positions` | Export open positions |
|
|
74
|
+
| `history-orders` | Export historical orders |
|
|
75
|
+
| `history-deals` | Export historical deals |
|
|
76
|
+
| `recent-history-deals` | Export historical deals from a recent trailing window |
|
|
77
|
+
| `mt5-summary` | Export terminal/account status summary |
|
|
78
|
+
| `order-check` | Check funds sufficiency for a trade request |
|
|
79
|
+
| `order-send` | Send a trade request to the trade server (`--yes` required) |
|
|
80
|
+
| `collect-history` | Bundle rates, ticks, history-orders, and history-deals for one or more symbols into a single SQLite database |
|
|
77
81
|
|
|
78
82
|
Use `order-check` to validate a request payload before running `order-send --yes`.
|
|
79
83
|
|
|
@@ -130,6 +134,7 @@ update_history_with_config(
|
|
|
130
134
|
- **`rates` table**: normalized storage with `symbol` and `timeframe` columns.
|
|
131
135
|
- **Rate compatibility views**: mt5cli manages all `rate_*` views. Naming is `rate_<symbol>__<timeframe>` when a symbol has one timeframe, otherwise `rate_<symbol>__<granularity>_<timeframe>` (for example `rate_EURUSD__M1_1`). Stale `rate_*` views are dropped and recreated when rates change for offline tools such as mteor optimize.
|
|
132
136
|
- **Rate view resolution**: use `mt5cli.history.resolve_rate_view_name()` / `resolve_rate_view_names()` to map symbols and granularities to existing SQLite compatibility views without creating databases.
|
|
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.
|
|
133
138
|
- **SQLite export helpers**: use `export_dataframe_to_sqlite()` for append mode, optional index export, and post-write deduplication by key columns.
|
|
134
139
|
- **Recent ticks and margins**: `recent_ticks()` and `minimum_margins()` SDK helpers (and matching CLI commands) cover common downstream read-only queries.
|
|
135
140
|
|
|
@@ -164,3 +164,22 @@ Resolution rules:
|
|
|
164
164
|
- Pass `require_existing=True` to raise `ValueError` instead of returning a
|
|
165
165
|
best-guess name when the database or view is missing.
|
|
166
166
|
- Accepts either a SQLite path or an open `sqlite3.Connection`.
|
|
167
|
+
|
|
168
|
+
### Rate data loading
|
|
169
|
+
|
|
170
|
+
Use `load_rate_data()` to load a table or view from a SQLite path, or
|
|
171
|
+
`load_rate_data_from_connection()` when you already have a connection:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from pathlib import Path
|
|
175
|
+
|
|
176
|
+
from mt5cli import load_rate_data
|
|
177
|
+
from mt5cli.history import resolve_rate_view_name
|
|
178
|
+
|
|
179
|
+
view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1", require_existing=True)
|
|
180
|
+
rates = load_rate_data(Path("history.db"), view, count=1000)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The loader accepts close-based OHLC rate data or tick-like bid/ask data. It
|
|
184
|
+
validates that `time` exists, parses timestamps with pandas, and returns a
|
|
185
|
+
DataFrame indexed by ascending `DatetimeIndex` named `time`.
|
|
@@ -13,6 +13,7 @@ mt5cli is a CLI application that exports MetaTrader 5 trading data to multiple f
|
|
|
13
13
|
- **Comprehensive data access**: Rates, ticks, account info, symbols, orders, positions, and trading history
|
|
14
14
|
- **Flexible timeframes**: Named timeframes (M1, H1, D1, etc.) and numeric values
|
|
15
15
|
- **Connection management**: Optional credentials, server, and timeout configuration
|
|
16
|
+
- **SQLite rate loading**: Load mt5cli-managed rate tables/views for offline workflows
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
@@ -34,6 +35,7 @@ from mt5cli import (
|
|
|
34
35
|
copy_rates_range,
|
|
35
36
|
export_dataframe,
|
|
36
37
|
export_dataframe_to_sqlite,
|
|
38
|
+
load_rate_data,
|
|
37
39
|
minimum_margins,
|
|
38
40
|
recent_ticks,
|
|
39
41
|
)
|
|
@@ -49,7 +51,8 @@ rates = copy_rates_range(
|
|
|
49
51
|
export_dataframe(rates, Path("rates.csv"), "csv")
|
|
50
52
|
|
|
51
53
|
# Resolve SQLite rate compatibility views for downstream tools
|
|
52
|
-
view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1")
|
|
54
|
+
view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1", require_existing=True)
|
|
55
|
+
offline_rates = load_rate_data(Path("history.db"), view, count=1000)
|
|
53
56
|
|
|
54
57
|
# Recent tick window and minimum margin summary
|
|
55
58
|
ticks = recent_ticks("EURUSD", seconds=300)
|
|
@@ -59,6 +62,9 @@ margins = minimum_margins("EURUSD")
|
|
|
59
62
|
with Mt5CliClient(login=12345, password="secret", server="Broker-Demo") as client:
|
|
60
63
|
account = client.account_info()
|
|
61
64
|
positions = client.positions()
|
|
65
|
+
latest = client.latest_rates("EURUSD", "M1", count=100)
|
|
66
|
+
summary = client.mt5_summary()
|
|
67
|
+
summary_table = client.mt5_summary_as_df()
|
|
62
68
|
|
|
63
69
|
# Bulk SQLite collection (same behavior as the collect-history CLI command)
|
|
64
70
|
collect_history(
|
|
@@ -74,6 +80,8 @@ collect_history(
|
|
|
74
80
|
|
|
75
81
|
Timeframes, tick flags, and ISO 8601 date strings are accepted wherever noted in the SDK API.
|
|
76
82
|
|
|
83
|
+
`Mt5CliClient.mt5_summary()` returns the SDK structured form as plain nested Python values. Use `Mt5CliClient.mt5_summary_as_df()` when you need a one-row DataFrame for export. The `mt5-summary` CLI command uses this tabular form, so nested terminal/account fields are JSON-encoded strings that are safe for CSV, JSON, Parquet, and SQLite output.
|
|
84
|
+
|
|
77
85
|
## Quick Start
|
|
78
86
|
|
|
79
87
|
```bash
|
|
@@ -104,6 +112,7 @@ mt5cli --login 12345 --password mypass --server MyBroker-Demo \
|
|
|
104
112
|
| ---------------- | ---------------------------------- |
|
|
105
113
|
| `rates-from` | Export rates from a start date |
|
|
106
114
|
| `rates-from-pos` | Export rates from a start position |
|
|
115
|
+
| `latest-rates` | Export latest rates |
|
|
107
116
|
| `rates-range` | Export rates for a date range |
|
|
108
117
|
|
|
109
118
|
### Ticks
|
|
@@ -130,14 +139,16 @@ mt5cli --login 12345 --password mypass --server MyBroker-Demo \
|
|
|
130
139
|
|
|
131
140
|
### Trading
|
|
132
141
|
|
|
133
|
-
| Command
|
|
134
|
-
|
|
|
135
|
-
| `orders`
|
|
136
|
-
| `positions`
|
|
137
|
-
| `history-orders`
|
|
138
|
-
| `history-deals`
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
142
|
+
| Command | Description |
|
|
143
|
+
| ---------------------- | ----------------------------------------------------------- |
|
|
144
|
+
| `orders` | Export active orders |
|
|
145
|
+
| `positions` | Export open positions |
|
|
146
|
+
| `history-orders` | Export historical orders |
|
|
147
|
+
| `history-deals` | Export historical deals |
|
|
148
|
+
| `recent-history-deals` | Export historical deals from a trailing window |
|
|
149
|
+
| `mt5-summary` | Export terminal/account status summary |
|
|
150
|
+
| `order-check` | Check funds sufficiency for a trade request |
|
|
151
|
+
| `order-send` | Send a trade request to the trade server (`--yes` required) |
|
|
141
152
|
|
|
142
153
|
Use `order-check` to validate a request payload before running `order-send --yes`.
|
|
143
154
|
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import version
|
|
4
4
|
|
|
5
|
+
from .history import load_rate_data, load_rate_data_from_connection
|
|
5
6
|
from .sdk import (
|
|
6
7
|
Mt5CliClient,
|
|
7
8
|
account_info,
|
|
8
9
|
build_config,
|
|
9
10
|
collect_history,
|
|
11
|
+
collect_latest_rates,
|
|
10
12
|
copy_rates_from,
|
|
11
13
|
copy_rates_from_pos,
|
|
12
14
|
copy_rates_range,
|
|
@@ -15,10 +17,14 @@ from .sdk import (
|
|
|
15
17
|
history_deals,
|
|
16
18
|
history_orders,
|
|
17
19
|
last_error,
|
|
20
|
+
latest_rates,
|
|
18
21
|
market_book,
|
|
19
22
|
minimum_margins,
|
|
23
|
+
mt5_summary,
|
|
24
|
+
mt5_summary_as_df,
|
|
20
25
|
orders,
|
|
21
26
|
positions,
|
|
27
|
+
recent_history_deals,
|
|
22
28
|
recent_ticks,
|
|
23
29
|
symbol_info,
|
|
24
30
|
symbol_info_tick,
|
|
@@ -47,6 +53,7 @@ __all__ = [
|
|
|
47
53
|
"account_info",
|
|
48
54
|
"build_config",
|
|
49
55
|
"collect_history",
|
|
56
|
+
"collect_latest_rates",
|
|
50
57
|
"copy_rates_from",
|
|
51
58
|
"copy_rates_from_pos",
|
|
52
59
|
"copy_rates_range",
|
|
@@ -58,11 +65,17 @@ __all__ = [
|
|
|
58
65
|
"history_deals",
|
|
59
66
|
"history_orders",
|
|
60
67
|
"last_error",
|
|
68
|
+
"latest_rates",
|
|
69
|
+
"load_rate_data",
|
|
70
|
+
"load_rate_data_from_connection",
|
|
61
71
|
"market_book",
|
|
62
72
|
"minimum_margins",
|
|
73
|
+
"mt5_summary",
|
|
74
|
+
"mt5_summary_as_df",
|
|
63
75
|
"mt5_version",
|
|
64
76
|
"orders",
|
|
65
77
|
"positions",
|
|
78
|
+
"recent_history_deals",
|
|
66
79
|
"recent_ticks",
|
|
67
80
|
"symbol_info",
|
|
68
81
|
"symbol_info_tick",
|
|
@@ -222,6 +222,31 @@ def rates_from_pos(
|
|
|
222
222
|
)
|
|
223
223
|
|
|
224
224
|
|
|
225
|
+
@app.command()
|
|
226
|
+
def latest_rates(
|
|
227
|
+
ctx: typer.Context,
|
|
228
|
+
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
|
229
|
+
timeframe: Annotated[
|
|
230
|
+
int,
|
|
231
|
+
typer.Option(
|
|
232
|
+
click_type=TIMEFRAME_TYPE,
|
|
233
|
+
help="Timeframe.",
|
|
234
|
+
),
|
|
235
|
+
],
|
|
236
|
+
count: Annotated[int, typer.Option(help="Number of records.")],
|
|
237
|
+
start_pos: Annotated[
|
|
238
|
+
int,
|
|
239
|
+
typer.Option(help="Start position (0 = current bar)."),
|
|
240
|
+
] = 0,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Export latest rates from a start position."""
|
|
243
|
+
client = _sdk_client(ctx)
|
|
244
|
+
_execute_export(
|
|
245
|
+
ctx,
|
|
246
|
+
lambda: client.latest_rates(symbol, timeframe, count, start_pos=start_pos),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
225
250
|
@app.command()
|
|
226
251
|
def rates_range(
|
|
227
252
|
ctx: typer.Context,
|
|
@@ -475,6 +500,37 @@ def history_deals(
|
|
|
475
500
|
)
|
|
476
501
|
|
|
477
502
|
|
|
503
|
+
@app.command()
|
|
504
|
+
def recent_history_deals(
|
|
505
|
+
ctx: typer.Context,
|
|
506
|
+
hours: Annotated[float, typer.Option(help="Lookback window in hours.")],
|
|
507
|
+
date_to: Annotated[
|
|
508
|
+
datetime | None,
|
|
509
|
+
typer.Option(click_type=DATETIME_TYPE, help="Window end date."),
|
|
510
|
+
] = None,
|
|
511
|
+
group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
|
|
512
|
+
symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
|
|
513
|
+
) -> None:
|
|
514
|
+
"""Export historical deals from a recent trailing window."""
|
|
515
|
+
client = _sdk_client(ctx)
|
|
516
|
+
_execute_export(
|
|
517
|
+
ctx,
|
|
518
|
+
lambda: client.recent_history_deals(
|
|
519
|
+
hours,
|
|
520
|
+
date_to=date_to,
|
|
521
|
+
group=group,
|
|
522
|
+
symbol=symbol,
|
|
523
|
+
),
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@app.command()
|
|
528
|
+
def mt5_summary(ctx: typer.Context) -> None:
|
|
529
|
+
"""Export a compact terminal/account status summary."""
|
|
530
|
+
client = _sdk_client(ctx)
|
|
531
|
+
_execute_export(ctx, client.mt5_summary_as_df)
|
|
532
|
+
|
|
533
|
+
|
|
478
534
|
@app.command()
|
|
479
535
|
def version(ctx: typer.Context) -> None:
|
|
480
536
|
"""Export MetaTrader5 version information."""
|
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
import sqlite3
|
|
7
7
|
from datetime import UTC, datetime
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING, Literal
|
|
9
|
+
from typing import TYPE_CHECKING, Literal, cast
|
|
10
10
|
|
|
11
11
|
import pandas as pd
|
|
12
12
|
|
|
@@ -126,6 +126,14 @@ def build_rate_view_name(
|
|
|
126
126
|
SqliteConnOrPath = sqlite3.Connection | Path | str
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
def _require_non_empty_identifier(identifier: str, kind: str) -> str:
|
|
130
|
+
value = identifier.strip()
|
|
131
|
+
if not value:
|
|
132
|
+
msg = f"SQLite {kind} name must not be empty."
|
|
133
|
+
raise ValueError(msg)
|
|
134
|
+
return value
|
|
135
|
+
|
|
136
|
+
|
|
129
137
|
def _open_history_connection(
|
|
130
138
|
conn_or_path: SqliteConnOrPath,
|
|
131
139
|
) -> tuple[sqlite3.Connection | None, bool]:
|
|
@@ -144,6 +152,133 @@ def _open_history_connection(
|
|
|
144
152
|
return conn, True
|
|
145
153
|
|
|
146
154
|
|
|
155
|
+
def _open_existing_sqlite_database(
|
|
156
|
+
conn_or_path: SqliteConnOrPath,
|
|
157
|
+
) -> tuple[sqlite3.Connection, bool]:
|
|
158
|
+
"""Open a read-only SQLite database or reuse an existing connection.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Tuple of connection and whether the caller should close it.
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
ValueError: If the database path does not exist or is not a file.
|
|
165
|
+
"""
|
|
166
|
+
if isinstance(conn_or_path, sqlite3.Connection):
|
|
167
|
+
return conn_or_path, False
|
|
168
|
+
path = Path(conn_or_path)
|
|
169
|
+
if not path.exists():
|
|
170
|
+
msg = f"SQLite database not found: {path}"
|
|
171
|
+
raise ValueError(msg)
|
|
172
|
+
if not path.is_file():
|
|
173
|
+
msg = f"SQLite database path is not a file: {path}"
|
|
174
|
+
raise ValueError(msg)
|
|
175
|
+
conn = sqlite3.connect(f"{path.resolve().as_uri()}?mode=ro", uri=True)
|
|
176
|
+
return conn, True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _validate_rate_load_request(table: str, count: int | None) -> str:
|
|
180
|
+
table_name = _require_non_empty_identifier(table, "table or view")
|
|
181
|
+
if count is not None and count <= 0:
|
|
182
|
+
msg = "count must be positive when provided."
|
|
183
|
+
raise ValueError(msg)
|
|
184
|
+
return table_name
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _ensure_rate_columns(columns: set[str], table: str) -> None:
|
|
188
|
+
if not columns:
|
|
189
|
+
msg = f"SQLite table or view not found: {table}"
|
|
190
|
+
raise ValueError(msg)
|
|
191
|
+
if "time" not in columns:
|
|
192
|
+
msg = f"SQLite table or view {table!r} must include a time column."
|
|
193
|
+
raise ValueError(msg)
|
|
194
|
+
if "close" not in columns and not {"ask", "bid"}.issubset(columns):
|
|
195
|
+
msg = (
|
|
196
|
+
f"SQLite table or view {table!r} must include close, "
|
|
197
|
+
"or both ask and bid columns."
|
|
198
|
+
)
|
|
199
|
+
raise ValueError(msg)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _parse_rate_time_index(frame: pd.DataFrame, table: str) -> pd.DataFrame:
|
|
203
|
+
parsed = frame["time"].map(parse_sqlite_timestamp)
|
|
204
|
+
if parsed.isna().any():
|
|
205
|
+
msg = f"SQLite table or view {table!r} contains unparsable time values."
|
|
206
|
+
raise ValueError(msg)
|
|
207
|
+
result = frame.drop(columns=["time"])
|
|
208
|
+
result.index = pd.DatetimeIndex(parsed, name="time")
|
|
209
|
+
return result.sort_index(kind="stable")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def load_rate_data_from_connection(
|
|
213
|
+
connection: sqlite3.Connection,
|
|
214
|
+
table: str,
|
|
215
|
+
count: int | None = None,
|
|
216
|
+
) -> pd.DataFrame:
|
|
217
|
+
"""Load rate-like data from a SQLite table or view.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
connection: Open SQLite connection.
|
|
221
|
+
table: Source table or view name.
|
|
222
|
+
count: Optional number of most recent rows to load.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
DataFrame indexed by ascending ``time``.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
ValueError: If inputs, schema, timestamps are invalid, or the table
|
|
229
|
+
or view contains no rows.
|
|
230
|
+
"""
|
|
231
|
+
table_name = _validate_rate_load_request(table, count)
|
|
232
|
+
columns = get_table_columns(connection, table_name)
|
|
233
|
+
_ensure_rate_columns(columns, table_name)
|
|
234
|
+
quoted_table = quote_sqlite_identifier(table_name)
|
|
235
|
+
if count is None:
|
|
236
|
+
frame = cast(
|
|
237
|
+
"pd.DataFrame",
|
|
238
|
+
pd.read_sql_query( # type: ignore[reportUnknownMemberType]
|
|
239
|
+
f"SELECT * FROM {quoted_table} ORDER BY time ASC", # noqa: S608
|
|
240
|
+
connection,
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
frame = cast(
|
|
245
|
+
"pd.DataFrame",
|
|
246
|
+
pd.read_sql_query( # type: ignore[reportUnknownMemberType]
|
|
247
|
+
f"SELECT * FROM {quoted_table} ORDER BY time DESC LIMIT ?", # noqa: S608
|
|
248
|
+
connection,
|
|
249
|
+
params=(count,),
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
if frame.empty:
|
|
253
|
+
msg = f"SQLite table or view {table_name!r} contains no rows."
|
|
254
|
+
raise ValueError(msg)
|
|
255
|
+
return _parse_rate_time_index(frame, table_name)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def load_rate_data(
|
|
259
|
+
conn_or_path: SqliteConnOrPath,
|
|
260
|
+
table: str,
|
|
261
|
+
count: int | None = None,
|
|
262
|
+
) -> pd.DataFrame:
|
|
263
|
+
"""Load rate-like data from a SQLite database path or connection.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
conn_or_path: SQLite database path or open connection.
|
|
267
|
+
table: Source table or view name.
|
|
268
|
+
count: Optional number of most recent rows to load.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
DataFrame indexed by ascending ``time``.
|
|
272
|
+
|
|
273
|
+
"""
|
|
274
|
+
conn, should_close = _open_existing_sqlite_database(conn_or_path)
|
|
275
|
+
try:
|
|
276
|
+
return load_rate_data_from_connection(conn, table, count=count)
|
|
277
|
+
finally:
|
|
278
|
+
if should_close:
|
|
279
|
+
conn.close()
|
|
280
|
+
|
|
281
|
+
|
|
147
282
|
def _load_rates_timeframe_counts(conn: sqlite3.Connection) -> dict[str, int] | None:
|
|
148
283
|
"""Return distinct timeframe counts per symbol from the normalized rates table."""
|
|
149
284
|
columns = get_table_columns(conn, Dataset.rates.table_name)
|
|
@@ -349,7 +484,8 @@ def resolve_rate_view_names(
|
|
|
349
484
|
|
|
350
485
|
def get_table_columns(conn: sqlite3.Connection, table: str) -> set[str]:
|
|
351
486
|
"""Return existing SQLite columns for a table."""
|
|
352
|
-
|
|
487
|
+
quoted_table = quote_sqlite_identifier(table)
|
|
488
|
+
rows = conn.execute(f"PRAGMA table_info({quoted_table})").fetchall()
|
|
353
489
|
return {str(row[1]) for row in rows}
|
|
354
490
|
|
|
355
491
|
|