aboutsummaryrefslogtreecommitdiff
path: root/services/data_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/data_service.py
parent96b27f1d00ae8110273de973053c3d6bfc4f3662 (diff)
Harden valuation edge cases
Diffstat (limited to 'services/data_service.py')
-rw-r--r--services/data_service.py68
1 files changed, 50 insertions, 18 deletions
diff --git a/services/data_service.py b/services/data_service.py
index 412ca94..c278a2f 100644
--- a/services/data_service.py
+++ b/services/data_service.py
@@ -298,10 +298,14 @@ def compute_ttm_ratios(ticker: str) -> dict:
ratios["netProfitMarginTTM"] = net_income / revenue
if equity and equity > 0 and net_income is not None:
- ratios["returnOnEquityTTM"] = net_income / equity
+ roe = net_income / equity
+ if abs(roe) < 10:
+ ratios["returnOnEquityTTM"] = roe
if total_assets and total_assets > 0 and net_income is not None:
- ratios["returnOnAssetsTTM"] = net_income / total_assets
+ roa = net_income / total_assets
+ if abs(roa) < 10:
+ ratios["returnOnAssetsTTM"] = roa
# ROIC = NOPAT / Invested Capital
if ebit is not None and pretax_income and pretax_income != 0:
@@ -309,27 +313,39 @@ def compute_ttm_ratios(ticker: str) -> dict:
nopat = ebit * (1 - effective_tax_rate)
invested_capital = (equity or 0) + (total_debt or 0) - cash
if invested_capital > 0:
- ratios["returnOnInvestedCapitalTTM"] = nopat / invested_capital
+ roic = nopat / invested_capital
+ if abs(roic) < 10:
+ ratios["returnOnInvestedCapitalTTM"] = roic
# ── Valuation multiples ───────────────────────────────────────────────
if market_cap and market_cap > 0:
if net_income and net_income > 0:
ratios["peRatioTTM"] = market_cap / net_income
if revenue and revenue > 0:
- ratios["priceToSalesRatioTTM"] = market_cap / revenue
+ ps = market_cap / revenue
+ if 0 < ps < 100:
+ ratios["priceToSalesRatioTTM"] = ps
if equity and equity > 0:
- ratios["priceToBookRatioTTM"] = market_cap / equity
+ pb = market_cap / equity
+ if 0 < pb < 100:
+ ratios["priceToBookRatioTTM"] = pb
if market_cap and market_cap > 0:
ev = market_cap + (total_debt or 0.0) - cash
if revenue and revenue > 0:
- ratios["evToSalesTTM"] = ev / revenue
- if ebitda and ebitda > 0:
- ratios["enterpriseValueMultipleTTM"] = ev / ebitda
+ ev_sales = ev / revenue
+ if 0 < ev_sales < 100:
+ ratios["evToSalesTTM"] = ev_sales
+ if ebitda and ebitda > 1e6:
+ ev_ebitda = ev / ebitda
+ if 0 < ev_ebitda < 500:
+ ratios["enterpriseValueMultipleTTM"] = ev_ebitda
# ── Leverage & Liquidity ──────────────────────────────────────────────
if equity and equity > 0 and total_debt is not None:
- ratios["debtToEquityRatioTTM"] = total_debt / equity
+ de = total_debt / equity
+ if 0 <= de < 100:
+ ratios["debtToEquityRatioTTM"] = de
if current_liabilities and current_liabilities > 0 and current_assets is not None:
ratios["currentRatioTTM"] = current_assets / current_liabilities
@@ -338,8 +354,10 @@ def compute_ttm_ratios(ticker: str) -> dict:
if ebit is not None and interest_expense:
ie = abs(interest_expense)
- if ie > 0:
- ratios["interestCoverageRatioTTM"] = ebit / ie
+ if ie > 0 and ebit > 0:
+ coverage = ebit / ie
+ if 0 < coverage < 1000:
+ ratios["interestCoverageRatioTTM"] = coverage
# ── Dividends (from cash flow statement) ─────────────────────────────
dividends_paid = None
@@ -351,9 +369,13 @@ def compute_ttm_ratios(ticker: str) -> dict:
if dividends_paid and dividends_paid > 0:
if market_cap and market_cap > 0:
- ratios["dividendYieldTTM"] = dividends_paid / market_cap
+ div_yield = dividends_paid / market_cap
+ if 0 <= div_yield < 1:
+ ratios["dividendYieldTTM"] = div_yield
if net_income and net_income > 0:
- ratios["dividendPayoutRatioTTM"] = dividends_paid / net_income
+ payout = dividends_paid / net_income
+ if 0 <= payout < 10:
+ ratios["dividendPayoutRatioTTM"] = payout
# Expose raw EBITDA so callers (e.g. DCF EV/EBITDA section) use the
# same TTM figure as the Key Ratios tab — single canonical source.
@@ -498,12 +520,18 @@ def get_historical_ratios_yfinance(ticker: str) -> list[dict]:
if equity and equity > 0:
if net_income is not None:
- row["returnOnEquity"] = net_income / equity
+ roe = net_income / equity
+ if abs(roe) < 10:
+ row["returnOnEquity"] = roe
if total_debt is not None:
- row["debtEquityRatio"] = total_debt / equity
+ de = total_debt / equity
+ if 0 <= de < 100:
+ row["debtEquityRatio"] = de
if total_assets and total_assets > 0 and net_income is not None:
- row["returnOnAssets"] = net_income / total_assets
+ roa = net_income / total_assets
+ if abs(roa) < 10:
+ row["returnOnAssets"] = roa
# Price-based ratios — average closing price in ±45-day window around year-end
if shares and not hist.empty:
@@ -525,9 +553,13 @@ def get_historical_ratios_yfinance(ticker: str) -> list[dict]:
if net_income and net_income > 0:
row["peRatio"] = market_cap / net_income
if equity and equity > 0:
- row["priceToBookRatio"] = market_cap / equity
+ pb = market_cap / equity
+ if 0 < pb < 100:
+ row["priceToBookRatio"] = pb
if total_rev and total_rev > 0:
- row["priceToSalesRatio"] = market_cap / total_rev
+ ps = market_cap / total_rev
+ if 0 < ps < 100:
+ row["priceToSalesRatio"] = ps
# EV/EBITDA — approximate. Skip if EBITDA is too small to be meaningful,
# which otherwise creates absurd multiples for some software names.