Credits
15
Tokens
250,918
import numpy as np
years = list(range(2016,2026))
vttvx = [7.48,15.94,-5.15,19.63,13.30,9.80,-15.55,14.55,9.44,14.60]
vthrx = [7.85,17.52,-5.86,21.07,14.10,11.38,-16.27,16.03,10.64,16.24]
vtsax = [12.66,21.17,-5.17,30.80,20.99,25.71,-19.53,26.01,23.74,17.12]
vbtlx = [2.60,3.56,-0.03,8.71,7.72,-1.67,-13.16,5.70,1.24,7.15]
p8020 = [0.8*s + 0.2*b for s,b in zip(vtsax,vbtlx)]
def cum_ann(rets):
g = np.prod([1+r/100 for r in rets])
ann = g**(1/len(rets)) - 1
return (g-1)*100, ann*100
print(f"{'Fund':<12}{'Cumulative':>14}{'Annualized':>14}")
for name, r in [("VTTVX",vttvx),("VTHRX",vthrx),("80/20",p8020),("VTSAX (100%)",vtsax),("VBTLX (100%)",vbtlx)]:
c,a = cum_ann(r)
print(f"{name:<12}{c:>13.2f}%{a:>13.2f}%")
print("\n80/20 annual returns (rebalanced yearly):")
for y,r in zip(years,p8020): print(f" {y}: {r:6.2f}%")
import math
vtsax = {2016:0.1238,2017:0.2108,2018:-0.0509,2019:0.3054,2020:0.2104,2021:0.2547,2022:-0.1949,2023:0.2605,2024:0.2352,2025:0.2180}
vbtlx = {2016:0.0260,2017:0.0356,2018:-0.0003,2019:0.0871,2020:0.0772,2021:-0.0167,2022:-0.1316,2023:0.0570,2024:0.0124,2025:0.0715}
# no rebalance
stock=0.8
bond=0.2
for y in sorted(vtsax):
stock*=1+vtsax[y]
bond*=1+vbtlx[y]
port=stock+bond
cagr=port**(1/10)-1
print('no rebalance growth', port, 'CAGR', cagr*100)
print('end stock weight', stock/port)
no rebalance growth 3.3707548935585216 CAGR 12.920480468061534 end stock weight 0.9277391758932342
import math
vtsax = {2016:0.1238,2017:0.2108,2018:-0.0509,2019:0.3054,2020:0.2104,2021:0.2547,2022:-0.1949,2023:0.2605,2024:0.2352,2025:0.2180}
vbtlx = {2016:0.0260,2017:0.0356,2018:-0.0003,2019:0.0871,2020:0.0772,2021:-0.0167,2022:-0.1316,2023:0.0570,2024:0.0124,2025:0.0715}
# no rebalance
stock=0.8
bond=0.2
for y in sorted(vtsax):
stock*=1+vtsax[y]
bond*=1+vbtlx[y]
port=stock+bond
cagr=port**(1/10)-1
print('no rebalance growth', port, 'CAGR', cagr*100)
print('end stock weight', stock/port)
no rebalance growth 3.3707548935585216 CAGR 12.920480468061534 end stock weight 0.9277391758932342
import math
# Known annual total returns from sources gathered
vt2025 = {
2016: 0.0748,
2017: 0.1594,
2018: -0.0515,
2019: 0.1963,
2020: 0.1330,
2021: 0.0980,
2022: -0.1555,
2023: 0.1455,
2024: 0.0944,
2025: 0.1460,
}
vthrx = {
2016: 0.0785,
2017: 0.1752,
2018: -0.0586,
2019: 0.2107,
2020: 0.1410,
2021: 0.1138,
2022: -0.1627,
2023: 0.1603,
2024: 0.1064,
2025: 0.1633, # from trust/select with same glidepath; used as approximation if needed
}
# Alternative 2025 estimate for VTHRX from 10y cumulative 140.13% on benchmark / 136.47% on trust select used for sensitivity
# VBTLX exact from source annual returns
vbtlx = {
2016: 0.0260,
2017: 0.0356,
2018: -0.0003,
2019: 0.0871,
2020: 0.0772,
2021: -0.0167,
2022: -0.1316,
2023: 0.0570,
2024: 0.0124,
2025: 0.0715,
}
# VTSAX annual total returns estimated from market/index-like annual returns for CRSP US Total Market / VTI-equivalent
# We use widely reported annual total returns for Vanguard Total Stock Market family for 2016-2025.
vtsax = {
2016: 0.1238,
2017: 0.2108,
2018: -0.0509,
2019: 0.3054,
2020: 0.2104,
2021: 0.2547,
2022: -0.1949,
2023: 0.2605,
2024: 0.2352,
2025: 0.2180,
}
def cagr(rdict):
p=1.0
for y in sorted(rdict):
p*=1+rdict[y]
return p**(1/10)-1, p
for name, data in [('VTTVX',vt2025),('VTHRXapprox',vthrx),('VBTLX',vbtlx),('VTSAXest',vtsax)]:
c,p=cagr(data)
print(name, 'growth', round(p,6), 'CAGR', round(c*100,4))
# exact 80/20 annual rebalanced portfolio from estimated VTSAX + exact VBTLX
w_stock=0.8; w_bond=0.2
p=1.0
rets=[]
for y in sorted(vtsax):
r=w_stock*vtsax[y] + w_bond*vbtlx[y]
rets.append((y,r))
p*=1+r
print('80/20 annual returns:')
for y,r in rets:
print(y, round(r*100,3))
print('80/20 growth', round(p,6), 'CAGR', round((p**(1/10)-1)*100,4))
# Sensitivity: if VTSAX 10y CAGR were 14.5,14.7,15.0 what would static blend CAGR be if solved annually impossible,
# approximate using CAGR blend and exact annual blend lower bound not used.
VTTVX growth 2.134156 CAGR 7.8754 VTHRXapprox growth 2.295571 CAGR 8.6648 VBTLX growth 1.217868 CAGR 1.9906 VTSAXest growth 3.908977 CAGR 14.6057 80/20 annual returns: 2016 10.424 2017 17.576 2018 -4.078 2019 26.174 2020 18.376 2021 20.042 2022 -18.224 2023 21.98 2024 19.064 2025 18.87 80/20 growth 3.152349 CAGR 12.1666
import numpy as np
years = list(range(2016,2026))
vttvx = [7.48,15.94,-5.15,19.63,13.30,9.80,-15.55,14.55,9.44,14.60]
vthrx = [7.85,17.52,-5.86,21.07,14.10,11.38,-16.27,16.03,10.64,16.24]
vtsax = [12.66,21.17,-5.17,30.80,20.99,25.71,-19.53,26.01,23.74,17.12]
vbtlx = [2.60,3.56,-0.03,8.71,7.72,-1.67,-13.16,5.70,1.24,7.15]
p8020 = [0.8*s + 0.2*b for s,b in zip(vtsax,vbtlx)]
def cum_ann(rets):
g = np.prod([1+r/100 for r in rets])
ann = g**(1/len(rets)) - 1
return (g-1)*100, ann*100
print(f"{'Fund':<12}{'Cumulative':>14}{'Annualized':>14}")
for name, r in [("VTTVX",vttvx),("VTHRX",vthrx),("80/20",p8020),("VTSAX (100%)",vtsax),("VBTLX (100%)",vbtlx)]:
c,a = cum_ann(r)
print(f"{name:<12}{c:>13.2f}%{a:>13.2f}%")
print("\n80/20 annual returns (rebalanced yearly):")
for y,r in zip(years,p8020): print(f" {y}: {r:6.2f}%")
Fund Cumulative Annualized VTTVX 113.42% 7.88% VTHRX 129.38% 8.66% 80/20 207.05% 11.87% VTSAX (100%) 278.46% 14.24% VBTLX (100%) 21.79% 1.99% 80/20 annual returns (rebalanced yearly): 2016: 10.65% 2017: 17.65% 2018: -4.14% 2019: 26.38% 2020: 18.34% 2021: 20.23% 2022: -18.26% 2023: 21.95% 2024: 19.24% 2025: 15.13%
vthrx_ret = [0.1624, 0.1064, 0.1603, -0.1627, 0.1138, 0.1410, 0.2107, -0.0586, 0.1752, 0.0785]
vttvx_ret = [0.1460, 0.0944, 0.1455, -0.1555, 0.0980, 0.1330, 0.1963, -0.0515, 0.1594, 0.0748]
vtsax_ret = [0.1712, 0.2374, 0.2601, -0.1953, 0.2571, 0.2099, 0.3080, -0.0517, 0.2117, 0.1266]
vbtlx_ret = [0.0715, 0.0124, 0.0570, -0.1316, -0.0167, 0.0772, 0.0871, -0.0003, 0.0356, 0.0260]
def calc_annualized(returns):
val = 1.0
for r in returns:
val *= (1 + r)
return val ** (1/len(returns)) - 1
blend_ret = [0.8 * s + 0.2 * b for s, b in zip(vtsax_ret, vbtlx_ret)]
print(f"VTTVX 10-year annualized: {calc_annualized(vttvx_ret):.4%}")
print(f"VTHRX 10-year annualized: {calc_annualized(vthrx_ret):.4%}")
print(f"80/20 Blend 10-year annualized: {calc_annualized(blend_ret):.4%}")
VTTVX 10-year annualized: 7.8754% VTHRX 10-year annualized: 8.6564% 80/20 Blend 10-year annualized: 11.8719%
vthrx_ret = [0.1624, 0.1064, 0.1603, -0.1627, 0.1138, 0.1410, 0.2107, -0.0586, 0.1752, 0.0785]
vttvx_ret = [0.1460, 0.0944, 0.1455, -0.1555, 0.0980, 0.1330, 0.1963, -0.0515, 0.1594, 0.0748]
vtsax_ret = [0.1712, 0.2374, 0.2601, -0.1953, 0.2571, 0.2099, 0.3080, -0.0517, 0.2117, 0.1266]
vbtlx_ret = [0.0715, 0.0124, 0.0570, -0.1316, -0.0167, 0.0772, 0.0871, -0.0003, 0.0356, 0.0260]
def calc_annualized(returns):
val = 1.0
for r in returns:
val *= (1 + r)
return val ** (1/len(returns)) - 1
blend_ret = [0.8 * s + 0.2 * b for s, b in zip(vtsax_ret, vbtlx_ret)]
print(f"VTTVX 10-year annualized: {calc_annualized(vttvx_ret):.4%}")
print(f"VTHRX 10-year annualized: {calc_annualized(vthrx_ret):.4%}")
print(f"80/20 Blend 10-year annualized: {calc_annualized(blend_ret):.4%}")
VTTVX 10-year annualized: 7.8754% VTHRX 10-year annualized: 8.6564% 80/20 Blend 10-year annualized: 11.8719%
vttvx_ret = [0.1460, 0.0944, 0.1455, -0.1555, 0.0980, 0.1330, 0.1963, -0.0515, 0.1594, 0.0748]
vtsax_ret = [0.1712, 0.2374, 0.2601, -0.1953, 0.2571, 0.2099, 0.3080, -0.0517, 0.2117, 0.1266]
vbtlx_ret = [0.0715, 0.0124, 0.0570, -0.1316, -0.0167, 0.0772, 0.0871, -0.0003, 0.0356, 0.0260]
def calc_annualized(returns):
val = 1.0
for r in returns:
val *= (1 + r)
return val ** (1/len(returns)) - 1
blend_ret = [0.8 * s + 0.2 * b for s, b in zip(vtsax_ret, vbtlx_ret)]
print(f"VTTVX (2025) 10-year annualized: {calc_annualized(vttvx_ret):.4%}")
print(f"VTSAX 10-year annualized: {calc_annualized(vtsax_ret):.4%}")
print(f"VBTLX 10-year annualized: {calc_annualized(vbtlx_ret):.4%}")
print(f"80/20 (VTSAX/VBTLX) 10-year annualized (annual rebalance): {calc_annualized(blend_ret):.4%}")
VTTVX (2025) 10-year annualized: 7.8754% VTSAX 10-year annualized: 14.2357% VBTLX 10-year annualized: 1.9906% 80/20 (VTSAX/VBTLX) 10-year annualized (annual rebalance): 11.8719%
import datetime
print("Current Date:", datetime.date.today())
Current Date: 2026-05-06
import numpy as np
vtsax = [12.66, 21.17, -5.17, 30.80, 20.99, 25.71, -19.53, 26.01, 23.74, 17.12]
vbtlx = [2.60, 3.56, -0.03, 8.71, 7.72, -1.67, -13.16, 5.70, 1.24, 7.15]
# Could 12.17% come from arithmetic mean of rebalanced blend?
blend = [0.8*s + 0.2*b for s,b in zip(vtsax, vbtlx)]
print(f"Arithmetic mean of 80/20 annual returns: {np.mean(blend):.3f}%")
# Or 80% * VTSAX CAGR + 20% * VBTLX CAGR (weighted CAGRs, not portfolio CAGR)?
cagr_s = (np.prod([1+r/100 for r in vtsax]))**(1/10) - 1
cagr_b = (np.prod([1+r/100 for r in vbtlx]))**(1/10) - 1
print(f"VTSAX CAGR: {cagr_s*100:.3f}%, VBTLX CAGR: {cagr_b*100:.3f}%")
print(f"Weighted average of CAGRs (0.8*S + 0.2*B): {(0.8*cagr_s + 0.2*cagr_b)*100:.3f}%")
Arithmetic mean of 80/20 annual returns: 12.716% VTSAX CAGR: 14.236%, VBTLX CAGR: 1.991% Weighted average of CAGRs (0.8*S + 0.2*B): 11.787%
import numpy as np
vtsax = [12.66, 21.17, -5.17, 30.80, 20.99, 25.71, -19.53, 26.01, 23.74, 17.12]
vbtlx = [2.60, 3.56, -0.03, 8.71, 7.72, -1.67, -13.16, 5.70, 1.24, 7.15]
# Could 12.17% come from arithmetic mean of rebalanced blend?
blend = [0.8*s + 0.2*b for s,b in zip(vtsax, vbtlx)]
print(f"Arithmetic mean of 80/20 annual returns: {np.mean(blend):.3f}%")
# Or 80% * VTSAX CAGR + 20% * VBTLX CAGR (weighted CAGRs, not portfolio CAGR)?
cagr_s = (np.prod([1+r/100 for r in vtsax]))**(1/10) - 1
cagr_b = (np.prod([1+r/100 for r in vbtlx]))**(1/10) - 1
print(f"VTSAX CAGR: {cagr_s*100:.3f}%, VBTLX CAGR: {cagr_b*100:.3f}%")
print(f"Weighted average of CAGRs (0.8*S + 0.2*B): {(0.8*cagr_s + 0.2*cagr_b)*100:.3f}%")
Arithmetic mean of 80/20 annual returns: 12.716% VTSAX CAGR: 14.236%, VBTLX CAGR: 1.991% Weighted average of CAGRs (0.8*S + 0.2*B): 11.787%
import numpy as np
# Same annual returns used by all three models (in %)
vtsax = [12.66, 21.17, -5.17, 30.80, 20.99, 25.71, -19.53, 26.01, 23.74, 17.12]
vbtlx = [2.60, 3.56, -0.03, 8.71, 7.72, -1.67, -13.16, 5.70, 1.24, 7.15]
# Method 1: Annual rebalancing (what Claude & Gemini did, what ChatGPT *claimed*)
blend_rebal = [0.8*s + 0.2*b for s,b in zip(vtsax, vbtlx)]
g1 = np.prod([1+r/100 for r in blend_rebal])
cagr1 = g1**(1/10) - 1
print(f"Method 1 - Annual rebalance: CAGR = {cagr1*100:.3f}% (Growth: ${10000*g1:,.0f})")
# Method 2: No rebalancing - buy & hold the two sleeves (what ChatGPT's code actually did)
stock = 0.8
bond = 0.2
for s, b in zip(vtsax, vbtlx):
stock *= 1 + s/100
bond *= 1 + b/100
port = stock + bond
cagr2 = port**(1/10) - 1
print(f"Method 2 - No rebalance: CAGR = {cagr2*100:.3f}% (Growth: ${10000*port:,.0f})")
print(f" End-of-period stock weight drifted to: {stock/port*100:.1f}%")
# Method 3: 100% VTSAX for reference
g3 = np.prod([1+r/100 for r in vtsax])
print(f"Method 3 - 100% VTSAX: CAGR = {(g3**(1/10)-1)*100:.3f}%")
print(f"\nChatGPT's reported figure: 12.17%")
print(f"Gap vs rebalanced (11.87%): +{12.17-11.87:.2f} pp")
print(f"Gap vs no-rebalance (12.92%): {12.17-12.92:.2f} pp")
Method 1 - Annual rebalance: CAGR = 11.872% (Growth: $30,705) Method 2 - No rebalance: CAGR = 12.583% (Growth: $32,712) End-of-period stock weight drifted to: 92.6% Method 3 - 100% VTSAX: CAGR = 14.236% ChatGPT's reported figure: 12.17% Gap vs rebalanced (11.87%): +0.30 pp Gap vs no-rebalance (12.92%): -0.75 pp