{"id":50808136,"url":"https://github.com/alwank/etfray","last_synced_at":"2026-06-30T10:00:48.185Z","repository":{"id":359064863,"uuid":"1244337069","full_name":"alwank/etfray","owner":"alwank","description":"Terminal ETF research \u0026 portfolio analytics via SEC EDGAR and IBKR","archived":false,"fork":false,"pushed_at":"2026-06-01T07:58:10.000Z","size":24756,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-28T16:05:01.370Z","etag":null,"topics":["etf","etf-investments","investing","investment","open-source","quantitative-finance"],"latest_commit_sha":null,"homepage":"http://etfray.xyz/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alwank.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-20T07:09:57.000Z","updated_at":"2026-06-01T08:47:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/alwank/etfray","commit_stats":null,"previous_names":["alwank/etfray"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/alwank/etfray","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwank%2Fetfray","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwank%2Fetfray/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwank%2Fetfray/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwank%2Fetfray/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alwank","download_url":"https://codeload.github.com/alwank/etfray/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwank%2Fetfray/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34961549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["etf","etf-investments","investing","investment","open-source","quantitative-finance"],"created_at":"2026-06-13T03:00:26.860Z","updated_at":"2026-06-30T10:00:48.176Z","avatar_url":"https://github.com/alwank.png","language":"Python","funding_links":[],"categories":["Portfolio Optimization \u0026 Risk Analysis"],"sub_categories":[],"readme":"# etfray\n\n[![CI](https://github.com/alwank/etfray/actions/workflows/ci.yml/badge.svg)](https://github.com/alwank/etfray/actions/workflows/ci.yml)\n[![Docs](https://readthedocs.org/projects/etfray/badge/?version=latest)](https://etfray.readthedocs.io/en/latest/)\n[![PyPI](https://img.shields.io/pypi/v/etfray)](https://pypi.org/project/etfray/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/Hero.jpeg\" alt=\"etfray\" width=\"800\"\u003e\n\u003c/p\u003e\n\nA terminal-based ETF research and portfolio analytics application built with [Textual](https://textual.textualize.io/).\n\netfray converts SEC fund filings and IBKR portfolio data into holdings, exposure, concentration, margin, and risk workflows — all from your terminal.\n\n## Why etfray?\n\n- **No cloud accounts** — No sign-ups, no API keys to manage, no third-party dashboards. Your data stays on your machine.\n- **No subscriptions** — ETF holdings data comes directly from SEC EDGAR filings. Free, authoritative, and always available.\n- **Keyboard-first** — Designed for speed. Command palette, tree navigation, and keybindings — no mouse required.\n\n## Features\n\n- **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\n- **ETF Research** — Search ETFs, view holdings, sector/geographic exposure, concentration, fees, risk metrics, and SEC documents via EDGAR\n- **Seasonals** — TradingView-style seasonals chart with year-over-year cumulative returns, period returns table (1W to Max), and year range selection\n- **Fund Overview** — Rich fund profile combining SEC filings with Yahoo Finance metadata (category, expense ratio, dividend yield, beta, returns, description)\n- **Watchlist** — Track ETFs with at-a-glance metrics: concentration, top sectors, overlap vs portfolio, and data freshness\n- **Portfolio Analytics** — Connect to IBKR TWS/Gateway for live positions, lookthrough exposure, concentration analysis, margin/leverage monitoring, and stress scenarios\n- **Side-by-side Compare** — Compare multiple ETFs across holdings, exposure, overlap, fees, and 52-week returns in a single view\n- **Export** — Save any view to CSV or JSON for further analysis\n- **Keyboard-first** — Full TUI with command palette, tree navigation, and keybindings\n- **Local \u0026 private** — All data cached locally in SQLite; no cloud accounts required\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/Home.png\" width=\"400\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003eHome Dashboard\u003c/b\u003e\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/Seasonals.png\" width=\"400\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003eSeasonals Chart\u003c/b\u003e\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/holdings.png\" width=\"400\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003eETF Holdings\u003c/b\u003e\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/Watchlist.png\" width=\"400\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003eWatchlist\u003c/b\u003e\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/compare.png\" width=\"400\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003eSide-by-side Compare\u003c/b\u003e\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/Portfolio_Lookthrough.png\" width=\"400\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003ePortfolio ETF Lookthrough\u003c/b\u003e\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## Key Capabilities\n\n| Capability | Details |\n|---|---|\n| ETF coverage | Thousands of ETFs via SEC EDGAR N-PORT filings |\n| Data sources | EDGAR (official), alternative web scraper, Yahoo Finance (metadata, price history \u0026 screener), IBKR TWS |\n| Home Dashboard | Live benchmark marquee, ETF movers (gainers/losers), watchlist snapshot, seasonal spotlight, recent quick-jump |\n| Holdings analysis | Full position-level breakdown with weight, value, shares |\n| Fund metadata | Category, expense ratio, dividend yield, beta, inception date, returns via yfinance |\n| Seasonals | Year-over-year cumulative return chart with matplotlib or plotext rendering; seasonal spotlight on home screen |\n| Exposure | Sector and geographic exposure from underlying holdings |\n| Concentration | Top-N analysis (top 10, 25, 50) with cumulative weight; Jaccard pairwise overlap scoring at portfolio level |\n| Compare | Side-by-side comparison of 2–5 ETFs with weight-adjusted overlap and 52-week return columns |\n| Watchlist | Track ETFs with concentration metrics, sector breakdown, and portfolio overlap |\n| Portfolio | Real-time positions, lookthrough exposure, margin \u0026 leverage, stress scenarios (−10%/−20%) |\n| Storage | Local SQLite — no cloud, no external databases |\n| Freshness | Configurable staleness thresholds (default: 30 days fresh, 90 days acceptable) |\n\n## Usage Examples\n\n### Home Dashboard\n\nThe Home screen loads automatically at startup with four live panels:\n\n1. **Benchmark Marquee** — Scrolling YTD return bar for SPY, QQQ, AGG, GLD. Click **Refresh** to force a fresh fetch.\n2. **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.\n3. **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.\n4. **Seasonal Spotlight** — Current-month win rate and MTD return for each watchlist ticker (e.g., `SPY ↑9/15 yrs +1.2% MTD`).\n\nRecent ETFs appear as quick-jump pill buttons below the panels.\n\n### Research an ETF\n\n1. Launch `etfray` and navigate to **Research → Search** in the sidebar\n2. Press `/` to open ETF Search, type a ticker (e.g., `VTI`), and press Enter\n3. Browse tabs: **Overview** → **Seasonals** → **Holdings** → **Exposure** → **Concentration** → **Risk**\n4. Press `w` to add the ETF to your watchlist\n\n### View seasonals\n\n1. Search for an ETF (e.g., `SPY`)\n2. Press `t` to jump to the Seasonals view\n3. Select year range to compare seasonal patterns across years\n4. Review the period returns table for standard return intervals\n\n### Manage your watchlist\n\n1. Navigate to **Workspace → Watchlist** in the sidebar\n2. Click **Add ticker** to search and add ETFs\n3. View concentration, sector, and overlap metrics at a glance\n4. Double-click any row to open that ETF's research view\n\n### Monitor your portfolio\n\n1. Ensure IBKR TWS/Gateway is running with API enabled on port 7497\n2. Navigate to **Portfolio → Positions** in the sidebar\n3. etfray connects lazily — positions load automatically on first access\n4. Switch to **Lookthrough** to see aggregated exposure across all your ETF holdings\n5. Check **Margin** for leverage ratio and margin cushion warnings\n\n## Architecture\n\n```mermaid\ngraph LR\n    A[SEC EDGAR API] --\u003e C[Data Services]\n    B[Web Scraper] --\u003e C\n    Y[Yahoo Finance] --\u003e C\n    D[IBKR TWS API] --\u003e C\n    C --\u003e E[(SQLite Cache)]\n    E --\u003e F[Domain Analytics]\n    F --\u003e G[Textual TUI]\n```\n\nDesign principles:\n\n- **Local-first** — All data cached in SQLite. Works offline after initial fetch.\n- **Source provenance** — Every data point tracks its origin and fetch date so you know how fresh it is.\n- **Lazy connection** — IBKR connects only when portfolio views are accessed, not at startup.\n- **Separation of concerns** — `data/` handles I/O, `domain/` handles computation, `ui/` handles presentation.\n\n## Configuration\n\nAll settings are managed via **Workspace → Settings** in the sidebar and stored in `~/.etfray/data.db`.\n\n| Setting | Default | Description |\n|---|---|---|\n| `ibkr_host` | `127.0.0.1` | IBKR TWS/Gateway host address |\n| `ibkr_port` | `7497` | IBKR TWS/Gateway API port |\n| `ibkr_client_id` | `1` | Client ID for the IBKR API connection |\n| `edgar_identity` | *(empty)* | Your email — required by SEC fair use policy |\n| `data_source` | `auto` | Holdings source: `auto`, `edgar`, or `web` |\n| `freshness_days_fresh` | `30` | Days before cached data is no longer considered fresh |\n| `freshness_days_acceptable` | `90` | Days before cached data is considered stale and re-fetched |\n| `margin_warning_cushion` | `0.15` | Margin cushion threshold for warnings |\n| `leverage_warning` | `2.0` | Leverage ratio warning threshold |\n| `cache_dir` | `~/.etfray/cache` | Directory for SEC series/class lookup cache files |\n| `export_dir` | `~/.etfray/exports` | Directory where CSV/JSON exports are saved |\n\nSee the [full configuration reference](https://etfray.readthedocs.io/en/latest/user-guide/configuration/) for all options.\n\n## Installation\n\n```bash\npip install etfray\n```\n\nRequires Python 3.11+.\n\n**Seasonals chart (optional):** For a matplotlib seasonals chart in the Seasonals tab:\n\n```bash\npip install etfray[charts]\n# or from source:\npip install -e \".[charts]\"\n```\n\nVerify dependencies: `python scripts/check_charts.py` (should report `Chart: image (matplotlib)` and `True`).\n\n**Terminal image support** is required for a crisp chart (not blocky ASCII). Enable one of:\n\n- **Cursor / VS Code:** Settings → `terminal.integrated.enableImages` → `true`, then restart the terminal\n- **iTerm2, Kitty, WezTerm, or Windows Terminal 1.22+** (recommended)\n\nWithout `[charts]` or without image support, etfray uses an ASCII plotext chart and shows the active mode in the Seasonals summary line.\n\n**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`.\n\n## Quick Start\n\n```bash\netfray\n```\n\nUse the sidebar tree to navigate between Research and Portfolio workspaces. Press `ctrl+p` to open the command palette.\n\n### IBKR Connection\n\nTo use portfolio analytics, you need [IBKR TWS](https://www.interactivebrokers.com/en/trading/tws.php) or [IB Gateway](https://www.interactivebrokers.com/en/trading/ibgateway-stable.php) running with API connections enabled (default port 7497).\n\nConfigure the connection in **Workspace → Settings** in the sidebar.\n\n## Documentation\n\nFull documentation at [etfray.readthedocs.io](https://etfray.readthedocs.io/en/latest/):\n\n- [Installation](https://etfray.readthedocs.io/en/latest/getting-started/installation/)\n- [User Guide](https://etfray.readthedocs.io/en/latest/user-guide/etf-research/)\n- [Seasonals](https://etfray.readthedocs.io/en/latest/user-guide/seasonals/)\n- [Watchlist](https://etfray.readthedocs.io/en/latest/user-guide/watchlist/)\n- [IBKR Setup](https://etfray.readthedocs.io/en/latest/user-guide/ibkr-setup/)\n- [Developer Guide](https://etfray.readthedocs.io/en/latest/developer/architecture/)\n\n## Development\n\n```bash\ngit clone https://github.com/alwank/etfray.git\ncd etfray\npython -m venv .venv\nsource .venv/bin/activate\npip install -e \".[dev,docs]\"\npytest\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falwank%2Fetfray","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falwank%2Fetfray","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falwank%2Fetfray/lists"}