DEV Community

Cover image for Monte Carlo Cash Forecasting: Simulate Your Runway in 40 Lines of Python
Micheal Slyderink
Micheal Slyderink

Posted on

Monte Carlo Cash Forecasting: Simulate Your Runway in 40 Lines of Python

Arron Bennett, Founder of Bennett Financials

Most cash forecasts are one line. Take this month's MRR, assume average churn, assume average new sales, subtract spend, and draw the line out 24 months. If it stays above zero, the plan is safe. The trouble is that nobody's business runs at the average every month.

A Monte Carlo cash forecast runs the same plan thousands of times, each with different but plausible churn and new revenue, and shows you the spread. Instead of one cash runway number, you get the odds. That's a better input for a hiring decision than any single line.

This post builds one in plain Python for a hypothetical bootstrapped restaurant inventory SaaS at $74,000 of MRR, with monthly churn that has swung between 1.5% and 4%. Every figure is example math. Swap in your own.

Key takeaways

  • A single average-case forecast can look safe while a real share of plausible futures run out of cash.
  • In this example, the average line never drops below $229,152, yet 20.0% of 10,000 simulated paths dip below a $100,000 floor.
  • About 1 in 9 paths, 10.6%, run out of cash entirely.
  • Use the simulation to set triggers for hiring plans, not to predict one number.

What is a Monte Carlo cash forecast?

A Monte Carlo cash forecast simulates your cash balance many times with random but realistic inputs, such as churn and new MRR, and summarises the results as percentiles and probabilities. Instead of asking what cash will be, it asks how often cash falls below the level where you'd have to act. Thousands of runs take seconds in Python.

The inputs come from your own history. If monthly churn has ranged from 1.5% to 4% over the last two years, the simulation draws from that range. If new MRR has averaged about $4,500 a month with good and bad months, it draws around that. The output is a spread of futures, not a set of financial projections you defend line by line.

Percentiles are the language. P50 is the middle outcome. P10 is the outcome that 90% of paths beat, a bad but plausible year. P90 is a good one. For cash planning, P10 matters most.

Why does a single-line forecast hide the risk?

Because averages cancel out the bad runs. On $74,000 of MRR, churn of 1.5% loses $1,110 a month, while 4% loses $2,960. A year that sits near the top of that range is very different from the average, and a one-line financial model never shows it. The bad years are exactly the ones that break runway.

The other thing averages hide is persistence. Churn doesn't bounce randomly between good and bad months. A bad quarter tends to follow a product problem, a competitor or a seasonal dip in the restaurant trade, and it stays bad for a while. The model below draws a churn level for each simulated year, then adds monthly noise around it, so bad years stay bad.

This short isn't about simulation. It's about tracking the numbers before revenue, lead flow, booked calls, no-shows, rebooking and close rate, so a drop in leads warns you about a cash problem weeks ahead. In this model, those are what the new MRR input should come from.

What does the Python look like?

Thirty-eight lines, standard library only. One function simulates a single 24-month path: it draws a churn level and a new-MRR level for that path, then steps month by month, updating MRR and cash. The script runs 10,000 paths, prints percentiles and breach rates, then prints the one-line average forecast for comparison.

The scenario: $310,000 in the bank, $78,000 a month of spend today, and three hires that take spend to $101,000 a month from month four. At today's MRR, that's a jump in burn from $4,000 to $27,000 a month. The fixed seed in Python's random module makes the output repeat exactly, so you can check it.

import random

MRR0, CASH0 = 74_000, 310_000           # today's MRR and cash in the bank
SPEND = [78_000] * 3 + [101_000] * 21   # monthly spend; three hires start in month 4
FLOOR = 100_000                         # cash level where you'd cut or raise
RUNS, MONTHS = 10_000, 24
rng = random.Random(2027)               # fixed seed so the output repeats

def one_path():
    churn_year = rng.uniform(0.015, 0.04)        # good year or bad year
    new_year = rng.gauss(4_500, 900)             # new MRR a month, this path
    mrr, cash, low, breach = MRR0, CASH0, CASH0, None
    for m in range(MONTHS):
        churn = min(max(rng.gauss(churn_year, 0.004), 0.015), 0.04)
        mrr = mrr * (1 - churn) + max(rng.gauss(new_year, 1_200), 0)
        cash += mrr - SPEND[m]
        low = min(low, cash)
        if breach is None and cash < FLOOR:
            breach = m + 1
    return cash, low, breach

usd = lambda x: f"-${-x:,.0f}" if x < 0 else f"${x:,.0f}"
pct = lambda xs, p: sorted(xs)[round(p / 100 * (len(xs) - 1))]
paths = [one_path() for _ in range(RUNS)]
ends = [end for end, _, _ in paths]
breaches = [b for _, _, b in paths if b]

print(f"month-{MONTHS} cash: P10 {usd(pct(ends, 10))}, P50 {usd(pct(ends, 50))}, P90 {usd(pct(ends, 90))}")
print(f"paths that dip below the {usd(FLOOR)} floor: {len(breaches) / RUNS:.1%}")
print(f"paths that run out of cash: {sum(low < 0 for _, low, _ in paths) / RUNS:.1%}")
print(f"median month the floor is hit, when it is: month {pct(breaches, 50)}")

mrr, cash, low = MRR0, CASH0, CASH0     # the one-line spreadsheet forecast
for m in range(MONTHS):
    mrr = mrr * (1 - 0.0275) + 4_500    # average churn, average new MRR
    cash += mrr - SPEND[m]
    low = min(low, cash)
print(f"average-case line: month-{MONTHS} cash {usd(cash)}, lowest {usd(low)}")
Enter fullscreen mode Exit fullscreen mode

Output:

month-24 cash: P10 -$7,002, P50 $337,089, P90 $703,069
paths that dip below the $100,000 floor: 20.0%
paths that run out of cash: 10.6%
median month the floor is hit, when it is: month 14
average-case line: month-24 cash $335,679, lowest $229,152
Enter fullscreen mode Exit fullscreen mode

What do the percentiles say about this business?

The average case says the hires are safe. The simulation says they're safe most of the time. The one-line forecast ends month 24 with $335,679 and never drops below $229,152. Yet 20.0% of paths dip below the $100,000 floor, 10.6% run out of cash, and the floor is usually hit around month 14.

Read the three percentiles together:

  • P50 is $337,089, almost exactly the average line. The middle future really does look like the spreadsheet.
  • P90 is $703,069. In a good year, the hires pay for themselves quickly.
  • P10 is -$7,002. In the worst tenth of futures, the company is out of cash by month 24.

That spread is the whole point. A founder looking only at the average line would make the three hires at once without a second thought. A founder looking at the simulation sees a one-in-five chance of a painful year and can plan for it now.

The floor breach month matters as much as the probability. If trouble typically shows up around month 14, you have about ten months after the hires start to see it coming. That's plenty of time, if you're watching the right numbers.

How do you use the simulation to make decisions?

Use it to set triggers, not to predict one number. Pick the cash floor you'd act at, run the simulation for each version of your plan, and choose the version where the chance of breaching the floor is one you can live with. Then write down the early signal that tells you which path you're on.

For this business, three practical moves:

  • Stage the hiring plans. Change SPEND so one hire starts in month four and the other two follow later, then re-run and compare the breach rate.
  • Set a churn trigger. If trailing three-month churn runs above 3%, pause the next hire. That's the signal you're on one of the bad paths.
  • Agree the floor in advance. Decide now what you'd do at $100,000: cut spend, raise funds, or draw a line of credit. Decisions made before the floor are cheaper than ones made at it.

A few modelling notes. Cash here equals MRR collected in the same month, which flatters annual plans and late payers. Spend is fixed, which is fine for payroll and most tooling. If your billing system exports monthly churn and new MRR, use the real distribution instead of a uniform range, and keep the same structure.

I've written more on stress-testing cash flow and a practical guide to building a cash flow forecast if you want the deterministic version first.

What should you do on Monday?

Export 24 months of monthly churn and new MRR from your billing system. Note the lowest and highest churn months and the average new MRR. Paste them into the script with your cash, spend and planned hires, pick a floor, and run it. If more than one path in five breaches, stage the plan.

FAQ

How many simulation runs do you need?

For planning, 10,000 is plenty. The percentiles barely move beyond that. Fewer than about 1,000 and the tails get noisy.

Is a Monte Carlo cash forecast better than scenario planning?

It's scenario planning with more scenarios. Best, base and bad cases are three hand-picked paths. A simulation shows how likely the bad case is, which three scenarios can't.

Why use a fixed random seed?

So the output repeats. Remove the seed when you want fresh draws, but keep one in any version you share with a co-founder or potential investors, so you're all looking at the same numbers.

Top comments (0)