research: analisi strategie FinanceOld + VRP v2 (defined-risk spread + gate IV-rank)
Analisi 4 progetti FinanceOld. Solo il filone opzioni-VRP backtestabile sui dati certificati (funding-arb senza dati storici; Polybot ticks corrotti/3gg/edge=latenza). VRP v2 porta 3 idee di OptionsAgent nel framework, causale + fee-aware: - put credit spread (rischio definito): worst-week -16.6%->-7.4%, DD 33%->21% - gate IV-rank>0.30: ribalta HOLD-OUT da -0.25 a +0.28 (alpha = filtro regime) - COMBO f=1.0: FULL Sh 1.10, HOLD 0.60, DD 12%, positiva/piatta ogni anno - blend TP01 70/30 -> Sh 1.00, DD 7% (corr +0.07) Lead quantificato, non deploy (premio modellato ATM, serve f di stress reale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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`.
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user