diff options
Diffstat (limited to 'components')
| -rw-r--r-- | components/news.py | 13 | ||||
| -rw-r--r-- | components/valuation.py | 33 |
2 files changed, 37 insertions, 9 deletions
diff --git a/components/news.py b/components/news.py index cea678e..522826c 100644 --- a/components/news.py +++ b/components/news.py @@ -7,11 +7,11 @@ from services.fmp_service import get_company_news as get_fmp_news def _sentiment_badge(sentiment: str) -> str: badges = { - "bullish": "🟢 Bullish", - "bearish": "🔴 Bearish", - "neutral": "⚪ Neutral", + "bullish": "🟢 Bullish (heuristic)", + "bearish": "🔴 Bearish (heuristic)", + "neutral": "⚪ Neutral (heuristic)", } - return badges.get(sentiment.lower(), "⚪ Neutral") + return badges.get(sentiment.lower(), "⚪ Neutral (heuristic)") def _classify_sentiment(article: dict) -> str: @@ -52,8 +52,9 @@ def render_news(ticker: str): col1.metric("Articles (7d)", buzz.get("articlesInLastWeek", "—")) bull_pct = score.get("bullishPercent") bear_pct = score.get("bearishPercent") - col2.metric("Bullish %", f"{bull_pct * 100:.1f}%" if bull_pct else "—") - col3.metric("Bearish %", f"{bear_pct * 100:.1f}%" if bear_pct else "—") + col2.metric("Bullish %", f"{bull_pct * 100:.1f}%" if bull_pct is not None else "—") + col3.metric("Bearish %", f"{bear_pct * 100:.1f}%" if bear_pct is not None else "—") + st.caption("Sentiment tags below are rule-based headline heuristics, not model-scored article sentiment.") st.divider() # Fetch articles — Finnhub first, FMP as fallback diff --git a/components/valuation.py b/components/valuation.py index 62ee1e3..82e0f0d 100644 --- a/components/valuation.py +++ b/components/valuation.py @@ -61,7 +61,7 @@ def _render_ratios(ticker: str): rows = [ ("Valuation", [ ("P/E (TTM)", r("peRatioTTM", "trailingPE")), - ("Forward P/E", r("priceEarningsRatioTTM", "forwardPE")), + ("Forward P/E", fmt_ratio(info.get("forwardPE")) if info.get("forwardPE") is not None else "—"), ("P/S (TTM)", r("priceToSalesRatioTTM", "priceToSalesTrailing12Months")), ("P/B", r("priceToBookRatioTTM", "priceToBook")), ("EV/EBITDA", r("enterpriseValueMultipleTTM", "enterpriseToEbitda")), @@ -99,6 +99,15 @@ def _render_dcf(ticker: str): info = get_company_info(ticker) 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 + cash_and_equivalents = ( + info.get("totalCash") + or info.get("cash") + or info.get("cashAndCashEquivalents") + or 0.0 + ) + preferred_equity = info.get("preferredStock") or 0.0 + minority_interest = info.get("minorityInterest") or 0.0 if not shares: st.info("Shares outstanding not available — DCF cannot be computed.") @@ -143,22 +152,40 @@ def _render_dcf(ticker: str): terminal_growth=terminal_growth, projection_years=projection_years, growth_rate_override=fcf_growth_pct / 100, + total_debt=total_debt, + cash_and_equivalents=cash_and_equivalents, + preferred_equity=preferred_equity, + minority_interest=minority_interest, ) if not result: st.warning("Insufficient data to run DCF model.") return + if result.get("error"): + st.warning(result["error"]) + return iv = result["intrinsic_value_per_share"] m1, m2, m3, m4 = st.columns(4) - m1.metric("Intrinsic Value / Share", fmt_currency(iv)) + m1.metric("Equity Value / Share", fmt_currency(iv)) if current_price: upside = (iv - current_price) / current_price m2.metric("Current Price", fmt_currency(current_price)) m3.metric("Upside / Downside", f"{upside * 100:+.1f}%", delta=f"{upside * 100:+.1f}%") m4.metric("FCF Growth Used", f"{result['growth_rate_used'] * 100:.1f}%") + st.caption( + "DCF is modeled on firm-level free cash flow, so enterprise value is bridged to equity value " + "using cash and debt before calculating per-share value." + ) + + bridge1, bridge2, bridge3, bridge4 = st.columns(4) + bridge1.metric("Enterprise Value", fmt_large(result["enterprise_value"])) + bridge2.metric("Net Debt", fmt_large(result["net_debt"])) + bridge3.metric("Equity Value", fmt_large(result["equity_value"])) + bridge4.metric("Terminal Value PV", fmt_large(result["terminal_value_pv"])) + st.write("") years = [f"Year {y}" for y in result["years"]] @@ -173,7 +200,7 @@ def _render_dcf(ticker: str): textposition="outside", )) fig.update_layout( - title="PV of Projected FCFs + Terminal Value (Billions)", + title="Enterprise Value Build: PV of Forecast FCFs + Terminal Value (Billions)", yaxis_title="USD (Billions)", plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)", |
