Files
Adriano Dal Pastro 9612560479 research(xsec): sweep cross-sectional su Hyperliquid (43 script/257 config) + verifica avversariale
Nuova harness condivisa xslib.py (panel HL certificato, score per-asset causale, book
long-k/short-k vol-targeted leak-free) + 43 script in runs/ su 11 famiglie (MOM/REV/VOL/
DIST/LIQ/VAL/STRUCT/UNIV). Scoring = earns_slot (full>0 AND hold-out>0 AND marginal ADDS
al portafoglio live AND corr XS01<0.6, con jackknife drop-one-month).

Find: 42/257 config earns_slot=True, ma TUTTE con corr TP01 -0.2..-0.4 e PnL ~solo 2025.
Verify (verify_survivors.py, 3 scettici deterministici):
 - S1 redundancy: cluster low-vol = UNA scommessa (XV01=XU02=1.00, XV02/XV03 r 0.44-0.67);
   XM09/XL02/XS06b/XR02 distinti (corr media off-diag +0.20).
 - S2 short-beta: cluster low-vol carica 0.44-0.70 su short-market -> NON market-neutral,
   e' un tilt short-alt-beta di regime. XM09(0.08)/XR02(-0.21) NON short-beta.
 - S3 per-anno: cluster low-vol decade (XV01/XU02 2026 -0.09); XL02 morto (2025 -0.14,
   2026 -0.43); XM09 (0.82/0.50/0.74) e XR02 (0.84/0.40/2.68) positivi in tutti e 3 gli anni.

Esito: nessuna sleeve nuova. Cluster low-vol RIGETTATO (regime-bet), XL02 RIGETTATO (overfit).
2 LEAD genuini (XM09 trend-gated x-sec momentum, XR02 reversal vol-gated) -> forward-monitor,
non deployabili (panel 2.5y regime unico + STAT-MODE esecuzione). Portafoglio live invariato.

Incluso anche options_vrp_managed.py (A/B VRP01 hold-to-expiry vs gestione attiva del doc
credit-spread): la gestione attiva DISTRUGGE l'edge (combo FULL managed Sh -1.29 vs HtE +0.96,
il delta-exit taglia i vincenti) -> scartata, VRP01 resta hold-to-expiry.

Diari: 2026-06-20-xsec-strategies-sweep.md, 2026-06-20-vrp-active-management.md.
gitignore: data/paper_portfolio/ (stato runtime paper) + scripts/research/xsec/runs/out/ (output rigenerabile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 21:36:57 +00:00

165 lines
7.9 KiB
Python

"""VRP01 + GESTIONE ATTIVA (test del doc 'strategia-credit-spread-eth', 2026-06-20).
Innesta sul put credit spread di VRP01 le regole di gestione intra-trade del documento:
- profit-take 50% del credito
- stop-loss stretto 1.5x il credito (debito di chiusura)
- VOL-STOP: chiudi se DVOL sale >=10 punti dall'apertura (regola crypto-specifica, NUOVA)
- delta-exit: chiudi se |delta| dello short put >= 0.30 (niente rolling/difesa)
- time-stop 7 DTE
Confronto A/B ONESTO sugli STESSI ingressi gated (VRP>0 + IV-rank>0.30) e dati certificati:
BASE = hold-to-expiry (come VRP01) vs MANAGED = stesso trade con la gestione attiva.
Il MTM giornaliero dello spread usa BS sul path certificato + DVOL reale (causale: decisione al
giorno j con dati <= j). CAVEAT invariato: premio MODELLATO su DVOL ATM (no skew), nessun fill di
stress reale -> LEAD, non deploy. Qui misuriamo solo SE la gestione attiva taglia la coda.
uv run python scripts/research/options_vrp_managed.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
from scipy.stats import norm
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from src.data.downloader import load_data
from src.strategies.trend_portfolio import resample_1d
from src.portfolio.portfolio import to_daily, metrics, HOLDOUT
from src.portfolio.sleeves import _bs_put, _strike_from_delta, VRP_CFG, _HL_DIR
CFG = dict(VRP_CFG) # short_delta -0.28, long_delta -0.10, f 1.0, gate_ivr 0.30, crash_skip 0.90, fee_frac 0.125
def _put_delta_mag(S, K, T, sig):
if T <= 0 or sig <= 0:
return 1.0 if S < K else 0.0
d1 = (np.log(S / K) + 0.5 * sig ** 2 * T) / (sig * np.sqrt(T))
return float(norm.cdf(-d1)) # |delta| dello short put (=N(-d1))
def simulate(asset: str, tenor_d: int, mode: str = "hte"):
"""mode: 'hte' hold-to-expiry | 'full' tutte le regole | 'volstop' solo vol-stop DVOL+10 (+PT50).
Ritorna (serie rendimenti per-trade indicizzata alla data di uscita, dict conteggio exit)."""
manage = mode != "hte"
full = mode == "full"
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")
dv = pd.read_parquet(_HL_DIR / f"dvol_{asset.lower()}.parquet")
d = pd.Series(dv["close"].values.astype(float), index=pd.to_datetime(dv["timestamp"], unit="ms", utc=True))
J = pd.concat({"px": s, "dvol": d}, axis=1, join="inner").sort_index().dropna()
px = J["px"].values
dvf = J["dvol"].values / 100.0
idx = J.index
n = len(px)
tn = tenor_d
f, fee = CFG["f"], CFG["fee_frac"]
rets, exits = {}, {}
i = 60
while i + tn < n:
S0, sig0 = px[i], dvf[i]
# --- gates d'ingresso identici a VRP01 (causali) ---
skip = False
if i >= 31:
rv = np.std(np.diff(np.log(px[i - 30:i + 1]))) * np.sqrt(365.25)
if (sig0 - rv) <= 0: # VRP>0
skip = True
if not skip and i >= 60:
ivr = float((dvf[:i] < dvf[i]).mean()) # IV-rank espandente causale
if ivr < CFG["gate_ivr"] or ivr > CFG["crash_skip"]:
skip = True
if skip:
i += tn
continue
T0 = tn / 365.25
Ks = _strike_from_delta(S0, T0, sig0, CFG["short_delta"])
Kl = _strike_from_delta(S0, T0, sig0, CFG["long_delta"])
net_prem = (_bs_put(S0, Ks, T0, sig0) - _bs_put(S0, Kl, T0, sig0)) * f
if net_prem <= 0:
i += tn
continue
reason, pnl, exit_j = None, None, i + tn
if manage:
for j in range(i + 1, i + tn): # giorni STRETTAMENTE prima della scadenza
Trem = (i + tn - j) / 365.25
Sj, sigj = px[j], dvf[j]
sval = _bs_put(Sj, Ks, Trem, sigj) - _bs_put(Sj, Kl, Trem, sigj) # MTM dello spread
if sval <= 0.5 * net_prem:
reason, pnl, exit_j = "PT50", net_prem - sval, j; break
if (sigj - sig0) >= 0.10: # VOL-STOP (la regola crypto nuova del doc)
reason, pnl, exit_j = "VOLSTOP", net_prem - sval, j; break
if full and sval >= 1.5 * net_prem:
reason, pnl, exit_j = "SL150", net_prem - sval, j; break
if full and _put_delta_mag(Sj, Ks, Trem, sigj) >= 0.30:
reason, pnl, exit_j = "DELTA", net_prem - sval, j; break
if full and (i + tn - j) <= 7:
reason, pnl, exit_j = "TIME7", net_prem - sval, j; break
if reason is None: # scadenza
S1 = px[i + tn]
payoff = max(0.0, Ks - S1) - max(0.0, Kl - S1)
pnl, reason, exit_j = net_prem - payoff, "expiry", i + tn
pnl -= fee * abs(net_prem) # fee d'ingresso (su entrambe le gambe via net_prem)
if reason != "expiry":
pnl -= fee * abs(net_prem) # fee di chiusura anticipata (ricompro lo spread)
rets[idx[exit_j]] = pnl / Ks
exits[reason] = exits.get(reason, 0) + 1
i += tn
return pd.Series(rets).sort_index(), exits
def daily(series):
if series.empty:
return series
days = pd.date_range(series.index.min().normalize(), series.index.max().normalize(), freq="1D", tz="UTC")
out = pd.Series(0.0, index=days)
out.loc[series.index.normalize()] = series.values
return out
def report(label, perTrade):
dl = to_daily(daily(perTrade))
m = metrics(dl)
mh = metrics(dl[dl.index >= HOLDOUT])
wins = float((perTrade > 0).mean()) * 100
worst = float(perTrade.min()) * 100
print(f" {label:<22s} n={len(perTrade):>3d} win={wins:>4.0f}% ret={m['ret']*100:>+6.0f}% "
f"Sh={m['sharpe']:>5.2f} DD={m['maxdd']*100:>4.1f}% HOLD Sh={mh['sharpe']:>+5.2f} "
f"worst-trade={worst:>+5.1f}%")
return dl
def main():
print("=" * 100)
print(" VRP01 hold-to-expiry vs GESTIONE ATTIVA (vol-stop DVOL+10, SL 1.5x, PT50, delta-exit, 7DTE)")
print(" Stessi ingressi gated (VRP>0 + IV-rank>0.30), dati certificati, premio MODELLATO su DVOL (no skew)")
print("=" * 100)
combos = {}
for asset in ("ETH", "BTC"):
print(f"\n--- {asset} ---")
report("VRP01 live (7d HtE)", simulate(asset, 7, "hte")[0]) # riferimento live
# confronto equo a tenor 14 (range del doc), STESSI ingressi
b14, _ = simulate(asset, 14, "hte")
v14, exv = simulate(asset, 14, "volstop") # SOLO vol-stop (la regola nuova)
m14, exm = simulate(asset, 14, "full") # tutte le regole del doc
report("14d hold-to-expiry", b14)
report("14d +vol-stop only", v14); print(f" exit volstop: {exv}")
report("14d FULL managed", m14); print(f" exit full: {exm}")
combos[asset] = dict(base14=daily(b14), vol14=daily(v14), man14=daily(m14))
# combo 50/50 BTC+ETH (come lo sleeve VRP01) — il confronto che conta per il portafoglio
print("\n--- COMBO 50/50 BTC+ETH (sleeve-level) ---")
for tag, key in (("14d hold-to-expiry", "base14"), ("14d +vol-stop only", "vol14"), ("14d FULL managed", "man14")):
J = pd.concat({"B": combos["BTC"][key], "E": combos["ETH"][key]}, axis=1, join="outer").fillna(0.0)
combo = to_daily(0.5 * J["B"] + 0.5 * J["E"])
m, mh = metrics(combo), metrics(combo[combo.index >= HOLDOUT])
print(f" {tag:<22s} Sh={m['sharpe']:>5.2f} DD={m['maxdd']*100:>4.1f}% ret={m['ret']*100:>+6.0f}% "
f"HOLD Sh={mh['sharpe']:>+5.2f}")
print("\n Lettura: la gestione attiva VALE se taglia maxDD e worst-trade SENZA distruggere Sharpe/ritorno.")
print(" Caveat invariato: premio modellato su DVOL ATM (no skew) + nessun fill di stress reale -> LEAD, non deploy.")
if __name__ == "__main__":
main()