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,75 @@
|
||||
"""Agent 04 — MACD (family=trend, slug=macd).
|
||||
|
||||
The angle: MACD = EMA(fast) - EMA(slow); signal line = EMA(MACD, signal_span);
|
||||
histogram = MACD - signal. Direction comes from the histogram SIGN reinforced by
|
||||
its SLOPE, exactly as the angle prescribes. Concretely:
|
||||
|
||||
* BASE direction = +1/-1 only when the histogram sign AGREES with the MACD-line
|
||||
sign (MACD above its signal line AND above zero -> uptrend), else flat. Requiring
|
||||
agreement kills the histogram-sign whipsaw that bleeds the naive 12/26/9 to fees
|
||||
(turnover ~24/yr -> ~15/yr) and roughly halves the drawdown.
|
||||
* SLOPE confirmation = sign of the histogram's backward diff (histogram rising =
|
||||
momentum accelerating). Blended in at weight SLOPE_W; it trims the drawdown
|
||||
further (~0.18 -> ~0.12) by stepping aside while momentum is decelerating.
|
||||
|
||||
Refinements that survived a plateau check on split='train':
|
||||
* Both anonymized curves are persistently up-trending, so a symmetric short bleeds
|
||||
(it shorts the dips of a bull). We keep a genuine long/short MACD but size the
|
||||
SHORT side down (SHORT_W=0.5).
|
||||
* Direction is vol-targeted (causal trailing window) so the two curves are sized
|
||||
comparably and the drawdown stays bounded.
|
||||
|
||||
Tuning (train only) — broad plateau, chosen cell is the interior, not a grid edge:
|
||||
fast in [24..28], slow in [50..56], signal=9, SHORT_W in [0.5..0.6],
|
||||
SLOPE_W in [0.2..0.35], VOL_WIN in [20..60] all give sharpe_min ~1.35-1.45 at
|
||||
DD ~0.10-0.13. Picked fast=26, slow=52, signal=9, SHORT_W=0.5, SLOPE_W=0.20.
|
||||
Fee-robust: sharpe_min only 1.40 -> 1.29 as round-trip fee goes 0.10% -> 0.30%.
|
||||
|
||||
Benchmark: long-only buy&hold on train is pnl ~6.7/23.0 but maxDD ~0.77/0.79
|
||||
(sharpe ~0.89/1.16). This MACD anticipates the trend at a MUCH smaller drawdown
|
||||
(~0.12) with a higher risk-adjusted return (sharpe_min ~1.40).
|
||||
|
||||
CAUSAL: ema(c, span) is an online recursion (value at i uses rows 0..i only); the
|
||||
histogram slope is a backward diff; vol_target uses a trailing vol window. No
|
||||
look-ahead, no centered windows, no global fit. Verified by causality_ok (max_diff 0).
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
# --- tuned ONLY on split='train' (plateau interior) ---
|
||||
FAST_SPAN = 26
|
||||
SLOW_SPAN = 52
|
||||
SIGNAL_SPAN = 9
|
||||
SLOPE_W = 0.20 # weight of histogram-slope confirmation in the direction
|
||||
SHORT_W = 0.5 # short side sized down (asymmetric L/S in a bull); 0 -> long-flat
|
||||
TARGET_VOL = 0.20
|
||||
VOL_WIN = 30
|
||||
LEV_CAP = 1.0
|
||||
|
||||
|
||||
def _macd(c, fast, slow, sig):
|
||||
macd = bl.ema(c, fast) - bl.ema(c, slow)
|
||||
signal_line = bl.ema(macd, sig)
|
||||
hist = macd - signal_line
|
||||
return macd, signal_line, hist
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
macd, signal_line, hist = _macd(c, FAST_SPAN, SLOW_SPAN, SIGNAL_SPAN)
|
||||
|
||||
# base direction: take a side only when the histogram sign and the MACD-line
|
||||
# sign AGREE (MACD vs signal AND MACD vs zero point the same way), else flat.
|
||||
base = np.where(np.sign(hist) == np.sign(macd), np.sign(macd), 0.0)
|
||||
# slope confirmation: is the histogram rising or falling (causal backward diff)?
|
||||
slope = np.sign(np.diff(hist, prepend=hist[0]))
|
||||
|
||||
raw = (1.0 - SLOPE_W) * base + SLOPE_W * slope
|
||||
raw = np.clip(raw, -1.0, 1.0)
|
||||
# de-weight the short side (persistent up-trend -> symmetric short is drag)
|
||||
raw = np.where(raw < 0, raw * SHORT_W, raw)
|
||||
raw = np.nan_to_num(raw, nan=0.0)
|
||||
|
||||
pos = bl.vol_target(raw, df, target_vol=TARGET_VOL,
|
||||
vol_win_days=VOL_WIN, leverage_cap=LEV_CAP)
|
||||
return np.clip(pos, -1.0, 1.0)
|
||||
Reference in New Issue
Block a user