aboutsummaryrefslogtreecommitdiff
path: root/services
diff options
context:
space:
mode:
authorTyler <tyler@tylerhoang.xyz>2026-04-02 00:10:06 -0700
committerTyler <tyler@tylerhoang.xyz>2026-04-02 00:10:06 -0700
commit7a267bc3c28bc7a77e84eaa400667a7b4c0d5adf (patch)
tree51b65d0ad1f1eaa1f276372a48cb319529284bb9 /services
parent3806bd3b4d69917f3f5312acfa57bc4ee2886a49 (diff)
Refactor valuation models tab
Diffstat (limited to 'services')
-rw-r--r--services/data_service.py16
-rw-r--r--services/fmp_service.py13
-rw-r--r--services/valuation_service.py49
3 files changed, 71 insertions, 7 deletions
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
@@ -213,6 +213,22 @@ def get_insider_transactions(ticker: str) -> 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),
+ }