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.
Files changed (113) hide show
  1. {mt5api-0.2.0 → mt5api-0.3.0}/.github/workflows/claude.yml +3 -3
  2. {mt5api-0.2.0 → mt5api-0.3.0}/PKG-INFO +17 -16
  3. {mt5api-0.2.0 → mt5api-0.3.0}/README.md +15 -11
  4. {mt5api-0.2.0 → mt5api-0.3.0}/docs/api/rest-api.md +6 -5
  5. {mt5api-0.2.0 → mt5api-0.3.0}/docs/index.md +12 -8
  6. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/models.py +41 -169
  7. {mt5api-0.2.0 → mt5api-0.3.0}/pyproject.toml +2 -5
  8. {mt5api-0.2.0 → mt5api-0.3.0}/tests/mt5_constants.py +1 -1
  9. mt5api-0.3.0/tests/openapi_mt5_constants.py +34 -0
  10. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_calc.py +16 -0
  11. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_market.py +8 -6
  12. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_models.py +97 -45
  13. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_trading.py +12 -0
  14. {mt5api-0.2.0 → mt5api-0.3.0}/uv.lock +47 -277
  15. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/local-qa/SKILL.md +0 -0
  16. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  17. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/mt5api/SKILL.md +0 -0
  18. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-analyze/SKILL.md +0 -0
  19. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-checklist/SKILL.md +0 -0
  20. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-clarify/SKILL.md +0 -0
  21. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-constitution/SKILL.md +0 -0
  22. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-implement/SKILL.md +0 -0
  23. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-plan/SKILL.md +0 -0
  24. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-specify/SKILL.md +0 -0
  25. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-tasks/SKILL.md +0 -0
  26. {mt5api-0.2.0 → mt5api-0.3.0}/.agents/skills/speckit-taskstoissues/SKILL.md +0 -0
  27. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/agents/codex.md +0 -0
  28. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/agents/copilot.md +0 -0
  29. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.analyze.md +0 -0
  30. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.checklist.md +0 -0
  31. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.clarify.md +0 -0
  32. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.constitution.md +0 -0
  33. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.implement.md +0 -0
  34. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.plan.md +0 -0
  35. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.specify.md +0 -0
  36. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.tasks.md +0 -0
  37. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/commands/speckit.taskstoissues.md +0 -0
  38. {mt5api-0.2.0 → mt5api-0.3.0}/.claude/settings.json +0 -0
  39. {mt5api-0.2.0 → mt5api-0.3.0}/.github/FUNDING.yml +0 -0
  40. {mt5api-0.2.0 → mt5api-0.3.0}/.github/dependabot.yml +0 -0
  41. {mt5api-0.2.0 → mt5api-0.3.0}/.github/renovate.json +0 -0
  42. {mt5api-0.2.0 → mt5api-0.3.0}/.github/workflows/ci.yml +0 -0
  43. {mt5api-0.2.0 → mt5api-0.3.0}/.github/workflows/release.yml +0 -0
  44. {mt5api-0.2.0 → mt5api-0.3.0}/.gitignore +0 -0
  45. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/memory/constitution.md +0 -0
  46. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/check-prerequisites.sh +0 -0
  47. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/common.sh +0 -0
  48. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/create-new-feature.sh +0 -0
  49. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/setup-plan.sh +0 -0
  50. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/scripts/bash/update-agent-context.sh +0 -0
  51. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/agent-file-template.md +0 -0
  52. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/checklist-template.md +0 -0
  53. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/plan-template.md +0 -0
  54. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/spec-template.md +0 -0
  55. {mt5api-0.2.0 → mt5api-0.3.0}/.specify/templates/tasks-template.md +0 -0
  56. {mt5api-0.2.0 → mt5api-0.3.0}/AGENTS.md +0 -0
  57. {mt5api-0.2.0 → mt5api-0.3.0}/CLAUDE.md +0 -0
  58. {mt5api-0.2.0 → mt5api-0.3.0}/LICENSE +0 -0
  59. {mt5api-0.2.0 → mt5api-0.3.0}/docs/api/deployment.md +0 -0
  60. {mt5api-0.2.0 → mt5api-0.3.0}/docs/api/index.md +0 -0
  61. {mt5api-0.2.0 → mt5api-0.3.0}/mkdocs.yml +0 -0
  62. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/__init__.py +0 -0
  63. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/__main__.py +0 -0
  64. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/auth.py +0 -0
  65. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/config.py +0 -0
  66. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/constants.py +0 -0
  67. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/dependencies.py +0 -0
  68. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/formatters.py +0 -0
  69. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/main.py +0 -0
  70. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/middleware.py +0 -0
  71. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/__init__.py +0 -0
  72. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/account.py +0 -0
  73. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/calc.py +0 -0
  74. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/connection.py +0 -0
  75. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/health.py +0 -0
  76. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/history.py +0 -0
  77. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/market.py +0 -0
  78. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/symbols.py +0 -0
  79. {mt5api-0.2.0 → mt5api-0.3.0}/mt5api/routers/trading.py +0 -0
  80. {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/contracts/openapi.yaml +0 -0
  81. {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/data-model.md +0 -0
  82. {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/plan.md +0 -0
  83. {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/quickstart.md +0 -0
  84. {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/research.md +0 -0
  85. {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/spec.md +0 -0
  86. {mt5api-0.2.0 → mt5api-0.3.0}/specs/041-mt5-rest-api/tasks.md +0 -0
  87. {mt5api-0.2.0 → mt5api-0.3.0}/specs/042-mt5-core-client/spec.md +0 -0
  88. {mt5api-0.2.0 → mt5api-0.3.0}/specs/043-mt5-dataframe-client/spec.md +0 -0
  89. {mt5api-0.2.0 → mt5api-0.3.0}/specs/044-mt5-trading-client/spec.md +0 -0
  90. {mt5api-0.2.0 → mt5api-0.3.0}/specs/045-mt5-utils/spec.md +0 -0
  91. {mt5api-0.2.0 → mt5api-0.3.0}/specs/046-mt5-api-service/spec.md +0 -0
  92. {mt5api-0.2.0 → mt5api-0.3.0}/specs/047-api-auth-rate-limit/spec.md +0 -0
  93. {mt5api-0.2.0 → mt5api-0.3.0}/specs/048-api-response-format/spec.md +0 -0
  94. {mt5api-0.2.0 → mt5api-0.3.0}/specs/049-api-routing-models/spec.md +0 -0
  95. {mt5api-0.2.0 → mt5api-0.3.0}/specs/050-api-runtime-deploy/spec.md +0 -0
  96. {mt5api-0.2.0 → mt5api-0.3.0}/specs/051-api-error-logging/spec.md +0 -0
  97. {mt5api-0.2.0 → mt5api-0.3.0}/specs/052-api-dependencies/spec.md +0 -0
  98. {mt5api-0.2.0 → mt5api-0.3.0}/tests/__init__.py +0 -0
  99. {mt5api-0.2.0 → mt5api-0.3.0}/tests/conftest.py +0 -0
  100. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_account.py +0 -0
  101. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_auth.py +0 -0
  102. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_cli.py +0 -0
  103. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_config.py +0 -0
  104. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_connection.py +0 -0
  105. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_dependencies.py +0 -0
  106. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_formatters.py +0 -0
  107. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_health.py +0 -0
  108. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_history.py +0 -0
  109. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_integration.py +0 -0
  110. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_lifespan.py +0 -0
  111. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_main.py +0 -0
  112. {mt5api-0.2.0 → mt5api-0.3.0}/tests/test_middleware.py +0 -0
  113. {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 == false
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 }} # zizmor: ignore[secrets-outside-env]
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 }} # zizmor: ignore[secrets-outside-env]
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.2.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: metatrader5>=5.0.4424; sys_platform == 'win32'
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
  [![CI/CD](https://github.com/dceoy/mt5api/actions/workflows/ci.yml/badge.svg)](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 uses the [`pdmt5`](https://github.com/dceoy/pdmt5)
38
- client internally and adds optional API-key auth and JSON/Parquet response
39
- formatting.
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
- The API server must run on Windows. The `MetaTrader5` Python package used by
42
- `pdmt5` is supported only on Windows, so you must host `mt5api` on a Windows
43
- machine with a logged-in MetaTrader 5 terminal. HTTP clients can connect from
44
- any operating system.
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 Library"]
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 either by
166
- official name (`TIMEFRAME_M1`, `COPY_TICKS_ALL`, `ORDER_TYPE_BUY`) or by their
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
  [![CI/CD](https://github.com/dceoy/mt5api/actions/workflows/ci.yml/badge.svg)](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 uses the [`pdmt5`](https://github.com/dceoy/pdmt5)
9
- client internally and adds optional API-key auth and JSON/Parquet response
10
- formatting.
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
- The API server must run on Windows. The `MetaTrader5` Python package used by
13
- `pdmt5` is supported only on Windows, so you must host `mt5api` on a Windows
14
- machine with a logged-in MetaTrader 5 terminal. HTTP clients can connect from
15
- any operating system.
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 Library"]
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 either by
137
- official name (`TIMEFRAME_M1`, `COPY_TICKS_ALL`, `ORDER_TYPE_BUY`) or by their
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 either the official MetaTrader 5 constant
112
- name (for example `TIMEFRAME_M1`, `COPY_TICKS_ALL`) or the equivalent integer
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 either the official MetaTrader 5 constant name (for example
126
- `ORDER_TYPE_BUY`) or the equivalent integer value.
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 exposes MT5 data and trading operations over HTTP using FastAPI. It
9
- relies on the underlying MT5 client library for connectivity and adds optional
10
- authentication and response formatting suitable for analytics
11
- workflows.
12
-
13
- The API server must run on Windows because the `MetaTrader5` Python package is
14
- Windows-only. Run `mt5api` on a Windows host with MetaTrader 5 installed and
15
- logged in. API clients can connect from any operating system.
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 functools import cache, cached_property
9
- from typing import TYPE_CHECKING, Annotated, Any, LiteralString, Self, TypeAlias, cast
7
+ from typing import Annotated, Any, Self, TypeAlias
10
8
 
11
- from pdmt5.mt5 import Mt5Client
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, or the corresponding integer value."
37
+ "COPY_TICKS_TRADE, COPY_TICKS_ALL, short aliases such as ALL, "
38
+ "or the corresponding integer value."
53
39
  )
54
- _COPY_TICKS_NAMES = (
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 the pdmt5 MT5 module copy."""
214
- return _TIMEFRAME_SPEC.sorted_values
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 the pdmt5 MT5 module copy."""
219
- return _TIMEFRAME_SPEC.sorted_names
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 the pdmt5 MT5 module copy."""
224
- return _TIMEFRAME_SPEC.example_values
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 _TIMEFRAME_SPEC.example_name_list
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 the pdmt5 MT5 module copy."""
234
- return _COPY_TICKS_SPEC.sorted_values
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 the pdmt5 MT5 module copy."""
239
- return _COPY_TICKS_SPEC.sorted_names
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 _COPY_TICKS_SPEC.example_values
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 _COPY_TICKS_SPEC.example_name_list
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 _parse_mt5_constant(value, spec=_TIMEFRAME_SPEC)
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 _parse_mt5_constant(value, spec=_COPY_TICKS_SPEC)
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 the pdmt5 MT5 module copy."""
272
- return _ORDER_TYPE_SPEC.sorted_values
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 the pdmt5 MT5 module copy."""
277
- return _ORDER_TYPE_SPEC.sorted_names
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 _ORDER_TYPE_SPEC.example_values
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 _ORDER_TYPE_SPEC.example_name_list
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 _parse_mt5_constant(value, spec=_ORDER_TYPE_SPEC)
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=_TIMEFRAME_SPEC.schema_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=_COPY_TICKS_SPEC.schema_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=_ORDER_TYPE_SPEC.schema_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=_COPY_TICKS_SPEC.members["COPY_TICKS_ALL"],
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=_COPY_TICKS_SPEC.members["COPY_TICKS_ALL"],
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.2.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
- "MetaTrader5 >= 5.0.4424; sys_platform == 'win32'",
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 = [
@@ -36,7 +36,7 @@ class Mt5CopyTicks(IntEnum):
36
36
 
37
37
  COPY_TICKS_INFO = 1
38
38
  COPY_TICKS_TRADE = 2
39
- COPY_TICKS_ALL = 3
39
+ COPY_TICKS_ALL = -1
40
40
 
41
41
 
42
42
  class Mt5BookType(IntEnum):
@@ -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"])