""" ml/position_sizer.py — Probabilistic position sizing. Sizes positions based on: 1. ML signal confidence (P(win)) 2. Regime volatility context 3. Rolling Sharpe-based scaling 4. Drawdown-based reduction (from original system) """ from __future__ import annotations import logging import numpy as np import pandas as pd logger = logging.getLogger("trading_system.ml.position_sizer") class ProbabilisticSizer: """Position sizing that scales with ML probability edge. Core formula: base_risk = equity * risk_per_trade_pct edge_scale = (P(win) - 0.5) * edge_mult (capped) vol_scale = avg_vol / current_vol (inverse vol) dd_scale = drawdown reduction final_risk = base_risk * (1 + edge_scale) * vol_scale * dd_scale Safeguards: - Min size: 25% of base risk (never go below this) - Max size: 200% of base risk (never exceed 2x) - Hard cap: max_risk_pct of equity per trade """ def __init__( self, base_risk_pct: float = 1.0, edge_multiplier: float = 2.0, max_scale: float = 2.0, min_scale: float = 0.25, max_risk_pct: float = 2.5, dd_reduction_start: float = 3.0, # start reducing at 3% DD dd_reduction_severe: float = 5.0, # halve at 5% DD ): self.base_risk_pct = base_risk_pct self.edge_multiplier = edge_multiplier self.max_scale = max_scale self.min_scale = min_scale self.max_risk_pct = max_risk_pct self.dd_reduction_start = dd_reduction_start self.dd_reduction_severe = dd_reduction_severe def compute_position_risk( self, equity: float, peak_equity: float, ml_confidence: float, conf_multiplier: float, atr_current: float, atr_avg: float, regime_vol: float = 0.0, strategy_risk_scale: float = 1.0, ) -> float: """Compute dollar risk for a single trade. Args: equity: Current portfolio equity. peak_equity: Peak equity for DD calculation. ml_confidence: ML model confidence [0, 1]. conf_multiplier: Rule-based confidence multiplier [0.5, 1.5]. atr_current: Current ATR. atr_avg: Average ATR (e.g. 50-bar). regime_vol: Current regime volatility level. strategy_risk_scale: Strategy-specific scale (e.g. 0.6 for MR). Returns: Dollar risk amount for this trade. """ base_risk = equity * (self.base_risk_pct / 100.0) # 1. ML edge scaling: conservative — only small bonus for high confidence edge = (ml_confidence - 0.5) * self.edge_multiplier # only > 50% gets bonus edge = max(-0.3, min(0.5, edge)) # tight clamp edge_scale = 1.0 + edge # 2. Inverse volatility scaling if atr_avg > 0 and atr_current > 0: vol_scale = atr_avg / atr_current vol_scale = max(0.5, min(1.5, vol_scale)) else: vol_scale = 1.0 # 3. Drawdown-based reduction dd_pct = ((peak_equity - equity) / peak_equity * 100) if peak_equity > 0 else 0 if dd_pct >= self.dd_reduction_severe: dd_scale = 0.5 elif dd_pct >= self.dd_reduction_start: # Linear interpolation from 1.0 at start to 0.5 at severe frac = (dd_pct - self.dd_reduction_start) / (self.dd_reduction_severe - self.dd_reduction_start) dd_scale = 1.0 - 0.5 * frac else: dd_scale = 1.0 # 4. Strategy-specific scaling + rule confidence final_risk = (base_risk * edge_scale * vol_scale * dd_scale * conf_multiplier * strategy_risk_scale) # Clamp to [min, max] of base risk final_risk = max(base_risk * self.min_scale, min(base_risk * self.max_scale, final_risk)) # Hard cap hard_cap = equity * (self.max_risk_pct / 100.0) final_risk = min(final_risk, hard_cap) return max(0.0, final_risk)