# The retirement projection model [`zfin projections`](../reference/cli/projections.md) 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](../reference/config/projections-srf.md) and [Plan for retirement](../guides/plan-retirement.md). ## 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: 1. **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). 2. **Distribution** -- annual spending withdrawn (CPI-adjusted by default), no contributions. Its length is the configured `horizon`. [Life events](../reference/config/projections-srf.md#event-fields) (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. Set `contribution_inflation_adjusted`, `target_spending_inflation_adjusted`, or an event's `inflation_adjusted` to `false` to 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 `max_accumulation_years`, 50 years by default) that sustains your spending, and renders the grid of answers. One cell is promoted to the headline (see [promotion rules](../reference/config/projections-srf.md#the-two-retirement-planning-inputs)). If no length within the cap works, the cell is **infeasible** -- shown honestly rather than fudged. A young saver with a runway longer than 50 years can raise the cap via [`max_accumulation_years`](../reference/config/projections-srf.md#config-fields). ## 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. ## Parity with FIRECalc Because zfin re-implements the FIRECalc method over the same Shiller dataset, its numbers should -- and do -- **track [FIRECalc.com](https://firecalc.com/) closely, while running systematically a little more optimistic**. This section records the evidence behind that claim from a June 2026 audit, so "tracks closely" isn't just an assertion. The cross-checks are pinned as a regression suite (`FIRECalc parity: ...` tests in `analytics/projections.zig`). ### Method FIRECalc 3.0 was driven directly through its web form (the same 1871-2025 Shiller span zfin embeds, "data thru 1/1/2026"). For an apples-to-apples comparison, FIRECalc's **expense ratio was set to 0%** (matching zfin with `expense_ratio:num:0`) and its fixed-income model left at the default "Long Interest" (10-year Treasury). Zeroing the fee on both sides removes it as a variable so the references below isolate the one structural difference, the equity return series. zfin uses the Treasury *yield* for bonds rather than a bond-price series, which is why the comparisons hold the allocation at familiar blends. ### What matches, and by how much Safe-withdrawal dollars (today's dollars, FIRECalc fee=0), the headline "how much can I spend" number: | Scenario (portfolio / alloc / horizon / confidence) | FIRECalc | zfin | Δ | |-----------------------------------------------------|---------:|---------:|------:| | $1M / 100% / 30y / 95% | $39,697 | $42,717 | +7.6% | | $1M / 75-25 / 30y / 95% | $41,221 | $44,036 | +6.8% | | $1M / 100% / 45y / 95% | $35,835 | $37,906 | +5.8% | | $1M / 100% / 20y / 95% | $45,879 | $49,660 | +8.2% | | $1M / 100% / 30y / 90% | $43,804 | $47,138 | +7.6% | | $1M / 100% / 30y / 99% | $35,864 | $38,098 | +6.2% | | $7.7M / 100% / 45y / 99% | $254,461 | $275,724 | +8.4% | | $7.7M / 82% / 45y / 99% | $262,770 | $286,314 | +9.0% | Success rate ($1M, $40k/yr, 30yr, fee=0): FIRECalc 94.4% vs zfin 97.6% (100% stock); FIRECalc 96.8% vs zfin 99.2% (75/25) -- zfin ~+2-3pp. Terminal portfolio value (same scenario, **nominal** dollars): median FIRECalc $5.12M vs zfin $5.71M (+11%); p90 $12.76M vs $14.75M (+16%). One contributions (accumulation-phase) cross-check -- $500k start, $30k/yr added for 10 years, then 30-year drawdown, 95% -- lands at FIRECalc $55,154 vs zfin $53,468 (-3.1%), the one case where zfin came out *lower* (more conservative). That flip is a real modeling difference in how the two tools treat the accumulation phase, not noise -- see "Accumulation phase" below. ### Why zfin runs a little hot: methodology, not a bug The divergence was isolated with a **$0-spending, 100%-stock** run, which removes withdrawals, withdrawal timing, fees, and bonds from the picture entirely. For the 1966 cohort, zfin's year-30 *nominal* balance is **$20.24M vs FIRECalc's $18.60M** -- a ratio of 0.919 over 30 years, i.e. FIRECalc's equity returns compound about **0.2-0.3%/yr lower** than zfin's. That is the entire discrepancy: the gap is in the **equity total-return series**, not the withdrawal logic. The reason zfin is higher is that **zfin uses the gold-standard construction and FIRECalc uses a coarser one**: - **zfin** reconstructs each year's nominal total return directly from Shiller's **Real Total Return Price** index -- the canonical academic S&P total-return series, in which dividends are reinvested **monthly** -- times that year's CPI change (see `build/gen_shiller.zig`). The reconstruction recovers Shiller's published nominal total return exactly. - **FIRECalc** computes "market growth + dividends" in the lineage of the 1998 Trinity Study and John Greaney's *Retire Early* spreadsheet (FIRECalc's own [methodology page](https://www.firecalc.com/intro.php) describes this). That construction reinvests dividends more coarsely (annually, in effect), which **systematically understates compounding** by roughly a quarter-percent a year versus the monthly-reinvested index. So zfin's equity returns are slightly higher **because they are more accurate** -- monthly dividend reinvestment is what actually happened. Over 30-45 year horizons that ~0.25%/yr compounds into the +6-9% safe-withdrawal gap, and the worst cohorts (which set the safe-withdrawal floor) diverge most because small per-year differences explode near the failure boundary. FIRECalc's own FAQ concedes the point -- it notes that implementations differ on exactly these details and "all of the studies converge on the same basic results." **Honest caveat (cuts the other way):** a more accurate *historical* return series does not make the *forecast* more accurate -- nobody can predict your future returns. It only means zfin replays history with better-constructed inputs. If you specifically want to reproduce FIRECalc's output, expect zfin to read a few percent higher for this reason, by design. ### Other differences - **Terminal values: nominal vs real.** FIRECalc's *on-screen* ending balances are **real** (start-of-retirement dollars); zfin's terminal bands are **nominal**. (FIRECalc's spreadsheet *export* is nominal, which is what the terminal-value table above compares against.) Don't compare zfin's nominal terminal bands to FIRECalc's on-screen ending range without deflating one of them first. - **Accumulation phase: zfin models it through history, FIRECalc doesn't.** For runs with a pre-retirement contribution phase, the two tools differ by design. FIRECalc always reports "N possible ** year periods" -- e.g. 126 thirty-year periods -- *regardless of accumulation length* (verified at 0, 10, and 25 years of accumulation, all 126). Since a 1871 distribution start with 25 years of accumulation would need 1846-1870 data that predates the dataset, FIRECalc cannot be replaying history during accumulation: it grows the pre-retirement portfolio **deterministically** and only Monte-Carlos the distribution. zfin instead runs the **full accumulation + distribution span through one continuous historical sequence** (116 cohorts for a 10+30yr run), so it also captures **accumulation-phase sequence-of-returns risk** -- a bad market in the years just before retirement. That extra realism is why zfin reads slightly *lower* (more conservative) on contribution scenarios. It's a deliberate fidelity gain, not a discrepancy to reconcile. ### The bottom line Treat zfin's safe-withdrawal numbers as **tracking FIRECalc within roughly +6-9%, in the optimistic direction** -- a pure equity-engine gap that holds with fees matched (both tools default to a 0.18% fee). If you want a FIRECalc-conservative read, mentally haircut zfin's safe-spending figure by ~5-10%. The parity suite asserts zfin stays within -3% / +15% of these references, so a future engine change that drifts materially further -- in either direction -- trips a test. ## 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](#how-inflation-is-handled). - **Taxes** are not modeled. Withdrawal figures are pre-tax. - **Fees** are modeled as a flat annual expense-ratio drag, defaulting to 0.18% (configurable via [`expense_ratio`](../reference/config/projections-srf.md)); set it to your portfolio's blended ratio. - **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](../guides/plan-retirement.md) -- the guided walkthrough. - [`projections.srf` reference](../reference/config/projections-srf.md) -- every input. - [`zfin projections`](../reference/cli/projections.md) -- flags and evaluation views.