"""Agent 19 — Vol-targeted long-only / risk-parity single asset (family=vol, slug=voltarget_lo). The angle (assigned): NO direction call. Hold the asset LONG at all times, but size the position by INVERSE realized volatility so the book runs at a roughly constant target volatility: exposure[i] = clip( target_vol / realized_vol[i] , 0, cap ). Why this anticipates anything at all, despite never predicting direction: realized vol is PERSISTENT (today's vol forecasts tomorrow's vol far better than today's return forecasts tomorrow's return). The big declines on these two curves are also the high- vol regimes — a crash is a vol spike. So scaling exposure DOWN when trailing vol is high mechanically pulls the book light right when the worst legs happen, and levers UP in the calm grind higher. The result on a structurally up-trending curve is a long-only book with most of buy&hold's upside but a much smaller drawdown (the risk-parity / "vol control" effect), at modest turnover (the weight only drifts with the vol forecast). CAUSAL: realized_vol[i] uses returns over a trailing window ending at i (rows <= i); the position is then shifted by the evaluator (held during bar i+1). No direction is derived from any future bar; no global fit. Verified by causality_ok (max_diff 0.0). Tuning (split='train' only, combined A&B). The free knobs are the trailing vol window, the target vol, and the leverage cap. * CAP is the single most important choice. Because both curves trend up hard, a high cap just re-levers into buy&hold and brings the drawdown right back. cap=1.0 (never more than fully invested) is what preserves the risk-parity de-risking benefit; with a vol-driven weight that almost always sits below 1.0 this is the whole point. * VOL_WIN is the vol-forecast horizon. A SLOW window (~120d) gives a stabler vol estimate, less whipsaw, lower turnover and the BEST risk-adjusted result here: sharpe_min climbs from ~0.85 (30d) to ~0.97 (120d) and the plateau (110..200d) is flat at sharpe 0.91..0.99 / DD ~0.42-0.44 -> 120 is a robust interior pick. * TARGET_VOL is a pure DD/PnL dial: it scales exposure up and down but (for a long- only inverse-vol book) leaves the Sharpe essentially flat (0.971 across 0.24..0.32). So it is chosen for the DD/PnL trade-off, not the Sharpe. Chosen cell, interior on every axis: TARGET_VOL = 0.28 # DD/PnL dial; Sharpe flat across 0.24..0.32 -> balanced cell VOL_WIN_D = 120 # slow, stable vol forecast; plateau 110..200d LEV_CAP = 1.0 # never lever past fully-invested -> keeps the DD-cut benefit -> train combined: pnl_mean ~2.93, maxdd_worst ~0.43, sharpe_min ~0.97. This is a DEFENSIVE long-only book, NOT alpha. Its honest value is the drawdown: ~0.43 vs ~0.77-0.79 buy&hold at comparable PnL. Because it never shorts, its Sharpe ceiling (~1.0) is set by the absence of any direction call -> it can avoid sizing into the big declines but cannot profit from them. That is the inherent limit of this angle. """ import numpy as np import blindlib as bl TARGET_VOL = 0.28 VOL_WIN_D = 120 LEV_CAP = 1.0 def signal(df): # direction = always long (+1), NO direction call. Sizing is pure inverse-vol. direction = np.ones(len(df)) pos = bl.vol_target(direction, df, target_vol=TARGET_VOL, vol_win_days=VOL_WIN_D, leverage_cap=LEV_CAP) # long-only risk-parity: clip to [0, cap] (no shorts by construction) return np.clip(np.nan_to_num(pos, nan=0.0), 0.0, LEV_CAP)