"""Top Movers component — day gainers, losers, most active.""" from html import escape import streamlit as st import yfinance as yf DEFAULT_VISIBLE_MOVERS = 3 MAX_MOVERS = 8 def _toggle_mover_tab(state_key: str): st.session_state[state_key] = not st.session_state.get(state_key, False) def _inject_styles(): st.markdown( """ """, unsafe_allow_html=True, ) @st.cache_data(ttl=180) def _fetch_movers(screen: str, count: int = MAX_MOVERS) -> list[dict]: try: result = yf.screen(screen, count=count) return result.get("quotes", []) except Exception: return [] def _fmt_price(val) -> str: try: return f"${float(val):,.2f}" except Exception: return "—" def _mover_row_html(q: dict) -> str: symbol = escape(str(q.get("symbol", ""))) name = escape(str(q.get("shortName") or q.get("longName") or symbol)) price = q.get("regularMarketPrice") chg_pct = q.get("regularMarketChangePercent") chg_abs = q.get("regularMarketChange") try: chg_f = float(chg_pct) color = "#2ecc71" if chg_f >= 0 else "#e74c3c" sign = "+" if chg_f >= 0 else "" pct_str = f"{sign}{chg_f:.2f}%" except Exception: color = "#9aa0b0" pct_str = "—" try: abs_str = f"({'+' if float(chg_abs) >= 0 else ''}{float(chg_abs):.2f})" except Exception: abs_str = "" abs_str = escape(abs_str) return ( "
" f"
{symbol}
" f"
{name}
" f"
{_fmt_price(price)}
" "
" f"{pct_str}" f"{abs_str}" "
" "
" ) def _render_mover_tab(screen: str, state_key: str): quotes = _fetch_movers(screen) if not quotes: st.caption("No data available.") return expanded = st.session_state.get(state_key, False) visible_count = len(quotes) if expanded else min(DEFAULT_VISIBLE_MOVERS, len(quotes)) rows_html = "".join(_mover_row_html(q) for q in quotes[:visible_count]) st.markdown(f"
{rows_html}
", unsafe_allow_html=True) if len(quotes) > DEFAULT_VISIBLE_MOVERS: button_label = "Show Less" if expanded else f"Show More ({len(quotes) - DEFAULT_VISIBLE_MOVERS} more)" st.button( button_label, key=f"{state_key}_button", use_container_width=True, on_click=_toggle_mover_tab, args=(state_key,), ) @st.fragment def render_top_movers(): _inject_styles() st.markdown("#### 🔥 Top Movers") tab_gainers, tab_losers, tab_active = st.tabs([ "📈 Gainers", "📉 Losers", "⚡ Most Active" ]) screens = { "gainers": "day_gainers", "losers": "day_losers", "active": "most_actives", } with tab_gainers: _render_mover_tab(screens["gainers"], "top_movers_gainers_expanded") with tab_losers: _render_mover_tab(screens["losers"], "top_movers_losers_expanded") with tab_active: _render_mover_tab(screens["active"], "top_movers_active_expanded")