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
+139
View File
@@ -0,0 +1,139 @@
"""RSK07 — Drawdown-scaled exposure
HYPOTHESIS: Exposure proportional to (1 - recent rolling drawdown) on a long-only base.
De-risk into weakness: when the asset is in a large drawdown, reduce position size.
Style: al.study_weights (continuous position / vol-targeted)
Idea:
- Compute the rolling drawdown over a lookback window.
- Target exposure = (1 - drawdown_fraction) where drawdown_fraction in [0, 1].
- Apply vol-targeting on top to keep risk constant.
- Long-only base (no shorting).
The rolling drawdown at bar i = (rolling_max(close, dd_win) - close[i]) / rolling_max(close, dd_win)
This is causal: uses close[i] and prior highs.
Exposure(i) = max(0, 1 - drawdown(i))
With vol-targeting, this scales by (target_vol / realized_vol).
Small grid (<=4 configs, total backtests = 4 * 2 assets <= 8):
A: dd_win=20, vol_target=0.20
B: dd_win=60, vol_target=0.20
C: dd_win=120, vol_target=0.20
D: dd_win=60, vol_target=0.15
TFs tested: 1d, 12h (2 TFs * 4 configs * 2 assets = 16 total — but study_weights
runs per config, so we do 4 configs across 2 TFs = 8 backtest calls)
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
# ---------------------------------------------------------------------------
# Core target function
# ---------------------------------------------------------------------------
def make_target(df, dd_win: int = 60, target_vol: float = 0.20) -> np.ndarray:
"""
Long-only drawdown-scaled exposure with vol-targeting.
Steps:
1. Compute rolling max of close over dd_win bars (causal: max(close[i-dd_win:i+1]))
2. Drawdown fraction = (rolling_max - close) / rolling_max
3. Raw exposure = max(0, 1 - drawdown_fraction) in [0, 1]
4. Apply vol-target scaling: multiply by (target_vol / realized_vol), cap at 2x
5. Result: long-only position in [0, 2], decided with data <= close[i]
"""
c = df["close"].values.astype(float)
n = len(c)
# Causal rolling maximum: max of close over [i-dd_win+1 .. i]
# Use pandas rolling with min_periods=1
c_series = df["close"].astype(float)
roll_max = c_series.rolling(dd_win, min_periods=1).max().values
# Drawdown fraction (0 = at high-water mark, 1 = fully drawn down)
dd_frac = np.where(roll_max > 0, (roll_max - c) / roll_max, 0.0)
dd_frac = np.clip(dd_frac, 0.0, 1.0)
# Raw direction/size: (1 - drawdown), always long [0, 1]
raw_exposure = 1.0 - dd_frac # 1.0 at HWM, 0.0 at full drawdown
# Vol-targeting: scale so expected volatility = target_vol
# Use al.vol_target with direction=raw_exposure (already in [0,1])
# But al.vol_target expects direction in {-1, 0, 1}; we'll do manual vol-scaling
# Realized vol: rolling std of log returns
log_ret = np.diff(np.log(c), prepend=np.nan)
vol_win = int(30 * al.bars_per_day(df))
vol_win = max(vol_win, 5)
r_series = pd.Series(log_ret) if False else __import__('pandas').Series(log_ret)
# Realized vol: annualized
log_ret_arr = al.log_returns(c)
bpy = al.bars_per_year(df)
rv = al.realized_vol(log_ret_arr, vol_win, bpy)
# Vol-target scaling
lev = np.where((rv > 0) & np.isfinite(rv), target_vol / rv, 1.0)
lev = np.clip(lev, 0.0, 2.0)
# Final target: drawdown-scaled exposure * vol lever
target = raw_exposure * lev
# Cap at 2.0 (leverage cap)
target = np.clip(target, 0.0, 2.0)
# First few bars: NaN until we have enough data
warmup = max(dd_win, vol_win)
target[:warmup] = np.nan
return target
# ---------------------------------------------------------------------------
# Grid search
# ---------------------------------------------------------------------------
import pandas as pd # noqa: E402 (needed above via __import__, explicit now)
GRID = [
{"dd_win": 20, "target_vol": 0.20, "label": "dd=20 vol=20%"},
{"dd_win": 60, "target_vol": 0.20, "label": "dd=60 vol=20%"},
{"dd_win": 120, "target_vol": 0.20, "label": "dd=120 vol=20%"},
{"dd_win": 60, "target_vol": 0.15, "label": "dd=60 vol=15%"},
]
best_rep = None
best_score = -999.0
best_label = ""
for params in GRID:
dd_win = params["dd_win"]
target_vol = params["target_vol"]
label = f"RSK07 {params['label']}"
print(f"\n--- Testing {label} ---")
rep = al.study_weights(
label,
lambda df, dw=dd_win, tv=target_vol: make_target(df, dd_win=dw, target_vol=tv),
tfs=("1d", "12h"),
)
print(al.fmt(rep))
# Score by min-asset hold-out Sharpe
best_cell = max(rep["cells"], key=lambda c: c.get("min_asset_holdout_sharpe", -9))
score = best_cell.get("min_asset_holdout_sharpe", -9.0)
if score > best_score:
best_score = score
best_rep = rep
best_label = label
# ---------------------------------------------------------------------------
# Final report
# ---------------------------------------------------------------------------
print("\n" + "=" * 60)
print(f"BEST CONFIG: {best_label}")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))