diff --git a/enhanced_trailing_stop.py b/enhanced_trailing_stop.py new file mode 100644 index 0000000..505a436 --- /dev/null +++ b/enhanced_trailing_stop.py @@ -0,0 +1,503 @@ +#!/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)") + logger.info(f" Session: {session.upper()} | ATR: {atr_value:.5f if atr_value else 'N/A'}") + + 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)") +"""