mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-28 16:07:46 +00:00
421a3889fa
Implement automatic trading protection system to prevent excessive losses: PROTECTIONS (100% original code, NOT copied from Freqtrade): - Max Drawdown Protection: Blocks trading when DD > 15% (configurable) - Cooldown Period: 4h mandatory rest after 5% loss - Stoploss Guard: Detects stoploss clusters (>5 per day) - Low Performance Filter: Filters factors with Sharpe < 0.5, Win Rate < 40% ARCHITECTURE: - Base protection interface with common utilities - 4 specialized protection implementations - ProtectionManager orchestrates all active protections - Time-based blocking with automatic expiry TESTS (32 total, ALL PASS): - 25 unit tests in test/backtesting/test_protections.py - 7 integration tests in test/integration/test_all_features.py - Tests cover: normal operation, edge cases, error handling DOCUMENTATION: - Update QWEN.md with development guidelines for AI assistant * Mandatory rules: Update QWEN.md, README, requirements.txt, tests * Pre-commit checklist * Example workflow - Update README.md with protection system features - Update project structure with new modules All code is 100% original - NO license issues with Freqtrade GPLv3.
146 lines
4.3 KiB
Python
146 lines
4.3 KiB
Python
"""
|
|
Trading Protection System
|
|
|
|
Prevents excessive losses by automatically pausing trading when risk thresholds are exceeded.
|
|
|
|
Inspired by common trading protection patterns, implemented from scratch for Predix.
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
from enum import Enum
|
|
|
|
|
|
class ProtectionType(Enum):
|
|
"""Type of protection mechanism."""
|
|
MAX_DRAWDOWN = "max_drawdown"
|
|
COOLDOWN = "cooldown"
|
|
STOPLOSS_GUARD = "stoploss_guard"
|
|
LOW_PERFORMANCE = "low_performance"
|
|
|
|
|
|
class ProtectionScope(Enum):
|
|
"""What scope does this protection apply to?"""
|
|
GLOBAL = "global" # All trading
|
|
FACTOR = "factor" # Per-factor
|
|
PORTFOLIO = "portfolio" # Per portfolio
|
|
|
|
|
|
@dataclass
|
|
class ProtectionResult:
|
|
"""Result of a protection check."""
|
|
should_block: bool # True if trading should be blocked
|
|
reason: str # Why was it blocked?
|
|
until: Optional[datetime] = None # If time-based, when does it expire?
|
|
protection_type: Optional[ProtectionType] = None
|
|
severity: float = 0.0 # How severe is the issue (0-1)
|
|
|
|
@property
|
|
def is_active(self) -> bool:
|
|
"""Check if protection is currently active."""
|
|
if not self.until:
|
|
return self.should_block
|
|
return datetime.now() < self.until and self.should_block
|
|
|
|
|
|
@dataclass
|
|
class ProtectionConfig:
|
|
"""Base configuration for a protection."""
|
|
enabled: bool = True
|
|
lookback_period_hours: int = 24 # How far back to look
|
|
severity_threshold: float = 0.8 # At what severity to block
|
|
|
|
|
|
class BaseProtection(ABC):
|
|
"""
|
|
Base class for all trading protections.
|
|
|
|
Each protection checks specific conditions and returns ProtectionResult.
|
|
Multiple protections can be combined in ProtectionManager.
|
|
"""
|
|
|
|
def __init__(self, config: ProtectionConfig):
|
|
self.config = config
|
|
self.last_check: Optional[datetime] = None
|
|
self.total_checks: int = 0
|
|
self.total_blocks: int = 0
|
|
|
|
@abstractmethod
|
|
def check(
|
|
self,
|
|
returns: list[float],
|
|
timestamps: list[datetime],
|
|
current_equity: float,
|
|
peak_equity: float,
|
|
**kwargs
|
|
) -> ProtectionResult:
|
|
"""
|
|
Check if protection should be triggered.
|
|
|
|
Parameters
|
|
----------
|
|
returns : list[float]
|
|
Historical returns in lookback period
|
|
timestamps : list[datetime]
|
|
Timestamps of returns
|
|
current_equity : float
|
|
Current equity value
|
|
peak_equity : float
|
|
Peak equity value (highest ever)
|
|
**kwargs
|
|
Additional context (factor name, portfolio ID, etc.)
|
|
|
|
Returns
|
|
-------
|
|
ProtectionResult
|
|
Decision on whether to block trading
|
|
"""
|
|
pass
|
|
|
|
def calculate_drawdown(self, current: float, peak: float) -> float:
|
|
"""Calculate drawdown percentage (negative value)."""
|
|
if peak == 0:
|
|
return 0.0
|
|
return (current - peak) / peak
|
|
|
|
def calculate_max_consecutive_losses(self, returns: list[float]) -> int:
|
|
"""Find maximum consecutive losing trades."""
|
|
max_losses = 0
|
|
current_losses = 0
|
|
|
|
for ret in returns:
|
|
if ret < 0:
|
|
current_losses += 1
|
|
max_losses = max(max_losses, current_losses)
|
|
else:
|
|
current_losses = 0
|
|
|
|
return max_losses
|
|
|
|
def calculate_recent_loss_rate(self, returns: list[float]) -> float:
|
|
"""Calculate percentage of losing trades."""
|
|
if not returns:
|
|
return 0.0
|
|
losses = sum(1 for r in returns if r < 0)
|
|
return losses / len(returns)
|
|
|
|
def record_check(self, blocked: bool = False):
|
|
"""Record that a check was performed."""
|
|
self.total_checks += 1
|
|
self.last_check = datetime.now()
|
|
if blocked:
|
|
self.total_blocks += 1
|
|
|
|
def get_stats(self) -> dict:
|
|
"""Get protection statistics."""
|
|
return {
|
|
"type": self.__class__.__name__,
|
|
"enabled": self.config.enabled,
|
|
"total_checks": self.total_checks,
|
|
"total_blocks": self.total_blocks,
|
|
"block_rate": self.total_blocks / max(1, self.total_checks),
|
|
"last_check": self.last_check.isoformat() if self.last_check else None
|
|
}
|