The strategy class we never tried: harvest structural premia + risk system, not predict. Diversified futures + risk-parity/vol-target/trend; clean 5-asset-class version (ES/ZN/GC/CL/BTC). Result: plain 60/40 (Sharpe +0.72, 2010-2026) BEATS risk-parity+vol-target (+0.46) and RP+trend (+0.33, trend hurts); 5-asset+crypto RP only ties 60/40 and loses to buy-hold equity. Meta-pattern now complete in BOTH games: simple beats/equals sophisticated in prediction AND harvesting. Constructive deliverable: a simple premium harvest (60/40 / risk-parity) IS a real deployable robust strategy (~0.5-0.72 Sharpe, low DD, no prediction, minimal complexity). The engine's sophistication was never the return-generator -- deployable path is light (harvest + risk overlay), engine's value is infra/discipline/product. (Also: CAISO intraday gate blocked by OASIS plumbing.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
99 lines
4.0 KiB
Python
99 lines
4.0 KiB
Python
#!/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()
|