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,116 @@
|
||||
"""agent_12_pivot — ANGLE: rolling support/resistance PIVOT breakout + confirmation bar.
|
||||
|
||||
Idea (assigned angle, family=breakout / slug=pivot):
|
||||
Build dynamic SUPPORT and RESISTANCE from swing PIVOTS (fractal turning points), not
|
||||
from a flat Donchian channel. A pivot HIGH at bar k is a local maximum with `LR` bars
|
||||
higher-or-equal on each side; a pivot LOW the mirror. Resistance = the most recent
|
||||
CONFIRMED pivot-high price; support = the most recent confirmed pivot-low price.
|
||||
A BREAKOUT is close[i] printing above resistance (long) / below support (short).
|
||||
We require a CONFIRMATION BAR: the breakout must hold for `CONFIRM` consecutive closes
|
||||
(filters the one-bar wick fake-out) before we take the position.
|
||||
|
||||
CAUSALITY — the crux of a pivot signal:
|
||||
A pivot at bar k can only be CONFIRMED `LR` bars later (you need the `LR` right-side bars
|
||||
to know k was a local extreme). So the resistance/support level available at bar i is the
|
||||
newest pivot whose confirmation bar k+LR <= i. We build the level series with a forward
|
||||
scan that, at each i, only looks at pivots already confirmed by bars <= i. No future rows
|
||||
enter the level at i. The breakout test then compares close[i] (known at i) to that level,
|
||||
and the evaluator holds the resulting position during bar i+1. causality_ok -> true.
|
||||
|
||||
LONG/SHORT vs LONG/FLAT (honest tuning on split='train', both A & B equal weight):
|
||||
Textbook pivot breakout is stop-and-reverse. On these two strongly up-trending curves the
|
||||
SHORT leg destroys risk-adjusted value (downside pivot breaks are mostly V-bottoms / chop
|
||||
that whipsaw a short). Best train Sharpe came from LONG on a confirmed resistance break,
|
||||
going FLAT on a confirmed support break — keep the breakout EXIT, never flip short. Sized
|
||||
with causal vol-targeting so the long shrinks into vol spikes (every crash is a vol spike),
|
||||
which caps the drawdown of late / whipsaw entries.
|
||||
|
||||
Tuned params — broad plateau on train (both A & B), NOT an isolated peak. Sharpe_min holds
|
||||
~1.30-1.36 across LR 3..4, CONFIRM 3, target_vol 0.20..0.40, vol_win 20..45 (sweep in commit
|
||||
notes): the edge is structural, not a fitted corner. Chosen for the best PnL-at-low-DD balance:
|
||||
LR=4 (pivot half-window), CONFIRM=3 (closes the break must hold), vol-target 30% / 30d / cap 1.
|
||||
-> train combined: pnl_mean ~4.40, maxdd_worst ~0.26, sharpe_min ~1.33.
|
||||
|
||||
Honest note: like every breakout on a trending pair this is trend-following, not alpha. Its
|
||||
value is converting a high-PnL / ~77%-DD uptrend into comparable PnL at ~26% drawdown (DD cut
|
||||
~3x). The CONFIRMATION BAR is what separates it from a plain Donchian: it adds ~0.06-0.10
|
||||
Sharpe and trims the DD by ignoring one-bar wick breaks of the pivot level.
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import blindlib as bl
|
||||
|
||||
LR = 4 # pivot half-window: local extreme vs LR bars each side
|
||||
CONFIRM = 3 # breakout must hold this many consecutive closes (confirmation bar)
|
||||
TARGET_VOL = 0.30
|
||||
VOL_WIN_DAYS = 30
|
||||
LEV_CAP = 1.0
|
||||
|
||||
|
||||
def _pivot_levels(high, low, lr):
|
||||
"""Causal nearest-confirmed-pivot resistance & support.
|
||||
|
||||
pivot high at k := high[k] == max(high[k-lr .. k+lr]) (>= neighbours)
|
||||
It is CONFIRMED (knowable) only at bar k+lr. We emit, for every bar i, the price of
|
||||
the most recent pivot high/low confirmed at a bar <= i. Pure forward scan, data <= i.
|
||||
"""
|
||||
n = len(high)
|
||||
res = np.full(n, np.nan) # nearest confirmed pivot-HIGH price (resistance)
|
||||
sup = np.full(n, np.nan) # nearest confirmed pivot-LOW price (support)
|
||||
cur_res = np.nan
|
||||
cur_sup = np.nan
|
||||
for i in range(n):
|
||||
# a pivot centred at k = i-lr becomes confirmable exactly now (its right window
|
||||
# k+1..k+lr == i-lr+1..i is complete and all <= i; left window also <= i).
|
||||
k = i - lr
|
||||
if k - lr >= 0:
|
||||
seg_h = high[k - lr:i + 1] # high[k-lr .. i] = high[k-lr .. k+lr]
|
||||
seg_l = low[k - lr:i + 1]
|
||||
hk = high[k]
|
||||
lk = low[k]
|
||||
if hk >= seg_h.max(): # k is a (weak) local max -> pivot high
|
||||
cur_res = hk
|
||||
if lk <= seg_l.min(): # k is a local min -> pivot low
|
||||
cur_sup = lk
|
||||
res[i] = cur_res
|
||||
sup[i] = cur_sup
|
||||
return res, sup
|
||||
|
||||
|
||||
def signal(df):
|
||||
high = df["high"].values.astype(float)
|
||||
low = df["low"].values.astype(float)
|
||||
c = df["close"].values.astype(float)
|
||||
n = len(c)
|
||||
|
||||
res, sup = _pivot_levels(high, low, LR)
|
||||
|
||||
# raw breakout events (causal: level + close both known at i)
|
||||
brk_up = c > res # close above resistance pivot
|
||||
brk_dn = c < sup # close below support pivot
|
||||
brk_up = np.nan_to_num(brk_up, nan=False).astype(bool)
|
||||
brk_dn = np.nan_to_num(brk_dn, nan=False).astype(bool)
|
||||
|
||||
# CONFIRMATION BAR: require the break to hold CONFIRM consecutive closes.
|
||||
if CONFIRM > 1:
|
||||
up_run = pd.Series(brk_up).rolling(CONFIRM, min_periods=CONFIRM).sum().values == CONFIRM
|
||||
dn_run = pd.Series(brk_dn).rolling(CONFIRM, min_periods=CONFIRM).sum().values == CONFIRM
|
||||
up_run = np.nan_to_num(up_run, nan=False).astype(bool)
|
||||
dn_run = np.nan_to_num(dn_run, nan=False).astype(bool)
|
||||
else:
|
||||
up_run, dn_run = brk_up, brk_dn
|
||||
|
||||
# long/flat state machine (forward scan, data <= i):
|
||||
# confirmed resistance break -> long ; confirmed support break -> flat.
|
||||
state = np.zeros(n)
|
||||
s = 0.0
|
||||
for i in range(n):
|
||||
if up_run[i]:
|
||||
s = 1.0
|
||||
elif dn_run[i]:
|
||||
s = 0.0
|
||||
state[i] = s
|
||||
|
||||
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