pdmt5 0.1.6__py3-none-any.whl → 0.1.8__py3-none-any.whl

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/trading.py CHANGED
@@ -6,7 +6,7 @@ from datetime import timedelta
6
6
  from math import floor
7
7
  from typing import TYPE_CHECKING, Any, Literal
8
8
 
9
- from pydantic import ConfigDict, Field
9
+ from pydantic import ConfigDict
10
10
 
11
11
  from .dataframe import Mt5DataClient
12
12
  from .mt5 import Mt5RuntimeError
@@ -27,15 +27,11 @@ class Mt5TradingClient(Mt5DataClient):
27
27
  """
28
28
 
29
29
  model_config = ConfigDict(frozen=True)
30
- order_filling_mode: Literal["IOC", "FOK", "RETURN"] = Field(
31
- default="IOC",
32
- description="Order filling mode: 'IOC' (Immediate or Cancel), "
33
- "'FOK' (Fill or Kill), 'RETURN' (Return if not filled)",
34
- )
35
30
 
36
31
  def close_open_positions(
37
32
  self,
38
33
  symbols: str | list[str] | tuple[str, ...] | None = None,
34
+ order_filling_mode: Literal["IOC", "FOK", "RETURN"] = "IOC",
39
35
  dry_run: bool = False,
40
36
  **kwargs: Any, # noqa: ANN401
41
37
  ) -> dict[str, list[dict[str, Any]]]:
@@ -44,6 +40,7 @@ class Mt5TradingClient(Mt5DataClient):
44
40
  Args:
45
41
  symbols: Optional symbol or list of symbols to filter positions.
46
42
  If None, all symbols will be considered.
43
+ order_filling_mode: Order filling mode, either "IOC", "FOK", or "RETURN".
47
44
  dry_run: If True, only check the order without sending it.
48
45
  **kwargs: Additional keyword arguments for request parameters.
49
46
 
@@ -59,13 +56,19 @@ class Mt5TradingClient(Mt5DataClient):
59
56
  symbol_list = self.symbols_get()
60
57
  self.logger.info("Fetching and closing positions for symbols: %s", symbol_list)
61
58
  return {
62
- s: self._fetch_and_close_position(symbol=s, dry_run=dry_run, **kwargs)
59
+ s: self._fetch_and_close_position(
60
+ symbol=s,
61
+ order_filling_mode=order_filling_mode,
62
+ dry_run=dry_run,
63
+ **kwargs,
64
+ )
63
65
  for s in symbol_list
64
66
  }
65
67
 
66
68
  def _fetch_and_close_position(
67
69
  self,
68
70
  symbol: str | None = None,
71
+ order_filling_mode: Literal["IOC", "FOK", "RETURN"] = "IOC",
69
72
  dry_run: bool = False,
70
73
  **kwargs: Any, # noqa: ANN401
71
74
  ) -> list[dict[str, Any]]:
@@ -73,6 +76,7 @@ class Mt5TradingClient(Mt5DataClient):
73
76
 
74
77
  Args:
75
78
  symbol: Optional symbol filter.
79
+ order_filling_mode: Order filling mode, either "IOC", "FOK", or "RETURN".
76
80
  dry_run: If True, only check the order without sending it.
77
81
  **kwargs: Additional keyword arguments for request parameters.
78
82
 
@@ -85,10 +89,6 @@ class Mt5TradingClient(Mt5DataClient):
85
89
  return []
86
90
  else:
87
91
  self.logger.info("Closing open positions for symbol: %s", symbol)
88
- order_filling_type = getattr(
89
- self.mt5,
90
- f"ORDER_FILLING_{self.order_filling_mode}",
91
- )
92
92
  return [
93
93
  self._send_or_check_order(
94
94
  request={
@@ -100,7 +100,10 @@ class Mt5TradingClient(Mt5DataClient):
100
100
  if p["type"] == self.mt5.POSITION_TYPE_BUY
101
101
  else self.mt5.ORDER_TYPE_BUY
102
102
  ),
103
- "type_filling": order_filling_type,
103
+ "type_filling": getattr(
104
+ self.mt5,
105
+ f"ORDER_FILLING_{order_filling_mode}",
106
+ ),
104
107
  "type_time": self.mt5.ORDER_TIME_GTC,
105
108
  "position": p["ticket"],
106
109
  **kwargs,
@@ -143,6 +146,7 @@ class Mt5TradingClient(Mt5DataClient):
143
146
  elif retcode in {
144
147
  self.mt5.TRADE_RETCODE_TRADE_DISABLED,
145
148
  self.mt5.TRADE_RETCODE_MARKET_CLOSED,
149
+ self.mt5.TRADE_RETCODE_NO_CHANGES,
146
150
  }:
147
151
  self.logger.info("response: %s", response)
148
152
  comment = response.get("comment", "Unknown error")
@@ -280,19 +284,25 @@ class Mt5TradingClient(Mt5DataClient):
280
284
  """
281
285
  symbol_info = self.symbol_info_as_dict(symbol=symbol)
282
286
  symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
283
- return {
284
- "volume": symbol_info["volume_min"],
285
- "margin": self.order_calc_margin(
286
- action=getattr(self.mt5, f"ORDER_TYPE_{order_side.upper()}"),
287
- symbol=symbol,
288
- volume=symbol_info["volume_min"],
289
- price=(
290
- symbol_info_tick["bid"]
291
- if order_side == "SELL"
292
- else symbol_info_tick["ask"]
293
- ),
287
+ margin = self.order_calc_margin(
288
+ action=getattr(self.mt5, f"ORDER_TYPE_{order_side.upper()}"),
289
+ symbol=symbol,
290
+ volume=symbol_info["volume_min"],
291
+ price=(
292
+ symbol_info_tick["bid"]
293
+ if order_side == "SELL"
294
+ else symbol_info_tick["ask"]
294
295
  ),
295
- }
296
+ )
297
+ if margin:
298
+ return {"volume": symbol_info["volume_min"], "margin": margin}
299
+ else:
300
+ self.logger.warning(
301
+ "No margin available for symbol: %s with order side: %s",
302
+ symbol,
303
+ order_side,
304
+ )
305
+ return {"volume": symbol_info["volume_min"], "margin": 0.0}
296
306
 
297
307
  def calculate_volume_by_margin(
298
308
  self,
@@ -314,10 +324,13 @@ class Mt5TradingClient(Mt5DataClient):
314
324
  symbol=symbol,
315
325
  order_side=order_side,
316
326
  )
317
- return (
318
- floor(margin / min_order_margin_dict["margin"])
319
- * min_order_margin_dict["volume"]
320
- )
327
+ if min_order_margin_dict["margin"]:
328
+ return (
329
+ floor(margin / min_order_margin_dict["margin"])
330
+ * min_order_margin_dict["volume"]
331
+ )
332
+ else:
333
+ return 0.0
321
334
 
322
335
  def calculate_spread_ratio(
323
336
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdmt5
3
- Version: 0.1.6
3
+ Version: 0.1.8
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>
@@ -191,9 +191,6 @@ Extends Mt5Client with pandas DataFrame and dictionary conversions:
191
191
 
192
192
  Advanced trading operations client that extends Mt5DataClient:
193
193
 
194
- - **Trading Configuration**:
195
- - `order_filling_mode` - Order execution mode: "IOC" (default), "FOK", or "RETURN"
196
- - `dry_run` - Test mode flag for simulating trades without execution
197
194
  - **Position Management**:
198
195
  - `close_open_positions()` - Close all positions for specified symbol(s)
199
196
  - `place_market_order()` - Place market orders with configurable side, volume, and execution modes
@@ -213,7 +210,6 @@ Advanced trading operations client that extends Mt5DataClient:
213
210
  - Comprehensive error handling with `Mt5TradingError`
214
211
  - Support for batch operations on multiple symbols
215
212
  - Automatic position closing with proper order type reversal
216
- - Dry run mode for strategy testing without real trades
217
213
 
218
214
  ### Configuration
219
215
 
@@ -290,8 +286,8 @@ with Mt5DataClient(config=config) as client:
290
286
  ```python
291
287
  from pdmt5 import Mt5TradingClient
292
288
 
293
- # Create trading client with specific order filling mode
294
- with Mt5TradingClient(config=config, order_filling_mode="IOC") as trader:
289
+ # Create trading client
290
+ with Mt5TradingClient(config=config) as trader:
295
291
  # Place a market buy order
296
292
  order_result = trader.place_market_order(
297
293
  symbol="EURUSD",
@@ -319,25 +315,16 @@ with Mt5TradingClient(config=config, order_filling_mode="IOC") as trader:
319
315
  )
320
316
  print(f"New position margin ratio: {margin_ratio:.2%}")
321
317
 
322
- # Close all EURUSD positions
323
- results = trader.close_open_positions(symbols="EURUSD")
318
+ # Close all EURUSD positions with specific order filling mode
319
+ results = trader.close_open_positions(
320
+ symbols="EURUSD",
321
+ order_filling_mode="FOK" # Fill or Kill
322
+ )
324
323
 
325
324
  if results:
326
325
  for symbol, close_results in results.items():
327
326
  for result in close_results:
328
327
  print(f"Closed position {result.get('position')} with result: {result['retcode']}")
329
-
330
- # Using dry run mode for testing
331
- trader_dry = Mt5TradingClient(config=config, dry_run=True)
332
- with trader_dry:
333
- # Test placing an order without actual execution
334
- test_order = trader_dry.place_market_order(
335
- symbol="GBPUSD",
336
- volume=0.1,
337
- order_side="SELL",
338
- dry_run=True # Override instance setting
339
- )
340
- print(f"Test order validation: {test_order['retcode']}")
341
328
  ```
342
329
 
343
330
  ### Market Analysis with Mt5TradingClient
@@ -405,7 +392,7 @@ cd pdmt5
405
392
  uv sync
406
393
 
407
394
  # Run tests
408
- uv run pytest test/ -v
395
+ uv run pytest tests/ -v
409
396
 
410
397
  # Run type checking
411
398
  uv run pyright .
@@ -1,9 +1,9 @@
1
1
  pdmt5/__init__.py,sha256=QbSFrsi7_bgFzb-ma4DmmUjR90UvrqKMnRZq1wPRmoI,446
2
2
  pdmt5/dataframe.py,sha256=rUWtR23hrXBdBqzJhbOlIemNy73RrjSTZZJUhwoL6io,38084
3
3
  pdmt5/mt5.py,sha256=KgxHapIrh5b4L0wIOAQIjfXNZafalihbFrh9fhYHmrI,32254
4
- pdmt5/trading.py,sha256=GOOYsbsjKtp26dw1VB6siZ1luBttD8cv-KNZi6pPUEs,20107
4
+ pdmt5/trading.py,sha256=GWznfYp2ndUwJqGBunxIJ9xwiQ-9oOwuUV8UaMcny1w,20655
5
5
  pdmt5/utils.py,sha256=Ll5Q3OE5h1A_sZ_qVEnOPGniFlT6_MmHfuu0zqeLdeU,3913
6
- pdmt5-0.1.6.dist-info/METADATA,sha256=cBYUp4lIyQuw7aRN7dphpgtejgW5jamAvYImYQiQS9o,16824
7
- pdmt5-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
- pdmt5-0.1.6.dist-info/licenses/LICENSE,sha256=iABrdaUGOBWLYotFupB_PGe8arV5o7rVhn-_vK6P704,1073
9
- pdmt5-0.1.6.dist-info/RECORD,,
6
+ pdmt5-0.1.8.dist-info/METADATA,sha256=6wf-gKgkEt16UKITUBimrAsicdwtbN8z6mB0_Q0HgwE,16176
7
+ pdmt5-0.1.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ pdmt5-0.1.8.dist-info/licenses/LICENSE,sha256=iABrdaUGOBWLYotFupB_PGe8arV5o7rVhn-_vK6P704,1073
9
+ pdmt5-0.1.8.dist-info/RECORD,,
File without changes