zfin/docs/explanation/concepts.md
Emil Lerch 74fc219afd
All checks were successful
Generic zig build / build (push) Successful in 5m48s
Generic zig build / publish-macos (push) Successful in 11s
Generic zig build / deploy (push) Successful in 23s
add docs/guides
2026-06-22 14:53:53 -07:00

6.8 KiB

Core concepts

A handful of ideas explain how zfin is put together. Once they click, the rest of the tool is predictable.

zfin is a reader, not a database

zfin doesn't store your portfolio. You own a few plain-text files; zfin reads them, fetches market data, and computes. There's no hidden state, no import step, no lock-in -- your data is text you can read, diff, and version-control.

This is why almost everything is grounded in files you edit directly, and why the docs can ship runnable examples.

The .srf files

You don't need any of these to look up a quote, trailing returns, or earnings for a symbol -- those commands need only an API key. The files matter only when you want zfin to track your portfolio, and you add them one at a time as you go. In practice you start with one file, portfolio.srf; everything else is optional, and zfin degrades gracefully when a file is absent (the last column says what you give up).

They all share one format: SRF (Simple Record Format) -- line-oriented, comma-separated key::value pairs (key:num: and key:bool: for typed values), # comments, and a #!srfv1 header.

File Holds When you need it
portfolio.srf Your lots (positions, cash, options, CDs) The one file to start with: required for any portfolio, analysis, or projection view.
accounts.srf Tax type / institution / account number / update cadence per account Optional. Without it, accounts show as "Unknown" in the tax-type breakdown; everything else works.
metadata.srf Sector / geo / asset-class per symbol Optional. Without it, the asset-class / sector / geography breakdowns have nothing to group by; valuation still works.
projections.srf Retirement projection inputs Only for zfin projections, which otherwise runs with sensible defaults.
watchlist.srf Price-only symbols Only if you want a watchlist.
transaction_log.srf Declared transfers Only to refine contribution attribution; missing means it's simply not applied.

A few more files show up only when you opt into a feature: history/*-portfolio.srf snapshots are written for you by zfin snapshot, and ~/.config/zfin/keys.srf / theme.srf customize the TUI. The same SRF format is used internally for the cache and snapshots, so those are inspectable too.

ZFIN_HOME: where your data lives

zfin resolves your files one of two ways -- never a mix of both:

  1. ZFIN_HOME is set -- zfin reads from that directory, and only there. The current directory is never consulted, so a stray file in cwd can't silently shadow your real data.
  2. ZFIN_HOME is unset -- zfin reads from the current directory.

To make a single run read the current directory while ZFIN_HOME is set, unset it just for that command: env -u ZFIN_HOME zfin portfolio.

accounts.srf and metadata.srf are always loaded from the same directory as the resolved portfolio.srf. Pointing ZFIN_HOME at an example directory is what makes the guides runnable:

ZFIN_HOME=examples/post-retirement zfin portfolio

Keep your real data in a private ZFIN_HOME outside any repo so you never commit holdings. See environment variables.

Live data vs. snapshots

zfin deals with two kinds of "your portfolio," and the distinction matters:

  • Live (current) portfolio -- computed from portfolio.srf plus the latest prices. This is what portfolio, analysis, review, and friends show. It always reflects what you hold now.
  • Snapshots -- immutable records of your totals on a past date, written by zfin snapshot into history/<date>-portfolio.srf. These are the time series that history and compare read.

The live portfolio is mutable (you edit it); snapshots are historical facts (you don't). See Snapshots and history.

The data service and caching

All market data flows through one path: check the local cache, and if the entry is fresh enough, use it; otherwise fetch from a provider, write it to the cache, and return it. You rarely think about providers directly -- you think about freshness, controlled by the --refresh-data flag. See Caching and data freshness and Why multiple data providers.

CLI and TUI are two faces of one engine

The CLI and the interactive TUI (zfin i) sit on the same engine: they read the same files, share the same cache, and run the same analytics, so a figure you see in one matches the other. Reach for the CLI for quick checks and scripting; reach for the TUI for browsing and high-fidelity charts.

They overlap heavily but aren't a one-to-one mirror -- and aren't trying to be:

  • CLI-only. Several commands have no TUI tab: the data-hygiene and journaling ones (audit, snapshot, import, enrich, compare, contributions), plus a few like exposure and milestones.
  • TUI-only. Interactive touches have no CLI counterpart: live charts, in-place refresh, the ? keybinding overlay, and conveniences like the overlay that shows a holding's full name behind its ticker.

The nine TUI tabs -- Portfolio, Analysis, Review, Projections, History, Quote, Performance, Earnings, Options -- cover the browsing and analysis surfaces; the rest lives on the CLI. See The interactive TUI.

Where to go next