From 3806bd3b4d69917f3f5312acfa57bc4ee2886a49 Mon Sep 17 00:00:00 2001 From: Openclaw Date: Wed, 1 Apr 2026 23:32:01 -0700 Subject: Harden valuation edge cases --- services/data_service.py | 68 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 18 deletions(-) (limited to 'services/data_service.py') 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. -- cgit v1.3-2-g0d8e