etfray
May 24, 2026 · View on GitHub
A terminal-based ETF research and portfolio analytics application built with Textual.
etfray converts SEC fund filings and IBKR portfolio data into holdings, exposure, concentration, margin, and risk workflows — all from your terminal.
Why etfray?
- No cloud accounts — No sign-ups, no API keys to manage, no third-party dashboards. Your data stays on your machine.
- No subscriptions — ETF holdings data comes directly from SEC EDGAR filings. Free, authoritative, and always available.
- Keyboard-first — Designed for speed. Command palette, tree navigation, and keybindings — no mouse required.
Features
- Home Dashboard — Live startup screen with benchmark marquee (SPY/QQQ/AGG/GLD YTD), watchlist snapshot, ETF daily movers (top-5 gainers/losers), seasonal spotlight for the current month, and recent quick-jump pills
- ETF Research — Search ETFs, view holdings, sector/geographic exposure, concentration, fees, risk metrics, and SEC documents via EDGAR
- Seasonals — TradingView-style seasonals chart with year-over-year cumulative returns, period returns table (1W to Max), and year range selection
- Fund Overview — Rich fund profile combining SEC filings with Yahoo Finance metadata (category, expense ratio, dividend yield, beta, returns, description)
- Watchlist — Track ETFs with at-a-glance metrics: concentration, top sectors, overlap vs portfolio, and data freshness
- Portfolio Analytics — Connect to IBKR TWS/Gateway for live positions, lookthrough exposure, concentration analysis, margin/leverage monitoring, and stress scenarios
- Side-by-side Compare — Compare multiple ETFs across holdings, exposure, overlap, fees, and 52-week returns in a single view
- Export — Save any view to CSV or JSON for further analysis
- Keyboard-first — Full TUI with command palette, tree navigation, and keybindings
- Local & private — All data cached locally in SQLite; no cloud accounts required
![]() Home Dashboard |
![]() Seasonals Chart |
![]() ETF Holdings |
![]() Watchlist |
![]() Side-by-side Compare |
![]() Portfolio ETF Lookthrough |
Key Capabilities
| Capability | Details |
|---|---|
| ETF coverage | Thousands of ETFs via SEC EDGAR N-PORT filings |
| Data sources | EDGAR (official), alternative web scraper, Yahoo Finance (metadata, price history & screener), IBKR TWS |
| Home Dashboard | Live benchmark marquee, ETF movers (gainers/losers), watchlist snapshot, seasonal spotlight, recent quick-jump |
| Holdings analysis | Full position-level breakdown with weight, value, shares |
| Fund metadata | Category, expense ratio, dividend yield, beta, inception date, returns via yfinance |
| Seasonals | Year-over-year cumulative return chart with matplotlib or plotext rendering; seasonal spotlight on home screen |
| Exposure | Sector and geographic exposure from underlying holdings |
| Concentration | Top-N analysis (top 10, 25, 50) with cumulative weight; Jaccard pairwise overlap scoring at portfolio level |
| Compare | Side-by-side comparison of 2–5 ETFs with weight-adjusted overlap and 52-week return columns |
| Watchlist | Track ETFs with concentration metrics, sector breakdown, and portfolio overlap |
| Portfolio | Real-time positions, lookthrough exposure, margin & leverage, stress scenarios (−10%/−20%) |
| Storage | Local SQLite — no cloud, no external databases |
| Freshness | Configurable staleness thresholds (default: 30 days fresh, 90 days acceptable) |
Usage Examples
Home Dashboard
The Home screen loads automatically at startup with four live panels:
- Benchmark Marquee — Scrolling YTD return bar for SPY, QQQ, AGG, GLD. Click Refresh to force a fresh fetch.
- Watchlist Snapshot — Compact table of your tracked ETFs with YTD, Top-10 Weight, Effective N, HHI, and Top Sector. Double-click any row to open that ETF.
- ETF Movers — Top-5 daily gainers and losers. A yellow "Last session" label appears when market data is stale (outside trading hours). Double-click to open an ETF. Click Refresh for the latest data.
- Seasonal Spotlight — Current-month win rate and MTD return for each watchlist ticker (e.g.,
SPY ↑9/15 yrs +1.2% MTD).
Recent ETFs appear as quick-jump pill buttons below the panels.
Research an ETF
- Launch
etfrayand navigate to Research → Search in the sidebar - Press
/to open ETF Search, type a ticker (e.g.,VTI), and press Enter - Browse tabs: Overview → Seasonals → Holdings → Exposure → Concentration → Risk
- Press
wto add the ETF to your watchlist
View seasonals
- Search for an ETF (e.g.,
SPY) - Press
tto jump to the Seasonals view - Select year range to compare seasonal patterns across years
- Review the period returns table for standard return intervals
Manage your watchlist
- Navigate to Workspace → Watchlist in the sidebar
- Click Add ticker to search and add ETFs
- View concentration, sector, and overlap metrics at a glance
- Double-click any row to open that ETF's research view
Monitor your portfolio
- Ensure IBKR TWS/Gateway is running with API enabled on port 7497
- Navigate to Portfolio → Positions in the sidebar
- etfray connects lazily — positions load automatically on first access
- Switch to Lookthrough to see aggregated exposure across all your ETF holdings
- Check Margin for leverage ratio and margin cushion warnings
Architecture
graph LR
A[SEC EDGAR API] --> C[Data Services]
B[Web Scraper] --> C
Y[Yahoo Finance] --> C
D[IBKR TWS API] --> C
C --> E[(SQLite Cache)]
E --> F[Domain Analytics]
F --> G[Textual TUI]
Design principles:
- Local-first — All data cached in SQLite. Works offline after initial fetch.
- Source provenance — Every data point tracks its origin and fetch date so you know how fresh it is.
- Lazy connection — IBKR connects only when portfolio views are accessed, not at startup.
- Separation of concerns —
data/handles I/O,domain/handles computation,ui/handles presentation.
Configuration
All settings are managed via Workspace → Settings in the sidebar and stored in ~/.etfray/data.db.
| Setting | Default | Description |
|---|---|---|
ibkr_host | 127.0.0.1 | IBKR TWS/Gateway host address |
ibkr_port | 7497 | IBKR TWS/Gateway API port |
ibkr_client_id | 1 | Client ID for the IBKR API connection |
edgar_identity | (empty) | Your email — required by SEC fair use policy |
data_source | auto | Holdings source: auto, edgar, or web |
freshness_days_fresh | 30 | Days before cached data is no longer considered fresh |
freshness_days_acceptable | 90 | Days before cached data is considered stale and re-fetched |
margin_warning_cushion | 0.15 | Margin cushion threshold for warnings |
leverage_warning | 2.0 | Leverage ratio warning threshold |
cache_dir | ~/.etfray/cache | Directory for SEC series/class lookup cache files |
export_dir | ~/.etfray/exports | Directory where CSV/JSON exports are saved |
See the full configuration reference for all options.
Installation
pip install etfray
Requires Python 3.11+.
Seasonals chart (optional): For a matplotlib seasonals chart in the Seasonals tab:
pip install etfray[charts]
# or from source:
pip install -e ".[charts]"
Verify dependencies: python scripts/check_charts.py (should report Chart: image (matplotlib) and True).
Terminal image support is required for a crisp chart (not blocky ASCII). Enable one of:
- Cursor / VS Code: Settings →
terminal.integrated.enableImages→true, then restart the terminal - iTerm2, Kitty, WezTerm, or Windows Terminal 1.22+ (recommended)
Without [charts] or without image support, etfray uses an ASCII plotext chart and shows the active mode in the Seasonals summary line.
Blurry chart? If the summary says Chart: image (halfcell) or (unicode), the terminal is using a low-resolution block renderer. For a sharp chart, run etfray in iTerm2 or Kitty, or enable Cursor terminal.integrated.enableImages and restart the terminal. Check python scripts/check_charts.py for protocol: sixel or tgp.
Quick Start
etfray
Use the sidebar tree to navigate between Research and Portfolio workspaces. Press ctrl+p to open the command palette.
IBKR Connection
To use portfolio analytics, you need IBKR TWS or IB Gateway running with API connections enabled (default port 7497).
Configure the connection in Workspace → Settings in the sidebar.
Documentation
Full documentation at etfray.readthedocs.io:
Development
git clone https://github.com/alwank/etfray.git
cd etfray
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,docs]"
pytest





