zfin/docs/guides/set-up-accounts.md
Emil Lerch 0e1a4862db
All checks were successful
Generic zig build / build (push) Successful in 4m56s
Generic zig build / publish-macos (push) Successful in 17s
Generic zig build / deploy (push) Successful in 24s
add stale harvesting date checks to audit command
2026-07-25 10:45:12 -07:00

8.9 KiB

Map your accounts

Goal: create an accounts.srf that tags each account with its tax treatment (and, optionally, its institution and maintenance cadence). This unlocks the By Tax Type breakdown, an umbrella-insurance exposure estimate, and broker reconciliation.

You'll need: a portfolio.srf whose lots use account:: labels (build one first). Full field list: accounts.srf reference.

1. List your accounts with a tax type

One record per account. The account:: name must match the account:: value on your lots exactly. The minimum is a tax type:

#!srfv1
account::Pat 401k,tax_type::traditional
account::Pat Roth,tax_type::roth
account::Joint taxable,tax_type::taxable
account::Family HSA,tax_type::hsa

The four recognized tax types are taxable, roth, traditional, and hsa. Run analysis to see the breakdown:

ZFIN_HOME=~/finance zfin analysis
  By Tax Type
  Traditional (Pre-Tax)    █████████████████▋              58.9%  $815,290.06
  Taxable                  ██████▍                         21.6%  $299,010.60
  Roth (Post-Tax)          █████                           16.9%  $233,732.95
  HSA (Triple Tax-Free)    ▊                                2.5%  $35,104.20

Accounts you don't list show up as "Unknown."

2. Add institution and account number (for auditing)

If you plan to reconcile against brokerage exports (audit guide), add the institution and a (placeholder) account number so zfin can match export files to accounts:

account::Pat 401k,tax_type::traditional,institution::fidelity,account_number::P401
account::Joint taxable,tax_type::taxable,institution::schwab,account_number::JT01

Recognized institution keys include fidelity, schwab, vanguard, and wells_fargo.

3. Tune the maintenance cadence

zfin audit (run with no flags) nags you about accounts you haven't refreshed recently. The default cadence is weekly; relax or silence it per account:

account::Family HSA,tax_type::hsa,update_cadence::quarterly
account::Old Rollover,tax_type::traditional,update_cadence::none

4. Advanced flags

Four optional fields change how analysis and the audit treat an account -- see the reference for details:

  • shielded:bool:false -- mark a pre-tax account that is not judgment-protected (deferred comp, a weak-state IRA) so it counts toward your umbrella-insurance exposure -- the slice of net worth a personal umbrella liability policy is meant to cover.

  • cash_is_contribution:bool:true -- treat cash increases on this account as real external contributions in zfin contributions, instead of internal noise.

  • direct_indexing:bool:true -- mark an account whose lots track a benchmark with tracking-error drift rather than holding it directly, so routine share reconciliation stops registering as money movement.

  • audit_large_lot_threshold:num:50000 -- raise (or lower) the dollar cutoff at which a flagless zfin audit nudges you to confirm a new lot's source. The default is $10,000; bump it on a noisy ESPP/payroll account so routine accruals stop spamming the report, while leaving quieter accounts at the default:

    account::Sample ESPP,tax_type::taxable,audit_large_lot_threshold:num:50000
    

5. Track tax-loss harvested on a synthetic account

If you run a tax-loss-harvesting sleeve (direct indexing, a managed separate account), you probably don't want its hundreds of churning positions in portfolio.srf. The usual shape is one aggregate lot with a ticker:: alias standing in for the whole sleeve:

symbol::DI-SPX,ticker::SPY,shares:num:1000,open_date::2024-01-15,open_price:num:400,account::Sample Tax Loss

That keeps the file readable, but it means zfin has no closed lots to work from, so it can't compute realized gain/loss -- and the harvested total is the whole reason the account exists. Record it by hand instead:

account::Sample Tax Loss,tax_type::taxable,direct_indexing:bool:true,harvested:num:45300,harvested_date::2026-06-24

Next time you check the figure on the brokerage site, update both fields. It shows up as a compact annotation beside the account name in zfin analysis, the TUI Analysis tab, the TUI account picker (a), and the TUI Portfolio tab header when you filter to that account:

  By Account
  Sample Tax Loss          █████████████████████████████   96.7%  $738,930.00  (45k 6/24)

The date is required -- an undated hand-copied number is indistinguishable from a stale one, so zfin won't display it and zfin doctor will tell you. The sign is ignored, so -45300 works too.

You don't have to remember to refresh it. A flagless zfin audit lists anything more than 90 days old, so it rides along with your normal maintenance sweep:

  Stale harvested figures (>90 days - refresh 'harvested' in accounts.srf)
    Sample Tax Loss                  last updated 146 days ago

Past 12 months the annotation retires itself -- data that old isn't decision-useful. The audit nag keeps going and says no longer displayed, so the figure never disappears silently.

Don't put the number in the account name. It's tempting -- the name already prints in a lot of places -- but the name is a join key, not a label. See the next section for what a rename costs you.

Renaming an account

Renaming an account is a bigger deal than it looks, because the account name is zfin's only identifier for an account. There is no separate account ID. The name is matched byte-exactly against account:: on every lot, and it is written into every historical snapshot at the time that snapshot is taken.

To rename, change it in both accounts.srf and every account:: on your portfolio.srf lots, in a single commit. Then expect the following:

One-time noise in contributions and compare. zfin identifies a lot by (security_type, symbol, account, open_date, open_price). Changing the account breaks that key on every lot in the account, so zfin contributions and zfin compare see the old lots vanish and identical new ones appear -- indistinguishable from closing one account and opening another. Any review window spanning the rename commit will report phantom new lots and an inflated contribution total, and a flagless zfin audit will list them under Large new lots - confirm source. This happens exactly once, for the one window that straddles the rename. Nothing is corrupted; just ignore that run.

Truncated audit-staleness history. The cadence nag derives each account's last-updated time by walking portfolio.srf's git history. Older commits only contain the old name, so the account's update history restarts at the rename commit. The rename itself registers as an update, so the account reads as freshly refreshed rather than falsely overdue.

A permanent split in per-account snapshot history. Snapshots already on disk record the old name and are immutable historical records -- zfin does not rewrite them. Any per-account historical series therefore has a discontinuity at the rename date. This one does not heal.

Possible renumbering in the TUI account picker. Shortcut keys are assigned by list position (accounts.srf order, then alphabetical for anything not listed), so a name that sorts differently can shift the keys. Cosmetic.

zfin import is unaffected: it resolves accounts by (institution, account_number) and looks the name up in accounts.srf, so a consistent rename in both files keeps working.

The practical advice: pick account names that never need to change. Don't encode anything that varies -- balances, dates, harvested totals, "current"/"old" markers. Values that change belong in fields (see harvested), not in the key.

Example (from examples/pre-retirement-both)

#!srfv1
account::Pat 401k,tax_type::traditional,institution::fidelity,account_number::P401
account::Pat Roth,tax_type::roth,institution::fidelity,account_number::PROTH
account::Sam 401k,tax_type::traditional,institution::vanguard,account_number::S401
account::Joint taxable,tax_type::taxable,institution::schwab,account_number::JT01
account::Family HSA,tax_type::hsa,institution::fidelity,account_number::HSA01

Next steps


Previous: Classify your holdings | Next: Read your portfolio | Documentation home