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.
Files changed (32) hide show
  1. pdmt5-0.1.9/.github/workflows/claude.yml +59 -0
  2. {pdmt5-0.1.7 → pdmt5-0.1.9}/CLAUDE.md +2 -2
  3. {pdmt5-0.1.7 → pdmt5-0.1.9}/PKG-INFO +6 -6
  4. {pdmt5-0.1.7 → pdmt5-0.1.9}/README.md +5 -5
  5. {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/trading.py +73 -12
  6. {pdmt5-0.1.7 → pdmt5-0.1.9}/pyproject.toml +7 -5
  7. {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_trading.py +153 -9
  8. {pdmt5-0.1.7 → pdmt5-0.1.9}/uv.lock +1 -1
  9. {pdmt5-0.1.7 → pdmt5-0.1.9}/.claude/settings.json +0 -0
  10. {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/FUNDING.yml +0 -0
  11. {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/copilot-instructions.md +0 -0
  12. {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/dependabot.yml +0 -0
  13. {pdmt5-0.1.7 → pdmt5-0.1.9}/.github/workflows/ci.yml +0 -0
  14. {pdmt5-0.1.7 → pdmt5-0.1.9}/.gitignore +0 -0
  15. {pdmt5-0.1.7 → pdmt5-0.1.9}/LICENSE +0 -0
  16. {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/dataframe.md +0 -0
  17. {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/index.md +0 -0
  18. {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/mt5.md +0 -0
  19. {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/trading.md +0 -0
  20. {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/api/utils.md +0 -0
  21. {pdmt5-0.1.7 → pdmt5-0.1.9}/docs/index.md +0 -0
  22. {pdmt5-0.1.7 → pdmt5-0.1.9}/mkdocs.yml +0 -0
  23. {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/__init__.py +0 -0
  24. {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/dataframe.py +0 -0
  25. {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/mt5.py +0 -0
  26. {pdmt5-0.1.7 → pdmt5-0.1.9}/pdmt5/utils.py +0 -0
  27. {pdmt5-0.1.7 → pdmt5-0.1.9}/renovate.json +0 -0
  28. {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/__init__.py +0 -0
  29. {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_dataframe.py +0 -0
  30. {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_init.py +0 -0
  31. {pdmt5-0.1.7/test → pdmt5-0.1.9/tests}/test_mt5.py +0 -0
  32. {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 test/ -v
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
- - `test/`: Comprehensive test suite (pytest-based)
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.7
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
- ### From GitHub
56
+ ### Using pip
57
57
 
58
58
  ```bash
59
- git clone https://github.com/dceoy/pdmt5.git
60
- pip install -U --no-cache-dir ./pdmt5
59
+ pip install -U pdmt5
60
+ pip install -U MetaTrader5
61
61
  ```
62
62
 
63
- ### Using uv (recommended for development)
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 test/ -v
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
- ### From GitHub
33
+ ### Using pip
34
34
 
35
35
  ```bash
36
- git clone https://github.com/dceoy/pdmt5.git
37
- pip install -U --no-cache-dir ./pdmt5
36
+ pip install -U pdmt5
37
+ pip install -U MetaTrader5
38
38
  ```
39
39
 
40
- ### Using uv (recommended for development)
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 test/ -v
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 ((not dry_run) and retcode == self.mt5.TRADE_RETCODE_DONE) or (
142
- dry_run and retcode == 0
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 retcode in {
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", "Unknown error")
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.7"
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 = ["test/**"]
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
- "test/**/*.py" = [
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 = ["test"]
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
- "test/**",
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 = 10004
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": 10004,
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 = 10004
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": 10004,
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
@@ -613,7 +613,7 @@ wheels = [
613
613
 
614
614
  [[package]]
615
615
  name = "pdmt5"
616
- version = "0.1.7"
616
+ version = "0.1.9"
617
617
  source = { editable = "." }
618
618
  dependencies = [
619
619
  { name = "metatrader5", marker = "sys_platform == 'win32'" },
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