mt5api 1.0.1__tar.gz → 1.0.2__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 (65) hide show
  1. {mt5api-1.0.1 → mt5api-1.0.2}/PKG-INFO +2 -2
  2. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/constants.py +1 -1
  3. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/models.py +30 -15
  4. {mt5api-1.0.1 → mt5api-1.0.2}/pyproject.toml +2 -2
  5. mt5api-1.0.2/tests/test_pdmt5_migration.py +232 -0
  6. {mt5api-1.0.1 → mt5api-1.0.2}/uv.lock +6 -6
  7. {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/local-qa/SKILL.md +0 -0
  8. {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  9. {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/mt5api/SKILL.md +0 -0
  10. {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  11. {mt5api-1.0.1 → mt5api-1.0.2}/.github/FUNDING.yml +0 -0
  12. {mt5api-1.0.1 → mt5api-1.0.2}/.github/dependabot.yml +0 -0
  13. {mt5api-1.0.1 → mt5api-1.0.2}/.github/renovate.json +0 -0
  14. {mt5api-1.0.1 → mt5api-1.0.2}/.github/workflows/ci.yml +0 -0
  15. {mt5api-1.0.1 → mt5api-1.0.2}/.github/workflows/claude.yml +0 -0
  16. {mt5api-1.0.1 → mt5api-1.0.2}/.github/workflows/release.yml +0 -0
  17. {mt5api-1.0.1 → mt5api-1.0.2}/.gitignore +0 -0
  18. {mt5api-1.0.1 → mt5api-1.0.2}/AGENTS.md +0 -0
  19. {mt5api-1.0.1 → mt5api-1.0.2}/CLAUDE.md +0 -0
  20. {mt5api-1.0.1 → mt5api-1.0.2}/LICENSE +0 -0
  21. {mt5api-1.0.1 → mt5api-1.0.2}/README.md +0 -0
  22. {mt5api-1.0.1 → mt5api-1.0.2}/docs/api/deployment.md +0 -0
  23. {mt5api-1.0.1 → mt5api-1.0.2}/docs/api/index.md +0 -0
  24. {mt5api-1.0.1 → mt5api-1.0.2}/docs/api/rest-api.md +0 -0
  25. {mt5api-1.0.1 → mt5api-1.0.2}/docs/index.md +0 -0
  26. {mt5api-1.0.1 → mt5api-1.0.2}/mkdocs.yml +0 -0
  27. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/__init__.py +0 -0
  28. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/__main__.py +0 -0
  29. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/auth.py +0 -0
  30. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/config.py +0 -0
  31. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/dependencies.py +0 -0
  32. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/formatters.py +0 -0
  33. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/main.py +0 -0
  34. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/middleware.py +0 -0
  35. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/__init__.py +0 -0
  36. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/account.py +0 -0
  37. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/calc.py +0 -0
  38. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/connection.py +0 -0
  39. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/health.py +0 -0
  40. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/history.py +0 -0
  41. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/market.py +0 -0
  42. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/symbols.py +0 -0
  43. {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/trading.py +0 -0
  44. {mt5api-1.0.1 → mt5api-1.0.2}/tests/__init__.py +0 -0
  45. {mt5api-1.0.1 → mt5api-1.0.2}/tests/conftest.py +0 -0
  46. {mt5api-1.0.1 → mt5api-1.0.2}/tests/mt5_constants.py +0 -0
  47. {mt5api-1.0.1 → mt5api-1.0.2}/tests/openapi_mt5_constants.py +0 -0
  48. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_account.py +0 -0
  49. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_auth.py +0 -0
  50. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_calc.py +0 -0
  51. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_cli.py +0 -0
  52. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_config.py +0 -0
  53. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_connection.py +0 -0
  54. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_dependencies.py +0 -0
  55. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_formatters.py +0 -0
  56. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_health.py +0 -0
  57. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_history.py +0 -0
  58. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_integration.py +0 -0
  59. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_lifespan.py +0 -0
  60. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_main.py +0 -0
  61. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_market.py +0 -0
  62. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_middleware.py +0 -0
  63. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_models.py +0 -0
  64. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_symbols.py +0 -0
  65. {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_trading.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5api
3
- Version: 1.0.1
3
+ Version: 1.0.2
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,7 +18,7 @@ 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: pdmt5>=1.1.1
21
+ Requires-Dist: pdmt5>=1.2.0
22
22
  Requires-Dist: pyarrow>=18.0.0
23
23
  Requires-Dist: python-multipart>=0.0.31
24
24
  Requires-Dist: uvicorn[standard]>=0.32.0
@@ -6,7 +6,7 @@ API_DESCRIPTION = (
6
6
  "Provides market data, account information, trading history, calculations, "
7
7
  "and safe operational endpoints via HTTP."
8
8
  )
9
- API_VERSION = "1.0.1"
9
+ API_VERSION = "1.0.2"
10
10
  API_DOCS_URL = "/docs"
11
11
  API_REDOC_URL = "/redoc"
12
12
  API_OPENAPI_URL = "/openapi.json"
@@ -6,7 +6,22 @@ from datetime import datetime # noqa: TC003
6
6
  from enum import StrEnum
7
7
  from typing import Annotated, Any, Self, TypeAlias
8
8
 
9
- import pdmt5
9
+ from pdmt5 import (
10
+ COPY_TICKS_MAP,
11
+ ORDER_TYPE_MAP,
12
+ TIMEFRAME_MAP,
13
+ parse_copy_ticks,
14
+ parse_order_type,
15
+ parse_timeframe,
16
+ )
17
+ from pdmt5.constants import (
18
+ list_copy_ticks_names,
19
+ list_copy_ticks_values,
20
+ list_order_type_names,
21
+ list_order_type_values,
22
+ list_timeframe_names,
23
+ list_timeframe_values,
24
+ )
10
25
  from pydantic import (
11
26
  BaseModel,
12
27
  BeforeValidator,
@@ -76,17 +91,17 @@ def _build_mt5_constant_json_schema(
76
91
 
77
92
  def get_mt5_timeframe_values() -> tuple[int, ...]:
78
93
  """Return all available MT5 timeframe values from pdmt5."""
79
- return tuple(pdmt5.list_timeframe_values())
94
+ return tuple(list_timeframe_values())
80
95
 
81
96
 
82
97
  def get_mt5_timeframe_names() -> tuple[str, ...]:
83
98
  """Return all available MT5 timeframe names from pdmt5."""
84
- return tuple(pdmt5.list_timeframe_names())
99
+ return tuple(list_timeframe_names())
85
100
 
86
101
 
87
102
  def get_mt5_timeframe_examples() -> list[int]:
88
103
  """Return common MT5 timeframe examples from pdmt5."""
89
- return [pdmt5.TIMEFRAME_MAP[name] for name in _TIMEFRAME_EXAMPLE_NAMES]
104
+ return [TIMEFRAME_MAP[name] for name in _TIMEFRAME_EXAMPLE_NAMES]
90
105
 
91
106
 
92
107
  def get_mt5_timeframe_example_names() -> list[str]:
@@ -96,17 +111,17 @@ def get_mt5_timeframe_example_names() -> list[str]:
96
111
 
97
112
  def get_mt5_copy_ticks_values() -> tuple[int, ...]:
98
113
  """Return all MT5 COPY_TICKS values from pdmt5."""
99
- return tuple(pdmt5.list_copy_ticks_values())
114
+ return tuple(list_copy_ticks_values())
100
115
 
101
116
 
102
117
  def get_mt5_copy_ticks_names() -> tuple[str, ...]:
103
118
  """Return all MT5 COPY_TICKS names from pdmt5."""
104
- return tuple(pdmt5.list_copy_ticks_names())
119
+ return tuple(list_copy_ticks_names())
105
120
 
106
121
 
107
122
  def get_mt5_copy_ticks_examples() -> list[int]:
108
123
  """Return common MT5 COPY_TICKS integer examples."""
109
- return [pdmt5.COPY_TICKS_MAP[name] for name in _COPY_TICKS_EXAMPLE_NAMES]
124
+ return [COPY_TICKS_MAP[name] for name in _COPY_TICKS_EXAMPLE_NAMES]
110
125
 
111
126
 
112
127
  def get_mt5_copy_ticks_example_names() -> list[str]:
@@ -120,7 +135,7 @@ def _validate_mt5_timeframe(value: object) -> int:
120
135
  Returns:
121
136
  The validated integer timeframe value.
122
137
  """
123
- return pdmt5.parse_timeframe(value)
138
+ return parse_timeframe(value)
124
139
 
125
140
 
126
141
  def _validate_mt5_copy_ticks(value: object) -> int:
@@ -129,22 +144,22 @@ def _validate_mt5_copy_ticks(value: object) -> int:
129
144
  Returns:
130
145
  The validated integer COPY_TICKS value.
131
146
  """
132
- return pdmt5.parse_copy_ticks(value)
147
+ return parse_copy_ticks(value)
133
148
 
134
149
 
135
150
  def get_mt5_order_type_values() -> tuple[int, ...]:
136
151
  """Return all available MT5 ORDER_TYPE values from pdmt5."""
137
- return tuple(pdmt5.list_order_type_values())
152
+ return tuple(list_order_type_values())
138
153
 
139
154
 
140
155
  def get_mt5_order_type_names() -> tuple[str, ...]:
141
156
  """Return all available MT5 ORDER_TYPE names from pdmt5."""
142
- return tuple(pdmt5.list_order_type_names())
157
+ return tuple(list_order_type_names())
143
158
 
144
159
 
145
160
  def get_mt5_order_type_examples() -> list[int]:
146
161
  """Return common MT5 ORDER_TYPE integer examples."""
147
- return [pdmt5.ORDER_TYPE_MAP[name] for name in _ORDER_TYPE_EXAMPLE_NAMES]
162
+ return [ORDER_TYPE_MAP[name] for name in _ORDER_TYPE_EXAMPLE_NAMES]
148
163
 
149
164
 
150
165
  def get_mt5_order_type_example_names() -> list[str]:
@@ -158,7 +173,7 @@ def _validate_mt5_order_type(value: object) -> int:
158
173
  Returns:
159
174
  The validated integer ORDER_TYPE value.
160
175
  """
161
- return pdmt5.parse_order_type(value)
176
+ return parse_order_type(value)
162
177
 
163
178
 
164
179
  Mt5Timeframe: TypeAlias = Annotated[
@@ -404,7 +419,7 @@ class TicksFromRequest(BaseModel):
404
419
  le=100000,
405
420
  )
406
421
  flags: Mt5CopyTicks = Field(
407
- default=pdmt5.COPY_TICKS_MAP["COPY_TICKS_ALL"],
422
+ default=COPY_TICKS_MAP["COPY_TICKS_ALL"],
408
423
  description=_COPY_TICKS_DESCRIPTION,
409
424
  )
410
425
  format: ResponseFormat | None = Field(default=None)
@@ -417,7 +432,7 @@ class TicksRangeRequest(BaseModel):
417
432
  date_from: datetime = Field(..., description="Start date (ISO 8601)")
418
433
  date_to: datetime = Field(..., description="End date (ISO 8601)")
419
434
  flags: Mt5CopyTicks = Field(
420
- default=pdmt5.COPY_TICKS_MAP["COPY_TICKS_ALL"],
435
+ default=COPY_TICKS_MAP["COPY_TICKS_ALL"],
421
436
  description=_COPY_TICKS_DESCRIPTION,
422
437
  )
423
438
  format: ResponseFormat | None = Field(default=None)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mt5api"
3
- version = "1.0.1"
3
+ version = "1.0.2"
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,7 +9,7 @@ license-files = ["LICENSE"]
9
9
  readme = "README.md"
10
10
  requires-python = ">= 3.11, < 3.14"
11
11
  dependencies = [
12
- "pdmt5 >= 1.1.1",
12
+ "pdmt5 >= 1.2.0",
13
13
  "fastapi >= 0.115.0",
14
14
  "uvicorn[standard] >= 0.32.0",
15
15
  "pyarrow >= 18.0.0",
@@ -0,0 +1,232 @@
1
+ """Regression tests for the pdmt5 >= 1.2.0 migration.
2
+
3
+ pdmt5 1.2.0 removed constant-introspection helpers (``list_*``/``get_*``)
4
+ from the package root; they remain available from ``pdmt5.constants``. These
5
+ tests guard against regressions in that migration: import-time schema
6
+ construction, unchanged OpenAPI schema content, unchanged parsing behavior,
7
+ and the dependency floor declared in package metadata.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import ast
13
+ import importlib
14
+ import importlib.metadata
15
+ import sys
16
+ from datetime import UTC, datetime
17
+ from pathlib import Path
18
+
19
+ import pdmt5
20
+ import pytest
21
+ from pdmt5 import constants as pdmt5_constants
22
+
23
+ from mt5api import models
24
+ from mt5api.models import (
25
+ CalcMarginRequest,
26
+ RatesFromRequest,
27
+ TicksFromRequest,
28
+ )
29
+
30
+ _TIMEFRAME_DESCRIPTION = (
31
+ "MetaTrader5 TIMEFRAME constant. Accepts a constant name such as "
32
+ "TIMEFRAME_M1, short aliases such as M1, or the corresponding integer value."
33
+ )
34
+ _COPY_TICKS_DESCRIPTION = (
35
+ "MetaTrader5 COPY_TICKS constant. Accepts COPY_TICKS_INFO, "
36
+ "COPY_TICKS_TRADE, COPY_TICKS_ALL, short aliases such as ALL, "
37
+ "or the corresponding integer value."
38
+ )
39
+ _ORDER_TYPE_DESCRIPTION = (
40
+ "MetaTrader5 ORDER_TYPE constant. Accepts a constant name such as "
41
+ "ORDER_TYPE_BUY, short aliases such as BUY, or the corresponding integer value."
42
+ )
43
+
44
+
45
+ def test_mt5api_models_imports_successfully() -> None:
46
+ """``import mt5api.models`` succeeds against the installed pdmt5."""
47
+ reloaded = importlib.reload(sys.modules["mt5api.models"])
48
+ assert reloaded is models
49
+
50
+
51
+ def test_removed_root_helpers_are_not_exposed_by_pdmt5() -> None:
52
+ """pdmt5 1.2.0 no longer exposes introspection helpers at the root."""
53
+ removed_names = (
54
+ "get_timeframe_name",
55
+ "get_timeframe_value",
56
+ "get_copy_ticks_name",
57
+ "get_copy_ticks_value",
58
+ "get_order_type_name",
59
+ "get_order_type_value",
60
+ "list_timeframe_names",
61
+ "list_timeframe_values",
62
+ "list_copy_ticks_names",
63
+ "list_copy_ticks_values",
64
+ "list_order_type_names",
65
+ "list_order_type_values",
66
+ )
67
+ for name in removed_names:
68
+ assert not hasattr(pdmt5, name)
69
+
70
+
71
+ def test_removed_root_helpers_remain_available_from_constants() -> None:
72
+ """The removed helpers remain public from ``pdmt5.constants``."""
73
+ for name in (
74
+ "list_timeframe_names",
75
+ "list_timeframe_values",
76
+ "list_copy_ticks_names",
77
+ "list_copy_ticks_values",
78
+ "list_order_type_names",
79
+ "list_order_type_values",
80
+ ):
81
+ assert callable(getattr(pdmt5_constants, name))
82
+
83
+
84
+ def test_production_code_does_not_access_removed_pdmt5_root_helpers() -> None:
85
+ """No production source accesses removed helpers via ``pdmt5.<name>``."""
86
+ removed_prefixes = ("get_", "list_")
87
+ package_dir = Path(models.__file__).resolve().parent
88
+ for path in package_dir.rglob("*.py"):
89
+ tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
90
+ for node in ast.walk(tree):
91
+ if (
92
+ isinstance(node, ast.Attribute)
93
+ and isinstance(node.value, ast.Name)
94
+ and node.value.id == "pdmt5"
95
+ and node.attr.startswith(removed_prefixes)
96
+ ):
97
+ pytest.fail(f"{path}:{node.lineno} accesses pdmt5.{node.attr}")
98
+ if isinstance(node, ast.ImportFrom) and node.module == "pdmt5":
99
+ for alias in node.names:
100
+ assert not alias.name.startswith(removed_prefixes), (
101
+ f"{path}:{node.lineno} imports pdmt5.{alias.name}"
102
+ )
103
+
104
+
105
+ def test_timeframe_values_present_in_generated_schema() -> None:
106
+ """Timeframe enum values and names remain present in the schema."""
107
+ schema = RatesFromRequest.model_json_schema()
108
+ timeframe_schema = schema["properties"]["timeframe"]
109
+ assert timeframe_schema["description"] == _TIMEFRAME_DESCRIPTION
110
+ name_enum = timeframe_schema["anyOf"][0]["enum"]
111
+ value_enum = timeframe_schema["anyOf"][1]["enum"]
112
+ assert "TIMEFRAME_M1" in name_enum
113
+ assert "TIMEFRAME_D1" in name_enum
114
+ assert 1 in value_enum
115
+ assert 16408 in value_enum
116
+ assert timeframe_schema["examples"] == [
117
+ "TIMEFRAME_M1",
118
+ "TIMEFRAME_M5",
119
+ "TIMEFRAME_M15",
120
+ "TIMEFRAME_M30",
121
+ "TIMEFRAME_H1",
122
+ "TIMEFRAME_H4",
123
+ "TIMEFRAME_D1",
124
+ "TIMEFRAME_W1",
125
+ "TIMEFRAME_MN1",
126
+ ]
127
+
128
+
129
+ def test_copy_ticks_values_present_in_generated_schema() -> None:
130
+ """COPY_TICKS enum values and names remain present in the schema."""
131
+ schema = TicksFromRequest.model_json_schema()
132
+ flags_schema = schema["properties"]["flags"]
133
+ assert flags_schema["description"] == _COPY_TICKS_DESCRIPTION
134
+ name_enum = flags_schema["anyOf"][0]["enum"]
135
+ value_enum = flags_schema["anyOf"][1]["enum"]
136
+ assert {"COPY_TICKS_INFO", "COPY_TICKS_TRADE", "COPY_TICKS_ALL"} <= set(name_enum)
137
+ assert {1, 2, -1} <= set(value_enum)
138
+ assert flags_schema["examples"] == [
139
+ "COPY_TICKS_INFO",
140
+ "COPY_TICKS_TRADE",
141
+ "COPY_TICKS_ALL",
142
+ ]
143
+
144
+
145
+ def test_order_type_values_present_in_generated_schema() -> None:
146
+ """ORDER_TYPE enum values and names remain present in the schema."""
147
+ schema = CalcMarginRequest.model_json_schema()
148
+ action_schema = schema["properties"]["action"]
149
+ assert action_schema["description"] == _ORDER_TYPE_DESCRIPTION
150
+ name_enum = action_schema["anyOf"][0]["enum"]
151
+ value_enum = action_schema["anyOf"][1]["enum"]
152
+ assert {"ORDER_TYPE_BUY", "ORDER_TYPE_SELL"} <= set(name_enum)
153
+ assert {0, 1} <= set(value_enum)
154
+ assert action_schema["examples"] == ["ORDER_TYPE_BUY", "ORDER_TYPE_SELL"]
155
+
156
+
157
+ @pytest.mark.parametrize(
158
+ ("alias", "expected"),
159
+ [
160
+ ("M1", 1),
161
+ ("TIMEFRAME_M1", 1),
162
+ (1, 1),
163
+ ("D1", 16408),
164
+ ],
165
+ )
166
+ def test_timeframe_parsing_accepts_existing_aliases(
167
+ alias: str | int, expected: int
168
+ ) -> None:
169
+ """Timeframe parsing accepts the same string aliases and integers."""
170
+ request = RatesFromRequest.model_validate({
171
+ "symbol": "EURUSD",
172
+ "timeframe": alias,
173
+ "date_from": datetime(2024, 1, 1, tzinfo=UTC),
174
+ "count": 1,
175
+ })
176
+ assert request.timeframe == expected
177
+
178
+
179
+ @pytest.mark.parametrize(
180
+ ("alias", "expected"),
181
+ [
182
+ ("ALL", -1),
183
+ ("COPY_TICKS_ALL", -1),
184
+ (-1, -1),
185
+ ("INFO", 1),
186
+ ],
187
+ )
188
+ def test_copy_ticks_parsing_accepts_existing_aliases(
189
+ alias: str | int, expected: int
190
+ ) -> None:
191
+ """COPY_TICKS parsing accepts the same string aliases and integers."""
192
+ request = TicksFromRequest.model_validate({
193
+ "symbol": "EURUSD",
194
+ "date_from": datetime(2024, 1, 1, tzinfo=UTC),
195
+ "count": 1,
196
+ "flags": alias,
197
+ })
198
+ assert request.flags == expected
199
+
200
+
201
+ @pytest.mark.parametrize(
202
+ ("alias", "expected"),
203
+ [
204
+ ("BUY", 0),
205
+ ("ORDER_TYPE_BUY", 0),
206
+ (0, 0),
207
+ ("SELL", 1),
208
+ ],
209
+ )
210
+ def test_order_type_parsing_accepts_existing_aliases(
211
+ alias: str | int, expected: int
212
+ ) -> None:
213
+ """ORDER_TYPE parsing accepts the same string aliases and integers."""
214
+ request = CalcMarginRequest.model_validate({
215
+ "action": alias,
216
+ "symbol": "EURUSD",
217
+ "volume": 1.0,
218
+ "price": 1.1,
219
+ })
220
+ assert request.action == expected
221
+
222
+
223
+ def test_installed_package_metadata_requires_pdmt5_1_2_0() -> None:
224
+ """Installed mt5api metadata declares a pdmt5 >= 1.2.0 requirement."""
225
+ requires = importlib.metadata.requires("mt5api") or []
226
+ pdmt5_requirements = [
227
+ req for req in requires if req.split(";")[0].strip().startswith("pdmt5")
228
+ ]
229
+ assert pdmt5_requirements, "mt5api metadata does not declare a pdmt5 requirement"
230
+ assert any(">=1.2.0" in req.replace(" ", "") for req in pdmt5_requirements), (
231
+ pdmt5_requirements
232
+ )
@@ -427,7 +427,7 @@ name = "metatrader5"
427
427
  version = "5.0.5735"
428
428
  source = { registry = "https://pypi.org/simple" }
429
429
  dependencies = [
430
- { name = "numpy" },
430
+ { name = "numpy", marker = "sys_platform == 'win32'" },
431
431
  ]
432
432
  wheels = [
433
433
  { url = "https://files.pythonhosted.org/packages/07/0b/3dd5143f14319dca393a3bcf11ddf24f36f16dfb8c6d1bf2b193ce0c5733/metatrader5-5.0.5735-cp311-cp311-win_amd64.whl", hash = "sha256:0d05b69cef5eb3f43ea47cb6d36dac0e7e15f82a4fb77974439c565daa54d1c9", size = 48091, upload-time = "2026-04-04T16:44:08.677Z" },
@@ -556,7 +556,7 @@ wheels = [
556
556
 
557
557
  [[package]]
558
558
  name = "mt5api"
559
- version = "1.0.1"
559
+ version = "1.0.2"
560
560
  source = { editable = "." }
561
561
  dependencies = [
562
562
  { name = "fastapi" },
@@ -588,7 +588,7 @@ dev = [
588
588
  requires-dist = [
589
589
  { name = "fastapi", specifier = ">=0.115.0" },
590
590
  { name = "httpx", specifier = ">=0.27.0" },
591
- { name = "pdmt5", specifier = ">=1.1.1" },
591
+ { name = "pdmt5", specifier = ">=1.2.0" },
592
592
  { name = "pyarrow", specifier = ">=18.0.0" },
593
593
  { name = "python-multipart", specifier = ">=0.0.31" },
594
594
  { name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" },
@@ -764,16 +764,16 @@ wheels = [
764
764
 
765
765
  [[package]]
766
766
  name = "pdmt5"
767
- version = "1.1.1"
767
+ version = "1.2.0"
768
768
  source = { registry = "https://pypi.org/simple" }
769
769
  dependencies = [
770
770
  { name = "metatrader5", marker = "sys_platform == 'win32'" },
771
771
  { name = "pandas" },
772
772
  { name = "pydantic" },
773
773
  ]
774
- sdist = { url = "https://files.pythonhosted.org/packages/4d/9c/4c51e9b109440629762a5ca719c367798469bb590b8ce99aa0652c467549/pdmt5-1.1.1.tar.gz", hash = "sha256:2d8e4e9b87d237793f8534813b373545eadb5c0e1ced13ee8be8dc473c77190d", size = 23879, upload-time = "2026-07-07T15:50:19.604Z" }
774
+ sdist = { url = "https://files.pythonhosted.org/packages/9b/44/410bdc4d567ddb49f5b8d0d366467c9cce5e1862ecfbbcd09734c8f34eac/pdmt5-1.2.0.tar.gz", hash = "sha256:972f500ee7e50032a14f83874baccbef69bf7377335011f72d0bbdd6e221edcd", size = 24017, upload-time = "2026-07-10T15:21:27.215Z" }
775
775
  wheels = [
776
- { url = "https://files.pythonhosted.org/packages/8f/18/4221deef65dc320da9b6299e1914c36c1e81b29a5557f0c1083718b36545/pdmt5-1.1.1-py3-none-any.whl", hash = "sha256:9df16148ebec754c15ce3d1b02af82aaeec3c5cb1603a47258fa68c74f45b453", size = 22274, upload-time = "2026-07-07T15:50:18.489Z" },
776
+ { url = "https://files.pythonhosted.org/packages/12/ec/88297de8aad400c922715cbdc15804dcf1ac5fbc0a3a0abeafc988c3c25d/pdmt5-1.2.0-py3-none-any.whl", hash = "sha256:ce355f24f4ec08ba5cc0148b5c529f73c671ea2e83b1d65248de1150369daeb7", size = 22369, upload-time = "2026-07-10T15:21:25.833Z" },
777
777
  ]
778
778
 
779
779
  [[package]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes