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>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user