From 7a267bc3c28bc7a77e84eaa400667a7b4c0d5adf Mon Sep 17 00:00:00 2001 From: Tyler Date: Thu, 2 Apr 2026 00:10:06 -0700 Subject: Refactor valuation models tab --- services/data_service.py | 16 ++++++++++++++ services/fmp_service.py | 13 ++++++------ services/valuation_service.py | 49 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 7 deletions(-) (limited to 'services') diff --git a/services/data_service.py b/services/data_service.py index c278a2f..e3f46cc 100644 --- a/services/data_service.py +++ b/services/data_service.py @@ -212,6 +212,22 @@ def get_insider_transactions(ticker: str) -> pd.DataFrame: return pd.DataFrame() +@st.cache_data(ttl=3600) +def get_revenue_ttm(ticker: str) -> float | None: + """Return trailing-twelve-month revenue from the last 4 reported quarters.""" + try: + t = yf.Ticker(ticker.upper()) + inc_q = t.quarterly_income_stmt + if inc_q is None or inc_q.empty or "Total Revenue" not in inc_q.index: + return None + vals = inc_q.loc["Total Revenue"].iloc[:4].dropna() + if len(vals) != 4: + return None + return float(vals.sum()) + except Exception: + return None + + @st.cache_data(ttl=3600) def compute_ttm_ratios(ticker: str) -> dict: """Compute all key financial ratios from raw yfinance quarterly statements. diff --git a/services/fmp_service.py b/services/fmp_service.py index 82a9c4c..914c14d 100644 --- a/services/fmp_service.py +++ b/services/fmp_service.py @@ -80,12 +80,13 @@ def get_key_ratios(ticker: str) -> dict: if merged.get("dividendYieldTTM") is None and info.get("dividendYield") is not None: merged["dividendYieldTTM"] = info["dividendYield"] 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 + if merged.get("dividendPayoutRatioTTM") is None and payout_ratio_info is not None: + try: + payout_ratio_value = float(payout_ratio_info) + except (TypeError, ValueError): + payout_ratio_value = None + if payout_ratio_value is not None and payout_ratio_value > 0: + merged["dividendPayoutRatioTTM"] = payout_ratio_value return merged if len(merged) > 1 else {} diff --git a/services/valuation_service.py b/services/valuation_service.py index 8559842..357c679 100644 --- a/services/valuation_service.py +++ b/services/valuation_service.py @@ -1,4 +1,4 @@ -"""Valuation engines for DCF and EV/EBITDA.""" +"""Valuation engines for DCF, EV/EBITDA, EV/Revenue, and simple multiple-based models.""" import numpy as np import pandas as pd @@ -158,3 +158,50 @@ def run_ev_ebitda( "implied_price_per_share": equity_value / shares_outstanding, "target_multiple_used": target_multiple, } + + +def run_ev_revenue( + revenue: float, + total_debt: float, + total_cash: float, + shares_outstanding: float, + target_multiple: float, +) -> dict: + """Derive implied equity value per share from an EV/Revenue multiple.""" + if not revenue or revenue <= 0: + return {} + if not shares_outstanding or shares_outstanding <= 0: + return {} + if not target_multiple or target_multiple <= 0: + return {} + + implied_ev = revenue * target_multiple + net_debt = (total_debt or 0.0) - (total_cash or 0.0) + equity_value = implied_ev - net_debt + + return { + "implied_ev": implied_ev, + "net_debt": net_debt, + "equity_value": equity_value, + "implied_price_per_share": equity_value / shares_outstanding, + "target_multiple_used": target_multiple, + "revenue_used": revenue, + } + + +def run_price_to_book( + book_value_per_share: float, + target_multiple: float, +) -> dict: + """Derive implied equity value per share from a P/B multiple.""" + if not book_value_per_share or book_value_per_share <= 0: + return {} + if not target_multiple or target_multiple <= 0: + return {} + + implied_price = float(book_value_per_share) * float(target_multiple) + return { + "implied_price_per_share": implied_price, + "target_multiple_used": float(target_multiple), + "book_value_per_share": float(book_value_per_share), + } -- cgit v1.3-2-g0d8e