193 lines
7.9 KiB
Markdown
193 lines
7.9 KiB
Markdown
# Build your portfolio
|
|
|
|
**Goal:** create a `portfolio.srf` that captures your holdings -- the
|
|
shares you own, what you paid, and which account each lot lives in.
|
|
Everything else in zfin reads from this file.
|
|
|
|
**You'll need:** a text editor and a private directory outside the
|
|
repo (so you never commit real holdings). This guide builds up a file
|
|
much like the one in
|
|
[`examples/pre-retirement-both`](../../examples/pre-retirement-both/portfolio.srf);
|
|
the full field list lives in the
|
|
[`portfolio.srf` reference](../reference/config/portfolio-srf.md).
|
|
|
|
You only need this one file to begin. `accounts.srf`, `metadata.srf`,
|
|
`projections.srf`, and the rest are optional add-ons you layer on as
|
|
you need them -- zfin works without them, just with fewer breakdowns
|
|
(see [the `.srf` files overview](../explanation/concepts.md#the-srf-files)).
|
|
|
|
## Key terms
|
|
|
|
Three words show up throughout zfin, nesting from smallest to largest:
|
|
|
|
- **Share** -- one unit of a security (a stock, ETF, or fund); the atom
|
|
of ownership.
|
|
- **Lot** -- one purchase: a batch of shares of a single security,
|
|
bought on one date at one price, in one account. A lot is the unit of
|
|
a `portfolio.srf` line. zfin tracks lots rather than running totals so
|
|
it can derive each lot's cost basis, holding period (short- vs
|
|
long-term), and gain/loss.
|
|
- **Position** (or **holding**) -- all the lots of the same security
|
|
rolled together **across every account**: total shares, average cost,
|
|
and market value. Your VTI shows as a single position even when it's
|
|
spread across a 401(k), a Roth IRA, and a taxable brokerage -- a
|
|
whole-household, cross-account rollup most brokerages won't show you,
|
|
and a core reason to run zfin. It's one row in the portfolio summary,
|
|
with its lots indented beneath.
|
|
|
|
In short: you record **lots** (one per line) and zfin aggregates them
|
|
into **positions** for display.
|
|
|
|
## 1. Start the file
|
|
|
|
A portfolio is one lot per line -- one purchase. Create
|
|
`~/finance/portfolio.srf`:
|
|
|
|
```srf
|
|
#!srfv1
|
|
symbol::VTI,shares:num:1100,open_date::2018-06-15,open_price:num:140.00,account::Pat 401k
|
|
```
|
|
|
|
The `#!srfv1` header is required. Each line is comma-separated
|
|
`key::value` pairs; numbers use `key:num:value`.
|
|
|
|
Check it:
|
|
|
|
```bash
|
|
ZFIN_HOME=~/finance zfin portfolio
|
|
```
|
|
|
|
## 2. Add more lots
|
|
|
|
Add one line per purchase. Multiple lots of the same symbol aggregate
|
|
into a single position automatically, so record each buy at its own
|
|
cost basis rather than averaging by hand:
|
|
|
|
```srf
|
|
#!srfv1
|
|
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
|
|
symbol::AGG,shares:num:600,open_date::2020-01-10,open_price:num:115.50,account::Pat 401k
|
|
symbol::QQQ,shares:num:65,open_date::2019-11-22,open_price:num:200.10,account::Pat Roth
|
|
```
|
|
|
|
zfin shows each position with its lots, market value, and gain/loss:
|
|
|
|
```
|
|
VTI 2480.0 $138.35 $373.38 $925,982.40 + $582,874.40 66.9%
|
|
open 1100.0 $140.00 $410,718.00 + $256,718.00 2018-06-15 LT Pat 401k
|
|
open 240.0 $103.40 $89,611.20 + $64,795.20 2015-01-08 LT Pat Roth
|
|
```
|
|
|
|
## 3. Record cash
|
|
|
|
Cash, money-market, and settlement balances are lots with
|
|
`security_type::cash`. They need no symbol, open date, or price:
|
|
|
|
```srf
|
|
security_type::cash,shares:num:48000.00,open_date::2026-04-30,open_price:num:1.00,account::Joint taxable
|
|
```
|
|
|
|
Cash is grouped by account in its own section of the portfolio
|
|
summary.
|
|
|
|
## 4. Use accounts consistently
|
|
|
|
The `account::` value is just a label, but it has to match **exactly**
|
|
across lots (and later in [`accounts.srf`](set-up-accounts.md)). Pick
|
|
names and reuse them verbatim: `Pat 401k`, `Joint taxable`,
|
|
`Family HSA`. Account names drive the By-Account and (with
|
|
`accounts.srf`) the By-Tax-Type breakdowns in
|
|
[`zfin analysis`](read-your-portfolio.md).
|
|
|
|
## 5. Special holdings
|
|
|
|
zfin handles more than stocks and cash. Each is one line; see the
|
|
[reference](../reference/config/portfolio-srf.md) for full field
|
|
lists:
|
|
|
|
- **Sold positions** -- add `close_date` and `close_price` to a lot.
|
|
- **Options** -- `security_type::option` with a readable symbol plus
|
|
explicit `option_type` / `underlying` / `strike` / `maturity_date`
|
|
fields; negative `shares` to write (sell) a contract. See the
|
|
covered-call example below.
|
|
- **CDs** -- `security_type::cd` with `maturity_date` and `rate`.
|
|
- **Illiquid assets** (home, vehicle) -- `security_type::illiquid`;
|
|
counted in Net Worth but not the liquid total.
|
|
- **Securities the providers don't cover** (e.g. a 401k CIT share
|
|
class) -- add a manual `price::` and `price_date::`, or a
|
|
`ticker::` alias for pricing. See
|
|
[price resolution](../reference/config/portfolio-srf.md#price-resolution).
|
|
|
|
### Example: a covered call
|
|
|
|
Options trip people up, so here's a worked one. A *covered call* is two
|
|
lots -- the shares you own, plus one call you write (sell) against them.
|
|
Say you hold 100 shares of MSFT and sell a call:
|
|
|
|
```srf
|
|
#!srfv1
|
|
# The 100 shares you own -- the "covered" part
|
|
symbol::MSFT,shares:num:100,open_date::2024-02-01,open_price:num:400.00,account::Joint taxable
|
|
# One call written against them
|
|
security_type::option,symbol::MSFT 06/19/2026 500.00 C,shares:num:-1,open_date::2026-01-15,open_price:num:6.68,option_type::call,underlying::MSFT,strike:num:500,maturity_date::2026-06-19,account::Joint taxable
|
|
```
|
|
|
|
Reading the option lot:
|
|
|
|
- **`shares:num:-1`** -- you *wrote* one contract; negative means short
|
|
(sold). Each contract covers 100 shares.
|
|
- **`open_price:num:6.68`** -- the premium you received, per share
|
|
($6.68 x 100 = $668 for the contract).
|
|
- **`option_type` / `underlying` / `strike` / `maturity_date`** define
|
|
the contract. `symbol` is just a human-readable label -- use whatever
|
|
your brokerage shows (here `MSFT 06/19/2026 500.00 C`).
|
|
- It's "covered" because the 100 MSFT shares in the same account back
|
|
the call. When it's closed or expires, add `close_date` and
|
|
`close_price` (use `0` if it expired worthless and you kept the
|
|
premium).
|
|
|
|
**zfin values this differently from your brokerage.** Your brokerage
|
|
tracks the call as its own security with its own gain/loss. zfin
|
|
doesn't price the contract at all; instead it caps the covered shares
|
|
at the strike while the call is in-the-money. So if MSFT trades at $510
|
|
here, zfin values these 100 shares at **$50,000** (the $500 strike),
|
|
not $51,000 -- the upside above the strike belongs to the call holder.
|
|
See [covered-call valuation](read-your-portfolio.md#covered-calls) for
|
|
the full rule.
|
|
|
|
## 6. Optional: split across multiple files
|
|
|
|
You can keep holdings in several files -- `portfolio.srf`,
|
|
`portfolio_401k.srf`, `portfolio_taxable.srf`. zfin union-merges every
|
|
`portfolio*.srf` in `ZFIN_HOME` by default, so the CLI and TUI both see
|
|
one combined view. Target a subset with `-p`:
|
|
|
|
```bash
|
|
zfin -p 'portfolio_*.srf' portfolio # quote the glob so the shell doesn't expand it
|
|
zfin -p portfolio.srf -p portfolio_hsa.srf portfolio
|
|
```
|
|
|
|
**A good first split: closed positions.** When you sell, move the
|
|
closed (sold) lots into a `portfolio_closed_positions.srf`. Your main
|
|
`portfolio.srf` then shows only what you currently hold, while the sold
|
|
lots still merge in -- so realized gain/loss and back-dated
|
|
(`--as-of`) snapshots stay accurate. Because the filename matches
|
|
`portfolio*.srf`, it's picked up automatically -- no flag needed.
|
|
|
|
```srf
|
|
#!srfv1
|
|
# Sold lots live here so portfolio.srf stays focused on current holdings.
|
|
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,account::Joint taxable
|
|
```
|
|
|
|
## Next steps
|
|
|
|
- [Classify your holdings](classify-holdings.md) so analysis can break
|
|
down your allocation.
|
|
- [Map your accounts](set-up-accounts.md) to unlock the tax-type view.
|
|
- [Read your portfolio](read-your-portfolio.md) to interpret the output.
|
|
|
|
---
|
|
|
|
[Next: Classify your holdings](classify-holdings.md) | [Documentation home](../README.md)
|