backtest: growth-discipline multi-premia (QQQ core + trend sleeve + book)

User's '10x boring' idea tested. GD-70 (70%QQQ/15%trend/15%book) has highest Sharpe-excess (0.67-0.84
vs QQQ 0.65, book 0.39); trend sleeve = genuine crisis-alpha. GD@1.3x cheap-lev ~= QQQ return with
shallower drawdown (-48 vs -53%). VRP(PUTW) does NOT add (dilutes); carry no clean retail ETF. Clean
stackable premia = equity-core + trend-sleeve + execution-alpha (behavior gap/tax/cheap-lev, on top,
not in backtest). GD genuinely beats both the over-conservative book and panic-prone raw QQQ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-08 09:42:30 +02:00
parent db8f5434a0
commit a3f309dd7e

View File

@@ -0,0 +1,113 @@
#!/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()