research(wave-0822): DEPEG — XS01-OOS sopravvive al falsificatore di venue; il caso peggiore non e' il depeg ma il 2025-10-10
This commit is contained in:
@@ -191,7 +191,12 @@ def certify_usd(asset: str, d: pd.DataFrame, a: str, b: str) -> dict:
|
||||
flat=float("nan"), zero_vol=float("nan"))
|
||||
v = series_of(d.assign(close=d["volume"]))
|
||||
v = v[(v.index >= pd.Timestamp(a, tz="UTC")) & (v.index < pd.Timestamp(b, tz="UTC"))]
|
||||
attesi = (pd.Timestamp(b, tz="UTC") - s.index.min()).days
|
||||
# ⚠️ la copertura si misura sulla finestra RICHIESTA, non sulla prima barra disponibile:
|
||||
# contando dal proprio inizio, un asset quotato a meta' finestra esce con gap=0 e "OK", e il
|
||||
# conteggio degli usabili contraddirebbe la riga sotto che lo esclude. E' lo stesso difetto
|
||||
# che il progetto ha gia' incontrato due volte (serie TRONCATA che passa ogni controllo
|
||||
# interno): un controllo che guarda solo DENTRO la serie non vede cio' che la serie non copre.
|
||||
attesi = (pd.Timestamp(b, tz="UTC") - pd.Timestamp(a, tz="UTC")).days
|
||||
c = s.values
|
||||
flat = float(np.mean(c[1:] == c[:-1])) if len(c) > 1 else float("nan")
|
||||
zv = float(np.mean(v.values == 0.0)) if len(v) else float("nan")
|
||||
@@ -294,19 +299,83 @@ def ranking_scan(PB: pd.DataFrame, PU: pd.DataFrame, phases: list[int],
|
||||
if ov < 1.0:
|
||||
diff = sorted(set(PB.columns[list(hiB ^ hiU)]) | set(PB.columns[list(loB ^ loU)]))
|
||||
rows.append(dict(fase=ph, data=PB.index[i], overlap=ov, gambe=",".join(diff)))
|
||||
D = pd.DataFrame(rows)
|
||||
D = pd.DataFrame(rows, columns=["fase", "data", "overlap", "gambe"])
|
||||
print(f" [{label}] {tot} ribilanciamenti su {len(phases)} fasi; specchio verificato "
|
||||
f"contro il motore in {checked}/{checked} con posizione")
|
||||
print(f" [{label}] Spearman dello score fra i venue: mediana {np.median(sp):.4f} "
|
||||
f"min {np.min(sp):.4f}")
|
||||
print(f" [{label}] ribilanciamenti con gambe DIVERSE: {len(D)}/{tot} "
|
||||
f"({len(D)/max(tot,1)*100:.2f}%)")
|
||||
for _, r in D.iterrows():
|
||||
print(f" fase {int(r['fase'])} {r['data'].date()} "
|
||||
f"sovrapposizione {r['overlap']*100:3.0f}% gambe scambiate: {r['gambe']}")
|
||||
if len(D):
|
||||
pre = D[D["data"] < pd.Timestamp(T2_END, tz="UTC")]
|
||||
print(f" [{label}] di cui NELLA finestra fuori campione ({T2_START}..{T2_END}): "
|
||||
f"{len(pre)} — elencati qui sotto; quelli dal 2024 in poi non sono la domanda")
|
||||
for _, r in pre.iterrows():
|
||||
print(f" fase {int(r['fase'])} {r['data'].date()} "
|
||||
f"sovrapposizione {r['overlap']*100:3.0f}% gambe scambiate: {r['gambe']}")
|
||||
return D
|
||||
|
||||
|
||||
def gate_flips(PB: pd.DataFrame, PU: pd.DataFrame, ph: int) -> list[tuple]:
|
||||
"""Il SECONDO canale, che una scansione di ranking non vede: il gate di dispersione e' BINARIO
|
||||
(`disp_i >= percentile espandente`) e quindi DISCONTINUO — due prezzi che differiscono di
|
||||
pochi bps possono cadere ai due lati della soglia, e allora uno sleeve resta pienamente
|
||||
investito per 10 giorni mentre l'altro sta FLAT. Non e' un cambio di ranking: e' un
|
||||
interruttore. Ritorna gli intervalli in cui i due venue non sono d'accordo su acceso/spento."""
|
||||
dB: dict = {}
|
||||
dU: dict = {}
|
||||
X.xsec_engine(PB, phase=ph, detail=dB)
|
||||
X.xsec_engine(PU, phase=ph, detail=dU)
|
||||
gb = np.abs(dB["W"]).sum(axis=1) > 1e-9
|
||||
gu = np.abs(dU["W"]).sum(axis=1) > 1e-9
|
||||
idx = PB.index
|
||||
flip = np.flatnonzero(gb != gu)
|
||||
runs, i0 = [], None
|
||||
for j, i in enumerate(flip):
|
||||
if i0 is None:
|
||||
i0 = i
|
||||
if j + 1 == len(flip) or flip[j + 1] != i + 1:
|
||||
runs.append((idx[i0], idx[i], int(i - i0 + 1), bool(gb[i0])))
|
||||
i0 = None
|
||||
return runs
|
||||
|
||||
|
||||
def autopsia(PB: pd.DataFrame, PU: pd.DataFrame, ph: int, D: pd.DataFrame, label: str) -> None:
|
||||
"""La fase con la corr piu' bassa NON si riassume, si guarda: una corr di 0,97 o e' un
|
||||
ribaltamento di ranking (il guasto che sto cercando) o e' un singolo giorno di rumore su una
|
||||
gamba sottile (che non e' un guasto). Sono conclusioni opposte a partire dallo stesso numero."""
|
||||
a, b = pair(alive(X.xsec_engine(PU, phase=ph)), alive(X.xsec_engine(PB, phase=ph)))
|
||||
g = (a - b)
|
||||
top = g.abs().sort_values(ascending=False).head(4)
|
||||
dph = D[D["fase"] == ph] if len(D) else D
|
||||
runs = gate_flips(PB, PU, ph)
|
||||
print(f" autopsia della fase peggiore ({label}, fase {ph}): i 4 giorni di scarto maggiore")
|
||||
for t, v in top.items():
|
||||
prev = dph[dph["data"] <= t]["data"]
|
||||
near = (t - prev.iloc[-1]).days if len(prev) else None
|
||||
inflip = [r for r in runs if r[0] <= t <= r[1]]
|
||||
# ⚠️ la coda: dopo una discordanza del gate uno dei due sleeve ha 10 giorni di ZERI in
|
||||
# piu' nella finestra rv30 del vol-target -> la LEVA dei due resta diversa per ~30 giorni
|
||||
# dopo che la discordanza e' finita. Chiamare "rumore di prezzo" un giorno dentro quella
|
||||
# coda attribuirebbe al venue un effetto che e' della soglia binaria.
|
||||
tail = [r for r in runs if r[1] < t and (t - r[1]).days <= 30]
|
||||
if inflip:
|
||||
r = inflip[0]
|
||||
tag = (f"GATE DISCORDE {r[0].date()}..{r[1].date()}: "
|
||||
f"{'USDT investito, USD flat' if r[3] else 'USD investito, USDT flat'}")
|
||||
elif tail:
|
||||
tag = (f"coda del vol-target: gate discorde fino al {tail[-1][1].date()}, "
|
||||
f"{(t - tail[-1][1]).days}g fa -> rv30 diversa, LEVA diversa")
|
||||
elif near is not None and near <= XS_CFG["H"]:
|
||||
tag = f"ultimo ribilanciamento con gambe diverse {near} giorni prima"
|
||||
else:
|
||||
tag = "stesse gambe, stesso gate: solo rumore di prezzo"
|
||||
print(f" {t.date()} scarto {g.loc[t]*1e4:+8.0f} bps ({tag})")
|
||||
print(f" scarto quadratico totale della fase: {float((g**2).sum())**0.5*1e4:.0f} bps; "
|
||||
f"i 4 giorni sopra ne spiegano "
|
||||
f"{float((top**2).sum())/float((g**2).sum())*100:.0f}%")
|
||||
|
||||
|
||||
def score_at(P: pd.DataFrame, i: int, lookbacks) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Specchio della riga di score del motore (z-score medio sui lookback). Serve per confrontare
|
||||
i RANKING fra venue; la sezione [4] verifica che i top-k/bottom-k che ne derivano coincidano
|
||||
@@ -494,6 +563,35 @@ def main() -> None:
|
||||
(D1["data"] < pd.Timestamp(DEPEG[1], tz="UTC"))]
|
||||
print(f" di cui dentro la finestra del depeg: {len(ind)}")
|
||||
|
||||
# ---------------------- [4b] il SECONDO canale: il gate binario, e l'attribuzione
|
||||
print("\n[4b] IL GATE DI DISPERSIONE E' UN INTERRUTTORE — e una scansione di ranking non lo")
|
||||
print(" vede. `disp_i >= percentile espandente` e' una soglia: pochi bps di differenza di")
|
||||
print(" prezzo possono cadere ai due lati, e per 10 giorni uno sleeve e' pieno e l'altro")
|
||||
print(" e' FLAT. E' l'unico modo in cui una differenza di venue microscopica puo'")
|
||||
print(" produrre una differenza di P&L macroscopica.")
|
||||
for label_, Pb_, Pu_ in (("TEST 1 / U10", PB, PU),):
|
||||
tot_fl = 0
|
||||
for ph in PHASES:
|
||||
for a_, b_, nd, bfirst in gate_flips(Pb_, Pu_, ph):
|
||||
tot_fl += 1
|
||||
print(f" [{label_}] fase {ph}: {a_.date()} .. {b_.date()} ({nd}g) — "
|
||||
f"{'USDT investito / USD FLAT' if bfirst else 'USD investito / USDT FLAT'}")
|
||||
if tot_fl == 0:
|
||||
print(f" [{label_}] nessuna discordanza del gate su 10 fasi")
|
||||
else:
|
||||
print(f" [{label_}] {tot_fl} intervalli di discordanza su 10 fasi")
|
||||
|
||||
print("\n ATTRIBUZIONE — stesso confronto con il gate SPENTO su ENTRAMBI i venue")
|
||||
print(" (diagnostica, NON una proposta di cambiare il meccanismo: serve a separare")
|
||||
print(" 'il prezzo e' diverso' da 'la soglia binaria amplifica una differenza minuscola'):")
|
||||
cg = []
|
||||
for ph in PHASES:
|
||||
a_, b_ = pair(alive(X.xsec_engine(PU, phase=ph, disp_pct=0)),
|
||||
alive(X.xsec_engine(PB, phase=ph, disp_pct=0)))
|
||||
cg.append(float(a_.corr(b_)))
|
||||
print(f" [TEST 1] corr con gate SPENTO: mediana {np.median(cg):.4f} minima {np.min(cg):.4f}"
|
||||
f" (col gate acceso: mediana {R['corr'].median():.4f}, minima {R['corr'].min():.4f})")
|
||||
|
||||
# ------------------------------- [5] TEST 2 — U13 ibrido, finestra INTERA di §21
|
||||
print(f"\n[5] TEST 2 — U13 IBRIDO (USD dove esiste, USDT altrove), {T2_START} .. {T2_END}")
|
||||
print(" Domanda operativa: sostituendo TUTTO il prezzo in dollari che esiste, il titolo")
|
||||
@@ -537,6 +635,24 @@ def main() -> None:
|
||||
print("\n E il ranking, sull'ibrido (la fase con la corr piu' bassa va GUARDATA, non")
|
||||
print(" riassunta: una corr di 0,97 su una fase o e' un ribaltamento vero o e' un giunto):")
|
||||
D2 = ranking_scan(PBh, PUh, PHASES, "TEST 2 / U13-IBRIDO")
|
||||
print("\n Discordanze del GATE binario sull'ibrido (finestra fuori campione):")
|
||||
nfl = 0
|
||||
for ph in PHASES:
|
||||
for a_, b_, nd, bfirst in gate_flips(PBh, PUh, ph):
|
||||
if a_ >= pd.Timestamp(T2_END, tz="UTC"):
|
||||
continue
|
||||
nfl += 1
|
||||
print(f" fase {ph}: {a_.date()} .. {b_.date()} ({nd}g) — "
|
||||
f"{'USDT investito / USD FLAT' if bfirst else 'USD investito / USDT FLAT'}")
|
||||
print(f" totale {nfl} intervalli su 10 fasi (su ~204 ribilanciamenti per fase)")
|
||||
cg2 = []
|
||||
for ph in PHASES:
|
||||
a_, b_ = pair(win(alive(X.xsec_engine(PUh, phase=ph, disp_pct=0)), T2_START, T2_END),
|
||||
win(alive(X.xsec_engine(PBh, phase=ph, disp_pct=0)), T2_START, T2_END))
|
||||
cg2.append(float(a_.corr(b_)))
|
||||
print(f" ATTRIBUZIONE [TEST 2] corr con gate SPENTO: mediana {np.median(cg2):.4f} "
|
||||
f"minima {np.min(cg2):.4f} (col gate acceso: mediana {R2['corr'].median():.4f}, "
|
||||
f"minima {R2['corr'].min():.4f})")
|
||||
|
||||
# ------------------------------- [5b] le gambe che NESSUN venue USD puo' controllare
|
||||
print("\n[5b] LE TRE GAMBE NON CONTROLLABILI — quanto del risultato di §21 ci passa?")
|
||||
@@ -588,15 +704,44 @@ def main() -> None:
|
||||
f"T2 fase {int(worst2['ph'])} corr {worst2['corr']:.4f}")
|
||||
print(f" peggior FASE per dSharpe: T1 fase {int(wd1['ph'])} {wd1['d']:+.3f} | "
|
||||
f"T2 fase {int(wd2['ph'])} {wd2['d']:+.3f}")
|
||||
autopsia(PB, PU, int(worst1["ph"]), D1, "TEST 1")
|
||||
autopsia(PBh, PUh, int(worst2["ph"]), D2, "TEST 2")
|
||||
|
||||
st = idio.abs().stack().sort_values(ascending=False)
|
||||
print(" le 8 divergenze IDIOSINCRATICHE piu' grandi della finestra (bps di livello):")
|
||||
print(" le 8 divergenze IDIOSINCRATICHE piu' grandi della finestra del TEST 1 (bps):")
|
||||
for (t, s), v in st.head(8).items():
|
||||
indep = "DEPEG" if pd.Timestamp(DEPEG[0], tz="UTC") <= t < pd.Timestamp(DEPEG[1], tz="UTC") else ""
|
||||
print(f" {str(t.date()):<12}{s:<6}{v:8.0f} bps (comune quel giorno "
|
||||
f"{common.loc[t]:+.0f} bps) {indep}")
|
||||
print(" -> se le peggiori NON cadono nel depeg, il rumore di venue e' book sottile,")
|
||||
print(" non contaminazione da stablecoin: due cause diverse, due implicazioni diverse.")
|
||||
print(" -> le peggiori NON cadono nel depeg e sono quasi tutte AVAX nelle settimane subito")
|
||||
print(" dopo la sua quotazione su Coinbase: book sottile, non contaminazione da")
|
||||
print(" stablecoin. Due cause diverse e due implicazioni diverse.")
|
||||
|
||||
# --------- il caso peggiore su TUTTO il campione, non solo sulla finestra del test
|
||||
print("\n ⚠️ E ORA LA DOMANDA GIUSTA, allargata: dove divergono di piu' i due venue in")
|
||||
print(" TUTTO il campione 2021-2026? (cercare il caso peggiore solo dentro la finestra")
|
||||
print(" che si sta difendendo e' un modo di non trovarlo)")
|
||||
have = [s for s in U13 if USD_SRC[s] is not None]
|
||||
Bf = bin_panel(have)
|
||||
Uf = usd_panel(have).reindex(Bf.index)
|
||||
bo = Bf.notna() & Uf.notna()
|
||||
Df = (1e4 * np.log(Uf / Bf)).where(bo)
|
||||
idf = Df.sub(Df.median(axis=1), axis=0)
|
||||
worst_day = idf.abs().max(axis=1).sort_values(ascending=False)
|
||||
print(f" {'giorno':<12}{'|idio| max':>11}{'asset':>7}{'comune':>9} finestra")
|
||||
for t in worst_day.head(6).index:
|
||||
s = idf.loc[t].abs().idxmax()
|
||||
w = "fuori campione" if t < pd.Timestamp(T2_END, tz="UTC") else "scoperta 2024+"
|
||||
print(f" {str(t.date()):<12}{idf.loc[t, s]:>11.0f}{s:>7}{Df.loc[t].median():>9.0f} {w}")
|
||||
tw = worst_day.index[0]
|
||||
print(f" il giorno peggiore in assoluto e' il {tw.date()}: scarto per asset (bps)")
|
||||
print(" " + " ".join(f"{s}:{idf.loc[tw, s]:.0f}" for s in have
|
||||
if np.isfinite(idf.loc[tw, s])))
|
||||
print(" 📌 E' un fatto che va detto per intero: la divergenza idiosincratica PUO'")
|
||||
print(" esplodere — qui di due ordini di grandezza — ma e' successo in una CASCATA DI")
|
||||
print(" LIQUIDAZIONI sulle coppie /USDT, non in un depeg, e cade nella finestra di")
|
||||
print(" SCOPERTA (2024+, dove lo sleeve gira su Hyperliquid), non nel fuori campione.")
|
||||
print(" Il meccanismo temuto dall'obiezione e' REALE; non e' accaduto nel 2021-2023.")
|
||||
|
||||
# il giorno peggiore: dove il P&L dei due sleeve si stacca di piu'
|
||||
a0 = alive(X.xsec_engine(PU, phase=0)); b0 = alive(X.xsec_engine(PB, phase=0))
|
||||
@@ -615,7 +760,11 @@ def main() -> None:
|
||||
print(f" TEST 1 U10 congelato : corr mediana {R['corr'].median():.4f}, minima "
|
||||
f"{R['corr'].min():.4f} -> {'PASS' if R['corr'].min() >= CORR_SOGLIA else 'FAIL'}")
|
||||
print(f" TEST 2 U13 ibrido : corr mediana {R2['corr'].median():.4f}, minima "
|
||||
f"{R2['corr'].min():.4f} -> {'PASS' if R2['corr'].min() >= CORR_SOGLIA else 'FAIL'}")
|
||||
f"{R2['corr'].min():.4f} -> {'PASS' if R2['corr'].min() >= CORR_SOGLIA else 'FAIL'}"
|
||||
f" (una fase su 10; col gate spento minima {np.min(cg2):.4f} = PASS)")
|
||||
print(f" ⚠️ il TEST 2 e' MIO, non quello dichiarato: e' piu' severo per costruzione")
|
||||
print(f" (le gambe cambiano venue in date diverse) e la sua unica fase sotto soglia")
|
||||
print(f" e' attribuita al gate binario, non al prezzo.")
|
||||
print(f" riferimento 2024+ di §21 (Binance vs Hyperliquid): 0,9991 / dSharpe -0,025")
|
||||
su = alive(X.xsec_engine(PUh, phase=0))
|
||||
mg = A.marginal_vs_tp01(win(su, T2_START, T2_END))
|
||||
@@ -633,6 +782,28 @@ def main() -> None:
|
||||
print(" null de-levering: NON PERTINENTE (nessun claim di 'meno drawdown').")
|
||||
print(" anchor: girato per costruzione — ogni numero sopra e' su tutte e 10 le fasi.")
|
||||
|
||||
# ------------------------------------------------------------ [8] verdetto
|
||||
print("\n[8] VERDETTO")
|
||||
print(f" Il TEST DICHIARATO (meccanismo congelato, stesso sottoinsieme, stessa finestra,")
|
||||
print(f" USD contro USDT) PASSA: corr mediana {R['corr'].median():.4f}, minima "
|
||||
f"{R['corr'].min():.4f} >= {CORR_SOGLIA}, dSharpe appaiato {R['d'].median():+.3f}.")
|
||||
print(f" Il titolo di §21 ricalcolato in dollari si muove di {R2['sh_usd'].median()-R2['sh_usdt'].median():+.2f} "
|
||||
f"({R2['sh_usdt'].median():+.2f} -> {R2['sh_usd'].median():+.2f} di mediana di fase).")
|
||||
print(f" Nella finestra del depeg la componente idiosincratica sale (med {idm.median():.1f} bps "
|
||||
f"contro {idio[~dm].abs().stack().median():.1f}) ma resta due ordini di grandezza sotto")
|
||||
print(f" la vol giornaliera di un alt, e ZERO ribilanciamenti cambiano gambe li' dentro.")
|
||||
print(f" L'unica corr sotto soglia (TEST 2, fase 2: {R2['corr'].min():.4f}) e' attribuita:")
|
||||
print(f" col gate di dispersione SPENTO la minima diventa {np.min(cg2):.4f} -> non e' il")
|
||||
print(f" prezzo, e' la soglia BINARIA che amplifica pochi bps in 10 giorni di flat.")
|
||||
print( " -> IL RISULTATO XS01-OOS SOPRAVVIVE al controllo di venue.")
|
||||
print( " Cio' che NON e' stato controllato, e va citato insieme al verdetto: BNB non ha un")
|
||||
print( " prezzo USD nel 2021-23 (e' il token nativo del venue sotto esame), e toglierlo")
|
||||
print( " costa -0.75 di mediana di fase. Il test copre 10-12 gambe su 13, non 13.")
|
||||
print(f" E il caso peggiore di divergenza dell'INTERO campione ({tw.date()}, "
|
||||
f"{idf.loc[tw].abs().max():.0f} bps su una gamba)")
|
||||
print( " dimostra che il meccanismo temuto e' reale: semplicemente non e' accaduto nella")
|
||||
print( " finestra fuori campione. Non e' 'non puo' succedere', e' 'non e' successo li''.")
|
||||
|
||||
print(f"\n[fine] {time.time()-t0:.1f}s")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user