"""Company overview tab rendered as a client-side HTML surface.""" from datetime import datetime from html import escape as _esc import pandas as pd import streamlit as st import streamlit.components.v1 as components from services.data_service import get_company_info, get_latest_price, get_price_history from utils.security import json_for_script PERIODS = {"1mo": "1M", "3mo": "3M", "6mo": "6M", "1y": "1Y", "5y": "5Y"} SECTOR_ETF_MAP = { "Technology": "XLK", "Communication Services": "XLC", "Consumer Cyclical": "XLY", "Consumer Defensive": "XLP", "Financial Services": "XLF", "Healthcare": "XLV", "Industrials": "XLI", "Energy": "XLE", "Utilities": "XLU", "Real Estate": "XLRE", "Basic Materials": "XLB", } INDUSTRY_PEER_MAP = { "consumer electronics": ["SONY", "DELL", "HPQ"], "software - infrastructure": ["MSFT", "ORCL", "CRM"], "semiconductors": ["NVDA", "AMD", "AVGO"], "internet content & information": ["GOOGL", "META", "RDDT"], "banks - diversified": ["JPM", "BAC", "WFC"], "credit services": ["V", "MA", "AXP"], "insurance - diversified": ["BRK-B", "AIG", "ALL"], "reit - industrial": ["PLD", "PSA", "EXR"], } SECTOR_PEER_MAP = { "Technology": ["AAPL", "MSFT", "NVDA"], "Communication Services": ["GOOGL", "META", "NFLX"], "Consumer Cyclical": ["AMZN", "TSLA", "HD"], "Consumer Defensive": ["WMT", "COST", "PG"], "Financial Services": ["JPM", "BAC", "GS"], "Healthcare": ["LLY", "UNH", "JNJ"], "Industrials": ["GE", "CAT", "RTX"], "Energy": ["XOM", "CVX", "COP"], "Utilities": ["NEE", "DUK", "SO"], "Real Estate": ["PLD", "AMT", "EQIX"], } _XMAP = {"NYQ": "NYSE", "NMS": "NASDAQ", "NGM": "NASDAQ", "NCM": "NASDAQ", "ASE": "AMEX"} def _to_float(val): try: n = float(val) except (TypeError, ValueError): return None if pd.isna(n): return None return n def _to_int(val): try: n = float(val) except (TypeError, ValueError): return None if pd.isna(n): return None return int(round(n)) def _series_points(symbol: str, period: str) -> list[dict]: hist = get_price_history(symbol, period=period) if hist is None or hist.empty or "Close" not in hist.columns: return [] closes = pd.to_numeric(hist["Close"], errors="coerce") out = [] for idx, v in closes.dropna().items(): ts = idx.strftime("%Y-%m-%d") out.append({"d": ts, "c": float(v)}) return out def _build_signals(info: dict) -> list[dict]: signals = [] pe = _to_float(info.get("trailingPE")) if pe is not None and pe > 0: if pe < 15: signals.append({"k": "Valuation", "state": "pos", "v": "P/E " + "{:.1f}".format(pe) + "x", "d": "Attractive multiple"}) elif pe < 30: signals.append({"k": "Valuation", "state": "warn", "v": "P/E " + "{:.1f}".format(pe) + "x", "d": "Middle of range"}) else: signals.append({"k": "Valuation", "state": "neg", "v": "P/E " + "{:.1f}".format(pe) + "x", "d": "Premium multiple"}) else: signals.append({"k": "Valuation", "state": "neu", "v": "P/E unavailable", "d": "No trailing earnings"}) rev_growth = _to_float(info.get("revenueGrowth")) if rev_growth is not None: if rev_growth > 0.10: state = "pos" desc = "Strong top-line growth" elif rev_growth >= 0: state = "warn" desc = "Low but positive growth" else: state = "neg" desc = "Contracting revenue" signals.append({"k": "Growth", "state": state, "v": "{:+.0f}%".format(rev_growth * 100.0), "d": desc}) margin = _to_float(info.get("profitMargins")) if margin is not None: if margin > 0.15: state = "pos" desc = "High net margin" elif margin > 0.05: state = "warn" desc = "Moderate net margin" else: state = "neg" desc = "Thin or negative margin" signals.append({"k": "Profit", "state": state, "v": "{:.0f}%".format(margin * 100.0), "d": desc}) de = _to_float(info.get("debtToEquity")) if de is not None: de_x = de / 100.0 if de_x < 0.5: state = "pos" desc = "Low leverage" elif de_x < 2.0: state = "warn" desc = "Moderate leverage" else: state = "neg" desc = "High leverage" signals.append({"k": "Leverage", "state": state, "v": "D/E " + "{:.2f}".format(de_x) + "x", "d": desc}) price = _to_float(info.get("currentPrice") or info.get("regularMarketPrice")) high52 = _to_float(info.get("fiftyTwoWeekHigh")) if price is not None and high52 is not None and high52 > 0: from_high = (price - high52) / high52 * 100.0 if from_high > -10: state = "pos" desc = "Near 52-week highs" elif from_high > -25: state = "warn" desc = "Mid-range momentum" else: state = "neg" desc = "Far below highs" signals.append({"k": "Momentum", "state": state, "v": "{:+.0f}%".format(from_high), "d": desc}) short_pct = _to_float(info.get("shortPercentOfFloat")) if short_pct is not None: if short_pct < 0.05: state = "pos" desc = "Low crowding" elif short_pct < 0.15: state = "warn" desc = "Moderate crowding" else: state = "neg" desc = "Elevated crowding" signals.append({"k": "Short Int.", "state": state, "v": "{:.1f}%".format(short_pct * 100.0), "d": desc}) return signals def _comparison_list(ticker: str, info: dict) -> list[dict]: out = [{"label": "S&P 500", "symbol": "^GSPC"}] sector = str(info.get("sector") or "").strip() industry = str(info.get("industry") or "").strip().lower() etf = SECTOR_ETF_MAP.get(sector) if etf: out.append({"label": sector + " ETF", "symbol": etf}) peers = INDUSTRY_PEER_MAP.get(industry) or SECTOR_PEER_MAP.get(sector) or [] for p in peers: pp = str(p).upper() if pp != ticker.upper(): out.append({"label": pp, "symbol": pp}) deduped = [] seen = set() for row in out: sym = row["symbol"] if sym not in seen: deduped.append(row) seen.add(sym) return deduped[:6] def render_overview(ticker: str): info = get_company_info(ticker) or {} if not info: st.error("Could not load data for this ticker.") return tkr = str(ticker or "").upper() name = str(info.get("longName") or info.get("shortName") or tkr) sector = str(info.get("sector") or "") or None industry = str(info.get("industry") or "") or None exchange_raw = str(info.get("exchange") or "") exchange = _XMAP.get(exchange_raw, exchange_raw) or None price = _to_float(info.get("currentPrice") or info.get("regularMarketPrice") or get_latest_price(ticker)) prev_close = _to_float(info.get("regularMarketPreviousClose") or info.get("previousClose")) comparisons = _comparison_list(tkr, info) symbols = [tkr] for c in comparisons: sym = c.get("symbol") if sym and sym not in symbols: symbols.append(sym) series = {} for period in PERIODS.keys(): bucket = {} for sym in symbols: pts = _series_points(sym, period) if pts: bucket[sym] = pts series[period] = bucket short_pct = _to_float(info.get("shortPercentOfFloat")) short_ratio = _to_float(info.get("shortRatio")) shares_short = _to_int(info.get("sharesShort")) shares_short_prior = _to_int(info.get("sharesShortPriorMonth")) short_delta_pct = None if shares_short is not None and shares_short_prior is not None and shares_short_prior > 0: short_delta_pct = (float(shares_short) - float(shares_short_prior)) / float(shares_short_prior) payload = { "ticker": tkr, "name": name, "sector": sector, "industry": industry, "exchange": exchange, "price": price, "prev_close": prev_close, "signals": _build_signals(info), "stats": { "marketCap": _to_float(info.get("marketCap")), "trailingPE": _to_float(info.get("trailingPE")), "trailingEps": _to_float(info.get("trailingEps")), "volume": _to_float(info.get("volume")), "averageVolume": _to_float(info.get("averageVolume")), "beta": _to_float(info.get("beta")), }, "range_52w": { "low": _to_float(info.get("fiftyTwoWeekLow")), "high": _to_float(info.get("fiftyTwoWeekHigh")), "price": price, }, "short_interest": { "shortPercentOfFloat": short_pct, "shortRatio": short_ratio, "sharesShort": shares_short, "sharesShortPriorMonth": shares_short_prior, "sharesShortDeltaPct": short_delta_pct, }, "comparisons": comparisons, "series": series, } meta = { "updated_label": datetime.now().strftime("%Y-%m-%d %H:%M"), "default_period": "1y", "default_mode": "price", "default_comparisons": ["^GSPC"], } payload_js = "const OVERVIEW_DATA=" + json_for_script(payload) + ";" meta_js = "const OVERVIEW_META=" + json_for_script(meta) + ";" if price is not None and prev_close is not None and prev_close > 0: chg_pct = (price - prev_close) / prev_close * 100.0 chg_sign = "+" if chg_pct >= 0 else "" chg_arrow = "▲" if chg_pct >= 0 else "▼" chg_str = chg_arrow + " " + chg_sign + "{:.2f}%".format(chg_pct) chg_cls = "chg-pos" if chg_pct >= 0 else "chg-neg" else: chg_str = "—" chg_cls = "" price_str = "${:,.2f}".format(price) if price is not None else "—" co_name = _esc(name) plotly_cdn = "" _ROOT = ( "" ) fonts_link = ( "" "" ) _OV_CSS = """""" ctx_html = ( '
' + '' + _esc(tkr) + "" + '' + co_name + "" + 'Overview' + '
' + "" + _esc(exchange or "—") + "" + '' + price_str + "" + '' + chg_str + "" + "
" ) lede_html = ( '
' + '
' + 'Snapshot' + '
Business quality, valuation, and positioning
' + '

A fast company snapshot with valuation and risk signals, core fundamentals, 52-week positioning, short-interest context, and price versus relative performance across multiple windows.

' + "
" + '
' + '
Sourceyfinance
' + '
Windowmarket + fundamentals
' + '
Updated' + _esc(meta["updated_label"]) + "
" + "
" ) controls_html = ( '
' + '
' + 'Period' + '' + '' + '' + '' + '' + "
" + '
' + 'Mode' + '' + '' + "
" + '' + "
" ) foot_html = ( '
' + "Overview data provided by Yahoo Finance via yfinance · Relative performance is rebased to 0% at period start · Some fields may be unavailable for certain tickers" + "
" ) js = ( "" ) height = 1950 # extra ~470px buffer for responsive single-column collapse at <1100px doc = ( "" + "" + fonts_link + plotly_cdn + _ROOT + _OV_CSS + "" + '
' + ctx_html + '
' + lede_html + '
' + '
' + '
' + '
52-Week Positioning
' + '
Short Interest
' + "
" + controls_html + '
' + '
' + foot_html + "
" + js + "" ) components.html(doc, height=height, scrolling=False)