aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler <tyler@tylerhoang.xyz>2026-05-17 00:40:20 -0700
committerTyler <tyler@tylerhoang.xyz>2026-05-17 00:40:20 -0700
commit9b5e0572de9721b90eee769251cf815cf714a5ad (patch)
tree67f4caf852924b22fa5238280def2ff854a9a179
parent5d24e21aa4ec234d51258b0741543b38c233c542 (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>
-rw-r--r--components/quotetable.py34
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>"