feat: math core v3 engine upgrade
This commit is contained in:
@@ -168,3 +168,151 @@ def classify_regime(
|
||||
bearish_threshold=-threshold,
|
||||
contradiction_penalty_multiplier=contradiction_mult,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# V3 Regime Detection — Calibrated Evidence Engine
|
||||
# Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class V3RegimeClassification:
|
||||
"""V3 regime classification result with calibrated parameters.
|
||||
|
||||
Attributes:
|
||||
regime: Market regime category.
|
||||
trend_z: ATR-normalized trend indicator (EMA_20 - EMA_100) / ATR_20.
|
||||
vol_ratio: Volatility ratio sigma_20 / sigma_100.
|
||||
evidence_multiplier: Regime-specific gamma for posterior LLR scaling.
|
||||
confidence_multiplier: Regime-specific confidence scaling factor.
|
||||
phi_decay: Evidence state decay factor for projection.
|
||||
atr_multiplier: Regime-specific ATR multiplier for stop computation.
|
||||
"""
|
||||
|
||||
regime: MarketRegime
|
||||
trend_z: float
|
||||
vol_ratio: float
|
||||
evidence_multiplier: float
|
||||
confidence_multiplier: float
|
||||
phi_decay: float
|
||||
atr_multiplier: float
|
||||
|
||||
|
||||
# Regime parameter lookup: (gamma, confidence_mult, phi, ATR_mult, min_edge)
|
||||
_V3_REGIME_PARAMS: dict[MarketRegime, tuple[float, float, float, float, float]] = {
|
||||
MarketRegime.PANIC: (0.70, 0.70, 0.35, 2.5, 0.0100),
|
||||
MarketRegime.TREND_FOLLOWING: (1.10, 1.00, 0.80, 1.8, 0.0035),
|
||||
MarketRegime.MEAN_REVERSION: (0.90, 0.95, 0.55, 1.4, 0.0050),
|
||||
MarketRegime.UNCERTAINTY: (0.80, 0.85, 0.50, 2.0, 0.0075),
|
||||
}
|
||||
|
||||
# Default uncertainty classification for v3 when data is insufficient (Req 6.8)
|
||||
_DEFAULT_V3_UNCERTAINTY = V3RegimeClassification(
|
||||
regime=MarketRegime.UNCERTAINTY,
|
||||
trend_z=0.0,
|
||||
vol_ratio=1.0,
|
||||
evidence_multiplier=0.80,
|
||||
confidence_multiplier=0.85,
|
||||
phi_decay=0.50,
|
||||
atr_multiplier=2.0,
|
||||
)
|
||||
|
||||
|
||||
def _compute_ema_full(values: list[float], span: int) -> float:
|
||||
"""Compute EMA over the full values list with given span.
|
||||
|
||||
Uses standard EMA formula: alpha = 2 / (span + 1), iterating from the
|
||||
beginning of the list. Seeds EMA with the first value.
|
||||
|
||||
This differs from ``compute_ema`` which only uses the last ``period``
|
||||
values. V3 requires iterating over the full history to produce a stable
|
||||
EMA_100.
|
||||
"""
|
||||
if not values or span < 1:
|
||||
raise ValueError("values must be non-empty and span must be >= 1")
|
||||
|
||||
alpha = 2.0 / (span + 1)
|
||||
ema = values[0]
|
||||
for value in values[1:]:
|
||||
ema = alpha * value + (1.0 - alpha) * ema
|
||||
return ema
|
||||
|
||||
|
||||
def classify_regime_v3(
|
||||
closing_prices: list[float],
|
||||
daily_returns: list[float],
|
||||
atr_20: float,
|
||||
) -> V3RegimeClassification:
|
||||
"""Classify market regime using v3 ATR-normalized indicators.
|
||||
|
||||
Computes trend_z = (EMA_20 - EMA_100) / ATR_20 and
|
||||
vol_ratio = sigma_20 / sigma_100 to determine the market regime.
|
||||
|
||||
Classification priority (Req 6.2–6.5):
|
||||
1. Panic: vol_ratio > 1.5 OR |trend_z| > 2.5
|
||||
2. Trend following: |trend_z| >= 0.75 AND vol_ratio < 1.3
|
||||
3. Mean reversion: |trend_z| < 0.50 AND vol_ratio < 1.0
|
||||
4. Uncertainty: all other cases
|
||||
|
||||
Falls back to uncertainty when data is insufficient (Req 6.8):
|
||||
- Fewer than 100 closing prices for EMA_100
|
||||
- ATR_20 <= 0 (insufficient bars for ATR)
|
||||
- Fewer than 100 daily returns for sigma_100
|
||||
|
||||
Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8
|
||||
"""
|
||||
# --- Data sufficiency check (Req 6.8) ---
|
||||
if len(closing_prices) < 100:
|
||||
return _DEFAULT_V3_UNCERTAINTY
|
||||
|
||||
if atr_20 <= 0.0:
|
||||
return _DEFAULT_V3_UNCERTAINTY
|
||||
|
||||
if len(daily_returns) < 100:
|
||||
return _DEFAULT_V3_UNCERTAINTY
|
||||
|
||||
# --- Compute trend_z (Req 6.1) ---
|
||||
ema_20 = _compute_ema_full(closing_prices, span=20)
|
||||
ema_100 = _compute_ema_full(closing_prices, span=100)
|
||||
trend_z = (ema_20 - ema_100) / atr_20
|
||||
|
||||
# --- Compute vol_ratio (Req 6.1) ---
|
||||
sigma_20 = statistics.stdev(daily_returns[-20:]) if len(daily_returns) >= 20 else 0.0
|
||||
sigma_100 = statistics.stdev(daily_returns[-100:])
|
||||
|
||||
# Guard against zero sigma_100
|
||||
if sigma_100 <= 0.0 or math.isnan(sigma_100):
|
||||
return _DEFAULT_V3_UNCERTAINTY
|
||||
|
||||
if math.isnan(sigma_20):
|
||||
return _DEFAULT_V3_UNCERTAINTY
|
||||
|
||||
vol_ratio = sigma_20 / sigma_100
|
||||
|
||||
# --- Classification rules (Req 6.2–6.5) ---
|
||||
# Priority 1: Panic (Req 6.2)
|
||||
if vol_ratio > 1.5 or abs(trend_z) > 2.5:
|
||||
regime = MarketRegime.PANIC
|
||||
# Priority 2: Trend following (Req 6.3)
|
||||
elif abs(trend_z) >= 0.75 and vol_ratio < 1.3:
|
||||
regime = MarketRegime.TREND_FOLLOWING
|
||||
# Priority 3: Mean reversion (Req 6.4)
|
||||
elif abs(trend_z) < 0.50 and vol_ratio < 1.0:
|
||||
regime = MarketRegime.MEAN_REVERSION
|
||||
# Priority 4: Uncertainty (Req 6.5)
|
||||
else:
|
||||
regime = MarketRegime.UNCERTAINTY
|
||||
|
||||
# --- Assign regime parameters (Req 6.6, 6.7) ---
|
||||
gamma, conf_mult, phi, atr_mult, _min_edge = _V3_REGIME_PARAMS[regime]
|
||||
|
||||
return V3RegimeClassification(
|
||||
regime=regime,
|
||||
trend_z=trend_z,
|
||||
vol_ratio=vol_ratio,
|
||||
evidence_multiplier=gamma,
|
||||
confidence_multiplier=conf_mult,
|
||||
phi_decay=phi,
|
||||
atr_multiplier=atr_mult,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user