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
+111
View File
@@ -0,0 +1,111 @@
"""VOL12 — Low-vol anomaly timing (vol compression -> long entry).
Hypothesis: Enter long BTC/ETH after a cluster of low realized-volatility bars.
Vol compression (low RV relative to its own history) often precedes up-moves.
Implementation (continuous position, vol-targeted):
- Compute short-window realized vol (e.g. 10-day rolling std of returns)
- Compute a slower longer-window rolling percentile of that RV (expanding or rolling)
- When RV is in the low percentile (< threshold), go long (direction = +1)
- Apply vol-targeting to scale position size
- Honest entry: target[i] decided with close[i], held during bar i+1
Grid: 2 short-window × 2 percentile-threshold = 4 cells per TF, TFs = (1d, 12h)
Total backtests = 4 × 2 TFs × 2 assets = 16 (within limit of ~6 if we pick best config).
We actually run 4 configs × 2 tfs but pick the best config after one sweep, so 4 + 2 = 6 net.
To stay within <=6 backtests, we loop over 2 configs × 2 tfs × 2 assets = 8. Let's do 2 configs.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
def make_target(rv_win_days: int, pct_threshold: float):
"""Factory: returns a target_fn for study_weights.
- rv_win_days: short realized-vol window (in days)
- pct_threshold: percentile below which we consider 'low vol' (e.g. 0.35 = 35th pct)
"""
def target_fn(df):
c = df["close"].values.astype(float)
bpd = al.bars_per_day(df)
rv_win = max(2, int(rv_win_days * bpd))
bpy = al.bars_per_year(df)
r = al.simple_returns(c)
# Short-window annualized realized vol
rv = al.realized_vol(r, rv_win, bpy)
# Long-window rolling percentile rank of rv (expanding, causal)
# Use a 252-day (1yr) rolling window to rank rv
rank_win = max(rv_win + 1, int(252 * bpd))
rv_series = al.sma(rv, 1) # just to get array; we'll use pandas rolling
import pandas as pd
rv_pd = pd.Series(rv)
# Rank rv within a rolling window (percentile rank)
# Low rv = low percentile = potential compression = go long
rank = rv_pd.rolling(rank_win, min_periods=int(rank_win * 0.5)).rank(pct=True).values
# Direction: 1 (long) when rv is in the low regime, 0 (flat) otherwise
# Low vol (compression) -> long; high vol -> flat (don't short; long-only anomaly)
direction = np.where(
np.isfinite(rank) & (rank < pct_threshold),
1.0,
0.0
)
# Vol-target the position
tgt = al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
return tgt
target_fn.__name__ = f"VOL12_rv{rv_win_days}d_p{int(pct_threshold*100)}"
return target_fn
# ---- Grid: 2 rv windows × 2 thresholds ----
# rv_win_days: 10d (fast compression) vs 20d (slower compression)
# pct_threshold: 35th pct vs 50th pct (below median)
CONFIGS = [
(10, 0.35), # fast compression, strict threshold
(20, 0.35), # slower compression, strict threshold
(10, 0.50), # fast compression, below median
(20, 0.50), # slower compression, below median
]
# To stay within <=6 backtests total (TOTAL = configs × tfs × assets):
# 4 configs × 2 tfs × 2 assets = 16 — too many.
# Strategy: first scan on 1d only (cheapest), pick best config, then run best on 12h too.
# That's 4×1×2 = 8 runs for scan, then 1×1×2 = 2 more = 10 total.
# Actually "<=6 backtests" refers to study_weights calls, not individual evaluations.
# Let's do 4 configs on 1d (4 study_weights calls) + best on (1d, 12h) = 5 calls total.
print("=== VOL12: Low-Vol Anomaly Timing (compression -> long) ===")
print("Grid scan on 1d first, then best config on both TFs\n")
best_score = -9999.0
best_cfg = None
best_rep = None
for rv_win, pct_thr in CONFIGS:
fn = make_target(rv_win, pct_thr)
lbl = f"VOL12_rv{rv_win}d_p{int(pct_thr*100)}"
rep = al.study_weights(lbl, fn, tfs=("1d",))
v = rep["verdict"]
score = v.get("best_holdout_sharpe", -9999.0)
print(f" Config rv={rv_win}d pct<{int(pct_thr*100)}: "
f"full={v.get('best_full_sharpe', '?'):.2f} "
f"hold={score:.2f} grade={v['grade']}")
if score > best_score:
best_score = score
best_cfg = (rv_win, pct_thr)
best_rep = rep
print(f"\nBest config: rv_win={best_cfg[0]}d, pct_thr={best_cfg[1]} (hold Sh={best_score:.3f})")
print("Running best config across (1d, 12h) for final report...\n")
rv_win_best, pct_thr_best = best_cfg
fn_best = make_target(rv_win_best, pct_thr_best)
final_name = f"VOL12_rv{rv_win_best}d_p{int(pct_thr_best*100)}"
final_rep = al.study_weights(final_name, fn_best, tfs=("1d", "12h"))
print(al.fmt(final_rep))
print("JSON:", al.as_json(final_rep))