research(blind): 52 agenti ciechi su curve anonime BTC/ETH — orchestratore valuta PnL/maxDD, niente di nuovo regge
Flotta di 52 subagenti "esperti di segnali" su storico BTC/ETH ANONIMIZZATO (Series A/B rebased a 100, calendario sintetico, split 70/30) — non sanno cosa siano. Ognuno scrive un signal(df)->position causale (script o ML), tunato solo sul train. Orchestratore valuta su PnL e maxDD nel test held-out. Harness cieco leak-free (riusabile): - make_blind.py: export anonimo + overlay; blindlib.py: evaluator con shift della posizione + GUARDIA DI CAUSALITA' online (squalifica ogni look-ahead, ML incluso); blind_eval.py CLI; score_all.py giudice OOS; verify_top.py (corr-al-trend, fee-stress, jackknife). - 52/52 passano la guardia (zero leak su tutta la flotta). Esito OOS (benchmark buy&hold: -7% PnL, 68% DD): - top = macd (+21%, DD 11%, Sh 0.84), accel, vol_of_vol, regime_switch, rf, obv — tutti trend/vol-regime. Sharpe OOS ~0.84 decade dal train ~1.4. Mean-rev e ML in fondo. - 3 scettici indipendenti: REFUTED. regime-luck (top-5 bar = 67-102% del PnL); trend-redundancy (HAC alpha t=+0.9..+1.5, nessuno >1.96 — TSMOM travestito); overfit (accel/vov knife-edge). Verdetto: ri-conferma CIECA e indipendente del soffitto direzionale ~1.3. macd = classe-TP01, forward-monitor non deploy. Diario 2026-06-21-blind-signal-fleet.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Agent 05 — Momentum z-score (family=trend, slug=momz).
|
||||
|
||||
The angle (assigned): take the N-bar return as a momentum signal, STANDARDIZE it with a
|
||||
CAUSAL rolling z-score, then squash with tanh into a position in [-1,+1]. Tune N.
|
||||
|
||||
Why z-score the momentum (not the raw return): the magnitude of an N-bar return drifts
|
||||
with the volatility regime — a +5% N-bar move means "strong" in a calm market and mere
|
||||
"noise" in a wild one. Dividing by the trailing std of that same N-bar momentum makes the
|
||||
signal regime-stationary: the position grows when momentum is unusually strong vs its own
|
||||
recent distribution and shrinks toward 0 when it is merely typical. tanh(K*z) gives a
|
||||
smooth, saturating long/short sizing (no hard sign flips -> less turnover/fee churn than a
|
||||
sign rule) that is already bounded in [-1,1].
|
||||
|
||||
Single N is regime-fragile here (a lone lookback's sharpe_min ricochets 0.4..1.1 across N
|
||||
on the two train curves). The cure, staying true to the z-score angle, is to BLEND THE
|
||||
Z-SCORES of a few momentum horizons (fast/mid/slow N) — the distinguishing feature is the
|
||||
standardization; multi-horizon is just averaging the standardized momentum, the same trick
|
||||
that stabilizes TSMOM. The blended z is the direction; a causal vol-target then sizes it so
|
||||
the two curves are risk-comparable and the drawdown stays bounded (every crash is a vol
|
||||
spike -> exposure shrinks into it).
|
||||
|
||||
Long-flat, not long-short: the two curves trend up structurally and a tuning sweep on
|
||||
split='train' is monotone — every bit of short weight ONLY adds drag and drawdown here
|
||||
(SHORT_W 0->1 takes sharpe_min from ~1.4 down to ~0.85 and DD 0.17->0.33). So SHORT_W=0:
|
||||
go long when blended momentum-z is positive, flat otherwise. (The short side is kept as a
|
||||
parameter, not hard-removed, so the rule is explicit and re-tunable on a different regime.)
|
||||
|
||||
CAUSAL: mom[i] = close[i]/close[i-N]-1 uses rows <= i; zscore uses a trailing window;
|
||||
vol_target uses trailing realized vol. No shift(-k), no centered windows, no global fit.
|
||||
Verified by causality_ok (max_diff 0.0).
|
||||
|
||||
Tuning (train only, combined A&B; coarse->fine sweep). The chosen cell is INTERIOR on every
|
||||
axis — all horizon-set neighbors, ZW in [200..280], VW in [30..40], K in [2.5..4] stay in
|
||||
sharpe_min ~1.2..1.45 at DD ~0.16..0.24, so it's a plateau, not a lucky spike:
|
||||
HORIZONS=(40,120,220) # ~fast/mid/slow N-bar momentum
|
||||
Z_WIN=250 # window standardizing each N-bar momentum
|
||||
K=3.0 # tanh gain (near-saturating; >=2.5 is flat)
|
||||
SHORT_W=0.0 # long-flat (short only added drag here)
|
||||
TARGET_VOL=0.25, VOL_WIN_DAYS=35, LEV_CAP=1.5
|
||||
-> train combined: pnl_mean ~2.77, maxdd_worst ~0.17, sharpe_min ~1.39
|
||||
(vs long-only buy&hold's ~7-23x PnL at ~70-80% DD — the z-momentum keeps a healthy
|
||||
PnL while cutting the drawdown ~4-5x by de-risking into the big declines).
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
HORIZONS = (40, 120, 220) # N-bar momentum lookbacks (fast/mid/slow) — the "N" of the angle
|
||||
Z_WIN = 250 # causal window standardizing each N-bar momentum
|
||||
K = 3.0 # tanh gain on the blended z-score (near-saturating)
|
||||
SHORT_W = 0.0 # de-weight the short side; 0 -> long-flat (best on train)
|
||||
TARGET_VOL = 0.25
|
||||
VOL_WIN_DAYS = 35
|
||||
LEV_CAP = 1.5
|
||||
|
||||
|
||||
def _mom(c: np.ndarray, n: int) -> np.ndarray:
|
||||
"""Causal N-bar return. mom[i] = c[i]/c[i-n] - 1, undefined (0) for i < n."""
|
||||
out = np.zeros(len(c))
|
||||
if n < len(c):
|
||||
out[n:] = c[n:] / c[:-n] - 1.0
|
||||
return out
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
|
||||
# blend the z-scores of several momentum horizons -> regime-stationary direction
|
||||
zsum = np.zeros(len(c))
|
||||
for n in HORIZONS:
|
||||
z = bl.zscore(_mom(c, n), Z_WIN) # standardize vs own trailing distribution
|
||||
zsum += np.nan_to_num(z, nan=0.0)
|
||||
z = zsum / len(HORIZONS)
|
||||
|
||||
raw = np.tanh(K * z) # smooth, saturating direction in [-1, 1]
|
||||
raw = np.where(raw >= 0.0, raw, raw * SHORT_W) # de-weight short side (0 = long-flat)
|
||||
|
||||
pos = bl.vol_target(raw, df, target_vol=TARGET_VOL,
|
||||
vol_win_days=VOL_WIN_DAYS, leverage_cap=LEV_CAP)
|
||||
return np.clip(np.nan_to_num(pos, nan=0.0), -1.0, 1.0)
|
||||
Reference in New Issue
Block a user