df8bfbceaa
Ricerca sostituto/miglioria a MR02/ETH (127 strategie + 18 overlay opzioni, verifica avversariale, gate PORT06). Esito: miglioria = no-SL (gia' ~catturata da EXIT-16 live); tail-hedge opzioni NO-GO empirico su prezzi reali. Infrastruttura opzioni REALE (muro ARGO/GEX caduto): - options_fetcher.py: importa lo storico per-strike + market_snapshots da cerbero-bite -> data/options/ (chain bid/ask/IV/greche/OI + pannello regime con net-GEX dealer/gamma-flip/VRP/funding/liquidation). - options_chain.py: loader + skew_curve/premium_levels (aggregati reali) + quote() causale + load_market() (pannello regime). - option_overlay_lab.py: overlay opzioni BS su DVOL (pricing sintetico). - mr02eth_port06_gate.py / eth_collar_gate.py: gate swap-sleeve e collar. - mr02eth_search/options.workflow.js: i 2 workflow. Numeri reali: skew put 10%OTM ~1.1 (liquido), premio ~1.0%/mese; niente strike 10%OTM a 24h (overlay per-trade infattibile); collar standing paga nei crash ma net-negativo a PORT06 (alza OOS DD). Diario + CLAUDE.md aggiornati. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
127 lines
6.1 KiB
Python
127 lines
6.1 KiB
Python
"""Gate del CATASTROPHE-CAP auto-finanziato (collar standing) sullo sleeve ETH no-SL.
|
|
|
|
Tesi: lo sleeve ETH no-SL ha la coda da crash (un long-fade puo' perdere -50/-65% in un
|
|
gap). Un COLLAR standing rollato mensilmente — put lunga ~13% OTM finanziata da call corta
|
|
~10% OTM — cappa quella coda a premio netto ~zero (validato sui premi REALI di cerbero-bite:
|
|
put -13%≈1.0%/m IV55, call +10%≈1.05%/m IV49). Pricing BS calibrato sul reale: skew_put 1.12,
|
|
skew_call 1.0. Caveat: il collar aggiunge delta SHORT-ETH con dead-zone -p/+c -> cappa anche
|
|
l'upside; nei mesi tranquilli (ETH dentro la banda) costa ~zero. Il gate dice se aiuta netto.
|
|
|
|
uv run python scripts/analysis/eth_collar_gate.py
|
|
"""
|
|
from __future__ import annotations
|
|
import sys
|
|
from pathlib import Path
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from scripts.analysis.explore_lab import get_df
|
|
from scripts.analysis.combine_portfolio import IDX, SPLIT, OOS_DATE, metrics, port_returns, _norm
|
|
from scripts.analysis.option_overlay_lab import dvol_for, bs_put, bs_call
|
|
from scripts.analysis.mr02eth_port06_gate import (
|
|
gen_donchian_base, build_trades, build_trades_exit16, daily_equity, port_metrics, CAPS)
|
|
from src.portfolio.sleeves import all_sleeve_equities
|
|
|
|
HY = 24 * 365.0
|
|
|
|
|
|
def collar_daily_returns(df, dvol, p_otm=0.13, c_otm=0.10, skew_put=1.12, skew_call=1.0,
|
|
roll_h=24 * 30) -> pd.Series:
|
|
"""Collar standing rollato ogni roll_h ore. Ritorna la SERIE di rendimenti GIORNALIERI
|
|
(frazione del notional collar): MTM = d(intrinseco) - theta (premio netto amortizzato)."""
|
|
c = df["close"].values; ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
|
|
n = len(c)
|
|
val = np.zeros(n) # valore collar (frac notional) marcato a intrinseco - premio residuo
|
|
T = roll_h / HY
|
|
k = 0
|
|
while k < n - 1:
|
|
S0 = c[k]; sig = dvol[k] if not np.isnan(dvol[k]) else 0.6
|
|
Kp = S0 * (1 - p_otm); Kc = S0 * (1 + c_otm)
|
|
prem = bs_put(S0, Kp, T, sig * skew_put) / S0 - bs_call(S0, Kc, T, sig * skew_call) / S0
|
|
end = min(k + roll_h, n - 1)
|
|
span = end - k
|
|
for j in range(k, end + 1):
|
|
intr = (max(Kp - c[j], 0.0) - max(c[j] - Kc, 0.0)) / S0
|
|
frac_elapsed = (j - k) / span if span else 1.0
|
|
val[j] = intr - prem * (1 - frac_elapsed) # premio pagato up-front, amortizzato a 0 a scadenza
|
|
k = end
|
|
s = pd.Series(val, index=ts)
|
|
daily = s.resample("1D").last().reindex(IDX).ffill().bfill()
|
|
return daily.diff().fillna(0.0) # rendimento giornaliero (frac notional)
|
|
|
|
|
|
def combine(fade_eq: pd.Series, collar_dr: pd.Series, hedge_frac: float) -> pd.Series:
|
|
"""sleeve = fade no-SL + hedge_frac * collar. Combina i rendimenti giornalieri."""
|
|
fr = fade_eq.pct_change().fillna(0.0)
|
|
return _norm((1 + fr + hedge_frac * collar_dr).cumprod())
|
|
|
|
|
|
def crash_audit(df, dvol, p_otm, c_otm, hedge_frac):
|
|
"""P&L del collar nei mesi di crollo ETH peggiori (frac notional)."""
|
|
cr = collar_daily_returns(df, dvol, p_otm=p_otm, c_otm=c_otm)
|
|
# ETH daily ret mensile
|
|
cdf = pd.Series(df["close"].values, index=pd.to_datetime(df["timestamp"], unit="ms", utc=True)).resample("1D").last().reindex(IDX).ffill()
|
|
mret = cdf.resample("30D").last().pct_change()
|
|
collar_m = (1 + cr).resample("30D").apply(lambda x: x.prod()) - 1
|
|
worst = mret.nsmallest(5)
|
|
print(f" {'mese (fine)':>12}{'ETH 30g%':>10}{'collar P&L%':>13}")
|
|
for t, r in worst.items():
|
|
cm = collar_m.reindex([t], method="nearest").iloc[0] * hedge_frac * 100
|
|
print(f" {str(t.date()):>12}{r*100:>9.0f}%{cm:>12.1f}%")
|
|
|
|
|
|
def main():
|
|
print("=" * 96)
|
|
print(f" GATE collar standing (catastrophe-cap) sullo sleeve ETH no-SL | OOS da {OOS_DATE}")
|
|
print("=" * 96)
|
|
df = get_df("ETH", "1h"); dvol = dvol_for(df, "ETH")
|
|
eq = dict(all_sleeve_equities())
|
|
ids = [k for k in eq if k in {"MR01_BTC","MR02_BTC","MR07_BTC","MR01_ETH","MR02_ETH","MR07_ETH",
|
|
"DIP01_BTC","TR01_basket","ROT02_rot","PR_ETHBTC","PR_LTCETH","PR_ADAETH","PR_BTCLTC","PR_ETHSOL","TSM01","SH_BTC","SH_ETH"}]
|
|
base_ents = gen_donchian_base(df, n=20, sl_atr=2.0, trend_max=3.0)
|
|
nosl_ents = gen_donchian_base(df, n=20, sl_atr=2.0, trend_max=3.0, use_sl=False)
|
|
|
|
def pm(ce):
|
|
m = dict(eq); m["MR02_ETH"] = ce; return port_metrics(m, ids)
|
|
|
|
f_l, o_l = pm(daily_equity(build_trades_exit16(base_ents, df, sl_confirm=0.5), df))
|
|
fade_nosl = daily_equity(build_trades(nosl_ents, df), df)
|
|
f0, o0 = pm(fade_nosl)
|
|
print(f"\n {'sleeve ETH':<30s}{'FULL Sh':>8s}{'FULL DD':>8s} |{'OOS Sh':>8s}{'OOS DD':>8s}")
|
|
print(" " + "-" * 78)
|
|
print(f" {'LIVE EXIT-16 (rif)':<30s}{f_l['sharpe']:>8.2f}{f_l['dd']:>8.2f} |{o_l['sharpe']:>8.2f}{o_l['dd']:>8.2f}")
|
|
print(f" {'no-SL nudo':<30s}{f0['sharpe']:>8.2f}{f0['dd']:>8.2f} |{o0['sharpe']:>8.2f}{o0['dd']:>8.2f}")
|
|
|
|
configs = [
|
|
("put13/call10 hf0.45", 0.13, 0.10, 0.45),
|
|
("put13/call10 hf0.30", 0.13, 0.10, 0.30),
|
|
("put15/call12 hf0.45", 0.15, 0.12, 0.45),
|
|
("put20/call15 hf0.45", 0.20, 0.15, 0.45),
|
|
("put13/call08 hf0.45", 0.13, 0.08, 0.45),
|
|
]
|
|
rows = []
|
|
for name, p, cc, hf in configs:
|
|
cr = collar_daily_returns(df, dvol, p_otm=p, c_otm=cc)
|
|
ce = combine(fade_nosl, cr, hf)
|
|
f_c, o_c = pm(ce)
|
|
rows.append((name, p, cc, hf, f_c, o_c))
|
|
print(f" {'no-SL + '+name:<30s}{f_c['sharpe']:>8.2f}{f_c['dd']:>8.2f} |{o_c['sharpe']:>8.2f}{o_c['dd']:>8.2f}")
|
|
|
|
print("\n " + "=" * 90)
|
|
print(f" vs LIVE EXIT-16 (FULL {f_l['sharpe']:.2f}/{f_l['dd']:.2f} OOS {o_l['sharpe']:.2f}/{o_l['dd']:.2f}) e vs no-SL")
|
|
print(" " + "-" * 90)
|
|
for name, p, cc, hf, f_c, o_c in rows:
|
|
print(f" {name:<22s} Δ vsEXIT16 FULL {f_c['sharpe']-f_l['sharpe']:+.2f}/{f_c['dd']-f_l['dd']:+.2f} "
|
|
f"OOS {o_c['sharpe']-o_l['sharpe']:+.2f}/{o_c['dd']-o_l['dd']:+.2f} | "
|
|
f"Δ vsNoSL DD {f_c['dd']-f0['dd']:+.2f}")
|
|
|
|
print("\n --- audit crash: P&L collar (hf-scaled) nei 5 mesi ETH peggiori (put13/call10 hf0.45) ---")
|
|
crash_audit(df, dvol, 0.13, 0.10, 0.45)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|