# 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 50 years) 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.

## 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
  *<distribution>* 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.
