feat(runtime): audit log include threshold rolling e window usata

Risponde al final review (spec §6.4): il decisions log ora
contiene iv_rv_threshold_used (la soglia P_q effettivamente
applicata) e iv_rv_window_used_days (giorni di history nella
finestra). Permette ricostruire ex-post perché un trade è stato
saltato e con quali numeri.

Helper privi di I/O — la soglia viene ricomputata in base alla
history già caricata, costo trascurabile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-08 23:27:40 +00:00
parent d2ff29fde3
commit 6f4f2ce02e
+43
View File
@@ -30,6 +30,7 @@ from cerbero_bite.clients.macro import MacroClient
from cerbero_bite.clients.portfolio import PortfolioClient
from cerbero_bite.clients.sentiment import SentimentClient
from cerbero_bite.config.schema import StrategyConfig
from cerbero_bite.core.adaptive_threshold import compute_adaptive_threshold
from cerbero_bite.core.combo_builder import ComboProposal, build, select_strikes
from cerbero_bite.core.entry_validator import (
EntryContext,
@@ -315,6 +316,44 @@ async def _build_quotes(
return out
def _audit_threshold(
entry_cfg: object,
iv_rv_history: tuple[Decimal, ...],
) -> str | None:
"""Soglia P_q rolling effettivamente usata dal gate, per il decisions log."""
if not getattr(entry_cfg, "iv_minus_rv_filter_enabled", False):
return None
if not getattr(entry_cfg, "iv_minus_rv_adaptive_enabled", False):
return str(getattr(entry_cfg, "iv_minus_rv_min", Decimal("0")))
threshold = compute_adaptive_threshold(
history=iv_rv_history,
percentile=entry_cfg.iv_minus_rv_percentile, # type: ignore[attr-defined]
absolute_floor=entry_cfg.iv_minus_rv_min, # type: ignore[attr-defined]
min_days=entry_cfg.iv_minus_rv_window_min_days, # type: ignore[attr-defined]
target_days=entry_cfg.iv_minus_rv_window_target_days, # type: ignore[attr-defined]
)
return None if threshold is None else str(threshold)
def _audit_window_days(
entry_cfg: object,
iv_rv_history: tuple[Decimal, ...],
) -> int | None:
"""Numero di giorni effettivamente usati dalla finestra rolling."""
if not getattr(entry_cfg, "iv_minus_rv_adaptive_enabled", False):
return None
n_days = len(iv_rv_history) // 96
target = int(getattr(entry_cfg, "iv_minus_rv_window_target_days", 60))
min_days = int(getattr(entry_cfg, "iv_minus_rv_window_min_days", 30))
if n_days < 1:
return 0
if n_days >= target:
return target
if n_days >= min_days:
return min_days
return n_days
def _max_loss_per_contract_usd(short_strike: Decimal, long_strike: Decimal) -> Decimal:
return (short_strike - long_strike).copy_abs()
@@ -490,6 +529,10 @@ async def run_entry_cycle(
str(snap.iv_minus_rv) if snap.iv_minus_rv is not None else None
),
"iv_rv_history_n": len(iv_rv_history),
"iv_rv_threshold_used": _audit_threshold(entry_cfg, iv_rv_history),
"iv_rv_window_used_days": _audit_window_days(
entry_cfg, iv_rv_history
),
"dvol_24h_ago": (
str(dvol_24h_ago) if dvol_24h_ago is not None else None
),