"""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 _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)"
if st.button(button_label, key=f"{state_key}_button", use_container_width=True):
st.session_state[state_key] = not expanded
@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")