"""Company overview — score card, key stats, 52W range, short interest, price chart."""
import streamlit as st
import plotly.graph_objects as go
from services.data_service import get_company_info, get_price_history
from utils.formatters import fmt_large, fmt_currency, fmt_pct, fmt_ratio
PERIODS = {"1 Month": "1mo", "3 Months": "3mo", "6 Months": "6mo", "1 Year": "1y", "5 Years": "5y"}
# ── Score card ───────────────────────────────────────────────────────────────
def _score_card(info: dict) -> None:
"""Render a row of green/yellow/red signal badges."""
signals: list[tuple[str, str, str, str]] = [] # (label, color, value, description)
# Valuation — trailing P/E
pe = info.get("trailingPE")
if pe and pe > 0:
if pe < 15:
signals.append(("Valuation", "green", f"P/E {pe:.1f}x", "Attractively valued"))
elif pe < 30:
signals.append(("Valuation", "yellow", f"P/E {pe:.1f}x", "Fairly valued"))
else:
signals.append(("Valuation", "red", f"P/E {pe:.1f}x", "Richly valued"))
else:
signals.append(("Valuation", "neutral", "P/E N/A", "No trailing earnings"))
# Revenue growth (TTM YoY)
rev_growth = info.get("revenueGrowth")
if rev_growth is not None:
if rev_growth > 0.10:
signals.append(("Growth", "green", f"{rev_growth*100:+.0f}% rev", "Strong growth"))
elif rev_growth >= 0:
signals.append(("Growth", "yellow", f"{rev_growth*100:+.0f}% rev", "Slow growth"))
else:
signals.append(("Growth", "red", f"{rev_growth*100:+.0f}% rev", "Declining revenue"))
# Profitability — net margin
margin = info.get("profitMargins")
if margin is not None:
if margin > 0.15:
signals.append(("Profit", "green", f"{margin*100:.0f}% margin", "High margins"))
elif margin > 0.05:
signals.append(("Profit", "yellow", f"{margin*100:.0f}% margin", "Moderate margins"))
else:
signals.append(("Profit", "red", f"{margin*100:.0f}% margin", "Thin/negative margins"))
# Leverage — D/E (yfinance returns as %, e.g. 162 = 1.62x)
de = info.get("debtToEquity")
if de is not None:
de_x = de / 100
if de_x < 0.5:
signals.append(("Leverage", "green", f"D/E {de_x:.2f}x", "Low leverage"))
elif de_x < 2.0:
signals.append(("Leverage", "yellow", f"D/E {de_x:.2f}x", "Moderate leverage"))
else:
signals.append(("Leverage", "red", f"D/E {de_x:.2f}x", "High leverage"))
# Momentum — price vs 52W high
price = info.get("currentPrice") or info.get("regularMarketPrice")
high52 = info.get("fiftyTwoWeekHigh")
if price and high52 and high52 > 0:
from_high_pct = (price - high52) / high52 * 100
if from_high_pct > -10:
signals.append(("Momentum", "green", f"{from_high_pct:.0f}% from 52W↑", "Near highs"))
elif from_high_pct > -25:
signals.append(("Momentum", "yellow", f"{from_high_pct:.0f}% from 52W↑", "Mid-range"))
else:
signals.append(("Momentum", "red", f"{from_high_pct:.0f}% from 52W↑", "Far from highs"))
# Short interest
short_pct = info.get("shortPercentOfFloat")
if short_pct is not None:
if short_pct < 0.05:
signals.append(("Short Int.", "green", f"{short_pct*100:.1f}% float", "Low short interest"))
elif short_pct < 0.15:
signals.append(("Short Int.", "yellow", f"{short_pct*100:.1f}% float", "Moderate short interest"))
else:
signals.append(("Short Int.", "red", f"{short_pct*100:.1f}% float", "High short interest"))
if not signals:
return
color_map = {
"green": ("rgba(46,204,113,0.15)", "#7ce3a1"),
"yellow": ("rgba(243,156,18,0.15)", "#f0c040"),
"red": ("rgba(231,76,60,0.15)", "#ff8a8a"),
"neutral": ("rgba(255,255,255,0.05)", "#9aa0b0"),
}
cards_html = ""
for label, color, value, desc in signals:
bg, fg = color_map[color]
cards_html += (
f'
'
f'
{label}
'
f'
{value}
'
f'
{desc}
'
f'
'
)
st.markdown(
f'{cards_html}
',
unsafe_allow_html=True,
)
# ── 52-week range bar ────────────────────────────────────────────────────────
def _render_52w_bar(info: dict) -> None:
low = info.get("fiftyTwoWeekLow")
high = info.get("fiftyTwoWeekHigh")
price = info.get("currentPrice") or info.get("regularMarketPrice")
if not (low and high and price and high > low):
return
pct = max(0.0, min(100.0, (price - low) / (high - low) * 100))
from_low_pct = (price - low) / low * 100
to_high_pct = (high - price) / price * 100
st.markdown(
f"""
52W Low: {fmt_currency(low)}
{fmt_currency(price)} · {pct:.0f}% of range
52W High: {fmt_currency(high)}
+{from_low_pct:.1f}% above low
{to_high_pct:.1f}% below high
""",
unsafe_allow_html=True,
)
# ── Short interest strip ─────────────────────────────────────────────────────
def _render_short_interest(info: dict) -> None:
short_pct = info.get("shortPercentOfFloat")
short_ratio = info.get("shortRatio")
shares_short = info.get("sharesShort")
shares_short_prior = info.get("sharesShortPriorMonth")
if not any([short_pct, short_ratio, shares_short]):
return
st.markdown("**Short Interest**")
cols = st.columns(4)
cols[0].metric(
"Short % of Float",
f"{short_pct * 100:.2f}%" if short_pct is not None else "—",
)
cols[1].metric(
"Days to Cover",
f"{short_ratio:.1f}" if short_ratio is not None else "—",
help="Shares short ÷ avg daily volume. Higher = harder to unwind.",
)
cols[2].metric(
"Shares Short",
fmt_large(shares_short) if shares_short else "—",
)
if shares_short and shares_short_prior:
chg = (shares_short - shares_short_prior) / shares_short_prior * 100
cols[3].metric(
"vs Prior Month",
fmt_large(shares_short_prior),
delta=f"{chg:+.1f}%",
)
else:
cols[3].metric("Prior Month", fmt_large(shares_short_prior) if shares_short_prior else "—")
# ── Main render ──────────────────────────────────────────────────────────────
def render_overview(ticker: str):
info = get_company_info(ticker)
if not info:
st.error(f"Could not load data for **{ticker}**. Check the ticker symbol.")
return
name = info.get("longName") or info.get("shortName", ticker.upper())
price = info.get("currentPrice") or info.get("regularMarketPrice")
prev_close = info.get("regularMarketPreviousClose") or info.get("previousClose")
price_change = price_change_pct = None
if price and prev_close:
price_change = price - prev_close
price_change_pct = price_change / prev_close
# ── Header ──────────────────────────────────────────────────────────────
col1, col2 = st.columns([3, 1])
with col1:
st.subheader(f"{name} ({ticker.upper()})")
sector = info.get("sector", "")
industry = info.get("industry", "")
if sector:
st.caption(f"{sector} · {industry}")
with col2:
delta_str = None
if price_change is not None and price_change_pct is not None:
delta_str = f"{price_change:+.2f} ({price_change_pct * 100:+.2f}%)"
st.metric(
label="Price",
value=fmt_currency(price) if price else "—",
delta=delta_str,
)
# ── Score card ──────────────────────────────────────────────────────────
_score_card(info)
# ── Key stats strip ─────────────────────────────────────────────────────
stats_cols = st.columns(6)
stats = [
("Mkt Cap", fmt_large(info.get("marketCap"))),
("P/E (TTM)", fmt_ratio(info.get("trailingPE"))),
("EPS (TTM)", fmt_currency(info.get("trailingEps"))),
("Volume", fmt_large(info.get("volume"))),
("Avg Volume", fmt_large(info.get("averageVolume"))),
("Beta", fmt_ratio(info.get("beta"))),
]
for col, (label, val) in zip(stats_cols, stats):
col.metric(label, val)
st.write("")
# ── 52-week range bar ────────────────────────────────────────────────────
_render_52w_bar(info)
# ── Short interest ───────────────────────────────────────────────────────
_render_short_interest(info)
st.divider()
# ── Price chart ─────────────────────────────────────────────────────────
period_label = st.radio(
"Period",
options=list(PERIODS.keys()),
index=3,
horizontal=True,
label_visibility="collapsed",
)
period = PERIODS[period_label]
hist = get_price_history(ticker, period=period)
if hist.empty:
st.warning("No price history available.")
return
fig = go.Figure()
fig.add_trace(go.Scatter(
x=hist.index,
y=hist["Close"],
mode="lines",
name="Close",
line=dict(color="#4F8EF7", width=2),
fill="tozeroy",
fillcolor="rgba(79,142,247,0.08)",
))
fig.update_layout(
margin=dict(l=0, r=0, t=10, b=0),
xaxis=dict(showgrid=False, zeroline=False),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.05)", zeroline=False),
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
hovermode="x unified",
height=320,
)
st.plotly_chart(fig, use_container_width=True)