# 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 should track [FIRECalc.com](https://firecalc.com/) closely. For the full method and its assumptions, 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,871,732.10 Range (10th–90th percentile): $5,807,693.45 to $18,240,675.15 ``` 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 $347,601 $311,857 $308,728 99% safe withdrawal $314,920 $293,374 $264,002 ``` Read it as: "retiring in 2046 with this portfolio, I could withdraw ~$264k/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)