Exotic Options and Complex Derivatives › Pricing Exotic Options with Monte Carlo

Michael BrenndoerferDecember 5, 20257 min read
Quantitative FinanceAll chapters
Part 6 of 15Numerical Methods for Derivative Pricing
Chapter 37Exotic Options and Complex Derivatives › Pricing Exotic Options with Monte Carlo
Reading section 4 of 10 · Pricing Exotic Options with Monte Carlo · 7 minSection 4 of 10 · 7 min

Book contents

Section 4 of 10 · 7 min read

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:

  1. Simulate many paths of the underlying price under the risk-neutral measure
  2. Calculate the payoff for each path based on the exotic option's rules
  3. Discount the average payoff to obtain the price

Let's implement an exotic option pricer using Monte Carlo:

In[30]:
Code
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
In[31]:
Code
# 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"))
Out[32]:
Console
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:

Out[33]:
Visualization
Histogram of arithmetic Asian call payoffs from 100,000 Monte Carlo paths. A large probability mass sits at zero and positive payoffs form a long right tail. A coral dashed vertical line marks the raw mean payoff at $6.05.
Distribution of arithmetic Asian call payoffs from 100,000 Monte Carlo paths. A large mass at zero comes from paths whose arithmetic average finishes below the $100 strike; positive payoffs form a right-skewed tail. The dashed line marks the undiscounted mean payoff of about $6.05. Multiplying the underlying mean of $6.0545 by the one-year discount factor exp(-0.05) gives the reported $5.7592 option price.
Out[34]:
Visualization
Histogram of down-and-out barrier call payoffs from 100,000 Monte Carlo paths. A large spike at zero is followed by a long right-skewed tail of positive payoffs. A coral dashed vertical line marks the conditional mean of positive payoffs at $22.09.
Distribution of down-and-out barrier call payoffs from 100,000 Monte Carlo paths. The dominant zero-payoff spike combines paths knocked out at the $90 barrier with surviving paths that finish below the $100 strike. Positive payoffs form a long right tail. The dashed line marks $22.09, the mean conditional on a positive payoff; it is not the unconditional $6.15 discounted option price reported above.
Out[35]:
Visualization
Histogram of floating-strike lookback call payoffs from 100,000 Monte Carlo paths. The distribution starts near zero and declines through a long right tail. A coral dashed vertical line marks the raw mean payoff at $17.44, and there is no zero-payoff spike.
Distribution of floating-strike lookback call payoffs from 100,000 Monte Carlo paths. Payoffs are concentrated at lower positive values and decline into a long right tail because each path's payoff is its terminal price minus its own observed minimum. The dashed line marks the raw mean payoff of $17.44. The absence of a zero spike contrasts with the Asian and barrier examples, although payoffs can approach zero when the terminal price is close to the path minimum.

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.