152 lines
6.9 KiB
Markdown
152 lines
6.9 KiB
Markdown
# Future Work
|
|
|
|
## CLI options command UX
|
|
|
|
The `options` command auto-expands only the nearest monthly expiration and
|
|
lists others collapsed. Reconsider the interaction model — e.g. allow
|
|
specifying an expiration date, showing all monthlies expanded by default,
|
|
or filtering by strategy (covered calls, spreads).
|
|
|
|
## Risk-free rate maintenance
|
|
|
|
T-bill rates are hardcoded in `src/analytics/risk.zig` as a year-by-year table
|
|
(source: FRED series DTB3). Each trailing period uses the average rate over its
|
|
date range. The table includes update instructions as doc comments.
|
|
|
|
**Action needed annually:** Update the current year's rate mid-year, finalize
|
|
the prior year's rate in January. See the curl commands in the `tbill_rates`
|
|
doc comment.
|
|
|
|
## CLI/TUI code review (lower priority)
|
|
|
|
No review has been done on these files. They are presentation-layer code
|
|
and not part of the reusable library API.
|
|
|
|
TUI:
|
|
- `src/tui.zig`
|
|
- `src/tui/chart.zig`
|
|
- `src/tui/keybinds.zig`
|
|
- `src/tui/theme.zig`
|
|
|
|
Commands:
|
|
- `src/commands/common.zig`
|
|
- `src/commands/analysis.zig`
|
|
- `src/commands/cache.zig`
|
|
- `src/commands/divs.zig`
|
|
- `src/commands/earnings.zig`
|
|
- `src/commands/enrich.zig`
|
|
- `src/commands/etf.zig`
|
|
- `src/commands/history.zig`
|
|
- `src/commands/lookup.zig`
|
|
- `src/commands/options.zig`
|
|
- `src/commands/perf.zig`
|
|
- `src/commands/portfolio.zig`
|
|
- `src/commands/quote.zig`
|
|
- `src/commands/splits.zig`
|
|
|
|
## TUI: toggle to last symbol keybind
|
|
|
|
Add a single-key toggle that flips between the current symbol and the
|
|
previously selected one (like `cd -` in bash or `Ctrl+^` in vim). Store
|
|
`last_symbol` on `App`; on symbol change, stash the previous. The toggle
|
|
key swaps current and last. Works on any tab — particularly useful for
|
|
eyeball-comparing performance/risk data between two symbols.
|
|
|
|
## Fix `enrich` command for international funds
|
|
|
|
`deriveMetadata` in `src/commands/enrich.zig` misclassifies international ETFs:
|
|
|
|
1. **`geo`** uses Alpha Vantage's `Country` field, which is the *fund issuer's*
|
|
domicile (USA for all US-listed ETFs), not the fund's investment geography.
|
|
Every US-domiciled international fund gets `geo::US`.
|
|
|
|
2. **`asset_class`** short-circuits to `"ETF"` when `asset_type == "ETF"`, or
|
|
falls through to a US-market-cap heuristic that always produces
|
|
`"US Large Cap"` / `"US Mid Cap"` / `"US Small Cap"`.
|
|
|
|
Known misclassified tickers (all came back as `geo::US, asset_class::US Large Cap`):
|
|
- **FRDM** — Freedom 100 Emerging Markets ETF → should be `geo::Emerging Markets, asset_class::Emerging Markets`
|
|
- **HFXI** — NYLI FTSE International Equity Currency Neutral ETF → should be `geo::International Developed, asset_class::International Developed`
|
|
- **IDMO** — Invesco S&P International Developed Momentum ETF → should be `geo::International Developed, asset_class::International Developed`
|
|
- **IVLU** — iShares MSCI International Developed Value Factor ETF → should be `geo::International Developed, asset_class::International Developed`
|
|
|
|
The Alpha Vantage OVERVIEW endpoint doesn't provide fund geography data.
|
|
Options: use the ETF_PROFILE holdings/country data to infer geography, parse
|
|
the fund name for keywords ("International", "Emerging", "ex-US"), or accept
|
|
that `enrich` is a scaffold and emit a `# TODO` comment for ETFs instead of
|
|
silently misclassifying.
|
|
|
|
## Market-aware cache TTL for daily candles
|
|
|
|
Daily candle TTL is currently 23h45m, but candle data only becomes meaningful
|
|
after the market close. Investigate keying the cache freshness to ~4:30 PM
|
|
Eastern rather than a rolling window. This would avoid unnecessary refetches
|
|
during the trading day and ensure a fetch shortly after close gets fresh data.
|
|
Probably alleviated by the cron job approach.
|
|
|
|
## On-demand server-side fetch for new symbols
|
|
|
|
Currently the server's SRF endpoints (`/candles`, `/dividends`, etc.) are pure
|
|
cache reads — they 404 if the data isn't already on disk. New symbols only get
|
|
populated when added to the portfolio and picked up by the next cron refresh.
|
|
|
|
Consider: on a cache miss, instead of blocking the HTTP response with a
|
|
multi-second provider fetch, kick off an async background fetch (or just
|
|
auto-add the symbol to the portfolio) and return 404 as usual. The next
|
|
request — or the next cron run — would then have the data. This gives
|
|
"instant-ish gratification" for new symbols without the downsides of
|
|
synchronous fetch-on-miss (latency, rate limit contention, unbounded cache
|
|
growth from arbitrary tickers).
|
|
|
|
Note that this process doesn't do anything to eliminate all the API keys
|
|
that are necessary for a fully functioning system. A more aggressive view
|
|
would be to treat ZFIN_SERVER as a 100% source of record, but that would
|
|
introduce some opacity to the process as we wait for candles (for example) to
|
|
populate. This could be solved on the server by spawning a thread to fetch the
|
|
data, then returning 202 Accepted, which could then be polled client side. Maybe
|
|
this is a better long term approach?
|
|
|
|
## Per-account covered call adjustment
|
|
|
|
`adjustForCoveredCalls` in `valuation.zig` operates on portfolio-wide aggregated
|
|
allocations. It matches sold calls against total underlying shares across all
|
|
accounts. This is wrong — calls in one account can only cover shares in that
|
|
same account. If NVDA calls are sold in Emil IRA, they shouldn't cap NVDA
|
|
shares held in Joint trust.
|
|
|
|
Fixing this means restructuring `portfolioSummary`, since `Allocation` is
|
|
currently account-agnostic. Approach: compute per-account reductions using
|
|
`positionsForAccount` + account-filtered option lots, then sum into
|
|
portfolio-wide reductions. Each account's reduction capped by that account's
|
|
shares, not the global total.
|
|
|
|
Low priority — naked calls are rare, and calls are typically in the same
|
|
account as the underlying.
|
|
|
|
## Covered call adjustment optimization
|
|
|
|
`adjustForCoveredCalls` has a nested loop — for each allocation, it iterates
|
|
all lots to find matching option contracts. O(N*M) is fine for personal
|
|
portfolios (<1000 lots). Pre-indexing options by underlying would help if
|
|
someone had a very large options-heavy portfolio.
|
|
|
|
## Mixed price_ratio grouping
|
|
|
|
`Position` grouping in `portfolio.zig` keys on `priceSymbol` alone. Lots with
|
|
different `price_ratio` values sharing the same `priceSymbol` get incorrectly
|
|
merged (e.g. investor vs institutional shares of the same fund). Should key
|
|
on `(priceSymbol, price_ratio)` tuple. Edge case — most people don't hold
|
|
both share classes simultaneously.
|
|
|
|
## HTTP connection pooling
|
|
|
|
Parallel server sync in `loadAllPrices` spawns up to 8 threads, each with its
|
|
own HTTP connection. Could reuse connections to reduce TCP handshake overhead.
|
|
Only matters with very large portfolios (100+ symbols) hitting ZFIN_SERVER.
|
|
8 concurrent connections is fine for now.
|
|
|
|
## Streaming cache deserialization
|
|
|
|
Cache store reads entire files into memory (`readFileAlloc` with 50MB limit).
|
|
For portfolios with 10+ years of daily candles, this could use significant
|
|
memory. Keep current approach unless memory becomes a real problem.
|