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:
@@ -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()
|
||||
Reference in New Issue
Block a user