feat: math core v3 engine upgrade

This commit is contained in:
Celes Renata
2026-06-27 12:21:41 +00:00
parent 365bc5d4b7
commit b4bf0f2361
34 changed files with 11693 additions and 3 deletions
+161
View File
@@ -14,6 +14,7 @@ from __future__ import annotations
import math
import uuid
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
from typing import Any
@@ -702,3 +703,163 @@ def evaluate_order(
state_snapshot=state,
evaluated_at=now,
)
# ===========================================================================
# v3 Stop-Defined Portfolio Heat (Requirements 15.115.5)
# ===========================================================================
def compute_portfolio_heat(
positions: list[dict[str, float]],
stop_distances: dict[str, float],
) -> float:
"""Compute total portfolio heat from stop-defined risk dollars.
risk_dollars = position_value × stop_distance_pct for each position.
portfolio_heat = sum of all risk_dollars.
positions: list of dicts with keys "ticker" and "position_value"
stop_distances: dict mapping ticker to stop_distance_pct
Requirements: 15.1, 15.2
"""
total_heat = 0.0
for pos in positions:
ticker = pos.get("ticker", "")
position_value = pos.get("position_value", 0.0)
stop_distance_pct = stop_distances.get(ticker, 0.0)
risk_dollars = position_value * stop_distance_pct
total_heat += risk_dollars
return total_heat
def check_heat_capacity(
current_heat: float,
new_risk_dollars: float,
max_heat_pct: float,
portfolio_value: float,
) -> bool:
"""Check if a new position would exceed heat capacity.
Returns True if the new entry is allowed (capacity exists).
Returns False if it would exceed max_heat_pct × portfolio_value.
Requirements: 15.3, 15.4, 15.5
"""
max_heat_dollars = max_heat_pct * portfolio_value
return (current_heat + new_risk_dollars) <= max_heat_dollars
def compute_available_heat_capacity(
current_heat: float,
max_heat_pct: float,
portfolio_value: float,
) -> float:
"""Compute available heat capacity for new positions.
available = max_heat_pct × portfolio_value - current_heat
Returns max(0, available).
Requirement: 15.4
"""
max_heat_dollars = max_heat_pct * portfolio_value
available = max_heat_dollars - current_heat
return max(0.0, available)
def compute_heat_capacity_pct(
current_heat: float,
max_heat_pct: float,
portfolio_value: float,
) -> float:
"""Compute available heat capacity as a portfolio percentage for Kelly sizing.
This converts absolute available heat dollars into a fraction of portfolio
value, suitable for use as `heat_capacity` in the Kelly sizing pipeline's
`available_caps` dict.
Requirements: 15.4, 15.5
"""
if portfolio_value <= 0.0:
return 0.0
available_dollars = compute_available_heat_capacity(
current_heat, max_heat_pct, portfolio_value
)
return available_dollars / portfolio_value
# ---------------------------------------------------------------------------
# v3 Risk Tier Auto-Adjustment (Requirements 18.118.6)
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class TierMetrics:
"""30-day rolling performance metrics for tier adjustment.
Collected once per calendar day after session close.
Requirements: 18.1
"""
profit_factor_30d: float
"""Gross profit / gross loss over last 30 days."""
max_drawdown_30d: float
"""Largest peak-to-trough as fraction over last 30 days."""
calibration_error: float
"""Mean |predicted P_up - realized outcome| over last 30 days."""
realized_sharpe_30d: float
"""Annualized Sharpe ratio of daily returns over last 30 days."""
n_trades_30d: int
"""Number of trades executed in last 30 days."""
reserve_pool_pct: float
"""Reserve pool as fraction of total portfolio value."""
def evaluate_tier_adjustment(metrics: TierMetrics) -> str:
"""Evaluate whether to upgrade, downgrade, or hold current risk tier.
Decision logic:
- Downgrade if ANY of: profit_factor < 1.0 OR max_drawdown > 0.12 OR
calibration_error > 0.20 OR realized_sharpe < 0
- Upgrade only if ALL of: profit_factor > 1.35 AND max_drawdown < 0.05 AND
calibration_error < 0.12 AND reserve_pool_pct > 0.20 AND n_trades >= 20
- Otherwise: hold
Downgrade is applied immediately; 7-day upgrade cooldown is enforced
at the caller level (not in this function). Evaluation runs once per
calendar day after session close.
Returns:
'upgrade' | 'downgrade' | 'hold'
Requirements: 18.2, 18.3, 18.4, 18.5, 18.6
"""
# --- Downgrade: any single condition triggers ---
if (
metrics.profit_factor_30d < 1.0
or metrics.max_drawdown_30d > 0.12
or metrics.calibration_error > 0.20
or metrics.realized_sharpe_30d < 0
):
return "downgrade"
# --- Upgrade: all conditions must be satisfied ---
if (
metrics.profit_factor_30d > 1.35
and metrics.max_drawdown_30d < 0.05
and metrics.calibration_error < 0.12
and metrics.reserve_pool_pct > 0.20
and metrics.n_trades_30d >= 20
):
return "upgrade"
# --- Hold: neither downgrade nor upgrade criteria met ---
return "hold"