aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler <tyler@tylerhoang.xyz>2026-05-16 01:06:05 -0700
committerTyler <tyler@tylerhoang.xyz>2026-05-16 01:06:05 -0700
commit658ed53544243a5efe08a6440a1297d521357f2c (patch)
tree1287273010f4b08906d73fd89ffb0a4f0370ca61
parent775762d75bf2b6b49893f84db1f4910ef1aa1e4b (diff)
Add TickerHeader + KPI strip above tab strip
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 <noreply@anthropic.com>
-rw-r--r--app.py154
1 files changed, 154 insertions, 0 deletions
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;
+}
</style>
""", 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(
+ "<div class='psm-header-band'>"
+ "<div class='psm-ticker-head'>"
+ "<div style='display:flex;align-items:baseline;gap:0.75rem;flex-wrap:wrap;'>"
+ + ("<span class='sector'>" + _sector_e + "</span>" if _sector_e else "")
+ + "<span class='sym'>" + _sym_e + "</span>"
+ "<span class='name'>" + _name_e + "</span>"
+ "</div>"
+ "<div class='psm-range'>"
+ "<span class='edge'>" + _lo_s + "</span>"
+ "<div class='rail'><div class='pin' style='left:" + str(round(_range_pct, 1)) + "%'></div></div>"
+ "<span class='edge'>" + _hi_s + "</span>"
+ "</div>"
+ "<div class='px-block'>"
+ "<span class='px'>" + _price_s + "</span>"
+ "<span class='chg " + _chg_cls + "'>" + _chg_s + "</span>"
+ "</div>"
+ "</div>"
+ "<div class='psm-kpis'>"
+ "<div class='psm-kpi'><span class='k'>Market Cap</span><span class='v'>" + _mktcap + "</span></div>"
+ "<div class='psm-kpi'><span class='k'>P/E (TTM)</span><span class='v'>" + _pe + "</span></div>"
+ "<div class='psm-kpi'><span class='k'>EPS (TTM)</span><span class='v'>" + _eps + "</span></div>"
+ "<div class='psm-kpi'><span class='k'>Div Yield</span><span class='v'>" + _div + "</span></div>"
+ "<div class='psm-kpi'><span class='k'>Beta</span><span class='v'>" + _beta + "</span></div>"
+ "<div class='psm-kpi'><span class='k'>Short %</span><span class='v'>" + _short + "</span></div>"
+ "</div>"
+ "</div>",
+ unsafe_allow_html=True,
+)
+
tab_overview, tab_financials, tab_valuation, tab_options, tab_insiders, tab_filings, tab_news = st.tabs([
"Overview",
"Financials",