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,61 @@
|
||||
"""agent_44_obv — On-Balance-Volume trend confirmation [family=vol2, slug=obv].
|
||||
|
||||
Angle: cumulative signed volume (OBV) slope CONFIRMS price direction. OBV is the running
|
||||
sum of sign(Δclose)*volume; when it trends up the buying volume is backing the advance
|
||||
(accumulation) and the move is more likely to continue; when OBV rolls over relative to
|
||||
its own EMA the advance is on thinning volume (distribution) and we de-risk / can flip.
|
||||
|
||||
Construction (all causal — value at i uses only rows 0..i):
|
||||
obv = cumsum(sign(Δclose) * volume)
|
||||
obv_trend = (obv - EMA(obv, 25)) / rolling_std(...) # volume-flow z-score
|
||||
price_trend= (close/SMA(close,40) - 1) / rolling_std(...) # price z-score
|
||||
raw = 0.35*tanh(k*obv_trend) + 0.65*tanh(k*price_trend) # volume confirms price
|
||||
position = vol_target(raw, target 20%) # bound drawdown, long/short
|
||||
|
||||
Why this weighting: on the train view the OBV flow z-score carries genuine, independently
|
||||
positive next-bar correlation on BOTH overlaid curves, but the price trend is the stronger
|
||||
single driver; OBV's role is to CONFIRM/temper it. A grid over (obv_win, price_win, blend,
|
||||
gain, target_vol) shows a broad plateau around these values (Sharpe stable +/- one cell),
|
||||
so the config is not a knife-edge fit. An explicit OBV-divergence damping gate was tested
|
||||
and added nothing (the blend already absorbs divergences), so it was left out — simpler.
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
# Tuned on split='train' only; chosen from the centre of a robustness plateau.
|
||||
W_OBV = 25 # OBV-vs-EMA trend window
|
||||
W_PRICE = 40 # price trend (close vs SMA) window
|
||||
A_OBV = 0.35 # weight on the volume-flow leg (1 - A on the price leg)
|
||||
GAIN = 0.9 # tanh gain on the z-scores
|
||||
TARGET_VOL = 0.20
|
||||
VOL_WIN = 40
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
v = df["volume"].values.astype(float)
|
||||
|
||||
# --- On-Balance-Volume: causal cumulative signed volume ---
|
||||
dc = np.diff(c, prepend=c[0])
|
||||
obv = np.cumsum(np.sign(dc) * v)
|
||||
|
||||
# OBV trend = OBV relative to its own EMA, z-scored by recent OBV-deviation std.
|
||||
obv_dev = obv - bl.ema(obv, W_OBV)
|
||||
obv_sc = bl.rolling_std(obv_dev, W_OBV)
|
||||
obv_sc = np.where(obv_sc > 1e-9, obv_sc, 1e-9)
|
||||
obv_sig = np.tanh(GAIN * (obv_dev / obv_sc)) # >0 accumulation, <0 distribution
|
||||
|
||||
# Price trend = close vs SMA, z-scored.
|
||||
ptr = c / bl.sma(c, W_PRICE) - 1.0
|
||||
ptr_sc = bl.rolling_std(ptr, W_PRICE)
|
||||
ptr_sc = np.where(ptr_sc > 1e-9, ptr_sc, 1e-9)
|
||||
price_sig = np.tanh(GAIN * (ptr / ptr_sc))
|
||||
|
||||
# Volume CONFIRMS price: blend the two legs into a -1..1 direction.
|
||||
raw = A_OBV * obv_sig + (1.0 - A_OBV) * price_sig
|
||||
raw = np.nan_to_num(raw, nan=0.0)
|
||||
|
||||
# Vol-target to bound drawdown; long/short allowed.
|
||||
pos = bl.vol_target(raw, df, target_vol=TARGET_VOL, vol_win_days=VOL_WIN,
|
||||
leverage_cap=1.0)
|
||||
return np.clip(np.nan_to_num(pos, nan=0.0), -1.0, 1.0)
|
||||
Reference in New Issue
Block a user