@@ -109,53 +109,58 @@ def mass_zero(self, spot, texp, log=False, mu=0):
109
109
return mass
110
110
111
111
112
- class SabrExactMcModel (sabr .SabrABC , sv .CondMcBsmABC ):
112
+ class SabrMcExactCai2017 (sabr .SabrABC , sv .CondMcBsmABC ):
113
113
"""
114
- SABR model with exact simulation
114
+ Cai et al (2017)'s exact simulation of the SABR model
115
+
115
116
References:
116
- Cai, N., Song, Y., & Chen, N. (2017). Exact Simulation of the SABR Model.
117
- Operations Research, 65(4), 931–951. https://doi.org/10.1287/opre.2017.1617
117
+ - Cai N, Song Y, Chen N (2017) Exact Simulation of the SABR Model. Oper Res 65:931–951. https://doi.org/10.1287/opre.2017.1617
118
118
"""
119
119
120
- def simu_sigma_T (self , sigma_0 , texp ):
120
+ def vol_paths (self , tobs , mu = 0 ):
121
+ return None
122
+
123
+ def sigma_final (self , vovn ):
121
124
"""
122
- Volatility at maturity.
125
+ Final Sigma
126
+
123
127
Parameters
124
128
----------
125
129
texp: time to expiry
126
130
Returns
127
131
-------
128
132
vol at maturity
129
133
"""
130
- zz = np . random .normal (size = self .n_path )
131
- sigma_T = sigma_0 * np .exp (- 1 / 2 * self . vov ** 2 * texp + self . vov * zz * np . sqrt ( texp ))
134
+ zz = self . rng .normal (size = self .n_path )
135
+ sigma_T = np .exp (vovn * ( zz - vovn / 2 ))
132
136
return sigma_T
133
137
134
- def laplace_tranfrom (self , theta , texp , sigma_T ):
138
+ def cond_laplace (self , theta , vovn , sigma_T ):
135
139
"""
136
- formula (15) in the article
140
+ Eq. (15) of the paper
137
141
Return the laplace transform function
138
- Parameters
139
- ----------
140
- theta: represent the input variable
141
- texp: time-to-expiry
142
- Returns
143
- -------
144
- (Laplace transform function)
142
+
143
+ Args:
144
+ theta: dummy variable
145
+ vovn: vov * sqrt(texp)
146
+ sigma_T: normalized sigma final
147
+
148
+ Returns:
149
+ (Laplace transform function)
145
150
"""
146
151
147
- l = theta * self . vov ** 2 / self . sigma ** 2
148
- x = np .log (sigma_T / self . sigma )
149
- z = l * np . exp ( - x ) + np . cosh ( x )
152
+ lam = theta * vovn ** 2
153
+ x = np .log (sigma_T )
154
+ z = lam / sigma_T + 0.5 * ( sigma_T + 1 / sigma_T )
150
155
phi_value = np .log (z + np .sqrt (z ** 2 - 1 ))
151
156
152
157
numerator = phi_value ** 2 - x ** 2
153
- denominator = 2 * ( self . vov ** 2 ) * texp
158
+ denominator = 2 * vovn ** 2
154
159
return 1 / theta * np .exp (- numerator / denominator )
155
160
156
- def origin_func (self , texp , sigma_T , u , m = 20 , n = 35 ):
161
+ def inv_laplace (self , texp , sigma_T , u , m = 20 , n = 35 ):
157
162
'''
158
- formula (16) in the article
163
+ Eq. (16) in the article
159
164
Return the original function from transform function
160
165
Parameters
161
166
----------
@@ -166,61 +171,61 @@ def origin_func(self, texp, sigma_T, u, m=20, n=35):
166
171
-------
167
172
original functions with parameter u
168
173
'''
169
- Euler_term = np . zeros ( self . n_path )
174
+
170
175
sum_term = np .zeros (m + 1 )
171
176
comb_vec = np .frompyfunc (spsp .comb , 2 , 1 )
172
177
comb_term = comb_vec (m , np .arange (0 , m + 1 )).astype (int )
173
178
for i in range (0 , m + 1 ):
174
179
sum_term [i ] = np .sum (
175
- (- 1 ) ** np .arange (0 , n + i + 1 ) * self .laplace_tranfrom ((m - 2j * np .pi * np .arange (0 , n + i + 1 )) / u ,
176
- texp , sigma_T ).real )
180
+ (- 1 ) ** np .arange (0 , n + i + 1 ) * self .cond_laplace ((m - 2j * np .pi * np .arange (0 , n + i + 1 )) / u ,
181
+ texp , sigma_T ).real )
177
182
Euler_term = np .sum (comb_term * sum_term * 2 ** (- m ))
178
183
179
- origin_L = 1 / (2 * u ) * self .laplace_tranfrom (m / (2 * u ), texp , sigma_T ).real \
184
+ origin_L = 1 / (2 * u ) * self .cond_laplace (m / (2 * u ), texp , sigma_T ).real \
180
185
* np .exp (m / 2 ) + Euler_term / u * np .exp (m / 2 ) - np .exp (- m )
181
186
182
187
return origin_L
183
188
184
- def conditional_state (self , texp ):
189
+ def conditional_state (self , vovn ):
185
190
"""
186
191
Return Exact integrated variance from samples of disturibution
187
192
The returns values are for sigma_0 = self.sigma and t = T
188
193
Parameters
189
194
----------
190
- texp : float
195
+ vovn : float
191
196
time-to-expiry
192
197
Returns
193
198
-------
194
199
(sigma at the time of expiration, Exact integrated variance)
195
200
"""
196
- _u = np .random .uniform (size = self .n_path )
201
+
202
+ _u = self .rng .uniform (size = self .n_path )
197
203
int_var = np .zeros (self .n_path )
198
- sigma_final = self .simu_sigma_T (self .sigma , texp )
204
+ sigma_final = self .sigma_final (vovn )
205
+
199
206
for i in range (self .n_path ):
200
- Lh_func = partial (self .origin_func , texp , sigma_final [i ])
207
+ Lh_func = partial (self .inv_laplace , vovn , sigma_final [i ])
201
208
obj_func = lambda x : Lh_func (x ) - _u [i ]
202
- sol = spop .root (obj_func , 1 / texp ** 3 )
209
+ sol = spop .root (obj_func , 1 / vovn ** 3 )
203
210
int_var [i ] = 1 / sol .x
204
211
return sigma_final , int_var
205
212
206
- def exact_fwd_vol (self , spot , texp ):
207
- """
208
- Returns forward prices and volatility conditional on sigma at time of expiration and integrated variance)
209
- Parameters
210
- ----------
211
- spot: spot prices
212
- texp: time-to-expiry
213
- Returns
214
- -------
215
- (forward price, volatility)
216
- """
217
- assert abs (self .beta - 1.0 ) < 1e-8
213
+ def cond_spot_sigma (self , texp ):
218
214
219
215
rhoc = np .sqrt (1.0 - self .rho ** 2 )
220
- sigma_final , int_var = self .conditional_state (texp )
221
- fwd_exact = spot * np .exp (self .rho / self .vov * (sigma_final - self .sigma ) - 0.5 * self .rho ** 2 * int_var )
222
- vol_exact = rhoc * np .sqrt (int_var / texp )
223
- return fwd_exact , vol_exact
216
+ rho_sigma = self .rho * self .sigma
217
+ vovn = self .vov * np .sqrt (texp )
218
+ sigma_final , int_var = self .conditional_state (vovn )
219
+
220
+ vol_cond = rhoc * np .sqrt (int_var )
221
+ if np .isclose (self .beta , 0 ):
222
+ fwd_cond = rho_sigma / self .vov * (sigma_final - 1 )
223
+ else :
224
+ fwd_cond = np .exp (
225
+ rho_sigma * (1.0 / self .vov * (sigma_final - 1 ) - 0.5 * rho_sigma * int_var * texp )
226
+ )
227
+ return fwd_cond , vol_cond
228
+
224
229
225
230
# The algorithem below is about pricing when 0<=beta<1
226
231
def simu_ST (self , beta , VT , spot ):
@@ -236,7 +241,8 @@ def simu_ST(self, beta, VT, spot):
236
241
----------
237
242
cdf of a central chi2 distribution with x=A0, degree of freedom = 1/(1 - beta)
238
243
'''
239
- u_lst = np .random .uniform (size = self .n_path )
244
+
245
+ u_lst = self .rng .uniform (size = self .n_path )
240
246
forward_ls = np .zeros (self .n_path )
241
247
242
248
for i in range (self .n_path ):
@@ -266,6 +272,7 @@ def central_chi2_cdf(beta, VT, spot):
266
272
----------
267
273
cdf of a central chi2 distribution with x=A0, degree of freedom = 1/(1 - beta)
268
274
'''
275
+
269
276
A0 = 1 / VT * (spot ** (1 - beta ) / (1 - beta )) ** 2
270
277
return spst .chi2 .cdf (A0 , 1 / (1 - beta ))
271
278
@@ -286,8 +293,8 @@ def C0_func(VT, beta, u):
286
293
numerator = u ** (2 * (1 - beta ))
287
294
return 1 / VT * numerator / (1 - beta ) ** 2
288
295
289
- @staticmethod
290
- def sabr_chi2_cdf (beta , VT , spot , u ):
296
+ @classmethod
297
+ def sabr_chi2_cdf (cls , beta , VT , spot , u ):
291
298
'''
292
299
Equation (18) in Cai(2017)'s paper
293
300
calculate chi2_cdf only for sabr model
@@ -303,8 +310,8 @@ def sabr_chi2_cdf(beta, VT, spot, u):
303
310
cdf of the chi-square distribution specified by a sabr model's parameter and u
304
311
'''
305
312
A0 = 1 / VT * (spot ** (1 - beta ) / (1 - beta )) ** 2 # Equation (6) in Cai's paper
306
- C0 = SabrExactMcModel .C0_func (VT , beta , u )
307
- return SabrExactMcModel .chi2_cdf_appr (A0 , 1 / (1 - beta ), C0 )
313
+ C0 = cls .C0_func (VT , beta , u )
314
+ return cls .chi2_cdf_appr (A0 , 1 / (1 - beta ), C0 )
308
315
309
316
@staticmethod
310
317
def chi2_cdf_appr (x , sigma , l ):
@@ -398,9 +405,9 @@ def price(self, strike, spot, texp, cp_sign=1):
398
405
if isinstance (strike , float ):
399
406
strike = np .array ([strike ])
400
407
if self .beta == 1 :
401
- fwd_exact , vol_exact = self .exact_fwd_vol ( spot , texp )
402
- base_model = bsm . BsmModel ( vol_exact [:, None ], intr = self . intr , divr = self . divr , is_fwd = self . is_fwd )
403
- price_grid = base_model .price (strike , fwd_exact [:, None ], texp , cp_sign )
408
+ fwd_cond , vol_cond = self .cond_spot_sigma ( texp )
409
+ base_model = self . base_model ( vol_cond [:, None ])
410
+ price_grid = base_model .price (strike , spot * fwd_cond [:, None ], texp , cp_sign )
404
411
price = np .mean (price_grid , axis = 0 )
405
412
return price [0 ] if price .size == 1 else price
406
413
else : # 0 < beta < 1
@@ -411,4 +418,4 @@ def price(self, strike, spot, texp, cp_sign=1):
411
418
price = np .maximum (self .simu_ST (self .beta , VT , spot ) - kk , 0 ) * np .exp (- self .intr * texp )
412
419
price = np .mean (price )
413
420
price_lst .append (price )
414
- return np .array (price_lst )
421
+ return np .array (price_lst )
0 commit comments