# `zfin compare`

Compare your portfolio at two points in time: liquid totals,
per-symbol price moves, and contribution attribution.

```
Usage:
  zfin compare <DATE>             # compare DATE vs. live portfolio
  zfin compare <DATE1> <DATE2>    # compare two historical dates
```

Arguments can be given in any order; output always reads older ->
newer. Dates accept `YYYY-MM-DD` or relative shortcuts
(`1W`/`1M`/`1Q`/`1Y`). Historical dates resolve against your
`history/*-portfolio.srf` snapshots; on a missing date, `compare`
prints the nearest available dates and exits rather than snapping
silently.

## Options

| Flag                                                   | Effect                                                                                                                                |
|--------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| `--projections`                                        | Add projected-return and 99% safe-withdrawal deltas (adds ~1-2s per endpoint).                                                        |
| `--no-events`                                          | With `--projections`, exclude life events.                                                                                            |
| `--snapshot-before <DATE>` / `--snapshot-after <DATE>` | Override a side's snapshot (`--snapshot-after live` for the current portfolio).                                                       |
| `--commit-before <SPEC>` / `--commit-after <SPEC>`     | Pin the git commit for the attribution block (`HEAD`, `HEAD~N`, SHA, or `working`). Rarely needed: by default each side anchors on the commit that *recorded* its snapshot. Passing `HEAD` is usually wrong -- see [Attribution and commit timing](../../guides/periodic-review.md#attribution-and-commit-timing). |

## Example

```bash
ZFIN_HOME=examples/post-retirement zfin compare 2024-04-01 2025-04-01
```

```
Portfolio comparison: 2024-04-01 -> 2025-04-01 (365 days)

Liquid:  $2,350,000.00 -> $2,580,000.00    +$230,000.00   +9.79%
```

With symbols held on both dates, a per-symbol price-change table
appears, sorted by percentage move.

### Mixed share classes

A symbol can cover holdings in more than one share class -- for example
a direct-indexing sleeve, a 401(k) collective trust and plain retail
shares all priced through the same `ticker::` alias with different
`price_ratio` values. Those trade at different per-share prices, so the
group has no single price to show and the price columns render as
`—`. The percentage and dollar figures come from the change in market
value instead.

Whether that percentage is trustworthy depends on one thing: **did the
share count move?**

**Share count unchanged** -- the percentage is a real return, and the row
sorts inline with everything else. A group's value is its underlying
price times a fixed basket, so with the basket held still the change in
value *is* the change in price. You just don't get a per-share price
beside it:

```
  SPYM               —   ->         —    +9.40%     +$110,000.00
```

**Shares bought or sold** -- the figure now mixes market movement with
your own cash flow and there is no way to separate them. Those rows are
tallied separately, named in a footnote, and always sorted last so you
can disregard them as a block:

```
  SPYM               —   ->         —   +30.69%    +$419,605.63
  18 gainers, 3 losers, 1 share count changed
  SPYM: spans share classes AND the share count moved, so the figure is total value change - part market, part shares bought or sold.
```

In that second example much of the `+30.69%` is a retail purchase made
partway through the window, not market movement.

Ordinary (non-mixed) rows are never affected by this: their percentage is
a pure price ratio, so buying or selling during the window cannot
distort it.

One caveat the tool cannot detect: restating a `price_ratio` without
changing shares also breaks the equivalence, because the ratio itself is
not recorded in a snapshot. If you have just re-based a proxied holding,
treat percentages spanning that date with suspicion.

This split is a limitation of what snapshots record rather than a display
choice: `price_ratio` is folded into each lot's stored price and the
underlying base price is never written, so a single meaningful price for
the group cannot be reconstructed after the fact.

## See also

- [Snapshots and history](../../guides/snapshots-and-history.md)
- [`history`](history.md) -- the full timeline. [`contributions`](contributions.md) -- the attribution detail.

---

[CLI command reference](index.md)
