mt5api 0.2.0__tar.gz → 0.3.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.
- {mt5api-0.2.0 → mt5api-0.3.0}/.github/workflows/claude.yml +3 -3
- {mt5api-0.2.0 → mt5api-0.3.0}/PKG-INFO +17 -16
- {mt5api-0.2.0 → mt5api-0.3.0}/README.md +15 -11
- {mt5api-0.2.0 → mt5api-0.3.0}/docs/api/rest-api.md +6 -5
- {mt5api-0.2.0 → mt5api-0.3.0}/docs/index.md +12 -8
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/models.py +41 -169
- {mt5api-0.2.0 → mt5api-0.3.0}/pyproject.toml +2 -5
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/mt5_constants.py +1 -1
- mt5api-0.3.0/tests/openapi_mt5_constants.py +34 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_calc.py +16 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_market.py +8 -6
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_models.py +97 -45
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_trading.py +12 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/uv.lock +47 -277
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/mt5api/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-analyze/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-checklist/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-clarify/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-constitution/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-implement/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-plan/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-specify/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-tasks/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-taskstoissues/SKILL.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/agents/codex.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/agents/copilot.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.analyze.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.checklist.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.clarify.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.constitution.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.implement.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.plan.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.specify.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.tasks.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.taskstoissues.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.claude/settings.json +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.github/FUNDING.yml +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.github/dependabot.yml +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.github/renovate.json +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.github/workflows/ci.yml +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.github/workflows/release.yml +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.gitignore +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/memory/constitution.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/common.sh +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/setup-plan.sh +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/agent-file-template.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/checklist-template.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/plan-template.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/spec-template.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/tasks-template.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/AGENTS.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/CLAUDE.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/LICENSE +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/docs/api/deployment.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/docs/api/index.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mkdocs.yml +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/__init__.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/__main__.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/auth.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/config.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/constants.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/dependencies.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/formatters.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/main.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/middleware.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/__init__.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/account.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/calc.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/connection.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/health.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/history.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/market.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/symbols.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/trading.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/contracts/openapi.yaml +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/data-model.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/plan.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/quickstart.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/research.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/tasks.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/042-mt5-core-client/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/043-mt5-dataframe-client/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/044-mt5-trading-client/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/045-mt5-utils/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/046-mt5-api-service/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/047-api-auth-rate-limit/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/048-api-response-format/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/049-api-routing-models/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/050-api-runtime-deploy/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/051-api-error-logging/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/specs/052-api-dependencies/spec.md +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/__init__.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/conftest.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_account.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_auth.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_cli.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_config.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_connection.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_dependencies.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_formatters.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_health.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_history.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_integration.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_lifespan.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_main.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_middleware.py +0 -0
- {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_symbols.py +0 -0
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
claude-code-review:
|
|
27
27
|
if: >
|
|
28
28
|
github.event_name == 'pull_request'
|
|
29
|
-
&& github.event.pull_request.draft
|
|
29
|
+
&& (! github.event.pull_request.draft)
|
|
30
30
|
&& (! startsWith(github.head_ref, 'dependabot/'))
|
|
31
31
|
&& (! startsWith(github.head_ref, 'renovate/'))
|
|
32
32
|
permissions:
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
actions: read
|
|
38
38
|
uses: dceoy/gh-actions-for-devops/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses]
|
|
39
39
|
secrets:
|
|
40
|
-
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
40
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env]
|
|
41
41
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
42
42
|
claude-code-bot:
|
|
43
43
|
if: >
|
|
@@ -53,5 +53,5 @@ jobs:
|
|
|
53
53
|
actions: read
|
|
54
54
|
uses: dceoy/gh-actions-for-devops/.github/workflows/claude-code-bot.yml@main # zizmor: ignore[unpinned-uses]
|
|
55
55
|
secrets:
|
|
56
|
-
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
56
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env]
|
|
57
57
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mt5api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: MetaTrader 5 REST API
|
|
5
5
|
Project-URL: Repository, https://github.com/dceoy/mt5api.git
|
|
6
6
|
Author-email: dceoy <dceoy@users.noreply.github.com>
|
|
@@ -18,11 +18,8 @@ Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
|
18
18
|
Requires-Python: <3.14,>=3.11
|
|
19
19
|
Requires-Dist: fastapi>=0.115.0
|
|
20
20
|
Requires-Dist: httpx>=0.27.0
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist: passlib[bcrypt]>=1.7.4
|
|
23
|
-
Requires-Dist: pdmt5>=0.2.1
|
|
21
|
+
Requires-Dist: pdmt5>=0.3.0
|
|
24
22
|
Requires-Dist: pyarrow>=18.0.0
|
|
25
|
-
Requires-Dist: python-jose[cryptography]>=3.3.0
|
|
26
23
|
Requires-Dist: python-multipart>=0.0.9
|
|
27
24
|
Requires-Dist: uvicorn[standard]>=0.32.0
|
|
28
25
|
Description-Content-Type: text/markdown
|
|
@@ -34,14 +31,18 @@ MetaTrader 5 REST API
|
|
|
34
31
|
[](https://github.com/dceoy/mt5api/actions/workflows/ci.yml)
|
|
35
32
|
|
|
36
33
|
mt5api exposes MT5 market data, account info, trading history, and trading
|
|
37
|
-
operations over HTTP. It
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
operations over HTTP. It is a FastAPI/HTTP adapter over
|
|
35
|
+
[`pdmt5`](https://github.com/dceoy/pdmt5), which provides the core MT5 client,
|
|
36
|
+
dataframe/trading primitives, and canonical MT5 constant parsing. mt5api adds
|
|
37
|
+
optional API-key auth and JSON/Parquet response formatting.
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
[`mt5cli`](https://github.com/dceoy/mt5cli) is a sibling CLI/batch adapter for
|
|
40
|
+
the same pdmt5 stack; it is not a dependency of mt5api.
|
|
41
|
+
|
|
42
|
+
The API server must run on Windows. pdmt5 connects through the MetaTrader 5
|
|
43
|
+
Python API, which is supported only on Windows, so you must host `mt5api` on a
|
|
44
|
+
Windows machine with a logged-in MetaTrader 5 terminal. HTTP clients can connect
|
|
45
|
+
from any operating system.
|
|
45
46
|
|
|
46
47
|
## Architecture
|
|
47
48
|
|
|
@@ -61,7 +62,7 @@ graph TB
|
|
|
61
62
|
Formatters --> Middleware
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
Deps --> pdmt5["pdmt5<br/>MT5 Client
|
|
65
|
+
Deps --> pdmt5["pdmt5<br/>MT5 Client · Constants · Dataframes"]
|
|
65
66
|
pdmt5 --> MT5["MetaTrader 5<br/>Terminal"]
|
|
66
67
|
end
|
|
67
68
|
|
|
@@ -162,9 +163,9 @@ curl -H "X-API-Key: your-secret-api-key" "http://windows-host:8000/symbols?group
|
|
|
162
163
|
curl -H "X-API-Key: your-secret-api-key" -H "Accept: application/parquet" "http://windows-host:8000/rates/from?symbol=EURUSD&timeframe=TIMEFRAME_M1&date_from=2024-01-01T00:00:00Z&count=100"
|
|
163
164
|
```
|
|
164
165
|
|
|
165
|
-
Market-data and calculation endpoints accept MetaTrader 5 constants
|
|
166
|
-
|
|
167
|
-
integer value.
|
|
166
|
+
Market-data and calculation endpoints accept MetaTrader 5 constants by official
|
|
167
|
+
name (`TIMEFRAME_M1`, `COPY_TICKS_ALL`, `ORDER_TYPE_BUY`), short alias (`M1`,
|
|
168
|
+
`ALL`, `BUY`), or integer value.
|
|
168
169
|
|
|
169
170
|
## Endpoints
|
|
170
171
|
|
|
@@ -5,14 +5,18 @@ MetaTrader 5 REST API
|
|
|
5
5
|
[](https://github.com/dceoy/mt5api/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
7
|
mt5api exposes MT5 market data, account info, trading history, and trading
|
|
8
|
-
operations over HTTP. It
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
operations over HTTP. It is a FastAPI/HTTP adapter over
|
|
9
|
+
[`pdmt5`](https://github.com/dceoy/pdmt5), which provides the core MT5 client,
|
|
10
|
+
dataframe/trading primitives, and canonical MT5 constant parsing. mt5api adds
|
|
11
|
+
optional API-key auth and JSON/Parquet response formatting.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
[`mt5cli`](https://github.com/dceoy/mt5cli) is a sibling CLI/batch adapter for
|
|
14
|
+
the same pdmt5 stack; it is not a dependency of mt5api.
|
|
15
|
+
|
|
16
|
+
The API server must run on Windows. pdmt5 connects through the MetaTrader 5
|
|
17
|
+
Python API, which is supported only on Windows, so you must host `mt5api` on a
|
|
18
|
+
Windows machine with a logged-in MetaTrader 5 terminal. HTTP clients can connect
|
|
19
|
+
from any operating system.
|
|
16
20
|
|
|
17
21
|
## Architecture
|
|
18
22
|
|
|
@@ -32,7 +36,7 @@ graph TB
|
|
|
32
36
|
Formatters --> Middleware
|
|
33
37
|
end
|
|
34
38
|
|
|
35
|
-
Deps --> pdmt5["pdmt5<br/>MT5 Client
|
|
39
|
+
Deps --> pdmt5["pdmt5<br/>MT5 Client · Constants · Dataframes"]
|
|
36
40
|
pdmt5 --> MT5["MetaTrader 5<br/>Terminal"]
|
|
37
41
|
end
|
|
38
42
|
|
|
@@ -133,9 +137,9 @@ curl -H "X-API-Key: your-secret-api-key" "http://windows-host:8000/symbols?group
|
|
|
133
137
|
curl -H "X-API-Key: your-secret-api-key" -H "Accept: application/parquet" "http://windows-host:8000/rates/from?symbol=EURUSD&timeframe=TIMEFRAME_M1&date_from=2024-01-01T00:00:00Z&count=100"
|
|
134
138
|
```
|
|
135
139
|
|
|
136
|
-
Market-data and calculation endpoints accept MetaTrader 5 constants
|
|
137
|
-
|
|
138
|
-
integer value.
|
|
140
|
+
Market-data and calculation endpoints accept MetaTrader 5 constants by official
|
|
141
|
+
name (`TIMEFRAME_M1`, `COPY_TICKS_ALL`, `ORDER_TYPE_BUY`), short alias (`M1`,
|
|
142
|
+
`ALL`, `BUY`), or integer value.
|
|
139
143
|
|
|
140
144
|
## Endpoints
|
|
141
145
|
|
|
@@ -108,9 +108,9 @@ If `MT5API_ROUTER_PREFIX` is configured, prepend it to each API route below.
|
|
|
108
108
|
|
|
109
109
|
### Market Data
|
|
110
110
|
|
|
111
|
-
- `timeframe` and `flags` accept
|
|
112
|
-
|
|
113
|
-
value.
|
|
111
|
+
- `timeframe` and `flags` accept the official MetaTrader 5 constant name (for
|
|
112
|
+
example `TIMEFRAME_M1`, `COPY_TICKS_ALL`), a short alias (for example `M1`,
|
|
113
|
+
`ALL`), or the equivalent integer value.
|
|
114
114
|
- `GET /rates/from` (`symbol`, `timeframe`, `date_from`, `count`, `format`)
|
|
115
115
|
- `GET /rates/from-pos` (`symbol`, `timeframe`, `start_pos`, `count`, `format`)
|
|
116
116
|
- `GET /rates/range` (`symbol`, `timeframe`, `date_from`, `date_to`, `format`)
|
|
@@ -122,8 +122,9 @@ If `MT5API_ROUTER_PREFIX` is configured, prepend it to each API route below.
|
|
|
122
122
|
|
|
123
123
|
### Calculations
|
|
124
124
|
|
|
125
|
-
- `action` accepts
|
|
126
|
-
`ORDER_TYPE_BUY`) or the equivalent
|
|
125
|
+
- `action` accepts the official MetaTrader 5 constant name (for example
|
|
126
|
+
`ORDER_TYPE_BUY`), a short alias (for example `BUY`), or the equivalent
|
|
127
|
+
integer value.
|
|
127
128
|
- `GET /calc/margin` (`action`, `symbol`, `volume`, `price`, `format`) —
|
|
128
129
|
Calculate required margin in account currency
|
|
129
130
|
- `GET /calc/profit` (`action`, `symbol`, `volume`, `price_open`,
|
|
@@ -5,14 +5,18 @@ trading operations.
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
-
mt5api
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
mt5api is a FastAPI/HTTP adapter over
|
|
9
|
+
[`pdmt5`](https://github.com/dceoy/pdmt5). pdmt5 provides the core MT5 client,
|
|
10
|
+
dataframe/trading primitives, and canonical MT5 constant parsing. mt5api adds
|
|
11
|
+
optional authentication, response formatting, and OpenAPI documentation.
|
|
12
|
+
|
|
13
|
+
[`mt5cli`](https://github.com/dceoy/mt5cli) is a sibling CLI/batch adapter for
|
|
14
|
+
the same pdmt5 stack; it is not a dependency of mt5api.
|
|
15
|
+
|
|
16
|
+
The API server must run on Windows because pdmt5 connects through the MetaTrader
|
|
17
|
+
5 Python API, which is Windows-only. Run `mt5api` on a Windows host with
|
|
18
|
+
MetaTrader 5 installed and logged in. API clients can connect from any operating
|
|
19
|
+
system.
|
|
16
20
|
|
|
17
21
|
## Features
|
|
18
22
|
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from dataclasses import dataclass
|
|
6
5
|
from datetime import datetime # noqa: TC003
|
|
7
6
|
from enum import StrEnum
|
|
8
|
-
from
|
|
9
|
-
from typing import TYPE_CHECKING, Annotated, Any, LiteralString, Self, TypeAlias, cast
|
|
7
|
+
from typing import Annotated, Any, Self, TypeAlias
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
import pdmt5
|
|
12
10
|
from pydantic import (
|
|
13
11
|
BaseModel,
|
|
14
12
|
BeforeValidator,
|
|
@@ -18,23 +16,10 @@ from pydantic import (
|
|
|
18
16
|
WithJsonSchema,
|
|
19
17
|
model_validator,
|
|
20
18
|
)
|
|
21
|
-
from pydantic_core import PydanticCustomError
|
|
22
|
-
|
|
23
|
-
if TYPE_CHECKING:
|
|
24
|
-
from collections.abc import Callable
|
|
25
|
-
from types import ModuleType
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class ResponseFormat(StrEnum):
|
|
29
|
-
"""Response format enumeration."""
|
|
30
|
-
|
|
31
|
-
JSON = "json"
|
|
32
|
-
PARQUET = "parquet"
|
|
33
|
-
|
|
34
19
|
|
|
35
20
|
_TIMEFRAME_DESCRIPTION = (
|
|
36
21
|
"MetaTrader5 TIMEFRAME constant. Accepts a constant name such as "
|
|
37
|
-
"TIMEFRAME_M1 or the corresponding integer value."
|
|
22
|
+
"TIMEFRAME_M1, short aliases such as M1, or the corresponding integer value."
|
|
38
23
|
)
|
|
39
24
|
_TIMEFRAME_EXAMPLE_NAMES = (
|
|
40
25
|
"TIMEFRAME_M1",
|
|
@@ -49,142 +34,22 @@ _TIMEFRAME_EXAMPLE_NAMES = (
|
|
|
49
34
|
)
|
|
50
35
|
_COPY_TICKS_DESCRIPTION = (
|
|
51
36
|
"MetaTrader5 COPY_TICKS constant. Accepts COPY_TICKS_INFO, "
|
|
52
|
-
"COPY_TICKS_TRADE, COPY_TICKS_ALL,
|
|
37
|
+
"COPY_TICKS_TRADE, COPY_TICKS_ALL, short aliases such as ALL, "
|
|
38
|
+
"or the corresponding integer value."
|
|
53
39
|
)
|
|
54
|
-
|
|
40
|
+
_COPY_TICKS_EXAMPLE_NAMES = (
|
|
55
41
|
"COPY_TICKS_INFO",
|
|
56
42
|
"COPY_TICKS_TRADE",
|
|
57
43
|
"COPY_TICKS_ALL",
|
|
58
44
|
)
|
|
59
45
|
_ORDER_TYPE_DESCRIPTION = (
|
|
60
46
|
"MetaTrader5 ORDER_TYPE constant. Accepts a constant name such as "
|
|
61
|
-
"ORDER_TYPE_BUY or the corresponding integer value."
|
|
47
|
+
"ORDER_TYPE_BUY, short aliases such as BUY, or the corresponding integer value."
|
|
62
48
|
)
|
|
63
49
|
_ORDER_TYPE_EXAMPLE_NAMES = (
|
|
64
50
|
"ORDER_TYPE_BUY",
|
|
65
51
|
"ORDER_TYPE_SELL",
|
|
66
52
|
)
|
|
67
|
-
_ORDER_TYPE_VALIDATION_DESCRIPTION = "MetaTrader5 ORDER_TYPE constant"
|
|
68
|
-
_TIMEFRAME_VALIDATION_DESCRIPTION = "MetaTrader5 TIMEFRAME constant"
|
|
69
|
-
_COPY_TICKS_VALIDATION_DESCRIPTION = "MetaTrader5 COPY_TICKS constant"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@dataclass(frozen=True)
|
|
73
|
-
class Mt5ConstantSpec:
|
|
74
|
-
"""Metadata and cached values for an MT5 constant family."""
|
|
75
|
-
|
|
76
|
-
validation_description: str
|
|
77
|
-
schema_description: str
|
|
78
|
-
prefix: str = ""
|
|
79
|
-
names: tuple[str, ...] = ()
|
|
80
|
-
example_names: tuple[str, ...] = ()
|
|
81
|
-
|
|
82
|
-
@cached_property
|
|
83
|
-
def members(self) -> dict[str, int]:
|
|
84
|
-
"""Return MT5 constant names and values from the pdmt5 MT5 module copy."""
|
|
85
|
-
mt5 = _get_mt5_module()
|
|
86
|
-
if self.names:
|
|
87
|
-
return {name: int(getattr(mt5, name)) for name in self.names}
|
|
88
|
-
return {
|
|
89
|
-
name: int(getattr(mt5, name))
|
|
90
|
-
for name in dir(mt5)
|
|
91
|
-
if name.startswith(self.prefix) and isinstance(getattr(mt5, name), int)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
@cached_property
|
|
95
|
-
def sorted_names(self) -> tuple[str, ...]:
|
|
96
|
-
"""Return sorted MT5 constant names."""
|
|
97
|
-
return tuple(sorted(self.members))
|
|
98
|
-
|
|
99
|
-
@cached_property
|
|
100
|
-
def sorted_values(self) -> tuple[int, ...]:
|
|
101
|
-
"""Return sorted MT5 constant values."""
|
|
102
|
-
return tuple(sorted(self.members.values()))
|
|
103
|
-
|
|
104
|
-
@cached_property
|
|
105
|
-
def example_values(self) -> list[int]:
|
|
106
|
-
"""Return MT5 constant value examples."""
|
|
107
|
-
return [self.members[name] for name in self.example_names]
|
|
108
|
-
|
|
109
|
-
@cached_property
|
|
110
|
-
def example_name_list(self) -> list[str]:
|
|
111
|
-
"""Return MT5 constant name examples."""
|
|
112
|
-
return list(self.example_names)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
@cache
|
|
116
|
-
def _get_mt5_module() -> ModuleType:
|
|
117
|
-
"""Return the MetaTrader5 module used by pdmt5."""
|
|
118
|
-
factory = cast(
|
|
119
|
-
"Callable[[], ModuleType]", Mt5Client.model_fields["mt5"].default_factory
|
|
120
|
-
)
|
|
121
|
-
return factory()
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
_TIMEFRAME_SPEC = Mt5ConstantSpec(
|
|
125
|
-
validation_description=_TIMEFRAME_VALIDATION_DESCRIPTION,
|
|
126
|
-
schema_description=_TIMEFRAME_DESCRIPTION,
|
|
127
|
-
prefix="TIMEFRAME_",
|
|
128
|
-
example_names=_TIMEFRAME_EXAMPLE_NAMES,
|
|
129
|
-
)
|
|
130
|
-
_COPY_TICKS_SPEC = Mt5ConstantSpec(
|
|
131
|
-
validation_description=_COPY_TICKS_VALIDATION_DESCRIPTION,
|
|
132
|
-
schema_description=_COPY_TICKS_DESCRIPTION,
|
|
133
|
-
names=_COPY_TICKS_NAMES,
|
|
134
|
-
example_names=_COPY_TICKS_NAMES,
|
|
135
|
-
)
|
|
136
|
-
_ORDER_TYPE_SPEC = Mt5ConstantSpec(
|
|
137
|
-
validation_description=_ORDER_TYPE_VALIDATION_DESCRIPTION,
|
|
138
|
-
schema_description=_ORDER_TYPE_DESCRIPTION,
|
|
139
|
-
prefix="ORDER_TYPE_",
|
|
140
|
-
example_names=_ORDER_TYPE_EXAMPLE_NAMES,
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _parse_mt5_constant(
|
|
145
|
-
value: object,
|
|
146
|
-
*,
|
|
147
|
-
spec: Mt5ConstantSpec,
|
|
148
|
-
) -> int:
|
|
149
|
-
"""Parse an MT5 constant name or integer value.
|
|
150
|
-
|
|
151
|
-
Returns:
|
|
152
|
-
The validated integer constant value.
|
|
153
|
-
|
|
154
|
-
Raises:
|
|
155
|
-
PydanticCustomError: If the value type is not supported.
|
|
156
|
-
ValueError: If the value is not a supported constant name or integer.
|
|
157
|
-
"""
|
|
158
|
-
type_error_code = "mt5_constant_type"
|
|
159
|
-
type_error_template: LiteralString = (
|
|
160
|
-
"{description} must be specified by constant name or integer value"
|
|
161
|
-
)
|
|
162
|
-
type_error_context = {"description": spec.validation_description}
|
|
163
|
-
type_error_message = type_error_template.format(**type_error_context)
|
|
164
|
-
if isinstance(value, str):
|
|
165
|
-
if value in spec.members:
|
|
166
|
-
return spec.members[value]
|
|
167
|
-
try:
|
|
168
|
-
value = int(value)
|
|
169
|
-
except ValueError as error:
|
|
170
|
-
raise ValueError(type_error_message) from error
|
|
171
|
-
|
|
172
|
-
if isinstance(value, bool) or not isinstance(value, int):
|
|
173
|
-
raise PydanticCustomError(
|
|
174
|
-
type_error_code,
|
|
175
|
-
type_error_template,
|
|
176
|
-
type_error_context,
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
parsed_value = value
|
|
180
|
-
|
|
181
|
-
if parsed_value not in spec.members.values():
|
|
182
|
-
error_message = (
|
|
183
|
-
f"Unsupported {spec.validation_description.lower()} value: {parsed_value}"
|
|
184
|
-
)
|
|
185
|
-
raise ValueError(error_message)
|
|
186
|
-
|
|
187
|
-
return parsed_value
|
|
188
53
|
|
|
189
54
|
|
|
190
55
|
def _build_mt5_constant_json_schema(
|
|
@@ -210,43 +75,43 @@ def _build_mt5_constant_json_schema(
|
|
|
210
75
|
|
|
211
76
|
|
|
212
77
|
def get_mt5_timeframe_values() -> tuple[int, ...]:
|
|
213
|
-
"""Return all available MT5 timeframe values from
|
|
214
|
-
return
|
|
78
|
+
"""Return all available MT5 timeframe values from pdmt5."""
|
|
79
|
+
return tuple(pdmt5.list_timeframe_values())
|
|
215
80
|
|
|
216
81
|
|
|
217
82
|
def get_mt5_timeframe_names() -> tuple[str, ...]:
|
|
218
|
-
"""Return all available MT5 timeframe names from
|
|
219
|
-
return
|
|
83
|
+
"""Return all available MT5 timeframe names from pdmt5."""
|
|
84
|
+
return tuple(pdmt5.list_timeframe_names())
|
|
220
85
|
|
|
221
86
|
|
|
222
87
|
def get_mt5_timeframe_examples() -> list[int]:
|
|
223
|
-
"""Return common MT5 timeframe examples from
|
|
224
|
-
return
|
|
88
|
+
"""Return common MT5 timeframe examples from pdmt5."""
|
|
89
|
+
return [pdmt5.TIMEFRAME_MAP[name] for name in _TIMEFRAME_EXAMPLE_NAMES]
|
|
225
90
|
|
|
226
91
|
|
|
227
92
|
def get_mt5_timeframe_example_names() -> list[str]:
|
|
228
93
|
"""Return common MT5 timeframe example names."""
|
|
229
|
-
return
|
|
94
|
+
return list(_TIMEFRAME_EXAMPLE_NAMES)
|
|
230
95
|
|
|
231
96
|
|
|
232
97
|
def get_mt5_copy_ticks_values() -> tuple[int, ...]:
|
|
233
|
-
"""Return all MT5 COPY_TICKS values from
|
|
234
|
-
return
|
|
98
|
+
"""Return all MT5 COPY_TICKS values from pdmt5."""
|
|
99
|
+
return tuple(pdmt5.list_copy_ticks_values())
|
|
235
100
|
|
|
236
101
|
|
|
237
102
|
def get_mt5_copy_ticks_names() -> tuple[str, ...]:
|
|
238
|
-
"""Return all MT5 COPY_TICKS names from
|
|
239
|
-
return
|
|
103
|
+
"""Return all MT5 COPY_TICKS names from pdmt5."""
|
|
104
|
+
return tuple(pdmt5.list_copy_ticks_names())
|
|
240
105
|
|
|
241
106
|
|
|
242
107
|
def get_mt5_copy_ticks_examples() -> list[int]:
|
|
243
108
|
"""Return common MT5 COPY_TICKS integer examples."""
|
|
244
|
-
return
|
|
109
|
+
return [pdmt5.COPY_TICKS_MAP[name] for name in _COPY_TICKS_EXAMPLE_NAMES]
|
|
245
110
|
|
|
246
111
|
|
|
247
112
|
def get_mt5_copy_ticks_example_names() -> list[str]:
|
|
248
113
|
"""Return common MT5 COPY_TICKS example names."""
|
|
249
|
-
return
|
|
114
|
+
return list(_COPY_TICKS_EXAMPLE_NAMES)
|
|
250
115
|
|
|
251
116
|
|
|
252
117
|
def _validate_mt5_timeframe(value: object) -> int:
|
|
@@ -255,7 +120,7 @@ def _validate_mt5_timeframe(value: object) -> int:
|
|
|
255
120
|
Returns:
|
|
256
121
|
The validated integer timeframe value.
|
|
257
122
|
"""
|
|
258
|
-
return
|
|
123
|
+
return pdmt5.parse_timeframe(value)
|
|
259
124
|
|
|
260
125
|
|
|
261
126
|
def _validate_mt5_copy_ticks(value: object) -> int:
|
|
@@ -264,27 +129,27 @@ def _validate_mt5_copy_ticks(value: object) -> int:
|
|
|
264
129
|
Returns:
|
|
265
130
|
The validated integer COPY_TICKS value.
|
|
266
131
|
"""
|
|
267
|
-
return
|
|
132
|
+
return pdmt5.parse_copy_ticks(value)
|
|
268
133
|
|
|
269
134
|
|
|
270
135
|
def get_mt5_order_type_values() -> tuple[int, ...]:
|
|
271
|
-
"""Return all available MT5 ORDER_TYPE values from
|
|
272
|
-
return
|
|
136
|
+
"""Return all available MT5 ORDER_TYPE values from pdmt5."""
|
|
137
|
+
return tuple(pdmt5.list_order_type_values())
|
|
273
138
|
|
|
274
139
|
|
|
275
140
|
def get_mt5_order_type_names() -> tuple[str, ...]:
|
|
276
|
-
"""Return all available MT5 ORDER_TYPE names from
|
|
277
|
-
return
|
|
141
|
+
"""Return all available MT5 ORDER_TYPE names from pdmt5."""
|
|
142
|
+
return tuple(pdmt5.list_order_type_names())
|
|
278
143
|
|
|
279
144
|
|
|
280
145
|
def get_mt5_order_type_examples() -> list[int]:
|
|
281
146
|
"""Return common MT5 ORDER_TYPE integer examples."""
|
|
282
|
-
return
|
|
147
|
+
return [pdmt5.ORDER_TYPE_MAP[name] for name in _ORDER_TYPE_EXAMPLE_NAMES]
|
|
283
148
|
|
|
284
149
|
|
|
285
150
|
def get_mt5_order_type_example_names() -> list[str]:
|
|
286
151
|
"""Return common MT5 ORDER_TYPE example names."""
|
|
287
|
-
return
|
|
152
|
+
return list(_ORDER_TYPE_EXAMPLE_NAMES)
|
|
288
153
|
|
|
289
154
|
|
|
290
155
|
def _validate_mt5_order_type(value: object) -> int:
|
|
@@ -293,7 +158,7 @@ def _validate_mt5_order_type(value: object) -> int:
|
|
|
293
158
|
Returns:
|
|
294
159
|
The validated integer ORDER_TYPE value.
|
|
295
160
|
"""
|
|
296
|
-
return
|
|
161
|
+
return pdmt5.parse_order_type(value)
|
|
297
162
|
|
|
298
163
|
|
|
299
164
|
Mt5Timeframe: TypeAlias = Annotated[
|
|
@@ -301,7 +166,7 @@ Mt5Timeframe: TypeAlias = Annotated[
|
|
|
301
166
|
BeforeValidator(_validate_mt5_timeframe),
|
|
302
167
|
WithJsonSchema(
|
|
303
168
|
_build_mt5_constant_json_schema(
|
|
304
|
-
description=
|
|
169
|
+
description=_TIMEFRAME_DESCRIPTION,
|
|
305
170
|
names=get_mt5_timeframe_names(),
|
|
306
171
|
values=get_mt5_timeframe_values(),
|
|
307
172
|
examples=get_mt5_timeframe_example_names(),
|
|
@@ -316,7 +181,7 @@ Mt5CopyTicks: TypeAlias = Annotated[
|
|
|
316
181
|
BeforeValidator(_validate_mt5_copy_ticks),
|
|
317
182
|
WithJsonSchema(
|
|
318
183
|
_build_mt5_constant_json_schema(
|
|
319
|
-
description=
|
|
184
|
+
description=_COPY_TICKS_DESCRIPTION,
|
|
320
185
|
names=get_mt5_copy_ticks_names(),
|
|
321
186
|
values=get_mt5_copy_ticks_values(),
|
|
322
187
|
examples=get_mt5_copy_ticks_example_names(),
|
|
@@ -331,7 +196,7 @@ Mt5OrderType: TypeAlias = Annotated[
|
|
|
331
196
|
BeforeValidator(_validate_mt5_order_type),
|
|
332
197
|
WithJsonSchema(
|
|
333
198
|
_build_mt5_constant_json_schema(
|
|
334
|
-
description=
|
|
199
|
+
description=_ORDER_TYPE_DESCRIPTION,
|
|
335
200
|
names=get_mt5_order_type_names(),
|
|
336
201
|
values=get_mt5_order_type_values(),
|
|
337
202
|
examples=get_mt5_order_type_example_names(),
|
|
@@ -341,6 +206,13 @@ Mt5OrderType: TypeAlias = Annotated[
|
|
|
341
206
|
]
|
|
342
207
|
|
|
343
208
|
|
|
209
|
+
class ResponseFormat(StrEnum):
|
|
210
|
+
"""Response format enumeration."""
|
|
211
|
+
|
|
212
|
+
JSON = "json"
|
|
213
|
+
PARQUET = "parquet"
|
|
214
|
+
|
|
215
|
+
|
|
344
216
|
class ErrorResponse(BaseModel):
|
|
345
217
|
"""RFC 7807 Problem Details error response model."""
|
|
346
218
|
|
|
@@ -532,7 +404,7 @@ class TicksFromRequest(BaseModel):
|
|
|
532
404
|
le=100000,
|
|
533
405
|
)
|
|
534
406
|
flags: Mt5CopyTicks = Field(
|
|
535
|
-
default=
|
|
407
|
+
default=pdmt5.COPY_TICKS_MAP["COPY_TICKS_ALL"],
|
|
536
408
|
description=_COPY_TICKS_DESCRIPTION,
|
|
537
409
|
)
|
|
538
410
|
format: ResponseFormat | None = Field(default=None)
|
|
@@ -545,7 +417,7 @@ class TicksRangeRequest(BaseModel):
|
|
|
545
417
|
date_from: datetime = Field(..., description="Start date (ISO 8601)")
|
|
546
418
|
date_to: datetime = Field(..., description="End date (ISO 8601)")
|
|
547
419
|
flags: Mt5CopyTicks = Field(
|
|
548
|
-
default=
|
|
420
|
+
default=pdmt5.COPY_TICKS_MAP["COPY_TICKS_ALL"],
|
|
549
421
|
description=_COPY_TICKS_DESCRIPTION,
|
|
550
422
|
)
|
|
551
423
|
format: ResponseFormat | None = Field(default=None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mt5api"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "MetaTrader 5 REST API"
|
|
5
5
|
authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
|
6
6
|
maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
|
@@ -9,14 +9,11 @@ license-files = ["LICENSE"]
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">= 3.11, < 3.14"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"
|
|
13
|
-
"pdmt5 >= 0.2.1",
|
|
12
|
+
"pdmt5 >= 0.3.0",
|
|
14
13
|
"fastapi >= 0.115.0",
|
|
15
14
|
"uvicorn[standard] >= 0.32.0",
|
|
16
15
|
"pyarrow >= 18.0.0",
|
|
17
16
|
"python-multipart >= 0.0.9",
|
|
18
|
-
"python-jose[cryptography] >= 3.3.0",
|
|
19
|
-
"passlib[bcrypt] >= 1.7.4",
|
|
20
17
|
"httpx >= 0.27.0",
|
|
21
18
|
]
|
|
22
19
|
classifiers = [
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Shared OpenAPI schema assertions for MT5 constant metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from mt5api.models import (
|
|
8
|
+
get_mt5_order_type_example_names,
|
|
9
|
+
get_mt5_order_type_names,
|
|
10
|
+
get_mt5_order_type_values,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
ORDER_TYPE_DESCRIPTION = (
|
|
14
|
+
"MetaTrader5 ORDER_TYPE constant. Accepts a constant name such as "
|
|
15
|
+
"ORDER_TYPE_BUY, short aliases such as BUY, or the corresponding integer value."
|
|
16
|
+
)
|
|
17
|
+
VALID_ORDER_TYPE_NAMES = list(get_mt5_order_type_names())
|
|
18
|
+
VALID_ORDER_TYPE_VALUES = list(get_mt5_order_type_values())
|
|
19
|
+
ORDER_TYPE_EXAMPLES = get_mt5_order_type_example_names()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def assert_openapi_mt5_order_type_schema(schema: dict[str, Any]) -> None:
|
|
23
|
+
"""Assert an OpenAPI schema documents ORDER_TYPE names, values, and examples."""
|
|
24
|
+
string_schema = next(
|
|
25
|
+
member for member in schema["anyOf"] if member["type"] == "string"
|
|
26
|
+
)
|
|
27
|
+
integer_schema = next(
|
|
28
|
+
member for member in schema["anyOf"] if member["type"] == "integer"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
assert sorted(string_schema["enum"]) == sorted(VALID_ORDER_TYPE_NAMES)
|
|
32
|
+
assert sorted(integer_schema["enum"]) == sorted(VALID_ORDER_TYPE_VALUES)
|
|
33
|
+
assert schema["examples"] == ORDER_TYPE_EXAMPLES
|
|
34
|
+
assert schema["description"] == ORDER_TYPE_DESCRIPTION
|
|
@@ -8,6 +8,7 @@ import pytest
|
|
|
8
8
|
from pdmt5.mt5 import Mt5RuntimeError
|
|
9
9
|
|
|
10
10
|
from tests.mt5_constants import Mt5OrderType
|
|
11
|
+
from tests.openapi_mt5_constants import assert_openapi_mt5_order_type_schema
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from unittest.mock import Mock
|
|
@@ -294,3 +295,18 @@ def test_get_calc_profit_returns_service_unavailable_on_mt5_error(
|
|
|
294
295
|
payload = response.json()
|
|
295
296
|
assert payload["title"] == "MT5 Terminal Error"
|
|
296
297
|
assert "order_calc_profit returned None" in payload["detail"]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_openapi_documents_mt5_order_type_consistently(
|
|
301
|
+
client: TestClient,
|
|
302
|
+
) -> None:
|
|
303
|
+
"""Calc endpoints should expose MT5 ORDER_TYPE names and integer values."""
|
|
304
|
+
openapi = client.get("/openapi.json").json()
|
|
305
|
+
paths = ("/calc/margin", "/calc/profit")
|
|
306
|
+
|
|
307
|
+
for path in paths:
|
|
308
|
+
parameters = openapi["paths"][path]["get"]["parameters"]
|
|
309
|
+
action_parameter = next(
|
|
310
|
+
parameter for parameter in parameters if parameter["name"] == "action"
|
|
311
|
+
)
|
|
312
|
+
assert_openapi_mt5_order_type_schema(action_parameter["schema"])
|