diff options
Diffstat (limited to 'components')
| -rw-r--r-- | components/market_bar.py | 84 | ||||
| -rw-r--r-- | components/valuation.py | 33 |
2 files changed, 107 insertions, 10 deletions
diff --git a/components/market_bar.py b/components/market_bar.py index cb813e5..411b232 100644 --- a/components/market_bar.py +++ b/components/market_bar.py @@ -1,25 +1,89 @@ """Market bar — displays major indices at the top of the app.""" import streamlit as st from services.data_service import get_market_indices -from utils.formatters import fmt_number + + +def _delta_class(change_pct: float | None) -> str: + if change_pct is None: + return "neutral" + return "positive" if change_pct >= 0 else "negative" + + +def _delta_text(change_pct: float | None) -> str: + if change_pct is None: + return "—" + arrow = "▲" if change_pct >= 0 else "▼" + return f"{arrow} {change_pct * 100:+.2f}%" def render_market_bar(): indices = get_market_indices() + st.markdown( + """ + <style> + .prism-market-card { + background: rgba(22, 28, 39, 0.92); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 14px; + padding: 0.8rem 0.95rem; + min-height: 96px; + } + .prism-market-label { + color: #9aa0b0; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + margin-bottom: 0.45rem; + } + .prism-market-value { + color: #f3f6fb; + font-size: 1.5rem; + font-weight: 700; + line-height: 1.15; + margin-bottom: 0.45rem; + } + .prism-market-delta { + display: inline-block; + border-radius: 999px; + font-size: 0.78rem; + font-weight: 600; + padding: 0.18rem 0.5rem; + } + .prism-market-delta.positive { + color: #7ce3a1; + background: rgba(28, 131, 72, 0.18); + } + .prism-market-delta.negative { + color: #ff8a8a; + background: rgba(180, 47, 47, 0.18); + } + .prism-market-delta.neutral { + color: #c6cfdd; + background: rgba(198, 207, 221, 0.12); + } + </style> + """, + unsafe_allow_html=True, + ) + cols = st.columns(len(indices)) for col, (name, data) in zip(cols, indices.items()): price = data.get("price") change_pct = data.get("change_pct") - if price is None: - col.metric(label=name, value="—") - continue + value = f"{price:,.2f}" if price is not None else "—" + delta_class = _delta_class(change_pct) + delta_text = _delta_text(change_pct) - price_str = f"{price:,.2f}" - delta_str = f"{change_pct * 100:+.2f}%" if change_pct is not None else None - col.metric( - label=name, - value=price_str, - delta=delta_str, + col.markdown( + f""" + <div class="prism-market-card"> + <div class="prism-market-label">{name}</div> + <div class="prism-market-value">{value}</div> + <span class="prism-market-delta {delta_class}">{delta_text}</span> + </div> + """, + unsafe_allow_html=True, ) diff --git a/components/valuation.py b/components/valuation.py index 82e0f0d..d7a84d4 100644 --- a/components/valuation.py +++ b/components/valuation.py @@ -15,6 +15,27 @@ from services.valuation_service import run_dcf, run_ev_ebitda, compute_historica from utils.formatters import fmt_ratio, fmt_pct, fmt_large, fmt_currency +FINANCIAL_SECTORS = {"Financial Services"} +FINANCIAL_INDUSTRY_KEYWORDS = ( + "bank", + "insurance", + "asset management", + "capital markets", + "financial data", + "credit services", + "mortgage", + "reit", +) + + +def _is_financial_company(info: dict) -> bool: + sector = str(info.get("sector") or "").strip() + industry = str(info.get("industry") or "").strip().lower() + if sector in FINANCIAL_SECTORS: + return True + return any(keyword in industry for keyword in FINANCIAL_INDUSTRY_KEYWORDS) + + def render_valuation(ticker: str): tab_ratios, tab_dcf, tab_comps, tab_analyst, tab_earnings = st.tabs([ "Key Ratios", "DCF Model", "Comps", "Analyst Targets", "Earnings History" @@ -97,6 +118,18 @@ def _render_ratios(ticker: str): def _render_dcf(ticker: str): info = get_company_info(ticker) + + if _is_financial_company(info): + st.warning( + "DCF is disabled for financial companies in Prism. Free-cash-flow and capital-structure " + "assumptions are not directly comparable for banks, insurers, and similar businesses." + ) + st.caption( + "Use ratios, comps, earnings history, and analyst targets instead. A bank-specific valuation " + "framework can be added later." + ) + return + shares = info.get("sharesOutstanding") or info.get("floatShares") current_price = info.get("currentPrice") or info.get("regularMarketPrice") total_debt = info.get("totalDebt") or 0.0 |
