Files
NexQuant/rdagent/components/backtesting/protections/max_drawdown.py
T

76 lines
2.5 KiB
Python
Raw Normal View History

"""
Maximum Drawdown Protection
Blocks trading when portfolio drawdown exceeds threshold.
"""
from dataclasses import dataclass
from datetime import datetime, timedelta
from .base import BaseProtection, ProtectionConfig, ProtectionResult, ProtectionType, ProtectionScope
@dataclass
class MaxDrawdownConfig(ProtectionConfig):
"""Configuration for MaxDrawdown protection."""
max_drawdown_pct: float = 0.15 # Block if drawdown > 15%
class MaxDrawdownProtection(BaseProtection):
"""
Blocks trading when drawdown exceeds safe threshold.
This prevents the system from continuing to trade during a losing streak,
giving the market time to stabilize.
"""
def __init__(self, config: MaxDrawdownConfig):
super().__init__(config)
self.config: MaxDrawdownConfig = config
@property
def scope(self) -> ProtectionScope:
return ProtectionScope.GLOBAL
def check(
self,
returns: list[float],
timestamps: list[datetime],
current_equity: float,
peak_equity: float,
**kwargs
) -> ProtectionResult:
"""Check if drawdown exceeds threshold."""
self.record_check()
if not self.config.enabled:
return ProtectionResult(
should_block=False,
reason="Protection disabled",
protection_type=ProtectionType.MAX_DRAWDOWN
)
drawdown = self.calculate_drawdown(current_equity, peak_equity)
severity = abs(drawdown) / self.config.max_drawdown_pct if self.config.max_drawdown_pct > 0 else 0
if abs(drawdown) > self.config.max_drawdown_pct:
# Calculate block duration based on severity
block_hours = int(self.config.lookback_period_hours * severity)
block_hours = min(block_hours, 168) # Max 1 week
result = ProtectionResult(
should_block=True,
reason=f"Drawdown {abs(drawdown)*100:.1f}% exceeds max {self.config.max_drawdown_pct*100:.1f}%",
until=datetime.now() + timedelta(hours=block_hours),
protection_type=ProtectionType.MAX_DRAWDOWN,
severity=severity
)
self.record_check(blocked=True)
return result
return ProtectionResult(
should_block=False,
reason=f"Drawdown {abs(drawdown)*100:.1f}% within safe range",
protection_type=ProtectionType.MAX_DRAWDOWN,
severity=severity
)