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:
Adriano Dal Pastro
2026-06-21 07:05:04 +00:00
parent f5d30d88b9
commit 1afb1014c9
63 changed files with 6660 additions and 0 deletions
@@ -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)