summaryrefslogtreecommitdiff
path: root/backend/app
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 01:31:30 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 01:31:30 -0700
commitb877047a76cd4b661dccfd1dc8c0e7f2aa6a346c (patch)
treefa372d0b7eda271a1a88550a3c372f148309f66e /backend/app
parent0811b116b992ac977630c7deb687f995d89dc9a6 (diff)
feat: add get_valuation() service function
Diffstat (limited to 'backend/app')
-rw-r--r--backend/app/services/data_service.py103
1 files changed, 103 insertions, 0 deletions
diff --git a/backend/app/services/data_service.py b/backend/app/services/data_service.py
index c7077db..9fe7f67 100644
--- a/backend/app/services/data_service.py
+++ b/backend/app/services/data_service.py
@@ -456,6 +456,109 @@ def _run_price_to_book(book_value_per_share: float, target_multiple: float) -> d
}
+@cached(VALUATION_CACHE)
+def get_valuation(symbol: str) -> dict:
+ sym = normalize_symbol(symbol)
+
+ cf_annual = get_cash_flow(sym, quarterly=False)
+ inc_q = get_income_statement(sym, quarterly=True)
+ bal_q = get_balance_sheet(sym, quarterly=True)
+ info = get_company_info(sym)
+ shares = get_shares_outstanding(sym)
+
+ current_price = _safe_float(info.get("currentPrice"))
+
+ total_debt = _balance_value(bal_q, "Total Debt") or 0.0
+ cash = _balance_value(
+ bal_q, "Cash And Cash Equivalents",
+ "Cash Cash Equivalents And Short Term Investments"
+ ) or 0.0
+ preferred = _balance_value(bal_q, "Preferred Stock") or 0.0
+ minority = _balance_value(bal_q, "Minority Interest") or 0.0
+ equity = _balance_value(bal_q, "Stockholders Equity", "Common Stock Equity")
+
+ ebitda_ttm = _statement_ttm(inc_q, "EBITDA", "Normalized EBITDA")
+ revenue_ttm = _statement_ttm(inc_q, "Total Revenue")
+
+ book_value_per_share: float | None = None
+ if equity is not None and shares is not None and shares > 0:
+ book_value_per_share = equity / shares
+
+ ev_ebitda_multiple = _safe_float(info.get("enterpriseToEbitda"))
+ ev_revenue_multiple = _safe_float(info.get("enterpriseToRevenue"))
+ pb_multiple = _safe_float(info.get("priceToBook"))
+
+ fcf_series = _build_fcf_series(cf_annual)
+ dcf_raw: dict = {}
+ if fcf_series is not None and shares is not None and shares > 0:
+ dcf_raw = _run_dcf(
+ fcf_series=fcf_series,
+ shares_outstanding=shares,
+ total_debt=total_debt,
+ cash_and_equivalents=cash,
+ preferred_equity=preferred,
+ minority_interest=minority,
+ )
+
+ if not dcf_raw:
+ dcf_out: dict = {"available": False, "wacc": 0.10, "terminal_growth": 0.03}
+ elif "error" in dcf_raw:
+ dcf_out = {"available": True, "error": dcf_raw["error"], "wacc": 0.10, "terminal_growth": 0.03}
+ else:
+ dcf_out = {
+ "available": True,
+ "intrinsic_value_per_share": dcf_raw.get("intrinsic_value_per_share"),
+ "enterprise_value": dcf_raw.get("enterprise_value"),
+ "equity_value": dcf_raw.get("equity_value"),
+ "net_debt": dcf_raw.get("net_debt"),
+ "cash_and_equivalents": dcf_raw.get("cash_and_equivalents"),
+ "total_debt": dcf_raw.get("total_debt"),
+ "terminal_value_pv": dcf_raw.get("terminal_value_pv"),
+ "fcf_pv_sum": dcf_raw.get("fcf_pv_sum"),
+ "growth_rate_used": dcf_raw.get("growth_rate_used"),
+ "base_fcf": dcf_raw.get("base_fcf"),
+ "wacc": 0.10,
+ "terminal_growth": 0.03,
+ }
+
+ common = dict(
+ total_debt=total_debt,
+ total_cash=cash,
+ preferred_equity=preferred,
+ minority_interest=minority,
+ shares_outstanding=shares or 0.0,
+ )
+
+ ev_ebitda_out = _build_multiple_result(
+ _run_ev_ebitda(ebitda=ebitda_ttm, target_multiple=ev_ebitda_multiple, **common)
+ if ebitda_ttm and ev_ebitda_multiple and shares
+ else {}
+ )
+ ev_revenue_out = _build_multiple_result(
+ _run_ev_revenue(revenue=revenue_ttm, target_multiple=ev_revenue_multiple, **common)
+ if revenue_ttm and ev_revenue_multiple and shares
+ else {}
+ )
+ pb_out = _build_multiple_result(
+ _run_price_to_book(
+ book_value_per_share=book_value_per_share,
+ target_multiple=pb_multiple,
+ )
+ if book_value_per_share and pb_multiple
+ else {}
+ )
+
+ return {
+ "symbol": sym,
+ "current_price": current_price,
+ "shares_outstanding": shares,
+ "dcf": dcf_out,
+ "ev_ebitda": ev_ebitda_out,
+ "ev_revenue": ev_revenue_out,
+ "price_to_book": pb_out,
+ }
+
+
@cached(FINANCIALS_CACHE)
def get_financials(symbol: str, period: str = "annual") -> dict:
sym = normalize_symbol(symbol)