5.1 KiB
transaction_log.srf reference
transaction_log.srf is an optional sibling of portfolio.srf that
declares real-world transactions which change how zfin interprets the
portfolio diff. It holds exactly one kind of record: transfers --
money or securities you moved between accounts you own.
Why it exists
zfin contributions infers "new money" by
diffing your portfolio over time. A plain diff can't tell an external
contribution apart from an internal transfer, so moving (say) $50k from
one account to another would otherwise be double-counted: the
receiving account's new lots look like contributions while the sending
account's removed lots are ignored. Declaring the transfer here cancels
that out.
Missing file -> the matcher is a no-op; nothing changes.
File format
One record per destination. A record pins the money to exactly one
landing spot: either a specific lot (SYMBOL@open_date) or the literal
token cash. A sweep that lands in several lots becomes several records
sharing the same (date, from, to) but differing in dest_lot.
#!srfv1
# Simple cash transfer between two accounts
transfer::2026-05-02,type::cash,amount:num:5000,from::Sample IRA,to::Sample Brokerage,dest_lot::cash
# Transfer that was invested into a single stock lot on arrival
transfer::2026-05-02,type::cash,amount:num:7000,from::Sample IRA,to::Sample Brokerage,dest_lot::VTI@2026-05-03
# Sweep into a basket plus a cash residual (two records, same date/from/to)
transfer::2026-05-02,type::cash,amount:num:145300,from::Sample IRA,to::Sample Brokerage,dest_lot::VTI@2026-05-03
transfer::2026-05-02,type::cash,amount:num:4700,from::Sample IRA,to::Sample Brokerage,dest_lot::cash
Fields
| Field | Type | Required | Description |
|---|---|---|---|
transfer |
date | Yes | Transfer date (YYYY-MM-DD); the record key. |
type |
string | Yes | cash (money moved) or in_kind (securities moved). See below. |
amount |
num | Yes | Dollar amount transferred to this destination. |
from |
string | Yes | Source account name (matches an account:: in your portfolio). |
to |
string | Yes | Destination account name. |
dest_lot |
string | Yes | Where it landed: cash, or SYMBOL@YYYY-MM-DD for a specific lot. |
type::cash vs type::in_kind
Both are matched by the contributions classifier; they differ in what moved and therefore in how the record is verified.
type::cash -- dollars left one account and arrived at another,
possibly getting invested on arrival. The amount is load-bearing: it's
matched against the destination's value, and the three outcomes are
- within $1 -- fully a transfer; contributes $0 to attribution.
- destination worth more than
amount-- a partial transfer. Onlyamountis cancelled out; the excess is still counted as new money. That's the "I moved $5k in and also added $2k of my own" case. amountexceeds the destination's value by more than $1 -- rejected, and reported under Flagged for review. You can't have moved more into a lot than the lot is worth, so the record is presumed wrong rather than trusted.
type::in_kind -- the securities themselves moved (an ACAT
transfer, an in-kind rollover); no cash changed hands. Requires
dest_lot::SYMBOL@YYYY-MM-DD -- dest_lot::cash is rejected, since
nothing can land as cash in a transfer where no cash moved.
#!srfv1
# 300 shares of VTI moved from one account to the other, no cash involved
transfer::2026-05-02,type::in_kind,amount:num:87000,from::Sample IRA,to::Sample Brokerage,dest_lot::VTI@2024-01-15
Here amount is informational only. The moved value comes from the
destination lot itself, and verification is by share count: the
shares that left from must match the shares that arrived at to,
within 1% or 0.01 shares (whichever is larger). A mismatch is reported
under Flagged for review rather than silently absorbing what might
be a real contribution.
A missing source side is not an error for either type -- the sending account may be untracked (an external rollover origin, a 401k you don't model). Only the destination is required.
Because no cash funded an in-kind move, there is no partial outcome: a destination is either fully a transfer or not one at all.
Scope and limits
- Only
transfer::records. Buys, sells, and dividends stay inferred from the portfolio diff. - Forward-looking only -- there is no historical reconstruction.
- Account names are matched byte-exactly, so a renamed account breaks records that reference the old name.
See also
- Track contributions -- the walkthrough.
zfin contributions-- the command that consumes this file.