pdmt5 0.1.7__tar.gz → 0.1.9__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.
- pdmt5-0.1.9/.github/workflows/claude.yml +59 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/CLAUDE.md +2 -2
- {pdmt5-0.1.7 → pdmt5-0.1.9}/PKG-INFO +6 -6
- {pdmt5-0.1.7 → pdmt5-0.1.9}/README.md +5 -5
- {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/trading.py +73 -12
- {pdmt5-0.1.7 → pdmt5-0.1.9}/pyproject.toml +7 -5
- {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_trading.py +153 -9
- {pdmt5-0.1.7 → pdmt5-0.1.9}/uv.lock +1 -1
- {pdmt5-0.1.7 → pdmt5-0.1.9}/.claude/settings.json +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/FUNDING.yml +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/copilot-instructions.md +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/dependabot.yml +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/workflows/ci.yml +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/.gitignore +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/LICENSE +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/dataframe.md +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/index.md +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/mt5.md +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/trading.md +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/utils.md +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/index.md +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/mkdocs.yml +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/__init__.py +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/dataframe.py +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/mt5.py +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/utils.py +0 -0
- {pdmt5-0.1.7 → pdmt5-0.1.9}/renovate.json +0 -0
- {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/__init__.py +0 -0
- {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_dataframe.py +0 -0
- {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_init.py +0 -0
- {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_mt5.py +0 -0
- {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_utils.py +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Claude Code
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types:
|
|
6
|
+
- created
|
|
7
|
+
pull_request_review_comment:
|
|
8
|
+
types:
|
|
9
|
+
- created
|
|
10
|
+
issues:
|
|
11
|
+
types:
|
|
12
|
+
- opened
|
|
13
|
+
- assigned
|
|
14
|
+
pull_request_review:
|
|
15
|
+
types:
|
|
16
|
+
- submitted
|
|
17
|
+
jobs:
|
|
18
|
+
claude:
|
|
19
|
+
if: >
|
|
20
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude'))
|
|
21
|
+
|| (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))
|
|
22
|
+
|| (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude'))
|
|
23
|
+
|| (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
pull-requests: read
|
|
27
|
+
issues: read
|
|
28
|
+
id-token: write
|
|
29
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
30
|
+
uses: dceoy/gh-actions-for-devops/.github/workflows/claude-code-action.yml@main
|
|
31
|
+
with:
|
|
32
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
|
33
|
+
additional-permissions: |
|
|
34
|
+
actions: read
|
|
35
|
+
|
|
36
|
+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
37
|
+
# model: "claude-opus-4-20250514"
|
|
38
|
+
|
|
39
|
+
# Optional: Customize the trigger phrase (default: @claude)
|
|
40
|
+
# trigger-phrase: "/claude"
|
|
41
|
+
|
|
42
|
+
# Optional: Trigger when specific user is assigned to an issue
|
|
43
|
+
# assignee-trigger: "claude-bot"
|
|
44
|
+
|
|
45
|
+
# Optional: Allow Claude to run specific commands
|
|
46
|
+
# allowed-tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
|
|
47
|
+
|
|
48
|
+
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
|
49
|
+
# custom-instructions: |
|
|
50
|
+
# Follow our coding standards
|
|
51
|
+
# Ensure all new code has tests
|
|
52
|
+
# Use TypeScript for new files
|
|
53
|
+
|
|
54
|
+
# Optional: Custom environment variables for Claude
|
|
55
|
+
# claude-env: |
|
|
56
|
+
# NODE_ENV: test
|
|
57
|
+
secrets:
|
|
58
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
59
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -26,7 +26,7 @@ uv run pyright .
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
# Run unit tests with pytest
|
|
29
|
-
uv run pytest
|
|
29
|
+
uv run pytest tests/ -v
|
|
30
30
|
|
|
31
31
|
# Type checking with pyright
|
|
32
32
|
uv run pyright .
|
|
@@ -68,7 +68,7 @@ uv run mkdocs gh-deploy
|
|
|
68
68
|
- `dataframe.py`: MT5 data client with pandas DataFrame conversion (`Mt5Config`, `Mt5DataClient`)
|
|
69
69
|
- `trading.py`: Trading operations client (`Mt5TradingClient`, `Mt5TradingError`)
|
|
70
70
|
- `utils.py`: Utility decorators and functions for time conversion and DataFrame indexing
|
|
71
|
-
- `
|
|
71
|
+
- `tests/`: Comprehensive test suite (pytest-based)
|
|
72
72
|
- `test_init.py`, `test_mt5.py`, `test_dataframe.py`, `test_trading.py`, `test_utils.py`
|
|
73
73
|
- `docs/`: MkDocs documentation with API reference
|
|
74
74
|
- `docs/api/`: Auto-generated API documentation for all modules
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdmt5
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Pandas-based data handler for MetaTrader 5
|
|
5
5
|
Project-URL: Repository, https://github.com/dceoy/pdmt5.git
|
|
6
6
|
Author-email: dceoy <dceoy@users.noreply.github.com>
|
|
@@ -53,14 +53,14 @@ Pandas-based data handler for MetaTrader 5
|
|
|
53
53
|
|
|
54
54
|
## Installation
|
|
55
55
|
|
|
56
|
-
###
|
|
56
|
+
### Using pip
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
60
|
-
pip install -U
|
|
59
|
+
pip install -U pdmt5
|
|
60
|
+
pip install -U MetaTrader5
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
### Using uv
|
|
63
|
+
### Using uv
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
git clone https://github.com/dceoy/pdmt5.git
|
|
@@ -392,7 +392,7 @@ cd pdmt5
|
|
|
392
392
|
uv sync
|
|
393
393
|
|
|
394
394
|
# Run tests
|
|
395
|
-
uv run pytest
|
|
395
|
+
uv run pytest tests/ -v
|
|
396
396
|
|
|
397
397
|
# Run type checking
|
|
398
398
|
uv run pyright .
|
|
@@ -30,14 +30,14 @@ Pandas-based data handler for MetaTrader 5
|
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
33
|
-
###
|
|
33
|
+
### Using pip
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
|
|
37
|
-
pip install -U
|
|
36
|
+
pip install -U pdmt5
|
|
37
|
+
pip install -U MetaTrader5
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
### Using uv
|
|
40
|
+
### Using uv
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
git clone https://github.com/dceoy/pdmt5.git
|
|
@@ -369,7 +369,7 @@ cd pdmt5
|
|
|
369
369
|
uv sync
|
|
370
370
|
|
|
371
371
|
# Run tests
|
|
372
|
-
uv run pytest
|
|
372
|
+
uv run pytest tests/ -v
|
|
373
373
|
|
|
374
374
|
# Run type checking
|
|
375
375
|
uv run pyright .
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from datetime import timedelta
|
|
6
|
+
from functools import cached_property
|
|
6
7
|
from math import floor
|
|
7
8
|
from typing import TYPE_CHECKING, Any, Literal
|
|
8
9
|
|
|
@@ -28,6 +29,67 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
28
29
|
|
|
29
30
|
model_config = ConfigDict(frozen=True)
|
|
30
31
|
|
|
32
|
+
@cached_property
|
|
33
|
+
def mt5_successful_trade_retcodes(self) -> set[int]:
|
|
34
|
+
"""Set of successful trade return codes.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Set of successful trade return codes.
|
|
38
|
+
"""
|
|
39
|
+
return {
|
|
40
|
+
self.mt5.TRADE_RETCODE_PLACED, # 10008
|
|
41
|
+
self.mt5.TRADE_RETCODE_DONE, # 10009
|
|
42
|
+
self.mt5.TRADE_RETCODE_DONE_PARTIAL, # 10010
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@cached_property
|
|
46
|
+
def mt5_failed_trade_retcodes(self) -> set[int]:
|
|
47
|
+
"""Set of failed trade return codes.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Set of failed trade return codes.
|
|
51
|
+
"""
|
|
52
|
+
return {
|
|
53
|
+
self.mt5.TRADE_RETCODE_REQUOTE, # 10004
|
|
54
|
+
self.mt5.TRADE_RETCODE_REJECT, # 10006
|
|
55
|
+
self.mt5.TRADE_RETCODE_CANCEL, # 10007
|
|
56
|
+
self.mt5.TRADE_RETCODE_ERROR, # 10011
|
|
57
|
+
self.mt5.TRADE_RETCODE_TIMEOUT, # 10012
|
|
58
|
+
self.mt5.TRADE_RETCODE_INVALID, # 10013
|
|
59
|
+
self.mt5.TRADE_RETCODE_INVALID_VOLUME, # 10014
|
|
60
|
+
self.mt5.TRADE_RETCODE_INVALID_PRICE, # 10015
|
|
61
|
+
self.mt5.TRADE_RETCODE_INVALID_STOPS, # 10016
|
|
62
|
+
self.mt5.TRADE_RETCODE_TRADE_DISABLED, # 10017
|
|
63
|
+
self.mt5.TRADE_RETCODE_MARKET_CLOSED, # 10018
|
|
64
|
+
self.mt5.TRADE_RETCODE_NO_MONEY, # 10019
|
|
65
|
+
self.mt5.TRADE_RETCODE_PRICE_CHANGED, # 10020
|
|
66
|
+
self.mt5.TRADE_RETCODE_PRICE_OFF, # 10021
|
|
67
|
+
self.mt5.TRADE_RETCODE_INVALID_EXPIRATION, # 10022
|
|
68
|
+
self.mt5.TRADE_RETCODE_ORDER_CHANGED, # 10023
|
|
69
|
+
self.mt5.TRADE_RETCODE_TOO_MANY_REQUESTS, # 10024
|
|
70
|
+
self.mt5.TRADE_RETCODE_NO_CHANGES, # 10025
|
|
71
|
+
self.mt5.TRADE_RETCODE_SERVER_DISABLES_AT, # 10026
|
|
72
|
+
self.mt5.TRADE_RETCODE_CLIENT_DISABLES_AT, # 10027
|
|
73
|
+
self.mt5.TRADE_RETCODE_LOCKED, # 10028
|
|
74
|
+
self.mt5.TRADE_RETCODE_FROZEN, # 10029
|
|
75
|
+
self.mt5.TRADE_RETCODE_INVALID_FILL, # 10030
|
|
76
|
+
self.mt5.TRADE_RETCODE_CONNECTION, # 10031
|
|
77
|
+
self.mt5.TRADE_RETCODE_ONLY_REAL, # 10032
|
|
78
|
+
self.mt5.TRADE_RETCODE_LIMIT_ORDERS, # 10033
|
|
79
|
+
self.mt5.TRADE_RETCODE_LIMIT_VOLUME, # 10034
|
|
80
|
+
self.mt5.TRADE_RETCODE_INVALID_ORDER, # 10035
|
|
81
|
+
self.mt5.TRADE_RETCODE_POSITION_CLOSED, # 10036
|
|
82
|
+
self.mt5.TRADE_RETCODE_INVALID_CLOSE_VOLUME, # 10038
|
|
83
|
+
self.mt5.TRADE_RETCODE_CLOSE_ORDER_EXIST, # 10039
|
|
84
|
+
self.mt5.TRADE_RETCODE_LIMIT_POSITIONS, # 10040
|
|
85
|
+
self.mt5.TRADE_RETCODE_REJECT_CANCEL, # 10041
|
|
86
|
+
self.mt5.TRADE_RETCODE_LONG_ONLY, # 10042
|
|
87
|
+
self.mt5.TRADE_RETCODE_SHORT_ONLY, # 10043
|
|
88
|
+
self.mt5.TRADE_RETCODE_CLOSE_ONLY, # 10044
|
|
89
|
+
self.mt5.TRADE_RETCODE_FIFO_CLOSE, # 10045
|
|
90
|
+
self.mt5.TRADE_RETCODE_HEDGE_PROHIBITED, # 10046
|
|
91
|
+
}
|
|
92
|
+
|
|
31
93
|
def close_open_positions(
|
|
32
94
|
self,
|
|
33
95
|
symbols: str | list[str] | tuple[str, ...] | None = None,
|
|
@@ -116,12 +178,14 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
116
178
|
def _send_or_check_order(
|
|
117
179
|
self,
|
|
118
180
|
request: dict[str, Any],
|
|
181
|
+
raise_on_error: bool = False,
|
|
119
182
|
dry_run: bool = False,
|
|
120
183
|
) -> dict[str, Any]:
|
|
121
184
|
"""Send or check an order request.
|
|
122
185
|
|
|
123
186
|
Args:
|
|
124
187
|
request: Order request dictionary.
|
|
188
|
+
raise_on_error: If True, raise an error on operation failure.
|
|
125
189
|
dry_run: If True, only check the order without sending it.
|
|
126
190
|
|
|
127
191
|
Returns:
|
|
@@ -138,24 +202,21 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
138
202
|
response = self.order_send_as_dict(request=request)
|
|
139
203
|
order_func = "order_send"
|
|
140
204
|
retcode = response.get("retcode")
|
|
141
|
-
if (
|
|
142
|
-
dry_run and retcode
|
|
205
|
+
if (dry_run and retcode == 0) or (
|
|
206
|
+
not dry_run and retcode in self.mt5_successful_trade_retcodes
|
|
143
207
|
):
|
|
144
208
|
self.logger.info("response: %s", response)
|
|
145
209
|
return response
|
|
146
|
-
elif
|
|
147
|
-
self.mt5.TRADE_RETCODE_TRADE_DISABLED,
|
|
148
|
-
self.mt5.TRADE_RETCODE_MARKET_CLOSED,
|
|
149
|
-
}:
|
|
150
|
-
self.logger.info("response: %s", response)
|
|
151
|
-
comment = response.get("comment", "Unknown error")
|
|
152
|
-
self.logger.warning("%s() failed and skipped. <= `%s`", order_func, comment)
|
|
153
|
-
return response
|
|
154
|
-
else:
|
|
210
|
+
elif raise_on_error:
|
|
155
211
|
self.logger.error("response: %s", response)
|
|
156
|
-
comment = response.get("comment"
|
|
212
|
+
comment = response.get("comment")
|
|
157
213
|
error_message = f"{order_func}() failed and aborted. <= `{comment}`"
|
|
158
214
|
raise Mt5TradingError(error_message)
|
|
215
|
+
else:
|
|
216
|
+
self.logger.warning("response: %s", response)
|
|
217
|
+
comment = response.get("comment")
|
|
218
|
+
self.logger.warning("%s() failed and skipped. <= `%s`", order_func, comment)
|
|
219
|
+
return response
|
|
159
220
|
|
|
160
221
|
def place_market_order(
|
|
161
222
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pdmt5"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.9"
|
|
4
4
|
description = "Pandas-based data handler for MetaTrader 5"
|
|
5
5
|
authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
|
6
6
|
maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
|
@@ -46,7 +46,7 @@ required-environments = ["platform_system == 'Windows'"]
|
|
|
46
46
|
|
|
47
47
|
[tool.uv.build-backend]
|
|
48
48
|
source-include = ["pdmt5/**", "LICENSE"]
|
|
49
|
-
source-exclude = ["
|
|
49
|
+
source-exclude = ["tests/**"]
|
|
50
50
|
|
|
51
51
|
[tool.ruff]
|
|
52
52
|
line-length = 88
|
|
@@ -126,7 +126,7 @@ ignore = [
|
|
|
126
126
|
]
|
|
127
127
|
|
|
128
128
|
[tool.ruff.lint.per-file-ignores]
|
|
129
|
-
"
|
|
129
|
+
"tests/**/*.py" = [
|
|
130
130
|
"DOC201", # Missing return documentation
|
|
131
131
|
"DOC501", # Raised exception missing from docstring
|
|
132
132
|
"PLC2701", # Private name import
|
|
@@ -147,6 +147,8 @@ max-public-methods = 40
|
|
|
147
147
|
|
|
148
148
|
[tool.pyright]
|
|
149
149
|
exclude = ["build", ".venv"]
|
|
150
|
+
venvPath = "."
|
|
151
|
+
venv = ".venv"
|
|
150
152
|
typeCheckingMode = "strict"
|
|
151
153
|
reportUnknownArgumentType = "none"
|
|
152
154
|
reportUnknownMemberType = "none"
|
|
@@ -162,7 +164,7 @@ addopts = [
|
|
|
162
164
|
"--capture=no",
|
|
163
165
|
]
|
|
164
166
|
pythonpaths = ["."]
|
|
165
|
-
testpaths = ["
|
|
167
|
+
testpaths = ["tests"]
|
|
166
168
|
python_files = ["test_*.py", "*_test.py"]
|
|
167
169
|
python_classes = ["Test*"]
|
|
168
170
|
python_functions = ["test_*"]
|
|
@@ -172,7 +174,7 @@ minversion = "6.0"
|
|
|
172
174
|
source = ["pdmt5"]
|
|
173
175
|
omit = [
|
|
174
176
|
"**/__init__.py",
|
|
175
|
-
"
|
|
177
|
+
"tests/**",
|
|
176
178
|
]
|
|
177
179
|
|
|
178
180
|
[tool.coverage.report]
|
|
@@ -20,7 +20,7 @@ Mt5TradingClient.model_rebuild()
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@pytest.fixture(autouse=True)
|
|
23
|
-
def mock_mt5_import(
|
|
23
|
+
def mock_mt5_import( # noqa: PLR0915
|
|
24
24
|
request: pytest.FixtureRequest,
|
|
25
25
|
mocker: MockerFixture,
|
|
26
26
|
) -> Generator[ModuleType | None, None, None]:
|
|
@@ -69,9 +69,50 @@ def mock_mt5_import(
|
|
|
69
69
|
mock_mt5.ORDER_FILLING_FOK = 2
|
|
70
70
|
mock_mt5.ORDER_FILLING_RETURN = 3
|
|
71
71
|
mock_mt5.ORDER_TIME_GTC = 0
|
|
72
|
+
|
|
73
|
+
# Trade return codes
|
|
74
|
+
mock_mt5.TRADE_RETCODE_REQUOTE = 10004
|
|
75
|
+
mock_mt5.TRADE_RETCODE_REJECT = 10006
|
|
76
|
+
mock_mt5.TRADE_RETCODE_CANCEL = 10007
|
|
77
|
+
mock_mt5.TRADE_RETCODE_PLACED = 10008
|
|
72
78
|
mock_mt5.TRADE_RETCODE_DONE = 10009
|
|
79
|
+
mock_mt5.TRADE_RETCODE_DONE_PARTIAL = 10010
|
|
80
|
+
mock_mt5.TRADE_RETCODE_ERROR = 10011
|
|
81
|
+
mock_mt5.TRADE_RETCODE_TIMEOUT = 10012
|
|
82
|
+
mock_mt5.TRADE_RETCODE_INVALID = 10013
|
|
83
|
+
mock_mt5.TRADE_RETCODE_INVALID_VOLUME = 10014
|
|
84
|
+
mock_mt5.TRADE_RETCODE_INVALID_PRICE = 10015
|
|
85
|
+
mock_mt5.TRADE_RETCODE_INVALID_STOPS = 10016
|
|
73
86
|
mock_mt5.TRADE_RETCODE_TRADE_DISABLED = 10017
|
|
74
87
|
mock_mt5.TRADE_RETCODE_MARKET_CLOSED = 10018
|
|
88
|
+
mock_mt5.TRADE_RETCODE_NO_MONEY = 10019
|
|
89
|
+
mock_mt5.TRADE_RETCODE_PRICE_CHANGED = 10020
|
|
90
|
+
mock_mt5.TRADE_RETCODE_PRICE_OFF = 10021
|
|
91
|
+
mock_mt5.TRADE_RETCODE_INVALID_EXPIRATION = 10022
|
|
92
|
+
mock_mt5.TRADE_RETCODE_ORDER_CHANGED = 10023
|
|
93
|
+
mock_mt5.TRADE_RETCODE_TOO_MANY_REQUESTS = 10024
|
|
94
|
+
mock_mt5.TRADE_RETCODE_NO_CHANGES = 10025
|
|
95
|
+
mock_mt5.TRADE_RETCODE_SERVER_DISABLES_AT = 10026
|
|
96
|
+
mock_mt5.TRADE_RETCODE_CLIENT_DISABLES_AT = 10027
|
|
97
|
+
mock_mt5.TRADE_RETCODE_LOCKED = 10028
|
|
98
|
+
mock_mt5.TRADE_RETCODE_FROZEN = 10029
|
|
99
|
+
mock_mt5.TRADE_RETCODE_INVALID_FILL = 10030
|
|
100
|
+
mock_mt5.TRADE_RETCODE_CONNECTION = 10031
|
|
101
|
+
mock_mt5.TRADE_RETCODE_ONLY_REAL = 10032
|
|
102
|
+
mock_mt5.TRADE_RETCODE_LIMIT_ORDERS = 10033
|
|
103
|
+
mock_mt5.TRADE_RETCODE_LIMIT_VOLUME = 10034
|
|
104
|
+
mock_mt5.TRADE_RETCODE_INVALID_ORDER = 10035
|
|
105
|
+
mock_mt5.TRADE_RETCODE_POSITION_CLOSED = 10036
|
|
106
|
+
mock_mt5.TRADE_RETCODE_INVALID_CLOSE_VOLUME = 10038
|
|
107
|
+
mock_mt5.TRADE_RETCODE_CLOSE_ORDER_EXIST = 10039
|
|
108
|
+
mock_mt5.TRADE_RETCODE_LIMIT_POSITIONS = 10040
|
|
109
|
+
mock_mt5.TRADE_RETCODE_REJECT_CANCEL = 10041
|
|
110
|
+
mock_mt5.TRADE_RETCODE_LONG_ONLY = 10042
|
|
111
|
+
mock_mt5.TRADE_RETCODE_SHORT_ONLY = 10043
|
|
112
|
+
mock_mt5.TRADE_RETCODE_CLOSE_ONLY = 10044
|
|
113
|
+
mock_mt5.TRADE_RETCODE_FIFO_CLOSE = 10045
|
|
114
|
+
mock_mt5.TRADE_RETCODE_HEDGE_PROHIBITED = 10046
|
|
115
|
+
|
|
75
116
|
mock_mt5.RES_S_OK = 1
|
|
76
117
|
mock_mt5.DEAL_TYPE_BUY = 0
|
|
77
118
|
mock_mt5.DEAL_TYPE_SELL = 1
|
|
@@ -568,6 +609,33 @@ class TestMt5TradingClient:
|
|
|
568
609
|
|
|
569
610
|
assert result["retcode"] == 10018
|
|
570
611
|
|
|
612
|
+
def test_send_or_check_order_no_changes(
|
|
613
|
+
self,
|
|
614
|
+
mock_mt5_import: ModuleType,
|
|
615
|
+
) -> None:
|
|
616
|
+
"""Test _send_or_check_order with no changes return code."""
|
|
617
|
+
client = Mt5TradingClient(mt5=mock_mt5_import)
|
|
618
|
+
mock_mt5_import.initialize.return_value = True
|
|
619
|
+
client.initialize()
|
|
620
|
+
|
|
621
|
+
request = {
|
|
622
|
+
"action": 1,
|
|
623
|
+
"symbol": "EURUSD",
|
|
624
|
+
"volume": 0.1,
|
|
625
|
+
"type": 1,
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
# Mock no changes response
|
|
629
|
+
mock_mt5_import.order_send.return_value.retcode = 10025
|
|
630
|
+
mock_mt5_import.order_send.return_value._asdict.return_value = {
|
|
631
|
+
"retcode": 10025,
|
|
632
|
+
"comment": "No changes",
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
result = client._send_or_check_order(request)
|
|
636
|
+
|
|
637
|
+
assert result["retcode"] == 10025
|
|
638
|
+
|
|
571
639
|
def test_send_or_check_order_failure(
|
|
572
640
|
self,
|
|
573
641
|
mock_mt5_import: ModuleType,
|
|
@@ -584,15 +652,15 @@ class TestMt5TradingClient:
|
|
|
584
652
|
"type": 1,
|
|
585
653
|
}
|
|
586
654
|
|
|
587
|
-
# Mock failure response
|
|
588
|
-
mock_mt5_import.order_send.return_value.retcode =
|
|
655
|
+
# Mock failure response with error retcode
|
|
656
|
+
mock_mt5_import.order_send.return_value.retcode = 10006
|
|
589
657
|
mock_mt5_import.order_send.return_value._asdict.return_value = {
|
|
590
|
-
"retcode":
|
|
658
|
+
"retcode": 10006,
|
|
591
659
|
"comment": "Invalid request",
|
|
592
660
|
}
|
|
593
661
|
|
|
594
662
|
with pytest.raises(Mt5TradingError, match=r"order_send\(\) failed and aborted"):
|
|
595
|
-
client._send_or_check_order(request)
|
|
663
|
+
client._send_or_check_order(request, raise_on_error=True)
|
|
596
664
|
|
|
597
665
|
def test_send_or_check_order_dry_run_failure(
|
|
598
666
|
self,
|
|
@@ -610,17 +678,17 @@ class TestMt5TradingClient:
|
|
|
610
678
|
"type": 1,
|
|
611
679
|
}
|
|
612
680
|
|
|
613
|
-
# Mock failure response
|
|
614
|
-
mock_mt5_import.order_check.return_value.retcode =
|
|
681
|
+
# Mock failure response with non-zero retcode for dry run
|
|
682
|
+
mock_mt5_import.order_check.return_value.retcode = 10013
|
|
615
683
|
mock_mt5_import.order_check.return_value._asdict.return_value = {
|
|
616
|
-
"retcode":
|
|
684
|
+
"retcode": 10013,
|
|
617
685
|
"comment": "Invalid request",
|
|
618
686
|
}
|
|
619
687
|
|
|
620
688
|
with pytest.raises(
|
|
621
689
|
Mt5TradingError, match=r"order_check\(\) failed and aborted"
|
|
622
690
|
):
|
|
623
|
-
client._send_or_check_order(request, dry_run=True)
|
|
691
|
+
client._send_or_check_order(request, raise_on_error=True, dry_run=True)
|
|
624
692
|
|
|
625
693
|
def test_send_or_check_order_dry_run_override(
|
|
626
694
|
self,
|
|
@@ -1868,3 +1936,79 @@ class TestMt5TradingClient:
|
|
|
1868
1936
|
assert isinstance(result, list)
|
|
1869
1937
|
assert len(result) == 2
|
|
1870
1938
|
assert all(r["retcode"] == 10009 for r in result)
|
|
1939
|
+
|
|
1940
|
+
def test_mt5_successful_trade_retcodes_property(
|
|
1941
|
+
self, mock_mt5_import: ModuleType
|
|
1942
|
+
) -> None:
|
|
1943
|
+
"""Test mt5_successful_trade_retcodes property returns correct set of codes."""
|
|
1944
|
+
client = Mt5TradingClient(mt5=mock_mt5_import)
|
|
1945
|
+
|
|
1946
|
+
# Get the property value
|
|
1947
|
+
retcodes = client.mt5_successful_trade_retcodes
|
|
1948
|
+
|
|
1949
|
+
# Verify it's a set
|
|
1950
|
+
assert isinstance(retcodes, set)
|
|
1951
|
+
|
|
1952
|
+
# Verify the expected codes are present
|
|
1953
|
+
assert retcodes == {
|
|
1954
|
+
mock_mt5_import.TRADE_RETCODE_PLACED, # 10008
|
|
1955
|
+
mock_mt5_import.TRADE_RETCODE_DONE, # 10009
|
|
1956
|
+
mock_mt5_import.TRADE_RETCODE_DONE_PARTIAL, # 10010
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
def test_mt5_failed_trade_retcodes_property(
|
|
1960
|
+
self, mock_mt5_import: ModuleType
|
|
1961
|
+
) -> None:
|
|
1962
|
+
"""Test mt5_failed_trade_retcodes property returns correct set of codes."""
|
|
1963
|
+
client = Mt5TradingClient(mt5=mock_mt5_import)
|
|
1964
|
+
|
|
1965
|
+
# Get the property value
|
|
1966
|
+
retcodes = client.mt5_failed_trade_retcodes
|
|
1967
|
+
|
|
1968
|
+
# Verify it's a set
|
|
1969
|
+
assert isinstance(retcodes, set)
|
|
1970
|
+
|
|
1971
|
+
# Verify it contains the expected codes
|
|
1972
|
+
expected_codes = {
|
|
1973
|
+
mock_mt5_import.TRADE_RETCODE_REQUOTE, # 10004
|
|
1974
|
+
mock_mt5_import.TRADE_RETCODE_REJECT, # 10006
|
|
1975
|
+
mock_mt5_import.TRADE_RETCODE_CANCEL, # 10007
|
|
1976
|
+
mock_mt5_import.TRADE_RETCODE_ERROR, # 10011
|
|
1977
|
+
mock_mt5_import.TRADE_RETCODE_TIMEOUT, # 10012
|
|
1978
|
+
mock_mt5_import.TRADE_RETCODE_INVALID, # 10013
|
|
1979
|
+
mock_mt5_import.TRADE_RETCODE_INVALID_VOLUME, # 10014
|
|
1980
|
+
mock_mt5_import.TRADE_RETCODE_INVALID_PRICE, # 10015
|
|
1981
|
+
mock_mt5_import.TRADE_RETCODE_INVALID_STOPS, # 10016
|
|
1982
|
+
mock_mt5_import.TRADE_RETCODE_TRADE_DISABLED, # 10017
|
|
1983
|
+
mock_mt5_import.TRADE_RETCODE_MARKET_CLOSED, # 10018
|
|
1984
|
+
mock_mt5_import.TRADE_RETCODE_NO_MONEY, # 10019
|
|
1985
|
+
mock_mt5_import.TRADE_RETCODE_PRICE_CHANGED, # 10020
|
|
1986
|
+
mock_mt5_import.TRADE_RETCODE_PRICE_OFF, # 10021
|
|
1987
|
+
mock_mt5_import.TRADE_RETCODE_INVALID_EXPIRATION, # 10022
|
|
1988
|
+
mock_mt5_import.TRADE_RETCODE_ORDER_CHANGED, # 10023
|
|
1989
|
+
mock_mt5_import.TRADE_RETCODE_TOO_MANY_REQUESTS, # 10024
|
|
1990
|
+
mock_mt5_import.TRADE_RETCODE_NO_CHANGES, # 10025
|
|
1991
|
+
mock_mt5_import.TRADE_RETCODE_SERVER_DISABLES_AT, # 10026
|
|
1992
|
+
mock_mt5_import.TRADE_RETCODE_CLIENT_DISABLES_AT, # 10027
|
|
1993
|
+
mock_mt5_import.TRADE_RETCODE_LOCKED, # 10028
|
|
1994
|
+
mock_mt5_import.TRADE_RETCODE_FROZEN, # 10029
|
|
1995
|
+
mock_mt5_import.TRADE_RETCODE_INVALID_FILL, # 10030
|
|
1996
|
+
mock_mt5_import.TRADE_RETCODE_CONNECTION, # 10031
|
|
1997
|
+
mock_mt5_import.TRADE_RETCODE_ONLY_REAL, # 10032
|
|
1998
|
+
mock_mt5_import.TRADE_RETCODE_LIMIT_ORDERS, # 10033
|
|
1999
|
+
mock_mt5_import.TRADE_RETCODE_LIMIT_VOLUME, # 10034
|
|
2000
|
+
mock_mt5_import.TRADE_RETCODE_INVALID_ORDER, # 10035
|
|
2001
|
+
mock_mt5_import.TRADE_RETCODE_POSITION_CLOSED, # 10036
|
|
2002
|
+
mock_mt5_import.TRADE_RETCODE_INVALID_CLOSE_VOLUME, # 10038
|
|
2003
|
+
mock_mt5_import.TRADE_RETCODE_CLOSE_ORDER_EXIST, # 10039
|
|
2004
|
+
mock_mt5_import.TRADE_RETCODE_LIMIT_POSITIONS, # 10040
|
|
2005
|
+
mock_mt5_import.TRADE_RETCODE_REJECT_CANCEL, # 10041
|
|
2006
|
+
mock_mt5_import.TRADE_RETCODE_LONG_ONLY, # 10042
|
|
2007
|
+
mock_mt5_import.TRADE_RETCODE_SHORT_ONLY, # 10043
|
|
2008
|
+
mock_mt5_import.TRADE_RETCODE_CLOSE_ONLY, # 10044
|
|
2009
|
+
mock_mt5_import.TRADE_RETCODE_FIFO_CLOSE, # 10045
|
|
2010
|
+
mock_mt5_import.TRADE_RETCODE_HEDGE_PROHIBITED, # 10046
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
# Verify all expected codes are present
|
|
2014
|
+
assert retcodes == expected_codes
|
|
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
|