尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Re-runs William P. Bengen, "Determining Withdrawal Rates Using Historical Data" (Journal of Financial Planning, Oct 1994), then tests flexible spending rules, allocation, front-loading and international diversification on actual returns through 2025.
Full write-up with charts: docs/beyond-the-4pct-rule.md
Spend the way retirees actually do, cap the good years, and keep the chance of running out near 2%. Per $1 million saved, in today's dollars:
Following it each year: a step-by-step January routine, the planned change for every age, and two worked retirements (2000 and 1990).
| Per $1M saved | Recommended plan | Earlier plan (6.1%, 75% floor) | Bengen 4% |
|---|---|---|---|
| First-year spending | $65,000 | $61,000 | $40,000 |
| Floor (never below) | $27,300 | $45,750 | none |
| Typical spending, year 1 / 10 / 20 / 30 | $65,000 / $59,800 / $56,100 / $52,100 | $61,000 / $62,600 / $59,500 / $50,700 | $40,000 flat |
| Ran out: 69 real retirements (no fee / 0.25% / 0.5% fee) | 0 / 0 / 0 | 1 / 6 / 9 | 0 / 0 / 1 |
| Ran out: 5,000 reshuffled retirements (no fee / 0.25% fee) | 2.0% / 2.8% | 13.7% / 16.3% | 4.9% / 6.2% |
| Ever spend below $30,000 (reshuffled) | 23% | 14% | 5% |
| Typical balance at year 30 (reshuffled) | $1.09M | $0.73M | $1.23M |
Typical = median retiree. "Reshuffled" retirements stitch together random runs of real years (10 on average) from 1928–2025, so they include plenty of bad sequences that never happened but could have.
The trade-off: the earlier plan promised never to fall below $45,750 but drained the account in about 1 retirement in 7 when history didn't repeat itself exactly. The recommended plan's floor is lower, so about 1 in 4 retirees have a lean stretch below $30,000 after a bad start, but only 1 in 50 runs out. Its smooth-spending score is slightly lower ($4.87 vs $5.00 per $100, reshuffled) because the cap leaves good-year money invested. Why the plan changed, and the other strategies tried, are in Testing beyond history.
69 overlapping 30-year retirements starting 1928–1996. Spending in real dollars per $100 of starting portfolio; "typical" = median retiree.
| Change added | Safe starting rate | Typical spending, yrs 1–10 | Failures (of 69) |
|---|---|---|---|
| Bengen: constant real spending, 50/50 US | 4.0% (max 4.16%, worst start 1966) | $4.00 | 0 |
| Guardrails ±30%, $4 floor, 80% stocks | 5.5% | $5.67 | 0 |
| Real spending −0.5%/yr, floor declines too | 6.3% | $6.29 | 0 |
| 25% of stocks international | 8.0% | $7.42 | 0 |
| Accept 95% success | 9.0% | $8.02 | 3 |
Each line is the highest starting rate a retiree starting that year could have used; its lowest point is the safe rate in the table. For the guardrail variants "safe" means spending never fell below the $4 floor and the money lasted, so a high start in a good year is cut back, not sustained.
Each January (8% plan): raise last year's spending by inflation and trim it 0.5%, then divide by the current portfolio. If that withdrawal rate is above 9.6% (8% + 20%), cut spending 30%; if it is below 6.4% (8% − 20%), raise it 30%; otherwise leave it. Spending never goes below the $4 floor (declining 0.5% a year) or above the 2× cap. "±30%" is the size of the move; the band is the trigger.
Higher starting rates are paid for with frequent deep cuts. Each retiree is placed by their deepest real spending cut: below the plan (planned 0.5% trims excluded), and from their best year so far. Shares are of the 69 historical retirees (overlapping windows, so frequencies, not forecasts).
| Strategy | Typical spending, yrs 1–10 | Cut ≥30% below plan | Cut ≥30% from best year |
|---|---|---|---|
| Bengen 4.0% | $4.00 | 0% | 0% |
| Bengen 5.0% | $5.00 | 25% (ran out) | 25% (ran out) |
| Guardrails, US, 5.5% | $5.67 | 0% | 58% |
| + 25% international, 8.0% | $7.42 | 83% | 100% |
| 95% success plan, 9.0% | $8.02 | 97% | 100% |
Barely. A 10% cash bucket (taken from bonds) is spent first, then bonds, while stocks are 10%+ below their peak, for up to 5 years without selling stocks, then refilled by a fifth of its target in each up year.
| No cash | Static 10% cash | 10% cash bucket | |
|---|---|---|---|
| Bengen 50/50: safe start | 4.16% | 4.18% | 4.14% |
| 8% plan: safe start, no failures | 8.06% | 8.12% | 8.20% |
| 8% plan: safe start, 95% success | 9.02% | 8.74% | 9.22% |
| 8% plan at 8%: cut ≥30% below plan | 77% | 78% | 74% |
10% covers only about 1.25 years of spending on the 8% plan, the 1966 retiree's damage came long after the cash was gone, and annual rebalancing already buys stocks in down years. This matches published research (Estrada 2018; Woerheide & Nanigan; Early Retirement Now Part 48). It slightly softens the worst outcomes, so it is a reasonable comfort tool, not a way to raise the safe rate.
More charts: Bengen replication · international share · full write-up in docs/beyond-the-4pct-rule.md.
Not financial advice: a historical study of US and developed-market returns, 1928–2025.
The earlier plan (6.1% start, 75% floor) was tuned on the same 69 overlapping retirements it was scored on, which hold only about three truly independent 30-year periods. Three checks showed it was fitted too closely:
The weak point was the floor: $45,750 per $1M, never cut, is above the roughly $40,000 that history has supported as spending that never falls, and buying it as a real guarantee (a 30-year TIPS ladder at about 2%) would cost more than the whole portfolio.
Bengen's 4% rule out of history. The same reshuffled retirements, run with Bengen's exact rule (fixed real
spending, 50/50 US stocks and bonds), turn 4% from a rate that never failed into one that survives about 95%
of the time (scripts/bengen_bootstrap.py).
| 50/50 US stocks and bonds | Real history (69) | Reshuffled (5,000) |
|---|---|---|
| Ran out at 4% | 0% | 4.9% |
| Rate that survives 99% / 95% / 90% of the time | 4.24% / 4.33% / 4.58% | 3.38% / 4.02% / 4.41% |
| Typical retiree's highest rate | 6.41% | 6.19% |
| Worst case | 4.16% (1966) | 2.59% |
Strategies from the literature. On the same engine and score: amortization rules (Bogleheads' ABW and VPW, TPAW-style), a CAPE-aware amortization rule and Early Retirement Now's CAPE rule, Vanguard's dynamic spending rule, a rising-stock glidepath, and a TIPS-ladder floor with stock upside. Several looked better in history (CAPE amortization +9%, glidepath +8%) and then lost most of their edge, or ran out more, out of history.
How the plan was set. All settings, including the floor, were tuned on 2,000 reshuffled retirements to maximise the smooth-spending score with at most 2% running out, then checked on 5,000 fresh ones, on real history and with fees. Caps of 1%, 2%, 3% and 5% were tried; 2% gave most of the safety without pushing the floor too low (at 1% it falls to about $21,000–24,000). Spending follows Blanchett's age curve (−0.96% a year on average in household data; J.P. Morgan finds spending more than 30% lower at 85 than at 60). Without a cap, good markets pushed typical spending up over time, against that curve; capping it at 10% above the planned path keeps the smile and lets the plan start $7,000 higher than an uncapped version.
python scripts/alt_optimize.py --boot 2000 --max-out 0.02 --families "Our rule, floor tuned, Blanchett spending curve" --set ceiling=1.1 --name cap110
python scripts/alt_robust.py final # fees, reshuffled history, other settings
python scripts/alt_charts.py # charts in output/alt/
python scripts/plan_summary.py # the table above
python scripts/bengen_bootstrap.py # Bengen's 4% rule on reshuffled history
Sources: Blanchett, "Exploring the Retirement Consumption Puzzle", Journal of Financial Planning (May 2014); J.P. Morgan Asset Management, Retirement by the Numbers; Kitces and Income Lab on risk-based guardrails; Early Retirement Now, SWR series parts 18, 19 and 54; Advisor Perspectives, "Beyond the 4% Rule: Flexible Withdrawal Strategies Using Certainty-Equivalent Spending" (2021); Vanguard's dynamic spending rule; Morningstar, safe withdrawal rate research (2026).
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
python scripts/reproduce.py --download # fetch fresh data, then run
python scripts/reproduce.py # or run on the bundled snapshot
pytest
Outputs land in output/: results.md, safemax_30yr_50_50.csv, and three charts.
Options: --timing start|end (default end), --bonds intermediate|10y (default intermediate).
Try it online: nicky-s-sandhu.github.io/beyond-4pct/dashboard
The online version is a static page (docs/dashboard/) that runs the simulation in your browser: a JavaScript
port of the Python engine, checked against it by tests/test_web_engine.py, with the data exported by
scripts/export_web_data.py (rerun it after changing the data or the recommended plan). Settings can be shared
as a link. The original Dash version runs locally:
pip install -e .
pip install -r app/requirements.txt
python app/dashboard.py # then open http://127.0.0.1:8050
Controls cover the starting rate, the planned spending path (Blanchett's curve or a fixed yearly change), floor and cap, guardrail band and cut/raise sizes, stock and international mix, the optional cash bucket, and the data and scoring choices. Presets load the recommended plan, the earlier 6.1% plan, Bengen's 4% rule and the 8% plan. Every tile shows the difference from the recommended plan, including the share of 5,000 reshuffled-history retirements that run out; a warning appears when that is above the plan's 2% target.
scripts/guardrails.py)Guyton-Klinger-style rule: each year spending rises with inflation, then is cut by step
if the current withdrawal rate is above initial × (1 + band) or raised by step if it is
below initial × (1 − band). --steps is the variance knob: the largest real change in
spending in any single year. step = 0 is exactly Bengen's rule.
python scripts/guardrails.py # 4% and 5%, steps 0–25%
python scripts/guardrails.py --floor 0.8 --name floor80
python scripts/guardrails.py --rates 0.055 --steps 0,0.1,0.2 --band 0.15 --gk
Options: --band, --floor / --ceiling (multiples of year-1 real spending), --gk
(skip inflation raise after a losing year; no cuts in the last 15 years), --stocks,
--horizon, --focus-year. Outputs go to output/guardrails/[name]/.
scripts/optimize.py)Grid over stock share (60%–125%, above 100% = leverage at T-bills + 1%), guardrail step,
spending ceiling and planned real drift. For each combination it finds the highest starting rate
(0.1% grid) at which the required share of historical retirees succeed. Success = real spending
never below --target (default $4 per $100) and never running out. A floor sits at the target.
--success 0.95: required historical success rate (overrides --allow K).--drifts 0,-0.005: planned real spending change per year (−0.005 = inflation − 0.5%).
By default the floor declines at the same rate; --flat-floor keeps it flat.--ceilings 0,1,1.25,1.5,2,3: cap on spending as a multiple of the planned path (0 = none).--rank early|avg: rank by median spending in years 1–10 (default) or over all years.python scripts/optimize.py --success 0.95 --name p95
python scripts/optimize.py --success 1.0 --name strict
Outputs in output/optimize/[name]/: results.md, optimize_grid.csv, heatmaps per drift,
spending_fan_top_pick.png and spending_shape.png (median vs 5th-percentile retiree).
--intl, scripts/intl_sweep.py)--intl 0.25 (the default in optimize.py and guardrails.py) puts 25% of the stock allocation in
developed ex-US stocks, rebalanced yearly. reproduce.py stays US-only to match Bengen.
--intl-stress uses a pessimistic series where wartime data gaps are filled with heavy losses.
python scripts/intl_sweep.py # max safe start vs intl share, fixed strategy
python scripts/optimize.py --success 0.95 --intl 0 --name p95_us
python scripts/optimize.py --success 0.95 --name p95_intl25
python scripts/build_intl.py [JSTdatasetR6.csv] # rebuild data/raw/intl_stocks.csv
# The raw JST file is not in the repo: download JSTdatasetR6 from https://www.macrohistory.net/database/
# into data/external/ (as CSV). The derived series data/raw/intl_stocks.csv is included.
python scripts/safemax_variants.py # safe start by start year, four variants
python scripts/explain_guardrails.py # 1966 and 1982 traces + how often the rule acts
python scripts/explain_guardrails.py --years 1929,1973
Outputs in output/variants/: the two charts, per-year CSVs and per-retiree trace CSVs.
python scripts/pain_analysis.py # cut-severity levels by strategy -> output/pain/
python scripts/cash_bucket.py # 10% bucket vs no cash vs static cash -> output/bucket/
python scripts/cash_bucket.py --cash 0.15 --threshold 0.2 --max-years 5 --refill-years 5
src/bengen/vec.py simulates all 69 retirees at once (about 2.6 ms per plan), with separate cut and
raise sizes, a floor and cap, a phased spending plan and an optional cash bucket. The optimisers maximise
certainty-equivalent spending (risk aversion 3, years weighted by a couple's survival from the SSA
2022 life table in data/raw/ssa_period_life_2022.csv) under hard limits on extreme cuts.
python scripts/decline_optimize.py # recommended family: declining plan, 75% floor, raise caps
python scripts/decline_optimize.py --seed 22 --name seed22 # extra seeds; best per family kept in output/decline/best
python scripts/decline_charts.py # charts for the recommended plan
python scripts/experiment_map.py # every strategy on one chart
python scripts/smooth_optimize.py && python scripts/smooth_charts.py # smooth-score experiment
python scripts/smile_optimize.py && python scripts/smile_charts.py # spending-smile experiment
python scripts/smile_optimize.py --shape mirror --name mirror && python scripts/smile_mirror_charts.py
The decline/best folder merges the best of three seeds; rerunning reproduces it with seeds 21–23.
The current recommended plan comes from scripts/alt_optimize.py (see
Testing beyond history); src/bengen/alt.py holds the
alternative strategies and the reshuffled-history (block bootstrap) sampler.
| Series | Source | Notes |
|---|---|---|
| Stocks | Damodaran, S&P 500 total return (histretSP.xls) | 1928– |
| Bonds | Damodaran, 10-yr US Treasury total return | 1928– |
| Bills | Damodaran, 3-month T-bill | used for the intermediate-bond proxy |
| Inflation | CPI-U NSA, Dec→Dec (FRED CPIAUCNS) | |
| International stocks | 1928–69: JST Macrohistory R6 (CC BY-NC-SA 4.0), 15 developed countries, USD, real-GDP weighted; 1970–2025: MSCI World ex USA (USD total return) | data/raw/intl_stocks.csv |
International notes: JST lacks equity data for Canada and Ireland; Germany 1949 uses the official DM
parity because JST's exchange rate straddles the 1948 currency reform; country-years with missing
data are skipped (the intl_stress column fills them with heavy losses instead). JST's Japanese
returns look smoothed after 1970, so MSCI replaces JST from 1970. MSCI World ex USA was checked
against MSCI's official EAFE factsheet for 2012–2025 (within ~2pp a year; the gap is Canada).
data/raw/*.csv is a snapshot transcribed from Damodaran's web table and
usinflationcalculator.com's Dec→Dec table. --download pulls the original files and
saves *_downloaded.csv, which are then preferred — diff them against the snapshot once.
intermediate proxy
(½ T-bills + ½ 10-yr) approximates a ~5-yr duration and closes most of the gap; use --bonds 10y to see the raw effect.data/raw/intl_stocks.csv: derived from the Jordà-Schularick-Taylor Macrohistory Database, so it
is licensed CC BY-NC-SA 4.0: non-commercial use only, share
alike, with attribution. Results and charts that use the international series (the --intl options,
output/optimize, output/intl, output/variants, and the international figures in the write-up)
are shared under the same terms.If you use the international data, cite: