Skip to content

Commit a6179e0

Browse files
committed
speed up the Laplace inversion
1 parent c393b6b commit a6179e0

File tree

1 file changed

+40
-17
lines changed

1 file changed

+40
-17
lines changed

pyfeng/sabr_mc.py

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,28 @@ class SabrMcExactCai2017(sabr.SabrABC, sv.CondMcBsmABC):
116116
References:
117117
- 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
"""
119+
m_inv = 20
120+
n_inv = 35
121+
122+
def set_mc_params(self, n_path=10000, m_inv=20, n_inv=35, rn_seed=None, antithetic=True):
123+
"""
124+
Set MC parameters
125+
126+
Args:
127+
n_path: number of paths
128+
m_inv: parameter M used in Laplace inversion
129+
n_inv: parameter n used in Laplace inversion
130+
rn_seed: random number seed
131+
antithetic: antithetic
132+
"""
133+
self.n_path = int(n_path)
134+
self.m_inv = m_inv
135+
self.n_inv = n_inv
136+
self.dt = None
137+
self.rn_seed = rn_seed
138+
self.antithetic = antithetic
139+
self.rn_seed = rn_seed
140+
self.rng = np.random.default_rng(rn_seed)
119141

120142
def vol_paths(self, tobs, mu=0):
121143
return None
@@ -131,7 +153,9 @@ def sigma_final(self, vovn):
131153
-------
132154
vol at maturity
133155
"""
134-
zz = self.rng.normal(size=self.n_path)
156+
zz = self.rng.normal(size=self.n_path//2)
157+
zz = np.hstack([zz, -zz])
158+
135159
sigma_T = np.exp(vovn * (zz - vovn/2))
136160
return sigma_T
137161

@@ -158,31 +182,31 @@ def cond_laplace(self, theta, vovn, sigma_T):
158182
denominator = 2 * vovn ** 2
159183
return 1 / theta * np.exp(-numerator / denominator)
160184

161-
def inv_laplace(self, texp, sigma_T, u, m=20, n=35):
185+
def inv_laplace(self, u, vovn, sigma_T):
162186
'''
163187
Eq. (16) in the article
164188
Return the original function from transform function
165189
Parameters
166190
----------
167-
texp: time to expiration
191+
u: variable
192+
vovn: vov * sqrt(texp)
168193
sigma_T: simulated sigma at time of expiration
169-
u: the u of original function
170194
Returns
171195
-------
172196
original functions with parameter u
173197
'''
174198

175-
sum_term = np.zeros(m + 1)
176-
comb_vec = np.frompyfunc(spsp.comb, 2, 1)
177-
comb_term = comb_vec(m, np.arange(0, m + 1)).astype(int)
178-
for i in range(0, m + 1):
179-
sum_term[i] = np.sum(
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)
182-
Euler_term = np.sum(comb_term * sum_term * 2 ** (-m))
199+
## index from 0 to m + n
200+
nn = np.arange(0, self.m_inv + self.n_inv + 0.1)
201+
ss_j = self.cond_laplace((self.m_inv - 2j * np.pi * nn) / u, vovn, sigma_T).real
202+
term1 = 0.5 * ss_j[0]
203+
ss_j[1::2] *= -1
204+
np.cumsum(ss_j, out=ss_j)
205+
206+
comb_coef = spsp.comb(self.m_inv, np.arange(0, self.m_inv+0.1)) * np.power(0.5, self.m_inv)
207+
term2 = np.sum(comb_coef * ss_j[self.n_inv:])
183208

184-
origin_L = 1 / (2 * u) * self.cond_laplace(m / (2 * u), texp, sigma_T).real \
185-
* np.exp(m / 2) + Euler_term / u * np.exp(m / 2) - np.exp(-m)
209+
origin_L = np.exp(self.m_inv/2) / u * (-term1 + term2)
186210

187211
return origin_L
188212

@@ -199,13 +223,12 @@ def conditional_state(self, vovn):
199223
(sigma at the time of expiration, Exact integrated variance)
200224
"""
201225

202-
_u = self.rng.uniform(size=self.n_path)
226+
u_rn = self.rng.uniform(size=self.n_path)
203227
int_var = np.zeros(self.n_path)
204228
sigma_final = self.sigma_final(vovn)
205229

206230
for i in range(self.n_path):
207-
Lh_func = partial(self.inv_laplace, vovn, sigma_final[i])
208-
obj_func = lambda x: Lh_func(x) - _u[i]
231+
obj_func = lambda x: self.inv_laplace(x, vovn, sigma_final[i]) - u_rn[i]
209232
sol = spop.root(obj_func, 1 / vovn ** 3)
210233
int_var[i] = 1 / sol.x
211234
return sigma_final, int_var

0 commit comments

Comments
 (0)