From cb2d8f5ebf417e1f01c1ed9345801d4b2216d9f2 Mon Sep 17 00:00:00 2001 From: Openclaw Date: Tue, 31 Mar 2026 20:34:39 -0700 Subject: Add top movers section (gainers, losers, most active) --- components/top_movers.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 components/top_movers.py (limited to 'components') diff --git a/components/top_movers.py b/components/top_movers.py new file mode 100644 index 0000000..ac76504 --- /dev/null +++ b/components/top_movers.py @@ -0,0 +1,100 @@ +"""Top Movers component — day gainers, losers, most active.""" +import streamlit as st +import yfinance as yf + + +@st.cache_data(ttl=180) +def _fetch_movers(screen: str, count: int = 8) -> list[dict]: + try: + result = yf.screen(screen, count=count) + return result.get("quotes", []) + except Exception: + return [] + + +def _fmt_pct(val) -> str: + try: + return f"{float(val):+.2f}%" + except Exception: + return "—" + + +def _fmt_price(val) -> str: + try: + return f"${float(val):,.2f}" + except Exception: + return "—" + + +def _mover_row(q: dict): + symbol = q.get("symbol", "") + name = 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 = "" + + col_sym, col_name, col_price, col_chg = st.columns([1, 3, 1.5, 1.5]) + with col_sym: + st.markdown(f"**{symbol}**") + with col_name: + st.caption(name[:40]) + with col_price: + st.markdown(_fmt_price(price)) + with col_chg: + st.markdown( + f"{pct_str}" + f" {abs_str}", + unsafe_allow_html=True, + ) + + +def render_top_movers(): + 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: + quotes = _fetch_movers(screens["gainers"]) + if quotes: + for q in quotes: + _mover_row(q) + else: + st.caption("No data available.") + + with tab_losers: + quotes = _fetch_movers(screens["losers"]) + if quotes: + for q in quotes: + _mover_row(q) + else: + st.caption("No data available.") + + with tab_active: + quotes = _fetch_movers(screens["active"]) + if quotes: + for q in quotes: + _mover_row(q) + else: + st.caption("No data available.") -- cgit v1.3-2-g0d8e