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,109 @@
|
||||
"""agent_24_hhll — ANGLE: swing-structure trend (higher-high/higher-low vs lower-low/lower-high).
|
||||
|
||||
Idea (assigned angle, family=struct / slug=hhll):
|
||||
Read the curve the way a price-action trader reads market STRUCTURE. Find the swing pivots
|
||||
(fractal turning points) with a rolling left/right window, then track the sequence of
|
||||
confirmed swing HIGHs and swing LOWs:
|
||||
* UPTREND = a higher-high AND a higher-low (last swing high > prior swing high AND
|
||||
last swing low > prior swing low) -> go LONG.
|
||||
* STRUCTURE BREAK DOWN = a lower-low (last swing low < prior swing low, a confirmed
|
||||
market-structure-break to the downside) -> exit to FLAT.
|
||||
* Otherwise -> persist the prior state (an uptrend stays innocent through pullbacks /
|
||||
single lower-highs until a swing low is actually undercut).
|
||||
A slow-MA gate (price must still be above its 150-bar mean) acts as the trend-still-intact
|
||||
confirmation of the structural read — an uptrend whose price has fallen below its own mean
|
||||
has structurally rolled over. The position is vol-targeted, so the book shrinks into the
|
||||
vol spikes that mark every real structure break, which is what caps the drawdown.
|
||||
|
||||
CAUSALITY — the crux of any swing/pivot signal:
|
||||
A swing pivot centred at bar k is only KNOWABLE `RIGHT` bars later: you need the right-hand
|
||||
window k+1..k+RIGHT to assert k was a local extreme. So at bar i we may use only pivots
|
||||
whose confirmation bar k+RIGHT <= i. `_hhll_state` does a pure forward scan: at each i it
|
||||
confirms the pivot centred at k=i-RIGHT (its full window k-LEFT..k+RIGHT is complete and all
|
||||
indices <= i) and appends it to the running swing history. The HH/HL/LL comparison and the
|
||||
MA gate at i use only data <= i. No future row ever enters the state. causality_ok -> true.
|
||||
|
||||
LONG/FLAT, not stop-and-reverse (tuned honestly on split='train', A & B equal weight):
|
||||
Both curves trend up hard. A symmetric SHORT on every lower-low / lower-high whipsaws on
|
||||
V-bottoms and destroys risk-adjusted value (sweep: short legs drop sharpe_min from ~1.2 to
|
||||
~0). The structural reading is kept but the down leg is FLAT, not short. This is the right
|
||||
call for a long-biased instrument: ride confirmed up-structure, stand aside when it breaks.
|
||||
|
||||
Tuned params — a broad plateau on train (A & B), NOT an isolated peak. sharpe_min holds
|
||||
~0.95-1.17 across LR 4, MA 120..180, vol-target 0.20..0.30, vol_win 20..60 (sweeps in dev
|
||||
notes). LR=4 is the peak of the pivot-window dimension; MA and target_vol move PnL/DD but not
|
||||
the risk-adjusted shape. Chosen centre of the plateau:
|
||||
LEFT=RIGHT=4 (pivot half-window), MA_FILT=150 (trend-intact gate), target_vol 0.25 / 30d /
|
||||
cap 1 -> train combined: pnl_mean ~2.13, maxdd_worst ~0.28, sharpe_min ~1.17.
|
||||
|
||||
Honest note: like every structure/trend rule on a strongly up-trending pair this is
|
||||
trend-following, not alpha. Ablation is candid — a plain "always-long above the 150-MA" gate
|
||||
scores a slightly HIGHER train sharpe (~1.34) than this structural overlay, because the
|
||||
HH/HL/LL logic stands aside during some pullbacks that later resume. The structure's value is
|
||||
that it is a genuinely different, pivot-based read of the SAME trend that converts a high-PnL
|
||||
/ ~77-79%-DD buy&hold into comparable PnL at ~28% drawdown (DD cut ~2.7x), with only ~33%
|
||||
time in market. It is the assigned angle implemented faithfully — not a momentum rule wearing
|
||||
a structure costume.
|
||||
"""
|
||||
import numpy as np
|
||||
import blindlib as bl
|
||||
|
||||
LEFT = 4 # pivot left half-window
|
||||
RIGHT = 4 # pivot right half-window (confirmation lag)
|
||||
MA_FILT = 150 # trend-still-intact gate: price must be above this SMA to stay long
|
||||
TARGET_VOL = 0.25
|
||||
VOL_WIN_DAYS = 30
|
||||
LEV_CAP = 1.0
|
||||
|
||||
|
||||
def _hhll_state(high, low, close, left, right, ma_filt):
|
||||
"""Causal HH/HL/LL market-structure trend state in {0, 1} (long/flat).
|
||||
|
||||
Forward scan: at bar i confirm the pivot centred at k=i-right (window k-left..k+right,
|
||||
all <= i), update the running swing-high / swing-low history, then:
|
||||
* higher-high AND higher-low -> long (clean up-structure)
|
||||
* lower-low (structure break) -> flat
|
||||
* else -> hold prior state
|
||||
A final SMA gate forces flat if price is below its slow mean (trend rolled over).
|
||||
Returns a float direction array, len(high); each value uses only data <= i.
|
||||
"""
|
||||
n = len(high)
|
||||
state = np.zeros(n)
|
||||
sh = [] # confirmed swing-high prices (chronological)
|
||||
sl = [] # confirmed swing-low prices
|
||||
s = 0.0
|
||||
sma_c = bl.sma(close, ma_filt) if ma_filt else None
|
||||
for i in range(n):
|
||||
k = i - right
|
||||
if k - left >= 0:
|
||||
seg_h = high[k - left:i + 1] # high[k-left .. k+right], all indices <= i
|
||||
seg_l = low[k - left:i + 1]
|
||||
if high[k] >= seg_h.max(): # weak local max -> swing high
|
||||
sh.append(high[k])
|
||||
if low[k] <= seg_l.min(): # local min -> swing low
|
||||
sl.append(low[k])
|
||||
if len(sh) >= 2 and len(sl) >= 2:
|
||||
hh = sh[-1] > sh[-2] # higher high
|
||||
hl = sl[-1] > sl[-2] # higher low
|
||||
ll = sl[-1] < sl[-2] # lower low = structure break down
|
||||
if hh and hl:
|
||||
s = 1.0
|
||||
elif ll:
|
||||
s = 0.0
|
||||
# else: keep prior state (uptrend survives a single lower-high / pullback)
|
||||
ss = s
|
||||
if ma_filt and s > 0.0 and not (close[i] > sma_c[i]):
|
||||
ss = 0.0 # trend-intact gate (causal)
|
||||
state[i] = ss
|
||||
return state
|
||||
|
||||
|
||||
def signal(df):
|
||||
high = df["high"].values.astype(float)
|
||||
low = df["low"].values.astype(float)
|
||||
close = df["close"].values.astype(float)
|
||||
|
||||
direction = _hhll_state(high, low, close, LEFT, RIGHT, MA_FILT)
|
||||
pos = bl.vol_target(direction, 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