pdmt5 0.1.1__tar.gz → 0.1.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.
- {pdmt5-0.1.1 → pdmt5-0.1.2}/PKG-INFO +1 -1
- {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/trading.py +9 -1
- {pdmt5-0.1.1 → pdmt5-0.1.2}/pyproject.toml +1 -1
- {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_trading.py +175 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/uv.lock +1 -1
- {pdmt5-0.1.1 → pdmt5-0.1.2}/.claude/settings.json +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/FUNDING.yml +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/copilot-instructions.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/dependabot.yml +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/workflows/ci.yml +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/.gitignore +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/CLAUDE.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/LICENSE +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/README.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/dataframe.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/index.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/mt5.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/trading.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/utils.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/index.md +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/mkdocs.yml +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/__init__.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/dataframe.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/mt5.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/utils.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/renovate.json +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/test/__init__.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_dataframe.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_init.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_mt5.py +0 -0
- {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_utils.py +0 -0
|
@@ -32,6 +32,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
32
32
|
def close_open_positions(
|
|
33
33
|
self,
|
|
34
34
|
symbols: str | list[str] | tuple[str, ...] | None = None,
|
|
35
|
+
dry_run: bool | None = None,
|
|
35
36
|
**kwargs: Any, # noqa: ANN401
|
|
36
37
|
) -> dict[str, list[dict[str, Any]]]:
|
|
37
38
|
"""Close all open positions for specified symbols.
|
|
@@ -39,6 +40,8 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
39
40
|
Args:
|
|
40
41
|
symbols: Optional symbol or list of symbols to filter positions.
|
|
41
42
|
If None, all symbols will be considered.
|
|
43
|
+
dry_run: Optional flag to enable dry run mode. If None, uses the instance's
|
|
44
|
+
`dry_run` attribute.
|
|
42
45
|
**kwargs: Additional keyword arguments for request parameters.
|
|
43
46
|
|
|
44
47
|
Returns:
|
|
@@ -53,18 +56,22 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
53
56
|
symbol_list = self.symbols_get()
|
|
54
57
|
self.logger.info("Fetching and closing positions for symbols: %s", symbol_list)
|
|
55
58
|
return {
|
|
56
|
-
s: self._fetch_and_close_position(symbol=s, **kwargs)
|
|
59
|
+
s: self._fetch_and_close_position(symbol=s, dry_run=dry_run, **kwargs)
|
|
60
|
+
for s in symbol_list
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
def _fetch_and_close_position(
|
|
60
64
|
self,
|
|
61
65
|
symbol: str | None = None,
|
|
66
|
+
dry_run: bool | None = None,
|
|
62
67
|
**kwargs: Any, # noqa: ANN401
|
|
63
68
|
) -> list[dict[str, Any]]:
|
|
64
69
|
"""Close all open positions for a specific symbol.
|
|
65
70
|
|
|
66
71
|
Args:
|
|
67
72
|
symbol: Optional symbol filter.
|
|
73
|
+
dry_run: Optional flag to enable dry run mode. If None, uses the instance's
|
|
74
|
+
`dry_run` attribute.
|
|
68
75
|
**kwargs: Additional keyword arguments for request parameters.
|
|
69
76
|
|
|
70
77
|
Returns:
|
|
@@ -96,6 +103,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
96
103
|
"position": p["ticket"],
|
|
97
104
|
**kwargs,
|
|
98
105
|
},
|
|
106
|
+
dry_run=dry_run,
|
|
99
107
|
)
|
|
100
108
|
for p in positions_dict
|
|
101
109
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pdmt5"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
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"}]
|
|
@@ -228,6 +228,90 @@ class TestMt5TradingClient:
|
|
|
228
228
|
assert result["EURUSD"][0]["retcode"] == 10009
|
|
229
229
|
mock_mt5_import.order_send.assert_called_once()
|
|
230
230
|
|
|
231
|
+
def test_close_position_with_positions_dry_run(
|
|
232
|
+
self,
|
|
233
|
+
mock_mt5_import: ModuleType,
|
|
234
|
+
mock_position_buy: MockPositionInfo,
|
|
235
|
+
) -> None:
|
|
236
|
+
"""Test close_position with existing positions in dry run mode."""
|
|
237
|
+
client = Mt5TradingClient(mt5=mock_mt5_import, dry_run=True)
|
|
238
|
+
mock_mt5_import.initialize.return_value = True
|
|
239
|
+
client.initialize()
|
|
240
|
+
|
|
241
|
+
# Mock positions
|
|
242
|
+
mock_mt5_import.positions_get.return_value = [mock_position_buy]
|
|
243
|
+
|
|
244
|
+
mock_mt5_import.order_check.return_value.retcode = 0
|
|
245
|
+
mock_mt5_import.order_check.return_value._asdict.return_value = {
|
|
246
|
+
"retcode": 0,
|
|
247
|
+
"result": "check_success",
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
result = client.close_open_positions("EURUSD")
|
|
251
|
+
|
|
252
|
+
assert len(result["EURUSD"]) == 1
|
|
253
|
+
assert result["EURUSD"][0]["retcode"] == 0
|
|
254
|
+
mock_mt5_import.order_check.assert_called_once()
|
|
255
|
+
mock_mt5_import.order_send.assert_not_called()
|
|
256
|
+
|
|
257
|
+
def test_close_position_with_dry_run_override(
|
|
258
|
+
self,
|
|
259
|
+
mock_mt5_import: ModuleType,
|
|
260
|
+
mock_position_buy: MockPositionInfo,
|
|
261
|
+
) -> None:
|
|
262
|
+
"""Test close_position with dry_run parameter override."""
|
|
263
|
+
# Client initialized with dry_run=False
|
|
264
|
+
client = Mt5TradingClient(mt5=mock_mt5_import, dry_run=False)
|
|
265
|
+
mock_mt5_import.initialize.return_value = True
|
|
266
|
+
client.initialize()
|
|
267
|
+
|
|
268
|
+
# Mock positions
|
|
269
|
+
mock_mt5_import.positions_get.return_value = [mock_position_buy]
|
|
270
|
+
|
|
271
|
+
mock_mt5_import.order_check.return_value.retcode = 0
|
|
272
|
+
mock_mt5_import.order_check.return_value._asdict.return_value = {
|
|
273
|
+
"retcode": 0,
|
|
274
|
+
"result": "check_success",
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Override with dry_run=True
|
|
278
|
+
result = client.close_open_positions("EURUSD", dry_run=True)
|
|
279
|
+
|
|
280
|
+
assert len(result["EURUSD"]) == 1
|
|
281
|
+
assert result["EURUSD"][0]["retcode"] == 0
|
|
282
|
+
# Should use order_check instead of order_send
|
|
283
|
+
mock_mt5_import.order_check.assert_called_once()
|
|
284
|
+
mock_mt5_import.order_send.assert_not_called()
|
|
285
|
+
|
|
286
|
+
def test_close_position_with_real_mode_override(
|
|
287
|
+
self,
|
|
288
|
+
mock_mt5_import: ModuleType,
|
|
289
|
+
mock_position_buy: MockPositionInfo,
|
|
290
|
+
) -> None:
|
|
291
|
+
"""Test close_position with real mode override."""
|
|
292
|
+
# Client initialized with dry_run=True
|
|
293
|
+
client = Mt5TradingClient(mt5=mock_mt5_import, dry_run=True)
|
|
294
|
+
mock_mt5_import.initialize.return_value = True
|
|
295
|
+
client.initialize()
|
|
296
|
+
|
|
297
|
+
# Mock positions
|
|
298
|
+
mock_mt5_import.positions_get.return_value = [mock_position_buy]
|
|
299
|
+
|
|
300
|
+
mock_mt5_import.order_send.return_value.retcode = 10009
|
|
301
|
+
mock_mt5_import.order_send.return_value._asdict.return_value = {
|
|
302
|
+
"retcode": 10009,
|
|
303
|
+
"result": "send_success",
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# Override with dry_run=False
|
|
307
|
+
result = client.close_open_positions("EURUSD", dry_run=False)
|
|
308
|
+
|
|
309
|
+
assert len(result["EURUSD"]) == 1
|
|
310
|
+
assert result["EURUSD"][0]["retcode"] == 10009
|
|
311
|
+
# Should use order_send instead of order_check
|
|
312
|
+
mock_mt5_import.order_send.assert_called_once()
|
|
313
|
+
mock_mt5_import.order_check.assert_not_called()
|
|
314
|
+
|
|
231
315
|
def test_close_open_positions_all_symbols(
|
|
232
316
|
self,
|
|
233
317
|
mock_mt5_import: ModuleType,
|
|
@@ -319,6 +403,39 @@ class TestMt5TradingClient:
|
|
|
319
403
|
assert call_args["comment"] == "custom_close"
|
|
320
404
|
assert call_args["magic"] == 12345
|
|
321
405
|
|
|
406
|
+
def test_close_open_positions_with_kwargs_and_dry_run(
|
|
407
|
+
self,
|
|
408
|
+
mock_mt5_import: ModuleType,
|
|
409
|
+
mock_position_buy: MockPositionInfo,
|
|
410
|
+
) -> None:
|
|
411
|
+
"""Test close_open_positions with additional kwargs and dry_run override."""
|
|
412
|
+
client = Mt5TradingClient(mt5=mock_mt5_import, dry_run=False)
|
|
413
|
+
mock_mt5_import.initialize.return_value = True
|
|
414
|
+
client.initialize()
|
|
415
|
+
|
|
416
|
+
# Mock positions
|
|
417
|
+
mock_mt5_import.positions_get.return_value = [mock_position_buy]
|
|
418
|
+
|
|
419
|
+
mock_mt5_import.order_check.return_value.retcode = 0
|
|
420
|
+
mock_mt5_import.order_check.return_value._asdict.return_value = {
|
|
421
|
+
"retcode": 0,
|
|
422
|
+
"result": "check_success",
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
# Pass custom kwargs with dry_run override
|
|
426
|
+
result = client.close_open_positions(
|
|
427
|
+
"EURUSD", dry_run=True, comment="custom_close", magic=12345
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
assert len(result["EURUSD"]) == 1
|
|
431
|
+
assert result["EURUSD"][0]["retcode"] == 0
|
|
432
|
+
|
|
433
|
+
# Check that kwargs were passed through to order_check
|
|
434
|
+
call_args = mock_mt5_import.order_check.call_args[0][0]
|
|
435
|
+
assert call_args["comment"] == "custom_close"
|
|
436
|
+
assert call_args["magic"] == 12345
|
|
437
|
+
mock_mt5_import.order_send.assert_not_called()
|
|
438
|
+
|
|
322
439
|
def test_send_or_check_order_dry_run_success(
|
|
323
440
|
self,
|
|
324
441
|
mock_mt5_import: ModuleType,
|
|
@@ -611,6 +728,64 @@ class TestMt5TradingClient:
|
|
|
611
728
|
call_args = mock_mt5_import.order_send.call_args[0][0]
|
|
612
729
|
assert call_args["type"] == mock_mt5_import.ORDER_TYPE_BUY
|
|
613
730
|
|
|
731
|
+
def test_fetch_and_close_position_with_dry_run(
|
|
732
|
+
self,
|
|
733
|
+
mock_mt5_import: ModuleType,
|
|
734
|
+
mock_position_buy: MockPositionInfo,
|
|
735
|
+
mock_position_sell: MockPositionInfo,
|
|
736
|
+
) -> None:
|
|
737
|
+
"""Test _fetch_and_close_position with dry_run parameter."""
|
|
738
|
+
client = Mt5TradingClient(mt5=mock_mt5_import, dry_run=False)
|
|
739
|
+
mock_mt5_import.initialize.return_value = True
|
|
740
|
+
client.initialize()
|
|
741
|
+
|
|
742
|
+
# Test with multiple positions and dry_run override
|
|
743
|
+
mock_mt5_import.positions_get.return_value = [
|
|
744
|
+
mock_position_buy,
|
|
745
|
+
mock_position_sell,
|
|
746
|
+
]
|
|
747
|
+
|
|
748
|
+
mock_mt5_import.order_check.return_value.retcode = 0
|
|
749
|
+
mock_mt5_import.order_check.return_value._asdict.return_value = {
|
|
750
|
+
"retcode": 0,
|
|
751
|
+
"result": "check_success",
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
# Call internal method directly with dry_run=True
|
|
755
|
+
result = client._fetch_and_close_position(symbol="EURUSD", dry_run=True)
|
|
756
|
+
|
|
757
|
+
assert len(result) == 2
|
|
758
|
+
assert all(r["retcode"] == 0 for r in result)
|
|
759
|
+
assert mock_mt5_import.order_check.call_count == 2
|
|
760
|
+
mock_mt5_import.order_send.assert_not_called()
|
|
761
|
+
|
|
762
|
+
def test_fetch_and_close_position_inherits_instance_dry_run(
|
|
763
|
+
self,
|
|
764
|
+
mock_mt5_import: ModuleType,
|
|
765
|
+
mock_position_buy: MockPositionInfo,
|
|
766
|
+
) -> None:
|
|
767
|
+
"""Test _fetch_and_close_position inherits instance dry_run if not given."""
|
|
768
|
+
# Client initialized with dry_run=True
|
|
769
|
+
client = Mt5TradingClient(mt5=mock_mt5_import, dry_run=True)
|
|
770
|
+
mock_mt5_import.initialize.return_value = True
|
|
771
|
+
client.initialize()
|
|
772
|
+
|
|
773
|
+
mock_mt5_import.positions_get.return_value = [mock_position_buy]
|
|
774
|
+
|
|
775
|
+
mock_mt5_import.order_check.return_value.retcode = 0
|
|
776
|
+
mock_mt5_import.order_check.return_value._asdict.return_value = {
|
|
777
|
+
"retcode": 0,
|
|
778
|
+
"result": "check_success",
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
# Call without specifying dry_run - should use instance's dry_run=True
|
|
782
|
+
result = client._fetch_and_close_position(symbol="EURUSD")
|
|
783
|
+
|
|
784
|
+
assert len(result) == 1
|
|
785
|
+
assert result[0]["retcode"] == 0
|
|
786
|
+
mock_mt5_import.order_check.assert_called_once()
|
|
787
|
+
mock_mt5_import.order_send.assert_not_called()
|
|
788
|
+
|
|
614
789
|
def test_calculate_minimum_order_margins_success(
|
|
615
790
|
self,
|
|
616
791
|
mock_mt5_import: ModuleType,
|
|
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
|