In this tutorial we will investigate the Monte Carlo simulation method for use in valuing financial derivatives. The mathematic notation and examples are from Les Clewlow and Chris Strickland’s book Implementing Derivatives Models.
Valuation of Financial Derivatives through Monte Carlo Simulations is only possible by using the Financial Mathematics of Risk-Neutral Pricing and simulating risk-neutral asset paths.
\(\begin{equation}\LARGE \frac{C_t}{B_t} = \mathbb{E}_{\mathbb{Q}}[\frac{C_T}{B_T}\mid F_t]\end{equation}\)
Note: This is the Risk-neutral Expectation Pricing Formula in Continuous Time
# Import dependencies import time import numpy as np import matplotlib.pyplot as plt
Geometric Brownian Motion Dynamics
Here we have constant interest rate so the discount factor is \(\exp(-rT)\), and the stock dynamics are modelled with Geometric Brownian Motion (GBM).
\(\large dS_t = rS_tdt+\sigma S_tdW_t\)
In terms of the stock price S, we have the following solution under risk-neutral dynamics:
\(\large S_{t+\Delta t} = S_{t} \exp( \nu \Delta t + \sigma \sqrt{\Delta t} \epsilon_i )\)
Where, \(\nu = r – \frac{1}{2} \sigma ^ 2\)
Key Differences for Path-Dependent Options
In pricing complex or exotic path dependent options, a popular product is the barrier option. These are standard European option expiration style options, however the options cease to exist or only comes into existence if the underlying price crosses a pretermined barrier level.
This barrier level can have either continuous or discrete barrier monitoring \(\large \tau\).
For an up-and-out barrier put option: \(C_T = f(S_T) = (K-S_T)^{+}Ind{(\max \limits_{t \in \tau} S_t < H)}\)
In other words for all simulations \(m \in M\):
– if \(t \in \tau\) and \(S_t \geq H\) then \(C_T = 0 \)
– else if \(t \notin \tau\) or \(S_t < H\) then \(C_T = (K-S_T)^{+}\)
# Initialise parameters S0 = 100 # initial stock price K = 100 # strike price T = 1 # time to maturity in years H = 125 # up-and-out barrier price/value r = 0.01 # annual risk-free rate vol = 0.2 # volatility (%) N = 100 # number of time steps M = 1000 # number of simulations
Slow Solution – Steps
Here we simulate stock price \(S_t\) directly because we require this value during the calculation to compare to the barrier \(H\).
start_time = time.time() # Precompute constants dt = T/N nudt = (r - 0.5*vol**2)*dt volsdt = vol*np.sqrt(dt) erdt = np.exp(r*dt) # Standard Error Placeholders sum_CT = 0 sum_CT2 = 0 # Monte Carlo Method for i in range(M): # Barrier Crossed Flag BARRIER = False St = S0 for j in range(N): epsilon = np.random.normal() Stn = St*np.exp( nudt + volsdt*epsilon ) St = Stn if St >= H: BARRIER = True break if BARRIER: CT = 0 else: CT = max(0, K - St) sum_CT = sum_CT + CT sum_CT2 = sum_CT2 + CT*CT # Compute Expectation and SE C0 = np.exp(-r*T)*sum_CT/M sigma = np.sqrt( (sum_CT2 - sum_CT*sum_CT/M)*np.exp(-2*r*T) / (M-1) ) SE = sigma/np.sqrt(M) print("Call value is ${0} with SE +/- {1}".format(np.round(C0,2),np.round(SE,3))) print("Computation time is: ", round(time.time() - start_time,4))
Fast Implementation – Vectorized with Numpy
np.any() is a function that returns True when ndarray passed to the first parameter contains at least one True element, and returns False otherwise
start_time = time.time() #precompute constants dt = T/N nudt = (r - 0.5*vol**2)*dt volsdt = vol*np.sqrt(dt) erdt = np.exp(r*dt) # Monte Carlo Method Z = np.random.normal(size=(N, M)) delta_St = nudt + volsdt*Z ST = S0*np.cumprod( np.exp(delta_St), axis=0) ST = np.concatenate( (np.full(shape=(1, M), fill_value=S0), ST ) ) # Copy numpy array for plotting S = np.copy(ST) # Apply Barrier Condition to ST numpy array mask = np.any(ST >= H, axis=0) ST[:,mask] = 0 CT = np.maximum(0, K - ST[-1][ST[-1] != 0]) C0 = np.exp(-r*T)*np.sum(CT)/M sigma = np.sqrt( np.sum( (np.exp(-r*T)*CT - C0)**2) / (M-1) ) sigma = np.std(np.exp(-r*T)*CT) SE = sigma/np.sqrt(M) print("Call value is ${0} with SE +/- {1}".format(np.round(C0,2),np.round(SE,3))) print("Computation time is: ", round(time.time() - start_time,4))
Visualisation
plt.figure(figsize=(8,6)) plt.rcParams["font.family"] = "Times New Roman" plt.rcParams["font.size"] = "16" plt.plot(np.linspace(0,T,N+1),S[:,mask],'r') plt.plot(np.linspace(0,T,N+1),S[:,~mask],'g') plt.plot([0,T],[H,H], 'k-',linewidth=5.0) plt.annotate('H', (0.05,130)) plt.xlim(0,1) plt.xlabel('Time') plt.ylabel('Price') plt.title('European Up-and-Out Put Option') plt.show()
![](https://quantpy.com.au/wp-content/uploads/2022/04/up-and-out-barrier.png)