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