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
+168
View File
@@ -0,0 +1,168 @@
"""RSK03 — Inverse-vol Risk Parity (2-asset blend BTC+ETH).
IDEA: Scale each asset's exposure by the inverse of its realized volatility,
normalized so the blended portfolio targets a fixed volatility (20%).
This is risk-parity weighting: assets contribute equally to portfolio risk
rather than receiving equal capital. Compare to fixed 50/50 exposure.
TWO sub-configs tested (small grid, <=4 param sets total over 2 TFs):
Config A: vol_win=30d, leverage_cap=2.0 (standard)
Config B: vol_win=60d, leverage_cap=2.0 (smoother vol estimate)
Approach:
- For each bar, compute realized vol for BTC and ETH
- Assign each an inverse-vol weight, normalize so sum of weights = 1
- Scale combined weight to target_vol=20% using blended portfolio vol
- Both assets always long (long-flat risk parity proxy)
- Result is a single "blended" return series; reported per-asset for consistency,
but the real edge is the BTC/ETH blend with risk-parity weighting
Since study_weights evaluates per-asset independently, we test two approaches:
1. Per-asset vol-targeted weights (each asset gets its own vol-targeting)
2. Cross-asset: for the combined report, we show the blend explicitly
For the per-asset evaluation compatible with altlib, we use vol_target per asset
(which IS inverse-vol risk parity when both assets are long) and let the library
evaluate each independently. The cross-asset blend is computed separately and
printed as the "combined" result.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
import pandas as pd
# ── Config grid ─────────────────────────────────────────────────────────────
# vol_win_days, leverage_cap
CONFIGS = [
(30, 2.0), # A: standard 30d window
(60, 2.0), # B: smoother 60d window
]
def make_target(vol_win_days: int, leverage_cap: float):
"""Returns a target_fn: df -> per-bar position.
Long-only, vol-targeted using inverse realized vol.
This is the per-asset component of inverse-vol RP.
direction=+1 always (long-flat), then scaled by target_vol/realized_vol.
"""
def target_fn(df):
direction = np.ones(len(df)) # always long
return al.vol_target(direction, df,
target_vol=0.20,
vol_win_days=vol_win_days,
leverage_cap=leverage_cap)
return target_fn
def combined_rp_report(vol_win_days: int, leverage_cap: float, tf: str):
"""Compute blended BTC+ETH inverse-vol risk-parity returns.
At each bar, blend BTC and ETH using inverse-vol weights normalized to 1,
then apply an overall vol-target to the combined portfolio.
Returns (sharpe_full, maxdd_full, sharpe_holdout, ret_full, ret_holdout).
"""
df_btc = al.get("BTC", tf)
df_eth = al.get("ETH", tf)
# Align BTC and ETH by timestamp (BTC starts 2018, ETH 2019)
df_btc = df_btc.set_index("datetime")
df_eth = df_eth.set_index("datetime")
common_idx = df_btc.index.intersection(df_eth.index)
df_btc = df_btc.loc[common_idx].reset_index()
df_eth = df_eth.loc[common_idx].reset_index()
c_btc = df_btc["close"].values.astype(float)
c_eth = df_eth["close"].values.astype(float)
bpd = al.bars_per_day(df_btc)
bpy = bpd * 365.25
vol_win = max(2, vol_win_days * bpd)
r_btc = al.simple_returns(c_btc)
r_eth = al.simple_returns(c_eth)
vol_btc = al.realized_vol(r_btc, vol_win, bpy)
vol_eth = al.realized_vol(r_eth, vol_win, bpy)
# Inverse-vol weights (causal: at i, vol computed using data<=i)
# weight_i = (1/vol_i) / (1/vol_btc + 1/vol_eth)
inv_btc = np.where((vol_btc > 0) & np.isfinite(vol_btc), 1.0 / vol_btc, np.nan)
inv_eth = np.where((vol_eth > 0) & np.isfinite(vol_eth), 1.0 / vol_eth, np.nan)
inv_sum = inv_btc + inv_eth
w_btc = np.where(np.isfinite(inv_sum) & (inv_sum > 0), inv_btc / inv_sum, 0.5)
w_eth = np.where(np.isfinite(inv_sum) & (inv_sum > 0), inv_eth / inv_sum, 0.5)
# Blended portfolio return (before vol-targeting)
r_blend = w_btc * r_btc + w_eth * r_eth
# Now vol-target the blended return to 20%
vol_blend = al.realized_vol(r_blend, vol_win, bpy)
scal = np.where((vol_blend > 0) & np.isfinite(vol_blend), 0.20 / vol_blend, 0.0)
pos = np.clip(scal, 0, leverage_cap) # long-flat only
pos = np.nan_to_num(pos, nan=0.0)
# Honest shift: pos[i] decided at close[i], held during bar i+1
pos_held = np.zeros(len(pos))
pos_held[1:] = pos[:-1]
gross = pos_held * r_blend
turn = np.abs(np.diff(pos_held, prepend=0.0))
fee_side = al.FEE_SIDE
net = gross - fee_side * turn
net[0] = 0.0
# Use BTC index for timestamps (both aligned)
idx = pd.DatetimeIndex(pd.to_datetime(df_btc["datetime"], utc=True))
full = al._metrics_from_net(net, idx)
hmask = idx >= al.HOLDOUT
if hmask.sum() > 3:
hold = al._metrics_from_net(net[hmask], idx[hmask])
else:
hold = dict(sharpe=0.0, ret=0.0, n=0)
yearly = al._yearly(net, idx)
return full, hold, yearly
# ── Main ────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
# Run per-asset study (vol-targeted, long-flat per asset)
# This is equivalent to inverse-vol RP: each asset separately scaled by 1/vol
TFS = ("1d", "12h")
best_rep = None
best_holdout = -999
for (vol_win, lev_cap) in CONFIGS:
name = f"RSK03-InvVol-vw{vol_win}d"
fn = make_target(vol_win, lev_cap)
rep = al.study_weights(name, fn, tfs=TFS)
verdict = rep["verdict"]
hold_sh = verdict.get("best_holdout_sharpe", -999) or -999
print(al.fmt(rep))
print()
if hold_sh > best_holdout:
best_holdout = hold_sh
best_rep = rep
# Also print the combined BTC+ETH blend for the best config
best_vw = CONFIGS[0][0] if best_rep is None else (
int(best_rep["name"].split("vw")[1].replace("d", ""))
)
best_lev = CONFIGS[0][1]
print("\n=== COMBINED BTC+ETH Blend (Inverse-Vol Risk Parity) ===")
for tf in TFS:
full, hold, yearly = combined_rp_report(best_vw, best_lev, tf)
yr_str = " ".join(f"{y}:{d['ret']*100:+.0f}%" for y, d in list(yearly.items()))
print(f" TF {tf}: FULL Sh={full['sharpe']:+.2f} DD={full['maxdd']*100:.0f}% "
f"ret={full['ret']*100:+.0f}% | HOLD Sh={hold.get('sharpe',0):+.2f} "
f"ret={hold.get('ret',0)*100:+.0f}% | {yr_str}")
print()
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))