diff options
| author | Tyler <tyler@tylerhoang.xyz> | 2026-05-17 00:40:20 -0700 |
|---|---|---|
| committer | Tyler <tyler@tylerhoang.xyz> | 2026-05-17 00:40:20 -0700 |
| commit | 9b5e0572de9721b90eee769251cf815cf714a5ad (patch) | |
| tree | 67f4caf852924b22fa5238280def2ff854a9a179 /components | |
| parent | 5d24e21aa4ec234d51258b0741543b38c233c542 (diff) | |
Fix quotetable heading, stale click state, and parallel fetch
- Rename "Positions" → "Quotes" (heading was misleading; implies holdings)
- Always clear _qt_click on read so stale values from removed tickers
don't persist across rerenders
- Replace serial get_latest_price + get_company_info calls with
parallel ThreadPoolExecutor fetch of get_company_info only;
regularMarketPrice/currentPrice from info is sufficient for the table
and halves the number of yfinance calls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'components')
| -rw-r--r-- | components/quotetable.py | 34 |
1 files changed, 26 insertions, 8 deletions
diff --git a/components/quotetable.py b/components/quotetable.py index e975a67..28a4d74 100644 --- a/components/quotetable.py +++ b/components/quotetable.py @@ -1,10 +1,15 @@ """QuoteTable — watchlist tickers as a full-width styled data table (empty-state landing page).""" import streamlit as st import streamlit.components.v1 as components -from services.data_service import get_latest_price, get_company_info +from concurrent.futures import ThreadPoolExecutor, as_completed +from services.data_service import get_company_info from utils.security import escape_html +def _fetch_info(sym: str) -> tuple[str, dict]: + return sym, get_company_info(sym) or {} + + def render_quotetable(watchlist: list[str]) -> None: """Render watchlist tickers as a styled quote table. @@ -12,21 +17,34 @@ def render_quotetable(watchlist: list[str]) -> None: st.session_state["ticker"] and reruns. Uses the same hidden-input JS pattern as the sidebar watchlist. """ - # Process any pending row click + # Process any pending row click — always clear stale state clicked = st.session_state.get("_qt_click", "") - if clicked and clicked in watchlist: - st.session_state["ticker"] = clicked + if clicked: st.session_state["_qt_click"] = "" - st.rerun() + if clicked in watchlist: + st.session_state["ticker"] = clicked + st.rerun() st.text_input("qt_click_receiver", key="_qt_click", label_visibility="collapsed") + # Fetch all tickers in parallel + infos: dict[str, dict] = {} + with ThreadPoolExecutor(max_workers=min(len(watchlist), 8)) as pool: + futures = {pool.submit(_fetch_info, sym): sym for sym in watchlist} + for future in as_completed(futures): + sym, info = future.result() + infos[sym] = info + # Build row data rows_html = "" for sym in watchlist: try: - price = get_latest_price(sym) - info = get_company_info(sym) or {} + info = infos.get(sym, {}) + price = ( + info.get("regularMarketPrice") + or info.get("currentPrice") + or info.get("previousClose") + ) prev = info.get("regularMarketPreviousClose") or info.get("previousClose") sector = info.get("sector") or info.get("quoteType") or "" vol = info.get("volume") @@ -136,7 +154,7 @@ def render_quotetable(watchlist: list[str]) -> None: "</style>" "<div class='psm-card'>" "<div class='psm-card-head'>" - "<div><span class='eyebrow'>Watchlist</span><h3>Positions</h3></div>" + "<div><span class='eyebrow'>Watchlist</span><h3>Quotes</h3></div>" "</div>" "<table class='psm-table'>" "<thead><tr>" |
