Skip to content

Commit 7b8ca25

Browse files
committed
- Added Solutions to Chapters 6-11 (together with Python codes)
1 parent 567194f commit 7b8ca25

31 files changed

+3302
-0
lines changed
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#%%
2+
"""
3+
Created on 29 Nov 2019
4+
Conditional expectation
5+
@author: Lech A. Grzelak
6+
"""
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
10+
def GeneratePathsTwoStocksEuler(NoOfPaths,NoOfSteps,T,r,S10,S20,rho,sigma1,sigma2):
11+
Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
12+
Z2 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
13+
W1 = np.zeros([NoOfPaths, NoOfSteps+1])
14+
W2 = np.zeros([NoOfPaths, NoOfSteps+1])
15+
# Initialization
16+
X1 = np.zeros([NoOfPaths, NoOfSteps+1])
17+
X1[:,0] =np.log(S10)
18+
X2 = np.zeros([NoOfPaths, NoOfSteps+1])
19+
X2[:,0] =np.log(S20)
20+
time = np.zeros([NoOfSteps+1])
21+
dt = T / float(NoOfSteps)
22+
23+
for i in range(0,NoOfSteps):
24+
# Making sure that samples from a normal have mean 0 and variance 1
25+
if NoOfPaths > 1:
26+
Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i])
27+
Z2[:,i] = (Z2[:,i] - np.mean(Z2[:,i])) / np.std(Z2[:,i])
28+
Z2[:,i] = rho *Z1[:,i] + np.sqrt(1.0-rho**2.0)*Z2[:,i]
29+
W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i]
30+
W2[:,i+1] = W2[:,i] + np.power(dt, 0.5)*Z2[:,i]
31+
X1[:,i+1] = X1[:,i] + (r -0.5*sigma1**2.0)* dt + sigma1 * (W1[:,i+1] -
32+
W1[:,i])
33+
X2[:,i+1] = X2[:,i] + (r -0.5*sigma2**2.0)* dt + sigma2 * (W2[:,i+1] -
34+
W2[:,i])
35+
time[i+1] = time[i] +dt
36+
37+
# Return stock paths
38+
paths = {"time":time,"S1":np.exp(X1),"S2":np.exp(X2)}
39+
return paths
40+
41+
def getEVBinMethod(S,v,NoOfBins):
42+
if (NoOfBins != 1):
43+
mat = np.transpose(np.array([S,v]))
44+
# Sort all the rows according to the first column
45+
val = mat[mat[:,0].argsort()]
46+
binSize = int((np.size(S)/NoOfBins))
47+
expectation = np.zeros([np.size(S),2])
48+
for i in range(1,binSize-1):
49+
sample = val[(i-1)*binSize:i*binSize,1]
50+
expectation[(i-1)*binSize:i*binSize,0] = val[(i-1)*binSize:i*binSize,0]
51+
expectation[(i-1)*binSize:i*binSize,1] = np.mean(sample)
52+
return expectation
53+
54+
def main():
55+
NoOfPaths = 10000
56+
NoOfSteps = 100
57+
T = 5.0
58+
rho = -0.5
59+
y10 = 1.0
60+
y20 = 0.05
61+
62+
# Set 1
63+
sigma1 = 0.3
64+
sigma2 = 0.3
65+
66+
# Set 2
67+
#sigma1 = 0.9
68+
#sigma2 = 0.9
69+
70+
paths = GeneratePathsTwoStocksEuler(NoOfPaths, NoOfSteps, T, 0.0, y10, y20, rho, sigma1, sigma2)
71+
y1 = paths["S1"]
72+
y2 = paths["S2"]
73+
74+
# Analytical expression for the conditional expectation
75+
condE = lambda y1: y20 * (y1/y10)**(rho*sigma2/sigma1)*np.exp(0.5*T*(rho*sigma2*sigma1-sigma2**2*rho**2))
76+
77+
plt.figure(1)
78+
plt.grid()
79+
plt.plot(y1[:,-1],y2[:,-1],'.')
80+
y1Grid = np.linspace(np.min(y1[:,-1]), np.max(y1[:,-1]), 2500)
81+
plt.plot(y1Grid,condE(y1Grid),'r')
82+
83+
# Bin method
84+
E = getEVBinMethod(y1[:,-1], y2[:,-1], 50)
85+
plt.plot(E[:,0],E[:,1],'k')
86+
plt.legend(['samples','E[Y2|Y1]-Monte Carlo','E[Y2|Y1]-Analytical'])
87+
plt.xlabel('Y1')
88+
plt.ylabel('Y2')
89+
90+
# for y1 = 1.75 we have
91+
y1 = 1.75
92+
print("Analytical expression for y1={0} yields E[Y2|Y1={0}] = {1}".format(y1,condE(y1)))
93+
94+
condValueInterp = lambda y1: np.interp(y1,E[:,0],E[:,1])
95+
print("Monte Carlo, for y1={0} yields E[Y2|Y1={0}] = {1}".format(y1,condValueInterp(y1)))
96+
97+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#%%
2+
"""
3+
Created on Jan 8 2020
4+
Exercise 10.7: The Bates model and pricing of forward start options
5+
@author: Lech A. Grzelak
6+
"""
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
import scipy.stats as st
10+
import enum
11+
import scipy.optimize as optimize
12+
13+
# set i= imaginary number
14+
i = np.complex(0.0,1.0)
15+
16+
# This class defines puts and calls
17+
class OptionType(enum.Enum):
18+
CALL = 1.0
19+
PUT = -1.0
20+
21+
def CallPutOptionPriceCOSMthd_FrwdStart(cf,CP,r,T1,T2,K,N,L):
22+
# cf - characteristic function as a functon, in the book denoted as \varphi
23+
# CP - C for call and P for put
24+
# S0 - Initial stock price
25+
# r - interest rate (constant)
26+
# K - list of strikes
27+
# N - Number of expansion terms
28+
# L - size of truncation domain (typ.:L=8 or L=10)
29+
30+
tau = T2 - T1
31+
# reshape K to a column vector
32+
if K is not np.array:
33+
K = np.array(K).reshape([len(K),1])
34+
35+
# Adjust strike
36+
K = K + 1.0
37+
38+
#assigning i=sqrt(-1)
39+
i = np.complex(0.0,1.0)
40+
x0 = np.log(1.0 / K)
41+
42+
# truncation domain
43+
a = 0.0 - L * np.sqrt(tau)
44+
b = 0.0 + L * np.sqrt(tau)
45+
46+
# sumation from k = 0 to k=N-1
47+
k = np.linspace(0,N-1,N).reshape([N,1])
48+
u = k * np.pi / (b - a);
49+
50+
# Determine coefficients for Put Prices
51+
H_k = CallPutCoefficients(CP,a,b,k)
52+
mat = np.exp(i * np.outer((x0 - a) , u))
53+
temp = cf(u) * H_k
54+
temp[0] = 0.5 * temp[0]
55+
value = np.exp(-r * T2) * K * np.real(mat.dot(temp))
56+
return value
57+
58+
# Determine coefficients for Put and Call Prices
59+
def CallPutCoefficients(CP,a,b,k):
60+
if CP==OptionType.CALL:
61+
c = 0.0
62+
d = b
63+
coef = Chi_Psi(a,b,c,d,k)
64+
Chi_k = coef["chi"]
65+
Psi_k = coef["psi"]
66+
if a < b and b < 0.0:
67+
H_k = np.zeros([len(k),1])
68+
else:
69+
H_k = 2.0 / (b - a) * (Chi_k - Psi_k)
70+
elif CP==OptionType.PUT:
71+
c = a
72+
d = 0.0
73+
coef = Chi_Psi(a,b,c,d,k)
74+
Chi_k = coef["chi"]
75+
Psi_k = coef["psi"]
76+
H_k = 2.0 / (b - a) * (- Chi_k + Psi_k)
77+
78+
return H_k
79+
80+
def Chi_Psi(a,b,c,d,k):
81+
psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a))
82+
psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi)
83+
psi[0] = d - c
84+
85+
chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0))
86+
expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d) - np.cos(k * np.pi
87+
* (c - a) / (b - a)) * np.exp(c)
88+
expr2 = k * np.pi / (b - a) * np.sin(k * np.pi *
89+
(d - a) / (b - a)) - k * np.pi / (b - a) * np.sin(k
90+
* np.pi * (c - a) / (b - a)) * np.exp(c)
91+
chi = chi * (expr1 + expr2)
92+
93+
value = {"chi":chi,"psi":psi }
94+
return value
95+
96+
# Forward start Black-Scholes option price
97+
def BS_Call_Option_Price_FrwdStart(K,sigma,T1,T2,r):
98+
if K is list:
99+
K = np.array(K).reshape([len(K),1])
100+
K = K + 1.0
101+
tau = T2 - T1
102+
d1 = (np.log(1.0 / K) + (r + 0.5 * np.power(sigma,2.0))* tau) / (sigma * np.sqrt(tau))
103+
d2 = d1 - sigma * np.sqrt(tau)
104+
value = np.exp(-r*T1) * st.norm.cdf(d1) - st.norm.cdf(d2) * K * np.exp(-r * T2)
105+
return value
106+
107+
# Implied volatility for the forward start call option
108+
def ImpliedVolatility_FrwdStart(marketPrice,K,T1,T2,r):
109+
# To determine initial volatility we interpolate define a grid for sigma
110+
# and interpolate on the inverse
111+
sigmaGrid = np.linspace(0,2,200)
112+
optPriceGrid = BS_Call_Option_Price_FrwdStart(K,sigmaGrid,T1,T2,r)
113+
sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)
114+
print("Initial volatility = {0}".format(sigmaInitial))
115+
116+
# Use determined input for the local-search (final tuning)
117+
func = lambda sigma: np.power(BS_Call_Option_Price_FrwdStart(K,sigma,T1,T2,r) - marketPrice, 1.0)
118+
impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)
119+
print("Final volatility = {0}".format(impliedVol))
120+
return impliedVol
121+
122+
def ChFBatesModelForwardStart(r,T1,T2,kappa,gamma,vbar,v0,rho,xiP,muJ,sigmaJ):
123+
i = np.complex(0.0,1.0)
124+
tau = T2 - T1
125+
D1 = lambda u: np.sqrt(np.power(kappa-gamma*rho*i*u,2)+(u*u+i*u)*gamma*gamma)
126+
g = lambda u: (kappa-gamma*rho*i*u-D1(u))/(kappa-gamma*rho*i*u+D1(u))
127+
C = lambda u: (1.0-np.exp(-D1(u)*tau))/(gamma*gamma*(1.0-g(u)*np.exp(-D1(u)*tau)))\
128+
*(kappa-gamma*rho*i*u-D1(u))
129+
# Note that we exclude the term -r*tau, as the discounting is performed in the COS method
130+
AHes = lambda u: r*i*u*tau + kappa*vbar*tau/gamma/gamma *(kappa-gamma*rho*i*u-D1(u))\
131+
- 2*kappa*vbar/gamma/gamma*np.log((1.0-g(u)*np.exp(-D1(u)*tau))/(1.0-g(u)))
132+
A = lambda u: AHes(u) - xiP * i * u * tau *(np.exp(muJ+0.5*sigmaJ*sigmaJ) - 1.0) + \
133+
xiP * tau * (np.exp(i*u*muJ - 0.5 * sigmaJ * sigmaJ * u * u) - 1.0)
134+
c_bar = lambda t1,t2: gamma*gamma/(4.0*kappa) * (1.0 - np.exp(-kappa*(t2-t1)))
135+
delta = 4.0*kappa*vbar/gamma/gamma
136+
kappa_bar = lambda t1, t2: 4.0*kappa*v0*np.exp(-kappa*(t2-t1))/(gamma*gamma*(1.0-np.exp(-kappa*(t2-t1))))
137+
term1 = lambda u: A(u) + C(u)*c_bar(0.0,T1)*kappa_bar(0.0,T1)/(1.0-2.0*C(u)*c_bar(0.0,T1))
138+
term2 = lambda u: np.power(1.0/(1.0-2.0*C(u)*c_bar(0.0,T1)),0.5*delta)
139+
cf = lambda u: np.exp(term1(u)) * term2(u)
140+
return cf
141+
142+
def mainCalculation():
143+
CP = OptionType.CALL
144+
r = 0.00
145+
146+
TMat1=[[1.0,3.0],[2.0,4.0],[3.0, 5.0],[4.0, 6.0]]
147+
TMat2=[[1.0,2.0],[1.0,3.0],[1.0, 4.0],[1.0, 5.0]]
148+
149+
K = np.linspace(-0.4,4.0,50)
150+
K = np.array(K).reshape([len(K),1])
151+
152+
N = 500
153+
L = 10
154+
155+
# Bates model parameters
156+
kappa = 0.6
157+
gamma = 0.2
158+
vbar = 0.1
159+
rho = -0.5
160+
v0 = 0.05
161+
162+
muJ = 0.05
163+
sigmaJ= 0.2
164+
xiP = 0.1
165+
166+
plt.figure(1)
167+
plt.grid()
168+
plt.xlabel('strike, K')
169+
plt.ylabel('implied volatility')
170+
legend = []
171+
for T_pair in TMat1:
172+
T1= T_pair[0]
173+
T2= T_pair[1]
174+
cf = ChFBatesModelForwardStart(r,T1,T2,kappa,gamma,vbar,v0,rho,xiP,muJ,sigmaJ)
175+
# Forward-start option from the COS method
176+
valCOS = CallPutOptionPriceCOSMthd_FrwdStart(cf,CP,r,T1,T2,K,N,L)
177+
# Implied volatilities
178+
IV =np.zeros([len(K),1])
179+
for idx in range(0,len(K)):
180+
IV[idx] = ImpliedVolatility_FrwdStart(valCOS[idx],K[idx],T1,T2,r)
181+
plt.plot(K,IV*100.0)
182+
legend.append('T1={0} & T2={1}'.format(T1,T2))
183+
plt.legend(legend)
184+
185+
plt.figure(2)
186+
plt.grid()
187+
plt.xlabel('strike, K')
188+
plt.ylabel('implied volatility')
189+
legend = []
190+
for T_pair in TMat2:
191+
T1= T_pair[0]
192+
T2= T_pair[1]
193+
cf = ChFBatesModelForwardStart(r,T1,T2,kappa,gamma,vbar,v0,rho,xiP,muJ,sigmaJ)
194+
195+
# Forward-start option from the COS method
196+
valCOS = CallPutOptionPriceCOSMthd_FrwdStart(cf,CP,r,T1,T2,K,N,L)
197+
198+
# Implied volatilities
199+
IV =np.zeros([len(K),1])
200+
for idx in range(0,len(K)):
201+
IV[idx] = ImpliedVolatility_FrwdStart(valCOS[idx],K[idx],T1,T2,r)
202+
plt.plot(K,IV*100.0)
203+
legend.append('T1={0} & T2={1}'.format(T1,T2))
204+
205+
plt.legend(legend)
206+
207+
mainCalculation()

0 commit comments

Comments
 (0)