#!/usr/bin/env python3 """Growth-discipline multi-premia backtest: high-equity (QQQ) core + trend crisis-alpha sleeve + diversified book, mechanically rebalanced, optional cheap-futures leverage. vs QQQ / book / 70-30. Also tests adding VRP (PUTW put-write) as a sleeve. Honest: execution-alpha (behavior gap, tax-loss harvest) is real but counterfactual/account-dependent -> NOT in the backtest; noted as +1-3%/yr on top. python3 growth_discipline_backtest.py [--fin 0.03] """ 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 book_series # noqa: E402 def yh(s): try: r = json.loads(urllib.request.urlopen(urllib.request.Request( f"https://query1.finance.yahoo.com/v8/finance/chart/{s}?interval=1d&range=25y", headers={"User-Agent": "Mozilla/5.0"}), timeout=30).read())["chart"]["result"][0] a = r["indicators"].get("adjclose", [{}])[0].get("adjclose") or r["indicators"]["quote"][0]["close"] return {datetime.datetime.utcfromtimestamp(t).strftime("%Y-%m-%d"): float(c) for t, c in zip(r["timestamp"], a) if c} except Exception: return {} FIN = float(sys.argv[sys.argv.index("--fin") + 1]) if "--fin" in sys.argv else 0.03 def build(syms): data = {s: yh(s) for s in syms} common = sorted(set.intersection(*[set(data[s]) for s in syms])) yrs = (datetime.date.fromisoformat(common[-1]) - datetime.date.fromisoformat(common[0])).days / 365.25 T = len(common) P = {s: np.array([data[s][d] for d in common]) for s in syms} R = {s: np.concatenate([[0], P[s][1:] / P[s][:-1] - 1]) for s in syms} return common, yrs, T, P, R def trend_sleeve(P, R, syms, T): """12-1 TS-momentum long/short on syms, equal-risk = managed-futures proxy.""" Pm = np.column_stack([P[s] for s in syms]); Rm = np.column_stack([R[s] for s in syms]) lc = np.log(Pm); sg = np.zeros((T, len(syms))); sg[252:] = np.sign(lc[252:] - lc[:-252]) vol = np.full((T, len(syms)), np.nan) for t in range(63, T): vol[t] = Rm[t - 63:t].std(0) iv = 1 / np.where(vol > 0, vol, np.nan); w = np.nan_to_num(sg * iv) g = np.abs(w).sum(1, keepdims=True); g[g == 0] = 1; w = w / g tr = np.zeros(T); tr[1:] = np.sum(w[:-1] * Rm[1:], axis=1); return tr def report(title, cands, yrs): def stats(r): r = np.nan_to_num(r); eq = np.cumprod(1 + r); c = eq[-1] ** (1 / yrs) - 1 v = r.std() * math.sqrt(252); dd = float((eq / np.maximum.accumulate(eq) - 1).min()) sh = (r.mean() * 252 - FIN) / v if v > 0 else 0 return c, v, sh, dd def fv(c, P0=360000, pmt=8000, years=20): rm = c / 12; N = years * 12; gg = (1 + rm) ** N; return P0 * gg + pmt * ((gg - 1) / rm) print(f"\n=== {title} ===") print(f" {'strategy':>30} | CAGR | vol | Shrp* | maxDD | $360k+$8k/mo 20y") for nm, r in cands: c, v, sh, dd = stats(r) print(f" {nm:>30} | {100*c:>4.1f}% | {100*v:>3.0f}% | {sh:>+4.2f} | {100*dd:>+4.0f}% | ${fv(c):>13,.0f}") def main(): # A) long history 2006-2026 common, yrs, T, P, R = build(["QQQ", "SPY", "IEF", "GLD", "DBC"]) tr = trend_sleeve(P, R, ["SPY", "IEF", "GLD", "DBC"], T) R4 = np.column_stack([R[s] for s in ["SPY", "IEF", "GLD", "DBC"]]) book, _, _ = book_series(np.column_stack([R4, tr])) qqq = R["QQQ"] def lev(r, L): return L * r - (L - 1) * FIN / 252 gd70 = 0.70 * qqq + 0.15 * tr + 0.15 * book gd60 = 0.60 * qqq + 0.20 * tr + 0.20 * book report(f"A) Long history {common[0]}..{common[-1]} ({yrs:.0f}y), fin {100*FIN:.0f}%", [ ("QQQ buy-hold", qqq), ("book (diversified)", book), ("70/30 QQQ+book", 0.7 * qqq + 0.3 * book), ("GD-70 (70Q/15trend/15book)", gd70), ("GD-60 (60Q/20trend/20book)", gd60), ("GD-70 @1.2x cheap lev", lev(gd70, 1.2)), ("GD-70 @1.3x cheap lev", lev(gd70, 1.3)), ], yrs) print(" *Sharpe = excess over financing. (Execution-alpha — behavior gap ~1-2%/yr, tax-loss") print(" harvest ~0.5-1.5%/yr taxable — is REAL but counterfactual, NOT in these numbers: add on top.)") # B) recent window incl VRP (PUTW) + real managed-futures (DBMF) common2, yrs2, T2, P2, R2 = build(["QQQ", "SPY", "IEF", "GLD", "DBC", "PUTW", "DBMF"]) tr2 = trend_sleeve(P2, R2, ["SPY", "IEF", "GLD", "DBC"], T2) book2, _, _ = book_series(np.column_stack([np.column_stack([R2[s] for s in ["SPY", "IEF", "GLD", "DBC"]]), tr2])) q2 = R2["QQQ"]; putw = R2["PUTW"]; dbmf = R2["DBMF"] base = 0.70 * q2 + 0.15 * dbmf + 0.15 * book2 # GD with REAL managed-futures ETF with_vrp = 0.60 * q2 + 0.15 * dbmf + 0.15 * book2 + 0.10 * putw report(f"B) Recent {common2[0]}..{common2[-1]} ({yrs2:.0f}y) — does VRP add?", [ ("QQQ buy-hold", q2), ("GD (70Q/15DBMF/15book)", base), ("GD + 10% VRP(PUTW)", with_vrp), ("PUTW alone (VRP)", putw), ("DBMF alone (trend)", dbmf), ], yrs2) print(" carry: no clean liquid retail ETF (DBV-type funds are tiny/closed) -> not testable cleanly here.") if __name__ == "__main__": main()