#!/usr/bin/env python3 """ 🎯 Advanced Position Management Module Performance Optimization Features: 1. Adaptive Position Sizing 2. Trailing Stop-Loss 3. Partial Take Profit """ import MetaTrader5 as mt import logging from datetime import datetime from typing import Tuple, Optional, Dict logger = logging.getLogger(__name__) # ========================================== # 1. ADAPTIVE POSITION SIZING # ========================================== class AdaptivePositionSizer: """ Passt Position Size basierend auf Signal Confidence an Bessere Signals → Größere Positionen Schwächere Signals → Kleinere Positionen """ def __init__(self, base_risk: float = 0.02, high_confidence_threshold: float = 80.0, medium_confidence_threshold: float = 70.0, high_multiplier: float = 1.5, medium_multiplier: float = 1.0, low_multiplier: float = 0.5): """ Args: base_risk: Basis-Risk pro Trade (default 1%) high_confidence_threshold: Ab diesem Wert gilt Signal als "high confidence" medium_confidence_threshold: Ab diesem Wert gilt Signal als "medium confidence" high_multiplier: Risk-Multiplikator für high confidence (1.5x = 1.5%) medium_multiplier: Risk-Multiplikator für medium confidence (1.0x = 1.0%) low_multiplier: Risk-Multiplikator für low confidence (0.5x = 0.5%) """ self.base_risk = base_risk self.high_threshold = high_confidence_threshold self.medium_threshold = medium_confidence_threshold self.high_mult = high_multiplier self.medium_mult = medium_multiplier self.low_mult = low_multiplier def calculate_risk_for_confidence(self, confidence: float) -> float: """ Berechnet angepasstes Risk basierend auf Confidence Args: confidence: Signal Confidence (0-100) Returns: Angepasstes Risk (z.B. 0.015 für 1.5%) """ if confidence >= self.high_threshold: multiplier = self.high_mult category = "HIGH" elif confidence >= self.medium_threshold: multiplier = self.medium_mult category = "MEDIUM" else: multiplier = self.low_mult category = "LOW" adjusted_risk = self.base_risk * multiplier logger.info(f"📊 Adaptive Position Sizing:") logger.info(f" Confidence: {confidence:.1f}% ({category})") logger.info(f" Base Risk: {self.base_risk*100:.1f}%") logger.info(f" Multiplier: {multiplier}x") logger.info(f" Adjusted Risk: {adjusted_risk*100:.1f}%") return adjusted_risk def calculate_position_size(self, confidence: float, balance: float, stop_loss_distance: float, symbol: str = "XAUUSD") -> float: """ Berechnet Position Size mit adaptivem Risk Args: confidence: Signal Confidence balance: Account Balance stop_loss_distance: Distanz zum Stop Loss in Pips symbol: Trading Symbol Returns: Lot Size (Volume) """ # Adaptive Risk adjusted_risk = self.calculate_risk_for_confidence(confidence) risk_amount = balance * adjusted_risk # Symbol Info symbol_info = mt.symbol_info(symbol) if not symbol_info: logger.error(f"Symbol info not available for {symbol}") return 0.10 # Minimum # Pip Value berechnen point = symbol_info.point tick_value = symbol_info.trade_tick_value tick_size = symbol_info.trade_tick_size pip_value = (tick_value / tick_size) * point # Volume berechnen volume = risk_amount / (stop_loss_distance * pip_value) # Auf erlaubte Schritte runden volume_min = max(symbol_info.volume_min, 0.10) # Min: Broker-Min oder 0.10 volume_max = min(symbol_info.volume_max, 0.20) # Max: Broker-Max oder 0.20 volume_step = symbol_info.volume_step volume = round(volume / volume_step) * volume_step volume = max(volume_min, min(volume_max, volume)) logger.info(f"💰 Position Size: {volume:.2f} lots") logger.info(f" Risk Amount: ${risk_amount:.2f}") logger.info(f" SL Distance: {stop_loss_distance:.2f} pips") return volume # ========================================== # 2. TRAILING STOP-LOSS # ========================================== class TrailingStopManager: """ Verwaltet Trailing Stop-Loss für laufende Positionen Bewegt Stop-Loss mit Profit mit: - Break-Even bei 50% des Weges zu TP - Lock 50% Profit bei 75% des Weges zu TP """ def __init__(self, breakeven_trigger_pct: float = 0.5, profit_lock_trigger_pct: float = 0.75, profit_lock_amount_pct: float = 0.5, min_distance_points: int = 100): """ Args: breakeven_trigger_pct: Bei wie viel % des TP-Wegs auf Break-Even profit_lock_trigger_pct: Bei wie viel % des TP-Wegs Profit locken profit_lock_amount_pct: Wie viel % vom Profit locken min_distance_points: Minimum Distanz in Points (Anti-Stop-Hunting) """ self.breakeven_trigger = breakeven_trigger_pct self.profit_lock_trigger = profit_lock_trigger_pct self.profit_lock_amount = profit_lock_amount_pct self.min_distance = min_distance_points def should_update_trailing_stop(self, position) -> Tuple[bool, Optional[float], str]: """ Prüft ob Trailing Stop aktualisiert werden sollte Args: position: MT5 Position Object Returns: (should_update, new_sl_price, reason) """ try: # Position Info ticket = position.ticket position_type = position.type # 0=BUY, 1=SELL entry_price = position.price_open current_sl = position.sl tp = position.tp # Current Price symbol_info = mt.symbol_info_tick(position.symbol) if not symbol_info: return False, None, "No symbol info" current_price = symbol_info.bid if position_type == 0 else symbol_info.ask # TP Distance if position_type == 0: # BUY tp_distance = tp - entry_price current_distance = current_price - entry_price else: # SELL tp_distance = entry_price - tp current_distance = entry_price - current_price if tp_distance <= 0: return False, None, "Invalid TP distance" # Progress to TP progress_pct = current_distance / tp_distance # Check Break-Even Trigger if progress_pct >= self.breakeven_trigger: new_sl = entry_price # Verify minimum distance if position_type == 0: # BUY sl_distance_points = (current_price - new_sl) / mt.symbol_info(position.symbol).point else: # SELL sl_distance_points = (new_sl - current_price) / mt.symbol_info(position.symbol).point if sl_distance_points < self.min_distance: return False, None, f"Distance too small: {sl_distance_points:.0f} points" # Don't move SL backwards if position_type == 0: # BUY if current_sl > 0 and new_sl <= current_sl: return False, None, "Would move SL backwards" else: # SELL if current_sl > 0 and new_sl >= current_sl: return False, None, "Would move SL backwards" return True, new_sl, f"Break-Even at {progress_pct*100:.1f}% progress" # Check Profit Lock Trigger if progress_pct >= self.profit_lock_trigger: if position_type == 0: # BUY locked_profit = tp_distance * self.profit_lock_amount new_sl = entry_price + locked_profit else: # SELL locked_profit = tp_distance * self.profit_lock_amount new_sl = entry_price - locked_profit # Verify minimum distance if position_type == 0: # BUY sl_distance_points = (current_price - new_sl) / mt.symbol_info(position.symbol).point else: # SELL sl_distance_points = (new_sl - current_price) / mt.symbol_info(position.symbol).point if sl_distance_points < self.min_distance: return False, None, f"Distance too small: {sl_distance_points:.0f} points" # Don't move SL backwards if position_type == 0: # BUY if current_sl > 0 and new_sl <= current_sl: return False, None, "Would move SL backwards" else: # SELL if current_sl > 0 and new_sl >= current_sl: return False, None, "Would move SL backwards" return True, new_sl, f"Locking {self.profit_lock_amount*100:.0f}% profit at {progress_pct*100:.1f}% progress" return False, None, f"Progress {progress_pct*100:.1f}% < trigger {self.breakeven_trigger*100:.0f}%" except Exception as e: logger.error(f"Error checking trailing stop: {e}") return False, None, str(e) def update_stop_loss(self, position, new_sl: float) -> bool: """ Aktualisiert Stop-Loss für Position Args: position: MT5 Position new_sl: Neuer Stop-Loss Preis Returns: Success """ try: request = { "action": mt.TRADE_ACTION_SLTP, "position": position.ticket, "symbol": position.symbol, "sl": new_sl, "tp": position.tp, "magic": 234000, "comment": "Trailing Stop" } result = mt.order_send(request) if result.retcode == mt.TRADE_RETCODE_DONE: logger.info(f"✅ Trailing Stop updated for #{position.ticket}") logger.info(f" Old SL: {position.sl:.5f}") logger.info(f" New SL: {new_sl:.5f}") return True else: logger.error(f"❌ Failed to update trailing stop: {result.comment}") return False except Exception as e: logger.error(f"Error updating stop loss: {e}") return False # ========================================== # 3. PARTIAL TAKE PROFIT # ========================================== class PartialTakeProfitManager: """ Verwaltet Partial Take Profit Schließt Teil der Position bei TP1, lässt Rest laufen bis TP2 """ def __init__(self, tp1_risk_ratio: float = 1.5, tp2_risk_ratio: float = 2.5, partial_close_pct: float = 0.5): """ Args: tp1_risk_ratio: TP1 bei diesem Risk-Reward (1.5 = 1.5x Risk) tp2_risk_ratio: TP2 bei diesem Risk-Reward (2.5 = 2.5x Risk) partial_close_pct: Wie viel % bei TP1 schließen (0.5 = 50%) """ self.tp1_ratio = tp1_risk_ratio self.tp2_ratio = tp2_risk_ratio self.partial_pct = partial_close_pct def calculate_partial_tp_levels(self, entry_price: float, sl_price: float, position_type: int) -> Tuple[float, float]: """ Berechnet TP1 und TP2 Levels Args: entry_price: Entry Preis sl_price: Stop Loss Preis position_type: 0=BUY, 1=SELL Returns: (tp1_price, tp2_price) """ if position_type == 0: # BUY risk = entry_price - sl_price tp1 = entry_price + (risk * self.tp1_ratio) tp2 = entry_price + (risk * self.tp2_ratio) else: # SELL risk = sl_price - entry_price tp1 = entry_price - (risk * self.tp1_ratio) tp2 = entry_price - (risk * self.tp2_ratio) logger.info(f"📊 Partial TP Levels:") logger.info(f" Entry: {entry_price:.5f}") logger.info(f" SL: {sl_price:.5f}") logger.info(f" Risk: {abs(risk):.5f}") logger.info(f" TP1 ({self.tp1_ratio}R): {tp1:.5f}") logger.info(f" TP2 ({self.tp2_ratio}R): {tp2:.5f}") return tp1, tp2 def should_close_partial(self, position, tp1_price: float) -> Tuple[bool, str]: """ Prüft ob Partial Close ausgeführt werden soll Args: position: MT5 Position tp1_price: TP1 Preis Level Returns: (should_close, reason) """ try: # Current Price symbol_info = mt.symbol_info_tick(position.symbol) if not symbol_info: return False, "No symbol info" current_price = symbol_info.bid if position.type == 0 else symbol_info.ask # Check if TP1 hit if position.type == 0: # BUY if current_price >= tp1_price: return True, f"TP1 hit: Price {current_price:.5f} >= TP1 {tp1_price:.5f}" else: # SELL if current_price <= tp1_price: return True, f"TP1 hit: Price {current_price:.5f} <= TP1 {tp1_price:.5f}" return False, f"TP1 not reached yet" except Exception as e: logger.error(f"Error checking partial close: {e}") return False, str(e) def close_partial_position(self, position, close_pct: float = None) -> bool: """ Schließt Teil der Position Args: position: MT5 Position close_pct: Prozent zum Schließen (default: self.partial_pct) Returns: Success """ try: if close_pct is None: close_pct = self.partial_pct # Calculate volume to close close_volume = round(position.volume * close_pct, 2) # Minimum volume check symbol_info = mt.symbol_info(position.symbol) if close_volume < symbol_info.volume_min: logger.warning(f"Close volume {close_volume} < minimum {symbol_info.volume_min}") return False # Close request close_type = mt.ORDER_TYPE_SELL if position.type == 0 else mt.ORDER_TYPE_BUY close_price = mt.symbol_info_tick(position.symbol).bid if position.type == 0 else mt.symbol_info_tick(position.symbol).ask request = { "action": mt.TRADE_ACTION_DEAL, "position": position.ticket, "symbol": position.symbol, "volume": close_volume, "type": close_type, "price": close_price, "deviation": 20, "magic": 234000, "comment": f"Partial TP1 ({close_pct*100:.0f}%)", "type_time": mt.ORDER_TIME_GTC, "type_filling": mt.ORDER_FILLING_IOC, } result = mt.order_send(request) if result.retcode == mt.TRADE_RETCODE_DONE: logger.info(f"✅ Partial close executed for #{position.ticket}") logger.info(f" Closed: {close_volume:.2f} lots ({close_pct*100:.0f}%)") logger.info(f" Remaining: {position.volume - close_volume:.2f} lots") logger.info(f" Profit: ${result.profit:.2f}") return True else: logger.error(f"❌ Partial close failed: {result.comment}") return False except Exception as e: logger.error(f"Error closing partial position: {e}") return False # ========================================== # INTEGRATED MANAGER # ========================================== class AdvancedPositionManager: """ Integrierter Manager für alle Advanced Features """ def __init__(self, enable_adaptive_sizing: bool = True, enable_trailing_stop: bool = True, enable_partial_tp: bool = True, base_risk: float = 0.02): """ Args: enable_adaptive_sizing: Adaptive Position Sizing aktivieren enable_trailing_stop: Trailing Stop aktivieren enable_partial_tp: Partial TP aktivieren base_risk: Base Risk per Trade (wird an AdaptivePositionSizer übergeben) """ self.adaptive_sizing = AdaptivePositionSizer(base_risk=base_risk) if enable_adaptive_sizing else None self.trailing_stop = TrailingStopManager() if enable_trailing_stop else None self.partial_tp = PartialTakeProfitManager() if enable_partial_tp else None # Track partial closes (avoid duplicate partial closes) self.partial_closed_positions = set() logger.info("🎯 Advanced Position Manager initialized") logger.info(f" Adaptive Sizing: {'✅' if enable_adaptive_sizing else '❌'}") logger.info(f" Trailing Stop: {'✅' if enable_trailing_stop else '❌'}") logger.info(f" Partial TP: {'✅' if enable_partial_tp else '❌'}") def check_and_update_positions(self, symbol: str = "XAUUSD"): """ Prüft alle offenen Positionen und aktualisiert Trailing Stops / Partial TPs Args: symbol: Symbol zum Checken """ try: positions = mt.positions_get(symbol=symbol) if not positions: return logger.info(f"\n🔍 Checking {len(positions)} position(s) for {symbol}...") for position in positions: # Trailing Stop Check if self.trailing_stop: should_update, new_sl, reason = self.trailing_stop.should_update_trailing_stop(position) if should_update: logger.info(f"📈 Trailing Stop Trigger for #{position.ticket}: {reason}") self.trailing_stop.update_stop_loss(position, new_sl) else: logger.debug(f"⏸️ No trailing stop update: {reason}") # Partial TP Check (only if not already partially closed) if self.partial_tp and position.ticket not in self.partial_closed_positions: # Calculate TP1 from current position tp1, tp2 = self.partial_tp.calculate_partial_tp_levels( position.price_open, position.sl, position.type ) should_close, reason = self.partial_tp.should_close_partial(position, tp1) if should_close: logger.info(f"🎯 Partial TP Trigger for #{position.ticket}: {reason}") if self.partial_tp.close_partial_position(position): self.partial_closed_positions.add(position.ticket) else: logger.debug(f"⏸️ No partial close: {reason}") except Exception as e: logger.error(f"Error checking positions: {e}") # ========================================== # USAGE EXAMPLE # ========================================== """ INTEGRATION IN NOTEBOOK: # Cell: Advanced Position Management Setup from advanced_position_management import AdvancedPositionManager, AdaptivePositionSizer # Initialize Manager adv_position_mgr = AdvancedPositionManager( enable_adaptive_sizing=True, enable_trailing_stop=True, enable_partial_tp=True ) print("✅ Advanced Position Management activated!") # Cell: In execute_trade_v2_adaptive() # BEFORE (old): volume = 0.10 # Fixed # AFTER (with Adaptive Sizing): if adv_position_mgr.adaptive_sizing: volume = adv_position_mgr.adaptive_sizing.calculate_position_size( confidence=confidence, balance=account_info.balance, stop_loss_distance=adjusted_atr_mult * atr * 10000, # Convert to pips symbol=symbol ) else: volume = 0.10 # Cell: Add to Scheduler (for Trailing Stop + Partial TP) scheduler.add_job( func=lambda: adv_position_mgr.check_and_update_positions(symbol), trigger='interval', minutes=1, id='advanced_position_management' ) print("✅ Advanced Position Management scheduler added!") """