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
+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]`
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:
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%)
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
@@ -42,11 +42,18 @@ Poi:
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.
COSA MI ASPETTAVO PRIMA DI MISURARE: che i tre monitor giornalieri su feed crypto (xsr, statarb,
dvolspread) avessero la stessa patologia identica; che `paper_prevday`, essendo su griglia 1h con
24 barre al giorno, ne avesse una MOLTO piu' mite (una barra su 24 troncata); e che
`paper_combo`, che vive sul calendario di borsa, fosse o rotto come gli altri o sano per caso.
Solo la terza aspettativa e' stata una sorpresa.
COSA MI ASPETTAVO PRIMA DI MISURARE, e cosa e' andato diversamente:
* i tre monitor giornalieri su feed crypto (xsr, statarb, dvolspread) con la STESSA patologia,
intorno ai ~35 minuti al giorno di XSR01 -> **meta' giusto**: la patologia e' la stessa ma la
TAGLIA no (2, 4 e 41 min/giorno). Un libro dollar-neutral perde molto piu' di un libro
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
"""
@@ -289,7 +296,8 @@ def b_xsr():
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]))
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():
@@ -323,17 +331,18 @@ def b_statarb():
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 "
"del feed.")
ev = freeze_evidence()
ev = sorted(freeze_evidence())
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 ''}) — "
"EPERM sul backup, difetto gia' riparato\n nel sorgente. Col feed fermo il monitor "
"non trovava barre nuove; al ripristino ne ha\n recuperate 7 in un colpo solo, di "
"cui 6 GIA' CHIUSE -> coincidono col replay al bit.")
f"{len(ev)} giri consecutivi")
print(f" ({ev[0]} -> {ev[-1]}), EPERM sul backup — difetto gia' riparato nel sorgente.")
print(" Col feed fermo il monitor non trovava barre nuove; al ripristino ne ha")
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(" 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.")
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():
@@ -374,7 +383,7 @@ def b_dvolspread():
RES["paper_dvolspread"] = dict(parziale="SI", cmp=out["clean"],
gate="DVOLSPREAD 2026-10-24 / 2027-01-24",
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():
@@ -429,7 +438,7 @@ def b_prevday():
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.")
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():
@@ -457,8 +466,9 @@ def b_portfolio():
if ident.any():
print(f"\n ⚠ TRAPPOLA DA NON PRENDERE: delle {int(ident.sum())} barre 'identiche', "
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(" gli sleeve erano flat (TP01 risk-off, GTAA a borsa chiusa, VRP fuori scadenza).")
print(" (" + ", ".join(str(x.date()) for x in T[zeri]) + ")")
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(" 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 "
@@ -474,7 +484,7 @@ def b_portfolio():
RES["paper_portfolio"] = dict(parziale="SI", cmp=out["clean"],
gate="nessuno (dashboard/book di ricerca)",
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():
@@ -514,7 +524,7 @@ def b_combo():
print(" senza che nulla lo segnali.")
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,
ric="SI* (equity IB ri-aggiustata)")
ric="SI non al bit")
# ============================================================================================
@@ -522,7 +532,7 @@ def b_combo():
# ============================================================================================
GATE_INFO = {
"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_prevday": ("-", "lead senza data"),
"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(" 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(f"\n {'monitor':<19}{'parz.':>6}{'ident./tot':>13}{'corr':>9}{'min/g':>9}"
f"{'ricostruibile':>25} gate dipendente")
print(f"\n {'monitor':<19}{'barra parz.':>13}{'ident./tot':>13}{'corr':>9}{'min/g':>8}"
f"{'ricostruibile':>17} gate dipendente")
for k, v in RES.items():
c = v["cmp"]
ratio = "{}/{}".format(c["ident"], c["n"])
print(f" {k:<19}{v['parziale']:>6}{ratio:>13}{c['corr']:>+9.3f}"
f"{c['minuti']:>9,.0f}{v['ric']:>25} {GATE_INFO[k][0]}")
print(f" {k:<19}{v['parziale']:>13}{ratio:>13}{c['corr']:>+9.3f}"
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(" 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(" ri-aggiustato all'indietro (`ADJUSTED_LAST`).")
print(" oggi dai feed su disco: e' letteralmente la colonna 'replay' di questo script.")
print(" 'non al bit' = ricostruibile, ma una gamba viene dal feed equity IB, che e'")
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(" 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.")
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
# ============================================================================================
@@ -608,8 +701,10 @@ def sezione_D():
if v["parziale"] == "SI" and seg == "tace":
miss.append(k)
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')}"
f": {', '.join(miss) if miss else '-'}")
rotti = sum(1 for v in RES.values() if v["parziale"] == "SI")
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(" 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")
@@ -697,17 +792,23 @@ def sezione_E():
("paper_dvolspread", "DVOLSPREAD (kill)", pd.Timestamp("2026-10-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}")
ritardi = []
for mon, nome, gate, ser in righe:
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)
rit = (rip - gate).days
print(f" {f'{mon} / {nome}':<34}{n:>12}{str(gate.date()):>13}"
f"{str(rip.date()):>12}{rit:>+9}g")
print("\n Cosi' letta, la riparazione sposta i tre gate di 1-2 mesi. MA la colonna giusta e'")
print(" un'altra, e cambia la decisione:")
ritardi.append((nome, rit))
print(f" {f'{mon} / {nome}':<42}{n:>7}{str(gate.date()):>13}"
f"{str(rip.date()):>18}{rit:>+9}g")
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("""
✅ 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
@@ -719,10 +820,12 @@ def sezione_E():
minuti.
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
1426 barre su 1488 identiche BIT A BIT al replay dopo 62 giorni di riscritture
notturne del parquet BTC/ETH (sezione B4). Per Hyperliquid la stessa cosa e' misurata
in `r0822_xsr_repro` T2 (Sharpe 2024 e 2025 riprodotti al centesimo su 731 barre).
(1) il feed non riscrive le barre gia' chiuse. **MISURATO qui, due volte e in modo
indipendente**: `paper_prevday` ha 1427 barre su 1488 identiche BIT A BIT al replay
dopo 62 notti di riscrittura del parquet BTC/ETH (B4); e le 6 barre che `paper_statarb`
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
(`frozen` nello stato, esce 1 se diverge); per gli altri il congelamento e' nel
sorgente ed e' in git.
@@ -743,11 +846,28 @@ def sezione_E():
verificabili in git (lo sono).
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
ribilanci sotto il min-order, e quella decisione dipende dal capitale corrente, quindi il
percorso REAL rigenerato non e' il percorso REAL registrato — non e' "lo stesso numero
calcolato meglio", e' un secondo percorso deterministico. Per XSR01 conta: il gate del 23/10
legge net_REAL-$5000 per l'haircut.""")
⚠ UNA COSA CHE LA RIGENERAZIONE CAMBIA DAVVERO, ma di poco — e il "di poco" e' MISURATO, non
assunto. I libri REAL saltano i ribilanci sotto il min-order e quella decisione dipende dal
capitale corrente: il percorso REAL rigenerato non e' "lo stesso numero calcolato meglio", e'
un secondo percorso deterministico. Contava sapere quanto, perche' il gate XSR01 del 23/10
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:
@@ -763,6 +883,7 @@ def main() -> None:
b_portfolio()
b_combo()
sezione_C()
sezione_C2()
sezione_D()
sezione_E()
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,
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
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"]
b = objective(wB, levB, firm, ev_idx, fu_idx)["jvec"]
standard della differenza. Il livello di J ha SE ~0.01; la DIFFERENZA molto meno.
⚠ `drift_mult` va passato: dimenticarlo confronta due configurazioni NON stressate e
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
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"])
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"])
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%} "
f"{o_['p_alive']:>8.1%} {o_['e_payout']:>9,.0f}$ LIVE {oL['J']:.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(" 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(f"\n {'drift SKH01':>13} {'config':>16} {'leva':>6} {'J':>7} {'J del LIVE':>11} "
f"{'delta appaiato':>16}")
for mult in (1.00, 0.66, 0.50, 0.33):
dmt = (("SKH01", mult),)
print(" Le ultime due righe sono il taglio CONGIUNTO su SKH01 E XS01 (i due sleeve che il")
print(" progetto misura come piu' fortunati / piu' recenti): e' il caso peggiore onesto.")
print(f"\n {'taglio drift':>21} {'config':>16} {'leva':>6} {'J':>7} {'J del LIVE':>11} "
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))
for w in top_w for lv in LEVS]
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,
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)
print(f" {mult:>13.2f} {wkey(w_):>16} {lv_:>6.3f} {o_['J']:>7.3f} {oL['J']:>11.3f} "
ev_idx, fu_idx, drift_mult=dmt)
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}")
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'.")