This also provides ensurance through zig build test that all fields are documented in markdown so they do not get out of sync
208 lines
12 KiB
Markdown
208 lines
12 KiB
Markdown
# `portfolio.srf` reference
|
|
|
|
Your portfolio is a plain-text [SRF](https://git.lerch.org/lobo/srf)
|
|
file with **one lot per line**. A lot is a batch of shares of one
|
|
security bought on one date in one account. Positions are aggregated
|
|
from lots automatically.
|
|
|
|
zfin looks for `portfolio.srf` in `ZFIN_HOME` (or the current
|
|
directory). You can split holdings across several `portfolio_*.srf`
|
|
files; zfin union-merges every match (see
|
|
[Build your portfolio](../../guides/set-up-your-portfolio.md) for the
|
|
multi-file workflow).
|
|
|
|
## File format
|
|
|
|
Every SRF line is a comma-separated list of `key::value` pairs. Typed
|
|
values use a type tag:
|
|
|
|
- `key::value` -- string
|
|
- `key:num:value` -- number
|
|
- `key:bool:value` -- boolean (`true` / `false`)
|
|
|
|
The first line must be the version header `#!srfv1`. Lines beginning
|
|
with `#` are comments.
|
|
|
|
```srf
|
|
#!srfv1
|
|
# A stock/ETF lot
|
|
symbol::VTI,shares:num:100,open_date::2024-01-15,open_price:num:220.50,account::Brokerage
|
|
```
|
|
|
|
## Lot fields
|
|
|
|
| Field | Type | Required | Description |
|
|
|-----------------|--------|----------|---------------------------------------------------------------------------------------|
|
|
| `symbol` | string | Yes\* | Ticker or CUSIP. \*Optional for `cash` lots. |
|
|
| `shares` | number | Yes | Share count (or face value for cash/CDs). Negative for short option positions. |
|
|
| `open_date` | string | Yes\*\* | Purchase date `YYYY-MM-DD`. \*\*Not required for `cash`/`watch`. |
|
|
| `open_price` | number | Yes\*\* | Purchase price per share. \*\*Not required for `cash`/`watch`. |
|
|
| `close_date` | string | No | Sale date. Omit for an open lot. See [Closed lots](#closed-lots). |
|
|
| `close_price` | number | No | Sale price per share. See [Closed lots](#closed-lots). |
|
|
| `security_type` | string | No | `stock` (default), `option`, `cd`, `cash`, `illiquid`, `watch`. |
|
|
| `account` | string | No | Account name. Should match an `account::` entry in [`accounts.srf`](accounts-srf.md). |
|
|
| `note` | string | No | Free-text note (shown in cash/CD/illiquid tables). |
|
|
| `label` | string | No | Display name for the symbol column, overriding `symbol`/`ticker` |
|
|
| `ticker` | string | No | Ticker alias used for price fetching when `symbol` is a CUSIP. |
|
|
| `price` | number | No | Manual price override (for securities the providers don't cover). |
|
|
| `price_date` | string | No | Date of the manual price (`YYYY-MM-DD`), for staleness display. |
|
|
| `drip` | bool | No | `true` if the lot is from dividend reinvestment (summarized as ST/LT groups). |
|
|
| `maturity_date` | string | No | CD maturity or option expiry date (`YYYY-MM-DD`). |
|
|
| `rate` | number | No | CD interest rate (e.g. `5.25` = 5.25%). |
|
|
|
|
## Security types
|
|
|
|
| Type | Meaning |
|
|
|-------------------|------------------------------------------------------------------------------------------------|
|
|
| `stock` (default) | Stocks, ETFs, mutual funds. Priced from Tiingo (Yahoo fallback), aggregated by symbol. |
|
|
| `option` | Option contracts. Shown separately; `shares` is the contract count (negative = written/short). |
|
|
| `cd` | Certificates of deposit. Shown by maturity with rate and face value. |
|
|
| `cash` | Cash, money-market, settlement balances. Grouped by account. |
|
|
| `illiquid` | Real estate, vehicles, etc. Excluded from the liquid total; included in Net Worth. |
|
|
| `watch` | Watchlist item: tracks price only, no position. See [`watchlist.srf`](watchlist-srf.md). |
|
|
|
|
## Examples
|
|
|
|
Each line below is valid on its own. These mirror the bundled
|
|
[`examples/`](../../../examples/) portfolios.
|
|
|
|
```srf
|
|
#!srfv1
|
|
# Stocks / ETFs (multiple lots of the same symbol aggregate)
|
|
symbol::VTI,shares:num:1100,open_date::2018-06-15,open_price:num:140.00,account::Pat 401k
|
|
symbol::VTI,shares:num:240,open_date::2015-01-08,open_price:num:103.40,account::Pat Roth
|
|
|
|
# Cash (no symbol, no open_date/open_price needed)
|
|
security_type::cash,shares:num:48000.00,open_date::2026-04-30,open_price:num:1.00,account::Joint taxable
|
|
|
|
# Closed (sold) lot
|
|
symbol::AMZN,shares:num:10,open_date::2022-03-15,open_price:num:150.25,close_date::2024-01-15,close_price:num:185.50
|
|
|
|
# DRIP lot (dividend reinvestment; summarized as ST/LT groups)
|
|
symbol::VTI,shares:num:0.234,open_date::2024-06-15,open_price:num:267.50,drip:bool:true,account::Brokerage
|
|
|
|
# CUSIP with a ticker alias for pricing (e.g. a 401k CIT share class)
|
|
symbol::02315N600,shares:num:1200,open_date::2022-01-01,open_price:num:140.00,ticker::VTTHX,account::Fidelity 401k,note::VANGUARD TARGET 2035
|
|
|
|
# Manual price override (security with no provider coverage)
|
|
symbol::NON40OR52,shares:num:500,open_date::2023-01-01,open_price:num:155.00,price:num:163.636,price_date::2026-02-27,account::Fidelity 401k
|
|
|
|
# Option: a written (short) call. Negative shares = contracts sold.
|
|
security_type::option,symbol::AAPL 06/20/2025 200.00 C,shares:num:-2,open_date::2025-01-15,open_price:num:12.50,option_type::call,underlying::AAPL,strike:num:200,maturity_date::2025-06-20,account::Brokerage
|
|
|
|
# CD
|
|
security_type::cd,symbol::912797KR0,shares:num:10000,open_date::2024-06-01,open_price:num:10000,maturity_date::2025-06-01,rate:num:5.25,account::Brokerage,note::6-Month T-Bill
|
|
|
|
# Illiquid asset (net worth only)
|
|
security_type::illiquid,symbol::HOME,shares:num:450000,open_date::2020-06-01,open_price:num:350000,note::Primary residence
|
|
```
|
|
|
|
## Closed lots
|
|
|
|
A lot with `close_date` set is sold. Both fields are optional -- an open
|
|
lot has neither -- but a sold lot should carry both, because they drive
|
|
two things beyond bookkeeping:
|
|
|
|
- **Realized gain/loss** comes from `close_price` against `open_price`.
|
|
- **[`zfin contributions`](../cli/contributions.md#internal-movement)**
|
|
treats the close as a sale and values it at `close_price`. That
|
|
matters when you sell one holding to buy another: the proceeds offset
|
|
the repurchase instead of it reading as new money. Without a
|
|
`close_price` the current market price stands in, which is close
|
|
enough for a recent sale and wrong for an old one.
|
|
|
|
A `close_date` must not be in the future. A close dated today is already
|
|
closed, but a later date leaves the lot counted as held until then --
|
|
while `zfin contributions` treats it as sold the moment the field
|
|
appears. For a scheduled end (a CD or option), use `maturity_date`.
|
|
|
|
`zfin audit` and `zfin doctor` flag these lot-lifecycle mistakes:
|
|
|
|
- `close_date` in the future, or before `open_date`.
|
|
- `close_date` without `close_price` on a stock lot (the realized gain
|
|
is recorded as 0), or `close_price` without `close_date` on any lot.
|
|
- `open_date` in the future (the lot is not counted until then).
|
|
- `price_date` in the future on a stock lot (the manual price is never
|
|
flagged as stale).
|
|
|
|
Watch lots are exempt; zfin reads only their `symbol`.
|
|
|
|
You can either edit the lot where it sits or move it to a sibling file
|
|
such as `portfolio_closed.srf` -- the `portfolio*.srf` glob picks it up
|
|
either way, so realized gains and back-dated (`--as-of`) views stay
|
|
correct. See
|
|
[splitting your portfolio](../../guides/set-up-your-portfolio.md).
|
|
|
|
## Price resolution
|
|
|
|
For stock lots, the displayed price is resolved in this order:
|
|
|
|
1. **Live API** -- latest close from cached candles (Tiingo, Yahoo fallback).
|
|
2. **Manual price** -- the lot's `price` field, for securities without coverage.
|
|
3. **Average cost** -- the position's `open_price`, as a last resort.
|
|
|
|
Manual-priced rows render in warning color so you know they may be
|
|
stale; `price_date` tracks when you last updated them.
|
|
|
|
## Ticker aliases and CUSIPs
|
|
|
|
Some securities (notably 401k CIT share classes) are identified by
|
|
CUSIP but have a retail-ticker equivalent for pricing. Use `ticker::`
|
|
so `symbol::` stays the display identifier while the alias drives API
|
|
calls:
|
|
|
|
```srf
|
|
symbol::02315N600,ticker::VTTHX,...
|
|
```
|
|
|
|
If the CUSIP and retail ticker have different NAVs (common for CIT vs.
|
|
retail funds), use a manual `price::` instead of `ticker::`. To resolve
|
|
a CUSIP to a ticker, use [`zfin lookup`](../cli/lookup.md).
|
|
|
|
## Advanced and option fields
|
|
|
|
These are less common but parse on any lot:
|
|
|
|
| Field | Type | Applies to | Description |
|
|
|-----------------|--------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
| `price_ratio` | number | share classes | Multiplier applied to the fetched (alias) price to get the institutional NAV. E.g. if the retail class is $27.78 and the institutional class trades at $144.04, set `price_ratio:num:5.185`. |
|
|
| `underlying` | string | options | Underlying stock symbol (e.g. `AMZN`). |
|
|
| `strike` | number | options | Strike price. |
|
|
| `maturity_date` | string | options | Expiration date (`YYYY-MM-DD`). |
|
|
| `multiplier` | number | options | Shares per contract (default `100`). |
|
|
| `option_type` | string | options | `call` (default) or `put`. |
|
|
|
|
An option lot is defined by these explicit fields, not by decoding the
|
|
symbol -- `symbol` is just a display label, so set it to whatever your
|
|
brokerage shows (e.g. `AAPL 06/20/2025 200.00 C`). For a worked
|
|
two-lot example, see the covered call in
|
|
[Build your portfolio](../../guides/set-up-your-portfolio.md#example-a-covered-call).
|
|
|
|
## DRIP lots
|
|
|
|
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; opt into split adjustment.
|
|
- [`zfin portfolio`](../cli/portfolio.md) and [`zfin import`](../cli/import.md).
|
|
|
|
---
|
|
|
|
[Documentation home](../../README.md#reference)
|