update docs for split adjustment
All checks were successful
Generic zig build / build (push) Successful in 4m53s
Generic zig build / publish-macos (push) Successful in 11s
Generic zig build / deploy (push) Successful in 20s

This commit is contained in:
Emil Lerch 2026-07-06 14:09:18 -07:00
parent 9aaa374f12
commit c08e89a025
Signed by: lobo
GPG key ID: A7B62D657EF764F8
4 changed files with 80 additions and 14 deletions

View file

@ -8,10 +8,10 @@ Usage: zfin audit [opts]
``` ```
**Flagless** runs the hygiene check -- stale manual prices, **Flagless** runs the hygiene check -- stale manual prices,
accounts overdue for update, and auto-discovered brokerage-file accounts overdue for update, unhandled stock splits, and
candidates. **With brokerage flags**, it reconciles your portfolio auto-discovered brokerage-file candidates. **With brokerage flags**, it
against the export (treating the brokerage as source of truth) and reconciles your portfolio against the export (treating the brokerage as
reports discrepancies. source of truth) and reports discrepancies.
## Options ## 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) via [`audit_large_lot_threshold`](../config/accounts-srf.md#audit_large_lot_threshold)
in `accounts.srf` (e.g. to silence a noisy ESPP account). 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) ## Example (hygiene check)
```bash ```bash
@ -59,6 +66,7 @@ ZFIN_HOME=examples/pre-retirement-both zfin audit
- [Audit against your brokerage](../../guides/audit-against-brokerage.md) -- the workflow. - [Audit against your brokerage](../../guides/audit-against-brokerage.md) -- the workflow.
- [`import`](import.md) -- build a portfolio file *from* an export instead. - [`import`](import.md) -- build a portfolio file *from* an export instead.
- [`accounts.srf` reference](../config/accounts-srf.md) -- `update_cadence`, `institution`, `account_number`. - [`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.
--- ---

View file

@ -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 Tiingo's price series (which rescues events Polygon's reference
endpoint occasionally misses). 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 ## Example
```bash ```bash
@ -35,6 +40,7 @@ Split History for AAPL
- [`divs`](divs.md) -- dividend history for a symbol. - [`divs`](divs.md) -- dividend history for a symbol.
- [`history`](history.md) -- split-adjusted price history. - [`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`.
--- ---

View file

@ -25,15 +25,16 @@ symbol::AGG,sector::Bonds,geo::US,asset_class::Bonds
## Fields ## Fields
| Field | Type | Required | Default | Description | | Field | Type | Required | Default | Description |
|---------------|--------|----------|-----------|---------------------------------------------------------------------------------------------------------------------------| |--------------------------|--------|----------|-----------|-------------------------------------------------------------------------------------------------------------------------------|
| `symbol` | string | Yes | -- | Ticker or CUSIP. Must match `symbol::` (or `ticker::`) on a portfolio lot. | | `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. | | `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`. | | `asset_class` | string | No | -- | e.g. `US Large Cap`, `Bonds`, `International Developed`, `Emerging Markets`. |
| `sector` | string | No | -- | e.g. `Technology`, `Healthcare`, `Financials`, `Diversified`, `Bonds`. | | `sector` | string | No | -- | e.g. `Technology`, `Healthcare`, `Financials`, `Diversified`, `Bonds`. |
| `geo` | string | No | -- | e.g. `US`, `International Developed`, `Emerging Markets`. | | `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). | | `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. | | `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 Cash and CD lots are classified as "Cash & CDs" automatically -- they
need no metadata entry. 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 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`) ## Example (from `examples/pre-retirement-both`)
```srf ```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 enrich`](../cli/enrich.md) -- bootstrap this file from Wikidata + SEC EDGAR.
- [`zfin analysis`](../cli/analysis.md) -- the breakdowns this file feeds. - [`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 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`.
--- ---

View file

@ -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 long-term (LT) groups in the position detail view (split on the
1-year capital-gains threshold) rather than listed individually. 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 ## See also
- [Build your portfolio](../../guides/set-up-your-portfolio.md) -- the task-oriented walkthrough. - [Build your portfolio](../../guides/set-up-your-portfolio.md) -- the task-oriented walkthrough.
- [`accounts.srf`](accounts-srf.md) -- give each `account::` a tax type. - [`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). - [`zfin portfolio`](../cli/portfolio.md) and [`zfin import`](../cli/import.md).
--- ---