diff options
| author | Tyler <tyler@tylerhoang.xyz> | 2026-03-30 19:09:45 -0700 |
|---|---|---|
| committer | Tyler <tyler@tylerhoang.xyz> | 2026-03-30 19:09:45 -0700 |
| commit | 92b7eae36866c3424f44b4b6a653833a65df91a9 (patch) | |
| tree | 1ab8d86c74b741d5e1b2e8df2caa6de0b0bc9464 /services/fmp_service.py | |
| parent | 8f6199a97c592e68f812d952b41942603723e2ed (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'services/fmp_service.py')
| -rw-r--r-- | services/fmp_service.py | 62 |
1 files changed, 21 insertions, 41 deletions
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 {} |
