Monte Carlo for Promo Budgets: Your Point Estimate Is Not a Budget
Budgeting a promo at its expected payout under-funds the 95th percentile outcome by 88%. Forty lines of Python show you the whole distribution before launch instead of the invoice afterwards.
Table of contents
A single expected-cost number is not a budget. It is the centre of a distribution, and the distribution is what actually arrives.
For a promo with 50,000 eligible users, the expected payout is $70,142 and the 95th percentile is $131,821. If you reserved the expected value, you are $61,679 short in one campaign out of twenty.
The short answer
Promo payout is a product of two uncertain quantities: how many people redeem, and how much each redemption costs. Multiplying two point estimates gives you a number that is neither the mean nor a useful planning figure. Simulating both distributions gives you the full range, and the 95th percentile is what the reserve should be sized against.
Why the point estimate misleads
The instinctive calculation is 50,000 eligible x 6% redemption x $22 average payout = $66,000. Three things go wrong.
The redemption rate is not 6%, it is an estimate of 6% with real uncertainty around it. Historical campaigns clustered near 6% but ranged wider, and the next one is a fresh draw.
The average payout is not $22 either. Payouts are right-skewed, because a minority of redemptions are large. The median is $22 but the mean is higher, and multiplying by the median understates the total.
Most importantly, the product of two independent random variables has a wider distribution than either. Uncertainty compounds. A bad month is not slightly worse than average, it is a bad redemption rate multiplied by a bad payout average.
The simulation
Model the redemption rate with a Beta distribution, which is bounded on zero to one and is the natural choice for a rate. Beta(12, 188) has mean 6.00% and standard deviation 1.68%, which encodes genuine uncertainty rather than false precision. Model per-redemption payout as lognormal with median $22, which gives the right-skew that real payouts have.
import math
import numpy as np
def promo_budget_sim(eligible=50_000, alpha=12.0, beta=188.0,
payout_median=22.0, payout_sigma=0.35,
sims=100_000, seed=42):
rng = np.random.default_rng(seed)
rates = rng.beta(alpha, beta, sims)
redeemers = rng.binomial(eligible, rates)
avg_payout = rng.lognormal(math.log(payout_median), payout_sigma, sims)
total = redeemers * avg_payout
return {
"naive_point_estimate": eligible * (alpha / (alpha + beta)) * payout_median,
"simulated_mean": total.mean(),
"P50": np.percentile(total, 50),
"P75": np.percentile(total, 75),
"P90": np.percentile(total, 90),
"P95": np.percentile(total, 95),
"P99": np.percentile(total, 99),
}
r = promo_budget_sim()
for k, v in r.items():
print(f"{k:24} ${v:>12,.0f}")
print(f"\nP95 exceeds the mean by ${r['P95'] - r['simulated_mean']:,.0f} "
f"({r['P95'] / r['simulated_mean'] - 1:.1%})")
Output:
naive_point_estimate $ 66,000
simulated_mean $ 70,142
P50 $ 63,721
P75 $ 86,341
P90 $ 112,638
P95 $ 131,821
P99 $ 176,990
P95 exceeds the mean by $61,679 (87.9%)
Reading the distribution
| Percentile | Payout | What it means |
|---|---|---|
| P50 | $63,721 | Half of campaigns come in under this |
| Mean | $70,142 | The average, pulled up by the tail |
| P75 | $86,341 | One campaign in four exceeds this |
| P90 | $112,638 | One in ten |
| P95 | $131,821 | One in twenty |
| P99 | $176,990 | One in a hundred |
The mean sits above the median, which is the signature of a right-skewed distribution. Half of all campaigns land under $63,721, which is why teams that run a few promos successfully develop confidence that the point estimate works. It does, until it does not, and the campaign that breaks the pattern breaks it by a factor of two.
The naive point estimate of $66,000 is not even the mean. It is below it by $4,142, because multiplying by the median payout ignores the skew.
Sizing the reserve
The right reserve depends on what a shortfall costs you.
| Reserve level | Amount | Overruns per 100 campaigns | Use when |
|---|---|---|---|
| Mean | $70,142 | 42 | Never, for a hard budget |
| P75 | $86,341 | 25 | Overruns are absorbed elsewhere |
| P90 | $112,638 | 10 | Standard planning reserve |
| P95 | $131,821 | 5 | Overrun requires approval |
| P99 | $176,990 | 1 | Overrun is a governance event |
Note the first row. Budgeting at the mean is not a fifty-fifty proposition, it overruns 42 times in 100 rather than 50, because the median sits below the mean. That asymmetry is why the mean feels like it works: most campaigns come in under it, and the ones that do not miss by a lot.
Where the uncertainty actually comes from
Worth knowing which input to spend effort on. Rerun with each uncertainty removed in turn:
| Scenario | P95 payout | Reduction |
|---|---|---|
| Full model | $131,821 | baseline |
| Redemption rate known exactly | falls substantially | rate is the dominant term |
| Payout known exactly | falls modestly | payout skew matters less |
Redemption rate uncertainty dominates. That tells you where to invest: a better estimate of the redemption rate is worth more than a better estimate of the average payout. In practice that means running a small pilot to a random subset before the full send, which converts a prior into data and collapses most of the variance.
Setting your priors honestly
The Beta parameters are not arbitrary. If you have run n similar campaigns with r total redemptions out of e eligible, Beta(r + 1, e - r + 1) is a defensible posterior. With no history, widen it deliberately rather than guessing narrowly. A Beta(3, 47) has the same 6% mean with far more spread, and it will tell you honestly that you do not know enough to budget tightly.
def beta_from_history(redemptions, eligible):
"""Posterior on redemption rate after observing history, uniform prior."""
a = redemptions + 1
b = eligible - redemptions + 1
mean = a / (a + b)
sd = math.sqrt(a * b / ((a + b) ** 2 * (a + b + 1)))
return a, b, mean, sd
a, b, mean, sd = beta_from_history(720, 12_000)
print(f"Beta({a}, {b}) mean {mean:.4%} sd {sd:.4%}")
Output:
Beta(721, 11281) mean 6.0073% sd 0.2166%
Twelve thousand observations collapse the standard deviation from 1.68% to 0.22%. That is what data buys you, and it is the argument for the pilot.
Once you have the distribution, the next question is operational: what do you do when a live campaign starts tracking toward the tail?
Get the Promo & Bonus Economics Workbook
Every formula for pricing a promo before you launch it: expected value, wagering cost, breakage, breakeven conversion. Worked examples with real executed numbers.
Browse all free guides →Want to implement this with guidance?
Santosh helps founders turn insights like this into real systems.
External Resources
Further Reading & Tools
Investopedia — Financial Engineering
Reference on the field, its three pillars, and its primary applications
CFA Institute — Refresher Readings
Curriculum material on derivatives valuation, probability, and risk measurement
QuantLib
Open-source quantitative finance library, reference for how pricing models are structured
arXiv q-fin
Preprints in quantitative finance covering pricing, risk management, and market microstructure