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,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)
|
||||
Reference in New Issue
Block a user