add shift-tab keybind

This commit is contained in:
Emil Lerch 2026-03-01 12:11:15 -08:00
parent facc233976
commit b510a86b09
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 83 additions and 82 deletions

164
README.md
View file

@ -33,15 +33,15 @@ zfin aggregates data from multiple free-tier APIs. Each provider is used for the
### Provider summary ### Provider summary
| Data type | Provider | Auth | Free-tier limit | Cache TTL | | Data type | Provider | Auth | Free-tier limit | Cache TTL |
|---|---|---|---|---| |-----------------------|---------------|------------------------|----------------------------|--------------|
| Daily candles (OHLCV) | TwelveData | `TWELVEDATA_API_KEY` | 8 req/min, 800/day | 24 hours | | 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 | | Real-time quotes | TwelveData | `TWELVEDATA_API_KEY` | 8 req/min, 800/day | Never cached |
| Dividends | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days | | Dividends | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days |
| Splits | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days | | Splits | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days |
| Options chains | CBOE | None required | ~30 req/min (self-imposed) | 1 hour | | Options chains | CBOE | None required | ~30 req/min (self-imposed) | 1 hour |
| Earnings | Finnhub | `FINNHUB_API_KEY` | 60 req/min | 24 hours | | Earnings | Finnhub | `FINNHUB_API_KEY` | 60 req/min | 24 hours |
| ETF profiles | Alpha Vantage | `ALPHAVANTAGE_API_KEY` | 25 req/day | 30 days | | ETF profiles | Alpha Vantage | `ALPHAVANTAGE_API_KEY` | 25 req/day | 30 days |
### TwelveData ### TwelveData
@ -102,12 +102,12 @@ The cache directory defaults to `~/.cache/zfin` and can be overridden with `ZFIN
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 |
|---|---| |------------------------|--------------------------------------------------------------------|
| `TWELVEDATA_API_KEY` | No candles, quotes, or trailing returns | | `TWELVEDATA_API_KEY` | No candles, quotes, or trailing returns |
| `POLYGON_API_KEY` | No dividends -- trailing returns show price-only (no total return) | | `POLYGON_API_KEY` | No dividends -- trailing returns show price-only (no total return) |
| `FINNHUB_API_KEY` | No earnings data (tab disabled) | | `FINNHUB_API_KEY` | No earnings data (tab disabled) |
| `ALPHAVANTAGE_API_KEY` | No ETF profiles | | `ALPHAVANTAGE_API_KEY` | No ETF profiles |
CBOE options require no API key. CBOE options require no API key.
@ -121,15 +121,15 @@ Every data fetch follows the same pattern:
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. 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 | | Data type | TTL | Rationale |
|---|---|---| |---------------|--------------|-------------------------------------------------|
| Daily candles | 24 hours | Only changes once per trading day | | Daily candles | 24 hours | Only changes once per trading day |
| Dividends | 7 days | Declared well in advance | | Dividends | 7 days | Declared well in advance |
| Splits | 7 days | Rare corporate events | | Splits | 7 days | Rare corporate events |
| Options | 1 hour | Prices change continuously during market hours | | Options | 1 hour | Prices change continuously during market hours |
| Earnings | 24 hours | Quarterly events, estimates update periodically | | Earnings | 24 hours | Quarterly events, estimates update periodically |
| ETF profiles | 30 days | Holdings/weights change slowly | | ETF profiles | 30 days | Holdings/weights change slowly |
| Quotes | Never cached | Intended for live price checks | | Quotes | Never cached | Intended for live price checks |
Manual refresh (`r` / `F5` in TUI) invalidates the cache for the current tab's data before re-fetching. Manual refresh (`r` / `F5` in TUI) invalidates the cache for the current tab's data before re-fetching.
@ -137,13 +137,13 @@ 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 |
|---|---| |---------------|------------|
| TwelveData | 8/minute | | TwelveData | 8/minute |
| Polygon | 5/minute | | Polygon | 5/minute |
| Finnhub | 60/minute | | Finnhub | 60/minute |
| CBOE | 30/minute | | CBOE | 30/minute |
| Alpha Vantage | 25/day | | Alpha Vantage | 25/day |
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.
@ -212,33 +212,33 @@ Default keybindings:
| Key | Action | | Key | Action |
|---|---| |---|---|
| `q`, `Ctrl+c` | Quit | | `q`, `Ctrl+c` | Quit |
| `r`, `F5` | Refresh current tab (invalidates cache) | | `r`, `F5` | Refresh current tab (invalidates cache) |
| `R` | Reload portfolio from disk (no network) | | `R` | Reload portfolio from disk (no network) |
| `h`, Left | Previous tab | | `h`, Left | Previous tab |
| `l`, Right, Tab | Next tab | | `l`, Right, Tab | Next tab |
| `1`-`6` | Jump to tab | | `1`-`6` | Jump to tab |
| `j`, Down | Select next row | | `j`, Down | Select next row |
| `k`, Up | Select previous row | | `k`, Up | Select previous row |
| `Enter` | Expand/collapse (positions, expirations, calls/puts) | | `Enter` | Expand/collapse (positions, expirations, calls/puts) |
| `s` | Select symbol from portfolio for other tabs | | `s` | Select symbol from portfolio for other tabs |
| `/` | Enter symbol search | | `/` | Enter symbol search |
| `e` | Edit portfolio/watchlist in `$EDITOR` | | `e` | Edit portfolio/watchlist in `$EDITOR` |
| `<` | Sort by previous column (portfolio tab) | | `<` | Sort by previous column (portfolio tab) |
| `>` | Sort by next column (portfolio tab) | | `>` | Sort by next column (portfolio tab) |
| `o` | Reverse sort direction (portfolio tab) | | `o` | Reverse sort direction (portfolio tab) |
| `[` | Previous chart timeframe (quote tab) | | `[` | Previous chart timeframe (quote tab) |
| `]` | Next chart timeframe (quote tab) | | `]` | Next chart timeframe (quote tab) |
| `c` | Toggle all calls collapsed/expanded (options tab) | | `c` | Toggle all calls collapsed/expanded (options tab) |
| `p` | Toggle all puts 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 | | `Ctrl+1`-`Ctrl+9` | Set options near-the-money filter to +/- N strikes |
| `g` | Scroll to top | | `g` | Scroll to top |
| `G` | Scroll to bottom | | `G` | Scroll to bottom |
| `Ctrl+d` | Half-page down | | `Ctrl+d` | Half-page down |
| `Ctrl+u` | Half-page up | | `Ctrl+u` | Half-page up |
| `PageDown` | Page down | | `PageDown` | Page down |
| `PageUp` | Page up | | `PageUp` | Page up |
| `?` | Help screen | | `?` | Help screen |
Mouse: scroll wheel navigates, left-click selects rows and switches tabs, double-click expands/collapses. Mouse: scroll wheel navigates, left-click selects rows and switches tabs, double-click expands/collapses.
@ -295,23 +295,23 @@ security_type::watch,symbol::TSLA
### Lot fields ### Lot fields
| Field | Type | Required | Description | | Field | Type | Required | Description |
|---|---|---|---| |-----------------|--------|----------|--------------------------------------------------------------------------|
| `symbol` | string | Yes* | Ticker symbol or CUSIP. *Optional for `cash` lots. | | `symbol` | string | Yes* | Ticker symbol or CUSIP. *Optional for `cash` lots. |
| `shares` | number | Yes | Number of shares (or face value for cash/CDs) | | `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_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. | | `open_price` | number | Yes** | Purchase price per share. **Not required for cash/watch. |
| `close_date` | string | No | Sale date (null = open lot) | | `close_date` | string | No | Sale date (null = open lot) |
| `close_price` | number | No | Sale price per share | | `close_price` | number | No | Sale price per share |
| `security_type` | string | No | `stock` (default), `option`, `cd`, `cash`, `illiquid`, `watch` | | `security_type` | string | No | `stock` (default), `option`, `cd`, `cash`, `illiquid`, `watch` |
| `account` | string | No | Account name (e.g. "Roth IRA", "Brokerage") | | `account` | string | No | Account name (e.g. "Roth IRA", "Brokerage") |
| `note` | string | No | Descriptive note (shown in cash/CD/illiquid tables) | | `note` | string | No | Descriptive note (shown in cash/CD/illiquid tables) |
| `ticker` | string | No | Ticker alias for price fetching (overrides `symbol` for API calls) | | `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` | 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) | | `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) | | `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) | | `maturity_date` | string | No | CD maturity date (YYYY-MM-DD) |
| `rate` | number | No | Interest rate for CDs (e.g. 5.25 = 5.25%) | | `rate` | number | No | Interest rate for CDs (e.g. 5.25 = 5.25%) |
### Security types ### Security types
@ -385,13 +385,13 @@ symbol::ARCC,sector::Financials,geo::US,asset_class::US Large Cap
### Classification fields ### Classification fields
| Field | Type | Required | Description | | Field | Type | Required | Description |
|---|---|---|---| |---------------|--------|----------|---------------------------------------------------------------------------|
| `symbol` | string | Yes | Ticker symbol or CUSIP (must match `symbol::` or `ticker::` in portfolio) | | `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" | | `asset_class` | string | No | e.g. "US Large Cap", "Bonds", "Cash & CDs", "Emerging Markets" |
| `sector` | string | No | e.g. "Technology", "Healthcare", "Financials" | | `sector` | string | No | e.g. "Technology", "Healthcare", "Financials" |
| `geo` | string | No | e.g. "US", "International Developed", "Emerging Markets" | | `geo` | string | No | e.g. "US", "International Developed", "Emerging Markets" |
| `pct` | number | No | Percentage weight for this entry (default 100). Use for blended funds. | | `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. 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.

View file

@ -84,6 +84,7 @@ const default_bindings = [_]Binding{
.{ .action = .refresh, .key = .{ .codepoint = vaxis.Key.f5 } }, .{ .action = .refresh, .key = .{ .codepoint = vaxis.Key.f5 } },
.{ .action = .prev_tab, .key = .{ .codepoint = 'h' } }, .{ .action = .prev_tab, .key = .{ .codepoint = 'h' } },
.{ .action = .prev_tab, .key = .{ .codepoint = vaxis.Key.left } }, .{ .action = .prev_tab, .key = .{ .codepoint = vaxis.Key.left } },
.{ .action = .prev_tab, .key = .{ .codepoint = vaxis.Key.tab, .mods = .{ .shift = true } } },
.{ .action = .next_tab, .key = .{ .codepoint = 'l' } }, .{ .action = .next_tab, .key = .{ .codepoint = 'l' } },
.{ .action = .next_tab, .key = .{ .codepoint = vaxis.Key.right } }, .{ .action = .next_tab, .key = .{ .codepoint = vaxis.Key.right } },
.{ .action = .next_tab, .key = .{ .codepoint = vaxis.Key.tab } }, .{ .action = .next_tab, .key = .{ .codepoint = vaxis.Key.tab } },