"""Watchlist sidebar component — session-scoped personal watchlist.""" import streamlit as st from services.data_service import get_latest_price, get_company_info from utils.security import escape_html _WATCHLIST_CSS_INJECTED = False def _inject_css(): global _WATCHLIST_CSS_INJECTED if _WATCHLIST_CSS_INJECTED: return _WATCHLIST_CSS_INJECTED = True st.markdown(""" """, unsafe_allow_html=True) def render_watchlist(): """Render the personal watchlist section in the sidebar. Displays a section label, then one clickable row per saved ticker showing symbol · price · Δ%. Clicking a row sets st.session_state["ticker"] and triggers a rerun. Shows an empty-state message when the list is empty. Must be called from within a `with st.sidebar:` block. """ _inject_css() watchlist: list[str] = st.session_state.get("watchlist", []) st.markdown('
Watchlist
', unsafe_allow_html=True) if not watchlist: st.markdown( '
No saved tickers
', unsafe_allow_html=True, ) return for sym in watchlist: try: price = get_latest_price(sym) info = get_company_info(sym) or {} prev = info.get("regularMarketPreviousClose") or info.get("previousClose") if price is not None and prev and prev > 0: chg_pct = (price - prev) / prev * 100 sign = "+" if chg_pct >= 0 else "" chg_color = "#4F8C5E" if chg_pct >= 0 else "#B5494B" row_label = ( escape_html(sym) + " $" + f"{price:,.2f}" + " " + sign + f"{chg_pct:.2f}%" ) elif price is not None: chg_color = "#8E8676" row_label = escape_html(sym) + " $" + f"{price:,.2f}" else: chg_color = "#8E8676" row_label = escape_html(sym) + " —" # Render a styled container div then the Streamlit button inside it st.markdown('
', unsafe_allow_html=True) if st.button(row_label, key="watch_row_" + sym, use_container_width=True): st.session_state["ticker"] = sym st.rerun() st.markdown("
", unsafe_allow_html=True) except Exception: # Never let one broken ticker blow up the whole watchlist sym_safe = escape_html(sym) st.markdown( '
', unsafe_allow_html=True, ) if st.button(sym_safe + " (error)", key="watch_row_" + sym, use_container_width=True): st.session_state["ticker"] = sym st.rerun() st.markdown("
", unsafe_allow_html=True)