From 658ed53544243a5efe08a6440a1297d521357f2c Mon Sep 17 00:00:00 2001 From: Tyler Date: Sat, 16 May 2026 01:06:05 -0700 Subject: Add TickerHeader + KPI strip above tab strip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Injects a persistent header band between the market bar and st.tabs(): sector tag, italic symbol, company name, 52W range rail with price pin, price, and Δ/% change chip; below that a 6-cell KPI strip (Market Cap, P/E, EPS, Div Yield, Beta, Short % Float). All data from cached service calls — free hits. Graceful "—" fallbacks for missing fields. Co-Authored-By: Claude Sonnet 4.6 --- app.py | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/app.py b/app.py index 51f30dd..b5f2735 100644 --- a/app.py +++ b/app.py @@ -403,6 +403,71 @@ hr { ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--ink-3); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: var(--ink-4); } + +/* ── Ticker Header Band ──────────────────────────────────────────────────── */ +.psm-header-band { + margin-bottom: 1rem; + border-bottom: 1px solid var(--line-2); + padding-bottom: 0.75rem; +} +.psm-ticker-head { + display: flex; align-items: center; gap: 1.5rem; + flex-wrap: wrap; margin-bottom: 0.5rem; +} +.psm-ticker-head .sector { + font-family: var(--font-sans); font-size: 11px; + color: var(--brass); text-transform: uppercase; + letter-spacing: 0.1em; +} +.psm-ticker-head .sym { + font-family: var(--font-display); font-style: italic; + font-size: 2rem; color: var(--fg-1); line-height: 0.95; + letter-spacing: -0.025em; +} +.psm-ticker-head .name { + font-family: var(--font-sans); font-size: 13px; + color: var(--fg-3); +} +.psm-range { + display: flex; align-items: center; gap: 0.5rem; + font-family: var(--font-mono); font-size: 11px; color: var(--fg-3); + flex: 1; min-width: 180px; max-width: 280px; +} +.psm-range .rail { + flex: 1; height: 3px; background: var(--line-2); + border-radius: 2px; position: relative; +} +.psm-range .pin { + position: absolute; top: -3px; width: 2px; height: 9px; + background: var(--brass); border-radius: 1px; +} +.px-block { margin-left: auto; text-align: right; } +.px-block .px { + font-family: var(--font-mono); font-size: 1.5rem; + color: var(--fg-1); display: block; +} +.px-block .chg { font-family: var(--font-mono); font-size: 13px; } +.px-block .chg.pos { color: var(--positive); } +.px-block .chg.neg { color: var(--negative); } +.psm-kpis { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 0; +} +.psm-kpi { + padding: 0.35rem 0.75rem 0.35rem 0; + border-right: 1px solid var(--line-1); +} +.psm-kpi:last-child { border-right: none; } +.psm-kpi .k { + font-family: var(--font-sans); font-size: 10px; + color: var(--fg-3); text-transform: uppercase; + letter-spacing: 0.08em; display: block; +} +.psm-kpi .v { + font-family: var(--font-mono); font-size: 13px; + color: var(--fg-1); display: block; margin-top: 2px; +} """, unsafe_allow_html=True) @@ -680,6 +745,95 @@ if not ticker: """, unsafe_allow_html=True) st.stop() +# ── Ticker Header + KPI Strip ───────────────────────────────────────────────── + +_hdr_info = get_company_info(ticker) or {} +_hdr_price = get_latest_price(ticker) +_hdr_prev = _hdr_info.get("regularMarketPreviousClose") or _hdr_info.get("previousClose") + +_lo52 = _hdr_info.get("fiftyTwoWeekLow") +_hi52 = _hdr_info.get("fiftyTwoWeekHigh") +_range_pct = 50 +if _lo52 and _hi52 and _hi52 > _lo52 and _hdr_price: + _range_pct = max(0, min(100, (_hdr_price - _lo52) / (_hi52 - _lo52) * 100)) + +_chg_abs = (_hdr_price - _hdr_prev) if (_hdr_price and _hdr_prev) else None +_chg_pct = (_chg_abs / _hdr_prev * 100) if (_chg_abs is not None and _hdr_prev) else None +_chg_cls = "pos" if (_chg_pct or 0) >= 0 else "neg" +_chg_arrow = "▲" if (_chg_pct or 0) >= 0 else "▼" + +def _fmt_large(v): + try: + n = float(v) + except (TypeError, ValueError): + return "—" + if abs(n) >= 1e12: + return f"${n / 1e12:.2f}T" + if abs(n) >= 1e9: + return f"${n / 1e9:.2f}B" + if abs(n) >= 1e6: + return f"${n / 1e6:.2f}M" + return f"${n:,.0f}" + +def _fmt_ratio(v, d=2): + try: + return f"{float(v):.{d}f}×" + except (TypeError, ValueError): + return "—" + +def _fmt_pct(v): + try: + return f"{float(v) * 100:.2f}%" + except (TypeError, ValueError): + return "—" + +_sym_e = escape_html(ticker) +_name_e = escape_html(_hdr_info.get("longName") or _hdr_info.get("shortName") or ticker) +_sector_e = escape_html(_hdr_info.get("sector") or "") +_price_s = f"${_hdr_price:,.2f}" if _hdr_price else "—" +_lo_s = f"${_lo52:,.2f}" if _lo52 else "—" +_hi_s = f"${_hi52:,.2f}" if _hi52 else "—" +_chg_s = ( + f"{_chg_arrow} ${abs(_chg_abs):,.2f} ({abs(_chg_pct):.2f}%)" + if (_chg_abs is not None and _chg_pct is not None) else "—" +) +_mktcap = _fmt_large(_hdr_info.get("marketCap")) +_pe = _fmt_ratio(_hdr_info.get("trailingPE")) +_eps = _fmt_ratio(_hdr_info.get("trailingEps")) +_div = _fmt_pct(_hdr_info.get("dividendYield")) +_beta = _fmt_ratio(_hdr_info.get("beta")) +_short = _fmt_pct(_hdr_info.get("shortPercentOfFloat")) + +st.markdown( + "
" + "
" + "
" + + ("" + _sector_e + "" if _sector_e else "") + + "" + _sym_e + "" + "" + _name_e + "" + "
" + "
" + "" + _lo_s + "" + "
" + "" + _hi_s + "" + "
" + "
" + "" + _price_s + "" + "" + _chg_s + "" + "
" + "
" + "
" + "
Market Cap" + _mktcap + "
" + "
P/E (TTM)" + _pe + "
" + "
EPS (TTM)" + _eps + "
" + "
Div Yield" + _div + "
" + "
Beta" + _beta + "
" + "
Short %" + _short + "
" + "
" + "
", + unsafe_allow_html=True, +) + tab_overview, tab_financials, tab_valuation, tab_options, tab_insiders, tab_filings, tab_news = st.tabs([ "Overview", "Financials", -- cgit v1.3-2-g0d8e