#!/usr/bin/env python3 """ 🛡️ Drawdown Protection System Schützt vor übermäßigen Verlusten durch automatische Handels-Pausen """ from datetime import datetime, timedelta from trading_database import TradingDatabase from telegram_notifier import TelegramNotifier import logging logger = logging.getLogger(__name__) class DrawdownProtection: """ Überwacht Verluste und pausiert Trading bei Überschreitung von Limits """ def __init__(self, database: TradingDatabase, telegram: TelegramNotifier = None, max_daily_loss: float = 100.0, max_weekly_loss: float = 300.0, max_monthly_loss: float = 800.0, max_consecutive_losses: int = 5, cooldown_hours: int = 24): """ Args: database: TradingDatabase instance telegram: TelegramNotifier instance max_daily_loss: Maximaler Tagesverlust in $ max_weekly_loss: Maximaler Wochenverlust in $ max_monthly_loss: Maximaler Monatsverlust in $ max_consecutive_losses: Maximale Anzahl aufeinanderfolgender Verluste cooldown_hours: Pause in Stunden nach Limit-Überschreitung """ self.db = database self.telegram = telegram # Limits self.max_daily_loss = max_daily_loss self.max_weekly_loss = max_weekly_loss self.max_monthly_loss = max_monthly_loss self.max_consecutive_losses = max_consecutive_losses self.cooldown_hours = cooldown_hours # State self.trading_paused = False self.pause_until = None self.pause_reason = None def can_trade(self) -> tuple[bool, str]: """ Prüft ob Trading erlaubt ist Returns: (can_trade: bool, reason: str) """ # Prüfe ob Pause noch aktiv if self.trading_paused and self.pause_until: if datetime.now() < self.pause_until: remaining = (self.pause_until - datetime.now()).total_seconds() / 3600 return False, f"Trading paused for {remaining:.1f} more hours. Reason: {self.pause_reason}" else: # Pause abgelaufen self._resume_trading() # Prüfe täglichen Verlust daily_loss = self._get_loss_today() if daily_loss >= self.max_daily_loss: self._pause_trading( f"Daily loss limit reached: ${daily_loss:.2f} / ${self.max_daily_loss:.2f}", hours=self.cooldown_hours ) return False, f"Daily loss limit: ${daily_loss:.2f}" # Prüfe wöchentlichen Verlust weekly_loss = self._get_loss_this_week() if weekly_loss >= self.max_weekly_loss: self._pause_trading( f"Weekly loss limit reached: ${weekly_loss:.2f} / ${self.max_weekly_loss:.2f}", hours=self.cooldown_hours * 2 ) return False, f"Weekly loss limit: ${weekly_loss:.2f}" # Prüfe monatlichen Verlust monthly_loss = self._get_loss_this_month() if monthly_loss >= self.max_monthly_loss: self._pause_trading( f"Monthly loss limit reached: ${monthly_loss:.2f} / ${self.max_monthly_loss:.2f}", hours=self.cooldown_hours * 7 ) return False, f"Monthly loss limit: ${monthly_loss:.2f}" # Prüfe aufeinanderfolgende Verluste consecutive = self._get_consecutive_losses() if consecutive >= self.max_consecutive_losses: self._pause_trading( f"Too many consecutive losses: {consecutive} in a row", hours=self.cooldown_hours ) return False, f"Consecutive losses: {consecutive}" return True, "OK" def _get_loss_today(self) -> float: """Berechnet Verlust heute""" try: today = datetime.now().strftime('%Y-%m-%d') conn = self.db.conn cursor = conn.cursor() cursor.execute(""" SELECT COALESCE(SUM(net_profit), 0) FROM trades WHERE DATE(exit_time) = ? AND status = 'closed' AND net_profit < 0 """, (today,)) loss = cursor.fetchone()[0] return abs(loss) if loss else 0.0 except Exception as e: logger.error(f"Error calculating daily loss: {e}") return 0.0 def _get_loss_this_week(self) -> float: """Berechnet Verlust diese Woche""" try: week_start = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d') conn = self.db.conn cursor = conn.cursor() cursor.execute(""" SELECT COALESCE(SUM(net_profit), 0) FROM trades WHERE DATE(exit_time) >= ? AND status = 'closed' AND net_profit < 0 """, (week_start,)) loss = cursor.fetchone()[0] return abs(loss) if loss else 0.0 except Exception as e: logger.error(f"Error calculating weekly loss: {e}") return 0.0 def _get_loss_this_month(self) -> float: """Berechnet Verlust diesen Monat""" try: month_start = datetime.now().replace(day=1).strftime('%Y-%m-%d') conn = self.db.conn cursor = conn.cursor() cursor.execute(""" SELECT COALESCE(SUM(net_profit), 0) FROM trades WHERE DATE(exit_time) >= ? AND status = 'closed' AND net_profit < 0 """, (month_start,)) loss = cursor.fetchone()[0] return abs(loss) if loss else 0.0 except Exception as e: logger.error(f"Error calculating monthly loss: {e}") return 0.0 def _get_consecutive_losses(self) -> int: """Zählt aufeinanderfolgende Verluste""" try: conn = self.db.conn cursor = conn.cursor() cursor.execute(""" SELECT net_profit FROM trades WHERE status = 'closed' ORDER BY exit_time DESC LIMIT 20 """) trades = cursor.fetchall() consecutive = 0 for trade in trades: if trade[0] < 0: consecutive += 1 else: break return consecutive except Exception as e: logger.error(f"Error counting consecutive losses: {e}") return 0 def _pause_trading(self, reason: str, hours: int): """Pausiert Trading""" self.trading_paused = True self.pause_until = datetime.now() + timedelta(hours=hours) self.pause_reason = reason logger.warning(f"🛑 Trading paused: {reason}") logger.warning(f" Resuming at: {self.pause_until}") if self.telegram: self.telegram.send_message( f"🛑 **TRADING PAUSED**\n\n" f"Reason: {reason}\n" f"Duration: {hours} hours\n" f"Resume at: {self.pause_until.strftime('%Y-%m-%d %H:%M')}\n\n" f"ℹ️ Review your strategy and market conditions." ) def _resume_trading(self): """Setzt Trading fort""" self.trading_paused = False self.pause_until = None previous_reason = self.pause_reason self.pause_reason = None logger.info(f"✅ Trading resumed after: {previous_reason}") if self.telegram: self.telegram.send_message( f"✅ **TRADING RESUMED**\n\n" f"Previous pause reason: {previous_reason}\n" f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M')}" ) def get_status(self) -> dict: """Gibt aktuellen Status zurück""" return { 'trading_allowed': not self.trading_paused, 'paused': self.trading_paused, 'pause_reason': self.pause_reason, 'pause_until': self.pause_until.isoformat() if self.pause_until else None, 'daily_loss': self._get_loss_today(), 'daily_limit': self.max_daily_loss, 'weekly_loss': self._get_loss_this_week(), 'weekly_limit': self.max_weekly_loss, 'monthly_loss': self._get_loss_this_month(), 'monthly_limit': self.max_monthly_loss, 'consecutive_losses': self._get_consecutive_losses(), 'consecutive_limit': self.max_consecutive_losses } def force_pause(self, reason: str, hours: int = 24): """Manuelles Pausieren""" self._pause_trading(reason, hours) def force_resume(self): """Manuelles Fortsetzen""" self._resume_trading() # ========================================== # INTEGRATION WRAPPER # ========================================== def create_protected_trading_check(infra, original_check_function): """ Wrapper der Drawdown Protection um Trading Check legt Usage: # Im Notebook: from drawdown_protection import DrawdownProtection, create_protected_trading_check # Erstelle Protection drawdown_protection = DrawdownProtection( database=infra.db, telegram=infra.telegram, max_daily_loss=100, max_weekly_loss=300, max_consecutive_losses=5 ) # Wrapping original_trading_check = trading_check trading_check = create_protected_trading_check( infra, original_trading_check ) """ # Create drawdown protection instance protection = DrawdownProtection( database=infra.db, telegram=infra.telegram, max_daily_loss=100, # $100 per day max_weekly_loss=300, # $300 per week max_monthly_loss=800, # $800 per month max_consecutive_losses=5, cooldown_hours=24 ) def protected_check(): """Geschützter Trading Check""" # Prüfe Drawdown Protection can_trade, reason = protection.can_trade() if not can_trade: logger.warning(f"Trading blocked by drawdown protection: {reason}") return # Wenn OK, führe normalen Check aus return original_check_function() # Attach protection instance for access protected_check.protection = protection return protected_check # ========================================== # MONITORING HELPER # ========================================== def print_protection_status(protection: DrawdownProtection): """Gibt Protection Status formatiert aus""" status = protection.get_status() print("=" * 70) print("🛡️ DRAWDOWN PROTECTION STATUS") print("=" * 70) if status['trading_allowed']: print("\n✅ Trading ALLOWED") else: print("\n🛑 Trading PAUSED") print(f" Reason: {status['pause_reason']}") print(f" Until: {status['pause_until']}") print("\n📊 Current Losses:") print(f" Daily: ${status['daily_loss']:.2f} / ${status['daily_limit']:.2f}") print(f" Weekly: ${status['weekly_loss']:.2f} / ${status['weekly_limit']:.2f}") print(f" Monthly: ${status['monthly_loss']:.2f} / ${status['monthly_limit']:.2f}") print(f"\n📉 Consecutive Losses: {status['consecutive_losses']} / {status['consecutive_limit']}") print("=" * 70) # ========================================== # USAGE EXAMPLE # ========================================== """ INTEGRATION IN NOTEBOOK: # Cell: Drawdown Protection Setup (nach Infrastructure) from drawdown_protection import DrawdownProtection, create_protected_trading_check, print_protection_status # Erstelle Drawdown Protection drawdown_protection = DrawdownProtection( database=infra.db, telegram=infra.telegram, max_daily_loss=100, # $100/Tag max_weekly_loss=300, # $300/Woche max_monthly_loss=800, # $800/Monat max_consecutive_losses=5, cooldown_hours=24 ) print("✅ Drawdown Protection aktiviert") print(f" Max Daily Loss: ${drawdown_protection.max_daily_loss}") print(f" Max Weekly Loss: ${drawdown_protection.max_weekly_loss}") print(f" Max Consecutive Losses: {drawdown_protection.max_consecutive_losses}") # Wrapper um trading_check original_trading_check = trading_check trading_check = create_protected_trading_check(infra, original_trading_check) print("✅ Trading Check ist jetzt geschützt!") # Cell: Status prüfen (optional, jederzeit ausführbar) print_protection_status(drawdown_protection) # Cell: Manuell pausieren/fortsetzen (optional) # Manuell pausieren: # drawdown_protection.force_pause("Manual review needed", hours=12) # Manuell fortsetzen: # drawdown_protection.force_resume() """