feat: math core v3 engine upgrade
This commit is contained in:
@@ -10,6 +10,10 @@ All decisions are rule-based with no model involvement. The LLM is only
|
||||
used downstream for optional thesis wording (a separate task).
|
||||
|
||||
Requirements: 7.1, 7.2, 7.3, 7.4, 14.1, 14.2, 14.3, 14.4, 14.5, 14.6
|
||||
|
||||
v3 additions:
|
||||
- ReturnDistribution and EV gate (Requirement 12)
|
||||
- Regime-aware eligibility and mode escalation (Requirements 13.1–13.5)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -464,3 +468,290 @@ def evaluate_eligibility(
|
||||
p_bull=p_bull if probabilistic else None,
|
||||
pipeline_mode="probabilistic" if probabilistic else "heuristic",
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# V3 Return Distribution and EV Gate (Requirements 12.1–12.7)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_V3_MIN_EDGE: dict[str, float] = {
|
||||
"panic": 0.0100,
|
||||
"trend_following": 0.0035,
|
||||
"mean_reversion": 0.0050,
|
||||
"uncertainty": 0.0075,
|
||||
}
|
||||
|
||||
_V3_ELIGIBILITY_THRESHOLDS: dict[str, tuple[float, float, float]] = {
|
||||
# (confidence_min, contradiction_max, strength_min)
|
||||
"panic": (0.70, 0.25, 0.36),
|
||||
"trend_following": (0.55, 0.40, 0.20),
|
||||
"mean_reversion": (0.60, 0.35, 0.26),
|
||||
"uncertainty": (0.65, 0.30, 0.30),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ReturnDistribution:
|
||||
"""V3 return distribution model output.
|
||||
|
||||
Encapsulates the horizon-scaled volatility, expected return, risk-adjusted
|
||||
expected value, regime minimum edge, and final eligibility decision.
|
||||
|
||||
Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7
|
||||
"""
|
||||
|
||||
sigma_h: float # realized_vol_20d * sqrt(horizon_days / 252)
|
||||
mu_h: float # tanh(A_projected / 3.0) * confidence * sigma_h
|
||||
ev_long: float # mu_h - costs - 0.10 * CVaR_5
|
||||
min_edge: float # regime-specific minimum edge
|
||||
eligible: bool
|
||||
|
||||
|
||||
def compute_return_distribution(
|
||||
a_projected: float,
|
||||
confidence: float,
|
||||
realized_vol_20d: float,
|
||||
horizon_days: int,
|
||||
costs: float,
|
||||
regime: str,
|
||||
*,
|
||||
confidence_actual: float = 1.0,
|
||||
contradiction: float = 0.0,
|
||||
n_eff_total: float = 0.0,
|
||||
data_quality: float = 1.0,
|
||||
) -> ReturnDistribution:
|
||||
"""Compute the v3 return distribution and EV gate eligibility.
|
||||
|
||||
Implements the return distribution model from the v3 math spec:
|
||||
- sigma_h: horizon-scaled volatility
|
||||
- mu_h: expected return using tanh-compressed projected alpha
|
||||
- CVaR_5: Gaussian approximation of 5th percentile tail loss
|
||||
- EV_long: risk-adjusted expected value after costs and tail risk
|
||||
|
||||
Eligibility requires:
|
||||
- EV_long > regime min_edge
|
||||
- EV_long > max(0.0025, 0.25 * costs)
|
||||
- confidence_actual >= regime confidence_min
|
||||
- contradiction <= regime contradiction_max
|
||||
- n_eff_total >= 2.0
|
||||
- data_quality >= 0.50
|
||||
|
||||
Args:
|
||||
a_projected: Projected evidence state A_projected_h from projection.
|
||||
confidence: Multiplicative confidence from the v3 pipeline.
|
||||
realized_vol_20d: 20-day realized annualized volatility.
|
||||
If <= 0 or unavailable, defaults to 0.25.
|
||||
horizon_days: Trading days for the horizon (1, 7, 30, or 90).
|
||||
costs: Total costs (spread + slippage + commission).
|
||||
regime: Market regime string (panic, trend_following, mean_reversion, uncertainty).
|
||||
confidence_actual: The raw confidence value for threshold checks (defaults to
|
||||
same as confidence if not separately provided).
|
||||
contradiction: Contradiction score in [0, 1].
|
||||
n_eff_total: Total effective evidence count across clusters.
|
||||
data_quality: Data quality score in [0, 1].
|
||||
|
||||
Returns:
|
||||
ReturnDistribution with computed fields and eligibility decision.
|
||||
|
||||
Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7
|
||||
"""
|
||||
# Default volatility when unavailable (Req 12.7)
|
||||
if realized_vol_20d is None or realized_vol_20d <= 0: # type: ignore[redundant-expr]
|
||||
realized_vol_20d = 0.25
|
||||
|
||||
# Req 12.1: sigma_h = realized_vol_20d * sqrt(horizon_days / 252)
|
||||
sigma_h = realized_vol_20d * math.sqrt(horizon_days / 252.0)
|
||||
|
||||
# Req 12.2: mu_h = tanh(A_projected / 3.0) * confidence * sigma_h
|
||||
mu_h = math.tanh(a_projected / 3.0) * confidence * sigma_h
|
||||
|
||||
# Req 12.3: CVaR_5 = sigma_h * 1.645 * 1.4
|
||||
cvar_5 = sigma_h * 1.645 * 1.4
|
||||
|
||||
# Req 12.3: EV_long = mu_h - costs - 0.10 * CVaR_5
|
||||
ev_long = mu_h - costs - 0.10 * cvar_5
|
||||
|
||||
# Req 12.4: regime-specific min_edge
|
||||
min_edge = _V3_MIN_EDGE.get(regime, _V3_MIN_EDGE["uncertainty"])
|
||||
|
||||
# Req 12.5: EV_long > min_edge AND EV_long > max(0.0025, 0.25 * costs)
|
||||
ev_gate_passed = ev_long > min_edge and ev_long > max(0.0025, 0.25 * costs)
|
||||
|
||||
# Req 12.6: Additional eligibility checks
|
||||
thresholds = _V3_ELIGIBILITY_THRESHOLDS.get(
|
||||
regime, _V3_ELIGIBILITY_THRESHOLDS["uncertainty"]
|
||||
)
|
||||
confidence_min, contradiction_max, _strength_min = thresholds
|
||||
|
||||
quality_gate_passed = (
|
||||
confidence_actual >= confidence_min
|
||||
and contradiction <= contradiction_max
|
||||
and n_eff_total >= 2.0
|
||||
and data_quality >= 0.50
|
||||
)
|
||||
|
||||
eligible = ev_gate_passed and quality_gate_passed
|
||||
|
||||
return ReturnDistribution(
|
||||
sigma_h=sigma_h,
|
||||
mu_h=mu_h,
|
||||
ev_long=ev_long,
|
||||
min_edge=min_edge,
|
||||
eligible=eligible,
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# v3 Regime-Aware Eligibility and Mode Escalation (Requirements 13.1–13.5)
|
||||
# ===========================================================================
|
||||
|
||||
# Direction thresholds for action mapping (from bayesian.py)
|
||||
_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),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class V3Eligibility:
|
||||
"""Result of v3 regime-aware eligibility evaluation.
|
||||
|
||||
Requirements: 13.1–13.5
|
||||
"""
|
||||
|
||||
action: str # "BUY", "SELL", "HOLD", "WATCH"
|
||||
mode: str # "live", "paper", "informational"
|
||||
eligible: bool # meets regime thresholds
|
||||
reasons: list[str] # reasons for non-eligibility or downgrade
|
||||
|
||||
|
||||
def compute_v3_eligibility(
|
||||
p_up: float,
|
||||
ev_long: float,
|
||||
min_edge: float,
|
||||
confidence: float,
|
||||
contradiction: float,
|
||||
strength: float,
|
||||
n_eff_total: float,
|
||||
data_quality: float,
|
||||
regime: str,
|
||||
has_existing_position: bool = False,
|
||||
risk_engine_passed: bool = True,
|
||||
) -> V3Eligibility:
|
||||
"""Compute regime-aware eligibility and mode escalation.
|
||||
|
||||
Requirements: 13.1–13.5
|
||||
|
||||
Steps:
|
||||
1. Check regime-specific eligibility gates (confidence, contradiction, strength)
|
||||
2. Determine action (BUY/SELL/HOLD/WATCH) based on P_up and EV
|
||||
3. Escalate mode (live/paper/informational) based on signal quality
|
||||
|
||||
Args:
|
||||
p_up: Posterior probability of upward move from Bayesian posterior.
|
||||
ev_long: Expected value from return distribution.
|
||||
min_edge: Regime-specific minimum edge from EV gate.
|
||||
confidence: Multiplicative confidence score in [0, 1].
|
||||
contradiction: LLR entropy contradiction in [0, 1].
|
||||
strength: Signal strength = abs(2 * P_up - 1).
|
||||
n_eff_total: Effective evidence count across all clusters.
|
||||
data_quality: Data quality score in [0, 1].
|
||||
regime: Current market regime string.
|
||||
has_existing_position: Whether the entity already has an open position.
|
||||
risk_engine_passed: Whether the risk engine approved the trade.
|
||||
|
||||
Returns:
|
||||
V3Eligibility with action, mode, eligible flag, and reasons.
|
||||
"""
|
||||
reasons: list[str] = []
|
||||
|
||||
# --- 1. Regime-specific eligibility gates (Requirement 13.1) ---
|
||||
conf_min, contra_max, str_min = _V3_ELIGIBILITY_THRESHOLDS.get(
|
||||
regime, (0.65, 0.30, 0.30) # default to uncertainty thresholds
|
||||
)
|
||||
|
||||
eligible = True
|
||||
|
||||
if confidence < conf_min:
|
||||
eligible = False
|
||||
reasons.append(f"confidence {confidence:.3f} < regime min {conf_min:.2f}")
|
||||
|
||||
if contradiction > contra_max:
|
||||
eligible = False
|
||||
reasons.append(
|
||||
f"contradiction {contradiction:.3f} > regime max {contra_max:.2f}"
|
||||
)
|
||||
|
||||
if strength < str_min:
|
||||
eligible = False
|
||||
reasons.append(f"strength {strength:.3f} < regime min {str_min:.2f}")
|
||||
|
||||
if data_quality < 0.50:
|
||||
eligible = False
|
||||
reasons.append(f"data_quality {data_quality:.3f} < 0.50")
|
||||
|
||||
if n_eff_total < 2.0:
|
||||
eligible = False
|
||||
reasons.append(f"n_eff_total {n_eff_total:.2f} < 2.0")
|
||||
|
||||
# --- 2. Action mapping (Requirement 13.2) ---
|
||||
bull_thresh, _bear_thresh = _V3_DIRECTION_THRESHOLDS.get(
|
||||
regime, (0.65, 0.35)
|
||||
)
|
||||
|
||||
if not eligible:
|
||||
action = "WATCH"
|
||||
elif p_up >= bull_thresh and ev_long > min_edge:
|
||||
action = "BUY"
|
||||
elif has_existing_position and ev_long <= 0:
|
||||
# SELL when existing position and exit EV > hold EV
|
||||
# Simplified: EV_exit > EV_hold approximated as ev_long <= 0
|
||||
# (holding has negative expected value → better to exit)
|
||||
action = "SELL"
|
||||
elif has_existing_position:
|
||||
action = "HOLD"
|
||||
else:
|
||||
action = "WATCH"
|
||||
|
||||
# --- 3. Mode escalation (Requirements 13.3, 13.4, 13.5) ---
|
||||
if action in ("BUY", "SELL"):
|
||||
# Check live eligibility (Requirement 13.3)
|
||||
if (
|
||||
confidence >= 0.75
|
||||
and contradiction <= 0.20
|
||||
and n_eff_total >= 5
|
||||
and ev_long > 2 * min_edge
|
||||
and risk_engine_passed
|
||||
):
|
||||
mode = "live"
|
||||
# Check paper eligibility (Requirement 13.4)
|
||||
elif (
|
||||
confidence >= 0.60
|
||||
and ev_long > min_edge
|
||||
and risk_engine_passed
|
||||
):
|
||||
mode = "paper"
|
||||
else:
|
||||
mode = "informational"
|
||||
if confidence < 0.60:
|
||||
reasons.append(
|
||||
f"paper requires confidence >= 0.60, got {confidence:.3f}"
|
||||
)
|
||||
if ev_long <= min_edge:
|
||||
reasons.append(
|
||||
f"paper requires EV > min_edge ({min_edge:.4f}), got {ev_long:.4f}"
|
||||
)
|
||||
if not risk_engine_passed:
|
||||
reasons.append("risk engine did not pass")
|
||||
else:
|
||||
# HOLD and WATCH are always informational (Requirement 13.5)
|
||||
mode = "informational"
|
||||
|
||||
return V3Eligibility(
|
||||
action=action,
|
||||
mode=mode,
|
||||
eligible=eligible,
|
||||
reasons=reasons,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user