Monte Carlo Simulation for Derivative Pricing › Pricing European Options: A Benchmark

Michael BrenndoerferDecember 2, 20255 min read
Quantitative FinanceAll chapters
Part 6 of 15Numerical Methods for Derivative Pricing
Chapter 34Monte Carlo Simulation for Derivative Pricing › Pricing European Options: A Benchmark
Reading section 4 of 13 · Pricing European Options: A Benchmark · 5 minSection 4 of 13 · 5 min

Book contents

Section 4 of 13 · 5 min read

Monte Carlo Simulation for Derivative Pricing › Pricing European Options: A BenchmarkOne section · Sections in this chapter4 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's build our first Monte Carlo pricer from the ground up. We start with European calls because we can validate our results against the Black-Scholes formula. This validation checks the implementation before we apply Monte Carlo to exotic options where no analytical solution exists.

In[2]:
Code
import numpy as np

# Set seed for reproducibility
np.random.seed(42)

# Option parameters
S0 = 100  # Initial stock price
K = 105  # Strike price
T = 1.0  # Time to maturity (1 year)
r = 0.05  # Risk-free rate
sigma = 0.2  # Volatility

First, we compute the analytical Black-Scholes price for comparison. This is our ground truth against which we will measure the accuracy of our Monte Carlo estimates:

In[3]:
Code
import numpy as np
from scipy.stats import norm


def black_scholes_call(S0, K, T, r, sigma):
    """Compute Black-Scholes price for a European call option."""
    d1 = (np.log(S0 / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    call_price = S0 * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
    return call_price


bs_price = black_scholes_call(S0, K, T, r, sigma)
Out[4]:
Console
Black-Scholes call price: $8.0214

The Black-Scholes price is the benchmark for the option's theoretical value. Monte Carlo estimates should converge to this value. Agreement between these estimates and the Black-Scholes price verifies our implementation.

Now let's implement the Monte Carlo estimator. The function generates many random terminal prices, computes the payoff for each, and returns both the estimated price and a measure of statistical uncertainty:

In[5]:
Code
def monte_carlo_european_call(S0, K, T, r, sigma, n_simulations):
    """
    Price a European call option using Monte Carlo simulation.

    Returns the estimated price and standard error.
    """
    # Generate terminal stock prices under risk-neutral measure
    Z = np.random.standard_normal(n_simulations)
    ST = S0 * np.exp((r - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * Z)

    # Compute payoffs
    payoffs = np.maximum(ST - K, 0)

    # Discount expected payoff
    discount_factor = np.exp(-r * T)
    price = discount_factor * np.mean(payoffs)

    # Standard error of the estimate
    std_error = discount_factor * np.std(payoffs) / np.sqrt(n_simulations)

    return price, std_error

Let's run the simulation with varying numbers of paths to observe how the estimate improves with more simulations:

In[6]:
Code
simulation_sizes = [1000, 10000, 100000, 1000000]
results = []

for n in simulation_sizes:
    np.random.seed(42)  # Reset for comparability
    price, se = monte_carlo_european_call(S0, K, T, r, sigma, n)
    error = price - bs_price
    results.append(
        {"n_paths": n, "mc_price": price, "std_error": se, "error": error}
    )
Out[7]:
Console
Black-Scholes price: $8.0214

Paths        MC Price     Std Error    Error       
------------------------------------------------
1,000        $8.0361      $0.4262      $+0.0148
10,000       $8.0240      $0.1326      $+0.0027
100,000      $8.0416      $0.0418      $+0.0203
1,000,000    $8.0072      $0.0132      $-0.0142

The results show how Monte Carlo estimation behaves. As we increase the number of simulations by a factor of 10, the standard error decreases by approximately 10≈3.16\sqrt{10} \approx 3.16. This reflects the fundamental 1/N1/\sqrt{N} convergence rate that governs all Monte Carlo methods. The error in our estimate shrinks with more simulations, but it shrinks slowly. This 1/N1/\sqrt{N} convergence rate is both the blessing and curse of Monte Carlo: convergence is guaranteed by the law of large numbers, but achieving high precision requires many simulations. To halve the error, you must quadruple the computational effort.

Out[9]:
Visualization
Density histogram of simulated terminal stock prices with an overlaid theoretical lognormal curve and vertical reference lines at the 100-dollar initial price and 105-dollar strike.
Distribution of simulated terminal stock prices under risk-neutral GBM. The lognormal shape is evident, with positive skew and a long right tail. The vertical lines mark the initial price and strike price.
Out[10]:
Visualization
Density histogram of call payoffs with a large zero-payoff spike labeled 53.7 percent expire worthless and a thinning right tail of positive payoffs.
Distribution of call option payoffs from Monte Carlo simulation. The large spike at zero represents paths where the option expires out of the money. The right tail shows profitable scenarios where the terminal price exceeds the strike.