research(wave-0822): MONITOR-AUDIT — 4 monitor su 6 rotti, il gate STATARB si ribalta, ma la finestra e' rigenerabile e nessuna data si sposta

This commit is contained in:
Adriano Dal Pastro
2026-08-22 17:52:53 +00:00
parent cef559b350
commit fbb6e75e67
3 changed files with 234 additions and 55 deletions
+48
View File
@@ -21,6 +21,7 @@ null de-levering superato + eseguibilita' al capitale dichiarato.
| — | **SLIP-AUDIT** (audit di fatto) | **SCARTATO** = nessun costo nascosto a questa taglia | i backtest a 10 bps RT restano **conservativi di ~1,5 bps/lato**; nessuna evidenza di impatto sopravvive al null (p=0,274; estremi avversi **1/18 contro 4,7 attesi**). **Ma la misura ha una data di scadenza** | | — | **SLIP-AUDIT** (audit di fatto) | **SCARTATO** = nessun costo nascosto a questa taglia | i backtest a 10 bps RT restano **conservativi di ~1,5 bps/lato**; nessuna evidenza di impatto sopravvive al null (p=0,274; estremi avversi **1/18 contro 4,7 attesi**). **Ma la misura ha una data di scadenza** |
| — | **SCETTICO su GROWTH-POLICY** | **REGGE** (condizione bloccante DECADUTA) | la lente wick accoppiata **non cambia il gradino**: e' un ricarico moltiplicativo **costante ~3,5%** sul maxDD, non un'amplificazione (Δ/k **cala**). Ma su una regola a **UN giorno** close-only e' **esattamente cieca** (rapporto INF) -> la lezione 25/07 e' vera e **non si trasferisce** | | — | **SCETTICO su GROWTH-POLICY** | **REGGE** (condizione bloccante DECADUTA) | la lente wick accoppiata **non cambia il gradino**: e' un ricarico moltiplicativo **costante ~3,5%** sul maxDD, non un'amplificazione (Δ/k **cala**). Ma su una regola a **UN giorno** close-only e' **esattamente cieca** (rapporto INF) -> la lezione 25/07 e' vera e **non si trasferisce** |
| 13 | BASIS-CALENDAR | **SCARTATO** + **1 dataset nuovo** | il basis dei futures datati **E' il funding del perp quotato diversamente** (7,33% contro 6,48%/anno): non e' un quarto lato del filone funding, e' lo stesso lato. Premio incassabile **+0,85%/anno con IC95 che contiene lo zero** | | 13 | BASIS-CALENDAR | **SCARTATO** + **1 dataset nuovo** | il basis dei futures datati **E' il funding del perp quotato diversamente** (7,33% contro 6,48%/anno): non e' un quarto lato del filone funding, e' lo stesso lato. Premio incassabile **+0,85%/anno con IC95 che contiene lo zero** |
| — | **MONITOR-AUDIT** (produzione) | 🚨 **4 monitor su 6 rotti**, ma **la finestra NON e' persa** | `paper_statarb` registra **4 minuti al giorno** e il suo gate del **27/09 si ribalta su 2 criteri su 3** (Sharpe registrato **+1,96** contro **2,02** ricostruito). **La riparazione e' "riparare + RIGENERARE", non "riparare e azzerare": nessuna data di gate si sposta** |
## Note che sopravvivono ai singoli filoni ## Note che sopravvivono ai singoli filoni
@@ -590,3 +591,50 @@ formula ma il binario**: meno gradi di liberta', **dimezza gli ordini** (miglior
$635), 115% sul FULL — ma paga in fragilita' d'ancora (20/23 contro 23/23, banda che include 0,061, $635), 115% sul FULL — ma paga in fragilita' d'ancora (20/23 contro 23/23, banda che include 0,061,
hold-out +0,072 contro +0,101). **Resta un terzo della fortuna d'ancora del libro stesso (+0,196): hold-out +0,072 contro +0,101). **Resta un terzo della fortuna d'ancora del libro stesso (+0,196):
niente qui giustifica di toccare pesi, cron o config.** niente qui giustifica di toccare pesi, cron o config.**
### MONITOR-AUDIT (r0822_monitor_audit.py) — audit di PRODUZIONE, 6 monitor, 4,7 s
**Metodo che rende il risultato non contestabile:** l'audit **riesegue `advance()` di produzione
tale e quale**, sostituendo solo `_append` con un raccoglitore in memoria -> nessuna
reimplementazione, quindi **nessun drift fra audit e monitor**. Sola lettura verificata (gli mtime
di `data/paper_*` sono rimasti quelli del cron delle 00:35).
| monitor | barra parziale | barre coincidenti | corr | **min/giorno misurati** | gate | ricostruibile |
|---|---|---|---|---|---|---|
| `paper_statarb` | **SI'** | **0/46** | +0,297 | **4** | **STATARB 27/09** | **SI', bit-exact** |
| `paper_dvolspread` | **SI'** | 0/28 | +0,262 | **2** | DVOLSPREAD 24/10 | SI', bit-exact |
| `paper_xsr` | **SI'** | 1/28 | 0,045 | **41** | XSR01 23/10 | SI', bit-exact |
| `paper_portfolio` | **SI'** | 7/56 | +0,245 | 19 | — (dashboard) | si', non al bit |
| `paper_prevday` | si' (1 barra su 24) | **1259/1319** | **+0,997** | 1.438 | — | si', bit-exact |
| `paper_combo` | **NO** | 7/37 | +0,991 | 1.402 | — | si', non al bit |
📌 **La patologia e' la stessa, la TAGLIA varia di un fattore 20** — e dipende da quanto pesa la
barra piu' recente nel segnale, esattamente come la lezione del 26/07 su SKH01 contro TP01.
🚨 **Il gate STATARB del 27/09 si ribalta su DUE criteri su tre:** Sharpe registrato **+1,96** (sopra
la soglia 0,5) contro **2,02** ricostruito; maxDD **2,2% contro 15,3%** (guardia <10%). *Fra 36
giorni si sarebbe deciso su una serie che misura 4 minuti di mercato al giorno.*
⚠️ XSR01 e DVOLSPREAD danno lo **stesso verdetto** oggi, ma su numeri implausibili (Sharpe 15,8 su
28 barre = oltre 4 SE). **`implausible_sharpe` esiste dal 26/07 e non e' mai stato puntato sulle
serie forward.**
**LA BUONA NOTIZIA, ed e' misurata: la finestra forward NON va persa.** Il replay *e'* la serie
corretta sulla stessa finestra, con config congelata e codice di produzione. Le due condizioni sono
**provate due volte in modo indipendente**: (a) `paper_prevday` ha **1427/1488 barre identiche al
bit** dopo 62 notti di riscrittura (il feed **non tocca** le barre chiuse); (b) le 6 barre di
`paper_statarb` recuperate dopo un guasto coincidono **al bit**.
-> **La decisione non e' "riparare e azzerare" ma "riparare `advance()` + RIGENERARE", e nessuna
delle tre date di gate si sposta.** Anche nell'ipotesi pessimista (`--reset`) l'unico gate che
slitterebbe e' STATARB, di **+18 giorni**.
📌 **Dettaglio che vale una regola: le uniche barre corrette di `paper_statarb` esistono perche' il
feed si e' ROTTO.** `rebuild_history` e' abortito per 7 giri consecutivi (09-15/07, EPERM sul
backup, gia' riparato); al ripristino il monitor ha recuperato 7 barre in un colpo, **6 delle quali
gia' chiuse** -> coincidono col replay al bit. *Il guasto ha prodotto da solo il controllo che
serviva a diagnosticare l'altro guasto.*
📌 **`paper_combo` e' sano PER CASO:** aspetta la chiusura di una **borsa**, non per progetto.
Nessuna riga di codice chiede la barra chiusa — **se la sua griglia cambiasse diventerebbe rotto in
silenzio.**
**Guardia raccomandata (NON implementata), e ha il pregio di essere banale:**
`ts_ultima_barra + cadenza <= mtime del file di serie`, grazia 5 min. **O(1)**, nessuna strategia da
rieseguire, usa solo cio' che il monitor gia' scrive; segnala **5/6** e **tace sul sano**; controllo
positivo sintetico **6/6 nei due versi**. Il vol-ratio va **dopo, non al posto**: copre un guasto
diverso (drift monitor-strategia) e **non vede `paper_prevday`** (rapporto 0,999).
⚠️ Limiti dichiarati: i "minuti/giorno" sono una lettura del **rapporto di varianza** (assume random
walk sull'intervallo troncato), non un cronometro — il cronometro diretto e' 32-35 min; e per
`paper_portfolio`/`paper_combo` la ricostruzione **non sara' al bit** perche' la gamba GTAA legge
`ADJUSTED_LAST` di IB, ri-aggiustato all'indietro.
+165 -44
View File
@@ -9,7 +9,7 @@ vol registrata 0,46% contro 2,74% ricalcolata.
Il difetto non e' di XSR01: e' della FORMA `new = [i for i in range(len(ts)) if ts[i] > last_ts]` Il difetto non e' di XSR01: e' della FORMA `new = [i for i in range(len(ts)) if ts[i] > last_ts]`
applicata a un pannello la cui ultima riga e' provvisoria. Quella forma e' copiata in sei file. applicata a un pannello la cui ultima riga e' provvisoria. Quella forma e' copiata in sei file.
Tre decisioni PRE-REGISTRATE si leggono su queste serie: Tre decisioni PRE-REGISTRATE si leggono su queste serie:
STATARB 2026-09-27 (r0724_statarb_deploy_gate: Sharpe di net_modeled >= 0.35) STATARB 2026-09-27 (r0724_statarb_deploy_gate: Sharpe di net_modeled >= 0.5)
XSR01 2026-10-23 (r0725_xsr_deploy_gate: Sharpe >= 1.0 E haircut $5.000 <= 40%) XSR01 2026-10-23 (r0725_xsr_deploy_gate: Sharpe >= 1.0 E haircut $5.000 <= 40%)
DVOLSPREAD 2026-10-24 kill / 2027-01-24 decisione (soglie dentro paper_dvolspread) DVOLSPREAD 2026-10-24 kill / 2027-01-24 decisione (soglie dentro paper_dvolspread)
e `monitor_health` dichiara **OK** tutti e sei, perche' misura freschezza e buchi: una serie e `monitor_health` dichiara **OK** tutti e sei, perche' misura freschezza e buchi: una serie
@@ -42,11 +42,18 @@ Poi:
E. IL COSTO DELLA RIPARAZIONE per gate: giorni di finestra persi, data in cui il contatore E. IL COSTO DELLA RIPARAZIONE per gate: giorni di finestra persi, data in cui il contatore
nuovo tornerebbe alla stessa numerosita', e se esiste una RICOSTRUZIONE a posteriori. nuovo tornerebbe alla stessa numerosita', e se esiste una RICOSTRUZIONE a posteriori.
COSA MI ASPETTAVO PRIMA DI MISURARE: che i tre monitor giornalieri su feed crypto (xsr, statarb, COSA MI ASPETTAVO PRIMA DI MISURARE, e cosa e' andato diversamente:
dvolspread) avessero la stessa patologia identica; che `paper_prevday`, essendo su griglia 1h con * i tre monitor giornalieri su feed crypto (xsr, statarb, dvolspread) con la STESSA patologia,
24 barre al giorno, ne avesse una MOLTO piu' mite (una barra su 24 troncata); e che intorno ai ~35 minuti al giorno di XSR01 -> **meta' giusto**: la patologia e' la stessa ma la
`paper_combo`, che vive sul calendario di borsa, fosse o rotto come gli altri o sano per caso. TAGLIA no (2, 4 e 41 min/giorno). Un libro dollar-neutral perde molto piu' di un libro
Solo la terza aspettativa e' stata una sorpresa. direzionale a parita' di troncamento, perche' su 35 minuti le due gambe si muovono insieme;
* `paper_prevday` MOLTO piu' mite (1 barra su 24) -> **confermato** (1.438 min/giorno su 1.440),
con un dettaglio non previsto: il difetto si propaga alla barra successiva quando il bersaglio
calcolato sulla barra parziale differisce;
* `paper_combo` rotto o sano per caso -> **sano, e per caso**: aspetta la chiusura di una BORSA;
* NON previsto: le uniche barre corrette di `paper_statarb` esistono perche' il feed si ROMPE
per 7 giorni a luglio e il monitor le recupera CHIUSE. Sono anche la prova che serviva per
dire che la finestra e' ricostruibile.
nice -n 19 timeout 900 uv run python scripts/research/r0822_monitor_audit.py nice -n 19 timeout 900 uv run python scripts/research/r0822_monitor_audit.py
""" """
@@ -289,7 +296,8 @@ def b_xsr():
print(f" inception {T[0].date()} = DOPO l'incidente del feed: la finestra e' gia' pulita.") print(f" inception {T[0].date()} = DOPO l'incidente del feed: la finestra e' gia' pulita.")
out = show(T, np.array([p[1] for p in pairs]), np.array([p[2] for p in pairs])) out = show(T, np.array([p[1] for p in pairs]), np.array([p[2] for p in pairs]))
RES["paper_xsr"] = dict(parziale="SI", cmp=out["clean"], gate="XSR01 2026-10-23", RES["paper_xsr"] = dict(parziale="SI", cmp=out["clean"], gate="XSR01 2026-10-23",
src="hl_*_1d.parquet", cad_h=24.0, ric="SI (bit-exact)") src="hl_*_1d.parquet", cad_h=24.0, ric="SI bit-exact",
rec=rec, rep=sink.of("returns.jsonl"))
def b_statarb(): def b_statarb():
@@ -323,17 +331,18 @@ def b_statarb():
print(f"\n 📌 LE UNICHE BARRE CORRETTE DELLA SERIE VENGONO DA UN GUASTO: le {int(ident.sum())} " print(f"\n 📌 LE UNICHE BARRE CORRETTE DELLA SERIE VENGONO DA UN GUASTO: le {int(ident.sum())} "
f"identiche\n sono CONSECUTIVE ({gg[0]} -> {gg[-1]}) e cadono dentro l'incidente " f"identiche\n sono CONSECUTIVE ({gg[0]} -> {gg[-1]}) e cadono dentro l'incidente "
"del feed.") "del feed.")
ev = freeze_evidence() ev = sorted(freeze_evidence())
print(f" Prova indipendente dal log del cron: `rebuild_history` e' abortito in " print(f" Prova indipendente dal log del cron: `rebuild_history` e' abortito in "
f"{len(ev)} giri\n ({', '.join(sorted(ev)[:8])}{'...' if len(ev) > 8 else ''}) — " f"{len(ev)} giri consecutivi")
"EPERM sul backup, difetto gia' riparato\n nel sorgente. Col feed fermo il monitor " print(f" ({ev[0]} -> {ev[-1]}), EPERM sul backup difetto gia' riparato nel sorgente.")
"non trovava barre nuove; al ripristino ne ha\n recuperate 7 in un colpo solo, di " print(" Col feed fermo il monitor non trovava barre nuove; al ripristino ne ha")
"cui 6 GIA' CHIUSE -> coincidono col replay al bit.") print(" recuperate 7 in un colpo solo, di cui 6 GIA' CHIUSE -> coincidono al bit.")
print(" Due conseguenze: (a) il 'min/giorno' globale e' GONFIATO da quelle barre — la") print(" Due conseguenze: (a) il 'min/giorno' globale e' GONFIATO da quelle barre — la")
print(" riga 'ESCLUSO l'incidente' e' la stima onesta; (b) quelle 6 barre sono la prova") print(" riga 'ESCLUSO l'incidente' e' la stima onesta; (b) quelle 6 barre sono la prova")
print(" che, su barre chiuse, il codice di produzione riproduce il replay ESATTAMENTE.") print(" che, su barre chiuse, il codice di produzione riproduce il replay ESATTAMENTE.")
RES["paper_statarb"] = dict(parziale="SI", cmp=out["clean"], gate="STATARB 2026-09-27", RES["paper_statarb"] = dict(parziale="SI", cmp=out["clean"], gate="STATARB 2026-09-27",
src="btc/eth 1h -> resample 1d", cad_h=24.0, ric="SI (bit-exact)") src="btc/eth 1h -> resample 1d", cad_h=24.0, ric="SI bit-exact",
rec=rec, rep=sink.of("returns.jsonl"))
def b_dvolspread(): def b_dvolspread():
@@ -374,7 +383,7 @@ def b_dvolspread():
RES["paper_dvolspread"] = dict(parziale="SI", cmp=out["clean"], RES["paper_dvolspread"] = dict(parziale="SI", cmp=out["clean"],
gate="DVOLSPREAD 2026-10-24 / 2027-01-24", gate="DVOLSPREAD 2026-10-24 / 2027-01-24",
src="btc/eth 1h -> resample 1d + dvol", cad_h=24.0, src="btc/eth 1h -> resample 1d + dvol", cad_h=24.0,
ric="SI (bit-exact)") ric="SI bit-exact", rec=rec, rep=sink.of("returns.jsonl"))
def b_prevday(): def b_prevday():
@@ -429,7 +438,7 @@ def b_prevday():
print(" 📌 E il verdetto operativo e' l'opposto degli altri quattro: su griglia oraria il difetto") print(" 📌 E il verdetto operativo e' l'opposto degli altri quattro: su griglia oraria il difetto")
print(" c'e' ma vale ~0.1% della varianza. `paper_prevday` NON e' da rigenerare.") print(" c'e' ma vale ~0.1% della varianza. `paper_prevday` NON e' da rigenerare.")
RES["paper_prevday"] = dict(parziale="SI", cmp=out["clean"], gate="nessuno (lead in monitor)", RES["paper_prevday"] = dict(parziale="SI", cmp=out["clean"], gate="nessuno (lead in monitor)",
src="btc/eth 1h grezzo", cad_h=1.0, ric="SI (bit-exact)") src="btc/eth 1h grezzo", cad_h=1.0, ric="SI bit-exact")
def b_portfolio(): def b_portfolio():
@@ -457,8 +466,9 @@ def b_portfolio():
if ident.any(): if ident.any():
print(f"\n ⚠ TRAPPOLA DA NON PRENDERE: delle {int(ident.sum())} barre 'identiche', " print(f"\n ⚠ TRAPPOLA DA NON PRENDERE: delle {int(ident.sum())} barre 'identiche', "
f"{int(zeri.sum())} sono ZERO contro ZERO") f"{int(zeri.sum())} sono ZERO contro ZERO")
print(" (" + ", ".join(str(x.date()) for x in T[zeri]) + ") = giorni in cui TUTTI e cinque") print(" (" + ", ".join(str(x.date()) for x in T[zeri]) + ")")
print(" gli sleeve erano flat (TP01 risk-off, GTAA a borsa chiusa, VRP fuori scadenza).") print(" = giorni in cui TUTTI e cinque gli sleeve erano flat insieme (TP01 risk-off,")
print(" GTAA a borsa chiusa nel weekend, VRP fuori scadenza).")
print(" Contarle come 'coincidono' sarebbe la trappola gia' codificata dal progetto —") print(" Contarle come 'coincidono' sarebbe la trappola gia' codificata dal progetto —")
print(" barre ATTIVE, non barre di calendario (26/07, il gate che segnalo' VRP01 perche'") print(" barre ATTIVE, non barre di calendario (26/07, il gate che segnalo' VRP01 perche'")
print(" contava il 94% di zeri). Su barre attive le coincidenze qui sono " print(" contava il 94% di zeri). Su barre attive le coincidenze qui sono "
@@ -474,7 +484,7 @@ def b_portfolio():
RES["paper_portfolio"] = dict(parziale="SI", cmp=out["clean"], RES["paper_portfolio"] = dict(parziale="SI", cmp=out["clean"],
gate="nessuno (dashboard/book di ricerca)", gate="nessuno (dashboard/book di ricerca)",
src="5 sleeve, il piu' recente su 1h->1d e HL 1d", cad_h=24.0, src="5 sleeve, il piu' recente su 1h->1d e HL 1d", cad_h=24.0,
ric="SI* (equity IB ri-aggiustata)") ric="SI non al bit")
def b_combo(): def b_combo():
@@ -514,7 +524,7 @@ def b_combo():
print(" senza che nulla lo segnali.") print(" senza che nulla lo segnali.")
RES["paper_combo"] = dict(parziale="NO", cmp=out["clean"], gate="nessuno (paper cross-venue)", RES["paper_combo"] = dict(parziale="NO", cmp=out["clean"], gate="nessuno (paper cross-venue)",
src="TP01 1h->1d su griglia IB", cad_h=24.0, src="TP01 1h->1d su griglia IB", cad_h=24.0,
ric="SI* (equity IB ri-aggiustata)") ric="SI non al bit")
# ============================================================================================ # ============================================================================================
@@ -522,7 +532,7 @@ def b_combo():
# ============================================================================================ # ============================================================================================
GATE_INFO = { GATE_INFO = {
"paper_xsr": ("XSR01 2026-10-23", "Sharpe>=1.0 E haircut $5.000<=40%"), "paper_xsr": ("XSR01 2026-10-23", "Sharpe>=1.0 E haircut $5.000<=40%"),
"paper_statarb": ("STATARB 2026-09-27", "Sharpe di net_modeled >= 0.35"), "paper_statarb": ("STATARB 2026-09-27", "Sharpe di net_modeled >= 0.5"),
"paper_dvolspread": ("DVOLSPREAD 24/10 kill", "Sharpe<-0.50 -> ritiro; 27/01 decisione piena"), "paper_dvolspread": ("DVOLSPREAD 24/10 kill", "Sharpe<-0.50 -> ritiro; 27/01 decisione piena"),
"paper_prevday": ("-", "lead senza data"), "paper_prevday": ("-", "lead senza data"),
"paper_portfolio": ("-", "dashboard"), "paper_portfolio": ("-", "dashboard"),
@@ -537,22 +547,105 @@ def sezione_C():
print("\n Le colonne sono calcolate ESCLUDENDO l'incidente del feed 09-15/07 (le barre") print("\n Le colonne sono calcolate ESCLUDENDO l'incidente del feed 09-15/07 (le barre")
print(" recuperate a posteriori sono chiuse e attenuerebbero il difetto per la ragione") print(" recuperate a posteriori sono chiuse e attenuerebbero il difetto per la ragione")
print(" sbagliata). 'min/g' = minuti di mercato al giorno effettivamente misurati, su 1440.") print(" sbagliata). 'min/g' = minuti di mercato al giorno effettivamente misurati, su 1440.")
print(f"\n {'monitor':<19}{'parz.':>6}{'ident./tot':>13}{'corr':>9}{'min/g':>9}" print(f"\n {'monitor':<19}{'barra parz.':>13}{'ident./tot':>13}{'corr':>9}{'min/g':>8}"
f"{'ricostruibile':>25} gate dipendente") f"{'ricostruibile':>17} gate dipendente")
for k, v in RES.items(): for k, v in RES.items():
c = v["cmp"] c = v["cmp"]
ratio = "{}/{}".format(c["ident"], c["n"]) ratio = "{}/{}".format(c["ident"], c["n"])
print(f" {k:<19}{v['parziale']:>6}{ratio:>13}{c['corr']:>+9.3f}" print(f" {k:<19}{v['parziale']:>13}{ratio:>13}{c['corr']:>+9.3f}"
f"{c['minuti']:>9,.0f}{v['ric']:>25} {GATE_INFO[k][0]}") f"{c['minuti']:>8,.0f}{v['ric']:>17} {GATE_INFO[k][0]}")
print("\n 'ricostruibile' = la serie CORRETTA sulla stessa finestra forward si puo' ricalcolare") print("\n 'ricostruibile' = la serie CORRETTA sulla stessa finestra forward si puo' ricalcolare")
print(" oggi dai feed su disco: e' letteralmente la colonna 'replay' di questo script. '*' =") print(" oggi dai feed su disco: e' letteralmente la colonna 'replay' di questo script.")
print(" ricostruibile ma NON al bit, perche' una gamba viene dal feed equity IB che e'") print(" 'non al bit' = ricostruibile, ma una gamba viene dal feed equity IB, che e'")
print(" ri-aggiustato all'indietro (`ADJUSTED_LAST`).") print(" ri-aggiustato all'indietro (`ADJUSTED_LAST`) -> i centesimi non torneranno uguali.")
print("\n LETTURA IN UNA RIGA: quattro monitor su sei registrano fra 2 e 41 minuti di mercato") print("\n LETTURA IN UNA RIGA: quattro monitor su sei registrano fra 2 e 41 minuti di mercato")
print(" al giorno spacciandoli per 24 ore; i due che non lo fanno sono quello a griglia oraria") print(" al giorno spacciandoli per 24 ore; i due che non lo fanno sono quello a griglia oraria")
print(" (dove il difetto vale una barra su 24) e quello che aspetta la chiusura di una BORSA.") print(" (dove il difetto vale una barra su 24) e quello che aspetta la chiusura di una BORSA.")
def sezione_C2():
"""Il criterio di ciascun gate PRE-REGISTRATO resta leggibile? Le soglie NON sono ridichiarate
qui: si IMPORTANO dagli script di gate e dal monitor (regola del progetto — un sorvegliante
deriva il proprio bersaglio dal codice sorvegliato, mai lo ridichiara: 21/08, `fee_watch`)."""
print("\n" + "=" * 102)
print(" C2 — IL CRITERIO DEI TRE GATE PRE-REGISTRATI E' ANCORA LEGGIBILE?")
print("=" * 102)
import r0724_statarb_deploy_gate as GS # noqa: E402 (solo costanti: ha il main-guard)
import r0725_xsr_deploy_gate as GX # noqa: E402
pv = live("paper_dvolspread")
def metriche(rows: list[dict], key_m="net_modeled", key_r=None):
rm = np.array([float(r[key_m]) for r in rows])
eq = np.cumprod(1 + rm)
pk = np.maximum.accumulate(eq)
out = dict(sh=float(rm.mean() / rm.std() * np.sqrt(365)) if rm.std() > 0 else 0.0,
dd=float(np.max((pk - eq) / pk)), tot=float(np.prod(1 + rm) - 1))
if key_r:
rr = np.array([float(r.get(key_r, np.nan)) for r in rows])
rr = rr[np.isfinite(rr)]
out["tot_r"] = float(np.prod(1 + rr) - 1) if len(rr) else float("nan")
return out
def coppia(name):
rec = RES[name]["rec"]
rep = {int(r["ts"]): r for r in RES[name]["rep"]}
keep = [r for r in rec if int(r["ts"]) in rep]
return keep, [rep[int(r["ts"])] for r in keep]
print(f"\n {'gate':<26}{'metrica primaria':<26}{'soglia':>10}{'REGISTR.':>11}"
f"{'RICOSTR.':>11} verdetto")
# --- STATARB -------------------------------------------------------------------------
a, b = coppia("paper_statarb")
ma, mb = metriche(a, key_r="net_real"), metriche(b, key_r="net_real")
flip = (ma["sh"] >= GS.SH_DEPLOY) != (mb["sh"] >= GS.SH_DEPLOY)
print(f" {'STATARB ' + str(GS.DECISION):<26}{'Sharpe net_modeled':<26}"
f"{'>= ' + str(GS.SH_DEPLOY):>10}{ma['sh']:>+11.2f}{mb['sh']:>+11.2f} "
f"{'*** SI RIBALTA ***' if flip else 'stesso'}")
print(f" {'':<26}{'maxDD':<26}{'< ' + f'{GS.DD_MAX:.0%}':>10}{ma['dd']:>10.1%}"
f"{mb['dd']:>11.1%} "
f"{'*** SI RIBALTA ***' if (ma['dd'] < GS.DD_MAX) != (mb['dd'] < GS.DD_MAX) else 'stesso'}")
hp_a = abs((1 + ma["tot"]) - (1 + ma["tot_r"])) * 100
hp_b = abs((1 + mb["tot"]) - (1 + mb["tot_r"])) * 100
print(f" {'':<26}{'fill-haircut cum (pp)':<26}{'< ' + str(GS.HAIRCUT_MAX_PP):>10}"
f"{hp_a:>11.2f}{hp_b:>11.2f} "
f"{'*** SI RIBALTA ***' if (hp_a < GS.HAIRCUT_MAX_PP) != (hp_b < GS.HAIRCUT_MAX_PP) else 'stesso'}")
# --- XSR01 ---------------------------------------------------------------------------
a, b = coppia("paper_xsr")
K5 = "net_REAL-$5000"
ma, mb = metriche(a, key_r=K5), metriche(b, key_r=K5)
hc_a = (ma["tot"] - ma["tot_r"]) / abs(ma["tot"]) if ma["tot"] else float("nan")
hc_b = (mb["tot"] - mb["tot_r"]) / abs(mb["tot"]) if mb["tot"] else float("nan")
fl1 = (ma["sh"] >= GX.SH_DEPLOY) != (mb["sh"] >= GX.SH_DEPLOY)
fl2 = (hc_a <= GX.HAIRCUT_MAX) != (hc_b <= GX.HAIRCUT_MAX)
print(f" {'XSR01 ' + str(GX.DECISION):<26}{'Sharpe net_modeled':<26}"
f"{'>= ' + str(GX.SH_DEPLOY):>10}{ma['sh']:>+11.2f}{mb['sh']:>+11.2f} "
f"{'*** SI RIBALTA ***' if fl1 else 'stesso'}")
print(f" {'':<26}{'haircut $5.000':<26}{'<= ' + f'{GX.HAIRCUT_MAX:.0%}':>10}"
f"{hc_a:>10.1%}{hc_b:>11.1%} {'*** SI RIBALTA ***' if fl2 else 'stesso'}")
# --- DVOLSPREAD ----------------------------------------------------------------------
a, b = coppia("paper_dvolspread")
ma, mb = metriche(a), metriche(b)
fl = (ma["sh"] < pv.KILL_SHARPE) != (mb["sh"] < pv.KILL_SHARPE)
print(f" {'DVOLSPREAD ' + pv.KILL_DATE:<26}{'Sharpe fwd (kill se sotto)':<26}"
f"{'> ' + f'{pv.KILL_SHARPE:+.2f}':>10}{ma['sh']:>+11.2f}{mb['sh']:>+11.2f} "
f"{'*** SI RIBALTA ***' if fl else 'stesso (KILL in entrambe)'}")
print("""
⚠ QUESTA NON E' UNA LETTURA DEI GATE, e non deve diventarlo: le tre date sono nel futuro
(anticiparle sarebbe selezione-sul-forward, contro la regola del progetto) e con 28-54 barre
l'errore standard di uno Sharpe annualizzato e' 2,6-3,6. Serve a una cosa sola, e la mostra:
il difetto NON e' rumore centrato — sposta la metrica primaria abbastanza da attraversare la
soglia. Su STATARB la serie registrata dice +1,96 (sopra la soglia di deploy 0,5) e quella
ricostruita dice -2,02: **stesso codice, stessa finestra, verdetto opposto**.
⚠ E c'e' un secondo segnale, gratis: uno Sharpe registrato di -15,8 su 28 barre e' IMPOSSIBILE
(|Sharpe| oltre 4 SE). Il gate `implausible_sharpe` esiste gia' in `altlib` dal 26/07 ma
nessuno lo punta sulle serie FORWARD — solo sui candidati. Un numero fuori scala e' un
rilevatore di guasti a costo zero, e in questo caso era li' da leggere da settimane.""")
# ============================================================================================ # ============================================================================================
# D — LA GUARDIA CHE MANCA A monitor_health # D — LA GUARDIA CHE MANCA A monitor_health
# ============================================================================================ # ============================================================================================
@@ -608,8 +701,10 @@ def sezione_D():
if v["parziale"] == "SI" and seg == "tace": if v["parziale"] == "SI" and seg == "tace":
miss.append(k) miss.append(k)
print(f" {k:<19}{c['sd_rec']:>9.2f}%{c['sd_rep']:>9.2f}%{rr:>10.3f} {seg}") print(f" {k:<19}{c['sd_rec']:>9.2f}%{c['sd_rep']:>9.2f}%{rr:>10.3f} {seg}")
print(f" -> MANCA {len(miss)} monitor rotti su {sum(1 for v in RES.values() if v['parziale'] == 'SI')}" rotti = sum(1 for v in RES.values() if v["parziale"] == "SI")
f": {', '.join(miss) if miss else '-'}") print(f" -> segnala {rotti - len(miss)} dei {rotti} monitor che leggono una barra parziale,")
print(f" NON segnala il sano (`paper_combo`, 0.987), e MANCA {len(miss)}: "
f"{', '.join(miss) if miss else '-'}")
print(" Motivo strutturale, non taratura: su una griglia oraria il difetto tocca 1 barra") print(" Motivo strutturale, non taratura: su una griglia oraria il difetto tocca 1 barra")
print(" su 24 e sposta la vol dell'1-2%, cioe' DENTRO il rumore di stima di una vol su") print(" su 24 e sposta la vol dell'1-2%, cioe' DENTRO il rumore di stima di una vol su")
print(" poche settimane. Abbassare la soglia per prenderlo genererebbe falsi allarmi ogni") print(" poche settimane. Abbassare la soglia per prenderlo genererebbe falsi allarmi ogni")
@@ -697,17 +792,23 @@ def sezione_E():
("paper_dvolspread", "DVOLSPREAD (kill)", pd.Timestamp("2026-10-24"), "returns.jsonl"), ("paper_dvolspread", "DVOLSPREAD (kill)", pd.Timestamp("2026-10-24"), "returns.jsonl"),
("paper_dvolspread", "DVOLSPREAD (decisione)", pd.Timestamp("2027-01-24"), "returns.jsonl"), ("paper_dvolspread", "DVOLSPREAD (decisione)", pd.Timestamp("2027-01-24"), "returns.jsonl"),
] ]
print(f"\n {'monitor / gate':<34}{'barre oggi':>12}{'data gate':>13}{'se AZZERI':>12}" print("\n IPOTESI PESSIMISTA — si ripara `advance()` e si fa `--reset` (la finestra riparte da")
print(" zero oggi). Con una barra al giorno, tornare a N barre richiede N giorni:")
print(f"\n {'monitor / gate':<42}{'barre':>7}{'data gate':>13}{'reset -> N barre':>18}"
f"{'ritardo':>10}") f"{'ritardo':>10}")
ritardi = []
for mon, nome, gate, ser in righe: for mon, nome, gate, ser in righe:
n = len(read_jsonl(ROOT / "data" / mon / ser)) n = len(read_jsonl(ROOT / "data" / mon / ser))
# con una barra al giorno, tornare a n barre richiede n giorni dal reset
rip = oggi + pd.Timedelta(days=n) rip = oggi + pd.Timedelta(days=n)
rit = (rip - gate).days rit = (rip - gate).days
print(f" {f'{mon} / {nome}':<34}{n:>12}{str(gate.date()):>13}" ritardi.append((nome, rit))
f"{str(rip.date()):>12}{rit:>+9}g") print(f" {f'{mon} / {nome}':<42}{n:>7}{str(gate.date()):>13}"
print("\n Cosi' letta, la riparazione sposta i tre gate di 1-2 mesi. MA la colonna giusta e'") f"{str(rip.date()):>18}{rit:>+9}g")
print(" un'altra, e cambia la decisione:") peggiore = max(ritardi, key=lambda x: x[1])
print(f"\n Anche nell'ipotesi pessimista il danno NON e' uniforme: l'unico gate che slitterebbe")
print(f" e' {peggiore[0]} ({peggiore[1]:+d} giorni) — gli altri hanno finestre cosi' giovani che")
print(" ricostruirebbero la propria numerosita' PRIMA della data di decisione. Ma la colonna")
print(" giusta e' ancora un'altra, e toglie di mezzo anche quel ritardo:")
print(""" print("""
✅ LA FINESTRA FORWARD **NON VA PERSA**: e' RICOSTRUIBILE, per tutti e sei i monitor. ✅ LA FINESTRA FORWARD **NON VA PERSA**: e' RICOSTRUIBILE, per tutti e sei i monitor.
Le ore mancanti non sono mai state registrate, ma non e' li' che vivono: vivono nei feed Le ore mancanti non sono mai state registrate, ma non e' li' che vivono: vivono nei feed
@@ -719,10 +820,12 @@ def sezione_E():
minuti. minuti.
LE DUE CONDIZIONI, ed e' giusto chiedere che siano verificate e non assunte: LE DUE CONDIZIONI, ed e' giusto chiedere che siano verificate e non assunte:
(1) il feed non riscrive le barre gia' chiuse. **MISURATO qui**: `paper_prevday` ha (1) il feed non riscrive le barre gia' chiuse. **MISURATO qui, due volte e in modo
1426 barre su 1488 identiche BIT A BIT al replay dopo 62 giorni di riscritture indipendente**: `paper_prevday` ha 1427 barre su 1488 identiche BIT A BIT al replay
notturne del parquet BTC/ETH (sezione B4). Per Hyperliquid la stessa cosa e' misurata dopo 62 notti di riscrittura del parquet BTC/ETH (B4); e le 6 barre che `paper_statarb`
in `r0822_xsr_repro` T2 (Sharpe 2024 e 2025 riprodotti al centesimo su 731 barre). recupero' durante l'incidente del feed coincidono anch'esse al bit (B2) — due percorsi
diversi, stesso verdetto. Per Hyperliquid la misura e' in `r0822_xsr_repro` T2 (Sharpe
2024 e 2025 riprodotti al centesimo su 731 barre).
(2) la config e' congelata e verificabile: `paper_dvolspread` lo controlla da solo (2) la config e' congelata e verificabile: `paper_dvolspread` lo controlla da solo
(`frozen` nello stato, esce 1 se diverge); per gli altri il congelamento e' nel (`frozen` nello stato, esce 1 se diverge); per gli altri il congelamento e' nel
sorgente ed e' in git. sorgente ed e' in git.
@@ -743,11 +846,28 @@ def sezione_E():
verificabili in git (lo sono). verificabili in git (lo sono).
Il costo vero e' (c), non i giorni: nessuna delle tre date di gate si sposta. Il costo vero e' (c), non i giorni: nessuna delle tre date di gate si sposta.
⚠ UNA COSA CHE LA RIGENERAZIONE CAMBIA DAVVERO, e va scritta nel diario: i libri REAL saltano i ⚠ UNA COSA CHE LA RIGENERAZIONE CAMBIA DAVVERO, ma di poco — e il "di poco" e' MISURATO, non
ribilanci sotto il min-order, e quella decisione dipende dal capitale corrente, quindi il assunto. I libri REAL saltano i ribilanci sotto il min-order e quella decisione dipende dal
percorso REAL rigenerato non e' il percorso REAL registrato non e' "lo stesso numero capitale corrente: il percorso REAL rigenerato non e' "lo stesso numero calcolato meglio", e'
calcolato meglio", e' un secondo percorso deterministico. Per XSR01 conta: il gate del 23/10 un secondo percorso deterministico. Contava sapere quanto, perche' il gate XSR01 del 23/10
legge net_REAL-$5000 per l'haircut.""") legge `net_REAL-$5000` per l'haircut: misurato in C2, **1,7% registrato contro 1,8%
ricostruito**. Trascurabile a $5.000 (i ticket superano il min-order comunque). Il caveat
resta vero in linea di principio e va scritto nel diario; non e' un ostacolo alla decisione.
COSA FARE, MONITOR PER MONITOR (raccomandazione, non azione — questo script non tocca nulla).
⚠ Le due cose sono SEPARATE: la RIPARAZIONE di `advance()` (fermarsi all'ultima barra chiusa)
ha senso su tutti e sei — su un monitor sano non cambia niente e lo protegge da un cambio di
griglia futuro; la RIGENERAZIONE della serie e' un'altra decisione, e va fatta solo dove il
difetto morde abbastanza da spostare cio' che si leggera':
paper_xsr rigenerare — 41 min/g, e il gate del 23/10 legge questa serie
paper_statarb rigenerare — 4 min/g, gate 27/09: e' il piu' vicino nel tempo
paper_dvolspread rigenerare — 2 min/g, il peggiore dei sei; kill-check il 24/10
paper_prevday LASCIARE — 1.438/1.440 min/g: la rigenerazione cambierebbe lo 0,1%
della varianza e azzererebbe 1.488 barre di storia
paper_portfolio rigenerare — 19 min/g; nessun gate, ma e' la serie che il dashboard
mostra e che finisce nei diari come "il book sta facendo X"
paper_combo LASCIARE — sano; semmai va PROTETTO con un test che rompa se la sua
griglia smette di essere il calendario di borsa""")
def main() -> None: def main() -> None:
@@ -763,6 +883,7 @@ def main() -> None:
b_portfolio() b_portfolio()
b_combo() b_combo()
sezione_C() sezione_C()
sezione_C2()
sezione_D() sezione_D()
sezione_E() sezione_E()
print("\n" + "=" * 102) print("\n" + "=" * 102)
+21 -11
View File
@@ -362,11 +362,14 @@ def objective(w: dict, lev: float, firm: str, ev_idx: np.ndarray, fu_idx: np.nda
def paired_delta(wA: dict, levA: float, wB: dict, levB: float, firm: str, def paired_delta(wA: dict, levA: float, wB: dict, levB: float, firm: str,
ev_idx: np.ndarray, fu_idx: np.ndarray) -> tuple[float, float]: ev_idx: np.ndarray, fu_idx: np.ndarray,
drift_mult: tuple = ()) -> tuple[float, float]:
"""Differenza APPAIATA di J fra due configurazioni (stessi indici di bootstrap) + errore """Differenza APPAIATA di J fra due configurazioni (stessi indici di bootstrap) + errore
standard della differenza. Il livello di J ha SE ~0.01; la DIFFERENZA molto meno.""" standard della differenza. Il livello di J ha SE ~0.01; la DIFFERENZA molto meno.
a = objective(wA, levA, firm, ev_idx, fu_idx)["jvec"] `drift_mult` va passato: dimenticarlo confronta due configurazioni NON stressate e
b = objective(wB, levB, firm, ev_idx, fu_idx)["jvec"] stampa un delta che non c'entra con la riga in cui compare (errore commesso in sessione)."""
a = objective(wA, levA, firm, ev_idx, fu_idx, drift_mult=drift_mult)["jvec"]
b = objective(wB, levB, firm, ev_idx, fu_idx, drift_mult=drift_mult)["jvec"]
d = a - b d = a - b
return float(d.mean()), float(d.std(ddof=1) / np.sqrt(len(d))) return float(d.mean()), float(d.std(ddof=1) / np.sqrt(len(d)))
@@ -721,7 +724,8 @@ def main() -> None:
w_, lv_, o_ = max(rows, key=lambda x: x[2]["J"]) w_, lv_, o_ = max(rows, key=lambda x: x[2]["J"])
lvL, oL = max(((lv, objective({"TP01": 0.75, "SKH01": 0.25}, lv, "HYRO", ev_idx, fu_idx, lvL, oL = max(((lv, objective({"TP01": 0.75, "SKH01": 0.25}, lv, "HYRO", ev_idx, fu_idx,
drift_mult=dmt)) for lv in LEVS), key=lambda x: x[1]["J"]) drift_mult=dmt)) for lv in LEVS), key=lambda x: x[1]["J"])
dd_, sd_ = paired_delta(w_, lv_, {"TP01": 0.75, "SKH01": 0.25}, lvL, "HYRO", ev_idx, fu_idx) dd_, sd_ = paired_delta(w_, lv_, {"TP01": 0.75, "SKH01": 0.25}, lvL, "HYRO", ev_idx,
fu_idx, drift_mult=dmt)
print(f" {mult:>12.2f} {wkey(w_):>16} {lv_:>6.3f} {o_['J']:>7.3f} {o_['p_pass']:>8.1%} " print(f" {mult:>12.2f} {wkey(w_):>16} {lv_:>6.3f} {o_['J']:>7.3f} {o_['p_pass']:>8.1%} "
f"{o_['p_alive']:>8.1%} {o_['e_payout']:>9,.0f}$ LIVE {oL['J']:.3f} " f"{o_['p_alive']:>8.1%} {o_['e_payout']:>9,.0f}$ LIVE {oL['J']:.3f} "
f"delta {dd_:+.3f} +-{sd_:.3f}") f"delta {dd_:+.3f} +-{sd_:.3f}")
@@ -736,18 +740,24 @@ def main() -> None:
print(" l'audit 02/07 lo trova al 93-98° pctl di 23 offset, 'spike, non plateau'. Questa lente") print(" l'audit 02/07 lo trova al 93-98° pctl di 23 offset, 'spike, non plateau'. Questa lente")
print(" gira sull'ancora CANONICA e la banda non e' ri-misurabile qui -> il mio ottimo e'") print(" gira sull'ancora CANONICA e la banda non e' ri-misurabile qui -> il mio ottimo e'")
print(" distorto A FAVORE di SKH01 e va detto PRIMA di leggerlo. Taglio del drift di SKH01:") print(" distorto A FAVORE di SKH01 e va detto PRIMA di leggerlo. Taglio del drift di SKH01:")
print(f"\n {'drift SKH01':>13} {'config':>16} {'leva':>6} {'J':>7} {'J del LIVE':>11} " print(" Le ultime due righe sono il taglio CONGIUNTO su SKH01 E XS01 (i due sleeve che il")
f"{'delta appaiato':>16}") print(" progetto misura come piu' fortunati / piu' recenti): e' il caso peggiore onesto.")
for mult in (1.00, 0.66, 0.50, 0.33): print(f"\n {'taglio drift':>21} {'config':>16} {'leva':>6} {'J':>7} {'J del LIVE':>11} "
dmt = (("SKH01", mult),) f"{'delta appaiato':>17}")
for dmt in ((("SKH01", 1.00),), (("SKH01", 0.66),), (("SKH01", 0.50),), (("SKH01", 0.33),),
(("SKH01", 0.50), ("XS01", 0.50)), (("SKH01", 0.33), ("XS01", 0.00))):
mult = dict(dmt)
rows = [(w, lv, objective(w, lv, "HYRO", ev_idx, fu_idx, drift_mult=dmt)) rows = [(w, lv, objective(w, lv, "HYRO", ev_idx, fu_idx, drift_mult=dmt))
for w in top_w for lv in LEVS] for w in top_w for lv in LEVS]
w_, lv_, o_ = max(rows, key=lambda x: x[2]["J"]) w_, lv_, o_ = max(rows, key=lambda x: x[2]["J"])
lvL, oL = max(((lv, objective({"TP01": 0.75, "SKH01": 0.25}, lv, "HYRO", ev_idx, fu_idx, lvL, oL = max(((lv, objective({"TP01": 0.75, "SKH01": 0.25}, lv, "HYRO", ev_idx, fu_idx,
drift_mult=dmt)) for lv in LEVS), key=lambda x: x[1]["J"]) drift_mult=dmt)) for lv in LEVS), key=lambda x: x[1]["J"])
dd_, sd_ = paired_delta(w_, lv_, {"TP01": 0.75, "SKH01": 0.25}, lvL, "HYRO", dd_, sd_ = paired_delta(w_, lv_, {"TP01": 0.75, "SKH01": 0.25}, lvL, "HYRO",
ev_idx, fu_idx) ev_idx, fu_idx, drift_mult=dmt)
print(f" {mult:>13.2f} {wkey(w_):>16} {lv_:>6.3f} {o_['J']:>7.3f} {oL['J']:>11.3f} " lab = " ".join(f"{k.replace('01','')} x{v:.2f}" for k, v in mult.items())
assert abs(dd_ - (o_["J"] - oL["J"])) < 0.05, ( # guardia contro l'errore di sopra
f"delta appaiato {dd_:+.3f} incoerente con J {o_['J']:.3f} - {oL['J']:.3f}")
print(f" {lab:>21} {wkey(w_):>16} {lv_:>6.3f} {o_['J']:>7.3f} {oL['J']:>11.3f} "
f"{dd_:>+11.3f} +-{sd_:.3f}") f"{dd_:>+11.3f} +-{sd_:.3f}")
print(" NB: il taglio si applica anche al LIVE 75/25 (che ha SKH01 al 25%), quindi il delta e'") print(" NB: il taglio si applica anche al LIVE 75/25 (che ha SKH01 al 25%), quindi il delta e'")
print(" un confronto onesto: entrambe le colonne perdono, la domanda e' quale perde di piu'.") print(" un confronto onesto: entrambe le colonne perdono, la domanda e' quale perde di piu'.")