finance library and cli/tui
Find a file
2026-02-26 15:52:08 -08:00
src ai: mouse clickable chart timeframes 2026-02-26 15:52:08 -08:00
.gitignore ai generated, functional, no code review yet 2026-02-25 14:10:27 -08:00
.mise.toml ai generated, functional, no code review yet 2026-02-25 14:10:27 -08:00
build.zig ai: bug fixes and charting 2026-02-25 17:02:19 -08:00
build.zig.zon ai: bug fixes and charting 2026-02-25 17:02:19 -08:00
LICENSE ai generated, functional, no code review yet 2026-02-25 14:10:27 -08:00
README.md ai generated, functional, no code review yet 2026-02-25 14:10:27 -08:00

zfin

A financial data library, CLI, and terminal UI written in Zig. Tracks portfolios, analyzes trailing returns, displays options chains, earnings history, and more -- all from the terminal.

Quick start

# Set at least one API key (see "API keys" below)
export TWELVEDATA_API_KEY=your_key

# Build
zig build

# CLI usage
zig build run -- perf VTI           # trailing returns
zig build run -- quote AAPL         # real-time quote
zig build run -- options AAPL       # options chains
zig build run -- earnings MSFT      # earnings history

# Interactive TUI
zig build run -- i                  # auto-loads portfolio.srf from cwd
zig build run -- i -p portfolio.srf -w watchlist.srf
zig build run -- i -s AAPL          # start with a symbol, no portfolio

Requires Zig 0.15.2 or later.

Data providers

zfin aggregates data from multiple free-tier APIs. Each provider is used for the data it does best, and aggressive caching keeps usage well within free-tier limits.

Provider summary

Data type Provider Auth Free-tier limit Cache TTL
Daily candles (OHLCV) TwelveData TWELVEDATA_API_KEY 8 req/min, 800/day 24 hours
Real-time quotes TwelveData TWELVEDATA_API_KEY 8 req/min, 800/day Never cached
Dividends Polygon POLYGON_API_KEY 5 req/min 7 days
Splits Polygon POLYGON_API_KEY 5 req/min 7 days
Options chains CBOE None required ~30 req/min (self-imposed) 1 hour
Earnings Finnhub FINNHUB_API_KEY 60 req/min 24 hours
ETF profiles Alpha Vantage ALPHAVANTAGE_API_KEY 25 req/day 30 days

TwelveData

Used for: daily candles and real-time quotes.

  • Endpoint: https://api.twelvedata.com/time_series and /quote
  • Free tier: 8 API credits per minute, 800 per day. Each symbol in a request costs 1 credit (batch requests do NOT reduce credit cost).
  • Candles are fetched with a 10-year + 60-day lookback window for trailing return calculations.
  • Returns split-adjusted but NOT dividend-adjusted prices. Total returns are computed separately using Polygon dividend data.
  • Quotes are never cached (always a live fetch, ~15 min delay on free tier).

Polygon

Used for: dividend history and stock splits.

  • Endpoints: https://api.polygon.io/v3/reference/dividends and /v3/reference/splits
  • Free tier: 5 requests per minute, unlimited daily. Full historical dividend/split data.
  • Dividend endpoint uses cursor-based pagination (automatically followed).
  • Provides dividend type classification (regular, special, supplemental).

CBOE

Used for: options chains.

  • Endpoint: https://cdn.cboe.com/api/global/delayed_quotes/options/{SYMBOL}.json
  • No API key required. Data is 15-minute delayed during market hours.
  • Returns all expirations with full chains including greeks (delta, gamma, theta, vega), bid/ask, volume, open interest, and implied volatility.
  • OCC option symbols are parsed to extract expiration, strike, and contract type.

Finnhub

Used for: earnings calendar (historical and upcoming).

  • Endpoint: https://finnhub.io/api/v1/calendar/earnings
  • Free tier: 60 requests per minute.
  • Fetches 5 years back and 1 year forward from today.
  • Note: Finnhub requires TLS 1.2. Since Zig's HTTP client only supports TLS 1.3, requests to Finnhub automatically fall back to system curl.

Alpha Vantage

Used for: ETF profiles (expense ratio, holdings, sector weights).

  • Endpoint: https://www.alphavantage.co/query?function=ETF_PROFILE
  • Free tier: 25 requests per day. Used sparingly -- ETF profiles rarely change.

API keys

Set keys as environment variables or in a .env file (searched in the executable's parent directory, then cwd):

TWELVEDATA_API_KEY=your_key   # Required for candles and quotes
POLYGON_API_KEY=your_key      # Required for dividends/splits (total returns)
FINNHUB_API_KEY=your_key      # Required for earnings data
ALPHAVANTAGE_API_KEY=your_key # Required for ETF profiles

The cache directory defaults to ~/.cache/zfin and can be overridden with ZFIN_CACHE_DIR.

Not all keys are required. Without a key, the corresponding data simply won't be available:

Key Without it
TWELVEDATA_API_KEY No candles, quotes, or trailing returns
POLYGON_API_KEY No dividends -- trailing returns show price-only (no total return)
FINNHUB_API_KEY No earnings data (tab disabled)
ALPHAVANTAGE_API_KEY No ETF profiles

CBOE options require no API key.

Caching strategy

Every data fetch follows the same pattern:

  1. Check local cache (~/.cache/zfin/{SYMBOL}/{data_type}.srf)
  2. If cached file exists and is within TTL -- deserialize and return (no network)
  3. Otherwise -- fetch from provider -- serialize to cache -- return

Cache files use SRF (Simple Record Format), a line-oriented key-value format. Freshness is determined by file modification time vs. the TTL for that data type.

Data type TTL Rationale
Daily candles 24 hours Only changes once per trading day
Dividends 7 days Declared well in advance
Splits 7 days Rare corporate events
Options 1 hour Prices change continuously during market hours
Earnings 24 hours Quarterly events, estimates update periodically
ETF profiles 30 days Holdings/weights change slowly
Quotes Never cached Intended for live price checks

Manual refresh (r / F5 in TUI) invalidates the cache for the current tab's data before re-fetching.

Rate limiting

Each provider has a client-side token-bucket rate limiter that prevents exceeding free-tier limits:

Provider Rate limit
TwelveData 8/minute
Polygon 5/minute
Finnhub 60/minute
CBOE 30/minute
Alpha Vantage 25/day

The limiter blocks until a token is available, spreading bursts of requests automatically rather than failing with 429 errors.

CLI commands

zfin <command> [args]

Commands:
  perf <SYMBOL>        Trailing returns (1yr/3yr/5yr/10yr, price + total)
  quote <SYMBOL>       Real-time quote
  history <SYMBOL>     Last 30 days price history
  divs <SYMBOL>        Dividend history with TTM yield
  splits <SYMBOL>      Split history
  options <SYMBOL>     Options chains (all expirations)
  earnings <SYMBOL>    Earnings history and upcoming events
  etf <SYMBOL>         ETF profile (expense ratio, holdings, sectors)
  portfolio <FILE>     Portfolio analysis from .srf file
  cache stats          Show cached symbols
  cache clear          Delete all cached data
  interactive, i       Launch interactive TUI
  help                 Show usage

Interactive TUI flags

zfin i [options]

  -p, --portfolio <FILE>   Portfolio file (.srf format)
  -w, --watchlist <FILE>   Watchlist file (default: watchlist.srf if present)
  -s, --symbol <SYMBOL>    Start with a specific symbol
  --default-keys           Print default keybindings config to stdout
  --default-theme          Print default theme config to stdout

If no portfolio or symbol is specified and portfolio.srf exists in the current directory, it is loaded automatically.

Interactive TUI

The TUI has five tabs: Portfolio, Quote, Performance, Options, and Earnings.

Tabs

Portfolio -- navigable list of positions with market value, gain/loss, weight, and purchase date. Multi-lot positions can be expanded to show individual lots with per-lot gain/loss, capital gains indicator (ST/LT), and account name.

Quote -- current price, OHLCV, daily change, and a 60-day ASCII chart with recent history table.

Performance -- trailing returns using two methodologies (as-of-date and month-end), matching Morningstar's "Trailing Returns" and "Performance" pages respectively. Shows price-only and total return (with dividend reinvestment) when Polygon data is available. Also shows risk metrics (volatility, Sharpe ratio, max drawdown).

Options -- all expirations in a navigable list. Expand any expiration to see calls and puts inline. Calls and puts sections are independently collapsible. Near-the-money filter limits strikes shown (default +/- 8, adjustable with Ctrl+1-9). ITM strikes are marked with |. Monthly expirations display in normal color, weeklies are dimmed.

Earnings -- historical and upcoming earnings events with EPS estimate/actual, surprise amount and percentage. Future events are dimmed. Tab is disabled for ETFs.

Keybindings

All keybindings are configurable via ~/.config/zfin/keys.srf. Generate the default config:

zfin i --default-keys > ~/.config/zfin/keys.srf

Default keybindings:

Key Action
q, Ctrl+c Quit
r, F5 Refresh current tab (invalidates cache)
h, Left Previous tab
l, Right, Tab Next tab
1-5 Jump to tab
j, Down Select next row
k, Up Select previous row
Enter Expand/collapse (positions, expirations, calls/puts)
s Select symbol from portfolio for other tabs
/ Enter symbol search
e Edit portfolio/watchlist in $EDITOR
c Toggle all calls collapsed/expanded (options tab)
p Toggle all puts collapsed/expanded (options tab)
Ctrl+1-Ctrl+9 Set options near-the-money filter to +/- N strikes
g Scroll to top
G Scroll to bottom
Ctrl+d Half-page down
Ctrl+u Half-page up
PageDown Page down
PageUp Page up
? Help screen

Mouse: scroll wheel navigates, left-click selects rows and switches tabs, double-click expands/collapses.

Theme

The TUI uses a dark theme inspired by Monokai/opencode. Customize via ~/.config/zfin/theme.srf:

zfin i --default-theme > ~/.config/zfin/theme.srf

Colors are specified as #rrggbb hex values. The theme uses RGB colors (not terminal color indices) to work correctly with transparent terminal backgrounds.

Portfolio format

Portfolios are SRF files with one lot per line:

#!srfv1
symbol::VTI,shares:num:100,open_date::2024-01-15,open_price:num:220.50
symbol::AAPL,shares:num:50,open_date::2024-03-01,open_price:num:170.00
symbol::AAPL,shares:num:25,open_date::2023-06-15,open_price:num:155.00,account::Roth IRA
symbol::AMZN,shares:num:10,open_date::2022-03-15,open_price:num:150.25,close_date::2024-01-15,close_price:num:185.50

Lot fields

Field Type Required Description
symbol string Yes Ticker symbol
shares number Yes Number of shares
open_date string Yes Purchase date (YYYY-MM-DD)
open_price number Yes Purchase price per share
close_date string No Sale date (null = open lot)
close_price number No Sale price per share
note string No Tag or note
account string No Account name (e.g. "Roth IRA", "Brokerage")

Open lots (no close_date) contribute to positions. Closed lots (with close_date and close_price) show realized P&L. The account field is displayed in the lot detail view when a position is expanded.

Watchlist format

A watchlist is an SRF file with just symbol fields:

#!srfv1
symbol::NVDA
symbol::TSLA
symbol::GOOG

Watchlist symbols appear at the bottom of the portfolio tab. They show the latest cached price but no position data. Press Enter or double-click to jump to the Quote tab for that symbol.

Architecture

src/
  root.zig              Library root, exports all public types
  config.zig            Configuration from env vars / .env files
  service.zig           DataService: cache-check -> fetch -> cache -> return
  models/
    candle.zig          OHLCV price bars
    date.zig            Date type with arithmetic, snapping, formatting
    dividend.zig        Dividend records with type classification
    split.zig           Stock splits
    option.zig          Option contracts and chains
    earnings.zig        Earnings events with surprise calculation
    etf_profile.zig     ETF profiles with holdings and sectors
    portfolio.zig       Lots, positions, and portfolio aggregation
    quote.zig           Real-time quote data
    ticker_info.zig     Security metadata
  providers/
    provider.zig        Type-erased provider interface (vtable)
    twelvedata.zig      TwelveData: candles, quotes
    polygon.zig         Polygon: dividends, splits
    finnhub.zig         Finnhub: earnings
    cboe.zig            CBOE: options chains (no API key)
    alphavantage.zig    Alpha Vantage: ETF profiles
  analytics/
    performance.zig     Trailing returns (as-of-date + month-end)
    risk.zig            Volatility, Sharpe, drawdown, portfolio summary
  cache/
    store.zig           SRF file cache with TTL freshness checks
  net/
    http.zig            HTTP client with retries and TLS 1.2 fallback
    rate_limiter.zig    Token-bucket rate limiter
  cli/
    main.zig            CLI entry point and all commands
  tui/
    main.zig            Interactive TUI application
    keybinds.zig        Configurable keybinding system
    theme.zig           Configurable color theme

Dependencies

Dependency Source Purpose
SRF Local (../../srf) Cache file format and portfolio/watchlist parsing
libvaxis Git (v0.5.1) Terminal UI rendering

Building

zig build              # build the zfin binary
zig build test         # run all tests
zig build run -- <args> # build and run

The compiled binary is at zig-out/bin/zfin.

License

MIT