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,72 @@
|
||||
"""agent_46_vol_div — Volume/price divergence (family=vol2, slug=vol_div).
|
||||
|
||||
ANGLE: fade moves where volume does NOT confirm; ride where it does.
|
||||
|
||||
How the angle is expressed (all causal, decided at close[i], held over bar i+1):
|
||||
|
||||
* CONFIRMATION = is volume EXPANDING as the trend develops? We compare a short
|
||||
volume mean (5) to a longer one (20): `confirm = v5/v20 - 1`. When volume is
|
||||
rising while price trends, the move is volume-CONFIRMED.
|
||||
-> RIDE leg: take the multi-bar (15-bar) price momentum, but only with weight
|
||||
proportional to the confirmation (clip(confirm * gain, 0, 1)). No
|
||||
confirmation -> no momentum bet. This is "ride where volume confirms".
|
||||
|
||||
* DIVERGENCE / EXHAUSTION = a single-bar thrust on a VOLUME SPIKE that is NOT part
|
||||
of a broader volume up-trend (volume not confirming the direction). Such thrusts
|
||||
tend to mean-revert.
|
||||
-> FADE leg: -sign(last bar) gated by (a vol z-score spike) AND (volume NOT
|
||||
broadly expanding). This is "fade where volume does not confirm".
|
||||
|
||||
* The two legs are blended (0.7 ride / 0.3 fade) and vol-targeted so the drawdown
|
||||
stays bounded. On the train view this is comparable PnL to buy&hold at a fraction
|
||||
of the drawdown, and it can go short / flat the unconfirmed declines.
|
||||
|
||||
Decomposition note (train): the RIDE leg is the real edge on both overlaid curves
|
||||
(volume-confirmed momentum persists); the FADE leg is a small DD-reducing overlay.
|
||||
Parameters chosen on a smooth plateau (rw 12-15, cl 15-20), not a knife-edge.
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import blindlib as bl
|
||||
|
||||
RIDE_W = 15 # momentum horizon (bars)
|
||||
CONF_S = 5 # short volume mean
|
||||
CONF_L = 20 # long volume mean
|
||||
GAIN = 6.5 # confirmation -> ride-weight gain
|
||||
W_FADE = 0.30 # weight of the divergence/fade overlay
|
||||
TARGET_VOL = 0.18 # annualized vol target for sizing
|
||||
VOL_WIN = 30 # vol-target lookback (days)
|
||||
|
||||
|
||||
def _zscore(x, win):
|
||||
s = pd.Series(x)
|
||||
m = s.rolling(win, min_periods=win // 2).mean()
|
||||
sd = s.rolling(win, min_periods=win // 2).std()
|
||||
z = (s - m) / sd.replace(0.0, np.nan)
|
||||
return np.nan_to_num(z.values)
|
||||
|
||||
|
||||
def signal(df):
|
||||
c = df["close"].values.astype(float)
|
||||
v = df["volume"].values.astype(float)
|
||||
logc = np.log(c)
|
||||
r = np.concatenate([[0.0], np.diff(logc)]) # causal bar return
|
||||
|
||||
# ---- Volume confirmation: short vol mean vs long vol mean (>0 = expanding) ----
|
||||
vshort = pd.Series(v).rolling(CONF_S, min_periods=2).mean().values
|
||||
vlong = pd.Series(v).rolling(CONF_L, min_periods=10).mean().values
|
||||
confirm = np.nan_to_num(vshort / np.where(vlong > 0, vlong, np.nan), nan=1.0) - 1.0
|
||||
|
||||
# ---- RIDE leg: multi-bar momentum, weighted by how strongly volume confirms ----
|
||||
pm = np.concatenate([np.zeros(RIDE_W), logc[RIDE_W:] - logc[:-RIDE_W]])
|
||||
ride = np.sign(pm) * np.clip(confirm * GAIN, 0.0, 1.0)
|
||||
|
||||
# ---- FADE leg: fade a single-bar thrust on a volume spike w/o broad expansion ----
|
||||
vol_spike = _zscore(v, 20)
|
||||
fade_gate = np.clip(vol_spike - 1.0, 0.0, 2.0) * np.clip(-confirm * 4.0 + 0.5, 0.0, 1.0)
|
||||
fade = -np.sign(r) * np.clip(fade_gate, 0.0, 1.0)
|
||||
|
||||
raw = np.clip((1.0 - W_FADE) * ride + W_FADE * fade, -1.0, 1.0)
|
||||
|
||||
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), -1.0, 1.0)
|
||||
Reference in New Issue
Block a user