backtest: deployable ETF book Sharpe +1.20, maxDD -5.4%, every year positive (beats 60/40)
Backtested the EXACT live pipeline on REAL ETFs (SPY/IEF/GLD/PDBC/DBMF+BTC), 2019-2026 multi-regime. Adaptive unlevered: Sharpe +1.20, ann +6.1%, maxDD -5.4%, positive EVERY year incl 2022. Beats 60/40 (+0.87, -21%) and equity (+0.85, -34%) with ~1/4 the drawdown. Better than the ~0.7 framing because real ETFs (DBMF/PDBC) >> roll-zeroed futures proxies + adaptive risk layer. Caveats: Sharpe partly low-vol-flattered (maxDD -5.4% + every-year-positive are the robust signals); 2019-26 favorable for premia; mild param-contamination -> realistic fwd ~0.8-1.0. Modest absolute return (~.1k/yr on 5k, very smooth); lever for $ is capital. The genuine deliverable of the search, validated on real instruments across regimes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
65
scripts/surfer/multistrat_etf_backtest.py
Normal file
65
scripts/surfer/multistrat_etf_backtest.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Backtest the EXACT deployable ETF book (the multistrat_paper live pipeline) on ~6y Yahoo history.
|
||||
|
||||
Real instruments (SPY/IEF/GLD/PDBC/DBMF + BTC-USD), same adaptive pipeline as the live harness
|
||||
(vol-norm -> edge-decay trust -> trust-weighted combo -> adaptive risk layer, unlevered). Window is
|
||||
limited by DBMF inception (~2019), so it spans COVID-2020, bear-2022, bulls-2021/2024 = multiple
|
||||
regimes. Per-year Sharpe = the regime/robustness check; compared to plain 60/40 (SPY/IEF).
|
||||
"""
|
||||
import datetime
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from multistrat_paper import INSTR, book_series # exact live pipeline # noqa: E402
|
||||
|
||||
|
||||
def yhist(sym, rng="10y"):
|
||||
res = json.loads(urllib.request.urlopen(urllib.request.Request(
|
||||
f"https://query1.finance.yahoo.com/v8/finance/chart/{sym}?interval=1d&range={rng}",
|
||||
headers={"User-Agent": "Mozilla/5.0"}), timeout=30).read())["chart"]["result"][0]
|
||||
ts = res["timestamp"]; ind = res["indicators"]
|
||||
adj = ind.get("adjclose", [{}])[0].get("adjclose") or ind["quote"][0]["close"]
|
||||
return {datetime.datetime.utcfromtimestamp(t).strftime("%Y-%m-%d"): float(c) for t, c in zip(ts, adj) if c is not None}
|
||||
|
||||
|
||||
def stats(r):
|
||||
r = r[np.isfinite(r) & (r != 0)]
|
||||
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 ann, vol, (ann / vol if vol > 0 else float("nan")), dd
|
||||
|
||||
|
||||
def main():
|
||||
data = {nm: yhist(sym) for sym, nm in INSTR}
|
||||
dates = sorted(set.intersection(*[set(d) for d in data.values()]))
|
||||
R = np.zeros((len(dates), len(INSTR)))
|
||||
for j, (_, nm) in enumerate(INSTR):
|
||||
s = np.array([data[nm][d] for d in dates]); R[1:, j] = s[1:] / s[:-1] - 1
|
||||
print(f"DEPLOYABLE ETF BOOK backtest — {len(dates)} days ({dates[0]}..{dates[-1]})")
|
||||
|
||||
book, w, L = book_series(R)
|
||||
a, v, sh, dd = stats(book)
|
||||
print(f" adaptive multi-strat (unlevered): Sharpe {sh:+.2f} ann {100*a:+.1f}% vol {100*v:.1f}% maxDD {100*dd:+.1f}%")
|
||||
# 60/40 benchmark over same window
|
||||
j_eq = [nm for _, nm in INSTR].index("equity"); j_bd = [nm for _, nm in INSTR].index("bond")
|
||||
r6040 = np.zeros(len(dates)); r6040[1:] = 0.6 * R[1:, j_eq] + 0.4 * R[1:, j_bd]
|
||||
a2, v2, sh2, dd2 = stats(r6040)
|
||||
print(f" 60/40 (SPY/IEF) benchmark: Sharpe {sh2:+.2f} ann {100*a2:+.1f}% vol {100*v2:.1f}% maxDD {100*dd2:+.1f}%")
|
||||
# equity buy-hold
|
||||
aeq, veq, sheq, ddeq = stats(np.concatenate([[0], R[1:, j_eq]]))
|
||||
print(f" equity buy-hold (SPY): Sharpe {sheq:+.2f} ann {100*aeq:+.1f}% maxDD {100*ddeq:+.1f}%")
|
||||
yr = np.array([int(d[:4]) for d in dates])
|
||||
print(" per-year Sharpe (adaptive book): " + " ".join(f"{y}:{stats(book[yr == y])[2]:+.1f}" for y in sorted(set(yr)) if (yr == y).sum() > 100))
|
||||
print(f" current target weights: " + " ".join(f"{nm}:{100*w[j]*L:.0f}%" for j, (_, nm) in enumerate(INSTR)))
|
||||
print("\n VERDICT: adaptive book Sharpe >= 60/40 AND lower maxDD across most years = the deployable book")
|
||||
print(" delivers on real ETFs. If ~60/40, the diversification+adaptive layer earns its keep modestly.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user