6.2 KiB
The retirement projection model
zfin projections simulates your
retirement portfolio against real market history. This page explains
the model so you can trust -- and correctly distrust -- its output. For
how to configure it, see the
projections.srf reference
and Plan for retirement.
Historical simulation, not a formula
Rather than assume a single average return, zfin replays your portfolio through actual historical sequences drawn from the Shiller dataset (US equity total returns and CPI back to 1871). Each simulated run uses a real historical path of returns and inflation, so the spread of outcomes reflects real sequences -- including bad-timing sequences like retiring into 1929, 1973, or 2000. This is the same family of method as FIRECalc.
Two phases
Every projection runs the same two phases in order:
- Accumulation -- contributions added each year, no spending. Its length comes from your retirement-date input. With no input, it's zero years (an already-retired view).
- Distribution -- annual spending withdrawn (CPI-adjusted by
default), no contributions. Its length is the configured
horizon.
Life events (Social Security, pensions, tuition, healthcare) adjust the cash flow in both phases.
How inflation is handled
Inflation isn't a fixed assumption. Each historical cycle uses that start year's actual CPI sequence alongside its actual returns (the Shiller dataset carries both), so a cycle beginning in 1966 replays 1966's stagflation while one beginning in 2009 replays low-inflation years.
The simulation runs in nominal dollars, which means the output mixes two units -- and knowing which is which is the difference between a sensible plan and a badly misread one:
- Flows are entered in today's dollars and inflated forward. Your
annual_contribution,target_spending, and inflation-adjusted life events are amounts in today's dollars; each simulated year the model multiplies them by that cycle's cumulative CPI, holding their purchasing power constant. Setcontribution_inflation_adjusted,target_spending_inflation_adjusted, or an event'sinflation_adjustedtofalseto pin a flow at a flat nominal amount instead (e.g. a fixed pension with no COLA). - Safe-withdrawal figures are in today's dollars. "You could spend ~$264k/yr at 99%" means ~$264k of today's purchasing power, with the actual dollar amount rising each retirement year to keep pace with inflation.
- Portfolio and terminal values are nominal (future dollars). The
"Median portfolio at retirement" and the
Terminal Portfolio Value (nominal, ...)percentiles are not inflation-adjusted. A ~$244M median balance 50 years out is heavily inflated dollars, not $244M of today's purchasing power -- judge it against the inflated spending it has to support, never against today's prices.
This split is deliberate and matches FIRECalc: you plan spending in real (today's) terms while the balance compounds in nominal terms.
Percentile bands
Across all the historical runs, zfin reports the distribution of outcomes rather than a single number:
- p10 (pessimistic) -- only 10% of histories did worse.
- p50 (median) -- the middle outcome.
- p90 (optimistic) -- only 10% did better.
Terminal Portfolio Value (nominal, at 99% withdrawal rate)
25 Year 35 Year
Pessimistic (p10) $6,739,560.02 $11,597,557.94
Median (p50) $30,023,255.68 $66,794,741.87
Optimistic (p90) $103,184,321.05 $279,372,182.75
The wide spread is the point: it shows sequence-of-returns risk honestly instead of hiding it behind an average.
Confidence and safe withdrawal
The Safe Withdrawal table answers "how much could I spend and still not run out?" at chosen confidence levels (90/95/99%). A 99% safe withdrawal is the spending level that survived 99% of historical sequences over that horizon -- the most conservative. Higher confidence and longer horizons both lower the safe number.
The earliest-retirement search
When you set a target_spending instead of a date, zfin inverts the
question: for each (horizon x confidence) cell it searches for the
earliest accumulation length (up to 50 years) that sustains your
spending, and renders the grid of answers. One cell is promoted to the
headline (see
promotion rules).
If no length within the cap works, the cell is infeasible -- shown
honestly rather than fudged.
The caveat that matters most
zfin states this loudly by design, and so does this page:
The actuals overlay and evaluation views (
--overlay-actuals,--convergence,--return-backtest) tell you whether the model was directionally honest -- did your real trajectory fall within the bands it would have drawn. They do not tell you whether a safe-withdrawal claim is accurate. An SWR claim is a 30-year claim; there is at most ~12 years of weekly history and a year or two of native snapshots to check it against. No one will have data to validate a full-retirement SWR within our lifetimes.
Treat the projection as a disciplined way to compare scenarios and visualize sequence risk -- not as a promise about your specific future.
Assumptions to keep in mind
- Allocation is a single stock/bond blend (
target_stock_pct), not your exact holdings. - Inflation comes from each historical cycle's own CPI; flows are real (today's-dollar) and balances are nominal. See How inflation is handled.
- Taxes are not modeled. Withdrawal figures are pre-tax.
- Imported-value overlays scale today's allocation to a historical
total when lot-level history isn't available, because a
liquid::row can't reconstruct past composition.
See also
- Plan for retirement -- the guided walkthrough.
projections.srfreference -- every input.zfin projections-- flags and evaluation views.