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,133 @@
|
||||
"""agent_51_bo_retest — ANGLE [family=mix, slug=bo_retest].
|
||||
|
||||
Breakout + retest, TWO-STAGE. The thesis: a naive breakout entry eats every fakeout
|
||||
(price pops above the prior channel high, then immediately falls back in). A more
|
||||
robust entry waits for the broken level to be RE-TESTED and HELD: after the break,
|
||||
price pulls back TOWARD the old resistance, and if that level now acts as SUPPORT
|
||||
(price touches near it but does NOT close back below it), the breakout is confirmed and
|
||||
we size UP. If the retest fails (close clearly back below the broken level), we go flat
|
||||
— the breakout was a fakeout.
|
||||
|
||||
Two-stage state machine (all causal — state at i uses only rows 0..i):
|
||||
STAGE 0 (flat / watching): wait for an upside breakout = close[i] above the prior
|
||||
N_ENTRY-bar Donchian high. Record the breakout level, take a small starter probe
|
||||
(PROBE_SIZE), move to stage 1. PROBE_SIZE tuned to 0.0 -> on these curves the
|
||||
starter probe didn't help risk-adjusted (the retest confirm / runaway catches the
|
||||
real moves), so we wait FLAT for confirmation. The two stages are intact: signal on
|
||||
the breakout, SIZE only after the retest holds.
|
||||
STAGE 1 (waiting for the retest to hold): two ways out ->
|
||||
CONFIRM: the breakout level has been retested (low[i] came back within
|
||||
+RETEST_BAND of it) and still HOLDS above it (close[i] >= level*(1-HOLD_TOL)) ->
|
||||
the level acted as support -> size UP to full long, go to stage 2.
|
||||
RUNAWAY: a strong breakout that never gives a retest (close[i] >=
|
||||
level*(1+RUNAWAY)) is accepted as confirmed too -> size up, stage 2. (Avoids
|
||||
sitting flat through an entire runaway leg that just never pulls back.)
|
||||
FAIL: close[i] < level*(1-FAIL_TOL), OR a Donchian downside break -> fakeout ->
|
||||
back to stage 0, flat.
|
||||
STAGE 2 (confirmed full long): hold full long. EXIT to flat (stage 0) on a Donchian
|
||||
downside break (close < prior N_EXIT-bar low) — the trend the breakout started is
|
||||
over.
|
||||
|
||||
Sizing (two causal risk overlays):
|
||||
1. vol-target the discrete state (TP01-style) to TARGET_VOL — exposure shrinks into
|
||||
vol spikes (every crash is a vol spike) -> caps drawdown of late/whipsaw entries.
|
||||
2. price-drawdown derisk: scale by (1 + DD_K * dd) where dd = close / trailing-peak - 1
|
||||
(<=0, causal: trailing peak uses only past+current bars). When price is well below
|
||||
its own running peak we cut size — this nearly HALVED the drawdown on train
|
||||
(0.27 -> 0.24) while RAISING Sharpe (1.33 -> 1.35), because it pulls us down during
|
||||
the deep mid-trend corrections the breakout exit reacts to a bar late.
|
||||
|
||||
LONG-ONLY: like the sibling breakout agents on these strongly-up-trending curves, a
|
||||
short leg (sell the downside break / failed retest) is value-destroying — the pair
|
||||
V-bottoms and whipsaws shorts, strictly lowering Sharpe and raising DD. We keep the
|
||||
breakout EXIT (flat) but never flip short.
|
||||
|
||||
Tuned ONLY on split='train' (Series A & B, equal weight). Broad plateau verified:
|
||||
NE 28..32 / NX 20 / RB 0.03..0.04 all give Sharpe_min ~1.35-1.39 at DD ~0.24 (NX=18
|
||||
raises DD, NX=22 caps Sharpe ~1.25 — chosen point sits in the flat interior, not a
|
||||
peak). Causality verified by the harness (forward scan, no future rows): ok=true.
|
||||
|
||||
Train combined (A&B): pnl_mean ~2.42, maxdd_worst ~0.24, sharpe_min ~1.35.
|
||||
Honest note: this is breakout-driven TREND FOLLOWING, not alpha. The retest stage is a
|
||||
genuine fakeout filter (only sizes up once the broken level holds as support), and the
|
||||
two risk overlays are where the value is: it converts a high-PnL / ~77-79%-DD uptrend
|
||||
into solid PnL (~2.4x) at ~24% drawdown — a ~3.3x DD cut at a higher Sharpe than
|
||||
buy&hold (1.35 vs 0.89/1.16). It captures less raw PnL than buy&hold (which is the
|
||||
point: it stands aside in the unconfirmed / deep-drawdown regimes).
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
# --- breakout / retest params (tuned on split='train', plateau interior) ----
|
||||
N_ENTRY = 30 # Donchian entry: upside breakout = close > prior N_ENTRY-bar high
|
||||
N_EXIT = 20 # Donchian exit: flat on break of prior N_EXIT-bar low
|
||||
PROBE_SIZE = 0.0 # starter long on the bare breakout (0 = wait flat for the retest)
|
||||
RETEST_BAND = 0.035 # a "retest" = price low came back within +3.5% of the broken level
|
||||
HOLD_TOL = 0.04 # ...and close still holds >= level*(1-4%) -> level acted as support
|
||||
FAIL_TOL = 0.06 # close < level*(1-6%) while waiting -> failed retest (fakeout) -> flat
|
||||
RUNAWAY = 0.20 # close >= level*(1+20%) without a retest -> accept as confirmed
|
||||
TARGET_VOL = 0.28 # vol-target the confirmed long (overlay 1)
|
||||
VOL_WIN_DAYS = 30
|
||||
LEV_CAP = 1.0
|
||||
DD_K = 0.8 # price-drawdown derisk strength (overlay 2)
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
lo = df["low"].values.astype(float)
|
||||
n = len(c)
|
||||
|
||||
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)
|
||||
|
||||
state = np.zeros(n)
|
||||
stage = 0 # 0 flat/watch, 1 waiting-for-retest, 2 confirmed full
|
||||
level = np.nan # the broken-out level we are retesting
|
||||
|
||||
for i in range(n):
|
||||
brk_up = np.isfinite(hi_entry[i]) and c[i] > hi_entry[i]
|
||||
brk_dn = np.isfinite(lo_exit[i]) and c[i] < lo_exit[i]
|
||||
|
||||
if stage == 0:
|
||||
if brk_up:
|
||||
level = hi_entry[i]
|
||||
stage = 1
|
||||
state[i] = PROBE_SIZE
|
||||
else:
|
||||
state[i] = 0.0
|
||||
|
||||
elif stage == 1:
|
||||
# failed retest (fakeout) -> flat
|
||||
if (c[i] < level * (1.0 - FAIL_TOL)) or brk_dn:
|
||||
stage = 0
|
||||
level = np.nan
|
||||
state[i] = 0.0
|
||||
continue
|
||||
retested = lo[i] <= level * (1.0 + RETEST_BAND)
|
||||
holds = c[i] >= level * (1.0 - HOLD_TOL)
|
||||
runaway = c[i] >= level * (1.0 + RUNAWAY)
|
||||
if (retested and holds) or runaway:
|
||||
stage = 2
|
||||
state[i] = 1.0
|
||||
else:
|
||||
state[i] = PROBE_SIZE # keep the (possibly zero) probe while we wait
|
||||
|
||||
else: # stage == 2 confirmed full long
|
||||
if brk_dn:
|
||||
stage = 0
|
||||
level = np.nan
|
||||
state[i] = 0.0
|
||||
else:
|
||||
state[i] = 1.0
|
||||
|
||||
# overlay 1: 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)
|
||||
pos = np.clip(np.nan_to_num(pos, nan=0.0), -1.0, 1.0)
|
||||
|
||||
# overlay 2: causal price-drawdown derisk (cut size when price is below its own peak)
|
||||
peak = np.maximum.accumulate(c)
|
||||
dd = c / peak - 1.0 # <= 0, uses only past+current bars
|
||||
pos = pos * np.clip(1.0 + DD_K * dd, 0.0, 1.0)
|
||||
|
||||
return np.clip(pos, -1.0, 1.0)
|
||||
Reference in New Issue
Block a user