From ad6b0b59c2a4f557d6d9d7fe9810c2ba7627580d Mon Sep 17 00:00:00 2001 From: Tyler Date: Sun, 29 Mar 2026 00:52:25 -0700 Subject: Add EV/EBITDA valuation, analyst targets, earnings history, and FCF growth override - DCF model: user-adjustable FCF growth rate slider (defaults to historical median) - EV/EBITDA valuation section with target multiple slider and implied price - Analyst Targets tab: price target summary + recommendation breakdown chart - Earnings History tab: EPS actual vs estimate table and line chart with next earnings date Co-Authored-By: Claude Sonnet 4.6 --- services/data_service.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'services/data_service.py') diff --git a/services/data_service.py b/services/data_service.py index fa9b026..0399c58 100644 --- a/services/data_service.py +++ b/services/data_service.py @@ -91,6 +91,60 @@ def get_market_indices() -> dict: return result +@st.cache_data(ttl=3600) +def get_analyst_price_targets(ticker: str) -> dict: + """Return analyst price target summary (keys: current, high, low, mean, median).""" + try: + t = yf.Ticker(ticker.upper()) + data = t.analyst_price_targets + return data if isinstance(data, dict) and data else {} + except Exception: + return {} + + +@st.cache_data(ttl=3600) +def get_recommendations_summary(ticker: str) -> pd.DataFrame: + """Return analyst recommendation counts by period. + Columns: period, strongBuy, buy, hold, sell, strongSell. + Row with period='0m' is the current month. + """ + try: + t = yf.Ticker(ticker.upper()) + df = t.recommendations_summary + return df if df is not None and not df.empty else pd.DataFrame() + except Exception: + return pd.DataFrame() + + +@st.cache_data(ttl=3600) +def get_earnings_history(ticker: str) -> pd.DataFrame: + """Return historical EPS actual vs estimate. + Columns: epsActual, epsEstimate, epsDifference, surprisePercent. + """ + try: + t = yf.Ticker(ticker.upper()) + df = t.earnings_history + return df if df is not None and not df.empty else pd.DataFrame() + except Exception: + return pd.DataFrame() + + +@st.cache_data(ttl=3600) +def get_next_earnings_date(ticker: str) -> str | None: + """Return the next expected earnings date as a string, or None. + Uses t.calendar (no lxml dependency). + """ + try: + t = yf.Ticker(ticker.upper()) + cal = t.calendar + dates = cal.get("Earnings Date", []) + if dates: + return str(dates[0]) + return None + except Exception: + return None + + @st.cache_data(ttl=3600) def get_free_cash_flow_series(ticker: str) -> pd.Series: """Return annual Free Cash Flow series (most recent first).""" -- cgit v1.3-2-g0d8e