Files
PythagorasGoal/scripts/research/r0702_capital_scaling.py
T
Adriano Dal Pastro 76120b59c2 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>
2026-07-02 23:31:38 +00:00

595 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()