#!/usr/bin/env python """r0702_capital_scaling.py — RI-PRICING dei MURI DI SCALA a capitale {600, 2000, 3500, 5000}. NON e' ricerca di strategie nuove: e' la ri-quantificazione ONESTA dei vincoli di scala gia' documentati (tutti quantificati a $600) in vista del funding del conto live a 2-5k $: (1) TP01 smallcap haircut — al.eval_weights_smallcap ai 4 capitali (budget per-asset = C/2, book 50/50): haircut Sharpe modellato→eseguibile, ordini eseguiti/saltati, turnover eseguito, fee drag (tetto noto ~0.4%/anno: a $600 il min-order fa da banda d'isteresi gratuita — ondata timing 2026-07-02 — a 2-5k l'effetto si riduce). (2) Book live TP01+SKH01 75/25 — replica CONCETTUALE dei target di src/live/book.py (formula net = clamp(0.5*E*(0.75*tp_frac + 0.25*skh_sign), ±cap)) sulla griglia 230m storica, SENZA importare il modulo live: notional tipici, % ordini sub-min, e quanto book resta NON investito se il cap $300/asset non viene alzato. (3) Tranching TP01 K=2/K=4 — (diario 2026-07-02: "NO deploy a $600, rivalutare a >=5-10k"): % di ribilanciamenti-tranche >= min-order $5 a ogni capitale. NB il blocco feed-intraday-fuori-path-certificato e' SEPARATO e resta (dichiarato in output). (4) STATARB-RESID — stesso smallcap a 2 gambe (eval_spread_smallcap di orthogonal_signals, W=45/sgn=+1 CONGELATI come in scripts/live/paper_statarb.py — qui solo LETTI, mai toccati). (5) Opzioni Deribit — matematica STATICA (nessun backtest): min 0.1 BTC / 1 ETH per gamba, margine defined-risk dello spread VRP01, fee Deribit 0.03% notional cap 12.5% premio. Solo pricing del muro: regola standing "niente short-vol da modello in deploy" INVARIATA. (6) XS01 (19 gambe HL, "~20k") — replica della matrice pesi di sleeves._xsec_returns (copia locale, sola lettura del modulo) + min order HL ~$10/gamba: conferma/rettifica della soglia. CC01: conti statici gambe. (7) SINTESI — tabella capitale × vincoli + raccomandazioni CONFIG (solo proposte: config/live.json NON viene toccato) + aspettativa onesta EUR/giorno col CAGR de-luckato del book (10-15%, audit SKH01 2026-07-02 path orario — NON i numeri canonici). Causalita'/pandas: niente DatetimeIndex.view("int64") — epoca ms esplicita ovunque (timestamp gia' int64 nei frame certificati). Fee 0.10% RT (0.05%/lato). Nessun file di produzione toccato. uv run python scripts/research/r0702_capital_scaling.py """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd from scipy.stats import norm ROOT = Path("/opt/docker/PythagorasGoal") sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt")) sys.path.insert(0, str(ROOT / "scripts" / "research")) sys.path.insert(0, str(ROOT)) import altlib as al # noqa: E402 from src.data.downloader import load_data # noqa: E402 from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio # noqa: E402 from src.strategies.skyhook import LTF_MIN, SKH01_V2_DD, build_frames, skyhook_entries # noqa: E402 from orthogonal_signals import build_joint, eval_spread, eval_spread_smallcap, f_statarb_resid # noqa: E402 CAPITALS = (600.0, 2000.0, 3500.0, 5000.0) MIN_ORDER = 5.0 # Deribit min order USD (config/live.json) MIN_ORDER_HL = 10.0 # Hyperliquid min order USD (~$10) CAP_NOW = 300.0 # max_notional_per_asset_usd corrente ASSETS = ("BTC", "ETH") MS_D = 86_400_000 MS_LTF = LTF_MIN * 60_000 FEE_SIDE = al.FEE_SIDE # 0.0005 TP = TrendPortfolio(**CANONICAL) def _years(ts_ms: np.ndarray) -> float: return max((int(ts_ms[-1]) - int(ts_ms[0])) / (MS_D * 365.25), 1e-9) # =========================================================================== # (1) TP01 smallcap haircut ai 4 capitali (budget per-asset = C/2, book 50/50) # =========================================================================== def smallcap_counts(tgt: np.ndarray, capital: float, min_order: float = MIN_ORDER) -> dict: """Replica il path di skip di al.eval_weights_smallcap per CONTARE ordini eseguiti vs desiderati (la funzione ufficiale riporta metriche, non i saltati).""" tgt = np.clip(np.nan_to_num(np.asarray(tgt, float)), -10, 10) cur = 0.0 n_exec = 0 n_want = 0 for x in tgt: d = abs(x - cur) if d * capital >= min_order: cur = x n_exec += 1 n_want += 1 elif d * capital >= 0.01: # un cambio era desiderato ma sub-min-order n_want += 1 return dict(n_exec=n_exec, n_want=n_want) def section1(): print("=" * 100) print("(1) TP01 CANONICO 1d — smallcap haircut ai 4 capitali (budget per-asset = C/2, book 50/50)") print(" min order $5 Deribit; fee 0.05%/lato; fee-drag = fee_side * turnover-eseguito/anno") print("=" * 100) out = {} d1 = {a: al.get(a, "1d") for a in ASSETS} tgt = {a: np.nan_to_num(TP.target_series(d1[a])) for a in ASSETS} modeled = {a: al.eval_weights(d1[a], tgt[a]) for a in ASSETS} for a in ASSETS: m = modeled[a] print(f" [{a}] modellato (fiction ribilanciamento continuo): Sh FULL {m['full']['sharpe']:.2f} " f"turnover {m['turnover_per_year']:.1f}x/anno -> fee drag modellato " f"{m['turnover_per_year'] * FEE_SIDE * 100:.2f}%/anno") hdr = (f" {'capitale':>8} {'asset':>5} {'Sh mod':>7} {'Sh real':>8} {'haircut':>8} " f"{'ordini/anno':>12} {'saltati%':>9} {'turn exec':>10} {'feedrag%':>9}") print(hdr) for C in CAPITALS: rows = [] for a in ASSETS: r = al.eval_weights_smallcap(d1[a], tgt[a], capital=C / 2.0, min_order=MIN_ORDER) cnt = smallcap_counts(tgt[a], C / 2.0) yrs = _years(d1[a]["timestamp"].values.astype("int64")) skipped = 1.0 - cnt["n_exec"] / max(cnt["n_want"], 1) drag = r["executed_turnover_per_year"] * FEE_SIDE * 100 rows.append(dict(asset=a, mod=r["modeled"]["sharpe"], real=r["realistic"]["sharpe"], hc=r["sharpe_haircut"], opy=cnt["n_exec"] / yrs, skip=skipped, turn=r["executed_turnover_per_year"], drag=drag)) print(f" {C:>8.0f} {a:>5} {r['modeled']['sharpe']:>7.2f} {r['realistic']['sharpe']:>8.2f} " f"{r['sharpe_haircut']:>8.3f} {cnt['n_exec'] / yrs:>12.0f} {skipped * 100:>8.1f}% " f"{r['executed_turnover_per_year']:>10.1f} {drag:>9.2f}") out[C] = dict(haircut=float(np.mean([x["hc"] for x in rows])), drag=float(np.mean([x["drag"] for x in rows])), skipped=float(np.mean([x["skip"] for x in rows]))) print(" NB contesto (ondata timing 2026-07-02): a $600 il min-order E' la banda d'isteresi ottimale") print(" (ordini -74% a costo ~0). Ai capitali alti la banda implicita si stringe (5$/2500$ = 0.2% del") print(" budget-asset) -> si eseguono quasi tutti i micro-ribilanci e il fee drag risale verso il") print(" modellato (tetto noto ~0.4%/anno) — e' il costo, atteso e piccolo, della fedelta' al modello.") return out # =========================================================================== # (2) BOOK LIVE TP01+SKH01 75/25 — replica concettuale dei target (griglia 230m) # =========================================================================== def skh_sign_series(asset: str) -> tuple[pd.DataFrame, np.ndarray]: """Segno SKH01 (+1/-1/0) alla DECISIONE di ogni barra 230m chiusa, replicando la logica non-overlap entry+exit (TP/SL/max_bars) di sleeves._skyhook_positions su tutta la storia.""" ltf, htf = build_frames(load_data(asset, "5m")) ent = skyhook_entries(ltf, htf, SKH01_V2_DD) H = ltf["high"].values L = ltf["low"].values n = len(ltf) sgn = np.zeros(n) i = 0 while i < n: e = ent[i] if e is None: i += 1 continue d, sl, tp, mb = e["dir"], e["sl"], e["tp"], e["max_bars"] exit_idx = None for s in range(1, mb + 1): j = i + s if j >= n: break hit = (L[j] <= sl or H[j] >= tp) if d == 1 else (H[j] >= sl or L[j] <= tp) if hit or s == mb: exit_idx = j break if exit_idx is None: # trade ancora aperto a fine storia sgn[i:] = d break sgn[i:exit_idx] = d # alla decisione di exit_idx il trade e' gia' chiuso i = exit_idx + 1 return ltf, sgn def tp_frac_on_ltf(asset: str, ltf: pd.DataFrame) -> np.ndarray: """tp_frac (target TP01 daily, causale) mappato sulla griglia 230m: per ogni chiusura 230m l'ultimo target daily la cui CHIUSURA nominale (open-label + 24h, epoca ms) e' <= chiusura 230m.""" d1 = al.get(asset, "1d") tgt = np.nan_to_num(TP.target_series(d1)) close_d = d1["timestamp"].values.astype("int64") + MS_D close_l = ltf["timestamp"].values.astype("int64") + MS_LTF idx = np.searchsorted(close_d, close_l, side="right") - 1 return np.where(idx >= 0, tgt[np.maximum(idx, 0)], 0.0) def book_sim(tpf: np.ndarray, sgn: np.ndarray, ts_ms: np.ndarray, C: float, cap: float, min_order: float = MIN_ORDER) -> dict: """Replica PURA della formula di book.book_net_target + build_book_order (senza import live): equity fissa = C (isola l'effetto scala; il live usa l'equity reale), ordini market al delta.""" raw = 0.5 * C * (0.75 * np.maximum(np.nan_to_num(tpf), 0.0) + 0.25 * np.nan_to_num(sgn)) net = np.clip(raw, -cap, cap) pos = 0.0 executed = [] n_skip = 0 for x in net: d = x - pos if abs(d) >= min_order: executed.append(abs(d)) pos = x elif abs(d) >= 0.01: n_skip += 1 yrs = _years(ts_ms) ex = np.asarray(executed) if executed else np.asarray([0.0]) nz = np.abs(raw) > 1e-9 mean_raw = float(np.mean(np.abs(raw[nz]))) if nz.any() else 0.0 mean_net = float(np.mean(np.minimum(np.abs(raw[nz]), cap))) if nz.any() else 0.0 return dict(orders_py=len(executed) / yrs, med_order=float(np.median(ex)), p90_order=float(np.percentile(ex, 90)), sub_min=n_skip / max(n_skip + len(executed), 1), cap_bind=float(np.mean(np.abs(raw) > cap)), invested=(mean_net / mean_raw) if mean_raw > 0 else 1.0, mean_raw=mean_raw) def section2(): print() print("=" * 100) print("(2) BOOK LIVE TP01+SKH01 75/25 — replica formula book.py su griglia 230m storica") print(" net = clamp(0.5*C*(0.75*tp_frac + 0.25*skh_sign), +/-cap); equity fissa = C; min order $5") print("=" * 100) data = {} for a in ASSETS: ltf, sgn = skh_sign_series(a) tpf = tp_frac_on_ltf(a, ltf) data[a] = (ltf["timestamp"].values.astype("int64"), tpf, sgn) print(f" [{a}] barre 230m: {len(sgn)} tempo con SKH aperto: {np.mean(sgn != 0) * 100:.1f}% " f"tp_frac medio: {np.mean(np.maximum(tpf, 0)):.2f}") out = {} hdr = (f" {'capitale':>8} {'cap/asset':>10} {'asset':>5} {'ordini/anno':>12} {'medONot$':>9} " f"{'p90$':>7} {'submin%':>8} {'capbind%':>9} {'investito%':>11}") print(hdr) for C in CAPITALS: for cap, lbl in ((CAP_NOW, "300 (oggi)"), (C / 2.0, "C/2 (prop)")): if C == 600.0 and cap != CAP_NOW: continue # a 600 i due scenari coincidono inv = [] for a in ASSETS: ts, tpf, sgn = data[a] r = book_sim(tpf, sgn, ts, C, cap) inv.append(r["invested"]) print(f" {C:>8.0f} {lbl:>10} {a:>5} {r['orders_py']:>12.0f} {r['med_order']:>9.0f} " f"{r['p90_order']:>7.0f} {r['sub_min'] * 100:>7.1f}% {r['cap_bind'] * 100:>8.1f}% " f"{r['invested'] * 100:>10.1f}%") out[(C, lbl)] = float(np.mean(inv)) print(" Lettura: 'investito%' = quota del target-notional desiderato che il cap/asset lascia") print(" effettivamente a mercato (media sulle barre con target != 0). Col cap fermo a $300 il book") print(" a 2-5k gira sotto-investito in modo strutturale; col cap = C/2 il rapporto attuale (300/600)") print(" e' preservato e il vincolo torna a mordere solo sulle leve alte (tp_frac -> 2x).") return out # =========================================================================== # (3) TRANCHING TP01 K=2/K=4 — eseguibilita' delle tranche a 2-5k # =========================================================================== def section3(): print() print("=" * 100) print("(3) TRANCHING TP01 (K ancore daily sfasate, 1/K del capitale per tranche)") print(" ordine-tranche per asset = |Delta tgt| * C/(2K); eseguibile se >= $5") print("=" * 100) out = {} dd = {} for a in ASSETS: d1 = al.get(a, "1d") tgt = np.nan_to_num(TP.target_series(d1)) d = np.abs(np.diff(tgt, prepend=0.0)) dd[a] = d[d > 1e-12] # solo i giorni in cui un ribilancio e' desiderato print(f" [{a}] |Delta tgt| giornaliero (giorni con cambio): mediana {np.median(dd[a]):.4f} " f"p25 {np.percentile(dd[a], 25):.4f} p75 {np.percentile(dd[a], 75):.4f}") alld = np.concatenate([dd[a] for a in ASSETS]) print(f" {'capitale':>8} {'K':>3} {'$/tranche-asset':>16} {'ordine mediano $':>17} " f"{'exec-eventi%':>13} {'exec-turnover%':>15}") for C in CAPITALS: for K in (1, 2, 4): pt = C / (2.0 * K) mask = alld * pt >= MIN_ORDER ex_ev = float(np.mean(mask)) # % degli EVENTI di ribilancio ex_tw = float(alld[mask].sum() / alld.sum()) # % del TURNOVER (massa) eseguibile med = float(np.median(alld)) * pt out[(C, K)] = ex_tw print(f" {C:>8.0f} {K:>3} {pt:>16.0f} {med:>17.2f} {ex_ev * 100:>12.1f}% {ex_tw * 100:>14.1f}%") print(" Lettura: 'exec-eventi%' basso e' in parte FISIOLOGICO (i micro-ribilanci vol-target saltati") print(" = la banda d'isteresi gratuita); il degrado vero e' 'exec-turnover%': la quota della MASSA") print(" di ribilanciamento che ogni tranche riesce a eseguire (i cambi grossi = entrate/uscite).") print(" Il 'degenera in K=1 a $600' del diario e' la granularita' EVENTO: l'ordine-tranche mediano") print(" K=2 resta sotto $5 perfino a 5k ($3.7) -> le tranche non fanno il fine-tuning giornaliero,") print(" ma da ~2k in su eseguono >95% della massa (entrate/uscite) ciascuna alla propria ancora.") print(" NB: distribuzione |Delta| presa dall'ancora canonica (proxy: le altre ancore hanno") print(" distribuzioni simili — r0702_tp01_offset). BLOCCO SEPARATO E INVARIATO: il tranching") print(" richiede decisioni intraday (ancore != 00:00) => feed intraday FUORI dal path certificato") print(" daily del cron attuale. Anche dove il min-order non degenera piu' K=2 in K=1, il deploy") print(" resta condizionato a quel lavoro di feed/infra (diario 2026-07-02-timing-crt-wave).") return out # =========================================================================== # (4) STATARB-RESID — haircut REAL ai 4 capitali (2 gambe, W=45/sgn=+1 congelati) # =========================================================================== def section4(): print() print("=" * 100) print("(4) STATARB-RESID (paper_statarb, config CONGELATA W=45 sgn=+1) — haircut min-order a scala") print("=" * 100) j = build_joint("1d") pos = f_statarb_resid(W=45, sgn=+1)(j) # identica a paper_statarb (solo lettura parametri) mod = eval_spread(j, pos) print(f" modellato (2 gambe, fee 0.05%/lato x2): Sh FULL {mod['full']['sharpe']:.2f} " f"turnover {mod['turnover']:.1f}x/anno (per gamba)") out = {} print(f" {'capitale':>8} {'Sh mod':>7} {'Sh real':>8} {'haircut':>8} {'trade eseguiti':>15}") for C in CAPITALS: r = eval_spread_smallcap(j, pos, capital=C, min_order=MIN_ORDER) out[C] = r["sharpe_haircut"] print(f" {C:>8.0f} {r['modeled']['sharpe']:>7.2f} {r['realistic']['sharpe']:>8.2f} " f"{r['sharpe_haircut']:>8.3f} {r['n_executed_trades']:>15d}") print(" (il vincolo binding e' il nozionale PER-GAMBA |Delta pos|*C >= $5; a 1d il turnover e'") print(" bassissimo -> haircut gia' ~0 a $600, a 2-5k e' rumore. Il muro di statarb NON e' la scala:") print(" e' l'EDGE — DSR 0.929 < 0.95, forward-monitor in corso.)") return out # =========================================================================== # (5) OPZIONI DERIBIT — matematica statica del muro (VRP01 spread / min size) # =========================================================================== def _bs_put(S, K, T, sig): if T <= 0 or sig <= 0: return max(K - S, 0.0) d1 = (np.log(S / K) + 0.5 * sig ** 2 * T) / (sig * np.sqrt(T)) return K * norm.cdf(-(d1 - sig * np.sqrt(T))) - S * norm.cdf(-d1) def _strike_from_delta(S, T, sig, target_delta): return S * np.exp(0.5 * sig ** 2 * T - (-norm.ppf(-target_delta)) * sig * np.sqrt(T)) def section5(): print() print("=" * 100) print("(5) OPZIONI DERIBIT — pricing STATICO del muro (nessun backtest, nessuna proposta di deploy)") print(" struttura VRP01: put credit spread 7g, short delta -0.28 / long -0.10, defined-risk") print(" min size: 0.1 BTC/gamba, 1 ETH/gamba; fee 0.03% notional cap 12.5% premio (per gamba)") print("=" * 100) T = 7.0 / 365.25 res = {} for a, minc in (("ETH", 1.0), ("BTC", 0.1)): d1 = al.get(a, "1d") S = float(d1["close"].iloc[-1]) dv = pd.read_parquet(ROOT / "data" / "raw" / f"dvol_{a.lower()}.parquet") iv_last = float(dv["close"].iloc[-1]) / 100.0 iv_med = float(dv["close"].iloc[-365:].median()) / 100.0 rows = {} for lbl, sig in (("DVOL oggi", iv_last), ("DVOL mediana 1y", iv_med)): Ks = _strike_from_delta(S, T, sig, -0.28) Kl = _strike_from_delta(S, T, sig, -0.10) prem_s = _bs_put(S, Ks, T, sig) * minc prem_l = _bs_put(S, Kl, T, sig) * minc credit = prem_s - prem_l width = (Ks - Kl) * minc maxloss = width - credit # margine defined-risk ~ max loss fee = (min(0.0003 * S * minc, 0.125 * prem_s) + min(0.0003 * S * minc, 0.125 * prem_l)) rows[lbl] = dict(S=S, sig=sig, credit=credit, width=width, maxloss=maxloss, fee=fee, net=credit - fee) print(f" [{a} x{minc}] {lbl}: spot ${S:,.0f} IV {sig * 100:.0f}% strike {Ks:,.0f}/{Kl:,.0f}" f" credito ${credit:,.2f} width ${width:,.2f} margine/max-loss ${maxloss:,.2f}" f" fee 2 gambe ${fee:,.2f} credito NETTO ${credit - fee:,.2f}" f" ({(credit - fee) / max(credit, 1e-9) * 100:.0f}% del lordo)") res[a] = rows["DVOL mediana 1y"] print(f" + fee delivery a scadenza se ITM: 0.015% notional cap 12.5% (non inclusa sopra).") m_eth = res["ETH"]["maxloss"] m_btc = res["BTC"]["maxloss"] print(f"\n {'capitale':>8} {'spread ETH conc. @12% peso':>27} {'@100% conto':>12} " f"{'% conto/spread':>15}") out = {} for C in CAPITALS: n12 = int((0.12 * C) // m_eth) nfull = int(C // m_eth) out[C] = n12 print(f" {C:>8.0f} {n12:>27d} {nfull:>12d} {m_eth / C * 100:>14.1f}%") c_btc_1 = m_btc / 0.12 c_btc_3 = 3 * m_btc / 0.12 print(f"\n BTC options (0.1 BTC min): margine/max-loss ~${m_btc:,.0f}/spread -> a peso 12% servono" f" ~${c_btc_1:,.0f} per 1 spread, ~${c_btc_3:,.0f} per granularita' minima (3 step di size).") print(" Muro fee: il credito ETH sopravvive alle fee (cap 12.5% del premio ~ perdi al massimo un") print(" quarto del credito con entry+delivery; il numero esatto sopra). Il muro VERO resta la regola") print(" standing: NIENTE short-vol da modello in deploy (f di stress reale mai osservato).") return out, m_eth, m_btc # =========================================================================== # (6) XS01 (19 gambe Hyperliquid) e CC01 — soglie di scala con conti espliciti # =========================================================================== def xs01_positions() -> tuple[pd.DatetimeIndex, np.ndarray]: """COPIA di sola-lettura della matrice pesi di src/portfolio/sleeves._xsec_returns (stessi parametri XS_CFG / XS_UNIVERSE), che ritorna le POSIZIONI finali per gamba P = W * scale(vol-target) invece dei rendimenti. Nessun modulo di produzione modificato.""" from src.portfolio.sleeves import XS_CFG, XS_UNIVERSE, _HL_DIR cols = {} for sym in XS_UNIVERSE: p = _HL_DIR / f"hl_{sym.lower()}_1d.parquet" if p.exists(): d = pd.read_parquet(p) cols[sym] = 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").sort_index().dropna() px = C.values n, A = px.shape lookbacks, H, k = XS_CFG["lookbacks"], XS_CFG["H"], XS_CFG["k"] mode, tv = XS_CFG["mode"], XS_CFG["target_vol"] disp_pct = XS_CFG.get("disp_pct", 0) minhist = XS_CFG.get("disp_minhist", 20) mlb = max(lookbacks) dret = np.vstack([np.zeros(A), px[1:] / px[:-1] - 1.0]) W = np.zeros((n, A)) w = np.zeros(A) disp_hist = [] for i in range(n): if i >= mlb and i % H == 0: rLs = [px[i] / px[i - L] - 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(A) 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(A) lo, hi = order[:k], 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(A) 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 C.index, W * scale[:, None] def xs01_exec(P: np.ndarray, idx: pd.DatetimeIndex, sleeve_cap: float, min_order: float = MIN_ORDER_HL) -> dict: """Skip sequenziale per gamba: un Delta di nozionale < min_order NON si esegue.""" n, A = P.shape yrs = max((idx[-1] - idx[0]).days / 365.25, 1e-9) tot_mod = 0.0 n_orders = 0 orders = [] for a in range(A): tgt = P[:, a] tot_mod += float(np.abs(np.diff(tgt, prepend=0.0)).sum()) cur = 0.0 for x in tgt: d = abs(x - cur) if d * sleeve_cap >= min_order: orders.append(d * sleeve_cap) cur = x n_orders += 1 exec_turn = float(np.sum(orders) / sleeve_cap) if orders else 0.0 return dict(exec_share=exec_turn / tot_mod if tot_mod > 0 else 0.0, orders_py=n_orders / yrs, med_order=float(np.median(orders)) if orders else 0.0) def section6(): print() print("=" * 100) print("(6) XS01 (19 gambe HL, min order ~$10) e CC01 — le soglie '~20k' ricontate") print("=" * 100) idx, P = xs01_positions() gross = np.abs(P).sum(axis=1) print(f" XS01: gross tipico {np.median(gross[gross > 0]):.2f}x del capitale sleeve; " f"10 gambe attive (5 long + 5 short da 19), ribilancio ogni 10g + vol-target giornaliero") print(f" {'capitale':>8} {'sleeve@15%':>11} {'exec-turnover%':>15} {'ordini/anno':>12} {'medONot$':>9}") out = {} for Ctot in list(CAPITALS) + [10000.0, 20000.0, 50000.0]: sc = 0.15 * Ctot r = xs01_exec(P, idx, sc) out[Ctot] = r["exec_share"] tag = " <- dichiarato" if Ctot == 20000 else "" print(f" {Ctot:>8.0f} {sc:>11.0f} {r['exec_share'] * 100:>14.1f}% {r['orders_py']:>12.0f} " f"{r['med_order']:>9.0f}{tag}") print(" Lettura: exec-turnover% = quota del turnover modellato che supera il min-order $10 per gamba.") print(" Sotto ~80-90% il libro reale diverge dal backtest (tracking error non modellato).") print("\n CC01 (cash-and-carry HL, spot+perp stesso asset — Sharpe modellato = ARTEFATTO, v. diario):") for Ctot in CAPITALS: for N in (2, 4, 19): D = 0.75 * Ctot # deploy carry: spot cash-funded + margine perp ~D/3 per_leg = D / (2 * N) yld = (0.08 * D, 0.14 * D) if N == 4: print(f" C={Ctot:>5.0f} N={N:>2} asset ({2 * N} gambe): ${per_leg:,.0f}/gamba; " f"funding 8-14% su ${D:,.0f} = ${yld[0]:,.0f}-{yld[1]:,.0f}/anno") print(" -> il MIN-ORDER non e' il muro di CC01 gia' a 2k (gambe > $90); i muri sono strutturali:") print(" (a) Sharpe 11-13 artefatto (manca il 2022: deleveraging/funding-negativo/basis blowout),") print(" (b) funding HL non eseguibile da Deribit (secondo venue + travaso capitale),") print(" (c) liquidazione short e slippage non modellati. A 5k il carry atteso ($300-525/anno") print(" lordi, prociclico) NON paga il rischio operativo: resta LEAD da rivedere a ~20k+.") return out # =========================================================================== # (7) SINTESI OPERATIVA # =========================================================================== def book_cagr_canonical() -> float: """CAGR canonico del book Deribit (0.75*TP01 + 0.25*SKH01, daily) — SOLO per confronto col band de-luckato 10-15% (audit SKH01 2026-07-02: hourly-path + fase mediana).""" from src.portfolio.sleeves import _skyhook_returns, _tp01_returns # sola lettura tp = _tp01_returns() sk = _skyhook_returns() if tp.index.tz is None: tp.index = tp.index.tz_localize("UTC") J = pd.concat({"tp": tp, "sk": sk}, axis=1, join="inner").fillna(0.0) r = 0.75 * J["tp"] + 0.25 * J["sk"] eq = float(np.prod(1.0 + r.values)) yrs = len(r) / 365.25 return eq ** (1.0 / yrs) - 1.0 def section7(s1, s2, s3, s4, s5, s6): n_eth_12, m_eth, m_btc = s5 print() print("=" * 100) print("(7) SINTESI OPERATIVA — capitale x vincoli (numeri dalle sezioni sopra)") print("=" * 100) hdr = (f" {'capitale':>8} | {'TP01 haircut':>12} | {'book inv.% cap300':>17} | {'K=2 turn%':>9} | " f"{'statarb hc':>10} | {'spreadETH@12%':>13} | {'XS01 exec%':>10}") print(hdr) print(" " + "-" * (len(hdr) - 2)) for C in CAPITALS: inv300 = s2.get((C, "300 (oggi)"), float("nan")) print(f" {C:>8.0f} | {s1[C]['haircut']:>12.3f} | {inv300 * 100:>16.1f}% | " f"{s3[(C, 2)] * 100:>8.1f}% | {s4[C]:>10.3f} | {n_eth_12[C]:>13d} | " f"{s6[C] * 100:>9.1f}%") try: cagr = book_cagr_canonical() print(f"\n CAGR canonico book Deribit (0.75 TP01 + 0.25 SKH01): {cagr * 100:.1f}%/anno " f"(lens research, ancora canonica)") except Exception as e: # pragma: no cover — il resto del report resta valido print(f"\n (CAGR canonico book non calcolabile in questo run: {e})") print(" ASPETTATIVA ONESTA (CAGR de-luckato 10-15%/anno — audit SKH01 2026-07-02, path orario +") print(" fase mediana, NON i numeri canonici):") for C in (2000.0, 5000.0): lo, hi = C * 0.10 / 365.25, C * 0.15 / 365.25 print(f" a ${C:,.0f}: ~EUR {lo:.2f}-{hi:.2f}/giorno (=${C * 0.10:,.0f}-{C * 0.15:,.0f}/anno)") print(" Regola di onesta' del progetto INVARIATA: EUR 50/giorno richiede ~130k di capitale a questo") print(" CAGR — il funding a 2-5k NON cambia l'ordine di grandezza, cambia solo cosa e' ESEGUIBILE.") print() print(" RACCOMANDAZIONI CONFIG (SOLO PROPOSTE — config/live.json non viene toccato da questo script):") print(" 1. max_notional_per_asset_usd: alzarlo INSIEME al funding mantenendo il rapporto attuale") print(" cap = equity/2 -> $1000 a 2k, $1750 a 3.5k, $2500 a 5k. Col cap fermo a $300 il book") print(" resta strutturalmente sotto-investito (vedi colonna 'book inv.% cap300' sopra).") print(" 2. min_order_usd $5: lasciarlo (floor Deribit). Nessuna banda artificiale in piu': il fee") print(" drag risale verso il modellato (~0.4%/anno max, sez.1) ed e' il costo corretto della") print(" fedelta' al target (l'ondata timing ha mostrato che il lag costa piu' del risparmio).") print(" 3. Tranching K=2: la matematica degli ordini regge da ~2k (exec-turnover >95%/tranche,") print(" sez.3) e a 5k e' matura (98%), MA (a) il beneficio atteso e' solo riduzione di varianza") print(" della STIMA (Delta-Sharpe n.s., diario timing) e (b) il blocco feed-intraday-fuori-path-") print(" certificato resta intero -> NON cablarlo ora; rivalutare solo con feed intraday validato.") print(" 4. Opzioni: a 3.5-5k il singolo spread ETH min-size e' sostenibile a peso ~12% (sez.5),") print(" ma la regola standing 'niente short-vol da modello' NON decade col capitale.") print(" 5. XS01/CC01: restano STAT-MODE/LEAD anche a 5k (sez.6). Soglia ~20k confermata per XS01") print(" come ordine di grandezza; per CC01 il capitale non e' comunque il muro binding.") def main(): s1 = section1() s2 = section2() s3 = section3() s4 = section4() s5 = section5() s6 = section6() section7(s1, s2, s3, s4, s5, s6) if __name__ == "__main__": main()