1afb1014c9
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>
100 lines
4.5 KiB
Python
100 lines
4.5 KiB
Python
"""Agent 39 — Efficiency-ratio / fractal GATE on a momentum signal (family=stat, slug=effratio).
|
|
|
|
THE ANGLE (assigned): take a plain momentum bet, but TRADE ONLY WHEN THE MOVE IS
|
|
"EFFICIENT". Efficiency = how straight the path is. We measure it with two
|
|
interchangeable causal fractal gauges and use them as an ON/OFF gate, NOT as an
|
|
adaptive average (that is the sibling KAMA angle). Here momentum decides DIRECTION
|
|
and the efficiency ratio decides WHETHER WE ARE ALLOWED TO TAKE THE TRADE.
|
|
|
|
EFFICIENCY GAUGES (both causal, both in [0,1], higher = straighter / more trending):
|
|
* Kaufman Efficiency Ratio (ER): net displacement / total path length over n bars.
|
|
ER[i] = |c[i]-c[i-n]| / sum_{k} |c[k]-c[k-1]|
|
|
ER -> 1 a clean directional move, ER -> 0 a random-walk chop.
|
|
* Fractal-dimension proxy (1 - normalized roughness): in chop the path's total
|
|
length is many times its displacement (high fractal dimension ~2 = plane-filling);
|
|
in a trend length ~ displacement (dimension ~1 = a line). We map this to an
|
|
efficiency score E_fd in [0,1] = ER itself is the cleanest such proxy, so the
|
|
primary gauge IS ER; we blend a SLOWER ER to require efficiency on two horizons.
|
|
|
|
DIRECTION (momentum): sign of a fast/slow EMA spread of price (a standard momentum
|
|
signal). This is the "plain momentum" the angle gates — not KAMA.
|
|
|
|
GATE: trade only when the (blended) efficiency ratio is above a CAUSAL expanding
|
|
quantile of its own history (the move is efficient ENOUGH for THIS curve right now).
|
|
In chop the gate is shut -> flat -> we skip the whipsaw that kills naked momentum.
|
|
|
|
LONG-SHORT: curves trend up structurally so a symmetric short bleeds (shorts the
|
|
dips). Keep the long full size, de-weight the short (SHORT_W) so the short only
|
|
protects the big EFFICIENT declines (a crash is a very efficient down-move -> the
|
|
gate is OPEN and momentum is down -> we are short exactly when it pays).
|
|
|
|
SIZING: causal vol_target so A and B are risk-comparable and every vol spike (= every
|
|
crash) auto-shrinks exposure -> the ~77-79% buy&hold drawdown collapses.
|
|
|
|
CAUSAL: EMA spread, ER (both horizons), the expanding-quantile gate, and vol_target
|
|
all use rows <= i only. No shift(-k), no centered window, no global fit. Verified by
|
|
causality_ok (max_diff ~0).
|
|
"""
|
|
import numpy as np
|
|
import pandas as pd
|
|
import blindlib as bl
|
|
|
|
# --- momentum (direction) --- [tuned on train, wide plateau]
|
|
EMA_FAST = 10
|
|
EMA_SLOW = 50
|
|
|
|
# --- efficiency gate (the angle) ---
|
|
ER_WIN = 25 # fast efficiency-ratio lookback (~1 month daily)
|
|
ER_WIN2 = 60 # slow efficiency-ratio lookback (require efficiency on 2 horizons)
|
|
ER_BLEND = 0.5 # weight of the slow ER in the blended gauge
|
|
ER_Q = 0.33 # expanding-quantile gate: trade only when eff above its own history
|
|
WARMUP = 60 # min bars before the expanding gate is trusted
|
|
|
|
# --- exposure ---
|
|
SHORT_W = 0.25 # de-weight the short side (curves trend up); 0 -> long-flat
|
|
TARGET_VOL = 0.30
|
|
VOL_WIN_DAYS = 25
|
|
LEV_CAP = 1.5
|
|
|
|
|
|
def _efficiency_ratio(c: np.ndarray, n: int) -> np.ndarray:
|
|
"""Kaufman efficiency ratio over n bars, causal. ER[i] uses close[i-n..i]."""
|
|
change = np.zeros(len(c))
|
|
change[n:] = np.abs(c[n:] - c[:-n])
|
|
d = np.abs(np.diff(c, prepend=c[0]))
|
|
volatility = pd.Series(d).rolling(n, min_periods=n).sum().values
|
|
er = np.where(volatility > 0, change / volatility, 0.0)
|
|
er[:n] = 0.0
|
|
return np.nan_to_num(er, nan=0.0)
|
|
|
|
|
|
def _expanding_quantile(x: np.ndarray, q: float, warmup: int) -> np.ndarray:
|
|
"""Causal expanding quantile: thr[i] = q-quantile of x[0..i]. Impassable before warmup."""
|
|
return pd.Series(x).expanding(min_periods=warmup).quantile(q).values
|
|
|
|
|
|
def signal(df):
|
|
c = df["close"].values.astype(float)
|
|
n = len(c)
|
|
|
|
# DIRECTION: plain momentum = sign of fast-slow EMA spread
|
|
ef = bl.ema(c, EMA_FAST)
|
|
es = bl.ema(c, EMA_SLOW)
|
|
direction = np.sign(ef - es)
|
|
|
|
# EFFICIENCY GAUGE: blend a fast and a slow Kaufman efficiency ratio
|
|
er_fast = _efficiency_ratio(c, ER_WIN)
|
|
er_slow = _efficiency_ratio(c, ER_WIN2)
|
|
eff = (1.0 - ER_BLEND) * er_fast + ER_BLEND * er_slow
|
|
|
|
# GATE: only trade when efficiency is high relative to this curve's own past
|
|
thr = _expanding_quantile(eff, ER_Q, WARMUP)
|
|
active = np.where(np.isfinite(thr) & (eff >= thr), 1.0, 0.0)
|
|
|
|
raw = direction * active
|
|
raw = np.where(raw >= 0.0, raw, raw * SHORT_W) # de-weight the short side
|
|
|
|
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)
|