diff options
| author | Tyler <tyler@tylerhoang.xyz> | 2026-04-03 19:01:53 -0700 |
|---|---|---|
| committer | Tyler <tyler@tylerhoang.xyz> | 2026-04-03 19:01:53 -0700 |
| commit | ba3fa0002205e6f15dbcd59b50d81715410547d3 (patch) | |
| tree | 13ec5da22fb81121bd4a43df5d4033a6b821ad14 | |
| parent | ccfbce79a66b2d8aa136fddbed7c61c7436f2733 (diff) | |
Show uncapped historical FCF growth
| -rw-r--r-- | components/valuation.py | 9 | ||||
| -rw-r--r-- | services/valuation_service.py | 19 |
2 files changed, 23 insertions, 5 deletions
diff --git a/components/valuation.py b/components/valuation.py index 24405d6..4e2d003 100644 --- a/components/valuation.py +++ b/components/valuation.py @@ -30,6 +30,7 @@ from services.valuation_service import ( run_ev_revenue, run_price_to_book, compute_historical_growth_rate, + compute_raw_historical_growth_rate, ) from utils.formatters import fmt_ratio, fmt_pct, fmt_large, fmt_currency @@ -337,6 +338,7 @@ def _build_model_context(ticker: str) -> dict: base_fcf = _coerce_float(get_free_cash_flow_ttm(ticker)) hist_growth = compute_historical_growth_rate(fcf_series) if len(fcf_series) >= 2 else None + hist_growth_raw = compute_raw_historical_growth_rate(fcf_series) if len(fcf_series) >= 2 else None ebitda = _coerce_float(ratios_data.get("ebitdaTTM")) revenue_ttm = _coerce_float(get_revenue_ttm(ticker)) if revenue_ttm is None or revenue_ttm <= 0: @@ -435,6 +437,7 @@ def _build_model_context(ticker: str) -> dict: "fcf_series": fcf_series, "base_fcf": base_fcf, "hist_growth": hist_growth, + "hist_growth_raw": hist_growth_raw, "ebitda": ebitda, "revenue_ttm": revenue_ttm, "book_value_per_share": book_value_per_share, @@ -473,7 +476,9 @@ def _render_dcf_model(ctx: dict): st.markdown("**Discounted Cash Flow (DCF)**") hist_growth = ctx["hist_growth"] + hist_growth_raw = ctx["hist_growth_raw"] hist_growth_pct = hist_growth * 100 if hist_growth is not None else 5.0 + hist_growth_raw_pct = hist_growth_raw * 100 if hist_growth_raw is not None else hist_growth_pct slider_default = float(max(-20.0, min(30.0, hist_growth_pct))) st.caption( @@ -515,11 +520,11 @@ def _render_dcf_model(ctx: dict): max_value=30.0, value=round(slider_default, 1), step=0.5, - help=f"Historical median: {hist_growth_pct:.1f}%. Drag to override.", + help=f"Historical median: {hist_growth_raw_pct:.1f}%. Drag to override.", key=f"dcf_growth_{ctx['ticker']}", ) - st.caption(f"Historical FCF growth (median): **{hist_growth_pct:.1f}%**") + st.caption(f"Historical FCF growth (median): **{hist_growth_raw_pct:.1f}%**") result = run_dcf( fcf_series=ctx["fcf_series"], diff --git a/services/valuation_service.py b/services/valuation_service.py index 1230aa5..0b8dfde 100644 --- a/services/valuation_service.py +++ b/services/valuation_service.py @@ -12,9 +12,9 @@ def _cap_growth(value: float) -> float: return max(GROWTH_FLOOR, min(GROWTH_CAP, float(value))) -def compute_historical_growth_rate(fcf_series: pd.Series) -> float | None: +def _compute_median_historical_growth_rate(fcf_series: pd.Series) -> float | None: """ - Return a capped median YoY FCF growth rate from historical data. + Return the raw median YoY FCF growth rate from historical data. Notes: - skips periods with near-zero prior FCF @@ -40,7 +40,20 @@ def compute_historical_growth_rate(fcf_series: pd.Series) -> float | None: if not growth_rates: return None - return _cap_growth(float(np.median(growth_rates))) + return float(np.median(growth_rates)) + + +def compute_historical_growth_rate(fcf_series: pd.Series) -> float | None: + """Return a capped median YoY FCF growth rate from historical data.""" + raw_growth = _compute_median_historical_growth_rate(fcf_series) + if raw_growth is None: + return None + return _cap_growth(raw_growth) + + +def compute_raw_historical_growth_rate(fcf_series: pd.Series) -> float | None: + """Return the uncapped median YoY FCF growth rate from historical data.""" + return _compute_median_historical_growth_rate(fcf_series) def run_dcf( |
