6.9 KiB
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.zigsrc/tui/chart.zigsrc/tui/keybinds.zigsrc/tui/theme.zig
Commands:
src/commands/common.zigsrc/commands/analysis.zigsrc/commands/cache.zigsrc/commands/divs.zigsrc/commands/earnings.zigsrc/commands/enrich.zigsrc/commands/etf.zigsrc/commands/history.zigsrc/commands/lookup.zigsrc/commands/options.zigsrc/commands/perf.zigsrc/commands/portfolio.zigsrc/commands/quote.zigsrc/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:
-
geouses Alpha Vantage'sCountryfield, which is the fund issuer's domicile (USA for all US-listed ETFs), not the fund's investment geography. Every US-domiciled international fund getsgeo::US. -
asset_classshort-circuits to"ETF"whenasset_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.