From 712bbf675cfcb32535d8c494505e566efa347feb Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 30 Mar 2026 18:54:56 -0700 Subject: Fix EV/EBITDA using income statement EBITDA instead of info dict yfinance's info["ebitda"] is a miscalculated TTM value for many tickers (e.g. DDOG shows $7.5M when the correct TTM EBITDA is $193.8M). Added get_ebitda_from_income_stmt() which reads directly from t.income_stmt, matching the annual and quarterly figures. Key Ratios and DCF EV/EBITDA valuation now both use this source, with FMP as the preferred override. Co-Authored-By: Claude Sonnet 4.6 --- services/data_service.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'services/data_service.py') diff --git a/services/data_service.py b/services/data_service.py index 3de2484..acc935f 100644 --- a/services/data_service.py +++ b/services/data_service.py @@ -158,6 +158,28 @@ def get_insider_transactions(ticker: str) -> pd.DataFrame: return pd.DataFrame() +@st.cache_data(ttl=3600) +def get_ebitda_from_income_stmt(ticker: str) -> float | None: + """Return the most recent annual EBITDA from the income statement. + + yfinance's info['ebitda'] can be badly wrong for companies with large + stock-based compensation (e.g. it may deduct SBC, leaving near-zero EBITDA + even when the income statement EBITDA line is hundreds of millions). + The income statement 'EBITDA' row is the standard EBIT + D&A figure. + """ + try: + t = yf.Ticker(ticker.upper()) + inc = t.income_stmt + for label in ("EBITDA", "Normalized EBITDA"): + if label in inc.index: + val = inc.loc[label].iloc[0] + if val is not None and pd.notna(val): + return float(val) + return None + except Exception: + return None + + @st.cache_data(ttl=900) def get_options_chain(ticker: str) -> dict: """Return options chain data for the nearest available expirations. -- cgit v1.3-2-g0d8e