# `zfin compare` Compare your portfolio at two points in time: liquid totals, per-symbol price moves, and contribution attribution. ``` Usage: zfin compare # compare DATE vs. live portfolio zfin compare # 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 ` / `--snapshot-after ` | Override a side's snapshot (`--snapshot-after live` for the current portfolio). | | `--commit-before ` / `--commit-after ` | Pin the git commit for the attribution block (`HEAD`, `HEAD~N`, SHA, or `working`). Useful when a review date and its commit diverge. | ## 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)