# zfin examples

Realistic-but-fictional portfolio configurations for spot-checking
zfin features and showing users how the configuration files fit
together. Run any zfin command against an example by setting
`ZFIN_HOME` to its directory:

```sh
ZFIN_HOME=examples/pre-retirement-both              zfin projections
ZFIN_HOME=examples/pre-retirement-age               zfin projections
ZFIN_HOME=examples/pre-retirement-spending          zfin projections
ZFIN_HOME=examples/pre-retirement-spending-target   zfin projections
ZFIN_HOME=examples/post-retirement                  zfin projections
ZFIN_HOME=examples/pre-retirement-both              zfin --tui
```

All names, share counts, account numbers, prices, and life events in
these examples are fictional. Do not interpret them as advice.

## Available examples

The five scenarios share the same fictional couple and balance sheet
(~$1.3M, age ~45, contributing $80k/yr) for the four pre-retirement
variants, and a separate retired couple for the distribution example.
Only the `projections.srf` configuration differs across the
pre-retirement variants — making it easy to see how each
retirement-planning input shapes the output.

### Background: how the projection accepts input

The simulation always runs the same two-phase model: an
**accumulation phase** (contributions in, no spending) followed by
a **distribution phase** (spending out, no contributions). What the
user configures in `projections.srf` decides which questions the
display answers:

- **Target retirement date** (`retirement_age` or `retirement_at`) —
  "Given my retirement date, what can I spend?" Produces the
  Accumulation phase block: median portfolio at retirement, p10–p90
  range, and the dated headline retirement line.
- **Target spending** (`target_spending`) — "Given my desired
  spending, when can I retire?" Produces the Earliest retirement
  grid (one cell per horizon × confidence) and promotes one cell
  into the Accumulation phase block as the headline.
- **Both** — both blocks render back-to-back. The configured
  retirement date wins for the headline; the grid is the
  side-by-side comparison.
- **Neither** — distribution-only mode. The Accumulation phase
  block reduces to a soft "Years until possible retirement: none"
  line.

### `pre-retirement-age/`

**Input: target retirement date only.** `retirement_age:num:65` is
set, `target_spending` is not. Output renders:

- **Accumulation phase** block — median portfolio at the configured
  retirement date, p10–p90 range, and the
  `Years until possible retirement: 19 (2046-04-12, ages 65/62)` line
  showing both partners' ages at retirement.
- Standard Safe Withdrawal table for the configured horizons.
- No "Earliest retirement" grid (no spending target to search against).

### `pre-retirement-spending/`

**Input: target spending only.** `target_spending:num:80000` is set,
`retirement_age`/`retirement_at` are not. Output renders:

- **Earliest retirement** grid — one cell per (horizon × confidence)
  showing the earliest year the household can retire and sustain
  $80k/yr at that confidence over that distribution horizon.
- The **Accumulation phase** block is populated by **promoting one
  cell** from the grid into the headline retirement line, plus the
  median portfolio at retirement and p10-p90 range. The default
  promotion rule walks horizons longest → shortest and picks the
  longest one whose end year keeps the oldest configured person
  under age 100, at 99% confidence (most conservative). If even
  the shortest horizon overshoots, it's used anyway.
- The grid stays rendered for transparency — the user can see how
  the headline cell compares to the rest of the matrix.

### `pre-retirement-spending-target/`

Same household and balance sheet as `pre-retirement-spending/`, but
with a much higher `target_spending` ($2.4M/yr) AND an explicit
`retirement_target:num:99` annotation on the `horizon_age:num:95`
record. That combination demonstrates two things at once:

- The **explicit override** mechanism: instead of the default
  promotion rule (longest horizon at 99% confidence, capped at age
  100), the user explicitly anchors the headline to the resolved
  `horizon_age:95 × 99%` cell. The override survives age-resolution
  — it rides on `horizon_age` records too, not just `horizon`.
- The **"not feasible" rendering path**: the annotated cell turns
  out to be infeasible at this spending level (no value of
  `accumulation_years` ≤ 50 sustains $2.4M/yr at 99% over a
  50-year retirement). The headline line renders
  "Years until possible retirement: not feasible" instead of a
  date, and the contribution / median lines below it are
  suppressed. The full Earliest retirement grid still renders so
  the user can see which (horizon × confidence) cells DO work and
  pick a different anchor.

Use this variant when the user has a preferred planning anchor
that's different from "longest feasible horizon at maximum
conservatism." Allowed `retirement_target` values are 90, 95, 99.
At most one horizon may carry the annotation; configuring two or
more drops them all (warning logged) and falls back to the default
rule.

### `pre-retirement-both/`

**Inputs: both target retirement date AND target spending.** Both
`retirement_age` and `target_spending` are set. Output renders both
blocks back-to-back so the user can compare "what I planned"
(target retirement date) against "what's earliest feasible" (target
spending). The configured retirement date wins for the headline
line; the Earliest retirement grid is rendered below for
comparison.

### `post-retirement/`

A retired couple, ~age 68, ~$2M total. Distribution-only mode (no
`target_spending`, no `retirement_age`/`retirement_at`).
Demonstrates the legacy projection behavior, including the soft
"Years until possible retirement: none" line that confirms the model
isn't projecting any pre-retirement growth.

Includes a late-life healthcare expense modeled as a life event
starting at age 80, plus Social Security already in pay status.
Asset allocation target: 60/40 (more bond-heavy than the
pre-retirement examples).

## Configuration file map

Every example contains:

- **`portfolio.srf`** — open lots, one per line. The source of truth
  for shares, cost basis, and account assignment.
- **`accounts.srf`** — tax type and institution metadata for each
  account name referenced by `portfolio.srf`.
- **`metadata.srf`** — sector / geography / asset-class
  classifications for each symbol.
- **`projections.srf`** — retirement projection configuration:
  birthdates, target allocation, horizons, life events, and (in the
  pre-retirement variants) the accumulation-phase / earliest-retirement
  fields. This is the only file that differs across the four
  pre-retirement variants.

There's no `watchlist.srf` in either example. Add one if you're
spot-checking watchlist features.

## Symbol selection

The examples use symbols whose candle data is commonly cached and
kept fresh by ongoing cron jobs:

- **Equity ETFs:** SPY, VTI, QQQ, SCHD
- **Bond ETF:** AGG

If you add a symbol not in this list, run `zfin quote <SYM>` once
inside the example directory to populate the cache before running
analytics commands.
