diff --git a/docs/README.md b/docs/README.md index 2de097c..7791557 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 | diff --git a/docs/guides/handle-a-stock-split.md b/docs/guides/handle-a-stock-split.md new file mode 100644 index 0000000..529442f --- /dev/null +++ b/docs/guides/handle-a-stock-split.md @@ -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) diff --git a/docs/guides/plan-retirement.md b/docs/guides/plan-retirement.md index 88dbbef..5d93c21 100644 --- a/docs/guides/plan-retirement.md +++ b/docs/guides/plan-retirement.md @@ -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) diff --git a/docs/guides/snapshots-and-history.md b/docs/guides/snapshots-and-history.md index d25141d..0f20013 100644 --- a/docs/guides/snapshots-and-history.md +++ b/docs/guides/snapshots-and-history.md @@ -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)