21. Monte Carlo and Option Pricing#

21.1. Overview#

Simple probability calculations can be done either

  • with pencil and paper, or

  • by looking up facts about well known probability distributions, or

  • in our heads.

For example, we can easily work out

  • the probability of three heads in five flips of a fair coin

  • the expected value of a random variable that equals 10-10 with probability 1/21/2 and 100100 with probability 1/21/2.

But some probability calculations are very complex.

Complex calculations concerning probabilities and expectations occur in many economic and financial problems.

Perhaps the most important tool for handling complicated probability calculations is Monte Carlo methods.

In this lecture we introduce Monte Carlo methods for computing expectations, with some applications in finance.

We will use the following imports.

import numpy as np
import matplotlib.pyplot as plt
from numpy.random import randn

21.2. An introduction to Monte Carlo#

In this section we describe how Monte Carlo can be used to compute expectations.

21.2.1. Share price with known distribution#

Suppose that we are considering buying a share in some company.

Our plan is either to

  1. buy the share now, hold it for one year and then sell it, or

  2. do something else with our money.

We start by thinking of the share price in one year as a random variable SS.

Before deciding whether or not to buy the share, we need to know some features of the distribution of SS.

For example, suppose the mean of SS is high relative to the price of buying the share.

This suggests we have a good chance of selling at a relatively high price.

Suppose, however, that the variance of SS is also high.

This suggests that buying the share is risky, so perhaps we should refrain.

Either way, this discussion shows the importance of understanding the distribution of SS.

Suppose that, after analyzing the data, we guess that SS is well represented by a lognormal distribution with parameters μ,σ\mu, \sigma .

  • SS has the same distribution as exp(μ+σZ)\exp(\mu + \sigma Z) where ZZ is standard normal.

  • We write this statement as SLN(μ,σ)S \sim LN(\mu, \sigma).

Any good reference on statistics (such as Wikipedia) will tell us that the mean and variance are

ES=exp(μ+σ22) \mathbb E S = \exp \left(\mu + \frac{\sigma^2}{2} \right)

and

VarS=[exp(σ2)1]exp(2μ+σ2) \mathop{\mathrm{Var}} S = [\exp(\sigma^2) - 1] \exp(2\mu + \sigma^2)

So far we have no need for a computer.

21.2.2. Share price with unknown distribution#

But now suppose that we study the distribution of SS more carefully.

We decide that the share price depends on three variables, X1X_1, X2X_2, and X3X_3 (e.g., sales, inflation, and interest rates).

In particular, our study suggests that

S=(X1+X2+X3)p S = (X_1 + X_2 + X_3)^p

where

  • pp is a positive number, which is known to us (i.e., has been estimated),

  • XiLN(μi,σi)X_i \sim LN(\mu_i, \sigma_i) for i=1,2,3i=1,2,3,

  • the values μi,σi\mu_i, \sigma_i are also known, and

  • the random variables X1X_1, X2X_2 and X3X_3 are independent.

How should we compute the mean of SS?

To do this with pencil and paper is hard (unless, say, p=1p=1).

But fortunately there’s an easy way to do this, at least approximately.

This is the Monte Carlo method, which runs as follows:

  1. Generate nn independent draws of X1X_1, X2X_2 and X3X_3 on a computer,

  2. use these draws to generate nn independent draws of SS, and

  3. take the average value of these draws of SS.

This average will be close to the true mean when nn is large.

This is due to the law of large numbers, which we discussed in LLN and CLT.

We use the following values for pp and each μi\mu_i and σi\sigma_i.

n = 1_000_000
p = 0.5
μ_1, μ_2, μ_3 = 0.2, 0.8, 0.4
σ_1, σ_2, σ_3 = 0.1, 0.05, 0.2

21.2.2.1. A routine using loops in python#

Here’s a routine using native Python loops to calculate the desired mean

1ni=1nSiES \frac{1}{n} \sum_{i=1}^n S_i \approx \mathbb E S
%%time

S = 0.0
for i in range(n):
    X_1 = np.exp(μ_1 + σ_1 * randn())
    X_2 = np.exp(μ_2 + σ_2 * randn())
    X_3 = np.exp(μ_3 + σ_3 * randn())
    S += (X_1 + X_2 + X_3)**p
S / n
CPU times: user 3.67 s, sys: 0 ns, total: 3.67 s
Wall time: 3.67 s
2.229789652237379

We can also construct a function that contains these operations:

def compute_mean(n=1_000_000):
    S = 0.0
    for i in range(n):
        X_1 = np.exp(μ_1 + σ_1 * randn())
        X_2 = np.exp(μ_2 + σ_2 * randn())
        X_3 = np.exp(μ_3 + σ_3 * randn())
        S += (X_1 + X_2 + X_3)**p
    return (S / n)

Now let’s call it.

compute_mean()
2.2297518882233383

21.2.3. A vectorized routine#

If we want a more accurate estimate we should increase nn.

But the code above runs quite slowly.

To make it faster, let’s implement a vectorized routine using NumPy.

def compute_mean_vectorized(n=1_000_000):
    X_1 = np.exp(μ_1 + σ_1 * randn(n))
    X_2 = np.exp(μ_2 + σ_2 * randn(n))
    X_3 = np.exp(μ_3 + σ_3 * randn(n))
    S = (X_1 + X_2 + X_3)**p
    return S.mean()
%%time

compute_mean_vectorized()
CPU times: user 80.3 ms, sys: 5.99 ms, total: 86.2 ms
Wall time: 85.8 ms
2.2297150888731245

Notice that this routine is much faster.

We can increase nn to get more accuracy and still have reasonable speed:

%%time

compute_mean_vectorized(n=10_000_000)
CPU times: user 791 ms, sys: 50 ms, total: 841 ms
Wall time: 841 ms
2.2297247427698874

21.3. Pricing a European call option under risk neutrality#

Next we are going to price a European call option under risk neutrality.

Let’s first discuss risk neutrality and then consider European options.

21.3.1. Risk-neutral pricing#

When we use risk-neutral pricing, we determine the price of a given asset according to its expected payoff:

cost = expected benefit \text{cost } = \text{ expected benefit}

For example, suppose someone promises to pay you

  • 1,000,000 dollars if “heads” is the outcome of a fair coin flip

  • 0 dollars if “tails” is the outcome

Let’s denote the payoff as GG, so that

P{G=106}=P{G=0}=12 \mathbb P\left\{G = 10^6 \right\} = \mathbb P\{G = 0\} = \frac{1}{2}

Suppose in addition that you can sell this promise to anyone who wants it.

  • First they pay you PP, the price at which you sell it

  • Then they get GG, which could be either 1,000,000 or 0.

What’s a fair price for this asset (this promise)?

The definition of “fair” is ambiguous, but we can say that the risk-neutral price is 500,000 dollars.

This is because the risk-neutral price is just the expected payoff of the asset, which is

EG=12×106+12×0=5×105 \mathbb E G = \frac{1}{2} \times 10^6 + \frac{1}{2} \times 0 = 5 \times 10^5

21.3.2. A comment on risk#

As suggested by the name, the risk-neutral price ignores risk.

To understand this, consider whether you would pay 500,000 dollars for such a promise.

Would you prefer to receive 500,000 for sure or 1,000,000 dollars with 50% probability and nothing with 50% probability?

At least some readers will strictly prefer the first option — although some might prefer the second.

Thinking about this makes us realize that 500,000 is not necessarily the “right” price — or the price that we would see if there was a market for these promises.

Nonetheless, the risk-neutral price is an important benchmark, which economists and financial market participants try to calculate every day.

21.3.3. Discounting#

Another thing we ignored in the previous discussion was time.

In general, receiving xx dollars now is preferable to receiving xx dollars in nn periods (e.g., 10 years).

After all, if we receive xx dollars now, we could put it in the bank at interest rate r>0r > 0 and receive (1+r)nx (1 + r)^n x in nn periods.

Hence future payments need to be discounted when we consider their present value.

We will implement discounting by

  • multiplying a payment in one period by β<1\beta < 1

  • multiplying a payment in nn periods by βn\beta^n, etc.

The same adjustment needs to be applied to our risk-neutral price for the promise described above.

Thus, if GG is realized in nn periods, then the risk-neutral price is

P=βnEG=βn5×105 P = \beta^n \mathbb E G = \beta^n 5 \times 10^5

21.3.4. European call options#

Now let’s price a European call option.

The option is described by three things:

  1. nn, the expiry date,

  2. KK, the strike price, and

  3. SnS_n, the price of the underlying asset at date nn.

For example, suppose that the underlying is one share in Amazon.

The owner of this option has the right to buy one share in Amazon at price KK after nn days.

If Sn>KS_n > K, then the owner will exercise the option, buy at KK, sell at SnS_n, and make profit SnKS_n - K.

If SnKS_n \leq K, then the owner will not exercise the option and the payoff is zero.

Thus, the payoff is max{SnK,0}\max\{ S_n - K, 0 \}.

Under the assumption of risk neutrality, the price of the option is the expected discounted payoff:

P=βnEmax{SnK,0} P = \beta^n \mathbb E \max\{ S_n - K, 0 \}

Now all we need to do is specify the distribution of SnS_n, so the expectation can be calculated.

Suppose we know that SnLN(μ,σ)S_n \sim LN(\mu, \sigma) and μ\mu and σ\sigma are known.

If Sn1,,SnMS_n^1, \ldots, S_n^M are independent draws from this lognormal distribution then, by the law of large numbers,

Emax{SnK,0}1Mm=1Mmax{SnmK,0} \mathbb E \max\{ S_n - K, 0 \} \approx \frac{1}{M} \sum_{m=1}^M \max \{S_n^m - K, 0 \}

We suppose that

μ = 1.0
σ = 0.1
K = 1
n = 10
β = 0.95

We set the simulation size to

M = 10_000_000

Here is our code

S = np.exp(μ + σ * np.random.randn(M))
return_draws = np.maximum(S - K, 0)
P = β**n * np.mean(return_draws)
print(f"The Monte Carlo option price is approximately {P:3f}")
The Monte Carlo option price is approximately 1.036937

21.4. Pricing via a dynamic model#

In this exercise we investigate a more realistic model for the share price SnS_n.

This comes from specifying the underlying dynamics of the share price.

First we specify the dynamics.

Then we’ll compute the price of the option using Monte Carlo.

21.4.1. Simple dynamics#

One simple model for {St}\{S_t\} is

lnSt+1St=μ+σξt+1 \ln \frac{S_{t+1}}{S_t} = \mu + \sigma \xi_{t+1}

where

  • S0S_0 is lognormally distributed and

  • {ξt}\{ \xi_t \} is IID and standard normal.

Under the stated assumptions, SnS_n is lognormally distributed.

To see why, observe that, with st:=lnSts_t := \ln S_t, the price dynamics become

(21.1)#st+1=st+μ+σξt+1s_{t+1} = s_t + \mu + \sigma \xi_{t+1}

Since s0s_0 is normal and ξ1\xi_1 is normal and IID, we see that s1s_1 is normally distributed.

Continuing in this way shows that sns_n is normally distributed.

Hence Sn=exp(sn)S_n = \exp(s_n) is lognormal.

21.4.2. Problems with simple dynamics#

The simple dynamic model we studied above is convenient, since we can work out the distribution of SnS_n.

However, its predictions are counterfactual because, in the real world, volatility (measured by σ\sigma) is not stationary.

Instead it rather changes over time, sometimes high (like during the GFC) and sometimes low.

In terms of our model above, this means that σ\sigma should not be constant.

21.4.3. More realistic dynamics#

This leads us to study the improved version:

lnSt+1St=μ+σtξt+1 \ln \frac{S_{t+1}}{S_t} = \mu + \sigma_t \xi_{t+1}

where

σt=exp(ht),ht+1=ρht+νηt+1 \sigma_t = \exp(h_t), \quad h_{t+1} = \rho h_t + \nu \eta_{t+1}

Here {ηt}\{\eta_t\} is also IID and standard normal.

21.4.4. Default parameters#

For the dynamic model, we adopt the following parameter values.

default_μ  = 0.0001
default_ρ  = 0.1
default_ν  = 0.001
default_S0 = 10
default_h0 = 0

(Here default_S0 is S0S_0 and default_h0 is h0h_0.)

For the option we use the following defaults.

default_K = 100
default_n = 10
default_β = 0.95

21.4.5. Visualizations#

With st:=lnSts_t := \ln S_t, the price dynamics become

st+1=st+μ+exp(ht)ξt+1 s_{t+1} = s_t + \mu + \exp(h_t) \xi_{t+1}

Here is a function to simulate a path using this equation:

def simulate_asset_price_path(μ=default_μ, S0=default_S0, h0=default_h0, n=default_n, ρ=default_ρ, ν=default_ν):
    s = np.empty(n+1)
    s[0] = np.log(S0)

    h = h0
    for t in range(n):
        s[t+1] = s[t] + μ + np.exp(h) * randn()
        h = ρ * h + ν * randn()

    return np.exp(s)

Here we plot the paths and the log of the paths.

fig, axes = plt.subplots(2, 1)

titles = 'log paths', 'paths'
transforms = np.log, lambda x: x
for ax, transform, title in zip(axes, transforms, titles):
    for i in range(50):
        path = simulate_asset_price_path()
        ax.plot(transform(path))
    ax.set_title(title)

fig.tight_layout()
plt.show()
_images/f7e9b0a79a3199b7dde6e8a5308ad622800f4dc4ef0c2778f8e7ad31f546aaf9.png

21.4.6. Computing the price#

Now that our model is more complicated, we cannot easily determine the distribution of SnS_n.

So to compute the price PP of the option, we use Monte Carlo.

We average over realizations Sn1,,SnMS_n^1, \ldots, S_n^M of SnS_n and appealing to the law of large numbers:

Emax{SnK,0}1Mm=1Mmax{SnmK,0} \mathbb E \max\{ S_n - K, 0 \} \approx \frac{1}{M} \sum_{m=1}^M \max \{S_n^m - K, 0 \}

Here’s a version using Python loops.

def compute_call_price(β=default_β,
                       μ=default_μ,
                       S0=default_S0,
                       h0=default_h0,
                       K=default_K,
                       n=default_n,
                       ρ=default_ρ,
                       ν=default_ν,
                       M=10_000):
    current_sum = 0.0
    # For each sample path
    for m in range(M):
        s = np.log(S0)
        h = h0
        # Simulate forward in time
        for t in range(n):
            s = s + μ + np.exp(h) * randn()
            h = ρ * h + ν * randn()
        # And add the value max{S_n - K, 0} to current_sum
        current_sum += np.maximum(np.exp(s) - K, 0)

    return β**n * current_sum / M
%%time
compute_call_price()
CPU times: user 190 ms, sys: 1 μs, total: 190 ms
Wall time: 190 ms
1204.1987397958637

21.5. Exercises#

Exercise 21.1

We would like to increase MM in the code above to make the calculation more accurate.

But this is problematic because Python loops are slow.

Your task is to write a faster version of this code using NumPy.

Exercise 21.2

Consider that a European call option may be written on an underlying with spot price of $100 and a knockout barrier of $120.

This option behaves in every way like a vanilla European call, except if the spot price ever moves above $120, the option “knocks out” and the contract is null and void.

Note that the option does not reactivate if the spot price falls below $120 again.

Use the dynamics defined in (21.1) to price the European call option.