From 92b7eae36866c3424f44b4b6a653833a65df91a9 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 30 Mar 2026 19:09:45 -0700 Subject: Compute all Key Ratios from raw statements, eliminating FMP ratio calls Added compute_ttm_ratios() which derives all 16 TTM ratios directly from yfinance quarterly income statements, balance sheets, and cash flow: Valuation: P/E, P/S, P/B, EV/EBITDA, EV/Revenue Profitability: Gross/Operating/Net Margin, ROE, ROA, ROIC Leverage: D/E, Current Ratio, Quick Ratio, Interest Coverage Dividends: Yield, Payout Ratio get_key_ratios() no longer calls FMP's /ratios-ttm or /key-metrics-ttm endpoints, saving ~2 FMP API calls per ticker load (including each Comps peer). Forward P/E still comes from yfinance info dict (analyst estimate). This also fixes EV/EBITDA for all tickers (DDOG was 4998x from FMP/yfinance pre-computed values, now correctly 194x from income statement EBITDA). Co-Authored-By: Claude Sonnet 4.6 --- components/valuation.py | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) (limited to 'components/valuation.py') diff --git a/components/valuation.py b/components/valuation.py index e77073f..7ac32f5 100644 --- a/components/valuation.py +++ b/components/valuation.py @@ -143,45 +143,34 @@ def _render_ratios(ticker: str): st.info("Ratio data unavailable. Check your FMP API key.") return - def r(fmp_key, yf_key=None, fmt=fmt_ratio): - val = ratios.get(fmp_key) if ratios else None - if val is None and yf_key and info: - val = info.get(yf_key) + def r(key, fmt=fmt_ratio): + val = ratios.get(key) if ratios else None return fmt(val) if val is not None else "—" - # Always compute EV/EBITDA from income statement — both FMP and yfinance's - # pre-computed multiples use a bad EBITDA figure for many tickers. - def _ev_ebitda() -> str: - ev = info.get("enterpriseValue") - ebitda = get_ebitda_from_income_stmt(ticker) - if ev and ebitda and ebitda > 0: - return fmt_ratio(ev / ebitda) - return "—" - rows = [ ("Valuation", [ - ("P/E (TTM)", r("peRatioTTM", "trailingPE")), - ("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", _ev_ebitda()), - ("EV/Revenue", r("evToSalesTTM", "enterpriseToRevenue")), + ("P/E (TTM)", r("peRatioTTM")), + ("Forward P/E", r("forwardPE")), + ("P/S (TTM)", r("priceToSalesRatioTTM")), + ("P/B", r("priceToBookRatioTTM")), + ("EV/EBITDA", r("enterpriseValueMultipleTTM")), + ("EV/Revenue", r("evToSalesTTM")), ]), ("Profitability", [ - ("Gross Margin", r("grossProfitMarginTTM", "grossMargins", fmt_pct)), - ("Operating Margin", r("operatingProfitMarginTTM", "operatingMargins", fmt_pct)), - ("Net Margin", r("netProfitMarginTTM", "profitMargins", fmt_pct)), - ("ROE", r("returnOnEquityTTM", "returnOnEquity", fmt_pct)), - ("ROA", r("returnOnAssetsTTM", "returnOnAssets", fmt_pct)), + ("Gross Margin", r("grossProfitMarginTTM", fmt=fmt_pct)), + ("Operating Margin", r("operatingProfitMarginTTM", fmt=fmt_pct)), + ("Net Margin", r("netProfitMarginTTM", fmt=fmt_pct)), + ("ROE", r("returnOnEquityTTM", fmt=fmt_pct)), + ("ROA", r("returnOnAssetsTTM", fmt=fmt_pct)), ("ROIC", r("returnOnInvestedCapitalTTM", fmt=fmt_pct)), ]), ("Leverage & Liquidity", [ - ("Debt/Equity", r("debtToEquityRatioTTM", "debtToEquity")), - ("Current Ratio", r("currentRatioTTM", "currentRatio")), - ("Quick Ratio", r("quickRatioTTM", "quickRatio")), + ("Debt/Equity", r("debtToEquityRatioTTM")), + ("Current Ratio", r("currentRatioTTM")), + ("Quick Ratio", r("quickRatioTTM")), ("Interest Coverage", r("interestCoverageRatioTTM")), - ("Dividend Yield", r("dividendYieldTTM", "dividendYield", fmt_pct)), - ("Payout Ratio", r("dividendPayoutRatioTTM", "payoutRatio", fmt_pct)), + ("Dividend Yield", r("dividendYieldTTM", fmt=fmt_pct)), + ("Payout Ratio", r("dividendPayoutRatioTTM", fmt=fmt_pct)), ]), ] -- cgit v1.3-2-g0d8e