Skip to content

Commit c393b6b

Browse files
committed
non-dimensionalized
1 parent c5c9a9f commit c393b6b

File tree

2 files changed

+66
-58
lines changed

2 files changed

+66
-58
lines changed

pyfeng/ex.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .sv32_mc import Sv32CondMcQE, Sv32McAe
55
from .garch import GarchCondMC, GarchApproxUncor
66
from .sabr_int import SabrCondQuad
7+
from .sabr_mc import SabrMcExactCai2017
78

89
# Basket-Asian from ASP 2021
910
from .multiasset_Ju2002 import BsmBasketAsianJu2002, BsmContinuousAsianJu2002

pyfeng/sabr_mc.py

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -109,53 +109,58 @@ def mass_zero(self, spot, texp, log=False, mu=0):
109109
return mass
110110

111111

112-
class SabrExactMcModel(sabr.SabrABC, sv.CondMcBsmABC):
112+
class SabrMcExactCai2017(sabr.SabrABC, sv.CondMcBsmABC):
113113
"""
114-
SABR model with exact simulation
114+
Cai et al (2017)'s exact simulation of the SABR model
115+
115116
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
118118
"""
119119

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):
121124
"""
122-
Volatility at maturity.
125+
Final Sigma
126+
123127
Parameters
124128
----------
125129
texp: time to expiry
126130
Returns
127131
-------
128132
vol at maturity
129133
"""
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))
132136
return sigma_T
133137

134-
def laplace_tranfrom(self, theta, texp, sigma_T):
138+
def cond_laplace(self, theta, vovn, sigma_T):
135139
"""
136-
formula (15) in the article
140+
Eq. (15) of the paper
137141
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)
145150
"""
146151

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)
150155
phi_value = np.log(z + np.sqrt(z ** 2 - 1))
151156

152157
numerator = phi_value ** 2 - x ** 2
153-
denominator = 2 * (self.vov ** 2) * texp
158+
denominator = 2 * vovn ** 2
154159
return 1 / theta * np.exp(-numerator / denominator)
155160

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):
157162
'''
158-
formula (16) in the article
163+
Eq. (16) in the article
159164
Return the original function from transform function
160165
Parameters
161166
----------
@@ -166,61 +171,61 @@ def origin_func(self, texp, sigma_T, u, m=20, n=35):
166171
-------
167172
original functions with parameter u
168173
'''
169-
Euler_term = np.zeros(self.n_path)
174+
170175
sum_term = np.zeros(m + 1)
171176
comb_vec = np.frompyfunc(spsp.comb, 2, 1)
172177
comb_term = comb_vec(m, np.arange(0, m + 1)).astype(int)
173178
for i in range(0, m + 1):
174179
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)
177182
Euler_term = np.sum(comb_term * sum_term * 2 ** (-m))
178183

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 \
180185
* np.exp(m / 2) + Euler_term / u * np.exp(m / 2) - np.exp(-m)
181186

182187
return origin_L
183188

184-
def conditional_state(self, texp):
189+
def conditional_state(self, vovn):
185190
"""
186191
Return Exact integrated variance from samples of disturibution
187192
The returns values are for sigma_0 = self.sigma and t = T
188193
Parameters
189194
----------
190-
texp: float
195+
vovn: float
191196
time-to-expiry
192197
Returns
193198
-------
194199
(sigma at the time of expiration, Exact integrated variance)
195200
"""
196-
_u = np.random.uniform(size=self.n_path)
201+
202+
_u = self.rng.uniform(size=self.n_path)
197203
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+
199206
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])
201208
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)
203210
int_var[i] = 1 / sol.x
204211
return sigma_final, int_var
205212

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):
218214

219215
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+
224229

225230
# The algorithem below is about pricing when 0<=beta<1
226231
def simu_ST(self, beta, VT, spot):
@@ -236,7 +241,8 @@ def simu_ST(self, beta, VT, spot):
236241
----------
237242
cdf of a central chi2 distribution with x=A0, degree of freedom = 1/(1 - beta)
238243
'''
239-
u_lst = np.random.uniform(size=self.n_path)
244+
245+
u_lst = self.rng.uniform(size=self.n_path)
240246
forward_ls = np.zeros(self.n_path)
241247

242248
for i in range(self.n_path):
@@ -266,6 +272,7 @@ def central_chi2_cdf(beta, VT, spot):
266272
----------
267273
cdf of a central chi2 distribution with x=A0, degree of freedom = 1/(1 - beta)
268274
'''
275+
269276
A0 = 1 / VT * (spot ** (1 - beta) / (1 - beta)) ** 2
270277
return spst.chi2.cdf(A0, 1 / (1 - beta))
271278

@@ -286,8 +293,8 @@ def C0_func(VT, beta, u):
286293
numerator = u ** (2 * (1 - beta))
287294
return 1 / VT * numerator / (1 - beta) ** 2
288295

289-
@staticmethod
290-
def sabr_chi2_cdf(beta, VT, spot, u):
296+
@classmethod
297+
def sabr_chi2_cdf(cls, beta, VT, spot, u):
291298
'''
292299
Equation (18) in Cai(2017)'s paper
293300
calculate chi2_cdf only for sabr model
@@ -303,8 +310,8 @@ def sabr_chi2_cdf(beta, VT, spot, u):
303310
cdf of the chi-square distribution specified by a sabr model's parameter and u
304311
'''
305312
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)
308315

309316
@staticmethod
310317
def chi2_cdf_appr(x, sigma, l):
@@ -398,9 +405,9 @@ def price(self, strike, spot, texp, cp_sign=1):
398405
if isinstance(strike, float):
399406
strike = np.array([strike])
400407
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)
404411
price = np.mean(price_grid, axis=0)
405412
return price[0] if price.size == 1 else price
406413
else: # 0 < beta < 1
@@ -411,4 +418,4 @@ def price(self, strike, spot, texp, cp_sign=1):
411418
price = np.maximum(self.simu_ST(self.beta, VT, spot) - kk, 0) * np.exp(-self.intr * texp)
412419
price = np.mean(price)
413420
price_lst.append(price)
414-
return np.array(price_lst)
421+
return np.array(price_lst)

0 commit comments

Comments
 (0)