research(options): gamma scalping (long-vol) — SCARTATO
"Scalping BTC/ETH con copertura in opzioni" = gamma scalping, lo specchio esatto del VRP01 (long straddle ATM + delta-hedge -> incassa RV-IV). Esito: perde ogni anno / ogni variante / ogni frequenza (Sharpe -3 a -6). Diagnostica strutturale: a 1d IV>=RV (paghi il VRP); a 1h RV>IV gross ma il rehedge orario paga 24x la fee di hedge -> variante peggiore. Marginale vs TP01 = DILUTES, non e' nemmeno un hedge. Muro eseguibilita': opzione BTC min $5968 >> $600. Schiacciato tra due muri: rehedge lento = premio, veloce = fee -> nessuna frequenza vince. Il VRP01 (lato short, gated) resta l'unico edge opzioni. - scripts/research/options_gamma_scalp.py (harness: naked/cheap/rich, 1d+1h, diagnostica RV-IV) - tests/test_gamma_scalp.py (lock della conclusione) - docs/diary/2026-06-26-gamma-scalp-options.md - CLAUDE.md: riga nella sintesi di ricerca Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,17 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
|
||||
regime-luck), calendar/seasonality (buy&hold travestito), volume/vol e momentum-reversal (negativi).
|
||||
- **MORTO/confermato artefatto:** mean-reversion / fade (negativo anche a fee zero — la vecchia
|
||||
libreria +201%/+1238% era contaminazione); trend 5m/15m (fee).
|
||||
- **GAMMA SCALPING (long-vol) "scalping BTC/ETH con copertura in opzioni" — SCARTATO (2026-06-26)** —
|
||||
`scripts/research/options_gamma_scalp.py`, test `tests/test_gamma_scalp.py`. È lo **specchio
|
||||
esatto del VRP01** (long straddle ATM + delta-hedge: incassa **RV−IV**, dove VRP01 incassa IV−RV).
|
||||
Perde **ogni anno, ogni variante, ogni frequenza** (Sharpe −3 a −6; nudo/cheap-gated/rich-skip;
|
||||
rehedge 1d e 1h). Diagnostica strutturale: a 1d IV≈o>RV (BTC +4.9pp) → paghi il VRP; a 1h RV>IV
|
||||
gross ma (a) gonfiata da microstruttura, (b) il rehedge orario paga **24× la fee di hedge** →
|
||||
variante *peggiore* (−6). Marginale vs TP01 = **DILUTES**, non è nemmeno hedge (perde sia TP01-up
|
||||
sia TP01-down). Muro eseguibilità: opzione BTC min $5.968 ≫ $600. Schiacciato tra due muri:
|
||||
rehedge lento = premio, veloce = fee → **nessuna frequenza vince.** Regola gemella del VRP:
|
||||
*niente long-vol scalp da modello in deploy*. Il VRP01 (lato short, gated) resta l'unico edge
|
||||
opzioni — funziona perché sta sul lato *giusto* dello stesso premio. Diario `2026-06-26-gamma-scalp-options.md`.
|
||||
- **Soffitto strutturale BTC/ETH-direzionale ~1.3** superato SOLO espandendo a un meccanismo diverso:
|
||||
cross-sectional su universo Hyperliquid certificato (XS01) → portafoglio Sharpe ~1.55.
|
||||
- **Sweep "strategie alternative" (2026-06-20) — 104 ipotesi / 153 agenti / NIENTE di nuovo regge.**
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# 2026-06-26 — "Scalping BTC/ETH con copertura in opzioni" = gamma scalping → SCARTATO
|
||||
|
||||
**Domanda (goal utente):** analizzare lo scalping su BTC/ETH con copertura in opzioni come
|
||||
possibile nuovo sleeve. **Esito: negativo, strutturale, su tutte le frequenze. Non è uno sleeve,
|
||||
non è nemmeno un hedge.** Branch `research/gamma-scalp-options`, script
|
||||
`scripts/research/options_gamma_scalp.py`.
|
||||
|
||||
## Cos'è davvero "scalping con copertura in opzioni"
|
||||
|
||||
L'interpretazione rigorosa è il **gamma scalping**: compri un'opzione (la *copertura* = long
|
||||
gamma), delta-hedgi il sottostante a cadenza fissa (lo *scalp*), e il P&L netto è
|
||||
≈ dollar-gamma × (vol realizzata² − vol implicita²). È lo **specchio esatto del VRP01** (short-vol):
|
||||
VRP01 incassa IV−RV (positivo in media), il gamma scalping incassa **RV−IV** (negativo in media).
|
||||
|
||||
Modello (mirror della struttura VRP per comparabilità): long straddle ATM, tenor 7g, IV = DVOL
|
||||
Deribit (la stessa fonte del VRP), delta-hedge sui prezzi certificati, fee opzioni cap 12.5% del
|
||||
premio + fee perp 0.05%/lato sull'hedge turnover, return-on-notional vol-targeted 20%.
|
||||
|
||||
## La diagnostica strutturale (il cuore)
|
||||
|
||||
| asset | IV (DVOL) | RV 1d | IV−RV 1d | RV 1h | IV−RV 1h |
|
||||
|---|---|---|---|---|---|
|
||||
| BTC | 60.9% | 56.0% | **+4.9pp** | 64.5% | −3.6pp |
|
||||
| ETH | 74.9% | 76.0% | −1.0pp | 81.0% | −6.1pp |
|
||||
|
||||
- A **rehedge giornaliero** l'IV ≈ o > la RV → il long gamma **paga il VRP** (sei sul lato sbagliato
|
||||
del premio). Perde gross, prima di ogni fee.
|
||||
- A **rehedge orario** la RV *supera* l'IV (BTC +3.6pp, ETH +6.1pp gross) — sembrerebbe un edge…
|
||||
**ma è una trappola doppia:** (a) la RV_1h è gonfiata da microstruttura (bid-ask bounce), quindi
|
||||
il "gross edge" è in parte fittizio; (b) il rehedge orario paga **24× la fee di hedge**, che
|
||||
spazza via qualunque edge intraday reale.
|
||||
|
||||
## I numeri (ogni variante, ogni anno, ogni frequenza: PERDE)
|
||||
|
||||
| variante | rehedge | Sharpe | CAGR | per-anno |
|
||||
|---|---|---|---|---|
|
||||
| NAKED | 1d | **−3.99** | −56% | 2021..2026 tutti −25..−68% |
|
||||
| CHEAP-GATED (IVr<0.30) | 1d | −3.05 | −47% | tutti negativi |
|
||||
| RICH-SKIP (IVr>0.90) | 1d | −3.92 | −55% | tutti negativi |
|
||||
| NAKED | 1h | **−6.06** | −71% | tutti −38..−81% (peggio: le fee) |
|
||||
| CHEAP-GATED (IVr<0.30) | 1h | −4.96 | −64% | tutti negativi |
|
||||
|
||||
Il gate "compra vol solo quando è a sconto" (lo specchio del gate vincente del VRP, *vendi vol solo
|
||||
quando è cara*) **migliora ma non salva**: −3.05 invece di −3.99. Il segno resta negativo perché
|
||||
l'IV-rank basso non garantisce RV > IV su 7 giorni.
|
||||
|
||||
## Scoring marginale vs TP01
|
||||
|
||||
Tutte le varianti: **DILUTES**, `is_hedge=False`, `has_insample_edge=False` (IS Sharpe −3..−4.5),
|
||||
blend-25 uplift hold −1.2/−1.5. Non aiuta nemmeno come hedge: perde sia quando TP01 è su
|
||||
(uplift −1.2..−1.6) sia quando TP01 è giù (uplift −2.5..−3.2). Corr a TP01 ≈ 0 ma il drift negativo
|
||||
domina la matematica di diversificazione.
|
||||
|
||||
## Eseguibilità a ~$600 (muro indipendente)
|
||||
|
||||
- **BTC**: contratto 1 BTC, min 0.1 BTC → notional minimo **$5.968** ≫ $600 → **NON eseguibile**.
|
||||
- **ETH**: min 0.1 ETH → **$157** → marginalmente OK, ma un solo straddle = ~25% del conto in premio.
|
||||
|
||||
Anche se l'edge esistesse, BTC è fuori e ETH è troppo grosso per diversificare.
|
||||
|
||||
## Conclusione
|
||||
|
||||
Lo "scalping con copertura in opzioni" è **schiacciato tra due muri**: rehedge lento → paghi il
|
||||
premio (RV < IV); rehedge veloce → paghi le fee (24×/day). **Nessuna frequenza vince.** Conferma
|
||||
due lezioni di prim'ordine del progetto, ora quantificate sul long-vol:
|
||||
1. **Le fee uccidono lo scalping** (il rehedge orario, vera frequenza di scalping, è la variante
|
||||
*peggiore*: −6 Sharpe).
|
||||
2. **Vol-da-modello è fragile in entrambe le direzioni.** Regola gemella del VRP: *niente long-vol
|
||||
scalp da modello in deploy*.
|
||||
|
||||
**Il VRP01 (lato short, gated, rischio-definito) resta l'unico edge opzioni del progetto** —
|
||||
e funziona proprio perché sta sul lato *giusto* dello stesso premio che il gamma scalp paga.
|
||||
|
||||
SCARTATO. Script preservato come riferimento. Nessun impatto sul book live (branch separato).
|
||||
@@ -0,0 +1,296 @@
|
||||
"""GAMMA SCALPING (long-vol) — "scalping BTC/ETH con copertura in opzioni", valutato onestamente.
|
||||
|
||||
Interpretazione rigorosa di "fare scalping con copertura in opzioni" = GAMMA SCALPING:
|
||||
compri un'opzione (la COPERTURA = long gamma), delta-hedgi il sottostante a cadenza fissa
|
||||
(lo SCALP), e il P&L netto e' ~ dollar-gamma * (vol realizzata^2 - vol implicita^2). E' lo
|
||||
SPECCHIO ESATTO del VRP01 (short-vol): VRP01 incassa IV-RV (positivo in media), il gamma
|
||||
scalping incassa RV-IV (negativo in media, ma CONVESSO -> paga nei crash).
|
||||
|
||||
Modello (mirror della struttura VRP per comparabilita'):
|
||||
- long STRADDLE ATM, tenor settimanale (7g), strike = spot all'ingresso.
|
||||
- IV = DVOL Deribit (data/raw/dvol_*.parquet) — la stessa fonte IV del VRP.
|
||||
- delta-hedge GIORNALIERO sui prezzi certificati 1d (BTC/ETH).
|
||||
- P&L delta-hedged classico per step: DG_t * [(dS/S)^2 - sigma^2*dt],
|
||||
DG_t = dollar-gamma dello straddle = phi(d1)*S/(sigma*sqrt(tau)).
|
||||
- fee opzioni Deribit cap 12.5% del premio (come VRP) + fee perp sull'hedge turnover.
|
||||
- return-on-notional (pnl/S0), poi VOL-TARGET 20% annuo per apples-to-apples con gli altri sleeve.
|
||||
|
||||
Varianti testate:
|
||||
NAKED -> sempre long gamma (baseline: deve perdere il premio in media).
|
||||
CHEAP-GATED -> entra solo quando IV-rank < gate (compri vol a sconto = specchio del gate VRP).
|
||||
RICH-SKIP -> entra sempre tranne quando IV-rank > skip (evita di pagare vol carissima).
|
||||
|
||||
Output: metriche standalone + per-anno + SCORING MARGINALE vs TP01 (corr, blend uplift, is_hedge,
|
||||
has_insample_edge) + il muro di ESEGUIBILITA' a $600 (min size opzioni Deribit).
|
||||
|
||||
uv run python scripts/research/options_gamma_scalp.py
|
||||
|
||||
ONESTA': premio MODELLATO su DVOL ATM (skew non esplicito) — stesso caveat del VRP. Long-vol da
|
||||
modello: meno pericoloso dello short-vol (loss capped al premio), ma resta un LEAD da modello.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "alt"))
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy.stats import norm
|
||||
|
||||
from src.data.downloader import load_data
|
||||
from src.strategies.trend_portfolio import resample_1d
|
||||
from altlib import marginal_vs_tp01 # type: ignore
|
||||
|
||||
RAW = PROJECT_ROOT / "data" / "raw"
|
||||
SQ365 = np.sqrt(365.25)
|
||||
DT = 1.0 / 365.25
|
||||
|
||||
# fee model (mirror VRP): opzioni Deribit cap 12.5% del premio; perp taker 0.05%/lato sull'hedge.
|
||||
OPT_FEE_FRAC = 0.125
|
||||
HEDGE_FEE_SIDE = 0.0005
|
||||
TENOR_D = 7
|
||||
|
||||
|
||||
def _bs_straddle(S, K, T, sig):
|
||||
"""Premio BS di uno straddle ATM (call+put), r=0."""
|
||||
if T <= 0 or sig <= 0:
|
||||
return abs(S - K)
|
||||
d1 = (np.log(S / K) + 0.5 * sig ** 2 * T) / (sig * np.sqrt(T))
|
||||
d2 = d1 - sig * np.sqrt(T)
|
||||
call = S * norm.cdf(d1) - K * norm.cdf(d2)
|
||||
put = K * norm.cdf(-d2) - S * norm.cdf(-d1)
|
||||
return call + put
|
||||
|
||||
|
||||
def _dvol(asset: str) -> pd.Series:
|
||||
dv = pd.read_parquet(RAW / f"dvol_{asset.lower()}.parquet")
|
||||
return pd.Series(dv["close"].values.astype(float),
|
||||
index=pd.to_datetime(dv["timestamp"], unit="ms", utc=True)) / 100.0
|
||||
|
||||
|
||||
def _load(asset: str):
|
||||
df = resample_1d(load_data(asset, "1h"))
|
||||
s = pd.Series(df["close"].values.astype(float), index=pd.to_datetime(df["datetime"]))
|
||||
if s.index.tz is None:
|
||||
s.index = s.index.tz_localize("UTC")
|
||||
J = pd.concat({"px": s, "dvol": _dvol(asset)}, axis=1, join="inner").sort_index().dropna()
|
||||
return J["px"].values, J["dvol"].values / 1.0, J.index
|
||||
|
||||
|
||||
def _load_hourly(asset: str):
|
||||
df = load_data(asset, "1h")
|
||||
s = pd.Series(df["close"].values.astype(float),
|
||||
index=pd.to_datetime(df["timestamp"], unit="ms", utc=True)).sort_index()
|
||||
return s
|
||||
|
||||
|
||||
def rv_iv_diagnostic(asset: str):
|
||||
"""Il CUORE strutturale: vol realizzata (a vari campionamenti) vs vol implicita (DVOL).
|
||||
Se RV < IV in media -> il long gamma PERDE gross, prima di ogni fee. E' il VRP, di segno opposto."""
|
||||
px, dvf, idx = _load(asset)
|
||||
iv = float(np.mean(dvf))
|
||||
rdaily = np.diff(np.log(px))
|
||||
rv_daily = float(np.std(rdaily) * SQ365)
|
||||
h = _load_hourly(asset)
|
||||
rhour = np.diff(np.log(h.values))
|
||||
rv_hour = float(np.std(rhour) * np.sqrt(365.25 * 24))
|
||||
return dict(asset=asset, iv=iv, rv_daily=rv_daily, rv_hour=rv_hour,
|
||||
spread_daily=iv - rv_daily, spread_hour=iv - rv_hour)
|
||||
|
||||
|
||||
def gamma_scalp_hourly(asset: str, mode: str = "naked",
|
||||
gate_cheap: float = 0.30) -> pd.Series:
|
||||
"""Gamma scalp a rehedge ORARIO (la 'vera' frequenza di scalping): cattura la RV intraday,
|
||||
ma paga 24x la fee di hedge. Tenor 7g = 168 step orari. IV costante nel giorno (DVOL)."""
|
||||
h = _load_hourly(asset)
|
||||
dv = _dvol(asset).reindex(h.index.normalize(), method="ffill")
|
||||
dv.index = h.index
|
||||
J = pd.concat({"px": h, "dvol": dv}, axis=1).dropna()
|
||||
px = J["px"].values; dvf = J["dvol"].values; idx = J.index
|
||||
n = len(px); STEPS = TENOR_D * 24; dt = 1.0 / (365.25 * 24)
|
||||
# IV-rank causale su DVOL giornaliero per il gate
|
||||
daily_iv = _dvol(asset)
|
||||
rets = {}
|
||||
i = 24 * 60
|
||||
while i + STEPS < n:
|
||||
S0 = px[i]; sig = dvf[i]; K = S0
|
||||
if mode == "cheap":
|
||||
day = idx[i].normalize()
|
||||
hist = daily_iv[daily_iv.index < day]
|
||||
if len(hist) >= 60 and float((hist < sig).mean()) > gate_cheap:
|
||||
rets[idx[i + STEPS]] = 0.0; i += STEPS; continue
|
||||
T = TENOR_D / 365.25
|
||||
premium = _bs_straddle(S0, K, T, sig)
|
||||
gamma_pnl = 0.0; hedge_fee = 0.0; prev_delta = None
|
||||
for t in range(1, STEPS + 1):
|
||||
tau = max((STEPS - (t - 1)) * dt, dt)
|
||||
Sp = px[i + t - 1]; Sn = px[i + t]
|
||||
r = Sn / Sp - 1.0
|
||||
d1 = (np.log(Sp / K) + 0.5 * sig ** 2 * tau) / (sig * np.sqrt(tau))
|
||||
dollar_gamma = norm.pdf(d1) * Sp / (sig * np.sqrt(tau))
|
||||
gamma_pnl += dollar_gamma * (r * r - sig * sig * dt)
|
||||
delta = 2.0 * norm.cdf(d1) - 1.0
|
||||
if prev_delta is not None:
|
||||
hedge_fee += HEDGE_FEE_SIDE * abs(delta - prev_delta) * Sp
|
||||
prev_delta = delta
|
||||
pnl = gamma_pnl - OPT_FEE_FRAC * premium - hedge_fee
|
||||
rets[idx[i + STEPS]] = pnl / S0
|
||||
i += STEPS
|
||||
out = pd.Series(rets)
|
||||
out.index = out.index.normalize()
|
||||
return out
|
||||
|
||||
|
||||
def gamma_scalp_asset(asset: str, mode: str = "naked",
|
||||
gate_cheap: float = 0.30, skip_rich: float = 0.90) -> pd.Series:
|
||||
"""Rendimenti settimanali (return-on-notional) di una catena di long-straddle delta-hedged,
|
||||
lumpati sul giorno di scadenza. Causale: IV/strike/gate da dati <= entry; payoff sul path."""
|
||||
px, dvf, idx = _load(asset)
|
||||
n = len(px)
|
||||
rets = {}
|
||||
i = 60
|
||||
while i + TENOR_D < n:
|
||||
S0 = px[i]; sig = dvf[i]; K = S0
|
||||
skip = False
|
||||
if i >= 60 and mode in ("cheap", "rich"):
|
||||
ivr = float((dvf[:i] < dvf[i]).mean()) # IV-rank espandente causale
|
||||
if mode == "cheap" and ivr > gate_cheap: # compra gamma solo se vol a SCONTO
|
||||
skip = True
|
||||
if mode == "rich" and ivr > skip_rich: # evita di pagare vol carissima
|
||||
skip = True
|
||||
if skip:
|
||||
rets[idx[i + TENOR_D]] = 0.0; i += TENOR_D; continue
|
||||
T = TENOR_D / 365.25
|
||||
premium = _bs_straddle(S0, K, T, sig)
|
||||
gamma_pnl = 0.0; hedge_fee = 0.0; prev_delta = None
|
||||
for t in range(1, TENOR_D + 1):
|
||||
tau = (TENOR_D - (t - 1)) / 365.25 # tempo residuo a inizio step
|
||||
Sp = px[i + t - 1]; Sn = px[i + t]
|
||||
r = Sn / Sp - 1.0
|
||||
d1 = (np.log(Sp / K) + 0.5 * sig ** 2 * tau) / (sig * np.sqrt(tau))
|
||||
dollar_gamma = norm.pdf(d1) * Sp / (sig * np.sqrt(tau)) # DG straddle = phi(d1)*S/(sig*sqrt(tau))
|
||||
gamma_pnl += dollar_gamma * (r * r - sig * sig * DT)
|
||||
delta = 2.0 * norm.cdf(d1) - 1.0 # delta straddle ATM
|
||||
if prev_delta is not None:
|
||||
hedge_fee += HEDGE_FEE_SIDE * abs(delta - prev_delta) * Sp
|
||||
prev_delta = delta
|
||||
opt_fee = OPT_FEE_FRAC * premium
|
||||
pnl = gamma_pnl - opt_fee - hedge_fee
|
||||
rets[idx[i + TENOR_D]] = pnl / S0 # return-on-notional
|
||||
i += TENOR_D
|
||||
return pd.Series(rets)
|
||||
|
||||
|
||||
def to_daily_voltgt(weekly_btc: pd.Series, weekly_eth: pd.Series, tgt_vol: float = 0.20) -> pd.Series:
|
||||
"""Book 50/50 BTC+ETH su griglia giornaliera, poi scalato a tgt_vol annuo (apples-to-apples
|
||||
con gli altri sleeve, tutti vol-targeted ~20%)."""
|
||||
wk = pd.concat({"B": weekly_btc, "E": weekly_eth}, axis=1, join="inner").mean(axis=1).sort_index()
|
||||
if wk.empty:
|
||||
return wk
|
||||
days = pd.date_range(wk.index.min().normalize(), wk.index.max().normalize(), freq="1D", tz="UTC")
|
||||
daily = pd.Series(0.0, index=days)
|
||||
daily.loc[wk.index.normalize()] = wk.values
|
||||
sd = daily.std()
|
||||
if sd > 0:
|
||||
daily = daily * (tgt_vol / (sd * SQ365))
|
||||
return daily
|
||||
|
||||
|
||||
def _metrics(daily: pd.Series) -> dict:
|
||||
r = daily.values
|
||||
sh = float(np.mean(r) / np.std(r) * SQ365) if np.std(r) > 0 else 0.0
|
||||
eq = np.cumprod(1.0 + np.clip(r, -0.99, None))
|
||||
pk = np.maximum.accumulate(eq)
|
||||
dd = float(np.max((pk - eq) / pk))
|
||||
yrs = (daily.index[-1] - daily.index[0]).days / 365.25
|
||||
cagr = eq[-1] ** (1 / yrs) - 1 if yrs > 0 and eq[-1] > 0 else -1.0
|
||||
s = pd.Series(eq, index=daily.index)
|
||||
yearly = {}
|
||||
for y, g in s.groupby(s.index.year):
|
||||
if len(g) > 1:
|
||||
v = g.values; p = np.maximum.accumulate(v)
|
||||
yearly[int(y)] = (float(g.iloc[-1] / g.iloc[0] - 1), float(np.max((p - v) / p)))
|
||||
return dict(sharpe=sh, dd=dd, cagr=cagr, yearly=yearly)
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 92)
|
||||
print(" GAMMA SCALPING (long-vol) — scalping BTC/ETH con copertura in opzioni")
|
||||
print(" Modello: long straddle ATM 7g, delta-hedge 1d, P&L = DG*(RV^2 - IV^2). Mirror del VRP01.")
|
||||
print("=" * 92)
|
||||
|
||||
print("\n DIAGNOSTICA STRUTTURALE — vol implicita (DVOL) vs realizzata (il segno dell'edge):")
|
||||
for a in ("BTC", "ETH"):
|
||||
d = rv_iv_diagnostic(a)
|
||||
print(f" {a}: IV {d['iv']*100:5.1f}% | RV_1d {d['rv_daily']*100:5.1f}% "
|
||||
f"(IV-RV {d['spread_daily']*100:+.1f}pp) RV_1h {d['rv_hour']*100:5.1f}% "
|
||||
f"(IV-RV {d['spread_hour']*100:+.1f}pp)")
|
||||
print(" -> IV-RV > 0 = il mercato PAGA per essere short-vol (VRP). Il long gamma e' su questo lato"
|
||||
", al ROVESCIO: paga il premio. RV_1h>RV_1d -> rehedge orario cattura piu' RV (test sotto).")
|
||||
|
||||
variants = {
|
||||
"NAKED (sempre long gamma)": dict(mode="naked"),
|
||||
"CHEAP-GATED (IV-rank<0.30 = vol scont)": dict(mode="cheap", gate_cheap=0.30),
|
||||
"CHEAP-GATED (IV-rank<0.50)": dict(mode="cheap", gate_cheap=0.50),
|
||||
"RICH-SKIP (no entry se IV-rank>0.90)": dict(mode="rich", skip_rich=0.90),
|
||||
}
|
||||
|
||||
series = {}
|
||||
for label, kw in variants.items():
|
||||
wB = gamma_scalp_asset("BTC", **kw)
|
||||
wE = gamma_scalp_asset("ETH", **kw)
|
||||
daily = to_daily_voltgt(wB, wE)
|
||||
series[label] = daily
|
||||
m = _metrics(daily)
|
||||
ntr = int((wB != 0).sum() + (wE != 0).sum())
|
||||
print(f"\n--- {label} ---")
|
||||
print(f" standalone (vol-tgt 20%): Sharpe {m['sharpe']:+.2f} CAGR {m['cagr']*100:+.1f}% "
|
||||
f"maxDD {m['dd']*100:.1f}% trade {ntr}")
|
||||
ys = " ".join(f"{y}:{p*100:+.0f}%" for y, (p, d) in sorted(m['yearly'].items()))
|
||||
print(f" per-anno PnL: {ys}")
|
||||
|
||||
print("\n" + "=" * 92)
|
||||
print(" REHEDGE ORARIO (la 'vera' frequenza di scalping: cattura RV intraday, paga 24x hedge fee)")
|
||||
print("=" * 92)
|
||||
for label, kw in (("NAKED orario", dict(mode="naked")),
|
||||
("CHEAP-GATED orario (IV-rank<0.30)", dict(mode="cheap", gate_cheap=0.30))):
|
||||
wB = gamma_scalp_hourly("BTC", **kw); wE = gamma_scalp_hourly("ETH", **kw)
|
||||
daily = to_daily_voltgt(wB, wE)
|
||||
m = _metrics(daily)
|
||||
ys = " ".join(f"{y}:{p*100:+.0f}%" for y, (p, d) in sorted(m['yearly'].items()))
|
||||
print(f"\n--- {label} ---")
|
||||
print(f" Sharpe {m['sharpe']:+.2f} CAGR {m['cagr']*100:+.1f}% maxDD {m['dd']*100:.1f}%")
|
||||
print(f" per-anno PnL: {ys}")
|
||||
|
||||
print("\n" + "=" * 92)
|
||||
print(" SCORING MARGINALE vs TP01 (lo standard del progetto: un nuovo sleeve si giudica QUI)")
|
||||
print("=" * 92)
|
||||
for label, daily in series.items():
|
||||
if daily.std() == 0:
|
||||
print(f"\n[{label}] flat — skip"); continue
|
||||
m = marginal_vs_tp01(daily)
|
||||
b25 = m.get("blends", {}).get("w25", {})
|
||||
print(f"\n[{label}]")
|
||||
print(f" verdict={m.get('marginal_verdict')} corr->TP01 full={m.get('corr_full')} "
|
||||
f"hold={m.get('corr_hold')} is_hedge={m.get('is_hedge')} "
|
||||
f"has_insample_edge={m.get('has_insample_edge')} (cand IS Sharpe {m.get('cand_insample_sharpe')})")
|
||||
print(f" cand Sharpe full={m.get('cand_full_sharpe')} hold={m.get('cand_hold_sharpe')} | "
|
||||
f"blend25 full {b25.get('full')} (upl {b25.get('uplift_full')}) "
|
||||
f"hold {b25.get('hold')} (upl {b25.get('uplift_hold')}) DD {b25.get('dd')}")
|
||||
print(f" hedge-check: uplift TP01-up {m.get('uplift_tp01_up')} / TP01-down {m.get('uplift_tp01_down')} "
|
||||
f"yearly-corr {m.get('hedge_yearly_corr')}")
|
||||
|
||||
print("\n" + "=" * 92)
|
||||
print(" ESEGUIBILITA' a ~$600 (Deribit options min size)")
|
||||
print("=" * 92)
|
||||
pxB = _load("BTC")[0][-1]; pxE = _load("ETH")[0][-1]
|
||||
for a, p, csz, minc in (("BTC", pxB, 1.0, 0.1), ("ETH", pxE, 1.0, 0.1)):
|
||||
notion = p * csz * minc
|
||||
print(f" {a}: spot ${p:,.0f} | contratto {csz} {a}, min {minc} {a} -> notional minimo "
|
||||
f"${notion:,.0f} ({'OK' if notion < 600 else 'OLTRE i $600 -> NON eseguibile'})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Lock della conclusione 2026-06-26: il gamma scalping (long-vol) su BTC/ETH PERDE
|
||||
strutturalmente — è lo specchio del VRP01, dal lato sbagliato del premio. Guardia contro
|
||||
ri-litigare l'idea "scalping con copertura in opzioni" come edge.
|
||||
Diario docs/diary/2026-06-26-gamma-scalp-options.md."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
from options_gamma_scalp import ( # type: ignore # noqa: E402
|
||||
_bs_straddle, gamma_scalp_asset, rv_iv_diagnostic, to_daily_voltgt, _metrics,
|
||||
)
|
||||
|
||||
|
||||
def test_bs_straddle_positive_and_increasing_in_vol():
|
||||
"""Premio straddle ATM > 0 e cresce con la vol implicita (sanity del pricer)."""
|
||||
p_lo = _bs_straddle(100.0, 100.0, 7 / 365.25, 0.30)
|
||||
p_hi = _bs_straddle(100.0, 100.0, 7 / 365.25, 0.90)
|
||||
assert p_lo > 0
|
||||
assert p_hi > p_lo
|
||||
|
||||
|
||||
def test_long_gamma_loses_when_rv_below_iv_synthetic():
|
||||
"""Sintetico: con prezzo PIATTO (RV=0) e IV>0, il long gamma deve perdere (solo theta)."""
|
||||
# P&L per step = DG*(r^2 - sigma^2 dt); con r=0 ogni step e' -DG*sigma^2*dt < 0.
|
||||
from options_gamma_scalp import OPT_FEE_FRAC # noqa: F401
|
||||
sig = 0.60
|
||||
# se RV=0 il gamma_pnl e' strettamente negativo -> ritorno negativo. Verifico via il segno
|
||||
# del contributo per-step a r=0.
|
||||
r = 0.0
|
||||
contrib = (r * r - sig * sig * (1.0 / 365.25))
|
||||
assert contrib < 0
|
||||
|
||||
|
||||
def test_iv_exceeds_or_near_daily_rv_btc():
|
||||
"""BTC: a campionamento giornaliero l'IV (DVOL) >= RV in media -> il long gamma paga il VRP."""
|
||||
d = rv_iv_diagnostic("BTC")
|
||||
assert d["iv"] > 0 and d["rv_daily"] > 0
|
||||
assert d["spread_daily"] > 0.0 # IV - RV_1d > 0 su BTC (lato short paga)
|
||||
|
||||
|
||||
def test_naked_gamma_scalp_standalone_sharpe_negative():
|
||||
"""La conclusione: il book gamma-scalp nudo (BTC+ETH) ha Sharpe standalone NEGATIVO."""
|
||||
wB = gamma_scalp_asset("BTC", mode="naked")
|
||||
wE = gamma_scalp_asset("ETH", mode="naked")
|
||||
daily = to_daily_voltgt(wB, wE)
|
||||
assert daily.std() > 0
|
||||
assert _metrics(daily)["sharpe"] < 0.0
|
||||
Reference in New Issue
Block a user