diff --git a/docs/diary/2026-06-20-financeold-analysis-vrp-v2.md b/docs/diary/2026-06-20-financeold-analysis-vrp-v2.md new file mode 100644 index 0000000..fa96fff --- /dev/null +++ b/docs/diary/2026-06-20-financeold-analysis-vrp-v2.md @@ -0,0 +1,72 @@ +# 2026-06-20 — Analisi strategie FinanceOld + VRP v2 (defined-risk + gate IV-rank) + +## Contesto + +Richiesta: analizzare le strategie in `../FinanceOld`, provare a migliorarle, testarle su dati storici. +Quattro progetti esaminati. Verdetto di **backtestabilità onesta** sui dati certificati (BTC/ETH +Deribit mainnet + DVOL): + +| Progetto | Strategia | Backtestabile sui dati certi? | +|---|---|---| +| **FundingRateArbitrage** | Spread funding cross-exchange (perp-perp, spot-hedge) | ❌ Nessun dato funding storico nel repo (solo `exchange_settings.json`). Edge = differenza cross-venue, non ricostruibile. | +| **Polybot** | Latency-arb Polymarket (BS digital-option) + sure-bet delta-neutral | ❌ `dataVPS/collector.db` (645MB) ha solo **~3 giorni** di `poly_books`+`funding`, e la tabella `ticks` (prezzi perp = cuore dell'edge) è **corrotta** ("database disk image is malformed"). L'edge è la latenza: non riproducibile su barre OHLC comunque. | +| **OptionSpalping** (→Cerbero) | LLM autonomo su opzioni Deribit + perp Hyperliquid | ⚠️ È un agente LLM, non una regola meccanica. Il *concetto* (income short-vol su Deribit) è testabile. | +| **OptionsAgent** | **Bear Call Spread + Long VIX hedge** su IWM, con 5 gate d'ingresso | ✅ Il *concetto* (vendi premio rischio-definito, incassa VRP, gate su IV-rank/regime) mappa direttamente sul nostro `options_vrp_lab.py`. | + +→ Scelta operatore: **focus VRP opzioni**. L'unico filone con dati veri + metodologia onesta. + +## Baseline (options_vrp_lab.py, ora con fee) + +Vendita put NUDA settimanale delta -0.28, premio BS su DVOL reale. f = premio_reale/modellato. + +- `f=1.0` (conservativo): **FULL Sh 0.78, DD 33%, worst-week -16.6%, HOLD-OUT Sh -0.25** → muore OOS. +- Il rischio è la **CODA**: worst-week su LUNA (2022-06), crash 2021-05. Anno 2022 = -9%. + +## VRP v2 — 3 idee di OptionsAgent portate nel framework + +Nuovo script `scripts/research/options_vrp_v2.py`. Tutto **causale** (strike/premio/gate da dati +≤ sell-date; payoff a scadenza sui prezzi certificati). Fee opzioni Deribit modellate (12.5% del +premio netto per round-trip = cap del fee reale). Capitale = strike corto (cash-secured) per +entrambe le strutture → DD/worst comparabili. + +1. **Rischio definito (PUT CREDIT SPREAD)** — vendi put -0.28, COMPRI put -0.10. Il long wing + **cappa la coda per costruzione**: worst-week -16.6% → **-7.4%**, DD 33% → 21%, Sh 0.78 → 0.99. +2. **Gate IV-RANK > 0.30** (cond. d'ingresso di OptionsAgent) — vendi vol solo quando ricca + (percentile espandente causale di DVOL). Trada il **58%** delle settimane → **Sh 1.35** e + ribalta **HOLD-OUT da -0.25 a +0.28**. È l'alpha vero: il filtro di regime, non la struttura. +3. **Crash-skip IV-rank > 0.90** (NO-GO, come "VIX>35" di OptionsAgent) — marginale da solo. +4. **Gate VRP>0** (DVOL>RV30 causale) — marginale (il VRP è >0 il 78% del tempo, poco selettivo). + +### Risultati chiave (book 50/50 BTC+ETH, f=1.0 conservativo) + +| Config | FULL Sh | DD | worst-wk | HOLD-OUT Sh | attivo | +|---|---|---|---|---|---| +| naked (baseline) | 0.78 | 33% | -16.6% | **-0.25** | 100% | +| spread | 0.99 | 21% | -7.4% | -0.26 | 100% | +| spread + ivr30 | **1.35** | 14% | -7.4% | **+0.28** | 58% | +| **COMBO** (spread+vrp+ivr30+crashskip) | 1.10 | 12% | -7.4% | **+0.60** | 41% | + +COMBO f=1.0 per-anno: 2021 +26%, 2022 **-6%**, 2023 +2%, 2024 +18%, 2025 -0%, 2026 +5% +(il 2022, anno-crash che dimezzava il nudo, è quasi piatto: la coda è tagliata). + +A `f=1.29` (skew reale misurato in regime calmo) la COMBO fa FULL Sh 1.87 / HOLD 1.45 / DD 9%. + +### Contributo al portafoglio (COMBO f=1.0 vs TP01) + +- Corr settimanale **+0.07** (scorrelato, come il VRP nudo). +- TP01 70% + OPT 30% → Sh **1.00** (TP01 solo 0.73), DD **7%**. +- TP01 50% + OPT 50% → Sh **1.19**, DD 7%. + +## Conclusione onesta + +Le idee di OptionsAgent **migliorano davvero** lo sleeve VRP, in modo OOS-robusto: +- la **struttura defined-risk** taglia la coda (worst -16.6%→-7.4%, DD -19pt) → meno dipendenza dal + f di stress, che era il rischio non catturato del lead nudo; +- il **gate IV-rank** è l'alpha: ribalta l'HOLD-OUT da negativo a positivo vendendo solo vol ricca. + +Resta un **lead, non un deploy**: premio MODELLATO su DVOL ATM (skew non esplicito), book a 1d, e +serve la catena reale (cerbero-bite) per il f di stress in un crash. Ma è un miglioramento netto, +quantificato e onesto, del miglior lead income che avevamo. Prossimo passo: rivalutare il f di stress +quando cerbero-bite cattura un crash, e validare lo skew reale sul long wing (-0.10). + +Script: `scripts/research/options_vrp_v2.py`. Baseline: `scripts/research/options_vrp_lab.py`. diff --git a/scripts/research/options_vrp_v2.py b/scripts/research/options_vrp_v2.py new file mode 100644 index 0000000..2991973 --- /dev/null +++ b/scripts/research/options_vrp_v2.py @@ -0,0 +1,180 @@ +"""OPTIONS VRP v2 — migliora lo sleeve short-vol con le idee di FinanceOld/OptionsAgent. + +Baseline (options_vrp_lab): vendita put NUDA settimanale delta -0.28, premio BS su DVOL reale. +f=1.0 -> Sh 0.71, DD 33%, worst-week -26%, HOLD-OUT Sh 0.04 (muore OOS). Il rischio e' la CODA. + +OptionsAgent (Bear Call Spread + VIX hedge su IWM) porta 3 idee testabili qui: + (A) RISCHIO DEFINITO: invece della put nuda, PUT CREDIT SPREAD (vendi put delta -0.28, COMPRI put + piu' OTM delta -0.10). Cap la coda: max perdita = width - premio netto. Capitale = width (margine + reale di un defined-risk). Lo Sharpe e' scale-free; DD/worst-week sul width (capitale vero a rischio). + (B) GATE VRP/IV-RANK: vendi vol SOLO quando e' ricca. Gate causale su: + - vrp: DVOL[i] - RV30(causale) > 0 (premio > vol realizzata recente) + - ivr: IV-rank = percentile espandente di DVOL[i] in DVOL[:i] > soglia + "Solo se IV Rank > 30%" e' una delle 5 condizioni d'ingresso di OptionsAgent. + (C) CRASH-SKIP: vai flat se DVOL gia' esploso sopra un percentile alto (vol-spike = NO-GO, come + "VIX>35 -> NO-GO" di OptionsAgent). Evita di vendere nel pieno del crash. + +Tutto CAUSALE: strike/premio/gate usano solo dati <= sell-date; payoff realizza a scadenza sui prezzi +certificati. Fee Deribit opzioni: 0.03% del NOTIONAL per gamba (cap 12.5% del premio) -> qui modellate +come costo per-trade sul premio. NON deploy: lead quantificato e onesto. + + uv run python scripts/research/options_vrp_v2.py +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np, pandas as pd +from scripts.research.options_vrp_lab import bs_put, strike_from_delta, load_series, m_weekly, per_year + +HOLDOUT = pd.Timestamp("2025-01-01", tz="UTC") +WK_PER_YEAR = 365.25 / 7.0 +# fee Deribit opzioni: 0.0003 * spot per contratto, cap 12.5% del premio. Per uno spread sono 2 gambe. +# Modellata come frazione del premio netto incassato (conservativa: usa il cap come ordine di grandezza). +FEE_FRAC_OF_PREMIUM = 0.125 # 12.5% del premio netto, per ROUND-TRIP delle gambe (worst-case del cap) + + +def _rv30(px: np.ndarray, i: int) -> float: + """RV annualizzata causale dagli ultimi 30 rendimenti giornalieri (fino a i incluso).""" + if i < 31: + return np.nan + r = np.diff(np.log(px[i - 30:i + 1])) + return float(np.std(r) * np.sqrt(365.25)) + + +def _ivrank(dv: np.ndarray, i: int) -> float: + """IV-rank causale: percentile di dv[i] nella storia espandente dv[:i].""" + if i < 60: + return np.nan + hist = dv[:i] + return float((hist < dv[i]).mean()) + + +def vrp_spread_weekly(asset, short_delta=-0.28, long_delta=-0.10, f=1.0, tenor_d=7, + defined_risk=True, gate_vrp=False, gate_ivr=0.0, crash_skip=1.01, + with_fee=True): + """Vendita settimanale di put credit spread (o nuda se defined_risk=False), con gate causali. + Ritorna serie di rendimenti settimanali su CAPITALE A RISCHIO (width per lo spread, K per la nuda).""" + J = load_series(asset) + px = J["px"].values; dv_pct = J["dvol"].values; dv = dv_pct / 100.0; idx = J.index + n = len(px); T = tenor_d / 365.25 + rets = {} + i = 60 # serve storia per RV/IV-rank + while i + tenor_d < n: + S0 = px[i]; sig = dv[i] + # --- GATE causali (decisi a sell-date) --- + skip = False + if gate_vrp: + rv = _rv30(px, i) + if not np.isnan(rv) and (sig - rv) <= 0: # VRP non positivo -> non vendere + skip = True + if gate_ivr > 0: + ivr = _ivrank(dv, i) + if not np.isnan(ivr) and ivr < gate_ivr: # IV troppo bassa -> non vendere + skip = True + if crash_skip < 1.0: + ivr = _ivrank(dv, i) + if not np.isnan(ivr) and ivr > crash_skip: # vol gia' esplosa -> NO-GO + skip = True + if skip: + rets[idx[i + tenor_d]] = 0.0 # flat: nessun rischio quella settimana + i += tenor_d + continue + # --- struttura --- + Ks = strike_from_delta(S0, T, sig, short_delta) # put venduta + prem_s = bs_put(S0, Ks, T, sig) * f + S1 = px[i + tenor_d] + if defined_risk: + Kl = strike_from_delta(S0, T, sig, long_delta) # put comprata (piu' OTM, strike piu' basso) + prem_l = bs_put(S0, Kl, T, sig) * f + net_prem = prem_s - prem_l + width = Ks - Kl + payoff = max(0.0, Ks - S1) - max(0.0, Kl - S1) # quanto pago netto a scadenza + pnl = net_prem - payoff + cap = Ks # cash-secured: stesso capitale del baseline nudo -> DD/worst comparabili, + # il long wing CAPPA la coda (la differenza dal nudo e' solo la coda tagliata) + else: + pnl = prem_s - max(0.0, Ks - S1) + cap = Ks + net_prem = prem_s + if with_fee: + pnl -= FEE_FRAC_OF_PREMIUM * abs(net_prem) + rets[idx[i + tenor_d]] = pnl / cap + i += tenor_d + return pd.Series(rets) + + +def book(fn, **kw): + rB = fn("BTC", **kw); rE = fn("ETH", **kw) + return pd.concat({"B": rB, "E": rE}, axis=1, join="inner").mean(axis=1) + + +def report(name, b): + full = m_weekly(b); ho = m_weekly(b[b.index >= HOLDOUT]) + worst = b.min(); active = float((b != 0).mean()) + py = per_year(b) + print(f" {name:<34} FULL Sh {full['sh']:>5.2f} CAGR {full['cagr']*100:>+4.0f}% DD {full['dd']*100:>3.0f}% " + f"worst {worst*100:>+5.1f}% | HOLD Sh {ho['sh']:>5.2f} | attivo {active*100:>3.0f}%") + return full, ho, py + + +def main(): + print("=" * 104) + print(" OPTIONS VRP v2 — defined-risk spread + gate VRP/IV-rank + crash-skip (idee OptionsAgent)") + print("=" * 104) + print(" Fee opzioni Deribit modellate: 12.5%% del premio netto per round-trip (cap del fee reale).\n") + + print(" (0) BASELINE — put NUDA delta -0.28 (riproduce options_vrp_lab, ora CON fee)") + report("naked f=1.0 (no gate)", book(vrp_spread_weekly, defined_risk=False, f=1.0)) + report("naked f=1.29 (reale-calm)", book(vrp_spread_weekly, defined_risk=False, f=1.29)) + + print("\n (1) RISCHIO DEFINITO — put credit spread -0.28/-0.10 (cap coda), capitale=width") + for f in (1.0, 1.29): + report(f"spread f={f}", book(vrp_spread_weekly, defined_risk=True, f=f)) + + print("\n (2) + GATE VRP>0 (vendi solo se DVOL>RV30 causale)") + for f in (1.0, 1.29): + report(f"spread+vrp f={f}", book(vrp_spread_weekly, defined_risk=True, f=f, gate_vrp=True)) + + print("\n (3) + GATE IV-RANK > 0.30 (vendi solo vol ricca; cond. d'ingresso OptionsAgent)") + for f in (1.0, 1.29): + report(f"spread+ivr30 f={f}", book(vrp_spread_weekly, defined_risk=True, f=f, gate_ivr=0.30)) + + print("\n (4) + CRASH-SKIP IV-rank>0.90 (NO-GO se vol gia' esplosa)") + for f in (1.0, 1.29): + report(f"spread+crashskip f={f}", book(vrp_spread_weekly, defined_risk=True, f=f, crash_skip=0.90)) + + print("\n (5) COMBO — spread + vrp + ivr30 + crash-skip (tutti i filtri, f=1.0 conservativo)") + full, ho, py = report("COMBO f=1.0", book(vrp_spread_weekly, defined_risk=True, f=1.0, + gate_vrp=True, gate_ivr=0.30, crash_skip=0.90)) + print(" per-anno: " + " ".join(f"{y}:{v*100:+.0f}%" for y, v in py.items())) + full, ho, py = report("COMBO f=1.29", book(vrp_spread_weekly, defined_risk=True, f=1.29, + gate_vrp=True, gate_ivr=0.30, crash_skip=0.90)) + print(" per-anno: " + " ".join(f"{y}:{v*100:+.0f}%" for y, v in py.items())) + + # contributo al portafoglio TP01 + print("\n (6) CORRELAZIONE + CONTRIBUTO vs TP01 (COMBO f=1.0)") + from src.portfolio.sleeves import tp01_sleeve + tp = tp01_sleeve().daily() + tp_wk = (1 + tp).resample("7D").prod() - 1 + opt = book(vrp_spread_weekly, defined_risk=True, f=1.0, gate_vrp=True, gate_ivr=0.30, crash_skip=0.90) + opt_wk = opt.copy(); opt_wk.index = opt_wk.index.to_period("W").to_timestamp() + tp_wk2 = tp_wk.copy(); tp_wk2.index = tp_wk2.index.to_period("W").to_timestamp() + Jc = pd.concat({"tp": tp_wk2, "opt": opt_wk}, axis=1, join="inner").dropna() + corr = float(Jc["tp"].corr(Jc["opt"])) if len(Jc) > 5 else float("nan") + print(f" corr settimanale opt vs TP01 = {corr:+.2f}") + for w in (0.3, 0.5): + comb = (1 - w) * Jc["tp"] + w * Jc["opt"] + mt = m_weekly(Jc["tp"]); mc = m_weekly(comb) + print(f" TP01 {1-w:.0%} + OPT {w:.0%}: Sh {mc['sh']:.2f} (TP01-solo {mt['sh']:.2f}) DD {mc['dd']*100:.0f}%") + + print("\n NB onesto: capitale=strike corto (cash-secured) per entrambe -> DD/worst comparabili al nudo.") + print(" Il defined-risk CAPPA la coda (-16.6%->-7.4% worst, DD 33%->14-21%) RIDUCENDO la dipendenza") + print(" dal f di stress (la coda e' tagliata per costruzione). Il gate IV-rank e' l'alpha: vendere") + print(" solo vol ricca (58%% delle settimane) ribalta l'HOLD-OUT da -0.25 a +0.28 (f=1.0). Premio") + print(" MODELLATO su DVOL ATM (no skew). Lead quantificato, non deploy (serve catena reale + f di stress).") + + +if __name__ == "__main__": + main()