summaryrefslogtreecommitdiff
path: root/backend/tests/test_dcf_edge_cases.py
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-06-29 01:37:18 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-06-29 01:37:18 -0700
commitccdc3572c802a29f54ceb85d020bb9ab8ffe040c (patch)
treec4e6a49999019a1f3e9722d3d0865216a60312e5 /backend/tests/test_dcf_edge_cases.py
parent74fcf05bf9a8d2bccb0016896ffa49886bc81300 (diff)
test: add DCF and valuation backend tests
Diffstat (limited to 'backend/tests/test_dcf_edge_cases.py')
-rw-r--r--backend/tests/test_dcf_edge_cases.py155
1 files changed, 155 insertions, 0 deletions
diff --git a/backend/tests/test_dcf_edge_cases.py b/backend/tests/test_dcf_edge_cases.py
new file mode 100644
index 0000000..94a960b
--- /dev/null
+++ b/backend/tests/test_dcf_edge_cases.py
@@ -0,0 +1,155 @@
+"""Boundary and invalid-input tests for the DCF engine."""
+
+import pandas as pd
+import pytest
+
+from app.services.data_service import _run_dcf
+
+
+def _fcf_series(base: float, growth: float) -> pd.Series:
+ """Return a two-point historical FCF series whose median YoY growth equals `growth`."""
+ prior = base / (1 + growth)
+ return pd.Series([prior, base], index=pd.to_datetime(["2023-09-30", "2024-09-30"]))
+
+
+def _expected_mid_year_enterprise_value(
+ base_fcf: float,
+ growth: float,
+ wacc: float,
+ terminal_growth: float,
+ projection_years: int,
+) -> float:
+ projected = [base_fcf * ((1 + growth) ** year) for year in range(1, projection_years + 1)]
+ discounted = [fcf / ((1 + wacc) ** (year - 0.5)) for year, fcf in enumerate(projected, start=1)]
+ terminal_fcf = projected[-1] * (1 + terminal_growth)
+ terminal_value = terminal_fcf / (wacc - terminal_growth)
+ terminal_value_pv = terminal_value / ((1 + wacc) ** (projection_years - 0.5))
+ return sum(discounted) + terminal_value_pv
+
+
+def test_run_dcf_two_year_horizon_uses_mid_year_discounting() -> None:
+ fcf = _fcf_series(100.0, 0.05)
+ expected_enterprise_value = _expected_mid_year_enterprise_value(
+ base_fcf=100.0,
+ growth=0.05,
+ wacc=0.10,
+ terminal_growth=0.03,
+ projection_years=2,
+ )
+
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=10.0,
+ wacc=0.10,
+ terminal_growth=0.03,
+ projection_years=2,
+ )
+
+ assert result["enterprise_value"] == pytest.approx(expected_enterprise_value)
+
+
+def test_run_dcf_terminal_growth_equals_wacc_is_error() -> None:
+ fcf = _fcf_series(100.0, 0.05)
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=10.0,
+ wacc=0.10,
+ terminal_growth=0.10,
+ projection_years=5,
+ )
+ assert "error" in result
+ assert "Terminal growth" in result["error"]
+ assert "intrinsic_value_per_share" not in result
+
+
+def test_run_dcf_terminal_growth_above_wacc_is_error() -> None:
+ fcf = _fcf_series(100.0, 0.05)
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=10.0,
+ wacc=0.10,
+ terminal_growth=0.11,
+ projection_years=5,
+ )
+ assert "error" in result
+ assert "Terminal growth" in result["error"]
+ assert "intrinsic_value_per_share" not in result
+
+
+def test_run_dcf_zero_wacc_is_error() -> None:
+ fcf = _fcf_series(100.0, 0.05)
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=10.0,
+ wacc=0.0,
+ terminal_growth=0.03,
+ projection_years=5,
+ )
+ assert "error" in result
+ assert "WACC" in result["error"]
+ assert "intrinsic_value_per_share" not in result
+
+
+def test_run_dcf_negative_base_fcf_is_error() -> None:
+ """The most recent FCF is negative, making the DCF not meaningful."""
+ fcf = pd.Series([100.0, -50.0], index=pd.to_datetime(["2023-09-30", "2024-09-30"]))
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=10.0,
+ wacc=0.10,
+ terminal_growth=0.03,
+ projection_years=5,
+ )
+ assert "error" in result
+ assert "negative" in result["error"].lower() or "zero" in result["error"].lower()
+ assert "intrinsic_value_per_share" not in result
+
+
+def test_run_dcf_zero_base_fcf_is_error() -> None:
+ fcf = pd.Series([100.0, 0.0], index=pd.to_datetime(["2023-09-30", "2024-09-30"]))
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=10.0,
+ wacc=0.10,
+ terminal_growth=0.03,
+ projection_years=5,
+ )
+ assert "error" in result
+ assert "zero" in result["error"].lower()
+ assert "intrinsic_value_per_share" not in result
+
+
+def test_run_dcf_zero_shares_returns_empty() -> None:
+ fcf = _fcf_series(100.0, 0.05)
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=0.0,
+ wacc=0.10,
+ terminal_growth=0.03,
+ projection_years=5,
+ )
+ assert result == {}
+
+
+def test_run_dcf_negative_shares_returns_empty() -> None:
+ fcf = _fcf_series(100.0, 0.05)
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=-1.0,
+ wacc=0.10,
+ terminal_growth=0.03,
+ projection_years=5,
+ )
+ assert result == {}
+
+
+def test_run_dcf_insufficient_history_returns_empty() -> None:
+ fcf = pd.Series([100.0], index=pd.to_datetime(["2024-09-30"]))
+ result = _run_dcf(
+ fcf_series=fcf,
+ shares_outstanding=10.0,
+ wacc=0.10,
+ terminal_growth=0.03,
+ projection_years=5,
+ )
+ assert result == {}