"""Risk tier auto-adjustment controller for the autonomous trading engine. Pure computation module — no DB access. Persistence of tier changes is handled by the caller. All methods operate on values passed in as arguments and return deterministic results. Tier ordering: conservative → moderate → aggressive """ from __future__ import annotations from services.trading.models import PerformanceMetrics # Ordered from lowest to highest risk. TIER_ORDER: list[str] = ["conservative", "moderate", "aggressive"] class RiskTierController: """Evaluates performance metrics and determines whether the active risk tier should change. Downgrade conditions (any one triggers a downgrade by one level): - Trailing 30-day win rate < 40% - Current drawdown > 15% Upgrade conditions (ALL must be true): - Trailing 30-day win rate > 55% - Reserve pool > 20% of total portfolio - Current drawdown < 5% """ def __init__(self) -> None: # No configuration needed — uses TIER_ORDER for ordering. pass def evaluate( self, current_tier: str, metrics: PerformanceMetrics, reserve_pct: float, ) -> str | None: """Evaluate whether the tier should change based on performance. Parameters ---------- current_tier: The currently active tier name (e.g. ``"moderate"``). metrics: Latest portfolio performance metrics. reserve_pct: Reserve pool balance as a fraction of total portfolio value (e.g. 0.25 means 25%). Returns ------- str | None The new tier name if a change is needed, or ``None`` if the current tier should remain. """ current_index = TIER_ORDER.index(current_tier) # --- Downgrade check (any condition triggers) --- should_downgrade = ( metrics.win_rate < 0.40 or metrics.current_drawdown_pct > 0.15 ) if should_downgrade: if current_index > 0: return TIER_ORDER[current_index - 1] # Already at the lowest tier — no change. return None # --- Upgrade check (all conditions must be true) --- should_upgrade = ( metrics.win_rate > 0.55 and reserve_pct > 0.20 and metrics.current_drawdown_pct < 0.05 ) if should_upgrade: if current_index < len(TIER_ORDER) - 1: return TIER_ORDER[current_index + 1] # Already at the highest tier — no change. return None # Neither condition met — stay at current tier. return None