summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 01:32:48 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 01:32:48 -0700
commit0662a31869ad60dbb76b2f534fe294d15b3b3492 (patch)
tree6328b0bc7275e403fd39808b7af4824fca857ee5
parentb877047a76cd4b661dccfd1dc8c0e7f2aa6a346c (diff)
feat: add GET /api/tickers/{symbol}/valuation route
-rw-r--r--backend/app/main.py7
-rw-r--r--backend/tests/test_api.py43
2 files changed, 49 insertions, 1 deletions
diff --git a/backend/app/main.py b/backend/app/main.py
index eb5fa40..1cc127e 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -9,7 +9,7 @@ from fastapi import FastAPI, HTTPException, Query, status
from fastapi.middleware.cors import CORSMiddleware
from app.db import watchlist
-from app.schemas import FinancialsResponse, HistoryPoint, MarketIndex, SearchResult, TickerOverview, WatchlistResponse
+from app.schemas import FinancialsResponse, HistoryPoint, MarketIndex, SearchResult, TickerOverview, ValuationResponse, WatchlistResponse
from app.services import data_service
load_dotenv()
@@ -70,6 +70,11 @@ def ticker_financials(symbol: str, period: str = Query(default="annual", pattern
return data_service.get_financials(symbol, period=period)
+@app.get("/api/tickers/{symbol}/valuation", response_model=ValuationResponse)
+def ticker_valuation(symbol: str) -> dict:
+ return data_service.get_valuation(symbol)
+
+
@app.get("/api/watchlist", response_model=WatchlistResponse)
def get_watchlist() -> dict:
items = []
diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py
index 66c021f..af43975 100644
--- a/backend/tests/test_api.py
+++ b/backend/tests/test_api.py
@@ -897,3 +897,46 @@ def test_get_valuation_missing_multiples_data(monkeypatch) -> None:
assert result["ev_ebitda"]["available"] is False
assert result["ev_revenue"]["available"] is False
assert result["price_to_book"]["available"] is False
+
+
+def test_valuation_route_returns_structure(monkeypatch) -> None:
+ monkeypatch.setattr(
+ main.data_service,
+ "get_valuation",
+ lambda symbol: {
+ "symbol": "AAPL",
+ "current_price": 150.0,
+ "shares_outstanding": 15_000_000_000.0,
+ "dcf": {
+ "available": True,
+ "intrinsic_value_per_share": 182.0,
+ "enterprise_value": 2_800_000_000_000.0,
+ "equity_value": 2_750_000_000_000.0,
+ "net_debt": 50_000_000_000.0,
+ "cash_and_equivalents": 100_000_000_000.0,
+ "total_debt": 150_000_000_000.0,
+ "terminal_value_pv": 2_000_000_000_000.0,
+ "fcf_pv_sum": 800_000_000_000.0,
+ "growth_rate_used": 0.082,
+ "base_fcf": 110_000_000_000.0,
+ "wacc": 0.10,
+ "terminal_growth": 0.03,
+ "error": None,
+ },
+ "ev_ebitda": {
+ "available": True,
+ "implied_price_per_share": 178.0,
+ "implied_ev": 2_700_000_000_000.0,
+ "equity_value": 2_650_000_000_000.0,
+ "net_debt": 50_000_000_000.0,
+ "multiple_used": 20.0,
+ },
+ "ev_revenue": {"available": False},
+ "price_to_book": {"available": False},
+ },
+ )
+ result = main.ticker_valuation("AAPL")
+ assert result["symbol"] == "AAPL"
+ assert result["dcf"]["intrinsic_value_per_share"] == 182.0
+ assert result["ev_ebitda"]["multiple_used"] == 20.0
+ assert result["ev_revenue"]["available"] is False