monitor: advance() consuma solo barre CHIUSE — serie rigenerate, guardia PREMATURO, 4 debiti chiusi

S5.1 RIPARATO E RIGENERATO. Filtro condiviso src/live/paper_guard.py (barra
open-labeled chiusa = ts + cadenza <= adesso) importato da tutti e 6 i monitor;
serie rigenerate dallo stesso start_ts con scripts/live/paper_regen.py (evidenza
in *.pre_regen_20260826.*): statarb +1,95 -> -1,61 (il ribaltamento del gate
27/09 previsto dall'audit), dvolspread -14,73 -> -4,41, xsr -4,98 -> -2,72,
prevday invariato. Nessuna data di gate si sposta. Guardia cablata in
monitor_health: stato PREMATURO (ultima barra che chiude dopo l'mtime, grazia
5 min, open_labeled=False per collect_chain) — sul dato vivo segnala i 5 rotti
e tace sui 2 sani; dopo la rigenerazione 7/7 OK. paper_portfolio non rigenerato
(GTAA su ADJUSTED_LAST: replay != serie registrata, P12), tolta la coda non
chiusa. D6 pagata di nuovo nel fix: asi8 in pandas 3 e' in us, non ns —
blindata con test su tre risoluzioni.

S5.12 ESTESO: conftest devia anche trades.db (wrapper su connect: il default
e' catturato alla definizione) e docs/journal/; book_executions.jsonl
sorvegliato con impronta inizio/fine suite.

S5.5 FATTO: test_leva_massima cancellato con nota (misurava frac*n_asset:
con una chiave di scala avrebbe continuato a passare smettendo di controllare).

S5.9 INDAGATO E RIPARATO (r0826_skh_band_drift): il dato regge (taglio 02/07
riproduce l'audit 1,6376, in-sample identico su ogni taglio); la deriva era la
finestra hold-out — e la sola settimana 15-22/08 vale +0,35 di Sharpe hold-out.
Il test ora taglia il feed al 02/07 e verifica la riproduzione stretta.

Suite: 751 passati, 0 falliti.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
This commit is contained in:
Adriano Dal Pastro
2026-08-26 10:12:23 +00:00
parent a51844875b
commit d597fc64e8
20 changed files with 872 additions and 54 deletions
+5
View File
@@ -18,6 +18,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd
from src.portfolio.sleeves import _tp01_returns, _tp01_positions
from src.portfolio.gtaa import gtaa_returns, gtaa_weights
from src.live import paper_guard as PG
STATE_DIR = PROJECT_ROOT / "data" / "paper_combo"
STATE = STATE_DIR / "state.json"
@@ -77,6 +78,10 @@ def advance():
return st
last = pd.Timestamp(st["last"])
nn = naked[naked.index > last]; gg = guard[guard.index > last]
# SOLO giorni CHIUSI. L'audit 22/08 ha trovato questo monitor sano PER CASO (aspetta la
# chiusura di una borsa, non per progetto): il filtro lo rende sano PER COSTRUZIONE.
m = PG.indice_chiuso(nn.index, PG.MS_1D)
nn, gg = nn[m], gg[PG.indice_chiuso(gg.index, PG.MS_1D)]
if len(nn):
e = st["equity"]; pk = st["peak"]; dd = st["max_dd"]
eg = st["equity_g"]; pkg = st["peak_g"]; ddg = st["max_dd_g"]; lines = []
+4 -1
View File
@@ -85,6 +85,7 @@ sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "alt"))
sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "ortho"))
import altlib as al # noqa: E402
from src.live import paper_guard as PG # noqa: E402
import ortholib as ol # noqa: E402
from r0726_dvolspread_gate import make_book # noqa: E402 (fabbrica ESATTA del T2)
@@ -152,7 +153,9 @@ def init_state(P: dict) -> dict:
def advance(st: dict, P: dict) -> dict:
ts, dt = P["ts"], P["dt"]
new = [i for i in range(len(ts)) if ts[i] > st["last_ts"]]
# SOLO barre CHIUSE: il pannello arriva da `resample_tf` col giorno in corso dentro —
# su uno spread BTC-ETH la barra troncata e' quasi solo rumore (~2 min/g, r0822_monitor_audit)
new = PG.nuove_chiuse(ts, st["last_ts"], PG.MS_1D)
if not new:
return st
hm, hr = st["wb_modeled"], st["wb_real"] # peso BTC TENUTO (w_eth = -w_btc)
+5
View File
@@ -17,6 +17,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd
from src.portfolio.portfolio import StrategyPortfolio
from src.portfolio.sleeves import active_sleeves
from src.live import paper_guard as PG
STATE_DIR = PROJECT_ROOT / "data" / "paper_portfolio"
STATE = STATE_DIR / "state.json"
@@ -51,6 +52,10 @@ def advance():
return st
last = pd.Timestamp(st["last"])
new = r[r.index > last]
# SOLO giorni CHIUSI: l'ultima riga dei rendimenti giornalieri e' il giorno in corso
# (~19 min/giorno registrati, r0822_monitor_audit). E' un monitor da dashboard, ma la
# regola D2 vale anche qui.
new = new[PG.indice_chiuso(new.index, PG.MS_1D)]
if len(new):
eq = st["equity"]; peak = st["peak"]; dd = st["max_dd"]
lines = []
+5 -1
View File
@@ -38,6 +38,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.backtest.harness import load # noqa: E402
from src.live import paper_guard as PG # noqa: E402
from src.strategies.prevday_breakout import target as prevday_target # noqa: E402
from src.strategies import prevday_breakout as pb # noqa: E402
@@ -93,7 +94,10 @@ def advance(st: dict, dfs: dict) -> dict:
dt=pd.to_datetime(df["datetime"]).values, r=r,
tgt=prevday_target(df))
common = sorted(set(data["BTC"]["ts"]).intersection(data["ETH"]["ts"]))
new_ts = [t for t in common if t > st["last_ts"]]
# SOLO barre 1h CHIUSE: il parquet 1h puo' contenere l'ora in corso (1 barra su 24 —
# r0822_monitor_audit); la si lascia al giro successivo
new_ts = [t for t in common
if t > st["last_ts"] and PG.chiusa(int(t), PG.MS_1H)]
if not new_ts:
return st
idx = {a: {int(t): i for i, t in enumerate(data[a]["ts"])} for a in ASSETS}
+228
View File
@@ -0,0 +1,228 @@
"""paper_regen.py — RIGENERA le serie dei forward-monitor rotti da `advance()` (§5.1).
PERCHE' RIGENERARE E NON AZZERARE. L'audit r0822_monitor_audit ha provato due volte, in modo
indipendente, che il replay sul pannello di oggi E' la serie corretta sulla stessa finestra:
(a) `paper_prevday` ha 1427/1488 barre identiche al bit dopo 62 notti di riscrittura del feed;
(b) le 6 barre chiuse di `paper_statarb` recuperate dopo il guasto EPERM coincidono al bit.
Quindi la finestra forward NON va persa: si rigenera dallo STESSO `start_ts` pre-registrato,
con la config congelata e il codice di produzione riparato — e nessuna data di gate si sposta.
COSA FA, per ciascun monitor (statarb · dvolspread · xsr · prevday):
1. archivia i file correnti come *.pre_regen_<data>.* (NON li cancella: sono l'evidenza
del difetto, e stanno nel perimetro di backup);
2. ricostruisce lo stato iniziale all'ORIGINALE `start_ts` (stessa costruzione dell'audit);
3. riesegue `advance()` di produzione — che ora consuma SOLO barre chiuse (paper_guard);
4. salva lo stato e stampa il verdetto a runtime (N11): barre, Sharpe vecchio vs nuovo,
e la prova che l'ultima barra registrata e' chiusa.
`paper_portfolio` e `paper_combo` NON si rigenerano qui, e la scelta e' dichiarata: la gamba
GTAA legge `ADJUSTED_LAST` di IB ri-aggiustato all'indietro, quindi il replay NON sarebbe la
serie che un monitor sano avrebbe registrato (P12: meglio una storia corrotta e dichiarata di
una riscritta e spacciata per registrata). Sono dashboard senza gate; dal fix in poi scrivono
solo barre chiuse.
uv run python scripts/live/paper_regen.py # tutti e 4
uv run python scripts/live/paper_regen.py --solo paper_statarb
"""
from __future__ import annotations
import importlib.util
import json
import shutil
import sys
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
ANN = float(np.sqrt(365.0))
SUFFISSO = datetime.now(timezone.utc).strftime("pre_regen_%Y%m%d")
def live(name: str):
spec = importlib.util.spec_from_file_location(f"_regen_{name}",
ROOT / "scripts" / "live" / f"{name}.py")
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
return m
def sharpe_jsonl(path: Path, campo: str = "net_modeled") -> tuple[float, int]:
if not path.exists():
return float("nan"), 0
r = []
for ln in path.read_text().splitlines():
ln = ln.strip()
if ln:
r.append(float(json.loads(ln)[campo]))
r = np.asarray(r)
if len(r) < 2 or r.std() == 0:
return float("nan"), len(r)
return float(r.mean() / r.std() * ANN), len(r)
def archivia(d: Path, nomi: tuple[str, ...]) -> None:
for n in nomi:
f = d / n
if f.exists():
dest = d / f"{f.stem}.{SUFFISSO}{f.suffix}"
if dest.exists():
raise SystemExit(f" {dest} esiste gia': rigenerazione doppia nello stesso "
"giorno? Verificare a mano prima di riprovare.")
shutil.move(str(f), str(dest))
print(f" archiviato {f.name} -> {dest.name}")
def verdetto(nome: str, d: Path, mod, st: dict, sh_prima: float, n_prima: int,
cadenza_ms: int) -> None:
from src.live.paper_guard import chiusa
sh_dopo, n_dopo = sharpe_jsonl(d / "returns.jsonl")
ult = st["last_ts"] if "last_ts" in st else None
ok_chiusa = chiusa(int(ult), cadenza_ms) if ult else False
print(f" barre: {n_prima} registrate (rotte) -> {n_dopo} rigenerate (chiuse)")
print(f" Sharpe ann. net_modeled: {sh_prima:+.2f} (serie rotta) -> {sh_dopo:+.2f} "
f"(serie vera)")
print(f" ultima barra {pd.Timestamp(int(ult), unit='ms', tz='UTC').date() if ult else '?'} "
f"chiusa: {'SI' if ok_chiusa else 'NO — QUALCOSA NON VA'}")
if not ok_chiusa:
raise SystemExit(f" {nome}: la rigenerazione ha consumato una barra NON chiusa")
def regen_statarb() -> None:
print("\n== paper_statarb (gate 2026-09-27) " + "=" * 50)
d = ROOT / "data" / "paper_statarb"
ps = live("paper_statarb")
st_old = json.loads((d / "state.json").read_text())
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
j = ps.build_joint("1d")
ts, _, pos, _ = ps._signal(j)
i0 = int(np.where(ts == st_old["start_ts"])[0][0])
archivia(d, ("returns.jsonl", "trades.jsonl", "state.json"))
st = dict(start_ts=st_old["start_ts"], last_ts=st_old["start_ts"], n_bars=0,
pos_modeled=float(pos[i0]), pos_real=float(pos[i0]),
cap_modeled=ps.MODELED_CAPITAL, cap_real=ps.REAL_CAPITAL,
peak_modeled=ps.MODELED_CAPITAL, peak_real=ps.REAL_CAPITAL,
dd_modeled=0.0, dd_real=0.0, n_trades=0)
st = ps.advance(st, j)
ps._state_io(st)
verdetto("paper_statarb", d, ps, st, sh0, n0, 86_400_000)
def regen_dvolspread() -> None:
print("\n== paper_dvolspread (kill 2026-10-24 / decisione 2027-01-24) " + "=" * 24)
d = ROOT / "data" / "paper_dvolspread"
pv = live("paper_dvolspread")
st_old = json.loads((d / "state.json").read_text())
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
P = pv._panel()
i0 = int(np.where(P["ts"] == st_old["start_ts"])[0][0])
archivia(d, ("returns.jsonl", "state.json"))
st = dict(start_ts=st_old["start_ts"], last_ts=st_old["start_ts"], n_bars=0, n_active=0,
n_flat_signal=0, n_flat_nodata=0,
wb_modeled=float(P["wb"][i0]), wb_real=float(P["wb"][i0]),
cap_modeled=pv.MODELED_CAPITAL, cap_real=pv.REAL_CAPITAL,
peak_modeled=pv.MODELED_CAPITAL, peak_real=pv.REAL_CAPITAL,
dd_modeled=0.0, dd_real=0.0, n_flips=0, frozen=pv.FROZEN)
st = pv.advance(st, P)
pv._state_io(st)
verdetto("paper_dvolspread", d, pv, st, sh0, n0, 86_400_000)
def regen_xsr() -> None:
print("\n== paper_xsr (gate 2026-10-23) " + "=" * 54)
d = ROOT / "data" / "paper_xsr"
px = live("paper_xsr")
st_old = json.loads((d / "state.json").read_text())
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
archivia(d, ("returns.jsonl", "state.json"))
st = dict(start_ts=st_old["start_ts"], last_ts=st_old["start_ts"], n_bars=0,
syms=st_old["syms"],
modeled=px._book(px.MODELED_CAPITAL, len(st_old["syms"])),
reals={n: px._book(c, len(st_old["syms"])) for c, n in px.REAL_BOOKS})
st = px.advance(st)
px._state_io(st)
verdetto("paper_xsr", d, px, st, sh0, n0, 86_400_000)
def regen_prevday() -> None:
print("\n== paper_prevday (griglia ORARIA, nessun gate a data fissa) " + "=" * 24)
d = ROOT / "data" / "paper_prevday"
pp = live("paper_prevday")
st_old = json.loads((d / "state.json").read_text())
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
dfs = pp.build_bars()
S = st_old["start_ts"]
pos = {a: pp.pb.current_target(dfs[a][dfs[a]["timestamp"] <= S]) for a in pp.ASSETS}
archivia(d, ("returns.jsonl", "trades.jsonl", "state.json"))
st = dict(start_ts=S, last_ts=S, n_bars=0, pos_modeled=pos, pos_real=dict(pos),
cap_modeled=pp.MODELED_CAPITAL, cap_real=pp.REAL_CAPITAL,
peak_modeled=pp.MODELED_CAPITAL, peak_real=pp.REAL_CAPITAL,
dd_modeled=0.0, dd_real=0.0, n_trades=0)
st = pp.advance(st, dfs)
pp._state_io(st)
verdetto("paper_prevday", d, pp, st, sh0, n0, 3_600_000)
def trim_portfolio() -> None:
"""`paper_portfolio` NON si rigenera (la gamba GTAA legge ADJUSTED_LAST ri-aggiustato:
il replay non sarebbe la serie registrata — P12). Ma la sua CODA contiene la riga del
giorno in corso, scritta dal codice vecchio: senza toglierla la guardia PREMATURO
resterebbe accesa per sempre su un difetto gia' riparato (P14). Il taglio e' onesto:
la storia registrata resta com'e' (troncata e DICHIARATA in memoria), va via solo la
riga che dice di essere un giorno e non lo e' — la versione chiusa la riscrive il
prossimo giro del cron col codice riparato."""
from src.live.paper_guard import MS_1D, indice_chiuso
print("\n== paper_portfolio (dashboard — TRIM della coda non chiusa, non rigenerazione) ==")
d = ROOT / "data" / "paper_portfolio"
righe = (d / "equity.csv").read_text().strip().splitlines()
testa, dati = righe[0], righe[1:]
date = pd.DatetimeIndex([r.split(",")[0] for r in dati])
m = indice_chiuso(date, MS_1D)
tolte = [r for r, k in zip(dati, m) if not k]
if not tolte:
print(" coda gia' pulita: niente da fare")
return
archivia(d, ("equity.csv", "state.json"))
dati_ok = [r for r, k in zip(dati, m) if k]
(d / "equity.csv").write_text("\n".join([testa] + dati_ok) + "\n")
eq = [float(r.split(",")[1]) for r in dati_ok]
peak, dd = 0.0, 0.0
for e in eq:
peak = max(peak, e)
dd = max(dd, (peak - e) / peak if peak > 0 else 0.0)
st_old = json.loads((d / f"state.{SUFFISSO}.json").read_text())
st = dict(st_old, last=dati_ok[-1].split(",")[0], equity=eq[-1], peak=peak, max_dd=dd,
n_days=st_old["n_days"] - len(tolte))
(d / "state.json").write_text(json.dumps(st, indent=2))
for r in tolte:
print(f" tolta riga non chiusa: {r}")
print(f" ultima riga ora: {dati_ok[-1]} (peak/max_dd ricalcolati dalla serie)")
REGEN = dict(paper_statarb=regen_statarb, paper_dvolspread=regen_dvolspread,
paper_xsr=regen_xsr, paper_prevday=regen_prevday,
paper_portfolio=trim_portfolio)
def main() -> None:
print("=" * 90)
print(" RIGENERAZIONE forward-monitor — stesso start_ts, config congelata, solo barre chiuse")
print("=" * 90)
solo = None
if "--solo" in sys.argv:
solo = sys.argv[sys.argv.index("--solo") + 1]
if solo not in REGEN:
raise SystemExit(f"monitor sconosciuto: {solo} (validi: {', '.join(REGEN)})")
for nome, fn in REGEN.items():
if solo and nome != solo:
continue
fn()
print("\n fatto. I file *.%s.* sono l'evidenza del difetto e restano nel backup." % SUFFISSO)
if __name__ == "__main__":
main()
+4 -1
View File
@@ -51,6 +51,7 @@ sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research"))
# Segnale ESATTO dello sweep (nessuna reimplementazione → identità garantita col backtest).
from orthogonal_signals import build_joint, f_statarb_resid, spread_ret # noqa: E402
from src.live import paper_guard as PG # noqa: E402
STATE_DIR = PROJECT_ROOT / "data" / "paper_statarb"
STATE_FILE = STATE_DIR / "state.json"
@@ -99,7 +100,9 @@ def init_state(j: pd.DataFrame) -> dict:
def advance(st: dict, j: pd.DataFrame) -> dict:
ts, dt, pos, sr = _signal(j)
new = [i for i in range(len(ts)) if ts[i] > st["last_ts"]]
# SOLO barre CHIUSE: `build_joint('1d')` passa da `resample_tf`, che NON scarta il giorno
# in corso (1 barra 1h su 24) — consumarlo faceva registrare ~4 min/giorno (r0822_monitor_audit)
new = PG.nuove_chiuse(ts, st["last_ts"], PG.MS_1D)
if not new:
return st
pm, pr = st["pos_modeled"], st["pos_real"] # posizioni TENUTE (decise alla barra precedente)
+4 -1
View File
@@ -65,6 +65,7 @@ sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research"))
# Segnale ESATTO dello studio (nessuna reimplementazione -> niente drift col backtest).
from r0725_statarb_multi import BASE, MIN_BARS, load_hl, signal, universe # noqa: E402
from src.live import paper_guard as PG # noqa: E402
STATE_DIR = PROJECT_ROOT / "data" / "paper_xsr"
STATE_FILE = STATE_DIR / "state.json"
@@ -158,7 +159,9 @@ def advance(st: dict) -> dict:
print(f" [XSR01] universo cambiato ({len(st['syms'])} -> {len(syms)}): "
"la finestra forward richiede universo costante. Usa --reset per ripartire.")
return st
new = [i for i in range(len(ts)) if ts[i] > st["last_ts"]]
# SOLO barre CHIUSE: l'ultima riga del parquet HL e' il giorno IN CORSO, e consumarla
# e' il difetto che faceva registrare ~41 minuti di mercato al giorno (r0822_monitor_audit)
new = PG.nuove_chiuse(ts, st["last_ts"], PG.MS_1D)
if not new:
return st
for i in new:
+111
View File
@@ -0,0 +1,111 @@
"""r0826_skh_band_drift.py — si guarda SOTTO il test rosso §5.9 prima di toccare la banda.
IL FATTO. `test_t1_canonical_riproduce_il_backtest_ufficiale` fallisce dal ~24-25/08:
Sharpe hold-out SKH01 `canonical` = 1,9223 contro la banda cablata `1.3 < h < 1.9`
(scritta il 25/07 sull'audit del 02/07: ~1,64). Il codice non e' cambiato; `data/raw/`
e' gitignored e il cron lo ricostruisce ogni notte -> la finestra si allunga e il numero
deriva. Lezione 07/08: un test che fallisce senza che il codice sia cambiato sta
segnalando QUALCOSA — si misura cosa, non si allarga la banda a occhio.
LA MISURA. Stesso codice di produzione (`r0726_skh_onbook.skh_series`), stesso `sh3`,
ma col feed TAGLIATO a date crescenti: se il numero alla data dell'audit riproduce
~1,64 e la curva sale in modo regolare con le barre aggiunte, la deriva e' CRESCITA
DELLA FINESTRA (hold-out 2025+ che si allunga con settimane favorevoli), non un dato
corrotto ne' un codice cambiato. Se invece il taglio all'epoca NON riproduce il numero
dell'epoca, il problema e' nel DATO RICOSTRUITO e la banda e' l'ultima delle questioni.
COSA MI ASPETTO PRIMA DI MISURARE (M12): riproduzione al taglio 02/07 dentro ±0,05;
curva monotona o quasi verso 1,92 (ago 2026 e' stato un rally forte, BTC +21%/30g,
e SKH01 era long ETH da meta' agosto).
uv run python scripts/research/r0826_skh_band_drift.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
for p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"):
sys.path.insert(0, str(p))
import r0702_anchor_skh01 as r02 # noqa: E402
import r0726_skh_onbook as ob # noqa: E402
TAGLI = ("2026-07-02", # la data dell'audit che ha scritto ~1,64
"2026-07-25", # il giorno in cui la banda e' stata cablata nel test
"2026-08-01", "2026-08-08", "2026-08-15", "2026-08-22",
None) # oggi: deve riprodurre l'1,9223 del test rosso
_GET5M_VERO = r02.get5m.__wrapped__ # la funzione nuda, senza lru_cache
def _taglia(cut_ms: int | None):
"""Rimpiazza get5m con una versione tagliata e svuota TUTTE le cache a valle."""
if cut_ms is None:
def g(asset: str):
return _GET5M_VERO(asset)
else:
def g(asset: str):
df = _GET5M_VERO(asset)
return df[df["timestamp"] < cut_ms].reset_index(drop=True)
r02.get5m = g
# ⚠️ run_asset non usa lru_cache: memoizza in un DICT di modulo (`_CACHE`) — svuotare
# solo le lru_cache serviva a niente e la prima stesura di questo script ha misurato
# SETTE volte la stessa finestra (tutte le righe identiche). Si svuota il dict.
r02._CACHE.clear()
r02.skh_port.cache_clear()
def main() -> None:
print("=" * 96)
print(" SKH01 canonical — Sharpe (full / in-sample / HOLD-OUT) in funzione della finestra dati")
print(" hold-out = barre >= 2025-01-01: si ALLUNGA ogni notte col cron")
print("=" * 96)
print(f" {'taglio':>12} {'barre HO (g)':>13} {'full':>7} {'in-s':>7} {'HOLD-OUT':>9} "
f"{'maxDD':>8} nota")
print(" " + "-" * 90)
esiti = {}
for cut in TAGLI:
cut_ms = None if cut is None else int(pd.Timestamp(cut, tz="UTC").value // 10**6)
_taglia(cut_ms)
s, _ = ob.skh_series(0, "canonical")
full, is_, hold, dd = ob.sh3(s)
n_ho = int((s.index >= r02.HOLDOUT).sum())
nota = ""
if cut == "2026-07-02":
nota = "<- deve riprodurre ~1,64 (audit 02/07)"
elif cut == "2026-07-25":
nota = "<- il giorno della banda 1.3<h<1.9"
elif cut is None:
nota = "<- deve riprodurre l'1,9223 del test rosso"
lab = cut or "oggi"
esiti[lab] = hold
print(f" {lab:>12} {n_ho:>13} {full:>7.3f} {is_:>7.3f} {hold:>9.4f} {dd:>7.1%} {nota}")
print("\n" + "=" * 96)
print(" VERDETTO (runtime)")
print("=" * 96)
rip_audit = abs(esiti["2026-07-02"] - 1.64) < 0.05
rip_oggi = abs(esiti["oggi"] - 1.9223) < 0.01
serie = [esiti[k] for k in esiti]
mono = all(b >= a - 0.03 for a, b in zip(serie, serie[1:]))
print(f" taglio 02/07 riproduce l'audit (~1,64)? {'SI' if rip_audit else 'NO'} "
f"({esiti['2026-07-02']:.4f})")
print(f" nessun taglio riproduce -> dato cambiato: "
f"{'no, il dato regge' if rip_audit else 'SI - INDAGARE IL FEED'}")
print(f" oggi riproduce il numero del test rosso? {'SI' if rip_oggi else 'NO'} "
f"({esiti['oggi']:.4f})")
print(f" la curva sale con la finestra (quasi-monot.)? {'SI' if mono else 'NO'}")
if rip_audit and rip_oggi:
print("\n => La deriva e' CRESCITA DELLA FINESTRA, non corruzione ne' codice: il test")
print(" confronta un numero che si muove col feed contro una banda scritta su una")
print(" finestra congelata. La riparazione giusta e' ancorare il test alla finestra")
print(" su cui la banda fu scritta (taglio fisso), non allargare la banda — una")
print(" banda che insegue il feed non riproduce niente (N11).")
if __name__ == "__main__":
main()