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'.")