research(wave-0702bis): ondata video-claims — Elliott 3/3 e Albimarini 2/2 scartati, mappa capital scaling 2-5k

Sei agenti, nessun sopravvissuto:
- ELL-A range-cycle: rumore (0/24 Bonferroni; nessuna cella weekly regge
  a tutte le 7 ancore). Lezione pandas: resample("7D", origin) IGNORA
  origin -> usare "168h" per le bande d'ancora weekly.
- ELL-B Fibonacci: l'edge apparente e' la POSIZIONE dei livelli, non i
  numeri (null location-matched: pctl 0.39-0.68); confluenza FAIL 4/4.
- ELL-C canale: Donchian travestito (non batte il Donchian equivalente,
  DSR 0.685, IS 1.40 -> HOLD -0.87; target 1.618 = caso; anchor-luck 4h).
- ALB-A diagonale: il condor stessa-scadenza la batte a ogni f; senza
  gate IV-rank tutte le strutture perdono (3a conferma: l'alpha del VRP
  e' il gate); fee-negativa su Deribit a qualsiasi size; 2o caso
  "0-perdite = Sharpe implausibile" dopo CC01.
- ALB-B claims: 82%/PF 5.16/"420%" consistente con zero skill (P=20-45%,
  78.6% delle finestre 6-mesi lo produce); replay con code reali =
  rovina 1998/2002/2020; la diagonale passa il 12-40% della perdita naked.
- Capital scaling 600->2-5k: unico vincolo binding = cap $300/asset
  (a 5k book al 49% del target) -> AL DEPOSITO alzare a equity/2;
  min_order $5 lasciare; XS01 ~20k confermata; aspettativa onesta
  de-luckata 2k ~EUR 0.6-0.8/g, 5k ~EUR 1.4-2/g.

Nessun nuovo sleeve, book live invariato. 168 test verdi.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-02 23:31:38 +00:00
parent a74cc69583
commit 76120b59c2
8 changed files with 3453 additions and 0 deletions
+594
View File
@@ -0,0 +1,594 @@
#!/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()