What Growth Rate Is the Market Pricing In? Reverse DCF in Python
July 17, 2026
What's the question?
A discounted cash flow model (DCF) values a company by projecting future cash flows and discounting them to today. The output is a price, determined almost entirely by an input the analyst invented. Assume 12% growth and the company looks cheap. Assume 6% and it looks expensive. The model launders the assumption into a conclusion.
A reverse DCF inverts the exercise. It takes the market price as given and solves for the growth rate that justifies it. Nothing is predicted: the market already made the forecast; the model reads it back out.
This converts a valuation into a falsifiable statement. "The market expects 17% free cash flow growth for a decade" can be checked against what the company delivered. The quantity of interest is the gap.
The approach
Free cash flow is cash from operations minus capital expenditure, measured after interest is paid — a levered figure, cash to shareholders. It must therefore be discounted against the equity: market capitalisation, not enterprise value, at a cost of equity. Mixing the two is the most common error here.
The sample is 10 large caps with positive, non-erratic free cash flow and wide growth dispersion: NVDA, GOOG, MSFT, AAPL, META, MA, COST, JNJ, PG and PEP.
- Base free cash flow is the trailing twelve months. A single year is noisy; a multi-year average is stale for a fast grower and inflates implied growth.
- Market capitalisation is pinned to 16 July 2026.
- Ten years of explicit growth at
g, then a terminal value growing at 2.5% forever. Discount at 8%, 10% and 12%. - Solve for
gwith Brent’s method so that model value equals market capitalisation. - Realised growth is the slope of a line fitted to log annual free cash flow over roughly seven years — robust to a spiky year where an endpoint CAGR is not.
Terminal growth stays below every discount rate tested, so the Gordon denominator (r − terminal growth) never nears zero: the model holds even when implied growth exceeds r. The bracket is capped at −50% to +100%, and a name with no root inside is reported as no solution.
Code
import numpy as np
import pandas as pd
from scipy.optimize import brentq
import xfinlink as xfl
xfl.set_api_key("YOUR_API_KEY") # free at https://xfinlink.com/signup
TICKERS = ["NVDA", "GOOGL", "MSFT", "AAPL", "META", "MA", "COST", "JNJ", "PG", "PEP"]
YEARS, TERMINAL_G, AS_OF = 10, 0.025, "2026-07-16"
def present_value(g, f0, r):
t = np.arange(1, YEARS + 1)
explicit = np.sum(f0 * (1 + g) ** t / (1 + r) ** t)
terminal = f0 * (1 + g) ** YEARS * (1 + TERMINAL_G) / ((r - TERMINAL_G) * (1 + r) ** YEARS)
return explicit + terminal
def implied_growth(market_cap, f0, r, lo=-0.50, hi=1.00):
fn = lambda g: present_value(g, f0, r) - market_cap
if fn(lo) > 0 or fn(hi) < 0:
return np.nan # no finite solution inside the bracket
return brentq(fn, lo, hi, xtol=1e-8)
# base free cash flow = trailing twelve months
q = xfl.fundamentals(TICKERS, period_type="quarterly", start="2023-01-01",
fields=["free_cash_flow"])
q = q.drop_duplicates(subset=["ticker", "fiscal_year", "fiscal_period"], keep="first")
q = q.sort_values(["ticker", "period_end"])
ttm_fcf = q.groupby("ticker").apply(lambda d: d.tail(4)["free_cash_flow"].sum(),
include_groups=False)
ann = xfl.fundamentals(TICKERS, period_type="annual", start="2015-01-01",
fields=["free_cash_flow"]).sort_values(["ticker", "fiscal_year"])
# realised growth = slope of log annual free cash flow
def trend_growth(d, n=7):
d = d.dropna(subset=["free_cash_flow"]).tail(n)
d = d[d["free_cash_flow"] > 0]
slope = np.polyfit(d["fiscal_year"].astype(float), np.log(d["free_cash_flow"]), 1)[0]
return np.exp(slope) - 1
realised = {t: trend_growth(ann[ann["ticker"] == t]) for t in ann["ticker"].unique()}
# robustness base: mean of the last 3 annual free cash flow figures
avg3_fcf = ann.groupby("ticker").apply(
lambda d: d.dropna(subset=["free_cash_flow"]).tail(3)["free_cash_flow"].mean(),
include_groups=False)
# end= pins the valuation date; without it .last() silently rolls forward
mkt = xfl.metrics(TICKERS, period_type="daily", fields=["market_cap"],
start=AS_OF, end=AS_OF)
market_cap = mkt.sort_values("period_end").groupby("ticker")["market_cap"].last()
rows = []
for t in ttm_fcf.index:
f0, cap = ttm_fcf[t], market_cap[t]
rows.append({"ticker": t, "realised": realised[t],
"g_8": implied_growth(cap, f0, 0.08),
"g_10": implied_growth(cap, f0, 0.10),
"g_12": implied_growth(cap, f0, 0.12),
"g_avg3": implied_growth(cap, avg3_fcf[t], 0.10)})
df = pd.DataFrame(rows)
df["gap"] = df["g_10"] - df["realised"]
print(df.to_string(index=False))
print("rank corr g@8% vs g@12%:", df["g_8"].corr(df["g_12"], method="spearman").round(3))
print("rank corr TTM vs 3y-avg base:", df["g_10"].corr(df["g_avg3"], method="spearman").round(3))
Full script with formatting and visualisation: reverse-dcf-implied-growth-rate-python.py
Output
Reverse DCF | 10y explicit + 2.5% terminal | equity-side (market cap vs levered FCF)
MktCap$B TTM FCF$B FCFyld g@8% g@10% g@12% realised gap
GOOG 4,295 64.4 1.50% 19.0% 24.2% 28.7% 14.0% 10.1%
COST 419 8.8 2.10% 14.6% 19.5% 23.9% 11.1% 8.4%
NVDA 5,019 119.1 2.37% 13.0% 17.9% 22.1% 75.1% -57.2%
MSFT 2,980 72.9 2.45% 12.6% 17.4% 21.7% 11.0% 6.4%
AAPL 4,888 129.2 2.64% 11.6% 16.4% 20.6% 9.0% 7.4%
META 1,684 48.3 2.87% 10.5% 15.3% 19.4% 15.8% -0.5%
JNJ 602 17.8 2.96% 10.1% 14.9% 19.0% 0.2% 14.6%
MA 491 17.8 3.62% 7.5% 12.1% 16.1% 15.7% -3.6%
PG 353 15.0 4.26% 5.5% 9.9% 13.7% 2.4% 7.5%
PEP 190 9.3 4.88% 3.7% 8.0% 11.8% 5.2% 2.9%
Implied g @10% spread: 16.1pp | rank corr g@8% vs g@12%: 1.000
Rank corr TTM base vs 3y-average base (both @10%): 0.927
What this tells us
At a 10% cost of equity, implied growth spans 16.1 points: 8.0% (PEP) to 24.2% (GOOG).
The sensitivity columns matter more than the point estimates. Moving the discount rate from 8% to 12% shifts PEP from 3.7% to 11.8% and GOOG from 19.0% to 28.7%. The level of implied growth is largely an artefact of the discount rate, itself an assumption. What survives is the ordering: the rank correlation between the 8% and 12% columns is 1.000, leaving the spread as the robust output.
Seven of the ten carry positive gaps: the price asks more than the record delivered. The widest four are JNJ at +14.6 points against realised growth of 0.2% per year, then GOOG +10.1, COST +8.4 and PG +7.5. META (−0.5) and MA (−3.6) sit near continuation.
NVDA is the outlier. Realised growth of 75.1% per year is genuine — free cash flow rose from roughly $4bn to $119bn — yet the price implies 17.9%, a gap of −57.2 points. The market is not extrapolating. It prices a severe deceleration, arithmetically necessary given that 75% sustained for another decade would imply free cash flow larger than most national economies.
One caveat. GOOG’s implied growth is highest partly because heavy capital expenditure compresses trailing free cash flow, and a depressed base mechanically raises implied growth. Rebuilding off a three-year average base leaves the ordering nearly intact (rank correlation 0.927).
So what?
The reverse DCF converts a price into a testable expectation, which is where the analytical work belongs. The question shifts from the unanswerable "what is this worth?" to the tractable "does a 14.9% growth requirement look reachable for a company that has compounded at 0.2%?"
Three practices follow. Report implied growth as a range across discount rates, not a point: the level moves several points for every point of r, while the ranking does not. Inspect the base, since a heavy investment cycle inflates implied growth for mechanical reasons. And treat the gap, not the level, as the signal — a stock priced for 17.9% after delivering 75% differs fundamentally from one priced for 14.9% after delivering 0.2%. Used this way, the model produces hypotheses rather than target prices.
Built with xfinlink — free financial data API for Python. pip install xfinlink
pip install xfinlink