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>
91 lines
4.0 KiB
Python
91 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""The strategy class we never tested: HARVEST structural premia with the risk system, not predict.
|
|
|
|
Diversified multi-asset futures (equity/rates/metals/energy/ags/FX). Build a ladder:
|
|
equal-weight -> inverse-vol (risk-parity) -> + vol-target -> + trend-overlay (long-flat by 252d trend,
|
|
the crisis-alpha de-risk). Measure RISK-ADJUSTED (Sharpe, ann-vol, max-DD, Calmar, per-year) vs
|
|
benchmarks (equal-weight, equity-only, 60/40). No prediction/alpha needed — just disciplined
|
|
harvesting + risk control (what the engine is actually good at). Roll-zeroed returns (understates
|
|
carry; fine for comparing the risk-management strategies).
|
|
"""
|
|
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
|
|
|
|
TARGET_VOL = 0.10 # 10% annualized portfolio vol
|
|
|
|
|
|
def metrics(r):
|
|
r = r[np.isfinite(r)]
|
|
if len(r) < 50 or r.std() == 0:
|
|
return dict(sr=float("nan"), ann=float("nan"), vol=float("nan"), dd=float("nan"), calmar=float("nan"))
|
|
ann = r.mean() * 252; vol = r.std() * math.sqrt(252); sr = ann / vol
|
|
eq = np.cumprod(1 + r); dd = float((eq / np.maximum.accumulate(eq) - 1).min())
|
|
return dict(sr=sr, ann=ann, vol=vol, dd=dd, calmar=ann / (abs(dd) + 1e-9))
|
|
|
|
|
|
def main():
|
|
roots, days, close, op, inst, weekday = load_panel()
|
|
lc, R, ov, intr = build_returns(close, op, inst)
|
|
T, N = close.shape
|
|
year = (1970 + days / 365.25).astype(int)
|
|
vol63 = np.full_like(lc, np.nan)
|
|
for t in range(63, T):
|
|
vol63[t] = np.nanstd(R[t - 63:t], axis=0)
|
|
iv = 1.0 / np.where(vol63 > 0, vol63, np.nan)
|
|
avail = np.isfinite(close) & np.isfinite(vol63) & (vol63 > 0)
|
|
|
|
def port(weights):
|
|
w = np.nan_to_num(weights)
|
|
w = np.where(avail, w, 0.0)
|
|
g = np.sum(np.abs(w), axis=1, keepdims=True); g[g == 0] = 1
|
|
w = w / g # unit gross (fully invested)
|
|
return np.sum(w[:-1] * R[1:], axis=1)
|
|
|
|
def voltarget(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, TARGET_VOL / (rv + 1e-9)) # scale to target vol, cap 3x leverage
|
|
return out
|
|
|
|
# weight schemes
|
|
eqw = np.where(avail, 1.0, 0.0)
|
|
rp = np.where(avail, iv, 0.0) # inverse-vol risk parity
|
|
trend = (np.full_like(lc, np.nan)); trend[252:] = lc[252:] - lc[:-252]
|
|
rp_trend = np.where(avail & (trend > 0), iv, 0.0) # trend-overlay: hold only uptrending assets
|
|
|
|
books = {
|
|
"equal-weight": port(eqw),
|
|
"risk-parity (inv-vol)": port(rp),
|
|
"risk-parity + vol-target": voltarget(port(rp)),
|
|
"RP + trend + vol-target": voltarget(port(rp_trend)),
|
|
}
|
|
# benchmarks
|
|
es = roots.index("ES") if "ES" in roots else 0
|
|
bench = {"equity-only (ES)": R[1:, es]}
|
|
if "ZN" in roots:
|
|
zn = roots.index("ZN")
|
|
w6040 = np.zeros((T, N)); w6040[:, es] = 0.6; w6040[:, zn] = 0.4
|
|
bench["60/40 (ES/ZN)"] = port(w6040)
|
|
|
|
print(f"\n===== PREMIUM HARVEST (diversified futures, {N} assets, {T} days, target {int(TARGET_VOL*100)}% vol) =====")
|
|
print(f"{'strategy':>26} {'Sharpe':>7} {'ann%':>6} {'vol%':>6} {'maxDD%':>7} {'Calmar':>7}")
|
|
for nm, r in {**bench, **books}.items():
|
|
m = metrics(r)
|
|
print(f"{nm:>26} {m['sr']:>+7.2f} {100*m['ann']:>+6.1f} {100*m['vol']:>5.1f} {100*m['dd']:>+7.1f} {m['calmar']:>7.2f}")
|
|
print("\nper-year Sharpe (RP + trend + vol-target):")
|
|
rtv = books["RP + trend + vol-target"]
|
|
print(" " + " ".join(f"{y}:{metrics(rtv[year[1:]==y])['sr']:+.1f}" for y in range(2010, 2027) if (year[1:] == y).sum() > 100))
|
|
print("\nVERDICT: if risk-managed harvest beats benchmarks on Sharpe/Calmar (esp lower maxDD) = the engine's")
|
|
print("real job (risk management of real premia) delivers, no alpha needed. Modest but robust = the honest win.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|