# Plan for retirement

**Goal:** configure a `projections.srf` and read the output to answer
the two questions that matter -- "given my retirement date, what can I
spend?" and "given my desired spending, when can I retire?"

This guide uses the five bundled households. Each is fully configured;
run them and compare:

```bash
ZFIN_HOME=examples/pre-retirement-both zfin projections
```

For the field-by-field file format, see the
[`projections.srf` reference](../reference/config/projections-srf.md);
for how the simulation works under the hood, see
[The retirement projection model](../explanation/projections-model.md).

## Why this is the payoff

Everything you've recorded -- lots, accounts, contributions -- feeds the
question most tools answer badly: *can I actually retire, and on what?*
zfin answers it by **replaying history**. The engine implements the
[FIRECalc](https://firecalc.com/) algorithm over Robert Shiller's market
dataset going back to **1871**: it runs your portfolio through every
historical starting year as a separate retirement -- 1871, 1872, ... --
including the cohorts who retired into 1929, 1966, or 2000. The spread
of those real outcomes is exactly what becomes the percentile bands and
safe-withdrawal numbers below.

Because it's the same method over the same dataset, results track
[FIRECalc.com](https://firecalc.com/) closely -- within roughly +6-9% on
safe-withdrawal dollars, in the slightly-optimistic direction (a June
2026 audit pinned the gap to zfin's equity-return series and made it a
regression suite). For the full method, its assumptions, and the parity
evidence, see
[The retirement projection model](../explanation/projections-model.md).

## Keeping the Shiller data current

That historical dataset is **compiled into the binary** (from
`src/data/ie_data.csv`), not fetched at runtime -- so it's one of the
few things in zfin that needs a periodic refresh. Once each year's
final market and CPI numbers are published, the dataset should be
updated to add that year. zfin tracks this for you: once the dataset is
overdue, **every command** (CLI and TUI) prints a one-line reminder to
stderr, above its normal output -- so you don't have to go looking for
it. [`zfin doctor`](../reference/cli/doctor.md) also reports it in its
Environment section (a `WARN` instead of `OK`, with the date it was
last updated).

Because the data is embedded, refreshing it means updating the
**binary**, not clearing a cache:

- **Built from source:** pull the latest repo and `zig build` -- the
  refreshed dataset recompiles in.
- **Pre-built binary:** install a newer release.

It's not urgent -- stale data just means you're missing the most recent
year of history, and projections still run -- so treat it as "refresh
when convenient."

## The two questions

zfin runs a two-phase historical Monte Carlo: an **accumulation** phase
(contributions in, no spending) followed by a **distribution** phase
(spending out, no contributions). What you put in `projections.srf`
decides which question the display answers:

| You configure                                            | zfin answers                  | Try the example           |
|----------------------------------------------------------|-------------------------------|---------------------------|
| A retirement **date** (`retirement_age`/`retirement_at`) | "What can I spend?"           | `pre-retirement-age`      |
| A target **spending** (`target_spending`)                | "When can I retire?"          | `pre-retirement-spending` |
| **Both**                                                 | both, side by side            | `pre-retirement-both`     |
| **Neither**                                              | already-retired drawdown view | `post-retirement`         |

Every projection also opens with a benchmark comparison and a
`Projected return` line -- your holdings' blended expected return,
which feeds the simulation.

## Question 1: "What can I spend?" (target date)

`pre-retirement-age` sets `retirement_age:num:65` and an
`annual_contribution`, but no target spending:

```bash
ZFIN_HOME=examples/pre-retirement-age zfin projections
```

The **Accumulation phase** block gives the dated headline and the
projected portfolio at retirement:

```
Accumulation phase:
  Years until possible retirement: 19 (2046-04-12, ages 65/62)
  Annual contributions:            $80,000  (CPI-adjusted)
  Median portfolio at retirement:  $7,599,829.01
  Range (10th–90th percentile):    $5,576,011.69 to $17,552,083.29
```

Below it, the **Safe Withdrawal** table shows the sustainable annual
spend at each horizon and confidence level (FIRECalc-style historical
simulation):

```
Safe Withdrawal (FIRECalc historical simulation)
                              25 Year     35 Year     50 Year
90% safe withdrawal          $331,658    $298,238    $291,267
99% safe withdrawal          $301,038    $280,477    $251,851
```

Read it as: "retiring in 2046 with this portfolio, I could withdraw
~$252k/yr and be 99% confident it lasts 50 years (historically)."

## Question 2: "When can I retire?" (target spending)

`pre-retirement-spending` sets `target_spending:num:80000` but no date.
zfin searches for the earliest year that sustains that spending and
renders the **Earliest retirement** grid:

```bash
ZFIN_HOME=examples/pre-retirement-spending zfin projections
```

```
Earliest retirement (target spending: $80,000/yr CPI-adjusted)
                                25 Year       35 Year       50 Year
  90% confidence             2030-06-19    2030-06-19    2030-06-19
  95% confidence             2030-06-19    2030-06-19    2030-06-19
  99% confidence             2031-06-19    2031-06-19    2031-06-19
```

One cell is **promoted** to the Accumulation-phase headline. The
default rule picks the longest horizon at 99% confidence that keeps the
oldest person under age 100. Override it by annotating one horizon line
in `projections.srf`:

```srf
type::config,horizon:num:35,retirement_target:num:95
```

## Both questions at once

`pre-retirement-both` sets a date **and** a spending target, so both
blocks render back to back -- "I planned to retire in 2046; at these
confidence levels I could actually retire as early as 2030." The
configured date wins the headline; the grid is the comparison.

## When a plan isn't feasible

`pre-retirement-spending-target` sets an aggressive
`target_spending:num:2400000` and pins the headline to the
longest-horizon, highest-confidence cell -- which turns out to be
unreachable inside the 50-year search:

```bash
ZFIN_HOME=examples/pre-retirement-spending-target zfin projections
```

```
Accumulation phase:
  Years until possible retirement: not feasible

Earliest retirement (target spending: $2,400,000/yr CPI-adjusted)
                                25 Year       35 Year       50 Year
  99% confidence             2075-06-19    infeasible    infeasible
```

The headline reports "not feasible" honestly, and the grid still shows
which cells *do* work so you can choose a reachable anchor.

## Already retired: the drawdown view

`post-retirement` configures neither input -- it's a distribution-only
household:

```bash
ZFIN_HOME=examples/post-retirement zfin projections
```

The accumulation block collapses to a single line, confirming no
pre-retirement growth is being modeled:

```
Accumulation phase:
  Years until possible retirement: none
```

Everything else -- the median-value chart, terminal-value percentiles,
and safe-withdrawal table over the configured horizons -- behaves as a
pure drawdown projection.

## Life events

Social Security, pensions, tuition, and late-life healthcare are
modeled as `type::event` lines (positive = income, negative =
expense). They appear in the Life Events block and shift the cash-flow
math in both phases:

```
Life Events
    Social Security (Pat)        +$38,400/yr   age 70  (in 25yr)
    College Tuition              -$55,000/yr   age 50  (in 5yr), 4yr
```

See [event fields](../reference/config/projections-srf.md#event-fields).

## Check the model against reality

Once you have [snapshot history](snapshots-and-history.md) (or imported
back-values), zfin can grade its own past projections three ways:

- **Actuals overlay** -- plot your realized trajectory on top of the
  bands the model *would have drawn* from a past date. Did reality stay
  inside the envelope?

  ```bash
  zfin projections --as-of 1Y --overlay-actuals
  ```

- **Convergence** (`--convergence`) -- as data accumulated, did the
  model's predicted retirement date settle down, or keep drifting?

- **Return back-test** (`--return-backtest`) -- was the
  expected-return assumption honest next to the realized forward
  returns?

The CLI prints these as text and braille; the TUI draws them as real
charts (next section).

> A caveat zfin states loudly: these show whether the model was
> **directionally honest** -- did your actual path fall within the
> bands it drew -- **not** whether a safe-withdrawal claim holds over a
> full 30-year retirement. There isn't enough history to answer the
> latter, and won't be within our lifetimes.

## In the interactive TUI

The CLI gives you the numbers; the **Projections tab** in the TUI
(`zfin i`, then tab over to Projections) is where it comes alive, with
high-fidelity charts the plain terminal can't draw. Press `?` for the
full keymap; the projections-specific keys:

| Key   | Does                                                                                                           |
|-------|----------------------------------------------------------------------------------------------------------------|
| `v`   | Show/hide the **percentile-band chart** -- the median line with the p10-p90 envelope across the horizon.       |
| `d`   | Set an **as-of date** -- back-date the whole projection to any past date (auto-snaps to the nearest snapshot). |
| `o`   | Overlay your **realized actuals** on the bands (needs an as-of date plus snapshot/imported history).           |
| `z`   | Zoom the overlay's x-axis to roughly `[as-of, today + horizon]`.                                               |
| `c`   | **Convergence** chart -- the model's predicted retirement date over time.                                      |
| `b`   | **Return back-test** chart -- expected vs. realized forward returns.                                           |
| `e`   | Show/hide the life-events annotations.                                                                         |
| `Esc` | Clear the as-of date, back to the live view.                                                                   |

A typical what-if loop: open the Projections tab, press `d` and enter a
date a few years back, then `o` to drop your real trajectory onto the
bands the model would have drawn then -- a visual, honest check of how
the projection has held up. `c` and `b` then grade the model's
retirement-date and return assumptions over time.

Charts render as crisp Kitty graphics when your terminal supports it,
and fall back to braille otherwise (see
[`--chart`](../reference/cli/interactive.md) and
[The interactive TUI](../reference/tui.md)).

## Example: a complete `projections.srf`

This is the
[`pre-retirement-both`](../../examples/pre-retirement-both/projections.srf)
household: Pat (born 1981) and Sam (born 1983), retiring at 65 and
targeting $80k/yr, with both an accumulation and a distribution phase.
Copy it as a starting point and change the numbers to yours.

```srf
#!srfv1

# Accumulation phase (while still working)
type::config,retirement_age:num:65
type::config,annual_contribution:num:80000
type::config,contribution_inflation_adjusted:bool:true

# Distribution phase (in retirement)
type::config,target_stock_pct:num:80
type::config,target_spending:num:80000
type::config,target_spending_inflation_adjusted:bool:true
type::config,horizon:num:25
type::config,horizon:num:35
type::config,horizon_age:num:95

# The two people (drive ages, retirement_age, and life-event timing)
type::birthdate,date::1981-04-12
type::birthdate,date::1983-09-08,person:num:2

# Life events (positive = income, negative = expense)
type::event,name::Social Security (Pat),start_age:num:70,person:num:1,amount:num:38400
type::event,name::Social Security (Sam),start_age:num:70,person:num:2,amount:num:36000
type::event,name::College Tuition,start_age:num:50,person:num:1,duration:num:4,amount:num:-55000
```

Run it with `ZFIN_HOME=examples/pre-retirement-both zfin projections`;
every field is documented in the
[`projections.srf` reference](../reference/config/projections-srf.md).

## Next steps

- [`projections.srf` reference](../reference/config/projections-srf.md) -- every field.
- [The retirement projection model](../explanation/projections-model.md) -- the math and assumptions.
- [The interactive TUI](../reference/tui.md) -- the Projections tab and its charts.
- [Snapshots and history](snapshots-and-history.md) -- build the actuals the overlay needs.

---

[Previous: Snapshots and history](snapshots-and-history.md) | [Next: Audit against your brokerage](audit-against-brokerage.md) | [Documentation home](../README.md)
