add stock split guide with full rationale
All checks were successful
Generic zig build / build (push) Successful in 4m54s
Generic zig build / deploy (push) Successful in 19s
Generic zig build / publish-macos (push) Successful in 32s

This commit is contained in:
Emil Lerch 2026-07-06 14:25:56 -07:00
parent c08e89a025
commit f1cabdcf9a
Signed by: lobo
GPG key ID: A7B62D657EF764F8
4 changed files with 126 additions and 2 deletions

View file

@ -41,6 +41,7 @@ example portfolio and links to the reference for exhaustive detail.
| [Read your portfolio](guides/read-your-portfolio.md) | Interpret `portfolio`, `analysis`, `exposure`, `review`, `perf` |
| [Track contributions](guides/track-contributions.md) | See money added over time and tag internal transfers |
| [Snapshots and history](guides/snapshots-and-history.md) | Record daily snapshots and compare your portfolio over time |
| [Handle a stock split](guides/handle-a-stock-split.md) | Adjust for splits without restating shares (and why that matters) |
| [Plan for retirement](guides/plan-retirement.md) | Configure `projections.srf` for accumulation and drawdown |
| [Audit against your brokerage](guides/audit-against-brokerage.md) | Reconcile zfin against Fidelity/Schwab/Wells exports |
| [A periodic review](guides/periodic-review.md) | Reconcile, see what changed, then commit the new baseline |

View file

@ -0,0 +1,123 @@
# Handle a stock split
**Goal:** keep your holdings correct through a stock split without
breaking the history that zfin's other commands rely on.
**The one rule:** when a stock you hold splits, **do not edit the share
count on your existing lot.** Leave the lot exactly as you transacted
it, and tell zfin about the split with one line in `metadata.srf`. zfin
does the rest on read.
## Why not just change the share count?
It seems natural: NVDA does a 10:1 split, your 100 shares become 1,000,
so you open `portfolio.srf` and change `shares:num:100` to
`shares:num:1000`. Don't -- here is what that quietly breaks.
**Your portfolio file is history, and zfin reads the history, not just
the latest version.** If you are utilizing all zfin features, `portfolio.srf`
lives in git, and several commands diff *past commits* of it:
- [`contributions`](track-contributions.md) attributes what changed
between two commits to *new money* vs. *market movement*. It works on
the raw share/lot changes in the diff.
- [`compare`](../reference/cli/compare.md) and
[`audit`](audit-against-brokerage.md) read git history the same way.
A split changes your **share count** but not your **money**: 100 shares
at $400 and 1,000 shares at $40 are the same $40,000. But if you edit the
lot from 100 to 1,000 and commit, the git diff shows **+900 shares**.
`contributions` cannot tell that apart from you *buying* 900 shares, so
it reports a large **phantom contribution** that never happened -- and it
is now baked into your commit history permanently.
**Snapshots make it worse.** [`zfin snapshot`](snapshots-and-history.md)
writes immutable, point-in-time records of your portfolio under
`history/`. A snapshot taken *before* your edit recorded 100 shares;
after the edit, your live file says 1,000. The two no longer agree, and
you **cannot fix the old snapshot** -- it is a frozen record of a past
day, and `compare` / `history` read it as-is.
Both git history and snapshots are **append-only truth**: undoing the
damage means rewriting git history (dangerous) or hand-editing frozen
snapshot files (error-prone). So the safe design is simple -- a split
must never touch your recorded shares. It is a *derivation*, not an
*edit*.
## What to do instead
Leave the lot alone. Add a per-symbol `splits_current_through` date to
that symbol's row in
[`metadata.srf`](../reference/config/metadata-srf.md#stock-split-adjustment):
```srf
#!srfv1
symbol::NVDA,sector::Technology,geo::US,asset_class::US Large Cap,splits_current_through::2020-01-01
```
Set the date to **when that symbol's recorded shares were last
accurate** -- for a lot you entered as transacted, that is on or around
the purchase. zfin then applies every split *after* that date to compute
the **effective** (split-adjusted) share count on the fly. Your
`portfolio.srf`, git history, and snapshots stay exactly as transacted;
only the *displayed and valued* shares reflect the split.
You can see the split zfin will apply with
[`zfin splits`](../reference/cli/splits.md):
```bash
zfin splits NVDA
```
## Copying post-split numbers from your brokerage is fine
Brokerages *do* restate: after a split, Fidelity or Schwab show you the
post-split share count and split-adjusted cost basis. Recording a lot
from one of those statements is **not** the mistake above -- you are
stating a holding as it stands today, not reaching back to rewrite a lot
that already lived through the split in your file.
The only thing zfin needs to know is whether the split is **already
baked into the number you typed.** The cutover date answers that:
- **Shares entered as transacted (pre-split).** The split happened after
you recorded them, so set `splits_current_through` to a date *before*
the split. zfin applies it.
- **Shares copied post-split from a statement.** The number already
includes the split, so set `splits_current_through` to the statement
date (*after* the split). zfin leaves it alone instead of applying it
twice.
Either way the rule holds: you never went back and edited an existing
lot's shares. You either left it as transacted or recorded a fresh lot
from a statement, and the cutover date tells zfin which.
> **Bright line:** recording a lot from a post-split statement is fine.
> Reaching back to change an *existing* committed lot's `shares` because
> of a split is what corrupts your history. If you are tempted to edit a
> number that is already committed, set the cutover instead.
## Verify
`zfin audit`'s hygiene check lists any held symbol that has a
post-purchase split but no `splits_current_through` yet, under
**Unhandled stock splits** -- so you can see at a glance which rows still
need the line:
```bash
zfin audit
```
Once set, [`zfin portfolio`](../reference/cli/portfolio.md) shows the
effective (split-adjusted) share count and the correct market value,
while your files stay untouched.
## Next steps
- [`metadata.srf` reference](../reference/config/metadata-srf.md#stock-split-adjustment) -- the field's exact semantics.
- [Track contributions](track-contributions.md) -- why the git diff must stay honest.
- [Snapshots and history](snapshots-and-history.md) -- the frozen records a restate would desync.
---
[Previous: Snapshots and history](snapshots-and-history.md) | [Next: Plan for retirement](plan-retirement.md) | [Documentation home](../README.md)

View file

@ -310,4 +310,4 @@ every field is documented in the
---
[Previous: Snapshots and history](snapshots-and-history.md) | [Next: Audit against your brokerage](audit-against-brokerage.md) | [Documentation home](../README.md)
[Previous: Handle a stock split](handle-a-stock-split.md) | [Next: Audit against your brokerage](audit-against-brokerage.md) | [Documentation home](../README.md)

View file

@ -147,4 +147,4 @@ example includes one spanning 2016-2024.
---
[Previous: Track contributions](track-contributions.md) | [Next: Plan for retirement](plan-retirement.md) | [Documentation home](../README.md)
[Previous: Track contributions](track-contributions.md) | [Next: Handle a stock split](handle-a-stock-split.md) | [Documentation home](../README.md)