pdmt5 0.1.5__py3-none-any.whl → 0.1.6__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
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from datetime import timedelta
6
+ from math import floor
6
7
  from typing import TYPE_CHECKING, Any, Literal
7
8
 
8
9
  from pydantic import ConfigDict, Field
@@ -263,41 +264,60 @@ class Mt5TradingClient(Mt5DataClient):
263
264
  )
264
265
  return []
265
266
 
266
- def calculate_minimum_order_margins(self, symbol: str) -> dict[str, float]:
267
- """Calculate minimum order margins for a given symbol.
267
+ def calculate_minimum_order_margin(
268
+ self,
269
+ symbol: str,
270
+ order_side: Literal["BUY", "SELL"],
271
+ ) -> dict[str, float]:
272
+ """Calculate the minimum order margins for a given symbol.
268
273
 
269
274
  Args:
270
- symbol: Symbol for which to calculate minimum order margins.
275
+ symbol: Symbol for which to calculate the minimum order margins.
276
+ order_side: Optional side of the order, either "BUY" or "SELL".
271
277
 
272
278
  Returns:
273
- Dictionary with margin information.
274
-
275
- Raises:
276
- Mt5TradingError: If margin calculation fails.
279
+ Dictionary with minimum volume and margin for the specified order side.
277
280
  """
278
281
  symbol_info = self.symbol_info_as_dict(symbol=symbol)
279
282
  symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
280
- min_ask_order_margin = self.order_calc_margin(
281
- action=self.mt5.ORDER_TYPE_BUY,
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
+ ),
294
+ ),
295
+ }
296
+
297
+ def calculate_volume_by_margin(
298
+ self,
299
+ symbol: str,
300
+ margin: float,
301
+ order_side: Literal["BUY", "SELL"],
302
+ ) -> float:
303
+ """Calculate volume based on margin for a given symbol and order side.
304
+
305
+ Args:
306
+ symbol: Symbol for which to calculate the volume.
307
+ margin: Margin amount to use for the calculation.
308
+ order_side: Side of the order, either "BUY" or "SELL".
309
+
310
+ Returns:
311
+ Calculated volume as a float.
312
+ """
313
+ min_order_margin_dict = self.calculate_minimum_order_margin(
282
314
  symbol=symbol,
283
- volume=symbol_info["volume_min"],
284
- price=symbol_info_tick["ask"],
315
+ order_side=order_side,
285
316
  )
286
- min_bid_order_margin = self.order_calc_margin(
287
- action=self.mt5.ORDER_TYPE_SELL,
288
- symbol=symbol,
289
- volume=symbol_info["volume_min"],
290
- price=symbol_info_tick["bid"],
317
+ return (
318
+ floor(margin / min_order_margin_dict["margin"])
319
+ * min_order_margin_dict["volume"]
291
320
  )
292
- min_order_margins = {"ask": min_ask_order_margin, "bid": min_bid_order_margin}
293
- self.logger.info("Minimum order margins for %s: %s", symbol, min_order_margins)
294
- if all(min_order_margins.values()):
295
- return min_order_margins
296
- else:
297
- error_message = (
298
- f"Failed to calculate minimum order margins for symbol: {symbol}."
299
- )
300
- raise Mt5TradingError(error_message)
301
321
 
302
322
  def calculate_spread_ratio(
303
323
  self,
@@ -477,15 +497,15 @@ class Mt5TradingClient(Mt5DataClient):
477
497
  def calculate_new_position_margin_ratio(
478
498
  self,
479
499
  symbol: str,
480
- new_side: Literal["BUY", "SELL"] | None = None,
481
- new_volume: float = 0,
500
+ new_position_side: Literal["BUY", "SELL"] | None = None,
501
+ new_position_volume: float = 0,
482
502
  ) -> float:
483
503
  """Calculate the margin ratio for a new position.
484
504
 
485
505
  Args:
486
506
  symbol: Symbol for which to calculate the margin ratio.
487
- new_side: Side of the new position, either "BUY" or "SELL".
488
- new_volume: Volume of the new position.
507
+ new_position_side: Side of the new position, either "BUY" or "SELL".
508
+ new_position_volume: Volume of the new position.
489
509
 
490
510
  Returns:
491
511
  float: Margin ratio for the new position as a fraction of account equity.
@@ -499,20 +519,20 @@ class Mt5TradingClient(Mt5DataClient):
499
519
  positions_df["signed_margin"].sum() if positions_df.size else 0
500
520
  )
501
521
  symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
502
- if new_volume == 0:
522
+ if not (new_position_side and new_position_volume):
503
523
  new_signed_margin = 0
504
- elif new_side == "BUY":
524
+ elif new_position_side.upper() == "BUY":
505
525
  new_signed_margin = self.order_calc_margin(
506
526
  action=self.mt5.ORDER_TYPE_BUY,
507
527
  symbol=symbol,
508
- volume=new_volume,
528
+ volume=new_position_volume,
509
529
  price=symbol_info_tick["ask"],
510
530
  )
511
- elif new_side == "SELL":
531
+ elif new_position_side.upper() == "SELL":
512
532
  new_signed_margin = -self.order_calc_margin(
513
533
  action=self.mt5.ORDER_TYPE_SELL,
514
534
  symbol=symbol,
515
- volume=new_volume,
535
+ volume=new_position_volume,
516
536
  price=symbol_info_tick["bid"],
517
537
  )
518
538
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdmt5
3
- Version: 0.1.5
3
+ Version: 0.1.6
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>
@@ -42,6 +42,8 @@ Pandas-based data handler for MetaTrader 5
42
42
  - 🚀 **Context Manager Support**: Clean initialization and cleanup with `with` statements
43
43
  - 📈 **Time Series Ready**: OHLCV data with proper datetime indexing
44
44
  - 🛡️ **Robust Error Handling**: Custom exceptions with detailed MT5 error information
45
+ - 💰 **Advanced Trading Operations**: Position management, margin calculations, and risk analysis tools
46
+ - 🧪 **Dry Run Mode**: Test trading strategies without executing real trades
45
47
 
46
48
  ## Requirements
47
49
 
@@ -195,9 +197,10 @@ Advanced trading operations client that extends Mt5DataClient:
195
197
  - **Position Management**:
196
198
  - `close_open_positions()` - Close all positions for specified symbol(s)
197
199
  - `place_market_order()` - Place market orders with configurable side, volume, and execution modes
198
- - `update_open_position_sltp()` - Modify stop loss and take profit levels for open positions
199
- - **Market Analysis**:
200
- - `calculate_minimum_order_margins()` - Calculate minimum required margins for buy/sell orders
200
+ - `update_sltp_for_open_positions()` - Modify stop loss and take profit levels for open positions
201
+ - **Margin Calculations**:
202
+ - `calculate_minimum_order_margin()` - Calculate minimum required margin for a specific order side
203
+ - `calculate_volume_by_margin()` - Calculate maximum volume for given margin amount
201
204
  - `calculate_spread_ratio()` - Calculate normalized bid-ask spread ratio
202
205
  - `calculate_new_position_margin_ratio()` - Calculate margin ratio for potential new positions
203
206
  - **Simplified Data Access**:
@@ -299,22 +302,20 @@ with Mt5TradingClient(config=config, order_filling_mode="IOC") as trader:
299
302
  )
300
303
  print(f"Order placed: {order_result['retcode']}")
301
304
 
302
- # Update stop loss and take profit for an open position
303
- if positions := trader.positions_get_as_df(symbol="EURUSD"):
304
- position_ticket = positions.iloc[0]['ticket']
305
- update_result = trader.update_open_position_sltp(
306
- symbol="EURUSD",
307
- position_ticket=position_ticket,
308
- sl=1.0950, # New stop loss
309
- tp=1.1050 # New take profit
310
- )
311
- print(f"Position updated: {update_result['retcode']}")
305
+ # Update stop loss and take profit for open positions
306
+ update_results = trader.update_sltp_for_open_positions(
307
+ symbol="EURUSD",
308
+ stop_loss=1.0950, # New stop loss
309
+ take_profit=1.1050 # New take profit
310
+ )
311
+ for result in update_results:
312
+ print(f"Position updated: {result['retcode']}")
312
313
 
313
314
  # Calculate margin ratio for a new position
314
315
  margin_ratio = trader.calculate_new_position_margin_ratio(
315
316
  symbol="EURUSD",
316
- new_side="SELL",
317
- new_volume=0.2
317
+ new_position_side="SELL",
318
+ new_position_volume=0.2
318
319
  )
319
320
  print(f"New position margin ratio: {margin_ratio:.2%}")
320
321
 
@@ -347,10 +348,18 @@ with Mt5TradingClient(config=config) as trader:
347
348
  spread_ratio = trader.calculate_spread_ratio("EURUSD")
348
349
  print(f"EURUSD spread ratio: {spread_ratio:.5f}")
349
350
 
350
- # Get minimum order margins
351
- margins = trader.calculate_minimum_order_margins("EURUSD")
352
- print(f"Minimum ask margin: {margins['ask']}")
353
- print(f"Minimum bid margin: {margins['bid']}")
351
+ # Get minimum order margin for BUY and SELL
352
+ buy_margin = trader.calculate_minimum_order_margin("EURUSD", "BUY")
353
+ sell_margin = trader.calculate_minimum_order_margin("EURUSD", "SELL")
354
+ print(f"Minimum BUY margin: {buy_margin['margin']} (volume: {buy_margin['volume']})")
355
+ print(f"Minimum SELL margin: {sell_margin['margin']} (volume: {sell_margin['volume']})")
356
+
357
+ # Calculate volume by margin
358
+ available_margin = 1000.0
359
+ max_buy_volume = trader.calculate_volume_by_margin("EURUSD", available_margin, "BUY")
360
+ max_sell_volume = trader.calculate_volume_by_margin("EURUSD", available_margin, "SELL")
361
+ print(f"Max BUY volume for ${available_margin}: {max_buy_volume}")
362
+ print(f"Max SELL volume for ${available_margin}: {max_sell_volume}")
354
363
 
355
364
  # Get recent OHLC data with custom timeframe
356
365
  rates_df = trader.fetch_latest_rates_as_df(
@@ -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=TJjxq2PP2eIRM8pe4G0DzxLkLjOFR-0ZV0rpzDwl-TU,19488
4
+ pdmt5/trading.py,sha256=GOOYsbsjKtp26dw1VB6siZ1luBttD8cv-KNZi6pPUEs,20107
5
5
  pdmt5/utils.py,sha256=Ll5Q3OE5h1A_sZ_qVEnOPGniFlT6_MmHfuu0zqeLdeU,3913
6
- pdmt5-0.1.5.dist-info/METADATA,sha256=YjTgWZTyhoLtzbLii57WYM5UajB0pix15sM7CzhjgwY,16085
7
- pdmt5-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
- pdmt5-0.1.5.dist-info/licenses/LICENSE,sha256=iABrdaUGOBWLYotFupB_PGe8arV5o7rVhn-_vK6P704,1073
9
- pdmt5-0.1.5.dist-info/RECORD,,
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,,
File without changes