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.
- {mt5api-1.0.1 → mt5api-1.0.2}/PKG-INFO +2 -2
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/constants.py +1 -1
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/models.py +30 -15
- {mt5api-1.0.1 → mt5api-1.0.2}/pyproject.toml +2 -2
- mt5api-1.0.2/tests/test_pdmt5_migration.py +232 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/uv.lock +6 -6
- {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/mt5api/SKILL.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.github/FUNDING.yml +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.github/dependabot.yml +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.github/renovate.json +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.github/workflows/ci.yml +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.github/workflows/claude.yml +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.github/workflows/release.yml +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/.gitignore +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/AGENTS.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/CLAUDE.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/LICENSE +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/README.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/docs/api/deployment.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/docs/api/index.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/docs/api/rest-api.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/docs/index.md +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mkdocs.yml +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/__init__.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/__main__.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/auth.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/config.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/dependencies.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/formatters.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/main.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/middleware.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/__init__.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/account.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/calc.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/connection.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/health.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/history.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/market.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/symbols.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/mt5api/routers/trading.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/__init__.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/conftest.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/mt5_constants.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/openapi_mt5_constants.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_account.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_auth.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_calc.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_cli.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_config.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_connection.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_dependencies.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_formatters.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_health.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_history.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_integration.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_lifespan.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_main.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_market.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_middleware.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_models.py +0 -0
- {mt5api-1.0.1 → mt5api-1.0.2}/tests/test_symbols.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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(
|
|
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 [
|
|
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(
|
|
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(
|
|
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 [
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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 [
|
|
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
|
|
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=
|
|
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=
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
|
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/
|
|
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
|
|
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
|