552 lines
27 KiB
Markdown
552 lines
27 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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
|
|
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.
|
|
|
|
## 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 | 14 days |
|
|
| Splits | Polygon | `POLYGON_API_KEY` | 5 req/min | 14 days |
|
|
| Options chains | CBOE | None required | ~30 req/min (self-imposed) | 1 hour |
|
|
| Earnings | Finnhub | `FINNHUB_API_KEY` | 60 req/min | 30 days* |
|
|
| 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):
|
|
|
|
```bash
|
|
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](https://github.com/lobo/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 | 14 days | Declared well in advance |
|
|
| Splits | 14 days | Rare corporate events |
|
|
| Options | 1 hour | Prices change continuously during market hours |
|
|
| Earnings | 30 days* | Quarterly events; smart refresh after announcements |
|
|
| ETF profiles | 30 days | Holdings/weights change slowly |
|
|
| Quotes | Never cached | Intended for live price checks |
|
|
|
|
\* **Earnings smart refresh:** Even within the 30-day TTL, cached earnings are automatically re-fetched when an earnings date has passed but the cache still has no actual results for it. This ensures results appear promptly after an announcement without wasteful daily polling.
|
|
|
|
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 six tabs: Portfolio, Quote, Performance, Options, Earnings, and Analysis.
|
|
|
|
### 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.
|
|
|
|
**Analysis** -- portfolio breakdown by asset class, sector, geographic region, account, and tax type. Uses classification data from `metadata.srf` and account tax types from `accounts.srf`. Displays horizontal bar charts with sub-character precision using Unicode block elements.
|
|
|
|
### Keybindings
|
|
|
|
All keybindings are configurable via `~/.config/zfin/keys.srf`. Generate the default config:
|
|
|
|
```bash
|
|
zfin i --default-keys > ~/.config/zfin/keys.srf
|
|
```
|
|
|
|
Default keybindings:
|
|
|
|
| Key | Action |
|
|
|------------------------|------------------------------------------------------|
|
|
| `q`, `Ctrl+c` | Quit |
|
|
| `r`, `F5` | Refresh current tab (invalidates cache) |
|
|
| `R` | Reload portfolio from disk (no network) |
|
|
| `h`, Left, Shift+Tab | Previous tab |
|
|
| `l`, Right, Tab | Next tab |
|
|
| `1`-`6` | 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` |
|
|
| `<` | Sort by previous column (portfolio tab) |
|
|
| `>` | Sort by next column (portfolio tab) |
|
|
| `o` | Reverse sort direction (portfolio tab) |
|
|
| `[` | Previous chart timeframe (quote tab) |
|
|
| `]` | Next chart timeframe (quote tab) |
|
|
| `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`:
|
|
|
|
```bash
|
|
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](https://github.com/lobo/srf) files with one lot per line. Each lot is a comma-separated list of `key::value` pairs (numbers use `key:num:value`).
|
|
|
|
```
|
|
#!srfv1
|
|
# Stocks/ETFs
|
|
symbol::VTI,shares:num:100,open_date::2024-01-15,open_price:num:220.50,account::Brokerage
|
|
symbol::AAPL,shares:num:50,open_date::2024-03-01,open_price:num:170.00,account::Roth IRA
|
|
symbol::AAPL,shares:num:25,open_date::2023-06-15,open_price:num:155.00,account::Roth IRA
|
|
|
|
# Closed lot (sold)
|
|
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
|
|
|
|
# DRIP lots (summarized as ST/LT groups in the UI)
|
|
symbol::VTI,shares:num:0.234,open_date::2024-06-15,open_price:num:267.50,drip::true,account::Brokerage
|
|
|
|
# CUSIP with ticker alias (401k CIT share class)
|
|
symbol::02315N600,shares:num:1200,open_date::2022-01-01,open_price:num:140.00,ticker::VTTHX,account::Fidelity 401k,note::VANGUARD TARGET 2035
|
|
|
|
# Manual price override (for securities without API coverage)
|
|
symbol::NON40OR52,shares:num:500,open_date::2023-01-01,open_price:num:155.00,price:num:163.636,price_date::2026-02-27,account::Fidelity 401k,note::CIT SHARE CLASS
|
|
|
|
# Options
|
|
security_type::option,symbol::AAPL 250321C00200000,shares:num:-2,open_date::2025-01-15,open_price:num:12.50,account::Brokerage
|
|
|
|
# CDs
|
|
security_type::cd,symbol::912797KR0,shares:num:10000,open_date::2024-06-01,open_price:num:10000,maturity_date::2025-06-01,rate:num:5.25,account::Brokerage,note::6-Month T-Bill
|
|
|
|
# Cash
|
|
security_type::cash,shares:num:15000,account::Brokerage
|
|
security_type::cash,shares:num:5200.50,account::Roth IRA,note::Money market settlement
|
|
|
|
# Illiquid assets
|
|
security_type::illiquid,symbol::HOME,shares:num:450000,open_date::2020-06-01,open_price:num:350000,note::Primary residence (Zillow est.)
|
|
|
|
# Watchlist (track price only, no position)
|
|
security_type::watch,symbol::NVDA
|
|
security_type::watch,symbol::TSLA
|
|
```
|
|
|
|
### Lot fields
|
|
|
|
| Field | Type | Required | Description |
|
|
|-----------------|--------|----------|--------------------------------------------------------------------------|
|
|
| `symbol` | string | Yes* | Ticker symbol or CUSIP. *Optional for `cash` lots. |
|
|
| `shares` | number | Yes | Number of shares (or face value for cash/CDs) |
|
|
| `open_date` | string | Yes** | Purchase date (YYYY-MM-DD). **Not required for cash/watch. |
|
|
| `open_price` | number | Yes** | Purchase price per share. **Not required for cash/watch. |
|
|
| `close_date` | string | No | Sale date (null = open lot) |
|
|
| `close_price` | number | No | Sale price per share |
|
|
| `security_type` | string | No | `stock` (default), `option`, `cd`, `cash`, `illiquid`, `watch` |
|
|
| `account` | string | No | Account name (e.g. "Roth IRA", "Brokerage") |
|
|
| `note` | string | No | Descriptive note (shown in cash/CD/illiquid tables) |
|
|
| `ticker` | string | No | Ticker alias for price fetching (overrides `symbol` for API calls) |
|
|
| `price` | number | No | Manual price override (fallback when API has no coverage) |
|
|
| `price_date` | string | No | Date of the manual price (YYYY-MM-DD, for staleness display) |
|
|
| `drip` | string | No | `true` if lot is from dividend reinvestment (summarized as ST/LT groups) |
|
|
| `maturity_date` | string | No | CD maturity date (YYYY-MM-DD) |
|
|
| `rate` | number | No | Interest rate for CDs (e.g. 5.25 = 5.25%) |
|
|
|
|
### Security types
|
|
|
|
- **stock** (default) -- Stocks, ETFs, and mutual funds. Prices are fetched from TwelveData. Positions are aggregated by symbol and shown with gain/loss.
|
|
- **option** -- Option contracts. Shown in a separate "Options" section. Shares can be negative for short positions.
|
|
- **cd** -- Certificates of deposit. Shown sorted by maturity date with rate and face value.
|
|
- **cash** -- Cash, money market, and settlement balances. Shown grouped by account with optional notes.
|
|
- **illiquid** -- Illiquid assets (real estate, vehicles, etc.). Shown in a separate section. Not included in the liquid portfolio total; contributes to Net Worth.
|
|
- **watch** -- Watchlist items. No position, just tracks the price. Shown at the bottom of the portfolio tab.
|
|
|
|
### Price resolution
|
|
|
|
For stock lots, prices are resolved in this order:
|
|
|
|
1. **Live API** -- Latest close from TwelveData cached candles
|
|
2. **Manual price** -- `price::` field on the lot (for securities without API coverage, e.g. 401k CIT share classes)
|
|
3. **Average cost** -- Falls back to the position's `open_price` as a last resort
|
|
|
|
Manual-priced rows are shown in warning color (yellow) so you know the price may be stale. The `price_date::` field helps you track when the price was last updated.
|
|
|
|
### Ticker aliases
|
|
|
|
Some securities (like 401k CIT share classes) use CUSIPs as identifiers but have a retail equivalent ticker for price fetching. Use `ticker::` to specify the API ticker:
|
|
|
|
```
|
|
symbol::02315N600,ticker::VTTHX,...
|
|
```
|
|
|
|
The `symbol::` is used as the display identifier and for classification lookups. The `ticker::` is used for API price fetching. If the CUSIP and retail ticker have different NAVs (common for CIT vs retail fund), use `price::` instead.
|
|
|
|
### CUSIP lookup
|
|
|
|
Use the `lookup` command to resolve CUSIPs to tickers via OpenFIGI:
|
|
|
|
```bash
|
|
zfin lookup 459200101 # -> IWM (iShares Russell 2000 ETF)
|
|
```
|
|
|
|
### DRIP lots
|
|
|
|
Lots marked with `drip::true` are summarized as ST (short-term) and LT (long-term) groups in the position detail view, rather than listing every small reinvestment lot individually. The grouping is based on the 1-year capital gains threshold.
|
|
|
|
### Watchlist
|
|
|
|
Watchlist symbols can be defined as `security_type::watch` lots in the portfolio file, or in a separate watchlist file (`-w` flag). They appear at the bottom of the portfolio tab showing the cached price.
|
|
|
|
## Classification metadata (metadata.srf)
|
|
|
|
The `metadata.srf` file provides classification data for portfolio analysis. It maps symbols to asset class, sector, and geographic region. Place it in the same directory as the portfolio file.
|
|
|
|
```
|
|
#!srfv1
|
|
# Individual stock: single classification at 100%
|
|
symbol::AMZN,sector::Technology,geo::US,asset_class::US Large Cap
|
|
|
|
# ETF: inherits sector from holdings, but classified by asset class
|
|
symbol::VTI,asset_class::US Large Cap,geo::US
|
|
|
|
# International ETF
|
|
symbol::VXUS,asset_class::International Developed,geo::International Developed
|
|
|
|
# Target date fund: blended allocation (percentages should sum to ~100)
|
|
symbol::02315N600,asset_class::US Large Cap,pct:num:55
|
|
symbol::02315N600,asset_class::International Developed,pct:num:20
|
|
symbol::02315N600,asset_class::Bonds,pct:num:15
|
|
symbol::02315N600,asset_class::Emerging Markets,pct:num:10
|
|
|
|
# BDC / REIT / specialty
|
|
symbol::ARCC,sector::Financials,geo::US,asset_class::US Large Cap
|
|
```
|
|
|
|
### Classification fields
|
|
|
|
| Field | Type | Required | Description |
|
|
|---------------|--------|----------|---------------------------------------------------------------------------|
|
|
| `symbol` | string | Yes | Ticker symbol or CUSIP (must match `symbol::` or `ticker::` in portfolio) |
|
|
| `asset_class` | string | No | e.g. "US Large Cap", "Bonds", "Cash & CDs", "Emerging Markets" |
|
|
| `sector` | string | No | e.g. "Technology", "Healthcare", "Financials" |
|
|
| `geo` | string | No | e.g. "US", "International Developed", "Emerging Markets" |
|
|
| `pct` | number | No | Percentage weight for this entry (default 100). Use for blended funds. |
|
|
|
|
For single-asset-class securities (individual stocks, single-focus ETFs), one line at the default 100% is sufficient. For multi-asset-class funds (target date, balanced), add multiple lines for the same symbol with `pct:num:` values that sum to approximately 100.
|
|
|
|
Cash and CD lots are automatically classified as "Cash & CDs" without needing metadata entries.
|
|
|
|
### Bootstrapping metadata
|
|
|
|
Use the `enrich` command to generate a starting `metadata.srf` from Alpha Vantage company overview data:
|
|
|
|
```bash
|
|
# Enrich an entire portfolio (generates full metadata.srf)
|
|
zfin enrich portfolio.srf > metadata.srf
|
|
|
|
# Enrich a single symbol and append to existing 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 `>>`.
|
|
|
|
## Account metadata (accounts.srf)
|
|
|
|
The `accounts.srf` file maps account names to tax types for the tax type breakdown in portfolio analysis. Place it in the same directory as the portfolio file.
|
|
|
|
```
|
|
#!srfv1
|
|
account::Brokerage,tax_type::taxable
|
|
account::Roth IRA,tax_type::roth
|
|
account::Traditional IRA,tax_type::traditional
|
|
account::Fidelity 401k,tax_type::traditional
|
|
account::HSA,tax_type::hsa
|
|
```
|
|
|
|
### Account fields
|
|
|
|
| Field | Type | Required | Description |
|
|
|------------|--------|----------|-----------------------------------------------------------------|
|
|
| `account` | string | Yes | Account name (must match `account::` in portfolio lots exactly) |
|
|
| `tax_type` | string | Yes | Tax classification (see below) |
|
|
|
|
### Tax types
|
|
|
|
| Value | Display label |
|
|
|---------------|-----------------------|
|
|
| `taxable` | Taxable |
|
|
| `roth` | Roth (Post-Tax) |
|
|
| `traditional` | Traditional (Pre-Tax) |
|
|
| `hsa` | HSA (Triple Tax-Free) |
|
|
| (other) | Shown as-is |
|
|
|
|
Accounts not listed in `accounts.srf` appear as "Unknown" in the tax type breakdown.
|
|
|
|
## CLI commands
|
|
|
|
```
|
|
zfin <command> [args]
|
|
|
|
Commands:
|
|
perf <SYMBOL> Trailing returns (1yr/3yr/5yr/10yr, price + total)
|
|
quote <SYMBOL> Real-time quote with chart
|
|
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 summary (default: portfolio.srf)
|
|
analysis [FILE] Portfolio analysis breakdowns (default: portfolio.srf)
|
|
enrich <FILE|SYMBOL> Generate metadata.srf from Alpha Vantage
|
|
lookup <CUSIP> CUSIP to ticker lookup via OpenFIGI
|
|
cache stats Show cached symbols
|
|
cache clear Delete all cached data
|
|
interactive, i Launch interactive TUI
|
|
help Show usage
|
|
|
|
Global options:
|
|
--no-color Disable colored output (also respects NO_COLOR env)
|
|
|
|
Portfolio options:
|
|
--refresh Force re-fetch all prices (ignore cache)
|
|
-w, --watchlist <FILE> Watchlist file
|
|
```
|
|
|
|
## Architecture
|
|
|
|
```
|
|
src/
|
|
root.zig Library root, exports all public types
|
|
format.zig Shared formatters, braille engine, ANSI helpers
|
|
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
|
|
classification.zig Classification metadata parser
|
|
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, company overview
|
|
openfigi.zig OpenFIGI: CUSIP to ticker lookup
|
|
analytics/
|
|
indicators.zig SMA, Bollinger Bands, RSI
|
|
performance.zig Trailing returns (as-of-date + month-end)
|
|
risk.zig Volatility, Sharpe, drawdown, portfolio summary
|
|
analysis.zig Portfolio analysis engine (breakdowns by class/sector/geo/account/tax)
|
|
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
|
|
chart.zig z2d pixel chart renderer (Kitty graphics)
|
|
keybinds.zig Configurable keybinding system
|
|
theme.zig Configurable color theme
|
|
```
|
|
|
|
Data files (user-managed, in project root):
|
|
```
|
|
portfolio.srf Portfolio lots
|
|
metadata.srf Classification metadata for analysis
|
|
accounts.srf Account to tax type mapping for analysis
|
|
```
|
|
|
|
### Dependencies
|
|
|
|
| Dependency | Source | Purpose |
|
|
|----------------------------------------------------|---------------------|--------------------------------------------------|
|
|
| [SRF](https://github.com/lobo/srf) | Local (`../../srf`) | Cache file format and portfolio/watchlist parsing |
|
|
| [libvaxis](https://github.com/rockorager/libvaxis) | Git (v0.5.1) | Terminal UI rendering |
|
|
|
|
## Building
|
|
|
|
```bash
|
|
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
|