import pandas as pd
import numpy as np
def simulate_path(s0, mu, sigma, Time, num_timestep, n_sim):
20230902)
np.random.seed(
= s0
S0 = mu
r = Time
T = num_timestep
t = n_sim
n
#defining dt
= T/t
dt
= np.zeros((t, n))
S 0] = S0
S[
for i in range(0, t-1):
= np.random.standard_normal(n)
w +1] = S[i] * (1 + r * dt + sigma * np.sqrt(dt) * w)
S[i
return S
Recall Monte-Carlo method exploits the relationship between options prices and expectation under a risk-neutral measure. It is the present value of the expectation (under a risk-neutral measure) of the payoff. In this sense \[V(S, t) = \text{PV} \space \space \mathbb{E}^\mathbb{Q} (Payoff)\]
We start with the usual SDE (except we use \(r\) instead of \(\mu\) as we are under the risk-neutral framework). \[dS_t = r S_t dt + \sigma S_t dW_t\] Using the Euler discretization \[S_{t + \delta t} = S_t \cdot (1 + r \delta t + \sigma \sqrt{\delta t} \phi)\]
Using Python
Let’s create a simulation for a quarter of a year (3 months or 63 trading days).
=100, mu=0.045, sigma=0.17, Time=0.25, num_timestep=63, n_sim=100) simulate_path(s0
array([[100. , 100. , 100. , ..., 100. ,
100. , 100. ],
[ 99.39825714, 100.88405395, 100.17361119, ..., 100.79029332,
98.89439673, 99.86236711],
[ 99.50936214, 100.97945468, 99.7824842 , ..., 98.66331487,
98.67131431, 100.50278255],
...,
[100.32398459, 110.16941406, 95.79494772, ..., 101.76681189,
91.43131552, 98.94795092],
[100.93630069, 111.0365789 , 94.89177952, ..., 101.32109813,
93.37392012, 98.42725475],
[101.17836924, 110.76099538, 95.51591487, ..., 101.28364139,
92.50938162, 96.80815562]])
Let’s put that into a data frame for further plotting and manipulation
Note each column of the data frame is a simulation. The number of rows is the number of time steps.
= pd.DataFrame(simulate_path(s0=100, mu=0.045, sigma=0.17, Time=0.25, num_timestep=63, n_sim=100)) simulated_paths
-1].hist(bins = 100) simulated_paths.iloc[
<Axes: >
import matplotlib.pyplot as plt
#plot the first 100 paths
plt.plot(simulated_paths) 'time steps')
plt.xlabel(0, 64)
plt.xlim(75, 135)
plt.ylim('Pries')
plt.ylabel('Monte-Carlo Simulation of an Asset Price')
plt.title( plt.show()
Under the risk-neutral measure, the value of the option is the discounted value of the expected payoff. \[C = e^{rT} \cdot \mathbb{E}[max(S_T - K, 0)]\]
- \(K\) is the strike price
For this simulation, we let \(K=100\) as well!
= 100
K = 0.045
r = 0.25
T
= simulate_path(s0=100, mu=0.045, sigma=0.17, Time=0.25, num_timestep=63, n_sim=10000)
S
## calculate payoff for call options
= np.exp(-r*T) * np.mean(np.maximum(0, S[-1]-K))
Co ## calculate payoff for put options
= np.exp(-r*T) * np.mean(np.maximum(0, K - S[-1]))
Po
print(f"European Call Option value is {Co: 0.4f}")
print(f"European Put Option value is {Po: 0.4f}")
European Call Option value is 3.8587
European Put Option value is 2.7757
import matplotlib.pyplot as plt
= np.linspace(50,150,100)
sT
= plt.subplots(1, 2, figsize=(20, 6), sharey = True)
figure, axes = ['Call payoff', 'Put payoff']
title = [np.maximum(0, sT-K), np.maximum(0, K-sT)]
payoff = ['green', 'red']
color = ['Call', 'Put']
label
for i in range(2):
= color[i], label = label[i])
axes[i].plot(sT, payoff[i], color
axes[i].set_title(title[i])
axes[i].legend()
'Option Payoff at Maturity')
figure.suptitle(
plt.show()
Asian Options
We are taking the averages of a given asset prices.
= np.mean(S, axis = 0) # axis = 0, mean is over the columns ==> results is 1000 means. We had a 1000 simulations of 63 steps.
A = np.mean(S, axis = 1) # axis = 1, mean is row by row ==> results is 63 means
B
= 100
K = 0.045
r = 0.25
T = simulate_path(s0=100, mu=0.045, sigma=0.17, Time=0.25, num_timestep=63, n_sim=10000)
S
# do not use S[-1] anymore (the last prices), but the average instead (here it is A)
= np.exp(-r * T) * np.mean(np.maximum(0, A - K))
Co = np.exp(-r * T) * np.mean(np.maximum(0, K - A))
Po
print(f"Asian Call Option value is: {Co: 0.4f}")
print(f'Asian Put Option Value is: {Po:0.4f}')
Barrier options.
Barrier options are path dependent. They’ll need another argument
In a paper titled A Continuity Correction for Discrete Barrier Option, Mark Broadie, Paul Glasser- man and Steven Kou have shown us that the discrete barrier options can be priced using continuous barrier formulas by applying a simple continuity correction to the barrier. The correction shifts the barrier away from the underlying by a factor of \[exp(\beta \sigma \sqrt{\delta_t})\] where \(\beta \approx 0.5826\)
= 100
K = 0.045
r = 0.17
sigma = 0.25
T = 63
num_timestep = 10000
num_sim = simulate_path(s0=100, mu=0.045, sigma=sigma, Time=T, num_timestep=num_timestep, n_sim=num_sim)
S
# Let's put the barrier at 117 ! we call it B
= 117
B = T / num_timestep
delta_t = 10
rebate = 0
value = 0.5826
beta
# Barrier shift - continuity correction for discrete monitoring
= B * np.exp(beta * sigma * np.sqrt(delta_t))
B_shift print(B_shift)
# finding discounted value of expected payoff
for i in range(num_sim):
# if final price of one simulation is less that the Barrier shift
if S[:,i].max() < B_shift:
+= np.maximum(0, S[-1, i] - K)
value else:
+= rebate
value
= np.exp(-r * T) * (value/num_sim)
Co print(f'The up-and-out Barrier Option value is {Co:04f}')
117.73225187428132
The up-and-out Barrier Option value is 3.369172
= plt.subplots(1,3, figsize=(20,6), constrained_layout=True)
figure, axes = ['Visualising the Barrier Condition', 'Spot Touched Barrier', 'Spot Below Barrier']
title 0].plot(S[:,:200])
axes[for i in range(200):
1].plot(S[:,i]) if S[:,i].max() > B_shift else axes[2].plot(S[:,i])
axes[for i in range(3):
axes[i].set_title(title[i])0, 65, colors='k', linestyles='dashed')
axes[i].hlines(B_shift, 'time steps')
figure.supxlabel('index levels')
figure.supylabel( plt.show()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def create_price_path(S0, rfr, sigma, time_horizon, num_steps, num_sim):
18092023)
np.random.seed(
= time_horizon / num_steps
dt
= np.zeros((num_steps, num_sim))
S 0] = S0
S[
for i in range(0,num_steps-1):
= np.random.standard_normal(num_sim)
phi +1] = S[i] * (1 + rfr * dt + phi * sigma * np.sqrt(dt))
S[i
return S
= create_price_path(100, 0.045, 0.17, 1, 252, 10000)
S
# for a european option.
= 100
K = 0.05
r = 0.55
T
= np.exp(-r*T) * np.mean(np.maximum(0, S[-1]-K))
C0
C0
9.038910855148245