# zfin

A financial data library, CLI, and terminal UI written in Zig. Tracks portfolios, analyzes trailing returns, displays options chains and earnings history, and renders price and projection charts (inline Kitty graphics or PNG export) -- all from the terminal.

## Quick start

### macOS (Apple Silicon) -- pre-built binary

```bash
# Download the latest aarch64-macos binary, make it executable, drop on $PATH
curl -L -o zfin \
  https://git.lerch.org/api/packages/lobo/generic/zfin-aarch64-macos/latest/zfin-aarch64-macos
chmod +x zfin
sudo mv zfin /usr/local/bin/   # or anywhere on $PATH
```

### 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.

## Documentation

Full user documentation lives in [`docs/`](docs/README.md) and is built
around the runnable example portfolios in [`examples/`](examples/).

- **New here?** [Getting started](docs/getting-started.md) -- install,
  configure, and run your first commands.
- **Learn the model:** [Core concepts](docs/explanation/concepts.md).
- **Do a task (the user manual):**
  - [Build your portfolio](docs/guides/set-up-your-portfolio.md)
  - [Classify your holdings](docs/guides/classify-holdings.md) and [map your accounts](docs/guides/set-up-accounts.md)
  - [Read your portfolio](docs/guides/read-your-portfolio.md)
  - [Plan for retirement](docs/guides/plan-retirement.md)
  - [Snapshots and history](docs/guides/snapshots-and-history.md), [track contributions](docs/guides/track-contributions.md)
  - [Audit against your brokerage](docs/guides/audit-against-brokerage.md)
  - [Customize the TUI](docs/guides/customize-the-tui.md), [offline use and refreshing data](docs/guides/offline-and-refresh.md)
- **Look something up (reference):**
  - [CLI commands](docs/reference/cli/index.md)
  - Config files: [`portfolio.srf`](docs/reference/config/portfolio-srf.md), [`accounts.srf`](docs/reference/config/accounts-srf.md), [`metadata.srf`](docs/reference/config/metadata-srf.md), [`projections.srf`](docs/reference/config/projections-srf.md), and [more](docs/reference/config/environment.md)
  - [The interactive TUI](docs/reference/tui.md)
  - [Data providers and API keys](docs/reference/providers.md)
- **Understand the why (explanation):** [caching](docs/explanation/caching.md), [data providers](docs/explanation/data-providers.md), [returns](docs/explanation/returns-and-performance.md), [the projection model](docs/explanation/projections-model.md), [FAQ](docs/explanation/faq-troubleshooting.md).

## Data providers

zfin aggregates data from multiple free-tier APIs, using each for what
it does best, with aggressive caching to stay within free-tier limits.

| Data type             | Provider         | Auth                 | Free-tier limit            | Cache TTL    |
|-----------------------|------------------|----------------------|----------------------------|--------------|
| Daily candles (OHLCV) | Tiingo           | `TIINGO_API_KEY`     | 1,000 req/day, 50 req/hour | 23h45m       |
| Real-time quotes      | Yahoo Finance    | None required        | Unofficial                 | Never cached |
| Quote fallback        | 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              | 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   |

Not all keys are required; without a given key, that data type is
simply unavailable. For per-provider notes, signup links, the full
caching/TTL model, and the complete environment-variable list, see
[Data providers and API keys](docs/reference/providers.md),
[Caching and data freshness](docs/explanation/caching.md), and
[Environment variables](docs/reference/config/environment.md).

## Architecture

The data/cache/fetch flow -- freshness, negative caching, incremental
candle updates, and the optional server tier -- is documented in
[Cache implementation](docs/dev/caching-implementation.md).

```
src/
  main.zig                CLI dispatch + TUI entry point
  root.zig                Library root, exports all public types
  Config.zig              Configuration from env vars / .env files
  service.zig             DataService: cache-check -> fetch -> cache -> return
  Date.zig                Date type (i32 days since epoch); arithmetic, formatting
  Money.zig               Money formatting ($1,234.56; whole/trim/signed variants)
  format.zig              Shared formatters, braille engine, ANSI helpers
  padded.zig              Generic width/alignment wrapper for any formattable type
  PortfolioData.zig       Per-portfolio parse, price fetch, summary, background workers
  portfolio_loader.zig    Loads + union-merges portfolio_*.srf for CLI and TUI
  compare.zig             Composes two points in time (snapshot vs live) for `compare`
  history.zig             Reads history/<date>-portfolio.srf snapshots; aggregation
  market.zig              Market calendar; close-anchored cache freshness
  git.zig                 Thin git subprocess wrappers (diff/walk tracked portfolios)
  atomic.zig              Crash-safe atomic file writes (tmp + fsync + rename)
  stderr.zig              Best-effort (non-throwing) stderr hint/progress writers
  term_graphics.zig       Kitty graphics emission for plain (non-TUI) CLI charts
  term_query.zig          Terminal capability probing for inline CLI graphics
  chart_export.zig        Chart-to-PNG export (`--export-chart <path>`)
  comptime_validator.zig  Shared comptime contract-validation helpers
  models/
    candle.zig           OHLCV price bars
    quote.zig            Real-time quote data
    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
    snapshot.zig         Snapshot record types (history/*-portfolio.srf wire format)
    transaction_log.zig  transaction_log.srf wire format (transfer:: records)
    shiller_year.zig     One year of Shiller returns (S&P / bond / CPI)
  providers/
    tiingo.zig        Tiingo: daily candles (primary)
    yahoo.zig         Yahoo Finance: quotes (primary), candle fallback
    twelvedata.zig    TwelveData: quote fallback
    polygon.zig       Polygon: dividends, splits (with forward-looking entries)
    fmp.zig           FMP: earnings (actuals + estimates)
    cboe.zig          CBOE: options chains (no API key)
    Edgar.zig         SEC EDGAR: ETF profiles (NPORT-P), ticker map, XBRL facts
    Wikidata.zig      Wikidata SPARQL: classification metadata for `enrich`
    openfigi.zig      OpenFIGI: CUSIP to ticker lookup
    json_utils.zig    Shared JSON parsing helpers
    xml.zig           Vendored XML parser used by Edgar.zig (see "Vendored code")
  analytics/
    indicators.zig           SMA, Bollinger Bands, RSI
    performance.zig          Trailing returns (as-of-date + month-end)
    risk.zig                 Per-symbol volatility, Sharpe, drawdown
    portfolio_risk.zig       Correlation-aware portfolio-level risk
    valuation.zig            Portfolio summary, allocations, covered-call adjustments
    analysis.zig             Breakdowns by class/sector/geo/account/tax
    benchmark.zig            Per-position benchmark returns
    exposure.zig             Look-through exposure (direct + ETF holdings)
    milestones.zig           Retirement-attainment grid
    projections.zig          Monte Carlo + percentile bands
    forecast_evaluation.zig  Forecast-vs-actual (convergence + return back-test)
    observations.zig         Portfolio sanity checks for the review surface
    timeline.zig             History-tab tier rollup
  data/
    ie_data.csv          Raw Shiller S&P 500 + CPI dataset (CSV)
    shiller.zig          Shiller series, comptime-generated from ie_data.csv
    imported_values.zig  Back-history portfolio values (imported from spreadsheet)
    staleness.zig        Account-cadence staleness checks
    Journal.zig          acknowledgments.srf journal (acknowledged findings)
  brokerage/
    types.zig        Normalized BrokeragePosition record + dollar parser
    fidelity.zig     Fidelity "Download Positions" CSV parser
    schwab.zig       Schwab export parser
    wells_fargo.zig  Wells Fargo export parser
  views/
    portfolio_sections.zig  Portfolio view model (renderer-agnostic StyleIntent)
    compare.zig             Compare view model
    history.zig             History view model
    projections.zig         Projections view model
    review.zig              Per-holding performance + risk dashboard view model
    observations_view.zig   Findings list (engine output + acknowledgments)
  cache/
    store.zig        SRF file cache with TTL freshness checks
  net/
    http.zig         HTTP client with retries and server-error retry
    RateLimiter.zig  Token-bucket rate limiter
  commands/
    common.zig       Shared CLI helpers (progress, formatting, color)
    framework.zig    CLI command framework + portfolio pattern resolution
    TimeRange.zig    Shared --range / time-window parsing
    audit/           Brokerage reconciliation helpers for `audit`
    (one .zig per CLI command: perf, quote, portfolio, analysis, history, ...)
  charts/
    chart.zig             Price + Bollinger + volume + RSI renderer (z2d)
    projection_chart.zig  Percentile-band + median projection renderer
    forecast_chart.zig    Convergence / back-test line renderer
    line_chart.zig        Single-series value-over-time line renderer
    axis.zig              Shared axis-label helpers
    draw.zig              Shared z2d drawing primitives
    text.zig              5x7 bitmap font for axis labels
  tui.zig                 Interactive TUI App orchestrator
  tui/
    tab_framework.zig  Tab contract + comptime validator
    keybinds.zig       Configurable keybinding system
    theme.zig          Configurable color theme
    input_buffer.zig   Modal text-input state machine
    (nine *_tab.zig tabs: portfolio, analysis, review, projections,
     history, quote, performance, earnings, options)
  version.zig             Build version string
```

Data files (user-managed, auto-detected in cwd or `$ZFIN_HOME`):
```
portfolio.srf        Portfolio lots (positions and cost basis)
accounts.srf         Account -> tax-type mapping for analysis
metadata.srf         Per-symbol classification (class/sector/geo) for analysis
watchlist.srf        Symbols to track in the TUI watchlist
transaction_log.srf  Declared transfers so contributions math skips internal moves
projections.srf      Retirement / Monte Carlo projection assumptions
theme.srf            TUI color theme overrides
keys.srf             TUI keybinding overrides
acknowledgments.srf  Acknowledged review-tab findings (app-maintained)
```

### Dependencies

| Dependency                                         | Source        | Purpose                                           |
|----------------------------------------------------|---------------|---------------------------------------------------|
| [SRF](https://git.lerch.org/lobo/srf)              | Git           | Cache file format and portfolio/watchlist parsing |
| [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)   |
| [zeit](https://github.com/rockorager/zeit)         | Git (v0.9.0)  | Calendar arithmetic + timezone conversion (ET)    |

## Building

Requires Zig 0.16.0. The canonical version is pinned in `.mise.toml`:

```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 test         # run all tests
zig build run -- <args> # build and run
```

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

## Vendored code

A small amount of third-party source is vendored directly into the
tree (rather than added as a Zig package dependency) where the
upstream is small, stable, and not packaged for `build.zig.zon`:

| File                    | Source                                                                                                                                                                                  | Purpose                                                                  |
|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
| `src/providers/xml.zig` | [Snektron/vulkan-zig](https://github.com/Snektron/vulkan-zig/blob/797ae8af88e84753af9640266de61a985b76b580/generator/xml.zig), via [aws-zig](https://github.com/elerch/aws-sdk-for-zig) | XML DOM parser used by the EDGAR provider for NPORT-P primary documents. |

Each vendored file carries a `// VENDORED - see README.md` header
identifying its upstream source. When updating, copy the new
upstream verbatim and re-add the header.

## License

MIT
