README update
All checks were successful
Generic zig build / build (push) Successful in 4m46s
Generic zig build / publish-macos (push) Successful in 10s
Generic zig build / deploy (push) Successful in 20s

This commit is contained in:
Emil Lerch 2026-05-31 11:30:12 -07:00
parent 195933176f
commit 641a88b0b7

178
README.md
View file

@ -4,28 +4,52 @@ A financial data library, CLI, and terminal UI written in Zig. Tracks portfolios
## Quick start ## Quick start
### macOS (Apple Silicon) -- pre-built binary
```bash ```bash
# Set at least one API key (see "API keys" below) # Download the latest aarch64-macos binary, make it executable, drop on $PATH
export TIINGO_API_KEY=your_key curl -L -o zfin \
https://git.lerch.org/api/packages/lobo/generic/zfin-aarch64-macos/latest/zfin-aarch64-macos
# Build chmod +x zfin
zig build sudo mv zfin /usr/local/bin/ # or anywhere on $PATH
# 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
zig build run -- portfolio # portfolio summary (reads portfolio.srf)
zig build run -- analysis # portfolio analysis (reads portfolio.srf + metadata.srf)
# 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. ### Linux / building from source
```bash
# Requires Zig 0.16.0. With mise installed, `mise install` will pick the right
# version from .mise.toml. Otherwise grab Zig 0.16.0 manually.
zig build
# Binary lands at zig-out/bin/zfin
```
### Configure
```bash
# Set at least one API key plus your contact email (see "API keys" below).
# The email is required for ETF profiles + `enrich` because SEC EDGAR mandates
# a User-Agent contact.
export TIINGO_API_KEY=your_key
export ZFIN_USER_EMAIL=you@example.com
```
### Use it
```bash
zfin perf VTI # trailing returns
zfin quote AAPL # real-time quote
zfin options AAPL # options chains
zfin earnings MSFT # earnings history
zfin portfolio # portfolio summary (reads portfolio.srf)
zfin analysis # portfolio analysis (reads portfolio.srf + metadata.srf)
# Interactive TUI
zfin i # auto-loads portfolio.srf from cwd
zfin i -p portfolio.srf -w watchlist.srf
zfin i -s AAPL # start with a symbol, no portfolio
```
Building from source requires Zig 0.16.0.
## Data providers ## Data providers
@ -33,15 +57,17 @@ zfin aggregates data from multiple free-tier APIs. Each provider is used for the
### Provider summary - primary providers ### Provider summary - primary providers
| Data type | Provider | Auth | Free-tier limit | Cache TTL | | Data type | Provider | Auth | Free-tier limit | Cache TTL |
|-----------------------|---------------|------------------------|----------------------------|--------------| |-----------------------|------------------|----------------------|----------------------------|--------------|
| Daily candles (OHLCV) | Tiingo | `TIINGO_API_KEY` | 1,000 req/day | 23h45m | | Daily candles (OHLCV) | Tiingo | `TIINGO_API_KEY` | 1,000 req/day | 23h45m |
| Real-time quotes | Yahoo Finance | None required | Unofficial | Never cached | | Real-time quotes | Yahoo Finance | None required | Unofficial | Never cached |
| Dividends | Polygon | `POLYGON_API_KEY` | 5 req/min | 14 days | | Quote fallback | TwelveData | `TWELVEDATA_API_KEY` | 8 req/min, 800/day | Never cached |
| Splits | Polygon | `POLYGON_API_KEY` | 5 req/min | 14 days | | Dividends | Polygon | `POLYGON_API_KEY` | 5 req/min | 14 days |
| Options chains | CBOE | None required | ~30 req/min (self-imposed) | 1 hour | | Splits | Polygon | `POLYGON_API_KEY` | 5 req/min | 14 days |
| Earnings | FMP | `FMP_API_KEY` | 250 req/day | 30 days* | | Options chains | CBOE | None required | ~30 req/min (self-imposed) | 1 hour |
| ETF profiles | Alpha Vantage | `ALPHAVANTAGE_API_KEY` | 25 req/day | 30 days | | Earnings | FMP | `FMP_API_KEY` | 250 req/day | 30 days* |
| ETF profiles | SEC EDGAR | `ZFIN_USER_EMAIL` | 10 req/sec | ~90 days |
| Classification | Wikidata + EDGAR | `ZFIN_USER_EMAIL` | No per-day quota | Long-lived |
### Tiingo ### Tiingo
@ -89,36 +115,51 @@ zfin aggregates data from multiple free-tier APIs. Each provider is used for the
- History depth: full — often back to the 1980s for long-listed tickers. - History depth: full — often back to the 1980s for long-listed tickers.
- Coverage: US stocks with real earnings. ETFs, mutual funds, CUSIPs, and some dual-class shares (BRK.B, GOOG) return 402 on the free tier and show up as "no earnings data" in the UI — a documented limitation, not a bug. - Coverage: US stocks with real earnings. ETFs, mutual funds, CUSIPs, and some dual-class shares (BRK.B, GOOG) return 402 on the free tier and show up as "no earnings data" in the UI — a documented limitation, not a bug.
### Alpha Vantage ### SEC EDGAR + Wikidata
**Used for:** ETF profiles (expense ratio, holdings, sector weights). **Used for:** ETF profiles (NPORT-P holdings, sector weights, AUM, inception
dates) and the `enrich` flow that bootstraps `metadata.srf` (sector / industry
/ country / asset-class classification).
- Endpoint: `https://www.alphavantage.co/query?function=ETF_PROFILE` - Endpoints:
- Free tier: 25 requests per day. Used sparingly -- ETF profiles rarely change. - `https://data.sec.gov/...` -- XBRL company facts, NPORT-P primary
documents, mutual-fund ticker map.
- `https://www.wikidata.org/sparql` -- sector / industry / country
statements.
- Free, but the SEC requires a contact email in the User-Agent header. zfin
reads this from `ZFIN_USER_EMAIL` (`.env` or environment). Without it, ETF
profiles and `enrich` are unavailable; everything else still works.
- The SEC caps requests at 10/sec; the rate limiter respects this. Wikidata
has no per-day quota.
- The `enrich` command queries Wikidata first (rich classification metadata);
when Wikidata has no entry for a symbol (common for managed funds, UITs),
EDGAR's mutual-fund ticker map is the fallback. Symbols that miss both fall
through as TODO entries the user fills in by hand.
## API keys ## API keys
Set keys as environment variables or in a `.env` file (searched in the executable's parent directory, then cwd): Set keys as environment variables or in a `.env` file (searched in the executable's parent directory, then cwd):
```bash ```bash
TIINGO_API_KEY=your_key # Required for candles (primary provider) TIINGO_API_KEY=your_key # Required for candles (primary provider)
TWELVEDATA_API_KEY=your_key # Quote fallback (after Yahoo) TWELVEDATA_API_KEY=your_key # Quote fallback (after Yahoo)
POLYGON_API_KEY=your_key # Required for dividends/splits (total returns) POLYGON_API_KEY=your_key # Required for dividends/splits (total returns)
FMP_API_KEY=your_key # Required for earnings data FMP_API_KEY=your_key # Required for earnings data
ALPHAVANTAGE_API_KEY=your_key # Required for ETF profiles ZFIN_USER_EMAIL=you@example.com # Required for ETF profiles + `enrich`
# (SEC EDGAR mandates a User-Agent contact)
``` ```
The cache directory defaults to `~/.cache/zfin` and can be overridden with `ZFIN_CACHE_DIR`. 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: Not all keys are required. Without a key, the corresponding data simply won't be available:
| Key | Without it | | Key | Without it |
|------------------------|-------------------------------------------------------------------------------------| |----------------------|-------------------------------------------------------------------------------------------|
| `TIINGO_API_KEY` | Candles fall back to Yahoo only; some symbols (especially mutual funds) won't work | | `TIINGO_API_KEY` | Candles fall back to Yahoo only; some symbols (especially mutual funds) won't work |
| `TWELVEDATA_API_KEY` | No quote fallback after Yahoo | | `TWELVEDATA_API_KEY` | No quote fallback after Yahoo |
| `POLYGON_API_KEY` | No forward-looking dividends; trailing total returns may use only Tiingo's view | | `POLYGON_API_KEY` | No forward-looking dividends; trailing total returns may use only Tiingo's view |
| `FMP_API_KEY` | No earnings data (tab disabled) | | `FMP_API_KEY` | No earnings data (tab disabled) |
| `ALPHAVANTAGE_API_KEY` | No ETF profiles | | `ZFIN_USER_EMAIL` | No ETF profiles; `enrich` cannot bootstrap metadata. (Used as the EDGAR User-Agent value) |
CBOE options require no API key. CBOE options require no API key.
@ -150,14 +191,15 @@ Manual refresh (`r` / `F5` in TUI) invalidates the cache for the current tab's d
Each provider has a client-side token-bucket rate limiter that prevents exceeding free-tier limits: Each provider has a client-side token-bucket rate limiter that prevents exceeding free-tier limits:
| Provider | Rate limit | | Provider | Rate limit |
|---------------|-------------| |---------------|---------------------|
| Tiingo | 1,000/day | | Tiingo | 1,000/day |
| TwelveData | 8/minute | | TwelveData | 8/minute |
| Polygon | 5/minute | | Polygon | 5/minute |
| FMP | 250/day | | FMP | 250/day |
| CBOE | 30/minute | | CBOE | 30/minute |
| Alpha Vantage | 25/day | | SEC EDGAR | 10/second |
| Wikidata | (no enforced limit) |
The limiter blocks until a token is available, spreading bursts of requests automatically rather than failing with 429 errors. The limiter blocks until a token is available, spreading bursts of requests automatically rather than failing with 429 errors.
@ -500,7 +542,9 @@ Cash and CD lots are automatically classified as "Cash & CDs" without needing me
### Bootstrapping metadata ### Bootstrapping metadata
Use the `enrich` command to generate a starting `metadata.srf` from Alpha Vantage company overview data: Use the `enrich` command to generate a starting `metadata.srf` from Wikidata
+ SEC EDGAR data. Requires `ZFIN_USER_EMAIL` set so EDGAR will accept the
request.
```bash ```bash
# Enrich an entire portfolio (generates full metadata.srf) # Enrich an entire portfolio (generates full metadata.srf)
@ -510,7 +554,12 @@ zfin enrich portfolio.srf > metadata.srf
zfin enrich SCHD >> metadata.srf zfin enrich SCHD >> metadata.srf
``` ```
When given a file path, it fetches all stock symbols and outputs a complete SRF file with headers. When given a symbol, it outputs just the classification lines (no header), so you can append directly with `>>`. When given a file path, it fetches all stock symbols and outputs a complete
SRF file with headers. When given a symbol, it outputs just the
classification lines (no header), so you can append directly with `>>`.
Wikidata is queried first; symbols not found in Wikidata fall through to
EDGAR's mutual-fund ticker map. Anything that misses both shows up as a
TODO entry to fill in by hand.
## Account metadata (accounts.srf) ## Account metadata (accounts.srf)
@ -832,7 +881,7 @@ Commands:
analysis [FILE] Portfolio analysis breakdowns (default: portfolio.srf) analysis [FILE] Portfolio analysis breakdowns (default: portfolio.srf)
snapshot [opts] Write a daily portfolio snapshot to history/ snapshot [opts] Write a daily portfolio snapshot to history/
compare <D1> [<D2>] Compare portfolio state across two dates compare <D1> [<D2>] Compare portfolio state across two dates
enrich <FILE|SYMBOL> Generate metadata.srf from Alpha Vantage enrich <FILE|SYMBOL> Generate metadata.srf from Wikidata + SEC EDGAR
lookup <CUSIP> CUSIP to ticker lookup via OpenFIGI lookup <CUSIP> CUSIP to ticker lookup via OpenFIGI
cache stats Show cached symbols cache stats Show cached symbols
cache clear Delete all cached data cache clear Delete all cached data
@ -872,9 +921,11 @@ src/
polygon.zig Polygon: dividends, splits (primary, with forward-looking entries) polygon.zig Polygon: dividends, splits (primary, with forward-looking entries)
fmp.zig FMP: earnings (actuals + estimates) fmp.zig FMP: earnings (actuals + estimates)
cboe.zig CBOE: options chains (no API key) cboe.zig CBOE: options chains (no API key)
alphavantage.zig Alpha Vantage: ETF profiles, company overview Edgar.zig SEC EDGAR: ETF profiles (NPORT-P), mutual-fund ticker map, XBRL company facts
Wikidata.zig Wikidata SPARQL: classification metadata for `enrich`
yahoo.zig Yahoo Finance: quotes (primary), candles (Tiingo fallback) yahoo.zig Yahoo Finance: quotes (primary), candles (Tiingo fallback)
openfigi.zig OpenFIGI: CUSIP to ticker lookup openfigi.zig OpenFIGI: CUSIP to ticker lookup
xml.zig Vendored XML parser used by Edgar.zig (see "Vendored code")
analytics/ analytics/
indicators.zig SMA, Bollinger Bands, RSI indicators.zig SMA, Bollinger Bands, RSI
performance.zig Trailing returns (as-of-date + month-end) performance.zig Trailing returns (as-of-date + month-end)
@ -907,14 +958,21 @@ accounts.srf Account to tax type mapping for analysis
### Dependencies ### Dependencies
| Dependency | Source | Purpose | | Dependency | Source | Purpose |
|----------------------------------------------------|---------------------|---------------------------------------------------| |----------------------------------------------------|----------------|---------------------------------------------------|
| [SRF](https://git.lerch.org/lobo/srf) | Git | Cache file format and portfolio/watchlist parsing | | [SRF](https://git.lerch.org/lobo/srf) | Git | Cache file format and portfolio/watchlist parsing |
| [libvaxis](https://github.com/rockorager/libvaxis) | Git (v0.5.1) | Terminal UI rendering | | [libvaxis](https://github.com/rockorager/libvaxis) | Git (v0.6.0) | Terminal UI rendering |
| [z2d](https://github.com/vancluever/z2d) | Git (v0.11.0) | Pixel chart rendering (Kitty graphics protocol) |
## Building ## Building
Requires Zig 0.16.0. The canonical version is pinned in `.mise.toml`:
```bash ```bash
# Recommended: use mise to install the right Zig and ZLS automatically
mise install
# Or, with Zig 0.16.0 already on PATH:
zig build # build the zfin binary zig build # build the zfin binary
zig build test # run all tests zig build test # run all tests
zig build run -- <args> # build and run zig build run -- <args> # build and run