doc+tool: CAGR-maximizer reference plan + side-by-side strategy comparison

Honest reference plan (docs/.../2026-06-08-cagr-reference-plan.md): CAGR builds wealth not Sharpe;
more CAGR always = more drawdown; only free CAGR is drag reduction; book's 0.96 Sharpe was an rf=0
artifact (excess-over-financing, equities win); leverage moat = cheap financing. Recommendation:
max equity + low drag + contribute + never sell, optional 1.2-1.3x futures if you survive -65%.
strategy_compare.py: re-runnable side-by-side (book / SPY 1x / SPY 1.3x / 60-40 / 70-30 / overlay)
with backtest stats + the P0+PMT trajectory. Configurable --p0 --pmt --fin --years.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-08 08:34:49 +02:00
parent 1cdbc81390
commit db8f5434a0
2 changed files with 184 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Side-by-side comparison of the wealth candidates (the CAGR reference plan, re-runnable).
Backtest (2006-2026, incl 2008) + the $360k+$8k/mo 20y trajectory for: adaptive multi-strat book,
SPY buy-hold, SPY 1.3x (cheap futures financing), 60/40, 70/30 SPY+book, SPY+adaptive overlay.
Honest: CAGR builds wealth; more CAGR = more drawdown. See docs/.../2026-06-08-cagr-reference-plan.md.
python3 strategy_compare.py # full table
python3 strategy_compare.py --p0 100000 --pmt 2000 --fin 0.03 --years 20
"""
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 arg(flag, default, cast=float):
return cast(sys.argv[sys.argv.index(flag) + 1]) if flag in sys.argv else default
def yh(s):
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"]
ts = r["timestamp"]
return {datetime.datetime.utcfromtimestamp(t).strftime("%Y-%m-%d"): float(c) for t, c in zip(ts, a) if c is not None}
def stats(r):
r = np.nan_to_num(r)
eq = np.cumprod(1 + r); cagr = eq[-1] ** (252 / len(r)) - 1
vol = r.std() * math.sqrt(252)
dd = float((eq / np.maximum.accumulate(eq) - 1).min())
rf = 0.03; sharpe = (r.mean() * 252 - rf) / vol if vol > 0 else 0 # excess-over-financing Sharpe
return cagr, vol, sharpe, dd
def main():
P0 = arg("--p0", 360000.0); PMT = arg("--pmt", 8000.0); FIN = arg("--fin", 0.03); YEARS = int(arg("--years", 20))
syms = ["SPY", "IEF", "GLD", "DBC"]
data = {s: yh(s) for s in syms}
dates = sorted(set.intersection(*[set(d) for d in data.values()]))
T = len(dates)
Pm = np.column_stack([np.array([data[s][d] for d in dates]) for s in syms])
R = np.zeros((T, 4)); R[1:] = Pm[1:] / Pm[:-1] - 1
spy, ief = R[:, 0], R[:, 1]
# multi-strat book (proxy: SPY/IEF/GLD/DBC + DIY trend sleeve)
lc = np.log(Pm); sg = np.zeros((T, 4)); sg[252:] = np.sign(lc[252:] - lc[:-252])
vol = np.full((T, 4), np.nan)
for t in range(63, T):
vol[t] = R[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
trend = np.zeros(T); trend[1:] = np.sum(w[:-1] * R[1:], axis=1)
book, _, _ = book_series(np.column_stack([R, trend]))
# SPY + adaptive overlay (vol-target 15% + 200d trend + drawdown de-lever)
lp = np.log(Pm[:, 0]); ma = np.array([lp[max(0, t - 200):t].mean() if t >= 200 else lp[t] for t in range(T)])
ovr = np.zeros(T); eqo = pk = 1.0
for t in range(63, T - 1):
rv = spy[t - 63:t].std() * math.sqrt(252); vs = min(1.0, 0.15 / (rv + 1e-9))
tr = 1.0 if lp[t] > ma[t] else 0.3
dd = eqo / pk - 1; dds = float(np.clip(1 + 3 * min(0, dd + 0.07), 0.4, 1.0))
e = float(np.clip(vs * tr * dds, 0, 1.0)); ovr[t + 1] = e * spy[t + 1]; eqo *= (1 + ovr[t + 1]); pk = max(pk, eqo)
cands = {
"SPY buy-hold (1.0x)": spy,
"SPY 1.3x (cheap fin)": 1.3 * spy - 0.3 * FIN / 252,
"60/40 (SPY/IEF)": 0.6 * spy + 0.4 * ief,
"70/30 SPY+book": 0.7 * spy + 0.3 * book,
"SPY+adaptive overlay": ovr,
"adaptive multi-strat book": book,
}
def fv(cagr):
rm = cagr / 12; N = YEARS * 12; gg = (1 + rm) ** N
return P0 * gg + PMT * ((gg - 1) / rm)
print(f"STRATEGY COMPARISON — {dates[0]}..{dates[-1]} | ${P0:,.0f} + ${PMT:,.0f}/mo, {YEARS}y, fin {100*FIN:.0f}%")
print(f" {'strategy':>26} | CAGR | vol | Sharpe* | maxDD | {YEARS}y wealth")
for nm, r in cands.items():
c, v, sh, dd = stats(r)
print(f" {nm:>26} | {100*c:>4.1f}% | {100*v:>3.0f}% | {sh:>+5.2f} | {100*dd:>+5.0f}% | ${fv(c):>13,.0f}")
print(" *Sharpe = excess over financing (the leverage-relevant one). More CAGR ALWAYS = deeper maxDD.")
print(" Reference plan: docs/superpowers/specs/2026-06-08-cagr-reference-plan.md")
if __name__ == "__main__":
main()