aboutsummaryrefslogtreecommitdiff
path: root/services/fmp_service.py
diff options
context:
space:
mode:
authorOpenclaw <openclaw@mail.tylerhoang.xyz>2026-04-01 23:32:01 -0700
committerOpenclaw <openclaw@mail.tylerhoang.xyz>2026-04-01 23:32:01 -0700
commit3806bd3b4d69917f3f5312acfa57bc4ee2886a49 (patch)
tree7fca5c5c8aacc5365d2db33e40da2e251e3c67b0 /services/fmp_service.py
parent96b27f1d00ae8110273de973053c3d6bfc4f3662 (diff)
Harden valuation edge cases
Diffstat (limited to 'services/fmp_service.py')
-rw-r--r--services/fmp_service.py39
1 files changed, 37 insertions, 2 deletions
diff --git a/services/fmp_service.py b/services/fmp_service.py
index 1e5ea42..82a9c4c 100644
--- a/services/fmp_service.py
+++ b/services/fmp_service.py
@@ -33,6 +33,11 @@ def get_key_ratios(ticker: str) -> dict:
All ratios are self-computed via compute_ttm_ratios() — no FMP calls.
Forward P/E and dividend fallbacks come from yfinance's info dict.
+
+ For edge cases, trailing P/E prefers the vendor-supplied value from the
+ info dict when the self-computed statement-based figure is missing or
+ materially inconsistent. This avoids obviously bad P/E outputs on tickers
+ with restatements, near-zero earnings, or statement mapping quirks.
"""
ticker = ticker.upper()
merged = {"symbol": ticker}
@@ -44,13 +49,43 @@ def get_key_ratios(ticker: str) -> dict:
# Forward P/E requires analyst estimates — can't compute from statements
info = get_company_info(ticker)
if info:
+ trailing_pe_info = info.get("trailingPE")
+ trailing_pe_computed = merged.get("peRatioTTM")
+
+ if trailing_pe_info is not None:
+ try:
+ vendor_pe = float(trailing_pe_info)
+ except (TypeError, ValueError):
+ vendor_pe = None
+
+ try:
+ computed_pe = float(trailing_pe_computed) if trailing_pe_computed is not None else None
+ except (TypeError, ValueError):
+ computed_pe = None
+
+ if vendor_pe is not None and vendor_pe > 0:
+ if computed_pe is None or computed_pe <= 0:
+ merged["peRatioTTM"] = vendor_pe
+ else:
+ # If the two values are wildly different, trust the vendor
+ # trailing P/E. This prevents edge-case display bugs where a
+ # malformed TTM net income makes P/E look duplicated/wrong.
+ ratio_gap = max(vendor_pe, computed_pe) / max(min(vendor_pe, computed_pe), 1e-9)
+ if ratio_gap > 2.0:
+ merged["peRatioTTM"] = vendor_pe
+
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"]
+ payout_ratio_info = info.get("payoutRatio")
+ if (
+ merged.get("dividendPayoutRatioTTM") is None
+ and payout_ratio_info is not None
+ and float(payout_ratio_info) > 0
+ ):
+ merged["dividendPayoutRatioTTM"] = payout_ratio_info
return merged if len(merged) > 1 else {}