← Back to Blog
Quantitative MarketingPromo EconomicsFinancial EngineeringPython

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.

SPSantosh Paudel· July 29, 2026· 9 min read
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

PercentilePayoutWhat it means
P50$63,721Half of campaigns come in under this
Mean$70,142The average, pulled up by the tail
P75$86,341One campaign in four exceeds this
P90$112,638One in ten
P95$131,821One in twenty
P99$176,990One 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 levelAmountOverruns per 100 campaignsUse when
Mean$70,14242Never, for a hard budget
P75$86,34125Overruns are absorbed elsewhere
P90$112,63810Standard planning reserve
P95$131,8215Overrun requires approval
P99$176,9901Overrun 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:

ScenarioP95 payoutReduction
Full model$131,821baseline
Redemption rate known exactlyfalls substantiallyrate is the dominant term
Payout known exactlyfalls modestlypayout 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?

Free resource

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.

No spam. Unsubscribe anytime.

Browse all free guides →

Want to implement this with guidance?

Santosh helps founders turn insights like this into real systems.

Promo Economics Audit

External Resources

Further Reading & Tools

Related Posts

01
9 min
Risk ManagementPromo Economics
TodayQuantitative Marketing

Value at Risk and Kill Switches for Live Promotions

A spend cap set at expected payout shuts down 46% of perfectly healthy campaigns. The right multiple is 1.52x. Sizing exposure limits and automated kill switches against a real payout distribution.

Read article
02
8 min
Incentive DesignBehavioral Economics
TodayQuantitative Marketing

The Line Between Motivating and Manipulating: An Incentive Design Ethics Test

Every mechanic in this series works because of a cognitive bias. That makes them powerful and makes some of them indefensible. A six-question test for which side of the line your design sits on, and what regulators have already acted against.

Read article
03
8 min
Price DiscriminationPricing
TodayQuantitative Marketing

Why Promo Codes Beat Sales: Price Discrimination in Practice

A blanket 20% discount earns $20,000 less than no discount at all. The same discount behind a code that only bargain hunters bother to find earns $64,000 more. The friction is the entire mechanism.

Read article