Files
Place-Order-Trading-Bot/drawdown_protection.py
T

398 lines
13 KiB
Python
Raw Normal View History

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
"""
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()
"""