From 6f4f2ce02e7084b137a0fc192d0954aa218ed930 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 8 May 2026 23:27:40 +0000 Subject: [PATCH] feat(runtime): audit log include threshold rolling e window usata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/cerbero_bite/runtime/entry_cycle.py | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/cerbero_bite/runtime/entry_cycle.py b/src/cerbero_bite/runtime/entry_cycle.py index aea5a06..d96479e 100644 --- a/src/cerbero_bite/runtime/entry_cycle.py +++ b/src/cerbero_bite/runtime/entry_cycle.py @@ -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 ),