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:
Adriano Dal Pastro
2026-06-20 19:50:39 +00:00
parent bf84bc91e2
commit 5ac4e16af8
111 changed files with 16924 additions and 0 deletions
+358
View File
@@ -0,0 +1,358 @@
"""OPT06 — Ratio Put Spread (Defensive Short-Vol with Tail Hedge)
IDEA: Ratio put spread (1x2 put ratio) modeled on DVOL:
- Sell 1 OTM put at strike K1 = S * exp(-delta1) (e.g., -0.15 log-moneyness)
- Buy 2 OTM puts at strike K2 = S * exp(-delta2) (e.g., -0.30 log-moneyness)
Net: collect premium from the short put, use proceeds to buy tail protection.
This is a "defensive short-vol" structure:
- Moderate down moves (to K2) → profitable (net premium + short put profit)
- Crash moves (below K2) → protected (long 2 puts offset the short)
- Up moves → lose net premium received (small cost)
The ratio 1:2 means the structure has POSITIVE gamma below K2 (net long put delta
when S < K2) — the tail hedge kicks in. Above K2 but below K1, it's short-gamma
(collects theta). Above K1, it's short a single put (small risk).
GATE: Only enter when DVOL >= gate threshold (elevated IV → richer premium).
Also gated on DVOL/RV ratio (only sell vol when IV > RV).
ROLL: Weekly (7d) or biweekly (14d).
GRID: 4 configs:
(short_moneyness=-0.10, long_moneyness=-0.25, gate_dvol=50)
(short_moneyness=-0.10, long_moneyness=-0.25, gate_dvol=60)
(short_moneyness=-0.15, long_moneyness=-0.30, gate_dvol=50)
(short_moneyness=-0.15, long_moneyness=-0.30, gate_dvol=60)
→ 4 configs × 1d TF = 4 backtests (within <=6 limit)
CAVEAT:
- MODELED on DVOL (ATM). Real puts have skew (OTM puts cost more → less premium).
- History starts 2021-03 (DVOL). Backtest from 2021-03 only.
- Tail risk partially mitigated by the ratio structure, but skew model error matters.
- Not for deployment without real options pricing data.
- Lead-only / modeled.
Style: study_weights (continuous modeled position via P&L series).
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
import pandas as pd
from scipy.stats import norm
# ── Black-Scholes helpers ──────────────────────────────────────────────────
def bs_put(S: float, K: float, T: float, sigma: float) -> float:
"""Black-Scholes put price (r=0, crypto/futures)."""
if T <= 0 or sigma <= 0 or S <= 0 or K <= 0:
return max(0.0, K - S)
d1 = (np.log(S / K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
return float(K * norm.cdf(-d2) - S * norm.cdf(-d1))
def bs_put_delta(S: float, K: float, T: float, sigma: float) -> float:
"""Black-Scholes put delta (negative)."""
if T <= 0 or sigma <= 0 or S <= 0 or K <= 0:
return -1.0 if S < K else 0.0
d1 = (np.log(S / K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T))
return float(norm.cdf(d1) - 1.0)
def ratio_spread_value(S: float, K1: float, K2: float, T: float, sigma: float) -> float:
"""Value of short 1 put(K1) + long 2 puts(K2). Positive = we received cash."""
# Short 1 put at K1 (we receive premium = +put_K1)
# Long 2 puts at K2 (we pay premium = -2*put_K2)
# Net received = put(K1) - 2*put(K2)
p1 = bs_put(S, K1, T, sigma)
p2 = bs_put(S, K2, T, sigma)
return p1 - 2.0 * p2
def ratio_spread_delta(S: float, K1: float, K2: float, T: float, sigma: float) -> float:
"""Net delta of position: short 1 put(K1) + long 2 puts(K2)."""
d1 = bs_put_delta(S, K1, T, sigma)
d2 = bs_put_delta(S, K2, T, sigma)
return -d1 + 2.0 * d2
def ratio_spread_payoff(S_exp: float, K1: float, K2: float) -> float:
"""Payoff at expiry of short 1 put(K1) + long 2 puts(K2) (as fraction of S0)."""
payoff_short = -max(0.0, K1 - S_exp)
payoff_long = 2.0 * max(0.0, K2 - S_exp)
return payoff_short + payoff_long
def simulate_ratio_spread_cycle(
close: np.ndarray,
sigma_iv: np.ndarray,
i0: int,
roll_bars: int,
short_moneyness: float, # log-moneyness of short put (e.g., -0.10 → 10% OTM)
long_moneyness: float, # log-moneyness of long puts (e.g., -0.25 → 25% OTM)
fee_side: float = 0.001 # 0.10% per leg per side (options spread)
) -> tuple[float, int]:
"""
Simulate one ratio put spread cycle.
At entry i0:
- K1 = S0 * exp(short_moneyness) [e.g., S0 * exp(-0.10) ≈ S0 * 0.905]
- K2 = S0 * exp(long_moneyness) [e.g., S0 * exp(-0.25) ≈ S0 * 0.779]
- Sell 1 put at K1, buy 2 puts at K2
- Net premium received = put(K1) - 2*put(K2) [in $]
At expiry i_exp:
- P&L = net_premium_received + payoff_at_expiry - transaction_costs
P&L per unit of notional S0 (fraction of S0):
net_pnl = (p1_entry - 2*p2_entry)/S0
+ payoff(S_exp, K1, K2)/S0
- (3 legs * 2 sides * fee_side) [3 legs: 1 short + 2 long → 3 contracts]
"""
n = len(close)
S0 = close[i0]
T = roll_bars / 365.25
sig = sigma_iv[i0]
if not (np.isfinite(sig) and sig > 0.02):
return 0.0, min(i0 + roll_bars, n - 1)
K1 = S0 * np.exp(short_moneyness) # short put (less OTM)
K2 = S0 * np.exp(long_moneyness) # long puts (more OTM)
# Net premium received at entry
p1 = bs_put(S0, K1, T, sig)
p2 = bs_put(S0, K2, T, sig)
net_prem = p1 - 2.0 * p2 # positive → we received net premium
i_exp = min(i0 + roll_bars, n - 1)
S_exp = close[i_exp]
# Payoff at expiry (from position payoff)
payoff = ratio_spread_payoff(S_exp, K1, K2)
# Transaction costs: 3 contracts (1 short + 2 long), entry + exit = 2 sides each
# fee_side applies per contract per side
tx_cost = 3 * 2 * fee_side * S0 # in $ terms
net_pnl_dollar = net_prem + payoff - tx_cost
net_pnl_frac = net_pnl_dollar / S0
return float(net_pnl_frac), i_exp
def compute_ratio_spread_series(
df: pd.DataFrame,
asset: str,
roll_days: int,
short_moneyness: float,
long_moneyness: float,
gate_dvol: float, # minimum DVOL level to enter (vol points, e.g., 50)
iv_rv_gate: float = 1.05, # minimum IV/RV ratio to enter
rv_win_days: int = 20,
fee_side: float = 0.001
) -> np.ndarray:
"""
Simulate the full ratio put spread strategy.
Returns per-bar P&L as fraction of equity (additive).
Flat when not in a cycle or gate not met.
"""
close = df["close"].values.astype(float)
n = len(close)
sigma_iv = al.dvol(df, asset) / 100.0 # convert vol points → decimal
log_r = al.log_returns(close)
bpy = al.bars_per_year(df)
rv_win = max(5, rv_win_days)
rv_ann = pd.Series(log_r).rolling(rv_win, min_periods=max(2, rv_win // 2)).std().values * np.sqrt(bpy)
# Find first bar with valid DVOL
first_valid = np.where(np.isfinite(sigma_iv) & (sigma_iv > 0.02))[0]
if len(first_valid) == 0:
return np.zeros(n)
start_bar = int(first_valid[0]) + rv_win # also need RV to warm up
r_opt = np.zeros(n)
i = start_bar
while i < n - 1:
sig_iv = sigma_iv[i]
sig_rv = rv_ann[i]
dvol_pts = sig_iv * 100.0 # back to vol points for gate
# Entry conditions:
# 1. Valid DVOL
# 2. DVOL >= gate_dvol (vol is elevated → richer premium)
# 3. IV/RV >= iv_rv_gate (selling vol when IV > RV)
if (np.isfinite(sig_iv) and sig_iv > 0.02 and
np.isfinite(sig_rv) and sig_rv > 0.02 and
dvol_pts >= gate_dvol and
sig_iv / sig_rv >= iv_rv_gate):
net_pnl, i_exp = simulate_ratio_spread_cycle(
close, sigma_iv, i, roll_days,
short_moneyness=short_moneyness,
long_moneyness=long_moneyness,
fee_side=fee_side
)
r_opt[i_exp] = net_pnl
i = i_exp + 1
else:
i += 1
return r_opt
def eval_ratio_spread(df: pd.DataFrame, r_opt: np.ndarray) -> dict:
"""Evaluate ratio put spread P&L series into standard metrics."""
idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True))
n = len(r_opt)
# The transaction costs are already inside simulate_ratio_spread_cycle.
# Just compound the net P&L.
r_net = r_opt.copy()
eq = np.cumprod(1.0 + np.clip(r_net, -0.99, None))
eq = np.concatenate([[1.0], eq])
r_eq = np.diff(eq) / eq[:-1]
r_eq = np.nan_to_num(r_eq)
bpy = al.bars_per_year(df)
rr = r_eq[np.isfinite(r_eq)]
sharpe = float(np.mean(rr) / np.std(rr) * np.sqrt(bpy)) if np.std(rr) > 0 else 0.0
pk = np.maximum.accumulate(eq[1:])
dd = float(np.max((pk - eq[1:]) / pk)) if len(eq) > 1 else 0.0
span_days = (idx[-1] - idx[0]).total_seconds() / 86400 if len(idx) > 1 else 1.0
years = max(span_days / 365.25, 1e-6)
total_ret = eq[-1] / eq[0] - 1
cagr = (eq[-1] / eq[0]) ** (1 / years) - 1
full = dict(sharpe=round(sharpe, 3), cagr=round(cagr, 4),
maxdd=round(dd, 4), ret=round(total_ret, 4), n=int(n))
hmask = idx >= al.HOLDOUT
hold = dict(sharpe=0.0, ret=0.0, n=0)
if hmask.sum() > 3:
r_h = r_eq[hmask]
hs = float(np.mean(r_h) / np.std(r_h) * np.sqrt(bpy)) if np.std(r_h) > 0 else 0.0
eq_h = np.cumprod(1.0 + np.clip(r_h, -0.99, None))
hold = dict(sharpe=round(hs, 3), ret=round(float(eq_h[-1] - 1), 4), n=int(hmask.sum()))
s = pd.Series(r_eq, index=idx)
yearly = {}
for y, g in s.groupby(s.index.year):
eq_y = np.cumprod(1 + g.values)
pk_y = np.maximum.accumulate(eq_y)
yearly[int(y)] = dict(ret=round(float(eq_y[-1] - 1), 4),
dd=round(float(np.max((pk_y - eq_y) / pk_y)), 4))
settle_bars = (r_opt != 0).sum()
turnover_per_year = round(float(settle_bars / (span_days / 365.25)), 1)
return dict(full=full, holdout=hold, yearly=yearly,
time_in_market=round(float(settle_bars / n), 3),
turnover_per_year=turnover_per_year)
def run_ratio_spread(
short_moneyness: float,
long_moneyness: float,
gate_dvol: float,
roll_days: int = 7,
tfs=("1d",)
) -> dict:
"""Run ratio put spread study for one parameter config."""
name = (f"OPT06-RatioPutSpread-short{abs(short_moneyness)*100:.0f}pct"
f"-long{abs(long_moneyness)*100:.0f}pct-dvol{gate_dvol:.0f}")
cells = []
for tf in tfs:
per_asset = {}
fee_ok_all = True
for asset in al.CERTIFIED:
df = al.get(asset, tf)
r_opt = compute_ratio_spread_series(
df, asset,
roll_days=roll_days,
short_moneyness=short_moneyness,
long_moneyness=long_moneyness,
gate_dvol=gate_dvol
)
base = eval_ratio_spread(df, r_opt)
# Fee sweep: scale the option tx cost
# Base fee_side=0.001; sweep by adjusting the per-cycle cost
sweep = {}
for f_side in al.FEE_SWEEP:
r_sweep = compute_ratio_spread_series(
df, asset,
roll_days=roll_days,
short_moneyness=short_moneyness,
long_moneyness=long_moneyness,
gate_dvol=gate_dvol,
fee_side=f_side
)
sw = eval_ratio_spread(df, r_sweep)
# Key: 0.20%RT = 0.0010/side = what we label
sweep[f"{2*f_side*100:.2f}%RT"] = sw["full"]["sharpe"]
fee_ok = sweep.get("0.20%RT", -9) > 0
fee_ok_all = fee_ok_all and fee_ok
per_asset[asset] = dict(full=base["full"], holdout=base["holdout"],
tim=base["time_in_market"],
turnover=base["turnover_per_year"],
fee_sweep=sweep, yearly=base["yearly"])
min_full = min(per_asset[a]["full"]["sharpe"] for a in al.CERTIFIED)
min_hold = min(per_asset[a]["holdout"].get("sharpe", 0.0) for a in al.CERTIFIED)
cells.append(dict(tf=tf, per_asset=per_asset,
min_asset_full_sharpe=round(min_full, 3),
min_asset_holdout_sharpe=round(min_hold, 3),
full_sharpe=round(np.mean([per_asset[a]["full"]["sharpe"]
for a in al.CERTIFIED]), 3),
fee_survives=fee_ok_all))
verdict = al._verdict(cells)
return dict(name=name, kind="weights", cells=cells, verdict=verdict)
if __name__ == "__main__":
print("OPT06 — Ratio Put Spread (Defensive Short-Vol with Tail Hedge)")
print("CAVEAT: MODELED on DVOL ATM. Skew not modeled → OTM puts underpriced in model.")
print("DVOL starts 2021-03 → backtest from 2021-03 only.")
print("Lead-only / modeled. Not for deployment.")
print()
# Grid: 4 configs
# (short_moneyness, long_moneyness, gate_dvol)
CONFIGS = [
(-0.10, -0.25, 50.0), # 10%/25% OTM, gate DVOL>=50
(-0.10, -0.25, 60.0), # 10%/25% OTM, gate DVOL>=60
(-0.15, -0.30, 50.0), # 15%/30% OTM, gate DVOL>=50
(-0.15, -0.30, 60.0), # 15%/30% OTM, gate DVOL>=60
]
best_rep = None
best_score = -999.0
for short_m, long_m, gate_d in CONFIGS:
print(f"--- short={short_m*100:.0f}%, long={long_m*100:.0f}%, gate_dvol={gate_d} ---")
rep = run_ratio_spread(
short_moneyness=short_m,
long_moneyness=long_m,
gate_dvol=gate_d,
roll_days=7,
tfs=("1d",)
)
print(al.fmt(rep))
score = rep["verdict"].get("best_holdout_sharpe", -9)
if score > best_score:
best_score = score
best_rep = rep
print()
print("=" * 60)
print("BEST CONFIG:")
print(al.fmt(best_rep))
print()
print("JSON:", al.as_json(best_rep))