2025-12-16 22:02:15 +01:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""
|
|
|
|
|
|
🛡️ Drawdown Protection System
|
|
|
|
|
|
Schützt vor übermäßigen Verlusten durch automatische Handels-Pausen
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-05-12 12:25:11 +02:00
|
|
|
|
from datetime import datetime, timedelta, timezone
|
2025-12-16 22:02:15 +01:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-12 12:25:11 +02:00
|
|
|
|
# State (loaded from DB so it survives restarts)
|
2025-12-16 22:02:15 +01:00
|
|
|
|
self.trading_paused = False
|
|
|
|
|
|
self.pause_until = None
|
|
|
|
|
|
self.pause_reason = None
|
2026-05-12 12:25:11 +02:00
|
|
|
|
self._load_state()
|
2025-12-16 22:02:15 +01:00
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
|
|
2026-05-12 12:25:11 +02:00
|
|
|
|
def _load_state(self):
|
|
|
|
|
|
"""Load persisted pause state from DB on startup"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
pause_until_str = self.db.load_setting('drawdown_pause_until')
|
|
|
|
|
|
pause_reason = self.db.load_setting('drawdown_pause_reason')
|
|
|
|
|
|
if pause_until_str:
|
|
|
|
|
|
pause_until = datetime.fromisoformat(pause_until_str)
|
|
|
|
|
|
if pause_until > datetime.now():
|
|
|
|
|
|
self.trading_paused = True
|
|
|
|
|
|
self.pause_until = pause_until
|
|
|
|
|
|
self.pause_reason = pause_reason
|
|
|
|
|
|
logger.info(f"Loaded active pause from DB: {pause_reason} until {pause_until}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._clear_persisted_state()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Error loading drawdown state from DB: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def _clear_persisted_state(self):
|
|
|
|
|
|
"""Clear pause state from DB"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.db.save_setting('drawdown_pause_until', '')
|
|
|
|
|
|
self.db.save_setting('drawdown_pause_reason', '')
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Error clearing drawdown state: {e}")
|
|
|
|
|
|
|
2025-12-16 22:02:15 +01:00
|
|
|
|
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}")
|
2026-05-12 12:25:11 +02:00
|
|
|
|
return float('inf') # safe: block trading when DB unreachable
|
2025-12-16 22:02:15 +01:00
|
|
|
|
|
|
|
|
|
|
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}")
|
2026-05-12 12:25:11 +02:00
|
|
|
|
return float('inf')
|
2025-12-16 22:02:15 +01:00
|
|
|
|
|
|
|
|
|
|
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}")
|
2026-05-12 12:25:11 +02:00
|
|
|
|
return float('inf')
|
2025-12-16 22:02:15 +01:00
|
|
|
|
|
|
|
|
|
|
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}")
|
|
|
|
|
|
|
2026-05-12 12:25:11 +02:00
|
|
|
|
# Persist so pause survives a restart
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.db.save_setting('drawdown_pause_until', self.pause_until.isoformat())
|
|
|
|
|
|
self.db.save_setting('drawdown_pause_reason', reason)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Error persisting drawdown pause state: {e}")
|
|
|
|
|
|
|
2025-12-16 22:02:15 +01:00
|
|
|
|
if self.telegram:
|
|
|
|
|
|
self.telegram.send_message(
|
2026-05-12 12:25:11 +02:00
|
|
|
|
f"🛑 *TRADING PAUSED*\n\n"
|
2025-12-16 22:02:15 +01:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-12 12:25:11 +02:00
|
|
|
|
self._clear_persisted_state()
|
2025-12-16 22:02:15 +01:00
|
|
|
|
logger.info(f"✅ Trading resumed after: {previous_reason}")
|
|
|
|
|
|
|
|
|
|
|
|
if self.telegram:
|
|
|
|
|
|
self.telegram.send_message(
|
2026-05-12 12:25:11 +02:00
|
|
|
|
f"✅ *TRADING RESUMED*\n\n"
|
2025-12-16 22:02:15 +01:00
|
|
|
|
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()
|
|
|
|
|
|
"""
|