You wrote a Monte Carlo pricer, ran it against the Black-Scholes formula as a sanity check, and the numbers don’t match. This is the most common rite of passage in computational finance, and the good news is that the cause is almost always one of a short list of bugs.

Here’s how to diagnose it, worked through each likely culprit with the code that produces the error — so you can spot which one is yours.

First: is it actually wrong, or just noise?

Before hunting for bugs, rule out the innocent explanation. A Monte Carlo price is a random estimate, and it comes with a standard error. If your price is within about two standard errors of Black-Scholes, nothing is wrong — that’s the method working as designed.

So the first thing to compute is not just the price but the error bar:

import numpy as np
from scipy.stats import norm

S0, K, r, sigma, T = 100.0, 100.0, 0.05, 0.20, 1.0
n = 500_000

d1 = (np.log(S0/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
bs = S0*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)

np.random.seed(0)
Z  = np.random.standard_normal(n)
ST = S0*np.exp((r - 0.5*sigma**2)*T + sigma*np.sqrt(T)*Z)
payoff = np.exp(-r*T) * np.maximum(ST - K, 0)

price = payoff.mean()
se    = payoff.std() / np.sqrt(n)

print(f"Black-Scholes: {bs:.4f}")
print(f"Monte Carlo:   {price:.4f}  (SE {se:.4f})")
print(f"Difference:    {abs(price-bs):.4f}  =  {abs(price-bs)/se:.1f} standard errors")
Black-Scholes: 10.4506
Monte Carlo:   10.4776  (SE 0.0208)
Difference:    0.0270  =  1.3 standard errors

1.3 standard errors away. That’s fine — well inside noise. If your difference is a fraction of a standard error or a small multiple of it, stop looking for bugs; you don’t have one. If it’s ten or fifty standard errors out, or the SE itself is huge, read on.

The single most common false alarm: too few paths. With 200 simulations instead of 500,000, the same correct code gives a price of 11.87 with a standard error of 1.11 — a massive error bar that makes it look broken when it’s just imprecise. Always check the SE before assuming a bug.

Bug 1: the missing Ito correction

This is the number one bug. The exact solution for the terminal stock price is:

    \[S_T = S_0 \exp\left[\left(r - \frac{1}{2}\sigma^2\right)T + \sigma\sqrt{T}\,Z\right]\]

That -\frac{1}{2}\sigma^2 term is not optional, and it’s not the discount. It’s the Ito correction — the gap between the arithmetic drift and the geometric drift of a lognormal process. Leave it out and your stock drifts too high, so your call comes out too expensive:

ST = S0*np.exp(r*T + sigma*np.sqrt(T)*Z)   # missing -0.5*sigma^2
price = np.exp(-r*T)*np.maximum(ST-K,0).mean()
# -> 11.8050, versus 10.45 target

If your MC price is a bit too high and you can’t see why, this is the first thing to check. The drift inside the exponential must be r - \frac{1}{2}\sigma^2, not r.

Bug 2: forgetting to discount

The option’s value today is the discounted expected payoff. Drop the e^{-rT} and every price is inflated by that factor:

ST = S0*np.exp((r-0.5*sigma**2)*T + sigma*np.sqrt(T)*Z)
price = np.maximum(ST-K,0).mean()          # no discounting
# -> 11.0148, too high by exactly e^{rT} = 1.0513x

The tell for this one: your price is too high by a factor of roughly e^{rT}. Divide your answer by e^{rT} and if it suddenly matches, you found it.

Bug 3: scaling volatility by T instead of sqrt(T)

Volatility scales with the square root of time. The diffusion term is \sigma\sqrt{T}\,Z, not \sigma T\,Z. This one hides when T = 1 (because \sqrt{1} = 1) and bites everywhere else:

# with T = 0.5
ST = S0*np.exp((r-0.5*sigma**2)*T + sigma*T*Z)   # T, should be sqrt(T)
# -> 5.0050, versus correct 6.89

If your pricer passes at one-year maturity but fails at others, this is almost certainly it. Testing at T \neq 1 is exactly why you should never sanity-check only at a single maturity.

Bug 4: using the real-world drift

Under the risk-neutral measure, the stock drifts at the risk-free rate r — not at its real expected return \mu. This is a conceptual bug rather than a typo, and it produces a large error:

mu = 0.10
ST = S0*np.exp((mu-0.5*sigma**2)*T + sigma*np.sqrt(T)*Z)
# -> 13.9855, badly too high

If you find yourself feeding the stock’s expected return into a pricer, the fix is one character — swap \mu for r — but the reason matters. Pricing happens under the risk-neutral measure, where the drift is always r.

The diagnostic checklist

Run through these in order and you’ll find almost any discrepancy:

  • Check the standard error first. Within ~2 SE of Black-Scholes? You have no bug. Increase paths if the SE is large.
  • Price too high by a small amount? Missing Ito correction (-\frac{1}{2}\sigma^2).
  • Price too high by a factor of e^{rT}? Missing discount.
  • Passes at T=1 but fails elsewhere? Volatility scaled by T instead of \sqrt{T}.
  • Wildly too high? Real-world drift \mu instead of r.
  • Still off? Check for a strike/spot mix-up, a sign error in the payoff, or seeding the RNG inside a loop so you’re reusing the same draws.

The habit worth building

Notice what made every one of these findable: we had a benchmark. Black-Scholes gave us a number we trusted, so any deviation was a signal. That’s the whole point of testing a pricer against a closed form before you use it on something exotic — for a vanilla call you can check, so you earn the right to trust the code on payoffs where you can’t.

Two more checks that catch bugs even when you don’t have a closed form: put-call parity, which must hold regardless of model, and the martingale test — the discounted stock price should average back to today’s spot under the risk-neutral measure. If your simulated stock fails that, the bug is in the path generation itself, before any payoff is even applied.

If you want the full treatment — how to build a Monte Carlo pricer properly, benchmark it, put honest error bars on it, and validate it with a complete test suite — that’s a core part of Risk-Neutral Valuation, built line by line from the pricing theory up.


Get the Quant Reference Card — four pages covering stochastic calculus, measure change, the models you’ll actually meet, discretisation, rates, credit and XVA, with the practitioner notes that don’t make it into textbooks. Free.