# `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)
