"""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 utils.security import escape_html 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 clicked = st.session_state.get("_qt_click", "") if clicked and clicked in watchlist: st.session_state["ticker"] = clicked st.session_state["_qt_click"] = "" st.rerun() st.text_input("qt_click_receiver", key="_qt_click", label_visibility="collapsed") # Build row data rows_html = "" for sym in watchlist: try: price = get_latest_price(sym) info = get_company_info(sym) or {} 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 += ( "
| Symbol | Sector | " "Last | Δ | " "% Day | Volume | " "
|---|