Files
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

378 lines
15 KiB
Python

"""OPT04 — Iron Condor Weekly (DVOL-gated).
IDEA: Sell OTM call+put spreads weekly, collect premium from both sides. Iron condor =
- Sell OTM put (delta ~-0.20), Buy further OTM put (delta ~-0.08) <- put credit spread
- Sell OTM call (delta ~+0.20), Buy further OTM call (delta ~+0.08) <- call credit spread
Premium collected from BOTH sides. Profit if underlying stays within the wings (range-bound week).
Max loss = wing width - net premium (total of both spreads).
MODELED APPROACH:
- DVOL used as ATM vol proxy (symmetric BS, no skew).
- Gate: IV-rank > 0.30 (sell only when IV is rich relative to own history).
- Optional crash-skip: IV-rank > 0.90 -> vol already exploded, skip.
- Capital = put wing width + call wing width (total defined risk).
- Fee: 12.5% of net premium (Deribit options cap, per side; 4 legs total = 2 round-trips).
GRID (4 configs on 1d TF):
A) delta ±0.20/±0.08, ivr_gate=0.30, no crash-skip
B) delta ±0.25/±0.10, ivr_gate=0.30, no crash-skip
C) delta ±0.20/±0.08, ivr_gate=0.30, crash-skip at 0.90
D) delta ±0.25/±0.10, ivr_gate=0.30, crash-skip at 0.90
CAVEAT: premiums MODELED on DVOL ATM (no skew, no real chain). Lead quantification only.
DVOL history starts 2021-03 -> effective backtest from ~2021-Q3.
"""
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, sig: float) -> float:
"""Black-Scholes put price, r=0."""
if T <= 0 or sig <= 0:
return max(K - S, 0.0)
d1 = (np.log(S / K) + 0.5 * sig**2 * T) / (sig * np.sqrt(T))
d2 = d1 - sig * np.sqrt(T)
return K * norm.cdf(-d2) - S * norm.cdf(-d1)
def bs_call(S: float, K: float, T: float, sig: float) -> float:
"""Black-Scholes call price, r=0."""
if T <= 0 or sig <= 0:
return max(S - K, 0.0)
d1 = (np.log(S / K) + 0.5 * sig**2 * T) / (sig * np.sqrt(T))
d2 = d1 - sig * np.sqrt(T)
return S * norm.cdf(d1) - K * norm.cdf(d2)
def strike_put_from_delta(S: float, T: float, sig: float, delta: float) -> float:
"""Strike for a put with given delta (delta < 0, e.g. -0.20).
put_delta = -N(-d1) = delta -> N(-d1) = -delta -> -d1 = N^{-1}(-delta)
d1 = -N^{-1}(-delta)
K = S * exp(0.5*sig^2*T - d1*sig*sqrt(T))."""
d1 = -norm.ppf(-delta)
return S * np.exp(0.5 * sig**2 * T - d1 * sig * np.sqrt(T))
def strike_call_from_delta(S: float, T: float, sig: float, delta: float) -> float:
"""Strike for a call with given delta (delta > 0, e.g. +0.20).
call_delta = N(d1) = delta -> d1 = N^{-1}(delta)
K = S * exp(-d1*sig*sqrt(T) + 0.5*sig^2*T)."""
d1 = norm.ppf(delta)
return S * np.exp(-d1 * sig * np.sqrt(T) + 0.5 * sig**2 * T)
# ─── IV-rank (causal, expanding window) ──────────────────────────────────────
def iv_rank_series(dv_pts: np.ndarray, min_history: int = 60) -> np.ndarray:
"""Causal expanding-window IV rank: fraction of past DVOL values below current.
NaN until min_history valid bars are available."""
n = len(dv_pts)
ivr = np.full(n, np.nan)
valid = np.where(np.isfinite(dv_pts))[0]
if len(valid) < min_history:
return ivr
start = valid[0]
for i in valid:
hist_len = i - start
if hist_len >= min_history:
hist = dv_pts[start:i]
hist = hist[np.isfinite(hist)]
if len(hist) >= min_history:
ivr[i] = float((hist < dv_pts[i]).mean())
return ivr
# ─── Standalone iron condor backtest ─────────────────────────────────────────
def backtest_ic(
df: pd.DataFrame,
asset: str,
short_delta_put: float = -0.20,
long_delta_put: float = -0.08,
short_delta_call: float = 0.20,
long_delta_call: float = 0.08,
ivr_gate: float = 0.30,
crash_skip: float = 1.01, # >1 disables crash-skip
tenor_d: int = 7,
fee_side: float = al.FEE_SIDE,
) -> dict:
"""Honest backtest of weekly iron condor on daily bars.
P&L mechanics:
- Every tenor_d bars (weekly) decide at bar i (close[i] known), settle at i+tenor_d.
- Net premium = put_net + call_net (both modeled with BS on DVOL, no skew).
- Payoff realized on close[i+tenor_d].
- Capital basis = put_wing + call_wing (total defined risk).
- Return_week = (net_premium - payoffs - fee) / capital.
- Booked at settlement bar; 0 elsewhere.
Returns al.eval_weights-compatible dict.
"""
close = df["close"].values.astype(float)
dts = pd.to_datetime(df["datetime"], utc=True)
n = len(close)
T_yr = tenor_d / 365.25
dv_pts = al.dvol(df, asset)
dv = dv_pts / 100.0
ivr = iv_rank_series(dv_pts, min_history=60)
daily_pnl = np.zeros(n)
in_trade = np.zeros(n, dtype=bool)
# Start from first bar where we have at least 60 bars of DVOL history
valid_dvol = np.where(np.isfinite(dv_pts))[0]
if len(valid_dvol) < 60:
return _empty_result(df, dts)
i_start = valid_dvol[60] # first bar with 60 history points
i = i_start
trades = 0
while i + tenor_d < n:
S0 = close[i]
sig = dv[i]
# DVOL must be available
if not np.isfinite(sig) or sig <= 0.0:
i += tenor_d
continue
# IV-rank must be available
if not np.isfinite(ivr[i]):
i += tenor_d
continue
# Gate: sell only when IV rank above threshold
if ivr_gate > 0.0 and ivr[i] < ivr_gate:
i += tenor_d
continue
# Crash-skip: do not sell when vol already exploded
if crash_skip < 1.0 and ivr[i] > crash_skip:
i += tenor_d
continue
# ── PUT credit spread ──────────────────────────────────────────────
Ks_put = strike_put_from_delta(S0, T_yr, sig, short_delta_put) # short (less OTM)
Kl_put = strike_put_from_delta(S0, T_yr, sig, long_delta_put) # long (more OTM)
prem_s_put = bs_put(S0, Ks_put, T_yr, sig)
prem_l_put = bs_put(S0, Kl_put, T_yr, sig)
net_put = prem_s_put - prem_l_put
wing_put = Ks_put - Kl_put # put short strike > long strike -> positive
# ── CALL credit spread ─────────────────────────────────────────────
Ks_call = strike_call_from_delta(S0, T_yr, sig, short_delta_call) # short (less OTM)
Kl_call = strike_call_from_delta(S0, T_yr, sig, long_delta_call) # long (more OTM)
prem_s_call = bs_call(S0, Ks_call, T_yr, sig)
prem_l_call = bs_call(S0, Kl_call, T_yr, sig)
net_call = prem_s_call - prem_l_call
wing_call = Kl_call - Ks_call # call long strike > short strike -> positive
# Sanity: net premiums must be positive (should always be true by construction)
if net_put <= 0.0 or net_call <= 0.0 or wing_put <= 0.0 or wing_call <= 0.0:
i += tenor_d
continue
S1 = close[i + tenor_d]
# ── PUT spread payoff ──────────────────────────────────────────────
payoff_put = max(0.0, Ks_put - S1) - max(0.0, Kl_put - S1)
# ── CALL spread payoff ─────────────────────────────────────────────
payoff_call = max(0.0, S1 - Ks_call) - max(0.0, S1 - Kl_call)
# ── Net P&L ────────────────────────────────────────────────────────
gross_pnl = (net_put - payoff_put) + (net_call - payoff_call)
# Capital basis: total defined risk (both wings)
cap = wing_put + wing_call
# Deribit options fee: 0.03% of notional per leg, cap 12.5% of premium.
# 4 legs total for an iron condor. Conservative: cap 12.5% of gross net premium.
FEE_FRAC = 0.125
fee_cost = FEE_FRAC * (net_put + net_call)
ret_week = (gross_pnl - fee_cost) / cap
# Book at settlement bar
settle = i + tenor_d
daily_pnl[settle] += ret_week
in_trade[i:settle] = True
trades += 1
i += tenor_d
idx = pd.DatetimeIndex(dts)
net = daily_pnl
full = al._metrics_from_net(net, idx)
hmask = idx >= al.HOLDOUT
hold = al._metrics_from_net(net[hmask], idx[hmask]) if hmask.sum() > 3 else dict(sharpe=0.0, n=0)
bpy_d = al.bars_per_day(df) * 365.25
return dict(
full=full, holdout=hold, yearly=al._yearly(net, idx),
time_in_market=round(float(np.mean(in_trade)), 3),
turnover_per_year=round(float(trades * 2) / max(1, len(net) / bpy_d), 1),
net=net, idx=idx,
)
def _empty_result(df, dts):
idx = pd.DatetimeIndex(pd.to_datetime(dts, utc=True))
net = np.zeros(len(df))
return dict(
full=al._metrics_from_net(net, idx), holdout=dict(sharpe=0.0, n=0),
yearly=al._yearly(net, idx), time_in_market=0.0, turnover_per_year=0.0,
net=net, idx=idx,
)
# ─── Config grid ──────────────────────────────────────────────────────────────
CONFIGS = [
# (label, sdp, ldp, ivr_gate, crash_skip)
("w20-08-ivr30", -0.20, -0.08, 0.30, 1.01), # wider wing, gate only
("w25-10-ivr30", -0.25, -0.10, 0.30, 1.01), # narrower wing, gate only
("w20-08-ivr30-cs90", -0.20, -0.08, 0.30, 0.90), # wider + crash-skip
("w25-10-ivr30-cs90", -0.25, -0.10, 0.30, 0.90), # narrower + crash-skip
]
def run_config(label, sdp, ldp, ivr_gate, cs, tf="1d") -> dict:
name = f"OPT04-IC-{label}"
per_asset = {}
fee_ok_all = True
for asset in ("BTC", "ETH"):
df = al.get(asset, tf)
base = backtest_ic(df, asset,
short_delta_put=sdp, long_delta_put=ldp,
short_delta_call=-sdp, long_delta_call=-ldp,
ivr_gate=ivr_gate, crash_skip=cs)
# Fee sweep: re-run with different fee fracs via fee_side proxy
# (fee_side not directly used in our custom backtest; we scale FEE_FRAC)
sweep = {}
for f_side in al.FEE_SWEEP:
# Map taker fee to options fee frac: baseline is 0.125 at f_side=FEE_SIDE=0.0005
# Scale proportionally
scale = f_side / al.FEE_SIDE if al.FEE_SIDE > 0 else 1.0
fee_frac_scaled = 0.125 * scale
# Recompute with scaled fee
net_scaled = _recompute_net_scaled(df, asset, sdp, ldp, ivr_gate, cs, fee_frac_scaled)
net_arr = net_scaled["net"]
idx_arr = net_scaled["idx"]
m = al._metrics_from_net(net_arr, idx_arr)
sweep[f"{2*f_side*100:.2f}%RT"] = m["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 ("BTC", "ETH"))
min_hold = min(per_asset[a]["holdout"].get("sharpe", 0.0) for a in ("BTC", "ETH"))
cells = [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 ("BTC", "ETH")]), 3),
fee_survives=fee_ok_all,
)]
return dict(name=name, kind="weights", cells=cells, verdict=al._verdict(cells))
def _recompute_net_scaled(df, asset, sdp, ldp, ivr_gate, cs, fee_frac):
"""Recompute iron condor returns with a different fee fraction."""
close = df["close"].values.astype(float)
dts = pd.to_datetime(df["datetime"], utc=True)
n = len(close)
T_yr = 7 / 365.25
dv_pts = al.dvol(df, asset)
dv = dv_pts / 100.0
ivr = iv_rank_series(dv_pts, min_history=60)
daily_pnl = np.zeros(n)
valid_dvol = np.where(np.isfinite(dv_pts))[0]
if len(valid_dvol) < 60:
return dict(net=daily_pnl, idx=pd.DatetimeIndex(pd.to_datetime(dts, utc=True)))
i = valid_dvol[60]
while i + 7 < n:
S0 = close[i]; sig = dv[i]
if not np.isfinite(sig) or sig <= 0:
i += 7; continue
if not np.isfinite(ivr[i]):
i += 7; continue
if ivr_gate > 0 and ivr[i] < ivr_gate:
i += 7; continue
if cs < 1.0 and ivr[i] > cs:
i += 7; continue
Ks_put = strike_put_from_delta(S0, T_yr, sig, sdp)
Kl_put = strike_put_from_delta(S0, T_yr, sig, ldp)
net_put = bs_put(S0, Ks_put, T_yr, sig) - bs_put(S0, Kl_put, T_yr, sig)
wing_put = Ks_put - Kl_put
Ks_call = strike_call_from_delta(S0, T_yr, sig, -sdp)
Kl_call = strike_call_from_delta(S0, T_yr, sig, -ldp)
net_call = bs_call(S0, Ks_call, T_yr, sig) - bs_call(S0, Kl_call, T_yr, sig)
wing_call = Kl_call - Ks_call
if net_put <= 0 or net_call <= 0 or wing_put <= 0 or wing_call <= 0:
i += 7; continue
S1 = close[i + 7]
payoff_put = max(0.0, Ks_put - S1) - max(0.0, Kl_put - S1)
payoff_call = max(0.0, S1 - Ks_call) - max(0.0, S1 - Kl_call)
gross = (net_put - payoff_put) + (net_call - payoff_call)
fee = fee_frac * (net_put + net_call)
cap = wing_put + wing_call
daily_pnl[i + 7] += (gross - fee) / cap
i += 7
return dict(net=daily_pnl, idx=pd.DatetimeIndex(pd.to_datetime(dts, utc=True)))
# ─── Main ─────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("OPT04 — Iron Condor Weekly (DVOL-gated)")
print("CAVEAT: premiums MODELED on DVOL ATM (no skew). Lead quantification only.")
print("DVOL history starts 2021-03 -> effective backtest from ~2021-Q3.")
print()
results = []
for label, sdp, ldp, ivr_gate, cs in CONFIGS:
print(f"Running: {label}")
rep = run_config(label, sdp, ldp, ivr_gate, cs, tf="1d")
results.append(rep)
print(al.fmt(rep))
print()
best = max(results, key=lambda r: max(
(c["min_asset_holdout_sharpe"] for c in r["cells"]), default=-9.0))
print("=" * 70)
print("BEST CONFIG:", best["name"])
print(al.fmt(best))
print()
print("JSON:", al.as_json(best))