Your board wants a revenue projection for next quarter. Your FP&A team builds it in Excel using trailing averages and gut-adjusted growth rates. Then a board member asks "what's the confidence interval on that?" and the room goes quiet. A spreadsheet formula gives one number and says nothing about how far off it might be. This page shows one way to forecast revenue and show prediction ranges. The time it takes depends on the data and the work needed to prepare it.
Worked Example (Simulated): What Might the Next Three Months Look Like?
Question: What might the next three monthly revenue values be, and how far off could a simple seasonal forecast run?
Inputs: Made-up (simulated) data, seed 2026. The script makes 36 monthly values with a built-in rise of 1.5 revenue units a month (18 a year), a seasonal pattern that repeats each year, and random noise with a spread of 5. Values are revenue units, not dollars.
Method: For each month, two forecasts use earlier data only. The seasonal-naive forecast takes the value from the same month a year earlier. The second adds the average 12-month change seen so far. To check them, forecast months 25 to 36. MAPE is average absolute percentage error: the mean absolute error as a share of actual values. Prediction ranges come from the size of the second forecast's backtest errors (the 80% and 95% points of the absolute errors).
Complete Python script:
import numpy as np
seed = 2026
rng = np.random.default_rng(seed)
months = np.arange(1, 37)
season = np.array(
[0.82, 0.84, 0.94, 0.98, 1.07, 1.16, 1.18, 1.10, 1.02, 0.98, 0.94, 0.97]
)
noise = rng.normal(0, 5, size=36)
revenue = 100 + 1.5 * months + 12 * season[(months - 1) % 12] + noise
# Two forecasts for month t, both from data before t:
# seasonal naive: the value 12 months earlier
# seasonal naive plus change: that value plus the average 12-month
# change seen so far
def forecast(t, with_change):
base = revenue[t - 13]
if not with_change:
return base
last = min(t - 1, len(revenue))
changes = revenue[12:last] - revenue[:last - 12]
return base + changes.mean()
# Backtest months 25-36. The 12-month change needs months 13-24 to exist.
test_months = np.arange(25, 37)
actual = revenue[24:36]
report = {}
for label, flag in (
("Seasonal naive", False),
("Seasonal naive plus yearly change", True),
):
pred = np.array([forecast(t, flag) for t in test_months])
abs_errors = np.abs(actual - pred)
mape = np.mean(abs_errors / actual) * 100
mean_error = np.mean(actual - pred)
q80 = np.quantile(abs_errors, 0.80, method="higher")
q95 = np.quantile(abs_errors, 0.95, method="higher")
report[label] = (mape, mean_error, q80, q95)
print(f"Simulated data, seed {seed}")
print(
"Input: 36 monthly revenue values; trend = 1.5 units/month, seasonal "
"multipliers repeated yearly, normal noise SD = 5 units."
)
print(
"Backtest: forecast months 25-36 from earlier data only; "
"no model refit needed."
)
for label, (mape, mean_error, q80, q95) in report.items():
print(
f"{label}: MAPE {mape:.2f}%; mean error (actual minus forecast) "
f"{mean_error:.2f}; error size at 80% {q80:.2f}, at 95% {q95:.2f} "
"revenue units"
)
print("Built-in rise per year: 18.00 revenue units (1.5 per month x 12)")
q80, q95 = report["Seasonal naive plus yearly change"][2:]
print(
"Months 37-39 are not in the data. The no-noise built-in level is "
"shown only to check the forecasts."
)
print(
"Forecast month | seasonal naive | with yearly change | "
"80% range for the second | 95% range for the second | built-in level"
)
for t in (37, 38, 39):
plain = forecast(t, False)
adjusted = forecast(t, True)
level = 100 + 1.5 * t + 12 * season[(t - 1) % 12]
print(
f"{t:>13} | {plain:6.2f} | {adjusted:6.2f} | "
f"{adjusted - q80:5.2f} to {adjusted + q80:5.2f} | "
f"{adjusted - q95:5.2f} to {adjusted + q95:5.2f} | {level:6.2f}"
)
Printed output:
Simulated data, seed 2026
Input: 36 monthly revenue values; trend = 1.5 units/month, seasonal multipliers repeated yearly, normal noise SD = 5 units.
Backtest: forecast months 25-36 from earlier data only; no model refit needed.
Seasonal naive: MAPE 12.83%; mean error (actual minus forecast) 20.57; error size at 80% 27.55, at 95% 31.63 revenue units
Seasonal naive plus yearly change: MAPE 3.66%; mean error (actual minus forecast) 2.41; error size at 80% 9.96, at 95% 13.82 revenue units
Built-in rise per year: 18.00 revenue units (1.5 per month x 12)
Months 37-39 are not in the data. The no-noise built-in level is shown only to check the forecasts.
Forecast month | seasonal naive | with yearly change | 80% range for the second | 95% range for the second | built-in level
37 | 150.55 | 169.38 | 159.42 to 179.34 | 155.56 to 183.20 | 165.34
38 | 158.20 | 177.03 | 167.07 to 186.99 | 163.21 to 190.85 | 167.08
39 | 148.21 | 167.04 | 157.08 to 177.00 | 153.22 to 180.86 | 169.78
Result: The plain seasonal-naive forecast ignores the rise built into the data, so it runs low: the mean error (actual minus forecast) is 20.57 revenue units, and its backtest MAPE of 12.83% comes mostly from that missed rise. Adding the average yearly change cuts the backtest MAPE from 12.83% to 3.66% and the mean error to 2.41. Months 37 to 39 are not in the data. For them the plain forecasts are 150.55, 158.20 and 148.21, and the forecasts with the yearly change are 169.38, 177.03 and 167.04. The last column shows the no-noise built-in level, which the script uses only to check the forecasts (165.34, 167.08, 169.78). Each 80% range for the second forecast contains its built-in level; the plain forecasts sit 15, 9 and 22 below it.
Limit: This is a small simulated example with one noise draw. The ranges are set from the same 12 backtest errors, so they cover at least their stated share of those 12 errors by construction. That is not an independent check and does not promise coverage for future months. The example uses the simplest seasonal method, not the ETS model described below.
Sources
- Ljung, G. M., and Box, G. E. P. (1978). On a measure of lack of fit in time series models. Biometrika. doi 10.1093/biomet/65.2.297. Ljung and Box published their lack-of-fit measure in 1978.
- Hyndman, R. J., Koehler, A. B., Snyder, R. D., and Grose, S. (2002). A state space framework for automatic forecasting using exponential smoothing methods. International Journal of Forecasting. doi 10.1016/S0169-2070(01)00110-8. Hyndman, Koehler, Snyder and Grose published state space models for exponential smoothing in 2002.
- Hyndman, R. J., and Athanasopoulos, G. Forecasting: Principles and Practice (3rd ed.). otexts.com/fpp3. Cited for prediction intervals.
Why Your Board Stopped Trusting the Spreadsheet
The core problem with a typical spreadsheet revenue forecast is not that it is wrong: it is that it does not tell you how wrong it might be. A projection of "$2.4M next quarter" gives the board one number to plan around. If the actual number comes in at $2.1M or $2.7M, the CFO looks either overly cautious or overly optimistic. Neither builds credibility.
Statistical forecasting solves this by producing three numbers instead of one: a point estimate ($2.4M), a conservative scenario ($2.15M at the 80% lower bound), and an optimistic scenario ($2.65M at the 80% upper bound). The board can plan against the conservative case for expenses, budget to the expected case for hiring, and prepare for the optimistic case in terms of capacity. That is the difference between a guess and a planning tool.
What This Analysis Does
The revenue forecasting module uses ETS (Error, Trend, Seasonality) exponential smoothing. It separates your historical revenue into three components:
- Trend: is your business growing 8% year-over-year, contracting, or flat? The trend line strips out seasonal noise and shows the genuine trajectory.
- Seasonality, which months are consistently strong or weak, and by how much? A construction company that books 40% of annual revenue between May and August needs to see that pattern quantified, not just felt.
- Noise: one-off events like a viral product launch or a supply chain disruption. The model isolates these so they do not distort the forecast.
The algorithm evaluates possible ETS model variants (additive or multiplicative error, trend, and seasonal components, damped or undamped) and selects the one that best fits your historical patterns using AIC (Akaike Information Criterion). You get the benefit of model selection without needing to understand the math.
The output is a month-by-month forecast with 80% and 95% prediction ranges and a forecast table.
Who This Is For
This analysis is built for finance teams at companies with $2M to $50M in annual revenue: large enough to have meaningful seasonal patterns in their data, but too small to justify an enterprise planning platform. The typical user is an FP&A analyst or controller who currently builds quarterly projections in Excel using the FORECAST function or trailing averages.
It works for any company with recurring or semi-recurring revenue: SaaS, professional services, e-commerce, manufacturing, or subscription businesses. The key requirement is 24+ months of historical revenue data so the model can observe at least two full seasonal cycles.
What Data You Need
A CSV export from your accounting system: QuickBooks, Xero, NetSuite, or a Stripe transaction export. You need two columns at minimum:
- Date: transaction date, invoice date, or period end date. Any date format works.
- Revenue: dollar amount per transaction or period total. The tool aggregates transaction-level records into monthly totals automatically.
Optional columns that unlock richer analysis:
- Category: product line, service type, or business segment. Enables per-category trend breakdowns showing which segments drive growth.
- Region: geographic breakdown if you operate in multiple markets.
Minimum: 24 months of data (24 rows if pre-aggregated monthly, or 500+ transaction rows spanning 24 months). The more history you provide, the more stable the seasonal estimates. Date formats are flexible: YYYY-MM-DD, MM/DD/YYYY, and most other common formats all work.
How to Read the Report
Revenue Forecast Chart: the centerpiece. Historical revenue appears as a solid line, then extends into the future with shaded confidence bands. The narrower 80% band is your planning range. Across many forecasts, about 80 in 100 actual values should land inside it, if the model fits the business. The wider 95% band shows the extreme scenarios. Use the 80% band for operational planning and the 95% for stress testing.
Seasonal Decomposition: breaks revenue into trend, seasonal pattern, and remainder using STL (Seasonal and Trend decomposition using Loess). The trend line shows whether your business is genuinely growing after removing holiday spikes and seasonal dips. If the seasonal component shows a December peak that has been growing 15% per year while baseline growth is only 5%, that is strategically important.
Category Trends, if you mapped a category column, this chart shows monthly revenue by product line or segment. It reveals which categories are driving overall growth and which are flat or declining. A total revenue increase of 12% that comes entirely from one product line while three others contract is a very different story than broad-based growth.
Forecast Detail Table: each forecasted month with point estimate, lower bound, and upper bound. This is the table you paste into your board deck or budget spreadsheet. Each row is a month, each column is a number you can plan against.
Model Performance: MAPE (Mean Absolute Percentage Error) tells you how accurate the model would have been on historical data. In the worked example above, MAPE was 12.83% for the plain method and 3.66% after adding the average yearly change. The Ljung-Box test checks residual autocorrelation. A result that does not flag autocorrelation does not show that all useful patterns were captured.
What To Do With the Results
Board Reporting
- Present three scenarios (conservative (lower bound), expected (point forecast), and optimistic (upper bound)) each grounded in the same statistical model rather than gut feel
- Show the seasonal decomposition to explain why Q1 and Q3 forecasts differ from Q4 and Q2
- Report MAPE as the average absolute percentage miss on past months. It is not an error range
Operational Planning
- Use the conservative scenario (80% lower bound) for expense budgeting: do not hire against the optimistic case
- Use category breakdowns to plan inventory, staffing, and marketing spend by product line
- Identify months where the confidence band widens significantly. Those are the months where the business is most unpredictable and buffer planning matters most
Ongoing Use
- Re-run quarterly as new data accumulates, the model improves with more history
- Compare actuals against prior forecasts to track whether your business is becoming more or less predictable over time
- If a month comes in outside the 95% confidence band, investigate, because something may have changed in the business
When to Use Something Else
- Need to understand what drives revenue: This model is purely historical. It extrapolates patterns from the past. For causal questions like "what happens if we increase ad spend 20%?" use ridge regression.
- Fewer than 12 months of data: Use a simple trend analysis to see the direction without committing to a formal forecast.
- Complex seasonality (daily data with weekly + yearly patterns): Use Prophet decomposition, which handles multiple seasonal frequencies.
- Need to compare financial metrics across companies: Use financial ratio benchmarking instead.
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