#!/usr/bin/env python3 """Clean premium-harvest: risk-parity across ~5 broad ASSET CLASSES (not 39 instruments). Equity (ES), bonds (ZN), gold (GC), energy (CL), crypto (BTC) — genuinely uncorrelated premia. Risk-parity (inv-vol) + vol-target + optional trend-overlay, vs 60/40 and buy-and-hold equity. Runs 4-asset (2010-2026, longer) and 5-asset (+BTC, 2019-2026). The fair test of whether the risk system adds value over plain 60/40 when applied to the RIGHT universe. Futures = roll-zeroed price returns (understates commodity carry); BTC = clean close-close. """ import math import os import sys import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from signal_sweep import load_panel, build_returns # noqa: E402 import pit_sweep # noqa: E402 TVOL = 0.10 def metrics(r): r = np.asarray(r); r = r[np.isfinite(r)] if len(r) < 50 or r.std() == 0: return dict(sr=float("nan"), ann=float("nan"), dd=float("nan"), cal=float("nan")) ann = r.mean() * 252; vol = r.std() * math.sqrt(252) eq = np.cumprod(1 + r); dd = float((eq / np.maximum.accumulate(eq) - 1).min()) return dict(sr=ann / vol, ann=ann, dd=dd, cal=ann / (abs(dd) + 1e-9)) def build(): roots, fdays, close, op, inst = load_panel()[:5] lc, R, ov, intr = build_returns(close, op, inst) fcol = {r: roots.index(r) for r in ["ES", "ZN", "GC", "CL"] if r in roots} fut = {r: {int(fdays[t]): R[t, c] for t in range(len(fdays))} for r, c in fcol.items()} syms, cdays, cc, cqv, cf = pit_sweep.load() j = syms.index("BTCUSDT"); lcb = np.log(cc[:, j]) btc = {int(cdays[t]): (lcb[t] - lcb[t - 1]) for t in range(1, len(cdays)) if np.isfinite(lcb[t]) and np.isfinite(lcb[t - 1])} return fut, btc def run(assets, rets, label): days = np.array(sorted(set.intersection(*[set(rets[a]) for a in assets]))) M = np.array([[rets[a][int(d)] for a in assets] for d in days]) # [T, K] M = np.where(np.isfinite(M), M, 0.0) T, K = M.shape year = (1970 + days / 365.25).astype(int) vol = np.full((T, K), np.nan) for t in range(63, T): vol[t] = M[t - 63:t].std(0) iv = 1.0 / np.where(vol > 0, vol, np.nan) trend = np.full((T, K), np.nan) L = 126 for t in range(L, T): trend[t] = M[t - L:t].sum(0) def book(w): w = np.nan_to_num(w); g = np.abs(w).sum(1, keepdims=True); g[g == 0] = 1 w = w / g return np.sum(w[:-1] * M[1:], axis=1) def vt(r): out = np.zeros_like(r) for t in range(63, len(r)): rv = np.std(r[t - 63:t]) * math.sqrt(252) out[t] = r[t] * min(3.0, TVOL / (rv + 1e-9)) return out rp = book(iv) rp_t = book(np.where(trend > 0, iv, 0.0)) es_i = assets.index("ES") if "ES" in assets else 0 bench_eq = M[1:, es_i] w60 = np.zeros((T, K)); w60[:, es_i] = 0.6 if "ZN" in assets: w60[:, assets.index("ZN")] = 0.4 print(f"\n===== {label}: {assets} ({T} days) =====") print(f"{'strategy':>24} {'Sharpe':>7} {'ann%':>6} {'maxDD%':>7} {'Calmar':>7}") for nm, r in [("equity-only (ES)", bench_eq), ("60/40", book(w60)), ("risk-parity", rp), ("RP + vol-target", vt(rp)), ("RP + trend + vol-target", vt(rp_t))]: m = metrics(r) print(f"{nm:>24} {m['sr']:>+7.2f} {100*m['ann']:>+6.1f} {100*m['dd']:>+7.1f} {m['cal']:>7.2f}") best = vt(rp) print(" per-year Sharpe (RP+vol-target): " + " ".join( f"{y}:{metrics(best[year[1:]==y])['sr']:+.1f}" for y in sorted(set(year)) if (year[1:] == y).sum() > 100)) def main(): fut, btc = build() run(["ES", "ZN", "GC", "CL"], {**fut}, "4-asset (2010-2026)") run(["ES", "ZN", "GC", "CL", "BTC"], {**fut, "BTC": btc}, "5-asset +crypto (2019-2026)") print("\nVERDICT: if RP/vol-target beats 60/40 on Sharpe AND maxDD = risk system adds real value on the") print("right universe (the engine's true job). If 60/40 still wins, the deployable answer is just simple harvest.") if __name__ == "__main__": main()