chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita

Reset del progetto su fondamenta verificate dopo la scoperta che l'intera
libreria "validata OOS" era artefatto di feed contaminato (print fantasma del
feed Cerbero TESTNET + storico Binance/USDT).

- Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e
  CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia
  (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample
  (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE
  50-82% barre flat; XRP/BNB non certificabili).
- Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni
  portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST
  con segnale residuo, da ri-validare in isolamento.
- Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio,
  runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/
  portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/
  (preservati, non cancellati). Diario consolidato in un unico documento.
- Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal +
  src/backtest/engine + load_data; tool dati certificati (rebuild_history,
  certify_feed, audit_feed, multi_source_check).
- Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico
  (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-19 15:16:03 +00:00
parent 8401a280b9
commit 14522262e6
383 changed files with 1971 additions and 779 deletions
+126
View File
@@ -0,0 +1,126 @@
"""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()