Exotic Options and Complex Derivatives › Pricing Exotic Options with Monte Carlo
Quantitative FinanceAll chapters Exotic Options and Complex Derivatives › Pricing Exotic Options with Monte CarloOne section · Sections in this chapter4 / 10 ↓
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
When closed-form solutions aren't available, or when you want to handle complex features like multiple barriers, partial averaging windows, or general path dependencies, Monte Carlo simulation provides a flexible framework. The general approach involves:
- Simulate many paths of the underlying price under the risk-neutral measure
- Calculate the payoff for each path based on the exotic option's rules
- Discount the average payoff to obtain the price
Let's implement an exotic option pricer using Monte Carlo:
import numpy as np
class ExoticMonteCarlo:
"""
Monte Carlo pricer for various exotic options.
"""
def __init__(self, S0, r, sigma, T, n_steps=252, n_paths=100000, seed=42):
self.S0 = S0
self.r = r
self.sigma = sigma
self.T = T
self.n_steps = n_steps
self.n_paths = n_paths
np.random.seed(seed)
# Pre-generate paths (more efficient for pricing multiple options)
self.paths = self._generate_paths()
def _generate_paths(self):
"""Generate price paths under risk-neutral measure."""
dt = self.T / self.n_steps
Z = np.random.standard_normal((self.n_paths, self.n_steps))
drift = (self.r - 0.5 * self.sigma**2) * dt
diffusion = self.sigma * np.sqrt(dt)
log_returns = drift + diffusion * Z
log_prices = np.concatenate(
[
np.full((self.n_paths, 1), np.log(self.S0)),
np.log(self.S0) + np.cumsum(log_returns, axis=1),
],
axis=1,
)
return np.exp(log_prices)
def price_option(self, payoff_func):
"""Price option given a payoff function that operates on paths."""
payoffs = payoff_func(self.paths)
price = np.exp(-self.r * self.T) * np.mean(payoffs)
std_error = (
np.exp(-self.r * self.T) * np.std(payoffs) / np.sqrt(self.n_paths)
)
return price, std_error
def asian_call_payoff(self, K, avg_type="arithmetic"):
"""Payoff function for Asian call."""
def payoff(paths):
if avg_type == "arithmetic":
avg = np.mean(paths[:, 1:], axis=1) # Exclude S0 from average
else:
avg = np.exp(np.mean(np.log(paths[:, 1:]), axis=1))
return np.maximum(avg - K, 0)
return payoff
def barrier_call_payoff(self, K, H, barrier_type="down-and-out"):
"""Payoff function for barrier call."""
def payoff(paths):
S_T = paths[:, -1]
if barrier_type == "down-and-out":
barrier_hit = np.any(paths < H, axis=1)
vanilla_payoff = np.maximum(S_T - K, 0)
return np.where(barrier_hit, 0, vanilla_payoff)
elif barrier_type == "up-and-out":
barrier_hit = np.any(paths > H, axis=1)
vanilla_payoff = np.maximum(S_T - K, 0)
return np.where(barrier_hit, 0, vanilla_payoff)
elif barrier_type == "down-and-in":
barrier_hit = np.any(paths < H, axis=1)
vanilla_payoff = np.maximum(S_T - K, 0)
return np.where(barrier_hit, vanilla_payoff, 0)
elif barrier_type == "up-and-in":
barrier_hit = np.any(paths > H, axis=1)
vanilla_payoff = np.maximum(S_T - K, 0)
return np.where(barrier_hit, vanilla_payoff, 0)
return payoff
def lookback_call_payoff(self, strike_type="floating"):
"""Payoff function for lookback call."""
def payoff(paths):
S_T = paths[:, -1]
S_min = np.min(paths, axis=1)
S_max = np.max(paths, axis=1)
if strike_type == "floating":
return S_T - S_min # Always positive
else:
return np.maximum(S_max - self.S0, 0) # Fixed strike at S0
return payoff# Create Monte Carlo pricer (parameters explicitly set)
mc = ExoticMonteCarlo(
S0=100, r=0.05, sigma=0.20, T=1.0, n_steps=252, n_paths=100000
)
# Asian options
arith_asian, arith_se = mc.price_option(mc.asian_call_payoff(100, "arithmetic"))
geo_asian, geo_se = mc.price_option(mc.asian_call_payoff(100, "geometric"))
# Barrier options
do_barrier, do_se = mc.price_option(
mc.barrier_call_payoff(100, 90, "down-and-out")
)
uo_barrier, uo_se = mc.price_option(
mc.barrier_call_payoff(100, 120, "up-and-out")
)
# Lookback options
float_lb, float_se = mc.price_option(mc.lookback_call_payoff("floating"))
fixed_lb, fixed_se = mc.price_option(mc.lookback_call_payoff("fixed"))Monte Carlo Exotic Option Prices (S0=100, K=100, T=1yr) ======================================================= Asian Call (arithmetic avg): $5.7592 ± $0.0494 Asian Call (geometric avg): $5.5445 ± $0.0477 Down-and-out Call (H=90): $8.8635 ± $0.0905 Up-and-out Call (H=120): $1.3384 ± $0.0212 Floating Strike Lookback: $16.5870 ± $0.0898 Fixed Strike Lookback: $18.2766 ± $0.0948
These Monte Carlo prices confirm the patterns we observed in the closed-form solutions. The arithmetic Asian call is priced at $5.7592 versus $5.5445 for the geometric Asian, an arithmetic-average premium of about 3.9%. Both estimates use the same 100,000 risk-neutral paths, 252 daily monitoring points, and 5% annual discount rate; their 95% confidence-interval half-widths are about $0.05. The remaining estimates show how strongly each payoff definition responds to its path condition: the barrier contracts lose value when paths knock out, while lookback contracts retain value from the most favorable price observed along each path.
Let's visualize the payoff distributions for different exotic option types:



Notice the distinctive shapes: the Asian option has a compressed distribution due to averaging, the barrier option shows a large mass at zero from knocked-out paths, and the lookback option has a strictly positive distribution starting above zero.