aboutsummaryrefslogtreecommitdiff
path: root/services/fmp_service.py
diff options
context:
space:
mode:
authorTyler <tyler@tylerhoang.xyz>2026-03-30 19:09:45 -0700
committerTyler <tyler@tylerhoang.xyz>2026-03-30 19:09:45 -0700
commit92b7eae36866c3424f44b4b6a653833a65df91a9 (patch)
tree1ab8d86c74b741d5e1b2e8df2caa6de0b0bc9464 /services/fmp_service.py
parent8f6199a97c592e68f812d952b41942603723e2ed (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.py62
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 {}