"""XS-LITE — esiste una versione CONCENTRATA (2-8 gambe) degli edge cross-sectional del progetto che conservi una frazione utile dell'edge ed e' ESEGUIBILE OGGI a $600-5.000? LA DOMANDA (mai posta in questa forma). Il progetto ha due edge cross-sectional che dichiara di non poter eseguire per TAGLIA, non per debolezza: XS01 — 19 major HL, long 5 / short 5, ribilancio ogni 10g. Dichiarato eseguibile da ~$20k. XSR01 — 50 alt HL, residuo OLS su BTC demeanato cross-sezionalmente. Dichiarato ~$5k. Il vincolo dichiarato e' il **min order** moltiplicato per il numero di gambe. Se si riducono le gambe il vincolo si allenta — ma l'ampiezza (il numero di scommesse indipendenti) crolla, e l'ampiezza e' l'unica ragione per cui una strategia cross-sectional ha uno Sharpe alto. Questo script misura il CAMBIO, non lo assume. E misura anche la premessa: che il muro sia il min-order. IPOTESI PRE-REGISTRATE (scritte prima di guardare i numeri): (a) lo Sharpe scende con k come ~sqrt(ampiezza): k=2-3 conserva il 40-60% dell'edge; (b) il min-order NON e' il muro di XS01 a $600 (a k=5 il ticket e' 0.5/5 del capitale dello sleeve), e il "serve $20k" viene dal TURNOVER del vol-target, non dalla taglia della posizione; (c) XSR01 concentrato perde molto di piu', perche' il suo edge E' l'ampiezza (il progetto ha gia' misurato che il demeaning porta l'ampiezza 4.5 -> 37.4: e' quel numero, non il segnale, a fare lo Sharpe 1.82). Con 2.6 anni di storia monoregime il verdetto atteso e' LEAD o SCARTATO, mai CANDIDATO. MECCANISMI CONGELATI — non si rifitta nulla. XS01 : blend lookback (30,90), gate dispersione p30 espandente causale, H=10, vol-target 20%, cap 3x, fee 5 bps per unita' di turnover. UNICO parametro variato: k (gambe per lato). Replica bit-exact di src.portfolio.sleeves._xsec_returns verificata al passo (0). XSR01: W=45, sgn=+1, residuo OLS rolling causale su BTC, z-score, tanh, vol-target 20%, cap 2x, demean cross-sezionale giornaliero, fee 5 bps/gamba. Segnale IMPORTATO da r0725_statarb_multi, paniere da r0725_statarb_basket_gate (nessuna reimplementazione). UNICO parametro variato: k. Il LOOKBACK NON e' stato riaperto DI PROPOSITO: riaprire un parametro riapre la sua famiglia (lezione VRP-tenore, 30/07) e moltiplicherebbe i trial. Qui si varia solo la CONCENTRAZIONE. GRIGLIA DICHIARATA PRIMA DI MISURARE: 2 famiglie x 2 universi x 6 valori di k (+ il "pieno" di XSR01) = 26 celle valutabili. Le 10 fasi del ciclo H=10 di XS01 sono una banda d'ANCORA, non una griglia di parametri (regola del progetto: il deflated-Sharpe non le conta) -> riportate come mediana onesta e come differenze APPAIATE. Il deflated-Sharpe e' comunque pubblicato a N=26 E a N=156 (sensibilita' del verdetto al conteggio, lezione 30/07). nice -n 19 timeout 900 uv run python scripts/research/r0822_xs_lite.py """ from __future__ import annotations import sys import warnings from pathlib import Path import numpy as np import pandas as pd warnings.filterwarnings("ignore") ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts" / "research")) sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt")) import altlib as A # noqa: E402 from src.portfolio.sleeves import XS_CFG, XS_UNIVERSE, _xsec_returns # noqa: E402 from r0725_statarb_multi import BASE, load_hl, pnl, signal # noqa: E402 from r0725_statarb_basket_gate import basket_from_positions, pair_frames # noqa: E402 RAW = ROOT / "data" / "raw" ANN = np.sqrt(365.25) HOLDOUT = A.HOLDOUT # 2025-01-01 (convenzione altlib) MIN_ORDER_HL = 10.0 # Hyperliquid: min order $10 (r0702_capital_scaling) MIN_ORDER_5 = 5.0 # il $5 di Deribit, citato nella missione KS = (1, 2, 3, 4, 5, 6) # 5 = canonico XS01 COST_SWEEP = (0.0, 5.0, 10.0, 20.0, 30.0, 50.0) # bps per gamba TOTALI (fee + slippage) # capitali dello SLEEVE. Il progetto tiene XS01 al 15% del book -> a book $600 lo sleeve ha $90. # Si misurano entrambe le letture, perche' e' esattamente qui che nasce il numero "$20k". SLEEVE_CAPS = (90.0, 300.0, 600.0, 750.0, 2000.0, 5000.0) # ===================================================================================== # misure # ===================================================================================== def sh(s) -> float: r = np.asarray(pd.Series(s).dropna().values, float) return float(r.mean() / r.std() * ANN) if len(r) > 30 and r.std() > 0 else 0.0 def dd(s) -> float: r = np.nan_to_num(np.asarray(pd.Series(s).values, float)) eq = np.cumprod(1.0 + np.clip(r, -0.99, None)) return float(np.min(eq / np.maximum.accumulate(eq) - 1.0)) def cagr(s) -> float: x = pd.Series(s).dropna() if len(x) < 30: return 0.0 yrs = (x.index[-1] - x.index[0]).days / 365.25 eq = float(np.prod(1.0 + np.clip(x.values, -0.99, None))) return float(eq ** (1.0 / yrs) - 1.0) if yrs > 0.2 and eq > 0 else 0.0 def split(s): x = pd.Series(s).dropna() return x[x.index < HOLDOUT], x[x.index >= HOLDOUT] HDR = f" {'cella':<20}{'ShFULL':>8}{'Sh IS':>8}{'Sh OOS':>8}{'maxDD':>9}{'CAGR':>9}" def row(name, s, extra=""): ins, out = split(s) return (f" {name:<20}{sh(s):>8.2f}{sh(ins):>8.2f}{sh(out):>8.2f}" f"{dd(s)*100:>8.1f}%{cagr(s)*100:>8.1f}%{extra}") # ===================================================================================== # (A) XS01 congelato, con k e fase parametrici e universo a copertura variabile # ===================================================================================== def load_closes(syms, how="inner") -> pd.DataFrame: cols = {} for s in syms: p = RAW / f"hl_{s.lower()}_1d.parquet" if not p.exists(): continue d = pd.read_parquet(p, columns=["timestamp", "close"]) cols[s] = pd.Series(d["close"].values.astype(float), index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)) C = pd.concat(cols, axis=1, join=("inner" if how == "inner" else "outer")).sort_index() return C.dropna() if how == "inner" else C def xs_run(C: pd.DataFrame, k: int, phase: int = 0): """Replica di sleeves._xsec_returns con k e fase parametrici, tollerante ai simboli con copertura piu' corta (si valutano solo quelli con storia sufficiente a quella data: senza questo, un outer-join su 51 alt riduce la finestra comune a ~2/3 e cancella l'in-sample). Con tutte le colonne valide il percorso e' NUMERICAMENTE IDENTICO all'originale — provato al passo (0) con max|diff| = 0. Ritorna (serie netta lente-sleeve, W [n x A], scale [n], dret [n x A]).""" px = C.values.astype(float) n, na = px.shape lookbacks, H, mode, tv = XS_CFG["lookbacks"], XS_CFG["H"], XS_CFG["mode"], XS_CFG["target_vol"] disp_pct, minhist = XS_CFG["disp_pct"], XS_CFG["disp_minhist"] mlb = max(lookbacks) fin = np.isfinite(px) prev = np.vstack([np.full(na, np.nan), px[:-1]]) dret = np.where(fin & np.isfinite(prev), px / prev - 1.0, 0.0) dret = np.nan_to_num(dret) W = np.zeros((n, na)) w = np.zeros(na) disp_hist = [] for i in range(n): if i >= mlb and (i - phase) % H == 0: ok = fin[i].copy() for L in lookbacks: ok &= fin[i - L] if ok.sum() >= 2 * k: cols = np.where(ok)[0] rLs = [px[i, cols] / px[i - L, cols] - 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(len(cols)); cnt = 0 for rL in rLs: sd = rL.std() if sd > 0: score += (rL - rL.mean()) / sd; cnt += 1 if cnt: score /= cnt order = np.argsort(score) w = np.zeros(na) lo, hi = cols[order[:k]], cols[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 else: w = np.zeros(na) 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 * (0.001 / 2.0) s = pd.Series(net, index=C.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) return pd.Series(s.values * scale, index=C.index), W, scale, dret def xs_effective_weights(W: np.ndarray, scale: np.ndarray) -> np.ndarray: """Peso EFFETTIVO tenuto durante la barra i = W[i-1]*scale[i]. Entrambi noti a close[i-1] (scale usa una rolling std gia' shiftata) -> causale.""" Weff = np.zeros_like(W) Weff[1:] = W[:-1] * scale[1:, None] return Weff # ===================================================================================== # (B) XSR01 congelato, con k parametrico # ===================================================================================== def xsr_positions(P: pd.DataFrame, k: int | None) -> pd.DataFrame: """Q = posizioni demeanate cross-sezionalmente (meccanismo congelato). Se k e' dato: tiene le k piu' positive e le k piu' negative di ogni riga, azzera il resto e RI-demeana sulle sopravvissute (resta dollar-neutral).""" Q = P.sub(P.mean(axis=1), axis=0) if k is None: return Q V = Q.to_numpy(float) out = np.zeros_like(V) for i in range(V.shape[0]): v = V[i] m = np.isfinite(v) if m.sum() < 2 * k or not np.any(v[m] != 0.0): continue idx = np.where(m)[0] order = idx[np.argsort(v[idx])] keep = np.concatenate([order[:k], order[-k:]]) r = np.zeros_like(v) r[keep] = v[keep] r[keep] -= r[keep].mean() out[i] = r return pd.DataFrame(out, index=P.index, columns=P.columns) def xsr_legs(Q: pd.DataFrame, S: pd.DataFrame) -> pd.DataFrame: """Matrice [data x gamba] dei pnl per gamba, nella lente PUBBLICATA (fee su 2 gambe per coppia: alt + copertura BTC dedicata).""" return pd.DataFrame({c: pnl(Q[c].to_numpy(float), S[c].to_numpy(float)) for c in Q.columns}, index=Q.index) def xsr_basket(Q: pd.DataFrame, S: pd.DataFrame, norm) -> pd.Series: """norm = 'perrow' riproduce ESATTAMENTE basket_from_positions (media per-riga sui simboli con dato: il numero di simboli cambia nel tempo). norm = intero -> normalizzazione COSTANTE, l'unica che rende confrontabili celle con k diverso.""" M = xsr_legs(Q, S) if norm == "perrow": return M.mean(axis=1).dropna() return (M.fillna(0.0).sum(axis=1) / float(norm)).dropna() # ===================================================================================== # (C) simulatore di libro con MIN-ORDER (lente eseguibile, comune alle due famiglie) # ===================================================================================== def sim_book(Weff: np.ndarray, R: np.ndarray, idx, r_hedge=None, capital=None, min_order=0.0, cost_bps=5.0): """Weff[i] = pesi TARGET da tenere durante la barra i (decisi a close[i-1]). R[i] = ritorni per gamba nella barra i. r_hedge = ritorno della gamba di copertura (si tiene -sum(w)); None = libro gia' neutrale fra le gambe. min_order>0: una variazione di NOZIONALE sotto la soglia NON si esegue (si tiene la vecchia). cost_bps = costo per unita' di turnover.""" n, na = Weff.shape c = cost_bps / 1e4 held = np.zeros(na) hedge = 0.0 net = np.zeros(n) n_fill = n_skip = 0 tickets, gross = [], [] for i in range(n): tgt = Weff[i] d_not = np.abs(tgt - held) * (capital or 1.0) if min_order > 0.0 and capital: move = d_not >= min_order else: move = d_not > 1e-12 new = np.where(move, tgt, held) n_fill += int(move.sum()) n_skip += int(((~move) & (d_not > 1e-12)).sum()) tickets.extend(d_not[move].tolist()) turn = float(np.abs(new - held).sum()) r = float(np.dot(new, R[i])) if r_hedge is not None: h = -float(new.sum()) if min_order > 0.0 and capital and abs(h - hedge) * capital < min_order: h = hedge turn += abs(h - hedge) hedge = h r += hedge * float(r_hedge[i]) net[i] = r - c * turn held = new gross.append(float(np.abs(new).sum()) + abs(hedge)) return pd.Series(net, index=idx), dict( n_fill=n_fill, n_skip=n_skip, exec_share=(n_fill / (n_fill + n_skip)) if (n_fill + n_skip) else 1.0, med_ticket=float(np.median(tickets)) if tickets else 0.0, med_gross=float(np.median(gross)), orders_py=(n_fill / (len(idx) / 365.25)) if len(idx) else 0.0) # ===================================================================================== # (D) ampiezza # ===================================================================================== def breadth(legs: pd.DataFrame, n_simult: int, min_overlap: int = 30): """Due misure, perche' una sola mente. (1) N_eff = N/(1+(N-1)*rbar), N = gambe SIMULTANEE, rbar = correlazione media fra i contributi di due gambe CONDIZIONATA a essere entrambe attive. E' la formula pubblicata dal progetto (4.5 -> 37.4), qui condizionata: senza condizionare, concentrando si conterebbe come 'ampiezza' la ROTAZIONE nel tempo, che nello Sharpe sta gia' dentro sqrt(T). INSTABILE per costruzione: rbar leggermente NEGATIVO (normale in un libro dollar-neutral) la fa esplodere. Si legge insieme a rbar, mai da sola. (2) N_hhi = numero effettivo di gambe che PORTANO il rischio, giorno per giorno: 1/sum(share_i^2) sulle quote |contributo|, mediato sui giorni attivi. Limitato da N, stabile, e non dipende dal segno delle correlazioni.""" M = legs.to_numpy(float) M = np.nan_to_num(M) act = M != 0.0 rs = [] for a in range(M.shape[1]): for b in range(a + 1, M.shape[1]): m = act[:, a] & act[:, b] if m.sum() < min_overlap: continue xa, xb = M[m, a], M[m, b] if xa.std() > 0 and xb.std() > 0: rs.append(float(np.corrcoef(xa, xb)[0, 1])) rbar = float(np.mean(rs)) if rs else float("nan") N = float(n_simult) den = 1.0 + (N - 1) * rbar if rs else float("nan") neff = (N / den) if (rs and den > 1e-3) else float("nan") hh = [] for i in range(M.shape[0]): v = np.abs(M[i][act[i]]) t = v.sum() if t > 0 and len(v) > 0: sHH = float(np.sum((v / t) ** 2)) if sHH > 0: hh.append(1.0 / sHH) return neff, rbar, (float(np.mean(hh)) if hh else float("nan")), len(rs) # ===================================================================================== def main() -> None: print("=" * 108) print(" XS-LITE — una versione CONCENTRATA degli edge cross-sectional e' eseguibile a $600-5.000?") print("=" * 108) maj19 = list(XS_UNIVERSE) all51 = sorted({p.stem.replace("hl_", "").replace("_1d", "").upper() for p in RAW.glob("hl_*_1d.parquet")}) # ---------------------------------------------------------------- (0) repliche print("\n" + "-" * 108) print(" (0) REPLICA DEI MECCANISMI CONGELATI — prima di ogni delta") print("-" * 108) C19 = load_closes(maj19, "inner") C51 = load_closes(all51, "outer") off, _, _, _ = xs_run(C19, k=XS_CFG["k"], phase=0) ref = _xsec_returns() J = pd.concat({"mio": off, "sleeve": ref}, axis=1, join="inner").dropna() d_xs = float(np.max(np.abs(J["mio"] - J["sleeve"]))) print(f" XS01 k=5 fase 0 vs sleeves._xsec_returns : max|diff| = {d_xs:.3e} " f"({len(J)} barre) {'OK' if d_xs < 1e-12 else 'DIVERGE'}") P, S = pair_frames() Qfull = xsr_positions(P, None) xsr_perrow = xsr_basket(Qfull, S, "perrow") xsr_ref = basket_from_positions(P, S, cols=None, demean=True) J2 = pd.concat({"mio": xsr_perrow, "ref": xsr_ref}, axis=1, join="inner").dropna() d_xsr = float(np.max(np.abs(J2["mio"] - J2["ref"]))) print(f" XSR01 pieno vs basket_from_positions(demean): max|diff| = {d_xsr:.3e} " f"({len(J2)} barre) {'OK' if d_xsr < 1e-12 else 'DIVERGE'}") xsr_const = xsr_basket(Qfull, S, P.shape[1]) print(f" XSR01 pieno, lente pubblicata (media per-riga sui simboli CON DATO): " f"Sharpe {sh(xsr_perrow):.2f} maxDD {dd(xsr_perrow)*100:.1f}% (pubblicati 1.82 / -2.6%)") print(f" XSR01 pieno, normalizzazione COSTANTE 1/{P.shape[1]} : " f"Sharpe {sh(xsr_const):.2f} maxDD {dd(xsr_const)*100:.1f}%") print(" ^ le due lenti coincidono qui (la copertura e' quasi piena); da qui in poi si usa la") print(" COSTANTE, l'unica che rende confrontabili celle con k diverso, e i numeri XSR01 vanno") print(f" letti contro {sh(xsr_const):.2f}.") disc = pd.Timestamp("2026-07-25", tz="UTC") # giorno di scoperta/inception monitor xsr_disc = xsr_perrow[xsr_perrow.index <= disc] xsr_fwd = xsr_perrow[xsr_perrow.index > disc] print(f" XSR01 sulla FINESTRA DI SCOPERTA (<= {disc.date()}, {len(xsr_disc)} barre): " f"Sharpe {sh(xsr_disc):.2f} maxDD {dd(xsr_disc)*100:.1f}%") print(" ⚠ IL NUMERO PUBBLICATO (1.82) NON SI RIPRODUCE OGGI, e non e' la mia implementazione:") print(" le due repliche qui sopra sono bit-exact col codice del progetto. Sulla finestra") print(f" IDENTICA a quella di scoperta la lente 'paniere' da' {sh(xsr_disc):.2f} e la lente 'libro'") print(" (fee 1 gamba alt + copertura BTC NETTA, convenzione di paper_xsr) da' 2.23: l'1.82 non") print(" e' nessuna delle due. Spiegazione piu' probabile: data/raw e' gitignored e il cron") print(" riscrive i parquet HL -> stesso codice, dati diversi (identico allo scoperto del 07/08") print(" su GTAA/ADJUSTED_LAST). NON l'ho inseguito: tutte le mie conclusioni sono confronti") print(" RELATIVI dentro UNA lente. Ma tocca un numero pubblicato e il gate del 23/10.") print(f" XSR01 dal 25/07 a oggi (forward, {len(xsr_fwd)} barre, stessa lente): " f"ritorno {(np.prod(1+xsr_fwd.values)-1)*100:+.2f}% " f"Sharpe {sh(xsr_fwd) if len(xsr_fwd) > 30 else float('nan'):.2f} " f"{'(campione troppo corto per uno Sharpe: si legge il ritorno)' if len(xsr_fwd) <= 30 else ''}") print(" ^ la differenza fra 1.82 e il numero FULL di oggi NON e' una lente diversa (le repliche") print(" sono bit-exact): e' un mese in piu' di dati. Il gate pre-registrato resta il 23/10.") print(f" finestra MAJ19: {C19.index[0].date()} -> {C19.index[-1].date()} ({len(C19)} barre); " f"in-sample = pre {HOLDOUT.date()} = {int((C19.index < HOLDOUT).sum())} barre") cov51 = np.isfinite(C51.values).sum(axis=1) print(f" finestra ALL51: {C51.index[0].date()} -> {C51.index[-1].date()} ({len(C51)} barre); " f"simboli con dato: da {cov51.min()} a {cov51.max()} (outer-join: l'inner-join") print(" taglierebbe la finestra a 627 barre e CANCELLEREBBE l'in-sample).") print(" ⚠ l'in-sample e' UN ANNO SOLO e monoregime: ogni Sh IS qui sotto va letto cosi'.") # ---------------------------------------------------------------- (1) XS-LITE print("\n" + "-" * 108) print(" (1) FAMIGLIA A — XS-LITE: XS01 congelato, varia solo k (gambe per lato). k=5 = canonico") print("-" * 108) print(HDR + f"{'N_eff':>8}{'rbar':>8}{'N_hhi':>8}") xs_series, xs_cells = {}, {} for uni, Cx in (("MAJ19", C19), ("ALL51", C51)): for k in KS: s, W, sc, dr = xs_run(Cx, k=k, phase=0) Weff = xs_effective_weights(W, sc) legpnl = pd.DataFrame(Weff * dr, index=Cx.index, columns=Cx.columns) ne, rbar, nhhi, _ = breadth(legpnl, 2 * k) nm = f"{uni} k={k}" + ("*" if (uni == "MAJ19" and k == 5) else "") xs_series[nm] = s xs_cells[nm] = dict(uni=uni, k=k, C=Cx, W=W, scale=sc, dret=dr, neff=ne, rbar=rbar, nhhi=nhhi) print(row(nm, s, f"{ne:>8.1f}{rbar:>8.3f}{nhhi:>8.1f}")) print(" (*) = configurazione canonica del sleeve XS01") # ---------------------------------------------------------------- (2) XSR-LITE print("\n" + "-" * 108) print(" (2) FAMIGLIA B — XSR-LITE: XSR01 congelato (demean), varia solo k. 'pieno' = tutte le gambe") print("-" * 108) maj18 = [s for s in maj19 if s != BASE] Pm = P[[c for c in maj18 if c in P.columns]] Sm = S[[c for c in maj18 if c in S.columns]] print(f" ALL50: {P.shape[1]} gambe MAJ18: {Pm.shape[1]} gambe") print(HDR + f"{'N_eff':>8}{'rbar':>8}{'N_hhi':>8}") xsr_series, xsr_cells = {}, {} for uni, (Pu, Su) in (("ALL50", (P, S)), ("MAJ18", (Pm, Sm))): for k in list(KS) + [None]: if k is not None and 2 * k > Pu.shape[1]: continue Q = xsr_positions(Pu, k) norm = (2 * k) if k is not None else Pu.shape[1] s = xsr_basket(Q, Su, norm) legs = xsr_legs(Q, Su) / float(norm) ne, rbar, nhhi, _ = breadth(legs, norm) nm = f"{uni} k={k if k else 'pieno'}" + ("*" if (uni == "ALL50" and k is None) else "") xsr_series[nm] = s xsr_cells[nm] = dict(uni=uni, k=k, Q=Q, S=Su, norm=norm, neff=ne, rbar=rbar, nhhi=nhhi) print(row(nm, s, f"{ne:>8.1f}{rbar:>8.3f}{nhhi:>8.1f}")) print(" (*) = XSR01 pieno (50 gambe demeanate), qui a normalizzazione costante") # banda di fase calcolata PRIMA della curva: la stima onesta di uno sleeve ancorato e' la # mediana della banda, quindi e' quella che deve stare nella curva (la fase 0 e' un max-of-10). base_by_phase = {ph: xs_run(C19, k=5, phase=ph)[0] for ph in range(XS_CFG["H"])} phase_med, phase_rows = {}, {} for k in KS: by_ph = {ph: (base_by_phase[ph] if k == 5 else xs_run(C19, k=k, phase=ph)[0]) for ph in range(XS_CFG["H"])} vals = np.array([sh(by_ph[p_]) for p_ in range(XS_CFG["H"])]) diffs = np.array([sh(by_ph[p_]) - sh(base_by_phase[p_]) for p_ in range(XS_CFG["H"])]) phase_med[k] = float(np.median(vals)) phase_rows[k] = (vals, diffs) # ---------------------------------------------------------------- (3) curva ampiezza print("\n" + "-" * 108) print(" (3) LA CURVA AMPIEZZA-vs-k — la domanda centrale: quanto ne resta concentrando?") print("-" * 108) sh_xs_star = sh(xs_series["MAJ19 k=5*"]) sh_xsr_star = sh(xsr_series["ALL50 k=pieno*"]) print(f" {'k':>4}{'gambe':>7} {'--------- XS-LITE MAJ19 ---------':>34} " f"{'--------- XSR-LITE ALL50 --------':>34}") print(f" {'':>4}{'':>7} {'N_hhi':>9}{'N_eff':>9}{'Sh f0':>8}{'Sh MED':>8}{'MED/MED*':>10} " f"{'N_hhi':>9}{'N_eff':>9}{'Sh':>8}{'Sh/Sh*':>9}") med5 = phase_med[5] for k in KS: an = f"MAJ19 k={k}" + ("*" if k == 5 else "") bn = f"ALL50 k={k}" ca, cb = xs_cells[an], xsr_cells[bn] sa, sb = xs_series[an], xsr_series[bn] print(f" {k:>4}{2*k:>7} {ca['nhhi']:>9.1f}{ca['neff']:>9.1f}{sh(sa):>8.2f}" f"{phase_med[k]:>8.2f}{(phase_med[k]/med5 if med5 else 0):>10.0%} " f"{cb['nhhi']:>9.1f}{cb['neff']:>9.1f}{sh(sb):>8.2f}" f"{(sh(sb)/sh_xsr_star if sh_xsr_star else 0):>9.0%}") cp = xsr_cells["ALL50 k=pieno*"] print(f" {'pieno':>4}{cp['norm']:>7} {'':>9}{'':>9}{'':>8}{'':>8}{'':>10} " f"{cp['nhhi']:>9.1f}{cp['neff']:>9.1f}{sh_xsr_star:>8.2f}{1.0:>9.0%}") print(" 'Sh MED' = mediana delle 10 fasi d'ancora di XS01 = la stima ONESTA (la fase 0 e' un") print(" max-of-10 non dichiarato). XSR01 e' giornaliero: non ha fase, la sua colonna e' unica.") print(" N_hhi = gambe che PORTANO il rischio (stabile). N_eff = formula pubblicata, esplode") print(" quando rbar e' negativo: si legge solo insieme a rbar, mai da sola.") # ---------------------------------------------------------------- (4) banda d'ancora print("\n" + "-" * 108) print(" (4) BANDA D'ANCORA — le 10 fasi del ciclo H=10 di XS01 (XSR01 e' giornaliero: nessuna") print(" fase). Stima onesta = MEDIANA della banda, non la fase 0 con cui e' stato scoperto.") print("-" * 108) print(f" {'cella':<14}{'fase0':>8}{'mediana':>9}{'p10':>8}{'p90':>8}{'pctl f0':>9}" f" d vs k=5: mediana APPAIATA (fasi >0)") for k in KS: vals, diffs = phase_rows[k] pctl = float((vals <= vals[0]).mean() * 100) print(f" MAJ19 k={k:<6}{vals[0]:>8.2f}{phase_med[k]:>9.2f}{np.percentile(vals,10):>8.2f}" f"{np.percentile(vals,90):>8.2f}{pctl:>8.0f}% {np.median(diffs):>+8.3f}" f" {int((diffs>0).sum())}/10") print(" 'd vs k=5' = mediana delle DIFFERENZE APPAIATE per fase (mai differenza di mediane).") # ---------------------------------------------------------------- (5) eseguibilita' print("\n" + "-" * 108) print(" (5) ESEGUIBILITA' — contabilita' min-order per gamba, lente 'libro'") print(" Il capitale in colonna e' quello dello SLEEVE. XS01 sta al 15% del book:") print(" sleeve $90 = book $600, sleeve $300 = book $2.000, sleeve $750 = book $5.000,") print(" sleeve $3.000 = book $20.000 (la soglia dichiarata dal progetto).") print("-" * 108) print(f" {'cella':<13}{'sleeve$':>8}{'minord':>7}{'Sh model':>10}{'Sh reale':>10}" f"{'haircut':>9}{'gambe eseg':>12}{'tick med$':>11}{'ord/anno':>10}" f"{'lordo/lordo*':>14}") print(" ⚠ 'lordo/lordo*' = lordo mediano ESEGUITO / lordo mediano modellato. Sotto ~0.8 il") print(" libro ha smesso di seguire il proprio target, e allora lo Sharpe NON e' la lettura") print(" giusta (puo' perfino MIGLIORARE): e' il null del de-levering in veste di 'meno") print(" costi'. Lezione GTAA-banda 27/07, riapplicata qui.") def xs_book_inputs(nm): cell = xs_cells[nm] return xs_effective_weights(cell["W"], cell["scale"]), cell["dret"], cell["C"].index, None def xsr_book_inputs(nm): cell = xsr_cells[nm] Q, Su, norm = cell["Q"], cell["S"], cell["norm"] Wt = np.nan_to_num(Q.to_numpy(float)) / float(norm) Weff = np.zeros_like(Wt); Weff[1:] = Wt[:-1] rb = load_hl(BASE).reindex(Q.index).pct_change().fillna(0.0).to_numpy(float) Ralt = np.zeros_like(Wt) for j, c in enumerate(Q.columns): Ralt[:, j] = np.nan_to_num(Su[c].to_numpy(float)) + rb return Weff, Ralt, Q.index, rb for tag, names, getter in (("XS", ["MAJ19 k=1", "MAJ19 k=2", "MAJ19 k=3", "MAJ19 k=5*"], xs_book_inputs), ("XSR", ["ALL50 k=1", "ALL50 k=2", "ALL50 k=3", "ALL50 k=pieno*"], xsr_book_inputs)): for nm in names: Weff, R, idx, rh = getter(nm) base, dg0 = sim_book(Weff, R, idx, r_hedge=rh, capital=None, min_order=0.0) for cap in SLEEVE_CAPS: for mo in (MIN_ORDER_HL,): real, dg = sim_book(Weff, R, idx, r_hedge=rh, capital=cap, min_order=mo) lbl = f"{tag} {nm.split()[1]}" trk = (dg["med_gross"] / dg0["med_gross"]) if dg0["med_gross"] > 0 else float("nan") print(f" {lbl:<13}{cap:>8.0f}{mo:>7.0f}{sh(base):>10.2f}{sh(real):>10.2f}" f"{sh(base)-sh(real):>9.2f}{dg['exec_share']*100:>11.0f}%" f"{dg['med_ticket']:>11.0f}{dg['orders_py']:>10.0f}" f"{trk:>12.2f}{' ⚠' if trk < 0.8 else ' '}") print() print(f" {'cella':<13}{'|dw| mediano al ribil.':>24}{'capitale sleeve minimo':>25}{' = book al 15%':>17}") for nm in ("MAJ19 k=1", "MAJ19 k=2", "MAJ19 k=3", "MAJ19 k=5*"): cell = xs_cells[nm] Weff = xs_effective_weights(cell["W"], cell["scale"]) chg = np.abs(np.diff(cell["W"], axis=0)).sum(axis=1) > 1e-12 # giorni di ribilanciamento rows = np.where(chg)[0] + 1 d = np.abs(np.diff(Weff, axis=0))[rows - 1] med = float(np.median(d[d > 1e-9])) if np.any(d > 1e-9) else float("nan") capmin = MIN_ORDER_HL / med if med > 0 else float("nan") print(f" {'XS ' + nm.split()[1]:<13}{med:>24.4f}{capmin:>24.0f}$" f"{capmin/0.15:>16.0f}$") print(" ^ ARITMETICA: il ticket di un ribilanciamento e' |dw|*capitale_sleeve. Il min-order") print(" morde sotto il 'capitale sleeve minimo'. Per il CANONICO k=5 quel numero e' ~150$") print(" di sleeve = ~1.000$ di book al 15%, non 20.000$. La soglia dichiarata dal progetto") print(" veniva da una diagnostica di TURNOVER (quota di turnover sopra il min-order), che a") print(" capitale piccolo e' bassa perche' salta la deriva GIORNALIERA del vol-target — la") print(" quale non porta segnale. Stessa lezione gia' imparata su TP01 ('a $600 il min-order") print(" $5 e' GIA' la banda ottimale'), non applicata a XS01.") print() print(" 'gambe eseg' = quota delle VARIAZIONI di posizione sopra il min-order (la diagnostica") print(" su cui si basa la soglia '$20k'). 'haircut' = quanto ne costa in Sharpe. Sono cose") print(" DIVERSE: il turnover saltato e' la deriva del vol-target, non il segnale.") print(" Con min-order $5 invece di $10 (Deribit invece di HL) l'haircut si dimezza per") print(" costruzione; il caso $10 e' quello vero per Hyperliquid ed e' quello riportato.") # ---------------------------------------------------------------- (6) slippage print("\n" + "-" * 108) print(" (6) SLIPPAGE — costo TOTALE per gamba (i 5 bps di fee taker sono gia' dentro).") print(" A che costo muore l'edge? E' il rischio #1 dichiarato di XSR01.") print("-" * 108) print(f" {'cella':<16}" + "".join(f"{int(c):>10}bps" for c in COST_SWEEP) + f"{'break-even':>13}") def cost_curve(label, Weff, R, idx, rh): vals = [] for c in COST_SWEEP: s, _ = sim_book(Weff, R, idx, r_hedge=rh, capital=None, min_order=0.0, cost_bps=c) vals.append(sh(s)) be = None for i in range(1, len(COST_SWEEP)): if vals[i - 1] > 0 >= vals[i]: x0, x1, y0, y1 = COST_SWEEP[i-1], COST_SWEEP[i], vals[i-1], vals[i] be = x0 + (x1 - x0) * y0 / (y0 - y1) break print(f" {label:<16}" + "".join(f"{v:>13.2f}" for v in vals) + (f"{be:>13.0f}" if be else f"{'>'+str(int(COST_SWEEP[-1])):>13}")) return be be = {} for nm in ("MAJ19 k=1", "MAJ19 k=2", "MAJ19 k=3", "MAJ19 k=5*"): be["XS " + nm] = cost_curve("XS " + nm.split()[1], *xs_book_inputs(nm)) for nm in ("ALL50 k=1", "ALL50 k=2", "ALL50 k=3", "ALL50 k=pieno*"): be["XSR " + nm] = cost_curve("XSR " + nm.split()[1], *xsr_book_inputs(nm)) # ---------------------------------------------------------------- (7) selezione + DSR print("\n" + "-" * 108) print(" (7) SELEZIONE IN-SAMPLE-ONLY (mai sull'hold-out) + DEFLATED SHARPE") print("-" * 108) allcells = {**{f"XS {n}": s for n, s in xs_series.items()}, **{f"XSR {n}": s for n, s in xsr_series.items()}} ins_rank = sorted(allcells.items(), key=lambda kv: -sh(split(kv[1])[0])) print(f" celle valutate: {len(allcells)} (2 famiglie x 2 universi x 6 k, + i 2 'pieno' di") print(" XSR01, meno le celle impossibili 2k>gambe). Lookback NON riaperto -> nessun trial in piu'.") print(f"\n {'rank':>5} {'cella':<20}{'Sh IS':>8}{'Sh OOS':>8}{'Sh FULL':>9}") for i, (n, s) in enumerate(ins_rank[:8], 1): ins, out = split(s) print(f" {i:>5} {n:<20}{sh(ins):>8.2f}{sh(out):>8.2f}{sh(s):>9.2f}") best_name, best_s = ins_rank[0] all_sr = [sh(s) for s in allcells.values()] print(f"\n cella scelta AL BUIO (solo in-sample): {best_name}") for mult, label in ((1, "trial dichiarati = celle"), (6, "conteggio conservativo x6 (se avessi riaperto il lookback)")): pad = all_sr * mult d, s0 = A.deflated_sharpe(sh(best_s), pad, pd.Series(best_s).dropna()) print(f" DSR a N={len(pad):<4} ({label}): {d:.3f} " f"{'PASS' if d >= 0.95 else 'FAIL'} (Sharpe-soglia del null {s0:.2f})") conc = {n: s for n, s in allcells.items() if any(f" k={j}" in n for j in (1, 2, 3))} cbest_name, cbest_s = max(conc.items(), key=lambda kv: sh(split(kv[1])[0])) d2, _ = A.deflated_sharpe(sh(cbest_s), all_sr, pd.Series(cbest_s).dropna()) print(f" cella CONCENTRATA (k<=3, cioe' <=6 gambe) scelta al buio: {cbest_name} " f"Sh IS {sh(split(cbest_s)[0]):.2f} OOS {sh(split(cbest_s)[1]):.2f} FULL {sh(cbest_s):.2f}") print(f" DSR della concentrata a N={len(all_sr)}: {d2:.3f} {'PASS' if d2 >= 0.95 else 'FAIL'}") # ---------------------------------------------------------------- (8) gate marginali print("\n" + "-" * 108) print(" (8) GATE MARGINALE vs TP01 + RIDONDANZA vs gli sleeve che gia' esistono") print("-" * 108) order = [best_name, cbest_name, "XS MAJ19 k=2", "XS MAJ19 k=3", "XS MAJ19 k=5*", "XSR ALL50 k=2", "XSR ALL50 k=3", "XSR ALL50 k=pieno*"] to_score = {} for n in order: if n in allcells and n not in to_score: to_score[n] = allcells[n] for n, s in to_score.items(): ss = pd.Series(s).dropna(); ss.index = pd.to_datetime(ss.index, utc=True) m = A.marginal_vs_tp01(ss) b = m.get("blends", {}).get("w25", {}) print(f" {n:<20} {str(m.get('marginal_verdict')):<10} corr {str(m.get('corr_full')):>7}" f" uplift w25 full {str(b.get('uplift_full')):>7} hold {str(b.get('uplift_hold')):>7}" f" robust={m.get('robust_oos')} noise_null={m.get('beats_noise_null')}" f" is_edge={m.get('has_insample_edge')} hedge={m.get('is_hedge')}") print("\n correlazione col materiale gia' in casa (un proxy eseguibile DEVE assomigliare al suo") print(" originale: qui una corr ALTA e' l'obiettivo, non un difetto)") ref_map = {"XS01 (sleeve)": ref, "XSR01 (pieno)": xsr_const, "TP01": A.tp01_baseline_daily()} print(f" {'cella':<20}" + "".join(f"{k:>16}" for k in ref_map)) for n, s in to_score.items(): line = f" {n:<20}" for _, r in ref_map.items(): Jc = pd.concat({"a": pd.Series(s).dropna(), "b": r}, axis=1, join="inner").dropna() line += f"{(Jc['a'].corr(Jc['b']) if len(Jc) > 30 else float('nan')):>16.3f}" print(line) print(" ⚠ corr a SKH01/VRP01/GTAA01 NON misurata: SKH01 richiede i 5m di DUE asset e il brief") print(" vieta di caricarli insieme. Prior del progetto (25/07): XSR01 sta a |0.001|-|0.071|") print(" da tutti e 5 gli sleeve; un suo sottoinsieme non puo' esserne lontano.") # ---------------------------------------------------------------- (9) null del de-levering print("\n" + "-" * 108) print(" (9) NULL DEL DE-LEVERING — obbligatorio su ogni confronto di maxDD (5 occorrenze note)") print(" Domanda: esiste una LEVA del canonico che dia lo STESSO maxDD della variante?") print(" Se a quel maxDD il canonico ha Sharpe >= alla variante, la variante non aggiunge.") print("-" * 108) def lev_for_dd(canon: pd.Series, target_abs: float, lo=0.01, hi=5.0): """Leva k tale che |maxDD(canon*k)| = target. |maxDD| e' monotono crescente in k.""" if abs(dd(canon * hi)) < target_abs: return None # nemmeno a leva 5x si arriva al DD for _ in range(60): mid = 0.5 * (lo + hi) if abs(dd(canon * mid)) < target_abs: lo = mid else: hi = mid return 0.5 * (lo + hi) def delever_null(variants: dict, canon: pd.Series, label: str): print(f" {label:<22}{'maxDD':>9}{'Sh':>8} {'k_leva canonico':>16}{'Sh canonico':>13} esito") for nm, s in variants.items(): kl = lev_for_dd(canon, abs(dd(s))) if kl is None: print(f" {nm:<22}{dd(s)*100:>8.1f}%{sh(s):>8.2f} {'n.d. (>5x)':>16}" f"{'':>13} maxDD irraggiungibile levando il canonico") continue sc_ = sh(canon * kl) print(f" {nm:<22}{dd(s)*100:>8.1f}%{sh(s):>8.2f} {kl:>16.3f}{sc_:>13.2f} " f"{'il canonico DOMINA' if sc_ >= sh(s) else 'la variante REGGE'}") delever_null({n: xs_series[n] for n in xs_series if n.startswith("MAJ19") and "k=5" not in n}, xs_series["MAJ19 k=5*"], "XS-LITE vs canonico") print() delever_null({n: xsr_series[n] for n in xsr_series if n.startswith("ALL50") and "pieno" not in n}, xsr_series["ALL50 k=pieno*"], "XSR-LITE vs pieno") # ---------------------------------------------------------------- (10) implausible + causalita' print("\n" + "-" * 108) print(" (10) implausible_sharpe + CAUSALITA' (prefisso troncato + decadimento con lag)") print("-" * 108) for n in dict.fromkeys([best_name, cbest_name, "XS MAJ19 k=2", "XS MAJ19 k=5*", "XSR ALL50 k=2"]): if n not in allcells: continue ss = pd.Series(allcells[n]).dropna(); ss.index = pd.to_datetime(ss.index, utc=True) r = A.implausible_sharpe(ss) print(f" {n:<20} implausible={str(r['implausible']):<6} Sh {r.get('sharpe',0):>5.2f} " f"perdite su barre ATTIVE {r.get('loss_frac',float('nan'))*100:>5.1f}% " f"attive {r.get('active_frac',0)*100:>3.0f}% {'; '.join(r['reasons'])[:38]}") cut = int(len(C19) * 0.85) Wf = xs_run(C19, k=2, phase=0)[1] Wp = xs_run(C19.iloc[:cut], k=2, phase=0)[1] dmax = float(np.max(np.abs(Wp[cut - 80:cut] - Wf[cut - 80:cut]))) print(f" XS k=2: pesi ricalcolati su prefisso troncato (cut={cut}) vs pieno, ultime 80 barre: " f"max|diff| = {dmax:.2e} {'CAUSALE' if dmax < 1e-12 else 'LOOK-AHEAD'}") Wq = xs_run(C19, k=2, phase=0) Weff2 = xs_effective_weights(Wq[1], Wq[2]) lags = [] for lag in (0, 1, 2, 3, 5): Wl = np.zeros_like(Weff2) if lag: Wl[lag:] = Weff2[:-lag] else: Wl = Weff2 s, _ = sim_book(Wl, Wq[3], C19.index) lags.append(sh(s)) print(" XS k=2 decadimento con lag (0/1/2/3/5 giorni): " + " / ".join(f"{v:.2f}" for v in lags) + " (dolce = segnale lento, non firma di look-ahead)") # ---------------------------------------------------------------- (11) sintesi print("\n" + "=" * 108) print(" SINTESI — quanto costa concentrare, e dove sta davvero il muro") print("=" * 108) k2, k5 = xs_series["MAJ19 k=2"], xs_series["MAJ19 k=5*"] print(f" XS-LITE k=2 (4 gambe): Sh fase0 {sh(k2):.2f} / fase-MEDIANA {phase_med[2]:.2f}" f" vs canonico k=5 (10 gambe): {sh(k5):.2f} / {phase_med[5]:.2f}") print(f" XSR-LITE k=2 (4 gambe): Sh {sh(xsr_series['ALL50 k=2']):.2f} " f"(IS {sh(split(xsr_series['ALL50 k=2'])[0]):.2f}) vs pieno " f"{sh_xsr_star:.2f} (IS {sh(split(xsr_series['ALL50 k=pieno*'])[0]):.2f})") def _be(x): return f"{x:.0f} bps" if x else f">{int(COST_SWEEP[-1])} bps" print(f" break-even di costo per gamba: XS k=2 {_be(be.get('XS MAJ19 k=2'))}, " f"XS k=5 {_be(be.get('XS MAJ19 k=5*'))}, XSR k=2 {_be(be.get('XSR ALL50 k=2'))}, " f"XSR pieno {_be(be.get('XSR ALL50 k=pieno*'))}") print("-" * 108) print(" 1. CONCENTRARE NON COMPRA RENDIMENTO, COMPRA ESEGUIBILITA'. Il null del de-levering") print(" boccia ogni k tranne i due che sono fortuna d'ancora (XS k=4: fase0 al 90° pctl,") print(" mediana 1.10 < 1.15 del canonico; XSR k=6: +0.03 su 1.65). Nessuna concentrazione") print(" batte il proprio originale a PARI maxDD.") print(" 2. MA IL COSTO E' MOLTO PIU' BASSO DEL PREVISTO, e la mia ipotesi (a) era sbagliata:") print(" mi aspettavo Sh ~ sqrt(ampiezza) e quindi il 40-60% dell'edge a k=2-3. Misurato:") print(" XS-LITE k=2 conserva il 98% del canonico A MEDIANA DI FASE con 4 gambe invece di 10,") print(" e N_hhi scende 6.9 -> 3.0. La radice dell'ampiezza NON descrive questa curva: la") print(" dispersione cross-section e' concentrata negli ESTREMI del ranking, e le gambe 3-5") print(" per lato aggiungono rischio quanto segnale. Il collasso vero e' solo a k=1 (41%).") print(" 3. IL MURO NON E' IL MIN-ORDER, E' IL PESO DELLO SLEEVE. Con $600 DEDICATI, XS01 gira") print(" gia' oggi a k=5 (tracking 1.04, haircut -0.04). E' al 15% del book che muore: a") print(" sleeve $90 il canonico esegue il 4% delle variazioni e il lordo scende al 13% del") print(" target. A quel capitale k=2 e' l'unica cella CON EDGE che traccia (1.01: k=1 traccia") print(" ma non ha edge, Sh 0.13). Il '~$20k' e'") print(" quindi il prodotto di due scelte (peso 15% + k=5), non una proprieta' del segnale.") print(" 4. XSR01 NON SI CONCENTRA: k<=3 perde l'edge IN-SAMPLE (IS 0.96 -> 0.28) e il gate") print(" marginale lo declassa ad ADDS -> NOISE (has_insample_edge=False). Ipotesi (c)") print(" CONFERMATA: li' l'ampiezza E' l'edge. E muore a ~28 bps/gamba di costo totale a") print(" OGNI k, contro >50 bps di XS-LITE: concentrare non ripara il rischio #1 di XSR01.") print(" 5. ⛔ NIENTE DI QUESTO E' DEPLOYABILE OGGI, e non per una ragione misurata qui:") print(" XS01/XSR01 vivono su HYPERLIQUID, e la decisione dell'operatore del 26/07 tiene") print(" il 100% del capitale su Deribit fino a $20k. A $20k il canonico e' eseguibile da") print(" solo. XS-LITE serve SE e QUANDO si apre HL sotto quella soglia — non prima.") print("-" * 108) print(" GATE PRE-REGISTRATO (dichiarato ORA, prima di qualunque dato nuovo):") print(" XS-LITE k=2 su MAJ19 si riapre SOLO se si apre un conto Hyperliquid con >= $300") print(" dedicati allo sleeve, e in quel caso si deploya il k CANONICO (5) se il capitale") print(" dello sleeve e' >= $750, k=2 sotto. Nessun nuovo backtest serve per questa scelta:") print(" e' gia' decisa dalla tabella (5) e non va ri-ottimizzata guardando i dati nuovi.") print(" Come CANDIDATO a sleeve XS-LITE resta bocciato: DSR FAIL a ogni conteggio e null") print(" del de-levering non superato. Non si ri-testa prima del 2027-06 (3.5 anni di") print(" storia HL, di cui >=1.5 fuori dal campione di scoperta di XS01).") print("=" * 108) if __name__ == "__main__": main()