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,97 @@
|
||||
"""Agent 06 — Acceleration / momentum-of-momentum (family=trend, slug=accel).
|
||||
|
||||
The angle (assigned): 2nd difference / momentum-of-momentum. Go WITH an accelerating
|
||||
trend, cut (de-risk toward flat) when the trend is decelerating.
|
||||
|
||||
Construction (all causal):
|
||||
1. velocity v[i] = EMA(log-return, FAST) — a smoothed 1st derivative of log-price
|
||||
(the local trend "speed", sign = up/down).
|
||||
2. acceleration a[i] = v[i] - v[i-LAG] — the momentum-OF-momentum (discrete 2nd
|
||||
difference of log-price). a>0 = the up-move is speeding up / a down-move is
|
||||
bottoming; a<0 = the up-move is rolling over / a down-move is accelerating.
|
||||
3. Standardize BOTH v and a with a causal rolling z-score so they are regime-
|
||||
stationary (a "fast" velocity in a calm tape is "slow" in a wild one).
|
||||
4. Direction = the trend you ride GATED by acceleration:
|
||||
dir = sign-ish(velocity) * gate(acceleration)
|
||||
where the gate OPENS exposure when momentum is accelerating in the trend's
|
||||
direction and CLOSES it (toward 0) when it decelerates. Concretely we combine
|
||||
a velocity term (ride the trend) with an acceleration term (the angle's edge):
|
||||
raw = tanh(KV * zv) * 0.5 + tanh(KA * za) * 0.5
|
||||
then de-weight the short side (these curves trend up structurally so a full
|
||||
symmetric short bleeds shorting the dips) and vol-target so A and B are
|
||||
risk-comparable and every crash (a vol spike) shrinks size into itself.
|
||||
|
||||
Why acceleration adds over plain momentum: plain TSMOM is fully long through a long
|
||||
top-formation and gives the gains back on the way down. The 2nd difference turns
|
||||
NEGATIVE while price is still high but rolling over (momentum decelerating) — it cuts
|
||||
risk EARLY, before the level-based trend flips. Symmetrically it re-engages when a
|
||||
decline starts decelerating (bottoming). That earlier turn is the whole point of the
|
||||
angle: comparable PnL to buy&hold at a much smaller drawdown.
|
||||
|
||||
CAUSAL: EMA, rolling z-score, the v[i]-v[i-LAG] difference and vol_target all use rows
|
||||
<= i only. No shift(-k), no centered windows, no global fit. Verified by causality_ok.
|
||||
|
||||
Tuning (train only, combined A&B): a coarse->fine sweep over (FAST, LAG, weights, KV/KA,
|
||||
short_w, Z_WIN, vol-target) picked a WIDE interior plateau, not a spike. The chosen cell
|
||||
(FAST=28, LAG=30, Z_WIN=200, KV=KA=1.5, W_VEL=0.4/W_ACC=0.6, SHORT_W=0, vol25) is interior
|
||||
on EVERY axis: FAST in [22..36] -> sh_min 1.50..1.52; LAG in [26..40] -> 1.41..1.52
|
||||
(peak 30); Z_WIN in [160..220] -> 1.52..1.56; W_ACC/KA/KV/vol all smooth & monotone.
|
||||
-> train combined: pnl_mean ~2.3, maxdd_worst ~0.20, sharpe_min ~1.52.
|
||||
SHORT_W=0 (long-flat) beat every short weight on train (sh_min collapses 1.31->0.43 as the
|
||||
short side is turned on) — the deceleration gate ALREADY de-risks to flat at the top, so a
|
||||
symmetric short just shorts the dips of a structural bull. The acceleration term is what
|
||||
earns the carry over plain velocity: W_ACC=0 drops pnl_mean to ~0.6 (it ducks risk too
|
||||
early); W_ACC~0.6 keeps the early de-risk while staying invested through the accelerating
|
||||
legs. DD ~0.20 vs a ~77-79% buy&hold drawdown.
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
FAST = 28 # EMA span for the velocity (smoothed log-return / local slope)
|
||||
LAG = 30 # horizon of the 2nd difference: accel = v[i] - v[i-LAG]
|
||||
Z_WIN = 200 # causal window to standardize velocity & acceleration
|
||||
KV = 1.5 # tanh gain on the velocity z (ride the trend)
|
||||
KA = 1.5 # tanh gain on the acceleration z (the angle's edge)
|
||||
W_VEL = 0.4 # weight on the velocity (trend) term
|
||||
W_ACC = 0.6 # weight on the acceleration (momentum-of-momentum) term
|
||||
SHORT_W = 0.0 # long-flat: the de-celeration gate already cuts to flat; a
|
||||
# symmetric short only bleeds shorting the dips of a structural
|
||||
# up-trend (train sweep: sh_min 1.31@0.0 -> 0.43@1.0). 0 = flat.
|
||||
TARGET_VOL = 0.27
|
||||
VOL_WIN_DAYS = 25
|
||||
LEV_CAP = 1.5
|
||||
|
||||
|
||||
def _lagged_diff(x: np.ndarray, lag: int) -> np.ndarray:
|
||||
"""Causal discrete derivative: out[i] = x[i] - x[i-lag], 0 for i < lag."""
|
||||
out = np.zeros(len(x))
|
||||
if lag < len(x):
|
||||
out[lag:] = x[lag:] - x[:-lag]
|
||||
return out
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
lr = np.zeros(len(c))
|
||||
lr[1:] = np.log(c[1:] / c[:-1]) # causal log returns
|
||||
|
||||
# 1) velocity: smoothed 1st derivative of log-price (local trend speed)
|
||||
vel = bl.ema(lr, FAST)
|
||||
# 2) acceleration: momentum-of-momentum = 2nd difference of the trend
|
||||
acc = _lagged_diff(vel, LAG)
|
||||
|
||||
# 3) standardize both vs their own trailing distribution (regime-stationary)
|
||||
zv = np.nan_to_num(bl.zscore(vel, Z_WIN), nan=0.0)
|
||||
za = np.nan_to_num(bl.zscore(acc, Z_WIN), nan=0.0)
|
||||
|
||||
# 4) ride the trend, GATED/boosted by acceleration (the angle's edge)
|
||||
raw = W_VEL * np.tanh(KV * zv) + W_ACC * np.tanh(KA * za)
|
||||
raw = np.clip(raw, -1.0, 1.0)
|
||||
|
||||
# asymmetric long-short: full long, de-weighted short (structural up-trend)
|
||||
raw = np.where(raw >= 0.0, raw, raw * SHORT_W)
|
||||
|
||||
# causal vol-targeting: shrink size into vol spikes (every crash is a vol spike)
|
||||
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