"""r0822b_xs01_oos.py — XS01 FUORI dalla sua finestra di scoperta (2026-08-22, ondata wave-0822). IL PROBLEMA (filone PROP-ALLOC, RESULTS-0822 §1) ------------------------------------------------ Il risultato piu' vicino all'obiettivo dell'operatore e': su un conto FUNDED da $100k, aggiungere XS01 al libro porta J = P(pass) x P(vivo 12m) da 0,335 a 0,738. Ma **tutto** quel vantaggio poggia sul drift di XS01 misurato sulla SUA STESSA finestra di scoperta (Hyperliquid, 2024-01 -> oggi, ~2,6 anni, e senza un solo mercato orso). Azzerando quel drift l'uplift scende a +0,038; sotto stress congiunto diventa negativo. Il gate proposto era "non aprire un funded finche' XS01 non ha una finestra fuori dal 2024-2026" = ANNI di attesa. LA DOMANDA DI QUESTO FILONE --------------------------- Si puo' testare il MECCANISMO CONGELATO su una storia PIU' LUNGA e INDIPENDENTE invece di aspettare? La risposta e' si', e il vincolo non e' il dato ma il CALENDARIO DELLE QUOTAZIONI: 6 dei 19 asset (APT, OP, ARB, SUI, SEI, TIA) non esistono prima del 2022-06/2023-10. Il piu' vecchio universo >= 10 asset dei 19 e' del **2020-10** -> circa **3 anni interi fuori dalla finestra di scoperta, 2021-01 -> 2023-12**, che contengono il top 2021, il crollo di maggio 2021, **LUNA e FTX (2022)** e la ripresa 2023. E' esattamente il regime che manca al campione di scoperta. IL DATO: BINANCE, E PERCHE' L'OBIEZIONE DEL PROGETTO NON MORDE QUI ----------------------------------------------------------------- CLAUDE.md: "Binance NON e' la verita' (e' USDT, ~10 bps fuori dal consenso, e sotto depeg fino al 3%) -> usare SOLO come audit indipendente, mai come ancora per ripulire i dati". Quella regola riguarda l'uso come **ANCORA DI PREZZO** (livello). Qui Binance serve come **audit indipendente per un segnale CROSS-SECTIONAL DI RANKING su barre giornaliere**, e i tre difetti citati sono tutti **fattori COMUNI alla valuta di quotazione**: - uno scarto costante di ~10 bps sul livello e' identico su tutti e 19 gli asset -> sparisce ESATTAMENTE nello z-score cross-sectional (che sottrae la media di sezione); - un depeg USDT muove tutte le gambe insieme -> non cambia il RANKING, e il libro e' dollar-neutral (0.5/k long, -0.5/k short) quindi il fattore comune si cancella anche nel P&L; - a 1d un errore di livello di 10 bps e' 1/50 della vol giornaliera tipica di un alt. NON e' un argomento teorico: e' MISURATO qui sotto (sezione 3) confrontando il meccanismo su Binance e su Hyperliquid nella finestra in cui esistono ENTRAMBI (2024+). E i dati scaricati sono CERTIFICATI comunque (barre flat, gap di calendario, volumi zero = backfill sintetico: il progetto ci e' gia' cascato una volta, correzione AXS del 2026-06-20). MECCANISMO: CONGELATO. Nessun parametro toccato. E' `src/portfolio/sleeves.py::_xsec_returns` (lookbacks (30,90), H=10, k=5, mode="mom", target_vol=0.20, gate dispersione p30 espandente causale, fee 5 bps/lato, vol-target su rv30 shiftata, clip leva 3x). Il motore qui sotto e' una versione MASCHERATA (regge NaN = asset non ancora quotato) e la sezione 2 dimostra che riproduce lo sleeve ufficiale a **max|diff| = 0.0** sui dati HL. ESITO (2026-08-22, corsa completa 78 s) --------------------------------------- - Il motore replica lo sleeve ufficiale a max|diff| = 0.0; Binance e Hyperliquid danno lo STESSO sleeve sulla finestra comune (corr 0.9991, dSharpe -0.025) -> la storia lunga e' utilizzabile. - Fuori dalla finestra di scoperta (2021-01 -> 2023-12, contiene LUNA e FTX) il meccanismo ha un edge: Sharpe MEDIANO di fase **+1.12** (U13 fisso) / **+1.17** (universo espandente), positivo in **10/10** fasi, p = 0.013 contro il null di permutazione cross-sezionale a fee zero. - **Il fuori campione e' MIGLIORE della finestra di scoperta**, a parita' di universo: differenza appaiata per fase +0.668 (10/10 fasi). L'ipotesi "il drift di XS01 e' un artefatto del 2024-2026" e' REFUTATA per la parte testabile del sleeve. - MA: il maxDD raddoppia (10.8% nella scoperta -> 20.9-22.0% mediano fuori), il 2021 vale meta' dell'edge, una gamba sola (LTC) ne vale 0.9 di Sharpe, e i due affinamenti del 2026-06-19 non si replicano. E il deflated-Sharpe FALLISCE (0.342-0.418). - Sul LIBRO, fuori campione: TP01+SKH01 75/25 Sharpe +1.17 -> **+1.65** e maxDD 9.4% -> **6.0%** a peso XS01 0.25; a peso **0.50** (l'argmax di PROP-ALLOC) il maxDD risale a **8.9%**. Uso: nice -n 19 timeout 900 uv run python scripts/research/r0822b_xs01_oos.py Rete: solo lettura, <= 2 req/s, MAI nella finestra :25-:29 (rate limit per-IP condiviso col collettore della catena). Il download e' messo in cache nello scratchpad: la seconda corsa e' offline. """ from __future__ import annotations import math import os import sys import time from datetime import datetime, timezone from pathlib import Path import numpy as np import pandas as pd ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt")) import altlib as A # noqa: E402 from src.portfolio.sleeves import XS_CFG, XS_UNIVERSE # noqa: E402 CACHE = Path(os.environ.get( "R0822B_CACHE", "/tmp/claude-1001/-opt-docker-PythagorasGoal/b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/r0822b", )) CACHE.mkdir(parents=True, exist_ok=True) RAW = ROOT / "data" / "raw" FEE_SIDE = 0.001 / 2.0 # identica al sleeve congelato DISCOVERY = pd.Timestamp("2024-01-01", tz="UTC") # inizio della finestra di scoperta (HL) # I 13 dei 19 quotati su Binance prima del 2020-11 -> l'universo FISSO piu' lungo possibile # che rispetta il vincolo del meccanismo congelato (>= 10 gambe). U13 = ["BTC", "ETH", "BNB", "XRP", "LTC", "ADA", "LINK", "DOGE", "SOL", "AVAX", "NEAR", "AAVE", "INJ"] SECTORS = ["xlb", "xlc", "xle", "xlf", "xli", "xlk", "xlp", "xlre", "xlu", "xlv", "xly"] # =========================================================================== # 0. disciplina di rete # =========================================================================== def _outside_forbidden_window() -> None: """Il collettore della catena gira al minuto :25 e satura il rate limit per-IP di Deribit; la regola d'ondata e' non toccare la rete fra :25 e :29. Qui parliamo con Binance, ma la disciplina e' della MACCHINA (2 core, libro con soldi veri), non del venue.""" while True: m = datetime.now(timezone.utc).minute if not (25 <= m <= 29): return print(f" [rete] minuto :{m:02d} nella finestra vietata :25-:29 — attendo 30s", flush=True) time.sleep(30) # =========================================================================== # 1. FETCH + CERTIFY (Binance spot USDT, 1d) # =========================================================================== def fetch_binance_daily(sym: str, since_iso: str = "2017-01-01T00:00:00Z") -> pd.DataFrame: f = CACHE / f"bin_{sym.lower()}_1d.parquet" if f.exists(): return pd.read_parquet(f) import ccxt ex = ccxt.binance({"enableRateLimit": True}) since = ex.parse8601(since_iso) rows, cur = [], since while True: _outside_forbidden_window() batch = ex.fetch_ohlcv(f"{sym}/USDT", "1d", since=cur, limit=1000) time.sleep(0.5) # <= 2 req/s if not batch: break rows.extend(batch) if len(batch) < 1000: break cur = batch[-1][0] + 86_400_000 d = pd.DataFrame(rows, columns=["timestamp", "open", "high", "low", "close", "volume"]) d = d.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True) d.to_parquet(f) return d def certify(sym: str, d: pd.DataFrame) -> dict: """Certificazione locale, stessa forma di scripts/analysis/fetch_hyperliquid.py. Il rivelatore del backfill SINTETICO NON e' il prezzo (cerbero copiava i prezzi da un altro venue) ma il **VOLUME**: un run di barre a volume 0 e' padding, non mercato.""" t = pd.to_datetime(d["timestamp"], unit="ms", utc=True) c = d["close"].values.astype(float) v = d["volume"].values.astype(float) span = (t.max() - t.min()).days + 1 flat = float(np.mean(c[1:] == c[:-1])) if len(c) > 1 else float("nan") zero_v = float(np.mean(v == 0.0)) # run iniziale a volume zero = firma del padding sintetico (lezione AXS 2026-06-20) lead0 = int(np.argmax(v > 0)) if (v > 0).any() else len(v) return dict(sym=sym, n=len(d), start=str(t.min().date()), end=str(t.max().date()), gap_days=int(span - len(d)), flat=flat, zero_vol=zero_v, lead_zero_vol=lead0, ok=bool(span - len(d) == 0 and flat < 0.02 and zero_v < 0.01 and lead0 == 0)) def load_panel(syms: list[str]) -> pd.DataFrame: """Pannello dei CLOSE, indice giornaliero UTC, NaN dove l'asset non e' ancora quotato.""" cols = {} for s in syms: d = fetch_binance_daily(s) cols[s] = pd.Series(d["close"].values.astype(float), index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)) return pd.concat(cols, axis=1).sort_index() def load_sector_panel() -> pd.DataFrame: cols = {} for s in SECTORS: d = pd.read_parquet(RAW / f"eq_{s}_1d.parquet") cols[s.upper()] = pd.Series(d["close"].values.astype(float), index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)) return pd.concat(cols, axis=1).sort_index() # =========================================================================== # 2. MOTORE — meccanismo CONGELATO, versione mascherata (regge i NaN) # =========================================================================== def xsec_engine(P: pd.DataFrame, phase: int = 0, fee_side: float = FEE_SIDE, mode: str | None = None, min_assets: int = 10, perm_rng: np.random.Generator | None = None, perm_kind: str = "cross", lookbacks=None, disp_pct=None, detail: dict | None = None) -> pd.Series: """Riproduce `sleeves._xsec_returns` riga per riga. Unica differenza: l'insieme ammissibile e' calcolato PER RIBILANCIAMENTO (un asset entra quando ha almeno max(lookbacks) barre), cosi' il meccanismo si puo' applicare a un universo che si allarga nel tempo — che e' cio' che un implementatore in tempo reale avrebbe avuto. Con un pannello senza NaN, e' identico all'originale. `perm_rng` attiva un NULL: 'cross' = permuta lo score FRA GLI ASSET al ribilanciamento (distrugge l'informazione, conserva ESATTAMENTE la struttura di posizione e di turnover), 'sign' = pesi casuali fra gli ammissibili (stesso gross, stesso k).""" px = P.values.astype(float) n, Ncols = px.shape H, k = XS_CFG["H"], XS_CFG["k"] lookbacks = tuple(XS_CFG["lookbacks"] if lookbacks is None else lookbacks) disp_pct = XS_CFG["disp_pct"] if disp_pct is None else disp_pct tv, minhist = XS_CFG["target_vol"], XS_CFG["disp_minhist"] mode = mode or XS_CFG["mode"] mlb = max(lookbacks) with np.errstate(invalid="ignore", divide="ignore"): dret = np.vstack([np.zeros(Ncols), px[1:] / px[:-1] - 1.0]) dret = np.nan_to_num(dret, nan=0.0, posinf=0.0, neginf=0.0) W = np.zeros((n, Ncols)); w = np.zeros(Ncols); disp_hist: list[float] = [] n_rebal = n_traded = 0 for i in range(n): if i >= mlb and (i % H) == phase: elig = np.isfinite(px[i]) for L in lookbacks: elig = elig & np.isfinite(px[i - L]) m = int(elig.sum()) if m >= min_assets: n_rebal += 1 idx = np.flatnonzero(elig) rLs = [px[i, idx] / px[i - L, idx] - 1.0 for L in lookbacks] disp_i = float(np.mean([r.std() for r in rLs])) thr = (np.percentile(disp_hist, disp_pct) if (disp_pct > 0 and len(disp_hist) >= minhist) else -np.inf) if disp_i >= thr: score = np.zeros(m); cnt = 0 for rL in rLs: sd = rL.std() if sd > 0: score += (rL - rL.mean()) / sd; cnt += 1 if cnt: score /= cnt w = np.zeros(Ncols) if perm_rng is not None and perm_kind == "sign": pick = perm_rng.permutation(m) lo, hi = idx[pick[:k]], idx[pick[-k:]] else: if perm_rng is not None: # perm_kind == "cross" score = score[perm_rng.permutation(m)] order = np.argsort(score) lo, hi = idx[order[:k]], idx[order[-k:]] if mode == "mom": w[hi] = 0.5 / k; w[lo] = -0.5 / k else: w[lo] = 0.5 / k; w[hi] = -0.5 / k n_traded += 1 else: w = np.zeros(Ncols) disp_hist.append(disp_i) W[i] = w gross = np.zeros(n); gross[1:] = np.sum(W[:-1] * dret[1:], axis=1) turn = np.zeros(n); turn[0] = np.abs(W[0]).sum() turn[1:] = np.abs(np.diff(W, axis=0)).sum(axis=1) net = gross - turn * fee_side s = pd.Series(net, index=P.index) rv = s.rolling(30, min_periods=15).std().shift(1) * np.sqrt(365.25) scale = np.clip(np.nan_to_num(tv / rv.replace(0, np.nan).values, nan=0.0), 0, 3.0) out = pd.Series(s.values * scale, index=P.index) out.attrs["n_rebal"] = n_rebal out.attrs["n_traded"] = n_traded if detail is not None: detail.update(W=W, scale=scale, turn=turn, index=P.index, n_rebal=n_rebal, n_traded=n_traded) return out def hl_panel(syms=None) -> pd.DataFrame: """Pannello Hyperliquid, ESATTAMENTE come lo costruisce lo sleeve congelato.""" cols = {} for sym in (syms or XS_UNIVERSE): p = RAW / f"hl_{sym.lower()}_1d.parquet" if not p.exists(): continue d = pd.read_parquet(p) cols[sym] = pd.Series(d["close"].values.astype(float), index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)) return pd.concat(cols, axis=1, join="inner").sort_index().dropna() # =========================================================================== # metriche # =========================================================================== def sh(s: pd.Series) -> float: return A._sh(s) def dd(s: pd.Series) -> float: return A._dd_ret(s) def cagr(s: pd.Series) -> float: r = s.dropna() if len(r) < 30: return float("nan") yrs = (r.index[-1] - r.index[0]).days / 365.25 return float(np.prod(1.0 + r.values) ** (1.0 / yrs) - 1.0) if yrs > 0 else float("nan") def win(s: pd.Series, a: str | None = None, b: str | None = None) -> pd.Series: x = s if a: x = x[x.index >= pd.Timestamp(a, tz="UTC")] if b: x = x[x.index < pd.Timestamp(b, tz="UTC")] return x def alive(s: pd.Series) -> pd.Series: """Taglia il prefisso di ZERI (warm-up dei lookback + attesa che l'universo raggiunga 10 gambe). ⚠️ Bug catturato nella prima corsa: sull'universo ESPANDENTE il pannello parte dal 2017 (BTC) ma il meccanismo non puo' operare prima del 2020-12, e i ~1.200 zeri iniziali entravano nella finestra 'PRE-2024' abbassandone lo Sharpe e ribaltando la differenza appaiata. E' la lezione gia' codificata nel progetto (barre ATTIVE, non di calendario), ripetuta in un contesto nuovo.""" v = np.asarray(s.values, float) nz = np.flatnonzero(v != 0.0) return s.iloc[nz[0]:] if len(nz) else s def line(tag: str, s: pd.Series) -> str: return (f"{tag:<22s} n={len(s):>5d} Sh {sh(s):+6.2f} maxDD {dd(s)*100:5.1f}% " f"CAGR {cagr(s)*100:+6.2f}% vol {s.std()*np.sqrt(365.25)*100:5.1f}%") # =========================================================================== def main() -> None: t0 = time.time() print("=" * 100) print("XS01 FUORI DALLA FINESTRA DI SCOPERTA — meccanismo CONGELATO su storia lunga indipendente") print(f"XS_CFG = {XS_CFG}") print("=" * 100) # ---------------------------------------------------------------- 1. dato print("\n[1] DATO — Binance spot USDT 1d, certificazione locale") panel19 = load_panel(XS_UNIVERSE) cert = [certify(s, fetch_binance_daily(s)) for s in XS_UNIVERSE] bad = [c for c in cert if not c["ok"]] print(f" {len(cert)} serie scaricate; certificate OK: {len(cert)-len(bad)}/{len(cert)}") print(f" {'sym':<6}{'n':>6}{'start':>12}{'end':>12}{'gap':>5}{'flat%':>8}{'vol0%':>8}{'lead0':>7}") for c in cert: flag = "" if c["ok"] else " <-- CONTROLLA" print(f" {c['sym']:<6}{c['n']:>6}{c['start']:>12}{c['end']:>12}{c['gap_days']:>5}" f"{c['flat']*100:>8.2f}{c['zero_vol']*100:>8.2f}{c['lead_zero_vol']:>7}{flag}") print(" NB: 'lead0' = barre iniziali a volume 0 = firma del backfill sintetico (lezione AXS).") # ---------------------------------------------------- 2. replica bit-exact print("\n[2] SANITY — il motore riproduce lo sleeve UFFICIALE?") from src.portfolio.sleeves import _xsec_returns off = _xsec_returns() rep = xsec_engine(hl_panel(), phase=0) j = pd.concat({"off": off, "rep": rep}, axis=1, join="inner").dropna() md = float(np.max(np.abs(j["off"] - j["rep"]))) if len(j) else float("nan") print(f" n={len(j)} max|diff| = {md:.2e} -> {'IDENTICO' if md < 1e-12 else 'DIVERGE'}") print(f" {line('XS01 ufficiale (HL)', off)}") if md >= 1e-12: print(" !! il motore NON replica: ogni numero sotto e' sospetto. STOP.") return # ------------------------------------------------ 3. validazione di VENUE print("\n[3] VALIDAZIONE DI VENUE — Binance vs Hyperliquid dove esistono ENTRAMBI (2024+)") hlp = hl_panel() b19 = panel19.reindex(columns=XS_UNIVERSE) common = b19.index.intersection(hlp.index) rr = [] for s in XS_UNIVERSE: a1 = hlp[s].reindex(common).pct_change() b1 = b19[s].reindex(common).pct_change() k2 = pd.concat({"a": a1, "b": b1}, axis=1).dropna() rr.append((s, float(k2["a"].corr(k2["b"])), float(np.median(np.abs(k2["a"] - k2["b"]))) * 1e4)) print(f" ritorni giornalieri per asset, {len(common)} giorni comuni:") print(" " + " ".join(f"{s}:{c:.4f}" for s, c, _ in rr)) print(f" corr min {min(c for _, c, _ in rr):.4f} | mediana |diff| ritorno: " f"{np.median([m for _, _, m in rr]):.1f} bps (max {max(m for _, _, m in rr):.1f} bps)") # e soprattutto: il MECCANISMO da' la stessa cosa? bin_on_hl_window = xsec_engine(b19.loc[common].dropna(), phase=0) hl_on_window = xsec_engine(hlp.loc[common], phase=0) jj = pd.concat({"bin": bin_on_hl_window, "hl": hl_on_window}, axis=1, join="inner").dropna() print(f" {line('XS01 su HL 2024+', jj['hl'])}") print(f" {line('XS01 su BINANCE 2024+', jj['bin'])}") print(f" corr fra le DUE VERSIONI DELLO SLEEVE: {jj['bin'].corr(jj['hl']):.4f} " f"dSharpe = {sh(jj['bin'])-sh(jj['hl']):+.3f}") print(" -> se questa corr e' ~1, la storia lunga Binance e' un'estensione legittima:") print(" lo scarto USDT e' un FATTORE COMUNE e sparisce nello z-score cross-sectional.") # ------------------------------------- 4. la storia lunga, fuori campione print("\n[4] STORIA LUNGA — meccanismo congelato, ancora canonica (fase 0)") p13 = panel19[U13].dropna() # universo FISSO 13 (il piu' lungo a >=10 gambe) s13 = xsec_engine(p13, phase=0) sEXP = xsec_engine(panel19, phase=0) # universo che si ALLARGA (19 quando esistono) s13, sEXP = alive(s13), alive(sEXP) print(f" U13-FIX universo fisso {len(U13)} gambe, dal {s13.index[0].date()} " f"({s13.attrs.get('n_traded')}/{s13.attrs.get('n_rebal')} ribilanciamenti con posizione)") print(f" U19-EXP universo espandente (10 -> 19 gambe), dal {sEXP.index[0].date()}") for tag, s in (("U13-FIX", s13), ("U19-EXP", sEXP)): print(f" {line(tag + ' FULL', s)}") print(f" {line(tag + ' PRE-2024 (OOS)', win(s, None, '2024-01-01'))}") print(f" {line(tag + ' 2024+ (scoperta)', win(s, '2024-01-01'))}") print("\n Sharpe per ANNO (netto fee):") yrs = sorted({d.year for d in sEXP.index}) hdr = " " + f"{'':<10}" + "".join(f"{y:>9d}" for y in yrs) print(hdr) for tag, s in (("U13-FIX", s13), ("U19-EXP", sEXP), ("XS01 HL", off)): cells = [] for y in yrs: x = win(s, f"{y}-01-01", f"{y+1}-01-01") cells.append(f"{sh(x):>9.2f}" if len(x) > 60 else f"{'—':>9s}") print(f" {tag:<10}" + "".join(cells)) print("\n Sharpe per REGIME:") regimes = [("2021 toro/top", "2021-01-01", "2022-01-01"), ("2022 ORSO (LUNA/FTX)", "2022-01-01", "2023-01-01"), ("2023 ripresa", "2023-01-01", "2024-01-01"), ("2024-26 scoperta", "2024-01-01", None)] for nm, a, b in regimes: x13, xEX = win(s13, a, b), win(sEXP, a, b) print(f" {nm:<24s} U13 Sh {sh(x13):+6.2f} DD {dd(x13)*100:5.1f}% | " f"U19-EXP Sh {sh(xEX):+6.2f} DD {dd(xEX)*100:5.1f}%") # -------------------------------------------- 5. fortuna di FASE (H = 10) print("\n[5] FORTUNA DI FASE — H=10, tutte e 10 le ancore (la stima onesta e' la MEDIANA)") phases = list(range(XS_CFG["H"])) bands = {} for tag, P, cut in (("U13-FIX FULL", p13, None), ("U13-FIX PRE-2024", p13, "pre"), ("U19-EXP FULL", panel19, None), ("U19-EXP PRE-2024", panel19, "pre")): def f(ph, P=P, cut=cut): s = alive(xsec_engine(P, phase=ph)) return win(s, None, "2024-01-01") if cut == "pre" else s bands[tag] = A.anchor_luck_band(f, phases, canonical=0, metric=sh) bands[tag + "|DD"] = A.anchor_luck_band(f, phases, canonical=0, metric=lambda x: -dd(x)) for tag in ("U13-FIX FULL", "U13-FIX PRE-2024", "U19-EXP FULL", "U19-EXP PRE-2024"): r = bands[tag]; rd = bands[tag + "|DD"] print(f" {tag:<20s} Sh canonico {r['canonical']:+.2f} (pctl {r['canonical_pctl']*100:4.0f}°) " f"| MEDIANA {r['median']:+.2f} | banda [{r['lo']:+.2f},{r['hi']:+.2f}] " f"| fasi>0 {r['frac_positive']*100:3.0f}% | fortuna {r['luck']:+.2f} | gate {r['gate_pass']}") print(f" {'':<20s} maxDD canonico {-rd['canonical']*100:5.1f}% " f"| MEDIANA {-rd['median']*100:5.1f}% | banda [{-rd['hi']*100:.1f},{-rd['lo']*100:.1f}]%") # anche lo sleeve UFFICIALE su HL, de-luckato di fase (per confronto onesto) bhl = A.anchor_luck_band(lambda ph: xsec_engine(hlp, phase=ph), phases, canonical=0, metric=sh) print(f" {'XS01 UFFICIALE (HL)':<20s} Sh canonico {bhl['canonical']:+.2f} " f"(pctl {bhl['canonical_pctl']*100:4.0f}°) | MEDIANA {bhl['median']:+.2f} " f"| banda [{bhl['lo']:+.2f},{bhl['hi']:+.2f}] | fortuna {bhl['luck']:+.2f}") # -------------- 6. differenza APPAIATA fuori campione vs finestra di scoperta print("\n[6] DIFFERENZA APPAIATA per fase: (fuori campione) - (finestra di scoperta)") for tag, P in (("U13-FIX", p13), ("U19-EXP", panel19)): def pre(ph, P=P): return win(alive(xsec_engine(P, phase=ph)), None, "2024-01-01") def post(ph, P=P): return win(alive(xsec_engine(P, phase=ph)), "2024-01-01") d1 = A.anchor_luck_delta(pre, post, phases, metric=sh) print(f" {tag:<10s} mediana DIFF appaiata {d1['median_paired']:+.3f} " f"[{d1['lo']:+.3f},{d1['hi']:+.3f}] fasi con OOS>scoperta {d1['n_positive']}/{d1['n_anchors']}") # ------------------------------------------------------------- 7. i NULL print("\n[7] NULL — a FEE ZERO (permutare un segnale ne fa esplodere il turnover: a fee piene") print(" il null perderebbe per COSTO invece che per assenza di informazione — regola XSR01)") NDRAW = 300 for tag, P, a, b in (("U13-FIX PRE-2024", p13, None, "2024-01-01"), ("U13-FIX FULL", p13, None, None)): real = win(alive(xsec_engine(P, phase=0, fee_side=0.0)), a, b) for kind in ("cross", "sign"): rng = np.random.default_rng(20260822) draws = np.array([sh(win(alive(xsec_engine(P, phase=0, fee_side=0.0, perm_rng=rng, perm_kind=kind)), a, b)) for _ in range(NDRAW)]) p = float(np.mean(draws >= sh(real))) print(f" {tag:<20s} null '{kind}': reale(lordo) {sh(real):+.2f} vs null " f"med {np.median(draws):+.2f} max {draws.max():+.2f} -> p = {p:.4f}") print("\n NULL 'universo dove NON dovrebbe funzionare' — 11 settoriali SPDR, 1998+,") print(" stesso meccanismo congelato (il progetto ha gia' misurato che il cross-sectional") print(" crypto non generalizza alle azioni: XSR01, 2026-07-26):") sect = load_sector_panel() ssec = alive(xsec_engine(sect, phase=0, min_assets=9)) bsec = A.anchor_luck_band(lambda ph: alive(xsec_engine(sect, phase=ph, min_assets=9)), phases, canonical=0, metric=sh) print(f" {line('SECT11 (azioni)', ssec)}") print(f" SECT11 banda di fase: mediana Sh {bsec['median']:+.2f} " f"[{bsec['lo']:+.2f},{bsec['hi']:+.2f}], fasi>0 {bsec['frac_positive']*100:.0f}%") print("\n CONTROLLO DI DIREZIONE — stesso meccanismo con mode='rev' (long i deboli):") for tag, P in (("U13-FIX", p13), ("U19-EXP", panel19)): r = alive(xsec_engine(P, phase=0, mode="rev")) print(f" {line(tag + ' REV', r)}") # ------------- 7b. la SELEZIONE del 2026-06-19, rivalidata FUORI CAMPIONE print("\n[7b] LA SELEZIONE DEL 2026-06-19, RIVALIDATA FUORI CAMPIONE") print(" XS01 non e' solo un meccanismo: e' un meccanismo AFFINATO due volte sui dati HL") print(" 2024+ (blend di lookback [30,90] e gate di dispersione p30). Quelle DUE scelte sono") print(" debito di multiple-testing contratto sulla finestra di scoperta. Qui si paga:") print(" ogni variante e' valutata sul PRE-2024, a MEDIANA delle 10 fasi (mai l'ancora).") variants = [("blend [30,90] + gate p30 (CANONICA)", dict(lookbacks=(30, 90), disp_pct=30)), ("solo L=30 + gate p30", dict(lookbacks=(30,), disp_pct=30)), ("solo L=90 + gate p30", dict(lookbacks=(90,), disp_pct=30)), ("blend [30,90] SENZA gate", dict(lookbacks=(30, 90), disp_pct=0)), ("blend [30,90] + gate p15", dict(lookbacks=(30, 90), disp_pct=15)), ("blend [30,90] + gate p50", dict(lookbacks=(30, 90), disp_pct=50))] sel_rows = [] for nm, kw in variants: b_pre = A.anchor_luck_band( lambda ph, kw=kw: win(alive(xsec_engine(p13, phase=ph, **kw)), None, "2024-01-01"), phases, canonical=0, metric=sh) b_post = A.anchor_luck_band( lambda ph, kw=kw: win(alive(xsec_engine(p13, phase=ph, **kw)), "2024-01-01"), phases, canonical=0, metric=sh) sel_rows.append((nm, b_pre["median"], b_post["median"], b_pre["per_offset"])) print(f" {nm:<38s} PRE-2024 mediana Sh {b_pre['median']:+5.2f} " f"[{b_pre['lo']:+.2f},{b_pre['hi']:+.2f}] | 2024+ {b_post['median']:+5.2f}") rank_pre = 1 + sorted([r[1] for r in sel_rows], reverse=True).index(sel_rows[0][1]) rank_post = 1 + sorted([r[2] for r in sel_rows], reverse=True).index(sel_rows[0][2]) print(f" -> la configurazione CANONICA (scelta nel 2026-06 guardando SOLO il 2024+) e'") print(f" {rank_pre}/{len(sel_rows)} sul PRE-2024 e {rank_post}/{len(sel_rows)} sul 2024+.") print( " 📌 LETTURA: il NUCLEO regge (ogni variante e' positiva fuori campione, banda") print( " [+0.76,+1.28]), ma i DUE AFFINAMENTI del 19/06 NON si replicano:") print( " - il BLEND [30,90] fuori campione e' PEGGIO del solo L=30 (+1.12 vs +1.28);") print( " - il GATE di dispersione p30 fuori campione vale ZERO (con e senza: +1.12);") print( " e' dentro la finestra di scoperta che vale (+0.37 contro -0.03).") print( " Cioe': cio' che sopravvive e' il meccanismo cross-sectional nudo; i due") print( " affinamenti sono, per quanto qui misurabile, adattamento alla finestra in") print( " cui furono scelti. NON e' un argomento per cambiarli (cambiarli guardando") print( " QUESTO risultato sarebbe la stessa selezione, spostata di finestra): e'") print( " un argomento per non attribuire loro il valore che il progetto gli attribuisce.") # --- concentrazione: un contributo positivo si scompone per ANNO e per GAMBA (lezione SOL) print("\n Concentrazione (lezione SOL 22/08: '24/24 ancore positive erano UN ANNO SOLO'):") pre13 = win(s13, None, "2024-01-01") for y in (2021, 2022, 2023): rest = pre13[pre13.index.year != y] print(f" PRE-2024 togliendo il {y}: Sh {sh(rest):+5.2f} " f"(intero pre-2024 {sh(pre13):+5.2f})") loo = {} for a_ in U13: sub = panel19[[x for x in U13 if x != a_]].dropna() bb = A.anchor_luck_band(lambda ph, sub=sub: win(alive(xsec_engine(sub, phase=ph, min_assets=10)), None, "2024-01-01"), phases, canonical=0, metric=sh) loo[a_] = bb["median"] base_med = bands["U13-FIX PRE-2024"]["median"] worst = min(loo, key=loo.get) print(f" Leave-one-asset-out sul PRE-2024 (mediana di fase; base {base_med:+.2f}):") print(" " + " ".join(f"-{k2}:{v:+.2f}" for k2, v in sorted(loo.items(), key=lambda t: t[1]))) print(f" gamba piu' importante: {worst} (senza di lei {loo[worst]:+.2f}); " f"minimo {min(loo.values()):+.2f}, tutte >0: {all(v > 0 for v in loo.values())}") # ---------------------------------------------------------- 8. i GATE print("\n[8] GATE") cand = s13.copy(); cand.index = cand.index.tz_convert("UTC") mg = A.marginal_vs_tp01(cand) print(f" marginal_vs_tp01 (U13-FIX, 2021+): {mg.get('marginal_verdict')} " f"corr {mg.get('corr_full')} n={mg.get('n_days')}") for kk in ("robust_oos", "has_insample_edge", "is_hedge", "beats_noise_null", "insample_sharpe", "tp01_beta", "alpha_ann"): if kk in mg: print(f" {kk:<20s} {mg[kk]}") if "blends" in mg: for wname, bl in mg["blends"].items(): print(f" blend {wname}: uplift_full {bl.get('uplift_full')} " f"uplift_hold {bl.get('uplift_hold')}") mgE = A.marginal_vs_tp01(sEXP) print(f" marginal_vs_tp01 (U19-EXP, 2020-12+): {mgE.get('marginal_verdict')} " f"corr {mgE.get('corr_full')}") # deflated Sharpe: TUTTI i trial valutati, al RIALZO all_sr = [] for tag in ("U13-FIX FULL", "U13-FIX PRE-2024", "U19-EXP FULL", "U19-EXP PRE-2024"): all_sr += list(bands[tag]["per_offset"].values()) all_sr += list(bhl["per_offset"].values()) + list(bsec["per_offset"].values()) for _, _, _, po in sel_rows: # 6 varianti x 10 fasi della sezione 7b all_sr += list(po.values()) all_sr += [float(v) for v in loo.values()] cand_pool = list(all_sr) # SOLO configurazioni di XS01 ctrl = [sh(alive(xsec_engine(p13, phase=0, mode="rev"))), sh(alive(xsec_engine(panel19, phase=0, mode="rev"))), sh(jj["bin"]), sh(jj["hl"])] for fs in (0.0005, 0.001, 0.002): ctrl.append(sh(alive(xsec_engine(p13, phase=0, fee_side=fs)))) dsr_val = bands["U13-FIX PRE-2024"]["median"] med_ph = int(np.argsort(list(bands["U13-FIX PRE-2024"]["per_offset"].values()))[len(phases) // 2]) med_series = win(alive(xsec_engine(p13, phase=med_ph)), None, "2024-01-01") d1, s1 = A.deflated_sharpe(dsr_val, cand_pool, med_series) d2, s2 = A.deflated_sharpe(dsr_val, cand_pool + ctrl, med_series) print(f" deflated_sharpe — headline = MEDIANA di fase OOS {dsr_val:+.2f}") print(f" pool A = SOLE configurazioni XS01 ({len(cand_pool)} trial): DSR {d1:.3f} " f"(max dal rumore {s1:+.2f}) -> {'PASS' if d1 >= 0.95 else 'FAIL'}") print(f" pool B = tutto cio' che ho valutato, controlli NEGATIVI inclusi " f"({len(cand_pool)+len(ctrl)} trial): DSR {d2:.3f} (max dal rumore {s2:+.2f}) " f"-> {'PASS' if d2 >= 0.95 else 'FAIL'}") print( " ⚠️ Il pool B e' il conteggio 'al rialzo' che il progetto impone, ma qui e' anche") print( " il piu' SEVERO in modo improprio: il DSR pesa la VARIANZA dei trial, e io ci") print( " ho messo dentro controlli costruiti per essere negativi (REV -0.97, SECT11") print( " -0.44). Si cita il piu' basso dei due; e si dice che entrambi penalizzano un") print( " MEDIANA-di-fase come se fosse un MAX-di-k, che e' il caso per cui il DSR e'") print( " costruito. Il debito di selezione vero di XS01 non e' stato contratto qui:") print( " e' quello del 2026-06-19, e la sezione 7b lo misura direttamente.") imp = A.implausible_sharpe(s13) print(f" implausible_sharpe: {imp['implausible']} Sh {imp.get('sharpe'):+.2f} " f"maxDD {imp.get('maxdd', 0)*100:.1f}% perdite su barre attive " f"{imp.get('loss_frac', float('nan'))*100:.1f}% (attive {imp.get('active_frac', 0)*100:.0f}%)" f"{' — ' + '; '.join(imp['reasons']) if imp['reasons'] else ''}") # causalita': i prezzi DOPO la data di decisione non devono cambiare le posizioni prima Pc = p13.copy() cut = len(Pc) // 2 rngc = np.random.default_rng(7) Pd = Pc.copy() Pd.iloc[cut:] = Pc.iloc[cut:].values * (1.0 + rngc.normal(0, 0.35, size=Pc.iloc[cut:].shape)) a_ = xsec_engine(Pc, phase=0); b_ = xsec_engine(Pd, phase=0) # confronto solo dove il vol-target non ha ancora "visto" i dati perturbati (rv30 shiftata) lim = cut - 35 cdiff = float(np.max(np.abs(a_.values[:lim] - b_.values[:lim]))) print(f" causalita' (prezzi futuri randomizzati da i={cut}): max|diff| prima di i={lim} " f"= {cdiff:.2e} -> {'LEAK-FREE' if cdiff < 1e-12 else 'PERDITA DI FUTURO'}") print(" null de-levering: NON PERTINENTE — il claim qui non e' 'meno drawdown' ma " "'l'edge esiste fuori campione'. Il de-levering e' comunque implicito nel vol-target,") print(" e le tabelle di fase riportano DD e Sharpe INSIEME (un k<1 abbassa entrambi).") print(f"\n sensibilita' al COSTO (il rischio #1 di uno sleeve a 13 gambe alt):") for fs in (0.0005, 0.001, 0.002, 0.003): x = alive(xsec_engine(p13, phase=0, fee_side=fs)) print(f" {fs*1e4:5.0f} bps/lato: FULL Sh {sh(x):+5.2f} | " f"PRE-2024 {sh(win(x, None, '2024-01-01')):+5.2f} | " f"2024+ {sh(win(x, '2024-01-01')):+5.2f}") # ------------------------------------------------- 9. impatto sul LIBRO print("\n[9] IMPATTO SUL LIBRO — TP01+SKH01 (75/25 live) con e senza XS01, su 2021+") from src.portfolio.sleeves import _tp01_returns, _skyhook_returns tp = A._to_daily(_tp01_returns()); sk = A._to_daily(_skyhook_returns()) for xs_tag, xs in (("XS01 storia lunga (U13-FIX)", s13), ("XS01 ufficiale (HL 2024+)", off)): J = pd.concat({"tp": tp, "sk": sk, "xs": A._to_daily(xs)}, axis=1, join="inner").dropna() if len(J) < 200: continue base = 0.75 * J["tp"] + 0.25 * J["sk"] for w in (0.15, 0.25, 0.50): mix = (1 - w) * base + w * J["xs"] print(f" [{xs_tag}] {J.index[0].date()}..{J.index[-1].date()} n={len(J)} " f"w={w:.2f}: Sh {sh(base):+.2f} -> {sh(mix):+.2f} " f"({sh(mix)-sh(base):+.2f}) maxDD {dd(base)*100:.1f}% -> {dd(mix)*100:.1f}% " f"corr(XS,libro) {J['xs'].corr(base):+.3f}") # e il numero che il PROP-ALLOC usa davvero: il libro 2021+ contiene il 2022 pre = J[J.index < DISCOVERY] if len(pre) > 200: bpre = 0.75 * pre["tp"] + 0.25 * pre["sk"] print(f" -> SOLO FUORI CAMPIONE ({pre.index[0].date()}..{pre.index[-1].date()}, " f"n={len(pre)}), sweep del peso di XS01:") for w in (0.15, 0.25, 0.33, 0.50): mpre = (1 - w) * bpre + w * pre["xs"] print(f" w={w:.2f}: libro Sh {sh(bpre):+.2f} -> {sh(mpre):+.2f} " f"({sh(mpre)-sh(bpre):+.2f}) maxDD {dd(bpre)*100:.1f}% -> " f"{dd(mpre)*100:.1f}% corr {pre['xs'].corr(bpre):+.3f}") print(" ⚠️ PROP-ALLOC mette XS01 al 50%: su un conto a BARRIERA cio' che si") print(" compra e' il maxDD, quindi la riga da guardare e' quella, non lo Sharpe.") # ---------------------------------------------------- 10. eseguibilita' print("\n[10] ESEGUIBILITA' — la domanda del canale funded e' se il libro STA su $100k") det: dict = {} xsec_engine(p13, phase=0, detail=det) W, sc = det["W"], det["scale"] dW = np.abs(np.diff(W, axis=0)); rb = np.flatnonzero(dW.sum(axis=1) > 1e-12) print(f" {len(U13)} gambe, {len(rb)} ribilanciamenti, gross medio " f"{np.abs(W).sum(axis=1).mean():.2f}, leva media (vol-target) {np.mean(sc[sc > 0]):.2f}x") print(f" {'capitale':>10} {'ticket mediano':>16} {'ticket p10':>12} {'<$5':>7} {'<$10':>7}") for cap in (600, 5000, 20000, 100000): parts = [dW[i][dW[i] > 1e-12] * sc[i + 1] * cap for i in rb if sc[i + 1] > 0] tk = np.concatenate(parts) if parts else np.array([]) if not tk.size: print(" (nessun ticket calcolabile — controlla il motore)"); break print(f" {cap:>10,d} {np.median(tk):>15.2f}$ {np.percentile(tk, 10):>11.2f}$ " f"{(tk < 5).mean()*100:>6.1f}% {(tk < 10).mean()*100:>6.1f}%") print(" ⚠️ Approssimazione DICHIARATA: il modello congelato applica il vol-target alla") print(" SERIE dei rendimenti, non alle posizioni -> qui il ticket e' calcolato sui soli") print(" delta di RIBILANCIAMENTO (ogni 10 giorni), ignorando il micro-turnover") print(" giornaliero che una implementazione vera del vol-target genererebbe. E' un") print(" LIMITE INFERIORE del numero di ordini, quindi ottimistico sul lato costi.") print(" ⚠️ E NON rimette in discussione il ~$20k citato dal progetto: quella soglia e' sui") print(" 19 alt Hyperliquid (min-order e liquidita' del venue), non su questi 13.") print(f"\n[fine] {time.time()-t0:.1f}s") if __name__ == "__main__": main()