10 KiB
accounts.srf reference
accounts.srf describes each account referenced by your portfolio:
its tax treatment, the institution it lives at, and a few flags that
tune analysis and reconciliation. It powers the By Tax Type and
By Account breakdowns in zfin analysis, the
umbrella-exposure estimate, the audit staleness checks, and broker
reconciliation.
zfin loads accounts.srf from the same directory as the resolved
portfolio file. It is optional -- without it, accounts show up as
"Unknown" in the tax-type breakdown and everything else still works.
File format
One record per account. The account name must match the
account:: value used on your portfolio lots exactly.
#!srfv1
account::Pat 401k,tax_type::traditional,institution::fidelity,account_number::P401
account::Joint taxable,tax_type::taxable,institution::schwab,account_number::JT01
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
account |
string | Yes | -- | Account name; must match account:: on lots exactly. |
tax_type |
string | Yes | -- | taxable, roth, traditional, or hsa. |
institution |
string | No | -- | Broker key, e.g. fidelity, schwab, vanguard, wells_fargo. Used by zfin audit to match export files. |
account_number |
string | No | -- | Account identifier used with institution for audit matching. Use a placeholder, not a full real number. |
update_cadence |
string | No | weekly |
How often you refresh this account's manual data: weekly, monthly, quarterly, or none. Drives the audit staleness nag. |
cash_is_contribution |
bool | No | false |
When true, raw cash-balance increases on this account count as real external contributions (see below). |
direct_indexing |
bool | No | false |
Marks an account whose lots track a benchmark with tracking-error drift (loosens contribution/audit tolerances). |
shielded |
bool | No | (derived) | Umbrella-exposure override (see below). |
audit_large_lot_threshold |
num | No | 10000 |
Per-account dollar cutoff for the audit "Large new lots" nudge (see below). Must be positive. |
harvested |
num | No | -- | Hand-declared cumulative tax-loss-harvested figure, for accounts whose realized P&L zfin cannot derive (see below). |
harvested_date |
date | No | -- | YYYY-MM-DD you last refreshed harvested. Required for it to display. |
Tax types
| Value | Display label |
|---|---|
taxable |
Taxable |
roth |
Roth (Post-Tax) |
traditional |
Traditional (Pre-Tax) |
hsa |
HSA (Triple Tax-Free) |
Any other value is shown as-is. Accounts missing from accounts.srf
appear as "Unknown".
audit_large_lot_threshold
When zfin audit runs flagless, its Large new
lots - confirm source section flags any newly-appeared lot worth at
least this many dollars, nudging you to confirm whether it's a real
external contribution or an unrecorded internal transfer. Smaller new
lots pass silently so routine payroll/ESPP accruals and weekly deposits
don't spam the report.
The threshold is per account, because the noise it fights is account-specific: an ESPP or payroll account that accrues routine large lots wants a high bar, while a taxable brokerage where any sizeable new lot deserves a look wants the default (or lower). Set it on the account's own record:
#!srfv1
account::Sample ESPP,tax_type::taxable,audit_large_lot_threshold:num:50000
account::Sample Brokerage,tax_type::taxable
Here the ESPP account stays quiet until a new lot tops $50k, while
Sample Brokerage (no override) uses the built-in $10,000 default.
Accounts you don't list, or list without the field, use that default.
The value must be positive -- zero or a negative number is rejected
at load time and the account falls back to the default.
harvested and harvested_date
Some accounts hold a number that matters to you but that zfin has no
way to compute. The motivating case is a synthetic direct-indexing
account: a tax-loss-harvesting sleeve holding hundreds of individual
positions that churn constantly. Rather than model all of them, you
track the sleeve as a single aggregate lot with a ticker:: alias:
#!srfv1
symbol::DI-SPX,ticker::SPY,shares:num:1000,open_date::2024-01-15,open_price:num:400,account::Sample Tax Loss
That keeps the portfolio readable, but it means there are no closed
lots, so zfin cannot derive realized gain/loss -- and the harvested
total is the entire point of such an account. harvested is where you
park the figure you read off the brokerage site:
#!srfv1
account::Sample Tax Loss,tax_type::taxable,direct_indexing:bool:true,harvested:num:45300,harvested_date::2026-06-24
It renders as a compact annotation next to the account name:
By Account
Sample Tax Loss █████████████████████████████ 96.7% $738,930.00 (45k 6/24)
It appears in four places:
- the By Account breakdown of
zfin analysis - the same breakdown in the TUI's Analysis tab
- the TUI's account picker (
a), beside the account number - the TUI Portfolio tab header when an account filter is active
Rules
harvested_dateis required for it to display. A hand-copied figure with no date is indistinguishable from a stale one, so zfin refuses to show it.zfin doctorwarns whenharvestedis set without a date, and when a date is in the future.- Entries older than 12 months stop displaying. Harvest data that
old isn't decision-useful. This is designed behavior, not an error --
doctorstays quiet about it. Refresh the figure and the date to bring it back. - The sign doesn't matter.
harvested:num:45300andharvested:num:-45300are equivalent; the annotation's parentheses already carry the accounting "this is a loss" convention. - It is display-only. It never enters a total, a weight, a breakdown value, or the contributions attribution. It is an annotation, not an input.
Why not put it in the account name?
Because the account name is a join key, not a label. It is matched
byte-exactly against account:: on every lot, and it is written into
every historical snapshot. Encoding a changing number in it means
renaming the account every time the number changes, and every rename
costs you history -- see
Renaming an account.
update_cadence and the audit nag
zfin audit (run flagless) flags accounts you
haven't refreshed within their cadence window: weekly = 7 days,
monthly = 30, quarterly = 90, none = never nag. The default is
weekly, so every account reminds you until you silence it -- set
update_cadence::none for accounts that update themselves (a live
brokerage feed) or that you simply don't track closely.
cash_is_contribution
Most cash-balance movement is internal noise -- interest postings,
dividend credits, CD coupons, settlement sweeps -- which would inflate
the zfin contributions attribution total
if counted as new money. So cash deltas are ignored by default. Set
cash_is_contribution:bool:true only on accounts whose cash movement
is dominated by external deposits (payroll ESPP accrual, direct 401k
cash contributions).
shielded (umbrella exposure)
The umbrella-exposure estimate in zfin analysis
splits your liquid net worth into "shielded" (retirement accounts,
assumed judgment-protected) and "exposed" (taxable). The default proxy
is "anything not taxable is shielded." Override it when that's wrong:
shielded:bool:falseon a pre-tax account that is not ERISA-protected (deferred-comp plans, non-qualified annuities), or on IRAs in states with weak IRA protection.shielded:bool:trueto mark a taxable account as shielded (rare; e.g. some asset-protection trusts).
IRA protection varies by state and is not modeled automatically; set this explicitly if it matters to you.
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::Sam Roth,tax_type::roth,institution::vanguard,account_number::SROTH
account::Joint taxable,tax_type::taxable,institution::schwab,account_number::JT01
account::Family HSA,tax_type::hsa,institution::fidelity,account_number::HSA01
account::Kids 529,tax_type::taxable,institution::vanguard,account_number::C529
See also
- Map your accounts -- the walkthrough.
zfin analysis-- tax-type and account breakdowns.zfin audit-- staleness checks and broker reconciliation.