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.
Files changed (31) hide show
  1. {pdmt5-0.1.1 → pdmt5-0.1.2}/PKG-INFO +1 -1
  2. {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/trading.py +9 -1
  3. {pdmt5-0.1.1 → pdmt5-0.1.2}/pyproject.toml +1 -1
  4. {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_trading.py +175 -0
  5. {pdmt5-0.1.1 → pdmt5-0.1.2}/uv.lock +1 -1
  6. {pdmt5-0.1.1 → pdmt5-0.1.2}/.claude/settings.json +0 -0
  7. {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/FUNDING.yml +0 -0
  8. {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/copilot-instructions.md +0 -0
  9. {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/dependabot.yml +0 -0
  10. {pdmt5-0.1.1 → pdmt5-0.1.2}/.github/workflows/ci.yml +0 -0
  11. {pdmt5-0.1.1 → pdmt5-0.1.2}/.gitignore +0 -0
  12. {pdmt5-0.1.1 → pdmt5-0.1.2}/CLAUDE.md +0 -0
  13. {pdmt5-0.1.1 → pdmt5-0.1.2}/LICENSE +0 -0
  14. {pdmt5-0.1.1 → pdmt5-0.1.2}/README.md +0 -0
  15. {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/dataframe.md +0 -0
  16. {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/index.md +0 -0
  17. {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/mt5.md +0 -0
  18. {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/trading.md +0 -0
  19. {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/api/utils.md +0 -0
  20. {pdmt5-0.1.1 → pdmt5-0.1.2}/docs/index.md +0 -0
  21. {pdmt5-0.1.1 → pdmt5-0.1.2}/mkdocs.yml +0 -0
  22. {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/__init__.py +0 -0
  23. {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/dataframe.py +0 -0
  24. {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/mt5.py +0 -0
  25. {pdmt5-0.1.1 → pdmt5-0.1.2}/pdmt5/utils.py +0 -0
  26. {pdmt5-0.1.1 → pdmt5-0.1.2}/renovate.json +0 -0
  27. {pdmt5-0.1.1 → pdmt5-0.1.2}/test/__init__.py +0 -0
  28. {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_dataframe.py +0 -0
  29. {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_init.py +0 -0
  30. {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_mt5.py +0 -0
  31. {pdmt5-0.1.1 → pdmt5-0.1.2}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdmt5
3
- Version: 0.1.1
3
+ Version: 0.1.2
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>
@@ -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) for s in symbol_list
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.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,
@@ -613,7 +613,7 @@ wheels = [
613
613
 
614
614
  [[package]]
615
615
  name = "pdmt5"
616
- version = "0.1.1"
616
+ version = "0.1.2"
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
File without changes
File without changes
File without changes