# `projections.srf` reference `projections.srf` configures [`zfin projections`](../cli/projections.md): the Monte-Carlo-style retirement simulation run over the Shiller dataset (1871-present). zfin loads it from the same directory as the portfolio. It is optional -- without it, the command runs with sensible defaults (20/30/45-year horizons, 90/95/99% confidence, no accumulation phase). This page is the field reference. For how the simulation actually works see [The retirement projection model](../../explanation/projections-model.md); for a guided setup see [Plan for retirement](../../guides/plan-retirement.md). ## Record types Every line starts with a `type::` discriminator: | `type::` | Purpose | |-------------|---------------------------------------------------------------------------------------------------| | `config` | A single configuration field (allocation, horizon, retirement target, contributions, spending). | | `birthdate` | A household member's birthdate (drives ages, `horizon_age`, `retirement_age`, life-event timing). | | `event` | A life event: recurring income or expense (Social Security, pension, tuition, healthcare). | ```srf #!srfv1 type::config,target_stock_pct:num:80 type::config,horizon:num:25 type::config,horizon_age:num:95 type::birthdate,date::1981-04-12 type::event,name::Social Security,start_age:num:70,amount:num:38400 ``` ## `config` fields | Field | Type | Description | |--------------------------------------|------|--------------------------------------------------------------------------------------------------------------------------------| | `target_stock_pct` | num | Asset-allocation target (0-100). Sets the simulation's stock/bond blend. | | `expense_ratio` | num | Annual fund expense ratio as a percent (e.g. `0.18` = 0.18%), subtracted from the blended return each year. Default `0.18` (FIRECalc's default; realistic for a fund portfolio). Override down (`0.04`) for low-cost index funds, up for active funds, or `0` for all individual stocks. | | `horizon` | num | Distribution-phase length in years. Repeat the line for multiple horizons. | | `horizon_age` | num | Horizon expressed as an age; resolves to `target_age - oldest_current_age`. Repeatable. | | `retirement_age` | num | Age the **oldest** configured person must reach to retire. | | `retirement_at` | date | Absolute retirement date (`YYYY-MM-DD`). Wins over `retirement_age` if both set. | | `annual_contribution` | num | Yearly accumulation-phase contribution, in today's dollars. | | `contribution_inflation_adjusted` | bool | If `true` (default), contributions grow with CPI year over year. | | `target_spending` | num | Desired retirement spending, in today's dollars. | | `target_spending_inflation_adjusted` | bool | If `true` (default), target spending grows with CPI during distribution. | | `retirement_target` | num | Annotation on a `horizon`/`horizon_age` line that overrides the earliest-retirement promotion rule. Allowed: `90`, `95`, `99`. | ### Choosing an `expense_ratio` The expense ratio is the annual fund-fee drag on the portfolio. zfin **defaults to `0.18%`** -- the same figure FIRECalc uses, and a realistic (mildly conservative) assumption for a portfolio that holds funds. Modeling *no* fee is less accurate and makes the projection too optimistic, so `0` is not the default; it's an explicit choice for an all-individual-stock portfolio. The right number is portfolio-specific, so override it with yours. zfin can't infer it automatically: the fund-profile provider doesn't return expense ratios, and the only free source that does (Yahoo) is accurate for ETFs but wrong for mutual funds -- so the default is a sensible constant rather than a derived value. Reference points for picking yours: | Portfolio style | Typical blended ER | |----------------------------------------------|--------------------| | Low-cost index (VTI/VOO/BND, 3-fund) | ~0.03-0.06% | | Target-date index funds | ~0.08-0.15% | | **FIRECalc's default** | **0.18%** | | Mix with active / specialty funds | ~0.20-0.50% | | Heavy active management | 0.50-1.00%+ | To estimate your own, take each fund's published ER (the fund company's page, or Yahoo Finance for ETFs), weight by market value, and sum; individual stocks, bonds, and cash contribute ~0. Set the result once: `type::config,expense_ratio:num:0.18`. See [Parity with FIRECalc](../../explanation/projections-model.md#parity-with-firecalc) for how the fee interacts with the rest of the model. ## `birthdate` fields | Field | Type | Description | |----------|------|----------------------------------------------------| | `date` | date | Birthdate (`YYYY-MM-DD`). | | `person` | num | Household member (`1` default, `2` for a partner). | ```srf type::birthdate,date::1981-04-12 type::birthdate,date::1983-09-08,person:num:2 ``` ## `event` fields Life events modify annual cash flow in both phases. Positive amounts are income (offset withdrawals); negative amounts are expenses (added to withdrawals). | Field | Type | Description | |----------------------|--------|-----------------------------------------------------------------------------------------| | `name` | string | Label shown in the Life Events table. | | `amount` | num | Annual amount. Positive = income, negative = expense. | | `start_age` | num | Age (of `person`) at which the event begins. | | `duration` | num | Optional length in years. Omit for a permanent event. | | `person` | num | Whose age `start_age` refers to (`1` default, `2` for a partner). | | `inflation_adjusted` | bool | If `true` (default), the amount grows with CPI. Set `false` for a fixed nominal amount. | ```srf # Permanent income starting at age 70 type::event,name::Social Security,start_age:num:70,amount:num:38400 # 4-year expense starting at age 50 type::event,name::College Tuition,start_age:num:50,duration:num:4,amount:num:-55000 # A partner's pension type::event,name::Pension,start_age:num:65,person:num:2,amount:num:24000 ``` ## The two retirement-planning inputs `projections.srf` answers a different question depending on which inputs you set. This is the single most important thing to understand about the file: | You set... | zfin answers... | Display | |----------------------------------------------------|-----------------------------------------|-----------------------------------------------------------------------| | A target date (`retirement_age` / `retirement_at`) | "Given my date, what can I spend?" | Accumulation-phase block with a dated headline. | | A target spending (`target_spending`) | "Given my spending, when can I retire?" | Earliest-retirement grid; one cell is promoted to the headline. | | Both | Both, back to back | Configured date wins the headline; grid renders below for comparison. | | Neither | Already-retired view | "Years until possible retirement: none". | When `target_spending` is set, the **earliest-retirement grid** shows, for each (horizon x confidence) pair, the earliest year that sustains the spending. The default promotion rule picks the headline cell by walking horizons longest-to-shortest at 99% confidence, preferring the longest horizon that keeps the oldest person under age 100. Override it with a `retirement_target` annotation on one horizon line: ```srf # use the 35yr x 95% cell as the headline type::config,horizon:num:35,retirement_target:num:95 ``` At most one horizon may carry the annotation; configuring more than one drops them all and falls back to the default rule. If the promoted cell is infeasible (no accumulation length <= 50 years sustains the spending), the headline reads "not feasible" and the grid still renders so you can pick a workable anchor. ## The example configurations The five bundled examples are fully-configured walkthroughs of each combination: | `examples/...` | Inputs | |----------------------------------|---------------------------------------------------| | `pre-retirement-age` | target date only | | `pre-retirement-spending` | target spending only | | `pre-retirement-spending-target` | target spending + an explicit (infeasible) anchor | | `pre-retirement-both` | target date + target spending | | `post-retirement` | neither (distribution-only) | ```bash ZFIN_HOME=examples/pre-retirement-both zfin projections ``` ## See also - [Plan for retirement](../../guides/plan-retirement.md) -- guided setup. - [The retirement projection model](../../explanation/projections-model.md) -- how the simulation works. - [`zfin projections`](../cli/projections.md) -- command flags (`--as-of`, `--overlay-actuals`, `--convergence`, `--return-backtest`). --- [Documentation home](../../README.md#reference)