Every board meeting needs a revenue projection. Every investor asks "what does the next 6 months look like?" And every SaaS founder builds that forecast in a spreadsheet with a growth assumption they picked because it felt right. The result is either embarrassingly optimistic or embarrassingly conservative. This analysis replaces the guesswork with a statistical time series forecast with a seasonal component and prediction intervals. Upload your revenue data and get a chart with projections and their ranges.
Worked Example (Simulated): A Six-Month Revenue Forecast
Question: What could sales be each month for the next six months if the past trend and yearly cycle hold?
Inputs: Simulated data, seed 23. The script makes 48 months of made-up monthly revenue. It builds in a rise of $600 a month, a yearly sine cycle of $4,000, and random noise with a standard deviation of $1,800. This is monthly revenue. The script does not compute ARR.
Method: Fit a straight trend plus yearly sine and cosine terms. First fit on the first 42 months and test on the last 6, which were left out of that fit. Then refit on all 48 months to make the forecast and its 95% prediction range.
import numpy as np
from scipy.stats import t
SEED = 23
N_MONTHS = 48
HOLDOUT = 6
HORIZON = 6
rng = np.random.default_rng(SEED)
t_index = np.arange(N_MONTHS, dtype=float)
season = 4000 * np.sin(2 * np.pi * t_index / 12)
revenue = 30000 + 600 * t_index + season + rng.normal(0, 1800, N_MONTHS)
def design(index):
index = np.asarray(index, dtype=float)
return np.column_stack([
np.ones(index.size),
index,
np.sin(2 * np.pi * index / 12),
np.cos(2 * np.pi * index / 12),
])
def fit(x, y):
beta, _, _, _ = np.linalg.lstsq(x, y, rcond=None)
resid = y - x @ beta
dof = len(y) - x.shape[1]
mse = float(resid @ resid / dof)
cov_base = np.linalg.inv(x.T @ x)
return beta, resid, dof, mse, cov_base
train_end = N_MONTHS - HOLDOUT
x_train = design(t_index[:train_end])
beta, resid, dof, mse, cov_base = fit(x_train, revenue[:train_end])
hold_idx = t_index[train_end:]
hold_pred = design(hold_idx) @ beta
hold_actual = revenue[train_end:]
mae = float(np.mean(np.abs(hold_actual - hold_pred)))
mape = float(np.mean(np.abs((hold_actual - hold_pred) / hold_actual)) * 100)
x_all = design(t_index)
beta_all, resid_all, dof_all, mse_all, cov_base_all = fit(x_all, revenue)
future_idx = np.arange(N_MONTHS, N_MONTHS + HORIZON, dtype=float)
x_future = design(future_idx)
forecast = x_future @ beta_all
leverage = np.einsum("ij,jk,ik->i", x_future, cov_base_all, x_future)
critical = float(t.ppf(0.975, dof_all))
pred_se = np.sqrt(mse_all * (1 + leverage))
lower = forecast - critical * pred_se
upper = forecast + critical * pred_se
print(f"Simulated data, seed {SEED}")
print(f"Input: {N_MONTHS} monthly revenue values; "
f"first value ${revenue[0]:,.0f}; last value ${revenue[-1]:,.0f}")
print("Method: linear trend plus sine/cosine yearly season terms; "
f"hold out last {HOLDOUT} months")
print(f"Holdout diagnostics: MAE ${mae:,.0f}; MAPE {mape:.1f}%")
print(f"Forecast: next {HORIZON} months, with 95% prediction intervals "
"based on regression residuals and t errors")
for j, (point, lo, hi) in enumerate(zip(forecast, lower, upper), start=1):
print(f"Month +{j}: forecast ${point:,.0f}; "
f"95% interval ${lo:,.0f} to ${hi:,.0f}")
print(f"Training residual standard error: ${np.sqrt(mse_all):,.0f}; "
f"residual degrees of freedom: {dof_all}")
Printed output:
Simulated data, seed 23
Input: 48 monthly revenue values; first value $30,996; last value $55,728
Method: linear trend plus sine/cosine yearly season terms; hold out last 6 months
Holdout diagnostics: MAE $1,471; MAPE 2.7%
Forecast: next 6 months, with 95% prediction intervals based on regression residuals and t errors
Month +1: forecast $58,829; 95% interval $54,623 to $63,035
Month +2: forecast $61,127; 95% interval $56,890 to $65,364
Month +3: forecast $62,977; 95% interval $58,716 to $67,238
Month +4: forecast $64,047; 95% interval $59,773 to $68,320
Month +5: forecast $64,210; 95% interval $59,936 to $68,483
Month +6: forecast $63,585; 95% interval $59,320 to $67,849
Training residual standard error: $1,958; residual degrees of freedom: 44
Result: The first forecast month is $58,829, with a 95% range of $54,623 to $63,035. On the 6 test months, the average error size is $1,471, or 2.7%. That low error is expected here. The fitted model has the same form as the rule that made the data (a line plus a yearly cycle), and the script builds in about $58,800 for month 49. A real series will not match a model this neatly, so expect larger errors.
Limit: The data are made up and this is one run. The model leaves out price changes, lost buyers, and other events. The low end of the range is not a floor.
Sources: Hyndman and Athanasopoulos, Forecasting: Principles and Practice (3rd ed.), for regression with seasonal terms and prediction intervals.
The Problem With Spreadsheet Forecasts
The typical SaaS revenue forecast works like this: take last month's MRR, apply a growth rate (usually a round number like 10% or 15% because it sounds good), and extend it for 6 months. The result is a hockey stick that tells your board what they want to hear but gives you nothing to plan against. When Q2 revenue comes in 30% below the projection, nobody knows whether the forecast was wrong, growth decelerated, churn spiked, or all three.
Dedicated FP&A platforms such as Causal, Runway, or Mosaic can be more than a Series A company needs when it wants one good revenue chart for the board deck.
What makes a forecast defensible is not a bigger number -- it is quantified uncertainty. A projection of $420,000 next month with a 95% prediction interval of $380,000 to $460,000 lets you plan for the pessimistic scenario in conservative budgets while knowing the upside. That range is what separates a planning tool from a guess.
How the Forecast Works
The forecast separates your revenue into three components (the worked example above uses a simpler method, a straight trend plus yearly sine and cosine terms; ETS exponential smoothing is a related approach that also splits a series into these parts):
- Trend -- is your business genuinely growing 8% year-over-year, or is it flat? The trend line strips away seasonal noise to show the underlying trajectory.
- Seasonality -- which months are consistently above or below average, and by how much? SaaS businesses with annual contracts see renewal clusters; those selling to enterprises see Q4 budget-flush spikes.
- Remainder -- one-off events, measurement noise, and anything the model cannot explain. Large spikes here flag months where something unusual happened.
ETS-family models differ in whether the error, trend, and seasonal components are additive or multiplicative, and whether the trend is damped. Comparing such variants with a criterion like AIC is a standard way to choose among them.
The output is not a single line extending into the future. It is a forecast with 80% and 95% prediction intervals -- shaded bands that show the range of likely outcomes. The 80% band is a narrower range for operational planning. The 95% band is wider. Neither is a guarantee: the low end of a band is not a sure floor, and actual revenue can land outside it.
What the Report Shows You
Revenue forecast chart -- historical monthly revenue as a solid line, extended into the future with point forecasts and shaded confidence bands. This is the centerpiece -- the chart that goes in the board deck. It communicates both the projection and the uncertainty in a single visual.
Seasonal decomposition -- your revenue broken into trend, seasonal pattern, and remainder using STL (Seasonal and Trend decomposition using Loess). The trend line answers "is our business genuinely growing?" stripped of seasonal noise. The seasonal component shows repeating patterns -- which months consistently over- or under-perform. If the remainder shows large spikes, those are months where something unusual happened that the model cannot explain.
Forecast detail table -- each forecasted month with point estimate, lower bound, and upper bound. This is the table you paste into your budget spreadsheet. Conservative scenario (lower bound), expected case (point forecast), and optimistic scenario (upper bound) -- all from the same statistical model.
Category breakdowns -- if you include a product line or plan tier column, the analysis shows per-category trends. You can see which segments drive growth and which are declining, which matters for portfolio decisions and resource allocation.
Model performance -- MAPE (Mean Absolute Percentage Error) tells you how accurate the model is on historical data. A lower MAPE means a closer fit on past months, and a high one can point to volatile data or too little history. The Ljung-Box test checks whether the model captured all predictable structure -- a passing result means the test found no significant autocorrelation in the residuals, which is what you want. It does not prove the residuals are random noise.
When to Use Revenue Forecasting
- Board meeting preparation -- replace the "we grew X% so let's plan for Y%" slide with a data-driven forecast showing expected revenue per quarter with confidence ranges
- Fundraising -- investors respond to forecasts that acknowledge uncertainty. A chart with confidence bands signals analytical maturity, not weakness.
- Annual budgeting and target setting -- instead of dividing an annual target by 4, see which quarters will naturally be stronger and allocate resources accordingly
- Cash flow planning -- the lower confidence bound gives a cautious case for hiring plans and runway calculations. It is not a guaranteed floor.
- Post-change measurement -- after a pricing change, product launch, or market shift, compare actual revenue against the pre-change forecast to see how far it moved. A gap alone does not show the change caused it.
What Data Do You Need?
A CSV with two columns: a date (transaction date, invoice date, or month) and a revenue amount. The tool aggregates transaction-level records into monthly totals automatically. You do not need to pre-aggregate.
Ideal data
- 24+ months of monthly revenue data -- needed for reliable seasonal estimation (the model must see at least two full cycles to distinguish a genuine Q4 spike from a one-time event)
- Category column (optional) -- product line, plan tier, or region for segmented forecasts
Minimum requirements
- 12 months -- produces a basic trend forecast with wider prediction intervals. Seasonal patterns will not be detected reliably.
- Revenue values should be numeric and positive. Returns or refunds as negative values are fine -- they net against positive sales during monthly aggregation.
Data sources: Stripe revenue export, QuickBooks/Xero P&L export, internal billing database export, or any transaction-level data with dates and amounts. If you have already run MRR analysis, feed the monthly MRR values from that report directly into this forecast.
What to Do With the Results
For the board deck
- Lead with the forecast chart -- the confidence bands communicate both ambition and realism. "We expect $520K MRR by Q4, with a 95% confidence range of $470K to $570K."
- Call out trend changes -- if the model detects a growth inflection point (acceleration or deceleration), highlight it. "Sales rose after our pricing change in Q2" or "the trend is slowing." More study is needed to say a change caused a rise.
- Show seasonal patterns -- if Q4 is consistently 15% above baseline, factor that into quota setting and hiring plans.
For operations
- Use the lower bound for conservative planning -- runway calculations, hiring gates, and cash reserves should use the pessimistic scenario, not the point forecast
- Use category breakdowns for resource allocation -- if one product line shows declining growth while another accelerates, shift investment accordingly
- Compare forecast to actuals monthly -- when actual revenue deviates from the forecast, investigate why. The model captures the predictable component; deviations flag the unpredictable events that need your attention.
When to Use Something Else
- Want to understand current MRR composition, not future projections: Use MRR analysis first -- decompose your revenue into new, churned, and net before forecasting where it is headed.
- Want to know what drives revenue changes: This forecast is purely historical: it extrapolates patterns. For questions about what drives revenue, such as the effect of a price change, use regression analysis.
- Want to predict individual customer churn: Use churn prediction -- it models the risk that drives revenue loss.
- Have fewer than 12 months of data: Time series forecasting needs history. Use a simple trend analysis to visualize direction without committing to a formal forecast.
- Need to model scenarios with different assumptions: Build a financial model in a spreadsheet or FP&A tool. This analysis gives you the statistical baseline; scenario planning requires manual assumption layers on top.
Your turn
Bring your own data and the question you actually need answered.
MCP AnalyticsData science by email Send us your data and your question, and we will reply. ds@mcpanalytics.ai