212 lines
11 KiB
Markdown
212 lines
11 KiB
Markdown
# `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`](../cli/analysis.md), 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**.
|
|
|
|
```srf
|
|
#!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`](../cli/audit.md) 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`](../cli/audit.md) 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:
|
|
|
|
```srf
|
|
#!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:
|
|
|
|
```srf
|
|
#!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:
|
|
|
|
```srf
|
|
#!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`](../cli/analysis.md)
|
|
- 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_date` is 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 doctor`](../cli/doctor.md) warns when
|
|
`harvested` is set without a date, and when a date is in the future.
|
|
- **`zfin audit` nags after 90 days.** A flagless
|
|
[`zfin audit`](../cli/audit.md) lists any account whose figure is more
|
|
than 90 days old under **Stale harvested figures**, so it's part of
|
|
your normal maintenance sweep. The threshold isn't configurable, and
|
|
doesn't need to be: only accounts that declare `harvested` are ever
|
|
considered, so not wanting the nag and not wanting the field are the
|
|
same choice.
|
|
- **Entries older than 12 months stop displaying.** Harvest data that
|
|
old isn't decision-useful. This is designed behavior, not an error --
|
|
`doctor` stays quiet about it. Refresh the figure and the date to
|
|
bring it back.
|
|
|
|
The audit nag keeps firing past 12 months, and says
|
|
`no longer displayed` once the annotation has retired -- otherwise the
|
|
annotation would simply vanish with nothing to explain why.
|
|
- **The sign doesn't matter.** `harvested:num:45300` and
|
|
`harvested:num:-45300` are 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.
|
|
|
|
The two thresholds together: fresh for 90 days, nagged from 90 to 365,
|
|
nagged *and* hidden past 365.
|
|
|
|
### 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](../../guides/set-up-accounts.md#renaming-an-account).
|
|
|
|
## `update_cadence` and the audit nag
|
|
[`zfin audit`](../cli/audit.md) (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`](../cli/contributions.md) 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`](../cli/analysis.md)
|
|
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:false` on 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:true` to 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`)
|
|
|
|
```srf
|
|
#!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](../../guides/set-up-accounts.md) -- the walkthrough.
|
|
- [`zfin analysis`](../cli/analysis.md) -- tax-type and account breakdowns.
|
|
- [`zfin audit`](../cli/audit.md) -- staleness checks and broker reconciliation.
|
|
|
|
---
|
|
|
|
[Documentation home](../../README.md#reference)
|