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

129 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""MRV03 — Z-score reversion trend-gated (discrete signals, 1d).
HYPOTHESIS: Fade |zscore(close,20)| > 2 toward mean ONLY when the long-horizon
trend (SMA200 slope) is flat. Skip entries in strong trends.
Logic:
- z = zscore(close, 20): deviation from 20-bar mean
- slope = (SMA200[i] - SMA200[i-slope_win]) / SMA200[i-slope_win]: recent slope of SMA200
- Gate: |slope| < flat_thresh → trend is flat → allow mean-reversion
- Entry: if z > +2 → SHORT (price too high, expect reversion to mean)
if z < -2 → LONG (price too low, expect reversion to mean)
- Exit: TP at SMA20 (mean reversion target), SL at 3*ATR14, max_bars=10
Grid: 2 param sets (zscore_win x flat_thresh):
A: zscore_win=20, flat_thresh=0.005
B: zscore_win=20, flat_thresh=0.010
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
# ── CONFIG GRID (keep total backtests ≤ 6: 2 params × 1 TF × 2 assets = 4 per config) ──
CONFIGS = [
dict(label="A", zscore_win=20, slope_win=5, flat_thresh=0.005, z_thresh=2.0, max_bars=10),
dict(label="B", zscore_win=20, slope_win=5, flat_thresh=0.010, z_thresh=2.0, max_bars=10),
]
def make_entries_fn(zscore_win: int, slope_win: int, flat_thresh: float,
z_thresh: float, max_bars: int):
"""Return an entries_fn(df) for study_signals."""
sma200_win = 200
def entries_fn(df):
c = df["close"].values.astype(float)
n = len(c)
# Indicators (all causal: value at i uses data <=i)
z = al.zscore(c, zscore_win)
sma20 = al.sma(c, zscore_win) # mean-reversion target = rolling mean
sma200 = al.sma(c, sma200_win)
atr14 = al.atr(df, 14)
# SMA200 slope: fractional change over last slope_win bars
sma200_prev = np.full(n, np.nan)
sma200_prev[slope_win:] = sma200[:-slope_win]
slope = np.where(
(sma200_prev > 0) & np.isfinite(sma200_prev),
(sma200 - sma200_prev) / sma200_prev,
np.nan,
)
entries = [None] * n
for i in range(sma200_win + slope_win, n):
zi = z[i]
si = slope[i]
ci = c[i]
atr_i = atr14[i]
m20_i = sma20[i]
# NaN guard
if not (np.isfinite(zi) and np.isfinite(si) and np.isfinite(ci)
and np.isfinite(atr_i) and np.isfinite(m20_i)):
continue
# Gate: trend must be flat
if abs(si) >= flat_thresh:
continue
# Signal
if zi > z_thresh:
# Price is stretched UP → SHORT toward mean
entries[i] = {
"dir": -1,
"tp": m20_i, # mean reversion target
"sl": ci + 3.0 * atr_i, # stop above
"max_bars": max_bars,
}
elif zi < -z_thresh:
# Price is stretched DOWN → LONG toward mean
entries[i] = {
"dir": +1,
"tp": m20_i, # mean reversion target
"sl": ci - 3.0 * atr_i, # stop below
"max_bars": max_bars,
}
return entries
return entries_fn
def run():
results = []
for cfg in CONFIGS:
print(f"\n--- Config {cfg['label']}: zscore_win={cfg['zscore_win']}, "
f"slope_win={cfg['slope_win']}, flat_thresh={cfg['flat_thresh']}, "
f"z_thresh={cfg['z_thresh']}, max_bars={cfg['max_bars']} ---")
entries_fn = make_entries_fn(
zscore_win=cfg["zscore_win"],
slope_win=cfg["slope_win"],
flat_thresh=cfg["flat_thresh"],
z_thresh=cfg["z_thresh"],
max_bars=cfg["max_bars"],
)
rep = al.study_signals(
f"MRV03-{cfg['label']}",
entries_fn,
tfs=("1d",),
)
print(al.fmt(rep))
print("JSON:", al.as_json(rep))
results.append((cfg, rep))
# Pick best config by min_asset_holdout_sharpe
best_cfg, best_rep = max(
results,
key=lambda x: x[1]["verdict"].get("best_holdout_sharpe", -99),
)
print(f"\n=== BEST CONFIG: {best_cfg['label']} ===")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))
return best_rep
if __name__ == "__main__":
run()