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,106 @@
"""Agent 48 — Multi-timescale agreement (family=mix, slug=multiscale).
The angle (assigned): build a weekly-ish momentum by rolling aggregation up to i and
combine it with a daily momentum, going long/short only when the timescales AGREE.
Why agreement, not just averaging: a single horizon whipsaws when its window straddles
a chop. By measuring momentum at DAILY (1-bar EMA slope), WEEKLY (~5-bar aggregated
returns) and MONTHLY (~21-bar) timescales and requiring them to point the same way, we
filter the rule down to the bars where the trend is coherent across scales. The position
size = the (weighted) fraction of timescales that agree, so a unanimous up-vote is full
size and a split vote is light/flat. A vol-target then makes the two curves risk-
comparable and shrinks size into every vol spike (i.e. into every crash), turning the
~77-79% buy&hold drawdown into a ~0.23 one at comparable PnL.
Multi-timescale construction (all causal, value at i uses rows <= i only):
* DAILY momentum: sign of close vs a short EMA (fast trend state).
* WEEKLY momentum: rolling aggregation — mean of the last WEEK_WIN daily log-returns
(= ~WEEK_WIN/5 weeks of weekly drift) up to i. This is the "weekly-ish momentum by
rolling aggregation up to i" the angle asks for.
* MONTHLY momentum: sign of the past-MONTH_H-bar return (slow ~6-month macro trend).
The three signs are combined with weights into a -1..+1 direction; the short side is
zeroed (SHORT_W=0 -> long-flat) because both curves trend structurally up, so any short
bleeds by shorting the dips — tuning on train, long-flat dominated every de-weighted
short on sharpe_min (1.475 vs 1.45 at SHORT_W=0.3).
CAUSAL: EMAs / rolling means / past-return signs all use data <= i; 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 (split='train' only, combined A&B). Coarse->fine sweep on the timescale set,
weights, the short weight and the vol-target block; one-axis neighbor check confirms the
cell is interior on a wide plateau (ema 6-10, wk 30-35, mo 110-126, tv 0.26-0.30, vw
30-35 all give sharpe_min 1.42-1.50). Chosen cell:
DAILY_EMA=8, WEEK_WIN=35 (~7 weeks of daily drift), MONTH_H=126
weights (daily,weekly,monthly) = (0.15, 0.40, 0.45)
SHORT_W=0.0 (long-flat), TARGET_VOL=0.28, VOL_WIN=35d, LEV_CAP=1.5
-> train combined: pnl_mean ~3.62, maxdd_worst ~0.23, sharpe_min ~1.48.
"""
import numpy as np
import blindlib as bl
# timescale set
DAILY_EMA = 8 # daily-ish trend state (fast EMA)
WEEK_WIN = 35 # rolling window of daily log-returns (~7 weeks of weekly drift)
MONTH_H = 126 # ~6-month macro lookback (monthly-ish slow trend)
# combination weights (sum ~1) — weekly + monthly carry the agreement
W_DAILY = 0.15
W_WEEK = 0.40
W_MONTH = 0.45
SHORT_W = 0.0 # zero the short side (curves trend up) -> long-flat
# sizing
TARGET_VOL = 0.28
VOL_WIN_DAYS = 35
LEV_CAP = 1.5
def _daily_mom(c: np.ndarray) -> np.ndarray:
"""Sign of close vs a short EMA — the fast (daily) trend state, causal."""
e = bl.ema(c, DAILY_EMA)
return np.sign(c / e - 1.0)
def _weekly_mom(c: np.ndarray) -> np.ndarray:
"""Weekly-ish momentum by ROLLING AGGREGATION up to i (the assigned angle).
Aggregate daily log-returns into the average drift over the last WEEK_WIN bars
(~7 weeks), then take its sign. Causal: at bar i it only averages r[i-W+1..i].
Vectorized via a prefix-sum so it is O(n)."""
lr = bl.log_returns(c) # lr[i] = log(c[i]/c[i-1]), causal
win = WEEK_WIN
s = np.concatenate([[0.0], np.cumsum(lr)]) # prefix sums, s[k] = sum(lr[:k])
out = np.zeros(len(c))
idx = np.arange(len(c))
lo = np.maximum(0, idx - win + 1)
full = idx >= (win - 1) # only emit once the full window exists
means = (s[idx + 1] - s[lo]) / win
out[full] = np.sign(means[full])
return out
def _monthly_mom(c: np.ndarray) -> np.ndarray:
"""Sign of the past-MONTH_H-bar return — the slow macro trend, causal."""
out = np.zeros(len(c))
h = MONTH_H
if h < len(c):
out[h:] = np.sign(c[h:] / c[:-h] - 1.0)
return out
def signal(df):
c = df["close"].values.astype(float)
d = _daily_mom(c)
w = _weekly_mom(c)
m = _monthly_mom(c)
# weighted multi-timescale agreement -> direction in [-1, +1]
sig = W_DAILY * d + W_WEEK * w + W_MONTH * m
# asymmetric long-short: keep longs full size, de-weight shorts
raw = np.where(sig >= 0.0, sig, sig * SHORT_W)
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)