diff options
| author | Openclaw <openclaw@mail.tylerhoang.xyz> | 2026-03-31 20:34:39 -0700 |
|---|---|---|
| committer | Openclaw <openclaw@mail.tylerhoang.xyz> | 2026-03-31 20:34:39 -0700 |
| commit | cb2d8f5ebf417e1f01c1ed9345801d4b2216d9f2 (patch) | |
| tree | ed3fcc1e2284ff90fb0c86a3958d471a2cb63f07 /components/top_movers.py | |
| parent | 26e83497a1f07cb7cb4dfe4193a0edf6a1b2bd8b (diff) | |
Add top movers section (gainers, losers, most active)
Diffstat (limited to 'components/top_movers.py')
| -rw-r--r-- | components/top_movers.py | 100 |
1 files changed, 100 insertions, 0 deletions
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"<span style='color:{color};font-weight:600'>{pct_str}</span>" + f"<span style='font-size:0.75rem;color:#9aa0b0'> {abs_str}</span>", + 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.") |
