From c08e89a025e6f9f51c8e8ff4dfc82c6f1db6bb79 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Mon, 6 Jul 2026 14:09:18 -0700 Subject: [PATCH] update docs for split adjustment --- docs/reference/cli/audit.md | 16 +++++-- docs/reference/cli/splits.md | 6 +++ docs/reference/config/metadata-srf.md | 58 ++++++++++++++++++++++---- docs/reference/config/portfolio-srf.md | 14 ++++++- 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/docs/reference/cli/audit.md b/docs/reference/cli/audit.md index 934c833..def2c2f 100644 --- a/docs/reference/cli/audit.md +++ b/docs/reference/cli/audit.md @@ -8,10 +8,10 @@ Usage: zfin audit [opts] ``` **Flagless** runs the hygiene check -- stale manual prices, -accounts overdue for update, and auto-discovered brokerage-file -candidates. **With brokerage flags**, it reconciles your portfolio -against the export (treating the brokerage as source of truth) and -reports discrepancies. +accounts overdue for update, unhandled stock splits, and +auto-discovered brokerage-file candidates. **With brokerage flags**, it +reconciles your portfolio against the export (treating the brokerage as +source of truth) and reports discrepancies. ## Options @@ -34,6 +34,13 @@ The cutoff is per account -- raise or lower it on an account's record via [`audit_large_lot_threshold`](../config/accounts-srf.md#audit_large_lot_threshold) in `accounts.srf` (e.g. to silence a noisy ESPP account). +It also lists held symbols that had a stock split *after* a lot's +purchase date but haven't opted into automatic split adjustment, in an +**Unhandled stock splits** section. Each needs a +[`splits_current_through`](../config/metadata-srf.md#stock-split-adjustment) +date on its `metadata.srf` row -- without it, that holding's shares (and +every value derived from them) are misstated across the split. + ## Example (hygiene check) ```bash @@ -59,6 +66,7 @@ ZFIN_HOME=examples/pre-retirement-both zfin audit - [Audit against your brokerage](../../guides/audit-against-brokerage.md) -- the workflow. - [`import`](import.md) -- build a portfolio file *from* an export instead. - [`accounts.srf` reference](../config/accounts-srf.md) -- `update_cadence`, `institution`, `account_number`. +- [`metadata.srf` reference](../config/metadata-srf.md#stock-split-adjustment) -- `splits_current_through` for the unhandled-splits check. --- diff --git a/docs/reference/cli/splits.md b/docs/reference/cli/splits.md index b490858..a9d83b8 100644 --- a/docs/reference/cli/splits.md +++ b/docs/reference/cli/splits.md @@ -11,6 +11,11 @@ Polygon (`POLYGON_API_KEY`), merged with per-row split factors from Tiingo's price series (which rescues events Polygon's reference endpoint occasionally misses). +This is the same split feed that drives portfolio split adjustment: to +have a split reflected in your holdings' effective shares, set +[`splits_current_through`](../config/metadata-srf.md#stock-split-adjustment) +on the symbol's `metadata.srf` row. + ## Example ```bash @@ -35,6 +40,7 @@ Split History for AAPL - [`divs`](divs.md) -- dividend history for a symbol. - [`history`](history.md) -- split-adjusted price history. +- [`metadata.srf`](../config/metadata-srf.md#stock-split-adjustment) -- apply a split to your holdings via `splits_current_through`. --- diff --git a/docs/reference/config/metadata-srf.md b/docs/reference/config/metadata-srf.md index b64b2f5..c4e3181 100644 --- a/docs/reference/config/metadata-srf.md +++ b/docs/reference/config/metadata-srf.md @@ -25,15 +25,16 @@ symbol::AGG,sector::Bonds,geo::US,asset_class::Bonds ## Fields -| Field | Type | Required | Default | Description | -|---------------|--------|----------|-----------|---------------------------------------------------------------------------------------------------------------------------| -| `symbol` | string | Yes | -- | Ticker or CUSIP. Must match `symbol::` (or `ticker::`) on a portfolio lot. | -| `name` | string | No | -- | Human-readable security name (e.g. "SPDR S&P 500 ETF Trust"). Shown where available; falls back to the symbol. | -| `asset_class` | string | No | -- | e.g. `US Large Cap`, `Bonds`, `International Developed`, `Emerging Markets`. | -| `sector` | string | No | -- | e.g. `Technology`, `Healthcare`, `Financials`, `Diversified`, `Bonds`. | -| `geo` | string | No | -- | e.g. `US`, `International Developed`, `Emerging Markets`. | -| `bucket` | string | No | (derived) | User-curated grouping label that overrides the auto-derived sector bucket for concentration/dominance checks (see below). | -| `pct` | number | No | `100` | Weight of this allocation line for the symbol. Use multiple lines for blended funds. | +| Field | Type | Required | Default | Description | +|--------------------------|--------|----------|-----------|-------------------------------------------------------------------------------------------------------------------------------| +| `symbol` | string | Yes | -- | Ticker or CUSIP. Must match `symbol::` (or `ticker::`) on a portfolio lot. | +| `name` | string | No | -- | Human-readable security name (e.g. "SPDR S&P 500 ETF Trust"). Shown where available; falls back to the symbol. | +| `asset_class` | string | No | -- | e.g. `US Large Cap`, `Bonds`, `International Developed`, `Emerging Markets`. | +| `sector` | string | No | -- | e.g. `Technology`, `Healthcare`, `Financials`, `Diversified`, `Bonds`. | +| `geo` | string | No | -- | e.g. `US`, `International Developed`, `Emerging Markets`. | +| `bucket` | string | No | (derived) | User-curated grouping label that overrides the auto-derived sector bucket for concentration/dominance checks (see below). | +| `pct` | number | No | `100` | Weight of this allocation line for the symbol. Use multiple lines for blended funds. | +| `splits_current_through` | date | No | (unset) | Per-symbol opt-in for automatic stock-split adjustment (`YYYY-MM-DD`). See [Stock split adjustment](#stock-split-adjustment). | Cash and CD lots are classified as "Cash & CDs" automatically -- they need no metadata entry. @@ -64,6 +65,44 @@ symbol::02315N600,asset_class::Bonds,pct:num:15 symbol::02315N600,asset_class::Emerging Markets,pct:num:10 ``` +## Stock split adjustment + +Lots in [`portfolio.srf`](portfolio-srf.md) are immutable historical +records -- the share count and price exactly as transacted. zfin never +rewrites them for a stock split; instead it derives *effective* +(split-adjusted) shares on read, so a position held across a split is +valued correctly without you editing the file. + +This adjustment is **opt-in, per symbol**. Add +`splits_current_through::YYYY-MM-DD` to a symbol's row to enable it for +that symbol: + +```srf +#!srfv1 +symbol::NVDA,sector::Technology,geo::US,asset_class::US Large Cap,splits_current_through::2024-01-01 +``` + +The date is the point through which that symbol's recorded shares are +*already current*. zfin auto-applies any split that occurs **after** it; +splits on or before it are assumed already reflected in your recorded +shares. Set it to the date you last reconciled (or entered) that +holding: + +- If you record lots **as transacted** (pre-split share counts), set the + cutover *before* the split so zfin applies it. +- If you entered a lot with **post-split** share counts -- common when + copying from a broker statement after the fact, e.g. an AMZN lot + entered after its 2022 20:1 split -- set the cutover *after* that split + so zfin does not double-apply it. + +When the field is absent, split adjustment is **off** for that symbol -- +identical to zfin's behavior before this feature existed. Split data +comes from the same feed as [`zfin splits`](../cli/splits.md). + +[`zfin audit`](../cli/audit.md) lists held symbols that have a +post-purchase split but no `splits_current_through` yet (under +"Unhandled stock splits"), so you know which rows still need it. + ## Example (from `examples/pre-retirement-both`) ```srf @@ -81,6 +120,7 @@ symbol::AGG,sector::Bonds,geo::US,asset_class::Bonds - [`zfin enrich`](../cli/enrich.md) -- bootstrap this file from Wikidata + SEC EDGAR. - [`zfin analysis`](../cli/analysis.md) -- the breakdowns this file feeds. - [`zfin review`](../cli/review.md) -- the per-holding Sector column and grouping this file feeds. +- [`zfin audit`](../cli/audit.md) -- flags symbols with an unhandled split that need `splits_current_through`. --- diff --git a/docs/reference/config/portfolio-srf.md b/docs/reference/config/portfolio-srf.md index 8cd0b59..2ff89a3 100644 --- a/docs/reference/config/portfolio-srf.md +++ b/docs/reference/config/portfolio-srf.md @@ -147,11 +147,23 @@ Lots marked `drip:bool:true` are collapsed into short-term (ST) and long-term (LT) groups in the position detail view (split on the 1-year capital-gains threshold) rather than listed individually. +## Stock splits + +Do **not** rewrite a lot's `shares` when a stock splits. A lot is an +immutable record of what you transacted -- rewriting it would corrupt +the history that [`zfin contributions`](../cli/contributions.md), +[`compare`](../cli/compare.md), and `audit` read from git. Instead, +opt the symbol into automatic split adjustment with a +[`splits_current_through`](metadata-srf.md#stock-split-adjustment) date +on its `metadata.srf` row; zfin then derives split-adjusted shares on +read. [`zfin audit`](../cli/audit.md) flags any held symbol that needs +this. + ## See also - [Build your portfolio](../../guides/set-up-your-portfolio.md) -- the task-oriented walkthrough. - [`accounts.srf`](accounts-srf.md) -- give each `account::` a tax type. -- [`metadata.srf`](metadata-srf.md) -- classify each `symbol::` for analysis. +- [`metadata.srf`](metadata-srf.md) -- classify each `symbol::` for analysis; opt into split adjustment. - [`zfin portfolio`](../cli/portfolio.md) and [`zfin import`](../cli/import.md). ---