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
+130
View File
@@ -0,0 +1,130 @@
"""STA08 — AR(1) residual reversion.
IDEA: Fit an expanding-window AR(1) on log returns. The AR(1) residual is
r[t] - (a0 + a1 * r[t-1]), where a0 and a1 are estimated causally from all
data up to t-1. Trade the mean-reversion of the residual: if residual is
positive (return exceeded AR(1) prediction) we expect reversion → short;
if negative → long.
Signal: z-score the residual over a rolling window, take the negative of it
as the continuous position (mean-reversion), then vol-target it.
Grid: 2 lookback windows for z-scoring (60, 120 bars), tested on 1d and 12h.
Total cells: 2 TFs × 2 params × 2 assets = 8 backtests — within limit.
We pick the best config by min-asset hold-out Sharpe.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
def ar1_residual_target(df, zscore_win: int = 60) -> np.ndarray:
"""
Causal AR(1) residual reversion target.
At each bar i:
- Use all returns r[0..i-1] to fit AR(1): regress r[t] on r[t-1]
(expanding OLS — efficient via running sums)
- Compute residual[i] = r[i] - (a0 + a1 * r[i-1]) (uses closed bar i)
- Z-score the residual over last zscore_win bars
- Position = -z (mean-reversion) → vol-targeted
Minimum warmup: 30 bars for stable OLS + zscore_win bars for z-score.
"""
c = df["close"].values.astype(float)
n = len(c)
r = al.log_returns(c) # r[0]=0, r[i] = log(c[i]/c[i-1])
# Expanding AR(1): for each bar i, estimate (a0, a1) from data up to i-1.
# We need: sum(r), sum(r^2), sum(r_t * r_{t-1}), sum(r_{t-1}), sum(r_{t-1}^2)
# for t in [1..i-1].
# Then OLS: regress r_t ~ a0 + a1*r_{t-1}.
# Normal equations:
# [n-1, sum_r1 ] [a0] [sum_r ]
# [sum_r1, sum_r1sq] [a1] = [sum_r_r1]
# where sum_r1 = sum(r[t-1]), sum_r = sum(r[t]), etc.
residuals = np.zeros(n)
min_warmup = 30 # minimum bars to fit AR(1)
# Running sums for expanding OLS (using pairs (r[t-1], r[t]) for t>=1)
S_n = 0.0 # count of pairs
S_x = 0.0 # sum of r[t-1]
S_y = 0.0 # sum of r[t]
S_xx = 0.0 # sum of r[t-1]^2
S_xy = 0.0 # sum of r[t-1]*r[t]
for i in range(1, n):
# Update running sums with pair (r[i-1], r[i]) but we use data up to i-1
# So at step i, we first compute residual using sums from [1..i-1],
# then update sums to include pair for t=i.
if S_n >= min_warmup:
# Fit AR(1) from expanding window up to t=i-1
denom = S_n * S_xx - S_x * S_x
if abs(denom) > 1e-14:
a1 = (S_n * S_xy - S_x * S_y) / denom
a0 = (S_y - a1 * S_x) / S_n
else:
a0, a1 = 0.0, 0.0
# Residual at bar i: actual r[i] minus AR(1) prediction
pred = a0 + a1 * r[i - 1]
residuals[i] = r[i] - pred
# else: residuals[i] remains 0
# Update running sums with the new observation pair (r[i-1], r[i])
# This is data point for t=i: x=r[i-1], y=r[i]
S_n += 1.0
S_x += r[i - 1]
S_y += r[i]
S_xx += r[i - 1] ** 2
S_xy += r[i - 1] * r[i]
# Z-score the residual with rolling window
z = al.zscore(residuals, zscore_win)
# Mean-reversion: negative of z-score
direction = -z
direction = np.nan_to_num(direction, nan=0.0)
# Vol-target to 20% annualized, cap at 2x leverage
target = al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
return target
def make_target(zscore_win: int):
return lambda df: ar1_residual_target(df, zscore_win=zscore_win)
if __name__ == "__main__":
# Small internal grid: 2 z-score windows × 2 TFs = 4 cells per config
# Pick best by min-asset holdout Sharpe
configs = [
{"zscore_win": 60, "label": "z60"},
{"zscore_win": 120, "label": "z120"},
]
tfs = ("1d", "12h")
best_rep = None
best_score = -9.0
for cfg in configs:
zw = cfg["zscore_win"]
rep = al.study_weights(
f"STA08-AR1resid-z{zw}",
make_target(zw),
tfs=tfs,
)
score = rep["verdict"].get("best_holdout_sharpe", -9.0)
if score > best_score:
best_score = score
best_rep = rep
# Print intermediate for debug
print(f"\n--- Config z{zw} ---")
print(al.fmt(rep))
print("\n\n=== BEST CONFIG ===")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))