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

126 lines
4.7 KiB
Python

"""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))