#!/usr/bin/env python3 """ 📈 Enhanced Trailing Stop Management Verbesserte Trailing Stops mit ATR-basierter Dynamic Trailing IMPROVEMENTS: 1. ATR-based Trailing (nicht fix, sondern dynamisch) 2. Time-based Breakeven (nach X Stunden) 3. Profit-based Aggressive Trailing 4. Session-aware Trailing (Asian vs NY) 5. Multi-tier Profit Locking """ import MetaTrader5 as mt from datetime import datetime, timedelta from typing import Tuple, Optional, Dict import logging logger = logging.getLogger(__name__) class EnhancedTrailingStopManager: """ Verbesserte Trailing Stop Logik Features: - Früher Breakeven (30% statt 50%) - ATR-basiertes Trailing (dynamisch statt fix) - Time-based Protection (nach 4h auf BE) - Multi-tier Profit Locking (50%, 75%, 90%) """ def __init__(self, # Breakeven Settings breakeven_trigger_pct: float = 0.30, # ← Früher! (war 0.50) breakeven_buffer_pips: int = 5, # ← +5 Pips über BE # Profit Locking (Multi-tier) tier1_trigger: float = 0.50, # Bei 50% zu TP tier1_lock_pct: float = 0.25, # Lock 25% profit tier2_trigger: float = 0.75, # Bei 75% zu TP tier2_lock_pct: float = 0.50, # Lock 50% profit tier3_trigger: float = 0.90, # Bei 90% zu TP tier3_lock_pct: float = 0.75, # Lock 75% profit # ATR-based Trailing use_atr_trailing: bool = True, atr_multiplier: float = 1.0, # Trail by 1 × ATR # Time-based Protection time_based_breakeven: bool = True, hours_to_breakeven: float = 4.0, # Nach 4h → BE # Session-aware session_trailing_multipliers: Optional[Dict[str, float]] = None, # Technical min_distance_points: int = 100): """ Args: breakeven_trigger_pct: Bei wie viel % zu TP → Breakeven breakeven_buffer_pips: Zusätzliche Pips über Breakeven tier1/2/3_trigger: Multi-tier Trigger Points tier1/2/3_lock_pct: Lock Amounts pro Tier use_atr_trailing: ATR-basiertes Trailing nutzen atr_multiplier: ATR Multiplikator für Trailing time_based_breakeven: Time-based BE aktivieren hours_to_breakeven: Stunden bis Auto-Breakeven session_trailing_multipliers: Custom Multiplier pro Session min_distance_points: Minimum Distanz (Anti-Stop-Hunting) """ self.breakeven_trigger = breakeven_trigger_pct self.breakeven_buffer_pips = breakeven_buffer_pips self.tier1_trigger = tier1_trigger self.tier1_lock = tier1_lock_pct self.tier2_trigger = tier2_trigger self.tier2_lock = tier2_lock_pct self.tier3_trigger = tier3_trigger self.tier3_lock = tier3_lock_pct self.use_atr_trailing = use_atr_trailing self.atr_multiplier = atr_multiplier self.time_based_be = time_based_breakeven self.hours_to_be = hours_to_breakeven self.session_multipliers = session_trailing_multipliers or { 'asian': 1.0, # Standard 'ny': 1.5, # Größer (mehr Volatilität) 'london': 1.2, 'overlap': 1.3 } self.min_distance = min_distance_points # Tracking self.position_tiers = {} # ticket → current tier logger.info("✅ Enhanced Trailing Stop Manager initialized") logger.info(f" Breakeven: {breakeven_trigger_pct*100:.0f}% + {breakeven_buffer_pips} pips") logger.info(f" Multi-tier: {tier1_trigger*100:.0f}%/{tier2_trigger*100:.0f}%/{tier3_trigger*100:.0f}%") logger.info(f" ATR Trailing: {'✅' if use_atr_trailing else '❌'}") logger.info(f" Time-based BE: {'✅' if time_based_breakeven else '❌'} ({hours_to_breakeven}h)") # ========================================== # MAIN LOGIC # ========================================== def should_update_trailing_stop(self, position, current_session: str = 'asian', atr_value: Optional[float] = None) -> Tuple[bool, Optional[float], str]: """ Prüft ob Trailing Stop aktualisiert werden sollte Args: position: MT5 Position Object current_session: Aktuelle Session (für session-aware trailing) atr_value: Aktueller ATR (für ATR-based trailing) Returns: (should_update, new_sl_price, reason) """ try: ticket = position.ticket position_type = position.type # 0=BUY, 1=SELL entry_price = position.price_open current_sl = position.sl tp = position.tp entry_time = datetime.fromtimestamp(position.time) # 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 point = mt.symbol_info(position.symbol).point # Calculate progress 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_pct = current_distance / tp_distance trade_age_hours = (datetime.now() - entry_time).total_seconds() / 3600 # ========================================== # 1. TIME-BASED BREAKEVEN # ========================================== if self.time_based_be and trade_age_hours >= self.hours_to_be: if current_distance > 0: # In profit new_sl = entry_price + (self.breakeven_buffer_pips * point if position_type == 0 else -self.breakeven_buffer_pips * point) if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point): return True, new_sl, f"Time-based BE after {trade_age_hours:.1f}h" # ========================================== # 2. EARLY BREAKEVEN (30% statt 50%) # ========================================== if progress_pct >= self.breakeven_trigger: if position_type == 0: # BUY new_sl = entry_price + (self.breakeven_buffer_pips * point) else: # SELL new_sl = entry_price - (self.breakeven_buffer_pips * point) if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point): return True, new_sl, f"Early BE at {progress_pct*100:.1f}% (+{self.breakeven_buffer_pips} pips buffer)" # ========================================== # 3. MULTI-TIER PROFIT LOCKING # ========================================== # Get current tier current_tier = self.position_tiers.get(ticket, 0) # Tier 3 (90%) if progress_pct >= self.tier3_trigger and current_tier < 3: locked_profit = tp_distance * self.tier3_lock if position_type == 0: # BUY new_sl = entry_price + locked_profit else: # SELL new_sl = entry_price - locked_profit # ATR-based trailing wenn verfügbar if self.use_atr_trailing and atr_value: session_mult = self.session_multipliers.get(current_session, 1.0) atr_distance = atr_value * self.atr_multiplier * session_mult if position_type == 0: new_sl = max(new_sl, current_price - atr_distance) else: new_sl = min(new_sl, current_price + atr_distance) if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point): self.position_tiers[ticket] = 3 return True, new_sl, f"Tier 3: Locking {self.tier3_lock*100:.0f}% profit at {progress_pct*100:.1f}%" # Tier 2 (75%) if progress_pct >= self.tier2_trigger and current_tier < 2: locked_profit = tp_distance * self.tier2_lock if position_type == 0: # BUY new_sl = entry_price + locked_profit else: # SELL new_sl = entry_price - locked_profit # ATR-based trailing if self.use_atr_trailing and atr_value: session_mult = self.session_multipliers.get(current_session, 1.0) atr_distance = atr_value * self.atr_multiplier * session_mult if position_type == 0: new_sl = max(new_sl, current_price - atr_distance) else: new_sl = min(new_sl, current_price + atr_distance) if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point): self.position_tiers[ticket] = 2 return True, new_sl, f"Tier 2: Locking {self.tier2_lock*100:.0f}% profit at {progress_pct*100:.1f}%" # Tier 1 (50%) if progress_pct >= self.tier1_trigger and current_tier < 1: locked_profit = tp_distance * self.tier1_lock if position_type == 0: # BUY new_sl = entry_price + locked_profit else: # SELL new_sl = entry_price - locked_profit if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point): self.position_tiers[ticket] = 1 return True, new_sl, f"Tier 1: Locking {self.tier1_lock*100:.0f}% profit at {progress_pct*100:.1f}%" # ========================================== # 4. AGGRESSIVE ATR TRAILING (wenn Tier 3) # ========================================== if current_tier >= 3 and self.use_atr_trailing and atr_value: session_mult = self.session_multipliers.get(current_session, 1.0) atr_distance = atr_value * 0.5 * session_mult # Tighter: 0.5 × ATR if position_type == 0: # BUY new_sl = current_price - atr_distance else: # SELL new_sl = current_price + atr_distance if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point): return True, new_sl, f"Aggressive ATR trail (0.5 × ATR)" return False, None, f"Progress {progress_pct*100:.1f}% (Tier {current_tier})" except Exception as e: logger.error(f"Error checking trailing stop: {e}") return False, None, str(e) # ========================================== # HELPER FUNCTIONS # ========================================== def _is_valid_sl_update(self, position_type: int, current_price: float, new_sl: float, current_sl: float, point: float) -> bool: """ Validiert SL Update Checks: - Minimum distance - No backward movement """ # Check minimum distance if position_type == 0: # BUY distance_points = (current_price - new_sl) / point else: # SELL distance_points = (new_sl - current_price) / point if distance_points < self.min_distance: logger.debug(f"Distance too small: {distance_points:.0f} < {self.min_distance}") return False # Don't move SL backwards if current_sl > 0: if position_type == 0: # BUY if new_sl <= current_sl: logger.debug(f"Would move SL backwards: {new_sl} <= {current_sl}") return False else: # SELL if new_sl >= current_sl: logger.debug(f"Would move SL backwards: {new_sl} >= {current_sl}") return False return True 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": "Enhanced Trailing" } result = mt.order_send(request) if result.retcode == mt.TRADE_RETCODE_DONE: logger.info(f"✅ Enhanced Trailing Stop updated for #{position.ticket}") logger.info(f" Old SL: {position.sl:.5f}") logger.info(f" New SL: {new_sl:.5f}") logger.info(f" Buffer: {abs(new_sl - position.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 def cleanup_closed_positions(self): """Entfernt geschlossene Positions aus Tier-Tracking""" open_tickets = {pos.ticket for pos in mt.positions_get()} closed_tickets = set(self.position_tiers.keys()) - open_tickets for ticket in closed_tickets: del self.position_tiers[ticket] if closed_tickets: logger.info(f"🧹 Cleaned up {len(closed_tickets)} closed position(s) from tier tracking") # ========================================== # INTEGRATION HELPER # ========================================== def create_enhanced_position_monitor( trailing_manager: EnhancedTrailingStopManager, rhythm_manager, symbol: str = "XAUUSD" ): """ Factory für Enhanced Position Monitor Args: trailing_manager: EnhancedTrailingStopManager Instanz rhythm_manager: AdaptiveRhythmManager (für Session) symbol: Trading Symbol Returns: Monitor Function (für Scheduler) """ def enhanced_position_monitor(): """ Überwacht Positionen mit Enhanced Trailing Features: - Session-aware Trailing - ATR-based Dynamic Trailing - Multi-tier Profit Locking - Time-based Breakeven """ try: positions = mt.positions_get(symbol=symbol) if not positions: return # Get current session session = rhythm_manager.get_current_session() # Get current ATR atr_value = None try: rates = mt.copy_rates_from_pos(symbol, mt.TIMEFRAME_M5, 0, 20) if rates is not None: import pandas as pd df = pd.DataFrame(rates) df['tr'] = df[['high', 'low', 'close']].apply( lambda x: max(x['high'] - x['low'], abs(x['high'] - x['close']), abs(x['low'] - x['close'])), axis=1 ) atr_value = df['tr'].rolling(14).mean().iloc[-1] except Exception as e: logger.debug(f"Could not calculate ATR: {e}") logger.info(f"\n🔍 Enhanced Position Monitor - {len(positions)} position(s)") atr_display = f"{atr_value:.5f}" if atr_value else "N/A" logger.info(f" Session: {session.upper()} | ATR: {atr_display}") for position in positions: should_update, new_sl, reason = trailing_manager.should_update_trailing_stop( position, current_session=session, atr_value=atr_value ) if should_update: logger.info(f"📈 Trailing Trigger for #{position.ticket}: {reason}") trailing_manager.update_stop_loss(position, new_sl) else: logger.debug(f"⏸️ No update: {reason}") # Cleanup trailing_manager.cleanup_closed_positions() except Exception as e: logger.error(f"Error in enhanced position monitor: {e}") return enhanced_position_monitor # ========================================== # USAGE EXAMPLE # ========================================== """ INTEGRATION IN NOTEBOOK: # Cell: Setup Enhanced Trailing Stop from enhanced_trailing_stop import EnhancedTrailingStopManager, create_enhanced_position_monitor # Initialize Manager enhanced_trailing = EnhancedTrailingStopManager( breakeven_trigger_pct=0.30, # Früher BE (30% statt 50%) breakeven_buffer_pips=5, # +5 Pips über BE tier1_trigger=0.50, # Multi-tier Locking tier1_lock_pct=0.25, tier2_trigger=0.75, tier2_lock_pct=0.50, tier3_trigger=0.90, tier3_lock_pct=0.75, use_atr_trailing=True, # ATR-based Trailing atr_multiplier=1.0, time_based_breakeven=True, # Time-based BE hours_to_breakeven=4.0, session_trailing_multipliers={ # Session-aware 'asian': 1.0, 'ny': 1.5, 'london': 1.2, 'overlap': 1.3 } ) print("✅ Enhanced Trailing Stop Manager activated!") # Cell: Add to Scheduler # Remove old trailing stop if exists try: scheduler.remove_job('advanced_position_management') except: pass # Add enhanced version enhanced_monitor = create_enhanced_position_monitor( enhanced_trailing, rhythm_manager, symbol="XAUUSD" ) scheduler.add_job( func=enhanced_monitor, trigger='interval', minutes=1, id='enhanced_trailing_stop' ) print("✅ Enhanced Trailing Stop scheduler added (checks every 1 min)") """