feat: math core v3 engine upgrade
This commit is contained in:
@@ -5,14 +5,20 @@ log-likelihood accumulation, Beta distribution parameters, and
|
||||
Shannon entropy for mixed-signal detection.
|
||||
|
||||
Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 9.1, 9.7
|
||||
V3 posterior assembly: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from services.aggregation.scoring import WeightedSignal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from services.aggregation.regime import V3RegimeClassification
|
||||
from services.aggregation.worker import EvidenceCluster
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BayesianPosterior:
|
||||
@@ -125,3 +131,126 @@ def compute_bayesian_posterior(
|
||||
entropy=entropy,
|
||||
signal_count=count,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# V3 Posterior Assembly — Calibrated Evidence Engine
|
||||
# Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class V3Posterior:
|
||||
"""V3 posterior result from log-odds Bayesian assembly.
|
||||
|
||||
Attributes:
|
||||
p_up: Posterior probability of upward move, (0, 1).
|
||||
p_down: 1 - p_up.
|
||||
log_odds: Raw log-odds (logit) of P_up.
|
||||
strength: abs(2 × P_up - 1), signal conviction [0, 1].
|
||||
direction: Classified direction string ('bullish', 'bearish', 'neutral').
|
||||
n_eff_total: Total effective evidence count across all clusters.
|
||||
regime: Market regime string used for this computation.
|
||||
"""
|
||||
|
||||
p_up: float
|
||||
p_down: float
|
||||
log_odds: float
|
||||
strength: float
|
||||
direction: str
|
||||
n_eff_total: float
|
||||
regime: str
|
||||
|
||||
|
||||
# Regime-specific direction thresholds: (bullish_threshold, bearish_threshold)
|
||||
# P_up >= bullish → "bullish"; P_up <= bearish → "bearish"; else "neutral"
|
||||
_V3_DIRECTION_THRESHOLDS: dict[str, tuple[float, float]] = {
|
||||
"panic": (0.68, 0.32),
|
||||
"trend_following": (0.60, 0.40),
|
||||
"mean_reversion": (0.63, 0.37),
|
||||
"uncertainty": (0.65, 0.35),
|
||||
}
|
||||
|
||||
|
||||
def _logit(p: float) -> float:
|
||||
"""Compute logit = ln(p / (1-p)) with boundary guard."""
|
||||
p = max(1e-10, min(1 - 1e-10, p))
|
||||
return math.log(p / (1 - p))
|
||||
|
||||
|
||||
def _sigmoid(x: float) -> float:
|
||||
"""Compute sigmoid = 1 / (1 + exp(-x)) with overflow guard."""
|
||||
if x > 500:
|
||||
return 1.0
|
||||
if x < -500:
|
||||
return 0.0
|
||||
return 1.0 / (1.0 + math.exp(-x))
|
||||
|
||||
|
||||
def compute_v3_posterior(
|
||||
clusters: list[EvidenceCluster],
|
||||
regime: V3RegimeClassification,
|
||||
p_prior: float = 0.50,
|
||||
) -> V3Posterior:
|
||||
"""Assemble v3 posterior via log-odds accumulation.
|
||||
|
||||
Computes:
|
||||
logit(P_up) = logit(P_prior) + sum(gamma_regime × LLR_c)
|
||||
P_up = sigmoid(log_odds), clamped to [1e-10, 1 - 1e-10]
|
||||
strength = abs(2 × P_up - 1)
|
||||
direction via regime-specific thresholds
|
||||
|
||||
Args:
|
||||
clusters: List of EvidenceCluster objects with computed cluster_llr.
|
||||
regime: V3RegimeClassification providing evidence_multiplier and regime.
|
||||
p_prior: Calibrated prior probability, clamped to [0.40, 0.60].
|
||||
|
||||
Returns:
|
||||
V3Posterior with computed posterior fields.
|
||||
|
||||
Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7
|
||||
"""
|
||||
# Clamp prior to [0.40, 0.60] (Req 5.6)
|
||||
p_prior = max(0.40, min(0.60, p_prior))
|
||||
|
||||
# Gamma: regime-specific evidence multiplier (Req 5.2)
|
||||
gamma = regime.evidence_multiplier
|
||||
|
||||
# Compute log-odds: logit(P_prior) + sum(gamma × LLR_c) (Req 5.1, 5.2)
|
||||
log_odds = _logit(p_prior) + sum(gamma * c.cluster_llr for c in clusters)
|
||||
|
||||
# Compute P_up via sigmoid (Req 5.3)
|
||||
p_up = _sigmoid(log_odds)
|
||||
# Clamp to open interval (Req 5.3)
|
||||
p_up = max(1e-10, min(1 - 1e-10, p_up))
|
||||
|
||||
p_down = 1.0 - p_up
|
||||
|
||||
# Strength = |2 × P_up - 1| (Req 5.4)
|
||||
strength = abs(2.0 * p_up - 1.0)
|
||||
|
||||
# n_eff_total = sum of cluster n_eff (Req 5.5)
|
||||
n_eff_total = sum(c.n_eff for c in clusters)
|
||||
|
||||
# Classify direction using regime-specific thresholds (Req 5.5)
|
||||
regime_key = regime.regime.value # MarketRegime enum → string
|
||||
bull_thresh, bear_thresh = _V3_DIRECTION_THRESHOLDS.get(
|
||||
regime_key, (0.65, 0.35)
|
||||
)
|
||||
|
||||
if p_up >= bull_thresh:
|
||||
direction = "bullish"
|
||||
elif p_up <= bear_thresh:
|
||||
direction = "bearish"
|
||||
else:
|
||||
direction = "neutral"
|
||||
|
||||
return V3Posterior(
|
||||
p_up=p_up,
|
||||
p_down=p_down,
|
||||
log_odds=log_odds,
|
||||
strength=strength,
|
||||
direction=direction,
|
||||
n_eff_total=n_eff_total,
|
||||
regime=regime_key,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user