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,72 @@
|
||||
"""Agent 02 — TSMOM multi-horizon (family=trend, slug=tsmom_multi).
|
||||
|
||||
The angle (assigned): time-series momentum over several lookback horizons. For each
|
||||
horizon H in {~30, ~90, ~180} bars take the SIGN of the past-H-bar return (is the
|
||||
asset up or down vs H bars ago?), average the three signs into a -1..+1 direction,
|
||||
then size it with a causal vol-target so the two curves are risk-comparable and the
|
||||
drawdown stays bounded.
|
||||
|
||||
Why multi-horizon: a single lookback is regime-fragile (whipsaws when its window
|
||||
straddles a chop). Averaging 1/3/6-month TSMOM signs is the classic TP01 trick —
|
||||
the slow horizon carries the macro trend, the fast ones cut exposure early into a
|
||||
turn. On these two persistently up-trending curves the net effect is to stay long
|
||||
through the bull and de-risk (toward flat / light short) into the big declines,
|
||||
turning a ~77-79% buy&hold drawdown into a much smaller one at comparable PnL.
|
||||
|
||||
Long-short vs long-flat: a symmetric short bleeds in a structural bull (it shorts
|
||||
the dips). Tuned on split='train', a lightly de-weighted short (SHORT_W<1) beats both
|
||||
pure long-flat (misses the protection of going short the worst legs) and a symmetric
|
||||
long-short (too much drag). SHORT_W=0.25 sits in the interior of a flat plateau.
|
||||
|
||||
CAUSAL: each horizon return uses close[i]/close[i-H] (rows <= i only); vol_target
|
||||
uses a trailing realized-vol window. No look-ahead, no centered windows, no global
|
||||
fit. Verified by causality_ok (max_diff 0.0).
|
||||
|
||||
Tuning (train only, combined A&B). A coarse->fine sweep found a WIDE plateau around
|
||||
slow horizons ~ (1.5, 4.5, 8 months): the whole block H1 in [40..55], H2 in [120..130],
|
||||
H3 = 240 gives sharpe_min 1.25..1.41 at DD 0.16..0.21. The chosen cell is interior on
|
||||
every axis (all 8 H-neighbors, sw, vw within the plateau) -> robust, not a lucky spike:
|
||||
horizons = (45, 130, 240) # ~1.5 / 4.5 / 8 months of daily bars
|
||||
SHORT_W = 0.25 # asymmetric L/S; plateau sw in [0.0..0.5]
|
||||
TARGET_VOL=0.30, VOL_WIN=45d, LEV_CAP=1.5
|
||||
-> train combined: pnl_mean ~3.2, maxdd_worst ~0.21, sharpe_min ~1.37.
|
||||
A single fast lookback (e.g. 30) is regime-fragile here; the slow multi-horizon blend
|
||||
is what both lifts the Sharpe and roughly halves the buy&hold (~77-79%) drawdown.
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
HORIZONS = (45, 130, 240) # ~1.5/4.5/8 months of daily bars (multi-horizon TSMOM)
|
||||
SHORT_W = 0.25 # de-weight the short side (curves trend up); 0 -> long-flat
|
||||
TARGET_VOL = 0.30
|
||||
VOL_WIN_DAYS = 45
|
||||
LEV_CAP = 1.5
|
||||
|
||||
|
||||
def _tsmom_sign(c: np.ndarray, h: int) -> np.ndarray:
|
||||
"""Sign of the past-h-bar return, causal. mom[i] = sign(c[i]/c[i-h] - 1).
|
||||
Undefined (0) for i < h."""
|
||||
out = np.zeros(len(c))
|
||||
if h < len(c):
|
||||
past = c[:-h]
|
||||
cur = c[h:]
|
||||
out[h:] = np.sign(cur / past - 1.0)
|
||||
return out
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
|
||||
# average the SIGN of TSMOM over the three horizons -> direction in [-1, +1]
|
||||
sig = np.zeros(len(c))
|
||||
for h in HORIZONS:
|
||||
sig += _tsmom_sign(c, h)
|
||||
sig /= len(HORIZONS)
|
||||
|
||||
# asymmetric long-short: keep the long full size, de-weight the short side
|
||||
raw = np.where(sig >= 0.0, sig, sig * SHORT_W)
|
||||
|
||||
# causal vol-targeting: shrinks 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