Quant Finance Foundations, Part 11 — the final post in this series. Previously: Historical vs Implied Volatility.

Monte Carlo option pricing is the third and most general way to value a derivative. This series has already priced an option with a binomial tree and with the Black-Scholes formula. Simulation now completes the set.

Moreover, it is the method that matters most in production. After all, it is the only one that scales to payoffs too complicated for a formula or a tree. The idea itself is almost embarrassingly simple: simulate many possible futures, compute the payoff in each, average them, and discount.

Monte Carlo option pricing code running on a laptop screen in Python
Photo by Arnold Francisca on Unsplash

The Monte Carlo option pricing recipe

  1. Simulate a large number of possible price paths for the underlying, under the risk-neutral measure.
  2. Compute the option’s payoff at expiry along each path.
  3. Average those payoffs.
  4. Discount the average back to today at the risk-free rate.
  5. Report a standard error alongside the price.

Step 1 carries the one real subtlety. The simulation uses the risk-neutral drift r, not the stock’s real expected return. The reason goes back to the binomial tree, where the replication argument made the real probability irrelevant. Consequently, substituting your own return forecast here produces a number that is not a price.

Generating a path

Under geometric Brownian motion, the price at expiry follows from the exact solution to the SDE covered in the post on Brownian motion and Itô’s lemma:

ST = S₀ · exp[(r − σ²/2)T + σ√T · Z]

Here Z is a standard normal draw. Notice that the −σ²/2 term is the Itô correction, appearing exactly where that post said it would.

Because this is an exact solution, a European option needs no time-stepping at all. One normal draw per path lands you directly at expiry. Path-dependent options, however, do require stepping through time, since their payoff depends on the route rather than the destination.

The code

import numpy as np
from scipy.stats import norm

def monte_carlo_call(S0, K, r, sigma, T, n_paths=1_000_000, seed=42):
    rng = np.random.default_rng(seed)
    Z = rng.standard_normal(n_paths)
    ST = S0 * np.exp((r - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * Z)
    payoffs = np.maximum(ST - K, 0.0)
    price = np.exp(-r * T) * payoffs.mean()
    stderr = np.exp(-r * T) * payoffs.std(ddof=1) / np.sqrt(n_paths)
    return price, stderr

def black_scholes_call(S0, K, r, sigma, T):
    d1 = (np.log(S0 / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    return S0 * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)

S0, K, r, sigma, T = 100, 105, 0.05, 0.20, 1.0

mc, se = monte_carlo_call(S0, K, r, sigma, T)
bs = black_scholes_call(S0, K, r, sigma, T)

print(f"Monte Carlo:   {mc:.4f}  (standard error {se:.4f})")
print(f"Black-Scholes: {bs:.4f}")
print(f"Difference:    {abs(mc - bs):.4f}")

That is roughly forty lines including the analytic cross-check. Running it puts the two prices within a couple of thousandths of each other, and the difference sits comfortably inside the reported standard error.

Always print that standard error. A Monte Carlo price without an error estimate is a number of unknown quality, and reporting one without the other is a habit worth refusing to develop.

Laptop running a simulation, illustrating convergence testing in Monte Carlo option pricing
Photo by Bernd 📷 Dittrich on Unsplash

Convergence, and why it is slow

Monte Carlo error shrinks with 1/√n. That is the same square root that governs volatility scaling, and it is punishing. To halve the error you need four times the paths; to add a decimal place, a hundred times.

Paths Approximate standard error Relative cost
1,000 0.25 1×
10,000 0.08 10×
100,000 0.025 100×
1,000,000 0.008 1,000×

Consequently, production systems invest heavily in variance reduction. Three techniques do most of the work:

  • Antithetic variates. For every draw Z, also use −Z. The negative correlation between paired paths cuts variance at no extra sampling cost.
  • Control variates. Simulate alongside a related instrument whose exact price you know, then use the simulation error on the known one to correct the unknown one. This works especially well for Asian options, where a geometric-average version has a closed form.
  • Quasi-random sequences. Replace pseudo-random numbers with low-discrepancy sequences such as Sobol, which cover the space more evenly. As a result, convergence improves to nearly 1/n for well-behaved problems.

Why bother, when a formula exists

For a European call, Monte Carlo is strictly worse than Black-Scholes — slower and less accurate. Its value therefore appears exactly where the formula runs out.

Situation Tree Closed form Monte Carlo
European option Good Best Works, but wasteful
American early exercise Best No Awkward
Path-dependent payoff Poor Rarely Best
Many underlyings Poor No Best
Structured note logic No No Best
XVA and exposure profiles No No Best

Path-dependent payoffs are the obvious case. Asian options on an average price, barriers that knock in or out, lookbacks on the maximum — simulation handles all of these by construction, because it generates the whole path anyway.

Multiple underlyings are the second case. Basket options, rainbow options and quanto structures overwhelm tree methods as dimensions increase, whereas Monte Carlo barely notices. Simulating correlated assets requires only a Cholesky decomposition of the correlation matrix.

Finally, there is risk. Counterparty exposure profiles, CVA, potential future exposure and most stress-testing frameworks are simulation exercises at heart. In fact, this is where the bulk of Monte Carlo compute in a bank actually goes.

The known weakness is early exercise. American and Bermudan options need an optimal-stopping decision at each date, which does not fit the forward-simulation structure naturally. The standard workaround is the Longstaff-Schwartz least-squares method, which regresses continuation values across paths. It is effective, though considerably more delicate than it looks.

Closing the loop

Price the same option three ways and all three should agree to within numerical tolerance. That agreement is the point of the whole series.

Three methods that look nothing alike — a discrete tree, a closed-form expression, brute-force simulation — converge on one number, because all three implement a single idea: a derivative is worth the cost of the portfolio that replicates it. Everything else is technique.

Frequently asked questions

Why does Monte Carlo option pricing use the risk-free rate as the drift?

Because the price comes from replication rather than forecasting. The risk-neutral measure encodes that argument, so the real expected return never enters the calculation.

How many paths are enough?

That depends on the tolerance you need. One million paths typically gives a standard error under one cent, whereas ten thousand leaves you with several cents of noise.

Why is Monte Carlo slow to converge?

Error falls with 1/√n, so each additional decimal place costs a hundred times more paths. Variance reduction techniques exist precisely to soften that cost.

Can Monte Carlo handle American options?

Yes, but awkwardly. The Longstaff-Schwartz least-squares method regresses continuation values across paths, though a binomial tree is often simpler for the same job.

Try it yourself

Build and validate your own pricer, in this order:

  1. Run the code above and confirm the Monte Carlo price matches Black-Scholes within the standard error.
  2. Drop n_paths to 1,000 and watch the error widen roughly as the table predicts.
  3. Add antithetic variates by concatenating Z with −Z, then compare the standard error at equal cost.
  4. Extend the simulation to 252 daily steps instead of one jump to expiry.
  5. Use those paths to price an Asian option on the average price — a payoff no closed form handles.

Where to go from here

The foundations are now in place, and two branches follow naturally. The first is risk: Value-at-Risk, Expected Shortfall, stress testing, and from there credit risk and expected credit loss modelling for banks. The second is honest backtesting: survivorship bias, look-ahead bias, transaction costs, and the deflated Sharpe ratio.

Both use everything built here. The discounting from the present value post, the yield curve, the replication logic from the binomial tree, and the simulation machinery from this post are the shared vocabulary of the rest of the field.

If you are arriving here without the earlier posts, the introduction to quantitative finance explains where all of this sits, and the post on the option Greeks covers what a desk does with these prices once it has them.

If there is a direction you would like covered first, leave a comment.