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:
@@ -0,0 +1,125 @@
|
||||
"""MRV05 — Williams %R Mean-Reversion
|
||||
|
||||
HYPOTHESIS: Buy when %R(14) < -90 (oversold) with trend filter (close > SMA200);
|
||||
exit (go flat) when %R > -50 (momentum restored). Long-flat only.
|
||||
|
||||
Williams %R = (Highest High(n) - Close) / (Highest High(n) - Lowest Low(n)) * -100
|
||||
Range: -100 (most oversold) to 0 (most overbought).
|
||||
%R < -80 = oversold zone; %R > -20 = overbought zone.
|
||||
|
||||
The exit condition (%R > -50) is causal: we check %R[i] and decide position for bar i+1.
|
||||
This maps naturally to study_weights (continuous hold logic):
|
||||
- position[i] = 1 if %R[i] < -90 AND close[i] > SMA200[i] (buy signal)
|
||||
- position[i] = 0 if %R[i] > -50 (exit signal)
|
||||
- else hold previous position
|
||||
|
||||
Variants (small grid, 4 configs):
|
||||
V1: %R entry -90, exit -50, SMA200 trend filter, long-flat
|
||||
V2: %R entry -85, exit -50, SMA200 trend filter, long-flat (slightly less oversold entry)
|
||||
V3: %R entry -90, exit -50, SMA50 trend filter, long-flat (shorter trend filter)
|
||||
V4: %R entry -90, exit -40, SMA200 trend filter, long-flat (later exit)
|
||||
|
||||
Best variant selected by min-asset hold-out Sharpe.
|
||||
All positions are vol-targeted (20% annualized, 2x leverage cap).
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
|
||||
import altlib as al
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Williams %R calculation (causal: uses data <= bar i)
|
||||
# ---------------------------------------------------------------------------
|
||||
def williams_r(df: pd.DataFrame, win: int = 14) -> np.ndarray:
|
||||
"""Causal Williams %R. Value at i uses data[i-win+1 .. i].
|
||||
%R = (HH - Close) / (HH - LL) * -100
|
||||
Range: -100 (oversold) to 0 (overbought).
|
||||
"""
|
||||
h = df["high"].values.astype(float)
|
||||
l = df["low"].values.astype(float)
|
||||
c = df["close"].values.astype(float)
|
||||
n = len(c)
|
||||
wr = np.full(n, np.nan)
|
||||
# Vectorized rolling using pandas
|
||||
hh = pd.Series(h).rolling(win, min_periods=win).max().values
|
||||
ll = pd.Series(l).rolling(win, min_periods=win).min().values
|
||||
rng = hh - ll
|
||||
# Avoid division by zero
|
||||
valid = rng > 0
|
||||
wr[valid] = (hh[valid] - c[valid]) / rng[valid] * -100.0
|
||||
return wr
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Strategy factory
|
||||
# ---------------------------------------------------------------------------
|
||||
def make_wrpct_target(wr_entry: float = -90.0, wr_exit: float = -50.0,
|
||||
sma_win: int = 200, wr_win: int = 14):
|
||||
"""Williams %R long-flat mean-reversion with trend filter.
|
||||
|
||||
Entry: %R[i] < wr_entry AND close[i] > SMA(sma_win)[i] -> go long
|
||||
Exit: %R[i] > wr_exit -> go flat
|
||||
Hold: otherwise, maintain current position
|
||||
|
||||
Causal: position decided using data <= close[i], held during bar i+1.
|
||||
Vol-targeted: 20% annualized, 2x leverage cap.
|
||||
"""
|
||||
def target_fn(df):
|
||||
c = df["close"].values.astype(float)
|
||||
wr = williams_r(df, wr_win)
|
||||
sma_trend = al.sma(c, sma_win)
|
||||
|
||||
# Vectorized state machine using ffill
|
||||
# Signal: 1 = enter long, 0 = exit to flat, NaN = hold
|
||||
# Priority: exit takes precedence over entry
|
||||
sig = np.where(
|
||||
wr > wr_exit, # exit condition
|
||||
0.0,
|
||||
np.where(
|
||||
(wr < wr_entry) & (c > sma_trend), # entry condition
|
||||
1.0,
|
||||
np.nan # hold
|
||||
)
|
||||
)
|
||||
|
||||
# Start flat
|
||||
sig[0] = 0.0
|
||||
|
||||
# Forward-fill NaN (hold previous position)
|
||||
pos = pd.Series(sig).ffill().fillna(0.0).values
|
||||
|
||||
# Vol-target
|
||||
return al.vol_target(pos, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
|
||||
|
||||
return target_fn
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Run all variants and pick best
|
||||
# ---------------------------------------------------------------------------
|
||||
if __name__ == "__main__":
|
||||
TFS = ("1d",)
|
||||
|
||||
variants = [
|
||||
("MRV05-V1-WR90-exit50-SMA200", make_wrpct_target(-90.0, -50.0, 200, 14)),
|
||||
("MRV05-V2-WR85-exit50-SMA200", make_wrpct_target(-85.0, -50.0, 200, 14)),
|
||||
("MRV05-V3-WR90-exit50-SMA50", make_wrpct_target(-90.0, -50.0, 50, 14)),
|
||||
("MRV05-V4-WR90-exit40-SMA200", make_wrpct_target(-90.0, -40.0, 200, 14)),
|
||||
]
|
||||
|
||||
results = []
|
||||
for name, fn in variants:
|
||||
print(f"\nRunning {name} ...")
|
||||
rep = al.study_weights(name, fn, tfs=TFS)
|
||||
print(al.fmt(rep))
|
||||
results.append(rep)
|
||||
|
||||
# Pick best by min_asset_holdout_sharpe
|
||||
best = max(results, key=lambda r: r["verdict"].get("best_holdout_sharpe", -99))
|
||||
print("\n" + "=" * 60)
|
||||
print(f"BEST VARIANT: {best['name']}")
|
||||
print(al.fmt(best))
|
||||
print("JSON:", al.as_json(best))
|
||||
Reference in New Issue
Block a user