mt5api 1.0.1__tar.gz → 1.0.3__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 (66) hide show
  1. {mt5api-1.0.1 → mt5api-1.0.3}/.agents/skills/local-qa/scripts/qa.sh +5 -0
  2. mt5api-1.0.3/.github/CODEOWNERS +2 -0
  3. {mt5api-1.0.1 → mt5api-1.0.3}/.github/workflows/ci.yml +5 -5
  4. {mt5api-1.0.1 → mt5api-1.0.3}/.github/workflows/claude.yml +2 -2
  5. {mt5api-1.0.1 → mt5api-1.0.3}/.github/workflows/release.yml +2 -2
  6. mt5api-1.0.3/AGENTS.md +38 -0
  7. {mt5api-1.0.1 → mt5api-1.0.3}/PKG-INFO +5 -5
  8. {mt5api-1.0.1 → mt5api-1.0.3}/README.md +0 -1
  9. {mt5api-1.0.1 → mt5api-1.0.3}/docs/api/deployment.md +0 -1
  10. {mt5api-1.0.1 → mt5api-1.0.3}/docs/api/rest-api.md +0 -1
  11. {mt5api-1.0.1 → mt5api-1.0.3}/docs/index.md +0 -1
  12. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/constants.py +3 -1
  13. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/main.py +10 -20
  14. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/models.py +30 -15
  15. {mt5api-1.0.1 → mt5api-1.0.3}/pyproject.toml +8 -8
  16. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_auth.py +0 -10
  17. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_health.py +6 -8
  18. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_main.py +14 -90
  19. {mt5api-1.0.1 → mt5api-1.0.3}/uv.lock +274 -17
  20. mt5api-1.0.1/.agents/skills/pr-feedback-triage/SKILL.md +0 -211
  21. mt5api-1.0.1/AGENTS.md +0 -70
  22. mt5api-1.0.1/tests/test_integration.py +0 -80
  23. {mt5api-1.0.1 → mt5api-1.0.3}/.agents/skills/local-qa/SKILL.md +0 -0
  24. {mt5api-1.0.1 → mt5api-1.0.3}/.agents/skills/mt5api/SKILL.md +0 -0
  25. {mt5api-1.0.1 → mt5api-1.0.3}/.github/FUNDING.yml +0 -0
  26. {mt5api-1.0.1 → mt5api-1.0.3}/.github/dependabot.yml +0 -0
  27. {mt5api-1.0.1 → mt5api-1.0.3}/.github/renovate.json +0 -0
  28. {mt5api-1.0.1 → mt5api-1.0.3}/.gitignore +0 -0
  29. {mt5api-1.0.1 → mt5api-1.0.3}/CLAUDE.md +0 -0
  30. {mt5api-1.0.1 → mt5api-1.0.3}/LICENSE +0 -0
  31. {mt5api-1.0.1 → mt5api-1.0.3}/docs/api/index.md +0 -0
  32. {mt5api-1.0.1 → mt5api-1.0.3}/mkdocs.yml +0 -0
  33. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/__init__.py +0 -0
  34. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/__main__.py +0 -0
  35. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/auth.py +0 -0
  36. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/config.py +0 -0
  37. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/dependencies.py +0 -0
  38. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/formatters.py +0 -0
  39. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/middleware.py +0 -0
  40. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/__init__.py +0 -0
  41. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/account.py +0 -0
  42. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/calc.py +0 -0
  43. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/connection.py +0 -0
  44. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/health.py +0 -0
  45. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/history.py +0 -0
  46. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/market.py +0 -0
  47. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/symbols.py +0 -0
  48. {mt5api-1.0.1 → mt5api-1.0.3}/mt5api/routers/trading.py +0 -0
  49. {mt5api-1.0.1 → mt5api-1.0.3}/tests/__init__.py +0 -0
  50. {mt5api-1.0.1 → mt5api-1.0.3}/tests/conftest.py +0 -0
  51. {mt5api-1.0.1 → mt5api-1.0.3}/tests/mt5_constants.py +0 -0
  52. {mt5api-1.0.1 → mt5api-1.0.3}/tests/openapi_mt5_constants.py +0 -0
  53. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_account.py +0 -0
  54. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_calc.py +0 -0
  55. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_cli.py +0 -0
  56. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_config.py +0 -0
  57. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_connection.py +0 -0
  58. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_dependencies.py +0 -0
  59. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_formatters.py +0 -0
  60. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_history.py +0 -0
  61. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_lifespan.py +0 -0
  62. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_market.py +0 -0
  63. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_middleware.py +0 -0
  64. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_models.py +0 -0
  65. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_symbols.py +0 -0
  66. {mt5api-1.0.1 → mt5api-1.0.3}/tests/test_trading.py +0 -0
@@ -3,6 +3,11 @@
3
3
  set -euox pipefail
4
4
  cd "$(git rev-parse --show-toplevel)"
5
5
 
6
+ COOLDOWN_DAYS=7
7
+ export UV_EXCLUDE_NEWER="${COOLDOWN_DAYS} days"
8
+ export NPM_CONFIG_MIN_RELEASE_AGE="${COOLDOWN_DAYS}"
9
+ export PNPM_CONFIG_MINIMUM_RELEASE_AGE=$((COOLDOWN_DAYS * 24 * 60))
10
+
6
11
  # Python
7
12
  uv run ruff format .
8
13
  uv run ruff check --fix .
@@ -0,0 +1,2 @@
1
+ # Default owner
2
+ * @dceoy
@@ -35,7 +35,7 @@ jobs:
35
35
  || (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test')
36
36
  permissions:
37
37
  contents: read
38
- uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-lint-and-scan.yml@main # zizmor: ignore[unpinned-uses]
38
+ uses: dceoy/gha-for-devops/.github/workflows/python-package-lint-and-scan.yml@main # zizmor: ignore[unpinned-uses]
39
39
  with:
40
40
  package-path: .
41
41
  runs-on: windows-latest
@@ -46,7 +46,7 @@ jobs:
46
46
  || (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test')
47
47
  permissions:
48
48
  contents: read
49
- uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-test.yml@main # zizmor: ignore[unpinned-uses]
49
+ uses: dceoy/gha-for-devops/.github/workflows/python-package-test.yml@main # zizmor: ignore[unpinned-uses]
50
50
  with:
51
51
  package-path: .
52
52
  runs-on: windows-latest
@@ -56,7 +56,7 @@ jobs:
56
56
  || (github.event_name == 'workflow_dispatch' && inputs.workflow == 'docs-deploy')
57
57
  permissions:
58
58
  contents: write
59
- uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-mkdocs-gh-deploy.yml@main # zizmor: ignore[unpinned-uses]
59
+ uses: dceoy/gha-for-devops/.github/workflows/python-package-mkdocs-gh-deploy.yml@main # zizmor: ignore[unpinned-uses]
60
60
  with:
61
61
  package-path: .
62
62
  runs-on: ubuntu-slim
@@ -71,7 +71,7 @@ jobs:
71
71
  contents: read
72
72
  security-events: write
73
73
  actions: read
74
- uses: dceoy/gh-actions-for-devops/.github/workflows/github-codeql-analysis.yml@main # zizmor: ignore[unpinned-uses]
74
+ uses: dceoy/gha-for-devops/.github/workflows/github-codeql-analysis.yml@main # zizmor: ignore[unpinned-uses]
75
75
  with:
76
76
  language: >
77
77
  ["python"]
@@ -81,7 +81,7 @@ jobs:
81
81
  needs:
82
82
  - python-lint-and-scan
83
83
  - python-test
84
- uses: dceoy/gh-actions-for-devops/.github/workflows/dependabot-auto-merge.yml@main # zizmor: ignore[unpinned-uses]
84
+ uses: dceoy/gha-for-devops/.github/workflows/dependabot-auto-merge.yml@main # zizmor: ignore[unpinned-uses]
85
85
  permissions:
86
86
  contents: write
87
87
  pull-requests: write
@@ -35,7 +35,7 @@ jobs:
35
35
  issues: write
36
36
  id-token: write
37
37
  actions: read
38
- uses: dceoy/gh-actions-for-devops/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses]
38
+ uses: dceoy/gha-for-devops/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses]
39
39
  secrets:
40
40
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env]
41
41
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -51,7 +51,7 @@ jobs:
51
51
  issues: write
52
52
  id-token: write
53
53
  actions: read
54
- uses: dceoy/gh-actions-for-devops/.github/workflows/claude-code-bot.yml@main # zizmor: ignore[unpinned-uses]
54
+ uses: dceoy/gha-for-devops/.github/workflows/claude-code-bot.yml@main # zizmor: ignore[unpinned-uses]
55
55
  secrets:
56
56
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env]
57
57
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -13,7 +13,7 @@ jobs:
13
13
  permissions:
14
14
  contents: write
15
15
  id-token: write
16
- uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-release-on-pypi-and-github.yml@main # zizmor: ignore[unpinned-uses]
16
+ uses: dceoy/gha-for-devops/.github/workflows/python-package-release-on-pypi-and-github.yml@main # zizmor: ignore[unpinned-uses]
17
17
  with:
18
18
  package-path: .
19
19
  create-github-release: true
@@ -39,6 +39,6 @@ jobs:
39
39
  name: ${{ needs.build-and-release.outputs.distribution-artifact-name }}
40
40
  path: dist/
41
41
  - name: Publish distribution 📦 to PyPI
42
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
42
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
43
43
  with:
44
44
  verbose: true
mt5api-1.0.3/AGENTS.md ADDED
@@ -0,0 +1,38 @@
1
+ # Repository Guidelines
2
+
3
+ ## Build, Test, and Development Commands
4
+
5
+ - `.agents/skills/local-qa/scripts/qa.sh`: run the repository QA workflow after
6
+ any file update. This wraps formatting, linting, type checks, tests, Markdown
7
+ formatting, and workflow/security checks.
8
+
9
+ The API server runtime requires Windows with MetaTrader 5 installed and logged
10
+ in. Non-Windows systems are suitable for editing docs and most mocked tests.
11
+
12
+ ## Testing Guidelines
13
+
14
+ Parametrize unit tests with `pytest.mark.parametrize` when the same behavior
15
+ should be verified across multiple input and expected-output cases. Prefer
16
+ mocked MT5/pdmt5 interactions unless a test explicitly requires a Windows MT5
17
+ terminal.
18
+
19
+ ## Design Principles
20
+
21
+ Apply KISS, DRY, and YAGNI when changing code. Prefer the simplest direct
22
+ implementation that satisfies the current requirement. Remove duplication when
23
+ it improves clarity or prevents drift, but avoid abstractions that do not serve
24
+ an immediate need. Keep changes small, focused, and easy to review.
25
+
26
+ ## Commit & Pull Request Guidelines
27
+
28
+ Keep commits scoped to one logical change. Before committing or opening a PR,
29
+ run `.agents/skills/local-qa/scripts/qa.sh`. Pull requests should describe the
30
+ behavior change, list verification commands, link related issues when
31
+ applicable, and call out any runtime impact for Windows/MT5 users.
32
+
33
+ ## Security & Configuration Tips
34
+
35
+ Do not commit credentials, terminal account details, or real API keys. Configure
36
+ runtime values through environment variables such as `MT5API_SECRET_KEY`,
37
+ `MT5API_ROUTER_PREFIX`, `MT5API_HOST`, `MT5API_PORT`, and
38
+ `MT5API_LOG_LEVEL`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5api
3
- Version: 1.0.1
3
+ Version: 1.0.3
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>
@@ -15,11 +15,12 @@ Classifier: Operating System :: Microsoft :: Windows
15
15
  Classifier: Programming Language :: Python
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Topic :: Office/Business :: Financial :: Investment
18
- Requires-Python: <3.14,>=3.11
18
+ Requires-Python: <3.15,>=3.11
19
19
  Requires-Dist: fastapi>=0.115.0
20
- Requires-Dist: httpx>=0.27.0
21
- Requires-Dist: pdmt5>=1.1.1
20
+ Requires-Dist: pandas>=3.0.3
21
+ Requires-Dist: pdmt5>=1.2.0
22
22
  Requires-Dist: pyarrow>=18.0.0
23
+ Requires-Dist: pydantic>=2.13.4
23
24
  Requires-Dist: python-multipart>=0.0.31
24
25
  Requires-Dist: uvicorn[standard]>=0.32.0
25
26
  Description-Content-Type: text/markdown
@@ -85,7 +86,6 @@ graph TB
85
86
 
86
87
  ## Requirements
87
88
 
88
- - Python 3.11+
89
89
  - Windows host with MetaTrader 5 terminal installed and logged in
90
90
  - Linux and macOS are not supported for the API server runtime, but they work
91
91
  for HTTP clients
@@ -59,7 +59,6 @@ graph TB
59
59
 
60
60
  ## Requirements
61
61
 
62
- - Python 3.11+
63
62
  - Windows host with MetaTrader 5 terminal installed and logged in
64
63
  - Linux and macOS are not supported for the API server runtime, but they work
65
64
  for HTTP clients
@@ -10,7 +10,6 @@ macOS because the `MetaTrader5` Python package is supported only on Windows.
10
10
 
11
11
  - Windows 10/11
12
12
  - MetaTrader 5 installed and logged in
13
- - Python 3.11+
14
13
  - `mt5api` installed
15
14
  - PowerShell access with permission to install and manage services
16
15
 
@@ -10,7 +10,6 @@ system.
10
10
 
11
11
  ## Runtime Requirements
12
12
 
13
- - Python 3.11+
14
13
  - Windows host with MetaTrader 5 terminal installed and logged in
15
14
  - Linux and macOS are not supported for the API server runtime, but they work
16
15
  for HTTP clients
@@ -26,7 +26,6 @@ system.
26
26
 
27
27
  ## Requirements
28
28
 
29
- - Python 3.11+
30
29
  - Windows OS with MetaTrader 5 terminal installed and logged in
31
30
  - Linux and macOS are not supported for the API server runtime, but they work
32
31
  for HTTP clients
@@ -1,12 +1,14 @@
1
1
  """Application-wide constants."""
2
2
 
3
+ from importlib.metadata import version
4
+
3
5
  API_TITLE = "MT5 REST API"
4
6
  API_DESCRIPTION = (
5
7
  "REST API for MetaTrader 5 data access and non-executing terminal utilities. "
6
8
  "Provides market data, account information, trading history, calculations, "
7
9
  "and safe operational endpoints via HTTP."
8
10
  )
9
- API_VERSION = "1.0.1"
11
+ API_VERSION = version("mt5api")
10
12
  API_DOCS_URL = "/docs"
11
13
  API_REDOC_URL = "/redoc"
12
14
  API_OPENAPI_URL = "/openapi.json"
@@ -81,17 +81,13 @@ def _strip_auth_from_openapi(openapi_schema: dict[str, Any]) -> None:
81
81
  """Remove API key requirements from OpenAPI when auth is disabled."""
82
82
  openapi_schema.pop("security", None)
83
83
 
84
- components = openapi_schema.get("components")
85
- if isinstance(components, dict):
86
- security_schemes = components.get("securitySchemes")
87
- if isinstance(security_schemes, dict):
88
- security_schemes.pop(API_KEY_SECURITY_SCHEME_NAME, None)
89
- if not security_schemes:
90
- components.pop("securitySchemes", None)
84
+ components = openapi_schema.get("components", {})
85
+ security_schemes = components.get("securitySchemes", {})
86
+ security_schemes.pop(API_KEY_SECURITY_SCHEME_NAME, None)
87
+ if not security_schemes:
88
+ components.pop("securitySchemes", None)
91
89
 
92
90
  for methods in openapi_schema.get("paths", {}).values():
93
- if not isinstance(methods, dict):
94
- continue
95
91
  for operation in methods.values():
96
92
  if isinstance(operation, dict):
97
93
  operation.pop("security", None)
@@ -100,16 +96,12 @@ def _strip_auth_from_openapi(openapi_schema: dict[str, Any]) -> None:
100
96
  def _patch_validation_error_responses(openapi_schema: dict[str, Any]) -> None:
101
97
  """Advertise RFC 7807 Problem Details for request validation failures."""
102
98
  components = openapi_schema.setdefault("components", {})
103
- if not isinstance(components, dict):
104
- return
105
-
106
99
  schemas = components.setdefault("schemas", {})
107
- if isinstance(schemas, dict):
108
- schemas["ErrorResponse"] = ErrorResponse.model_json_schema(
109
- ref_template="#/components/schemas/{model}",
110
- )
111
- schemas.pop("HTTPValidationError", None)
112
- schemas.pop("ValidationError", None)
100
+ schemas["ErrorResponse"] = ErrorResponse.model_json_schema(
101
+ ref_template="#/components/schemas/{model}",
102
+ )
103
+ schemas.pop("HTTPValidationError", None)
104
+ schemas.pop("ValidationError", None)
113
105
 
114
106
  error_response = {
115
107
  "description": "Validation Error",
@@ -121,8 +113,6 @@ def _patch_validation_error_responses(openapi_schema: dict[str, Any]) -> None:
121
113
  }
122
114
 
123
115
  for methods in openapi_schema.get("paths", {}).values():
124
- if not isinstance(methods, dict):
125
- continue
126
116
  for operation in methods.values():
127
117
  if isinstance(operation, dict) and "422" in operation.get("responses", {}):
128
118
  operation["responses"]["422"] = error_response
@@ -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,20 +1,21 @@
1
1
  [project]
2
2
  name = "mt5api"
3
- version = "1.0.1"
3
+ version = "1.0.3"
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"}]
7
7
  license = "MIT"
8
8
  license-files = ["LICENSE"]
9
9
  readme = "README.md"
10
- requires-python = ">= 3.11, < 3.14"
10
+ requires-python = ">= 3.11, < 3.15"
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
+ "pandas >= 3.0.3",
16
+ "pydantic >= 2.13.4",
15
17
  "pyarrow >= 18.0.0",
16
18
  "python-multipart >= 0.0.31",
17
- "httpx >= 0.27.0",
18
19
  ]
19
20
  classifiers = [
20
21
  "Development Status :: 4 - Beta",
@@ -32,9 +33,12 @@ Repository = "https://github.com/dceoy/mt5api.git"
32
33
 
33
34
  [tool.uv]
34
35
  required-environments = ["platform_system == 'Windows'"]
36
+ exclude-newer = "1 week"
37
+ exclude-newer-package = { pdmt5 = false }
35
38
 
36
39
  [dependency-groups]
37
40
  dev = [
41
+ "httpx >= 0.27.0",
38
42
  "ruff >= 0.11.0",
39
43
  "pyright >= 1.1.407",
40
44
  "pytest >= 8.0.0",
@@ -50,10 +54,6 @@ dev = [
50
54
  "mkdocstrings[python] >= 0.29.1",
51
55
  ]
52
56
 
53
- [tool.uv.build-backend]
54
- source-include = ["mt5api/**", "LICENSE"]
55
- source-exclude = ["tests/**"]
56
-
57
57
  [tool.hatch.build.targets.wheel]
58
58
  packages = ["mt5api"]
59
59
 
@@ -41,16 +41,6 @@ def test_version_endpoint_rejects_invalid_api_key(client: TestClient) -> None:
41
41
  assert "Invalid API key" in data["detail"]
42
42
 
43
43
 
44
- def test_version_endpoint_accepts_valid_api_key(
45
- client: TestClient,
46
- api_headers: dict[str, str],
47
- ) -> None:
48
- """Test version endpoint accepts valid API key."""
49
- response = client.get("/version", headers=api_headers)
50
-
51
- assert response.status_code == 200
52
-
53
-
54
44
  def test_symbols_endpoint_requires_authentication(
55
45
  client: TestClient,
56
46
  mock_mt5_client: Mock,
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from importlib.metadata import version
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  import pytest
@@ -14,6 +15,11 @@ if TYPE_CHECKING:
14
15
  from fastapi.testclient import TestClient
15
16
 
16
17
 
18
+ def test_api_version_uses_package_metadata() -> None:
19
+ """Test API version has one authoritative package-metadata source."""
20
+ assert version("mt5api") == API_VERSION
21
+
22
+
17
23
  def test_health_endpoint_returns_healthy_status(client: TestClient) -> None:
18
24
  """Test health endpoint returns healthy status when MT5 is connected."""
19
25
  response = client.get("/health")
@@ -27,14 +33,6 @@ def test_health_endpoint_returns_healthy_status(client: TestClient) -> None:
27
33
  assert data["api_version"] == API_VERSION
28
34
 
29
35
 
30
- def test_health_endpoint_no_authentication_required(client: TestClient) -> None:
31
- """Test health endpoint works without authentication."""
32
- # No X-API-Key header
33
- response = client.get("/health")
34
-
35
- assert response.status_code == 200
36
-
37
-
38
36
  def test_version_endpoint_returns_mt5_version(
39
37
  client: TestClient,
40
38
  api_headers: dict[str, str],
@@ -14,42 +14,6 @@ if TYPE_CHECKING:
14
14
  from pytest_mock import MockerFixture
15
15
 
16
16
 
17
- def test_strip_auth_from_openapi_handles_non_dict_components() -> None:
18
- """OpenAPI auth stripping should tolerate missing component mappings."""
19
- from mt5api import main # noqa: PLC0415
20
-
21
- openapi_schema: dict[str, Any] = {
22
- "security": [{API_KEY_SECURITY_SCHEME_NAME: []}],
23
- "components": None,
24
- "paths": {
25
- "/bad": [],
26
- "/version": {
27
- "summary": "Version endpoint",
28
- "get": {"security": [{API_KEY_SECURITY_SCHEME_NAME: []}]},
29
- },
30
- },
31
- }
32
-
33
- main._strip_auth_from_openapi(openapi_schema) # pyright: ignore[reportPrivateUsage]
34
-
35
- assert "security" not in openapi_schema
36
- assert "security" not in openapi_schema["paths"]["/version"]["get"]
37
-
38
-
39
- def test_strip_auth_from_openapi_handles_non_dict_security_schemes() -> None:
40
- """OpenAPI auth stripping should tolerate non-dict security schemes."""
41
- from mt5api import main # noqa: PLC0415
42
-
43
- openapi_schema: dict[str, Any] = {
44
- "components": {"securitySchemes": []},
45
- "paths": {},
46
- }
47
-
48
- main._strip_auth_from_openapi(openapi_schema) # pyright: ignore[reportPrivateUsage]
49
-
50
- assert openapi_schema["components"]["securitySchemes"] == []
51
-
52
-
53
17
  def test_strip_auth_from_openapi_preserves_other_security_schemes() -> None:
54
18
  """OpenAPI auth stripping should keep unrelated security schemes."""
55
19
  from mt5api import main # noqa: PLC0415
@@ -58,12 +22,21 @@ def test_strip_auth_from_openapi_preserves_other_security_schemes() -> None:
58
22
  "components": {
59
23
  "securitySchemes": {API_KEY_SECURITY_SCHEME_NAME: {}, "Other": {}}
60
24
  },
61
- "paths": {},
25
+ "paths": {
26
+ "/rates/from": {
27
+ "parameters": [{"name": "symbol", "in": "query"}],
28
+ "get": {"security": [{API_KEY_SECURITY_SCHEME_NAME: []}]},
29
+ },
30
+ },
62
31
  }
63
32
 
64
33
  main._strip_auth_from_openapi(openapi_schema) # pyright: ignore[reportPrivateUsage]
65
34
 
66
35
  assert openapi_schema["components"]["securitySchemes"] == {"Other": {}}
36
+ assert "security" not in openapi_schema["paths"]["/rates/from"]["get"]
37
+ assert openapi_schema["paths"]["/rates/from"]["parameters"] == [
38
+ {"name": "symbol", "in": "query"}
39
+ ]
67
40
 
68
41
 
69
42
  def test_patch_validation_error_responses_updates_422_schema() -> None:
@@ -78,8 +51,8 @@ def test_patch_validation_error_responses_updates_422_schema() -> None:
78
51
  },
79
52
  },
80
53
  "paths": {
81
- "/bad": [],
82
54
  "/rates/from": {
55
+ "parameters": [{"name": "symbol", "in": "query"}],
83
56
  "get": {
84
57
  "responses": {
85
58
  "422": {
@@ -106,58 +79,9 @@ def test_patch_validation_error_responses_updates_422_schema() -> None:
106
79
  assert "ErrorResponse" in schemas
107
80
  assert "HTTPValidationError" not in schemas
108
81
  assert "ValidationError" not in schemas
109
- assert openapi_schema["paths"]["/rates/from"]["get"]["responses"]["422"] == {
110
- "description": "Validation Error",
111
- "content": {
112
- "application/json": {
113
- "schema": {"$ref": "#/components/schemas/ErrorResponse"},
114
- },
115
- },
116
- }
117
-
118
-
119
- def test_patch_validation_error_responses_handles_non_dict_components() -> None:
120
- """OpenAPI validation patching should tolerate missing component mappings."""
121
- from mt5api import main # noqa: PLC0415
122
-
123
- openapi_schema: dict[str, Any] = {"components": None, "paths": {}}
124
-
125
- main._patch_validation_error_responses(openapi_schema) # pyright: ignore[reportPrivateUsage]
126
-
127
- assert openapi_schema["components"] is None
128
-
129
-
130
- def test_patch_validation_error_responses_handles_non_dict_schemas() -> None:
131
- """OpenAPI validation patching should still update 422 responses."""
132
- from mt5api import main # noqa: PLC0415
133
-
134
- openapi_schema: dict[str, Any] = {
135
- "components": {"schemas": []},
136
- "paths": {
137
- "/rates/from": {
138
- "get": {
139
- "responses": {
140
- "422": {
141
- "description": "Validation Error",
142
- "content": {
143
- "application/json": {
144
- "schema": {
145
- "$ref": (
146
- "#/components/schemas/HTTPValidationError"
147
- ),
148
- },
149
- },
150
- },
151
- },
152
- },
153
- },
154
- },
155
- },
156
- }
157
-
158
- main._patch_validation_error_responses(openapi_schema) # pyright: ignore[reportPrivateUsage]
159
-
160
- assert openapi_schema["components"]["schemas"] == []
82
+ assert openapi_schema["paths"]["/rates/from"]["parameters"] == [
83
+ {"name": "symbol", "in": "query"}
84
+ ]
161
85
  assert openapi_schema["paths"]["/rates/from"]["get"]["responses"]["422"] == {
162
86
  "description": "Validation Error",
163
87
  "content": {