"""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 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. Shows Symbol · Sector · Last · Δ · % Day · Volume. Clicking a row sets st.session_state["ticker"] and reruns. Uses the same hidden-input JS pattern as the sidebar watchlist. """ # Process any pending row click — always clear stale state clicked = st.session_state.get("_qt_click", "") if clicked: st.session_state["_qt_click"] = "" 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: 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") sym_e = escape_html(sym) sector_e = escape_html(sector) if price is not None and prev and prev > 0: chg = price - prev pct = chg / prev * 100 sign = "+" if chg >= 0 else "−" arrow = "▲" if chg >= 0 else "▼" chg_cls = "pos" if chg >= 0 else "neg" px_str = "$" + f"{price:,.2f}" chg_str = arrow + " " + f"{abs(chg):.2f}" pct_str = sign + f"{abs(pct):.2f}%" elif price is not None: chg_cls = "flat" px_str = "$" + f"{price:,.2f}" chg_str = "—" pct_str = "—" else: chg_cls = "flat" px_str = "—" chg_str = "—" pct_str = "—" if vol is not None: if vol >= 1_000_000: vol_str = f"{vol/1_000_000:.1f}M" elif vol >= 1_000: vol_str = f"{vol/1_000:.0f}K" else: vol_str = str(vol) else: vol_str = "—" rows_html += ( "" "" + sym_e + "" "" + sector_e + "" "" + px_str + "" "" + chg_str + "" "" + pct_str + "" "" + vol_str + "" "" ) except Exception: sym_e = escape_html(sym) rows_html += ( "" "" + sym_e + "" "—" "—" "—" "—" "—" "" ) n = len(watchlist) height = 40 + 36 + n * 47 + 24 # card-head + thead + rows + buffer html = ( "" "
" "
" "
Watchlist

Quotes

" "
" "" "" "" "" "" "" "" + rows_html + "" "
SymbolSectorLastΔ% DayVolume
" "
" "" ) components.html(html, height=height, scrolling=False)