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,65 @@
|
||||
"""agent_09_donchian — ANGLE: Donchian channel breakout (long / flat).
|
||||
|
||||
Idea (assigned angle): a classic Donchian / turtle breakout trend-follower. ENTER LONG
|
||||
when the close prints above the prior N-bar HIGH (an upside breakout) and EXIT (go FLAT)
|
||||
when it prints below the prior X-bar LOW (a downside breakout). Hold the long between
|
||||
those two events. Tune N (entry) and X (exit) on split='train' only.
|
||||
|
||||
WHY LONG/FLAT, NOT LONG/SHORT (honest tuning result):
|
||||
The textbook donchian is stop-and-reverse (short below the prior low). I tested it.
|
||||
On BOTH series the SHORT leg is purely value-destroying: every short_size > 0 raised
|
||||
the drawdown AND lowered Sharpe (the pair trends up, so downside breakouts are mostly
|
||||
V-shaped bottoms / chop where the short gets whipsawed). So the breakout *exit* is
|
||||
kept (a low-channel break flattens us, turtle-style), but we never flip short. The
|
||||
donchian breakout EVENT is still what drives every entry and exit — the angle is intact.
|
||||
|
||||
Tuned on split='train' (both Series A and B, equal weight) — broad plateau Nin 25..36 /
|
||||
Xout 18..20, Sharpe_min ~1.20-1.27 throughout (not an isolated peak):
|
||||
* N_ENTRY = 36 bars (prior-N high that defines an upside breakout)
|
||||
* N_EXIT = 18 bars (shorter prior-low channel -> exit faster than we enter)
|
||||
* vol-target the long to 30% ann vol (vol_win=30d, cap 1.0): long size shrinks into
|
||||
vol spikes (every crash is a vol spike) -> caps the drawdown of late/whipsaw entries.
|
||||
|
||||
Causality: bl.donchian shifts the rolling max/min by one bar, so the channel at i is
|
||||
built from bars STRICTLY before i; a close[i] that breaks it is a real, tradeable event
|
||||
at close[i]. The evaluator then holds the position during bar i+1. No future rows; the
|
||||
state machine is a forward scan (uses only data <= i). causality_ok -> true.
|
||||
|
||||
Train (combined A&B): pnl_mean ~3.43, maxdd_worst ~0.31, sharpe_min ~1.27.
|
||||
Honest note: Donchian is pure trend-following, not alpha. Its value here is converting a
|
||||
high-PnL / ~74%-DD uptrend into comparable PnL at ~31% drawdown (DD cut ~2.4x). The full
|
||||
long/short donchian was MUCH worse (Sharpe_min ~0.2, DD ~74%); the edge is the FLAT side.
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
N_ENTRY = 36 # Donchian entry: long on break of prior N_ENTRY-bar high
|
||||
N_EXIT = 18 # Donchian exit: flat on break of prior N_EXIT-bar low
|
||||
TARGET_VOL = 0.30
|
||||
VOL_WIN_DAYS = 30
|
||||
LEV_CAP = 1.0
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
hi_entry, _ = bl.donchian(df, N_ENTRY) # prior N_ENTRY-bar high (shifted, causal)
|
||||
_, lo_exit = bl.donchian(df, N_EXIT) # prior N_EXIT-bar low (shifted, causal)
|
||||
|
||||
up = c > hi_entry # upside breakout -> enter/stay long
|
||||
dn = c < lo_exit # downside breakout -> exit to flat
|
||||
|
||||
# turtle long/flat state machine (forward scan, uses only data <= i)
|
||||
n = len(c)
|
||||
state = np.zeros(n)
|
||||
s = 0.0
|
||||
for i in range(n):
|
||||
if up[i]:
|
||||
s = 1.0
|
||||
elif dn[i]:
|
||||
s = 0.0
|
||||
state[i] = s
|
||||
|
||||
# size the long with causal vol-targeting (shrinks into vol spikes -> caps DD)
|
||||
pos = bl.vol_target(state, 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