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 --- services/fmp_service.py | 62 +++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 41 deletions(-) (limited to 'services/fmp_service.py') diff --git a/services/fmp_service.py b/services/fmp_service.py index 3bfa5c1..6d0ecd0 100644 --- a/services/fmp_service.py +++ b/services/fmp_service.py @@ -3,7 +3,7 @@ import os import requests import streamlit as st from dotenv import load_dotenv -from services.data_service import get_company_info, get_historical_ratios_yfinance +from services.data_service import get_company_info, get_historical_ratios_yfinance, compute_ttm_ratios load_dotenv() @@ -27,51 +27,31 @@ def _get(base_url: str, endpoint: str, params: dict | None = None) -> dict | lis return None -def _apply_yfinance_ratio_fallbacks(ticker: str, merged: dict) -> dict: - info = get_company_info(ticker) - if not info: - return merged - - fallback_map = { - "peRatioTTM": info.get("trailingPE"), - "priceToSalesRatioTTM": info.get("priceToSalesTrailing12Months"), - "priceToBookRatioTTM": info.get("priceToBook"), - "enterpriseValueMultipleTTM": info.get("enterpriseToEbitda"), - "evToEBITDATTM": info.get("enterpriseToEbitda"), - "evToSalesTTM": info.get("enterpriseToRevenue"), - "grossProfitMarginTTM": info.get("grossMargins"), - "operatingProfitMarginTTM": info.get("operatingMargins"), - "netProfitMarginTTM": info.get("profitMargins"), - "returnOnEquityTTM": info.get("returnOnEquity"), - "returnOnAssetsTTM": info.get("returnOnAssets"), - "debtToEquityRatioTTM": info.get("debtToEquity"), - "currentRatioTTM": info.get("currentRatio"), - "quickRatioTTM": info.get("quickRatio"), - "dividendYieldTTM": info.get("dividendYield"), - "dividendPayoutRatioTTM": info.get("payoutRatio"), - } - - for key, value in fallback_map.items(): - if merged.get(key) is None and value is not None: - merged[key] = value - - return merged - - @st.cache_data(ttl=3600) def get_key_ratios(ticker: str) -> dict: - """Return merged stable TTM ratios + key metrics for a ticker, with yfinance fallbacks.""" - ticker = ticker.upper() - ratios = _get(STABLE_BASE, "/ratios-ttm", params={"symbol": ticker}) - metrics = _get(STABLE_BASE, "/key-metrics-ttm", params={"symbol": ticker}) + """Return TTM ratios for a ticker, computed from raw financial statements. + All ratios are self-computed via compute_ttm_ratios() — no FMP calls. + Forward P/E and dividend fallbacks come from yfinance's info dict. + """ + ticker = ticker.upper() merged = {"symbol": ticker} - if isinstance(ratios, list) and ratios: - merged.update(ratios[0]) - if isinstance(metrics, list) and metrics: - merged.update(metrics[0]) - merged = _apply_yfinance_ratio_fallbacks(ticker, merged) + computed = compute_ttm_ratios(ticker) + if computed: + merged.update(computed) + + # Forward P/E requires analyst estimates — can't compute from statements + info = get_company_info(ticker) + if info: + if info.get("forwardPE") is not None: + merged["forwardPE"] = info["forwardPE"] + # Fallback: dividends from info dict when cash-flow data is missing + if merged.get("dividendYieldTTM") is None and info.get("dividendYield") is not None: + merged["dividendYieldTTM"] = info["dividendYield"] + if merged.get("dividendPayoutRatioTTM") is None and info.get("payoutRatio") is not None: + merged["dividendPayoutRatioTTM"] = info["payoutRatio"] + return merged if len(merged) > 1 else {} -- cgit v1.3-2-g0d8e