Files
PythagorasGoal/scripts/research/alt/runs/OPT08.py
T
Adriano Dal Pastro 5ac4e16af8 research(alt): sweep 104 strategie alternative su Deribit (153 agenti) + marginal scorer
Ondata di ricerca onesta a largo spettro su BTC/ETH+DVOL certificati: 104 ipotesi
distinte (11 famiglie), un agente-finder per ipotesi, verifica avversariale a 3
scettici sui promettenti, sintesi (153 agenti totali). Esito: NIENTE di nuovo regge
-> conferma del soffitto strutturale ~1.3 BTC/ETH-direzionale; lo stack
TP01+XS01+VRP01 resta imbattuto.

- altlib.py: harness condiviso vettoriale leak-free (eval_weights/study_weights,
  fee-sweep, both-asset + hold-out 2025+). Riproduce i numeri canonici di TP01.
- MARGINAL SCORER (study_marginal/marginal_vs_tp01): Sharpe INCREMENTALE vs baseline
  TP01 (corr, blend uplift OOS, alpha residua) + jackknife OOS (clean-year +
  drop-best-month). earns_slot = abs!=FAIL & ADDS & robust_oos. Smaschera gli overlay
  su TSMOM con PASS assoluti fasulli (CMB04, VOL11, ...) e il falso positivo KAMA
  (ADDS ma muore al jackknife).
- runs/*.py (104) script riproducibili per ipotesi; wf_altstrat.js workflow.
- Verdetto: 0 candidati deployabili; 2 LEAD fragili (VOL08, STA05_LS) da forward-monitor.
- test_marginal_scorer.py blocca baseline + invarianti. Suite: 32 verde.

Diario: docs/diary/2026-06-20-alt-strategies-100agent-sweep.md

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

128 lines
5.4 KiB
Python

"""OPT08 — Risk-reversal directional via DVOL-change skew proxy.
HYPOTHESIS: The 25-delta risk reversal sign can be proxied from DVOL changes.
When DVOL rises sharply relative to recent history (puts bid up = skew bullish for
downside fear = bearish tilt) we go short; when DVOL falls (fear subsides / calls
catching up relative = bullish tilt) we go long. We also test the opposite sign to
be honest about direction. We use DVOL z-score over rolling windows as the signal.
CAVEAT: This is a heavy proxy — DVOL is the ATM vol index, not skew. The actual
25d risk reversal is not in the data. Results should be treated as suggestive only.
DVOL history: starts 2021-03, so ~4 years of data. FULL window covers 2021-2026.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
# ── Signal construction ──────────────────────────────────────────────────────
# Proxy: if DVOL z-score is high (fear spike) -> bearish; if low (complacency) -> bullish
# This is the "risk-reversal as directional tilt" interpretation:
# put skew expensive (DVOL spike) = hedgers worried -> fade / go short or stay flat
# put skew cheap (DVOL low) = complacency -> go long
#
# We test 4 configurations:
# A) zscore_win=20d, signal sign = bearish_on_dvol_spike (negative z -> long)
# B) zscore_win=60d, signal sign = bearish_on_dvol_spike
# C) zscore_win=20d, signal sign = bullish_on_dvol_spike (positive z -> long, contrarian)
# D) zscore_win=60d, signal sign = bullish_on_dvol_spike
#
# After picking best config from 1d, we finalize.
def make_target(df, asset: str, zscore_win_days: int, dvol_spike_bearish: bool,
vol_target_enabled: bool = True):
"""
Build a continuous position in [-lev, +lev] based on DVOL z-score.
dvol_spike_bearish=True: high DVOL z -> short (fear = downside risk real)
dvol_spike_bearish=False: high DVOL z -> long (contrarian, mean-reversion of fear)
"""
dv = al.dvol(df, asset) # float array len(df), NaN before 2021-03
bpd = al.bars_per_day(df)
win = max(5, zscore_win_days * bpd)
# z-score of DVOL level over rolling window (causal)
z = al.zscore(dv, win)
# Raw direction: clip z to [-2, 2] and normalize to [-1, 1]
z_clip = np.clip(z, -2.0, 2.0) / 2.0
if dvol_spike_bearish:
# high DVOL (z>0) -> bearish (negative position)
direction = -z_clip
else:
# high DVOL (z>0) -> bullish (contrarian: fear is overdone, buy the dip)
direction = z_clip
# Zero out where DVOL is NaN (pre-history)
direction[~np.isfinite(dv)] = 0.0
direction[~np.isfinite(direction)] = 0.0
if vol_target_enabled:
pos = al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
else:
pos = np.clip(direction, -1.0, 1.0)
return pos
# ── Grid: 4 configs ──────────────────────────────────────────────────────────
configs = [
dict(zscore_win_days=20, dvol_spike_bearish=True, label="z20-bearish"),
dict(zscore_win_days=60, dvol_spike_bearish=True, label="z60-bearish"),
dict(zscore_win_days=20, dvol_spike_bearish=False, label="z20-bullish"),
dict(zscore_win_days=60, dvol_spike_bearish=False, label="z60-bullish"),
]
# ── Run on 1d only (DVOL is daily, so sub-daily adds no signal) ─────────────
print("Running OPT08 — Risk-reversal directional (DVOL z-score proxy)")
print("DVOL history starts 2021-03; effective backtest window 2021-2026")
print()
best_rep = None
best_score = -999.0
for cfg in configs:
lbl = cfg["label"]
win = cfg["zscore_win_days"]
bearish = cfg["dvol_spike_bearish"]
def target_fn(df, _win=win, _bearish=bearish):
# detect asset from the DVOL data shape
# We must detect which asset this df belongs to; use a closure trick:
# try BTC first, if raises try ETH -- but study_weights iterates per asset
# so we need a per-asset function. We handle this in a wrapper below.
return make_target(df, "BTC", _win, _bearish)
# We need per-asset targets, so wrap differently
def make_target_fn(win_, bearish_):
def fn(df):
# Detect asset: try BTC DVOL alignment and check if it matches
# Actually altlib study_weights passes df already for each asset;
# we don't know which asset from df alone. Use a heuristic:
# check price range (BTC >> ETH)
c = df["close"].values
med_price = float(np.nanmedian(c))
asset = "BTC" if med_price > 5000 else "ETH"
return make_target(df, asset, win_, bearish_)
return fn
tf_fn = make_target_fn(win, bearish)
rep = al.study_weights(f"OPT08-{lbl}", tf_fn, tfs=("1d",))
best_cell = rep["cells"][0]
score = best_cell["min_asset_holdout_sharpe"]
print(f"Config {lbl}: minFull={best_cell['min_asset_full_sharpe']:+.2f} "
f"minHold={best_cell['min_asset_holdout_sharpe']:+.2f} "
f"feeOK={best_cell['fee_survives']}")
if score > best_score:
best_score = score
best_rep = rep
best_cfg = cfg
print()
print(f"Best config: {best_cfg['label']}")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))