Files
Adriano Dal Pastro 1afb1014c9 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>
2026-06-21 07:05:04 +00:00

73 lines
3.3 KiB
Python

"""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)