Monte Carlo Simulation for Derivative Pricing › Pricing European Options: A Benchmark
Quantitative FinanceAll chapters 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.
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 # VolatilityFirst, 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:
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)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:
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_errorLet's run the simulation with varying numbers of paths to observe how the estimate improves with more simulations:
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}
)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 . This reflects the fundamental convergence rate that governs all Monte Carlo methods. The error in our estimate shrinks with more simulations, but it shrinks slowly. This 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.

