"""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)