diff options
Diffstat (limited to 'backend/app')
| -rw-r--r-- | backend/app/services/data_service.py | 103 |
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) |
