13 KiB
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:
ZFIN_HOME=examples/pre-retirement-both zfin projections
For the field-by-field file format, see the
projections.srf reference;
for how the simulation works under the hood, see
The retirement projection model.
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 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 closely. For the full method and its assumptions, see The retirement projection model.
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 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:
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:
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:
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:
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:
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.
Check the model against reality
Once you have snapshot history (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?
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 and
The interactive TUI).
Example: a complete projections.srf
This is the
pre-retirement-both
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.
#!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.
Next steps
projections.srfreference -- every field.- The retirement projection model -- the math and assumptions.
- The interactive TUI -- the Projections tab and its charts.
- Snapshots and history -- build the actuals the overlay needs.
Previous: Snapshots and history | Next: Audit against your brokerage | Documentation home