Skip to content

Commit 55fd5d7

Browse files
authored
Merge pull request #133 from PyFE/heston-mc
Heston mc
2 parents 75b8974 + 99e49ab commit 55fd5d7

File tree

11 files changed

+74
-31
lines changed

11 files changed

+74
-31
lines changed

pyfeng/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
SabrChoiWu2021H,
1313
SabrChoiWu2021P,
1414
)
15+
from .garch import GarchMcTimeStep, GarchUncorrBaroneAdesi2004
1516
from .sabr_int import SabrUncorrChoiWu2021
1617
from .sabr_mc import SabrMcCond
1718
from .nsvh import Nsvh1, NsvhMc

pyfeng/data/heston_benchmark.xlsx

170 Bytes
Binary file not shown.

pyfeng/ex.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from .heston_mc import HestonMcAndersen2008, HestonMcGlassermanKim2011, HestonMcTseWan2013, HestonMcChoiKwok2023
55
from .sv32_mc import Sv32McCondQE, Sv32McAe2
66
from .sv32_mc2 import Sv32McTimeStep, Sv32McExactBaldeaux2012, Sv32McExactChoiKwok2023
7-
from .garch import GarchMcTimeStep, GarchUncorrBaroneAdesi2004
87
from .subord_bm import VarGammaQuad, ExpNigQuad
98

109
# SABR / OUSV models for research

pyfeng/garch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class GarchMcTimeStep(sv.SvABC, sv.CondMcBsmABC):
8181
var_process = True
8282
scheme = 1 #
8383

84-
def set_mc_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, scheme=1):
84+
def set_num_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, scheme=1):
8585
"""
8686
Set MC parameters
8787
@@ -95,7 +95,7 @@ def set_mc_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, sc
9595
References:
9696
- Andersen L (2008) Simple and efficient simulation of the Heston stochastic volatility model. Journal of Computational Finance 11:1–42. https://doi.org/10.21314/JCF.2008.189
9797
"""
98-
super().set_mc_params(n_path, dt, rn_seed, antithetic)
98+
super().set_num_params(n_path, dt, rn_seed, antithetic)
9999
self.scheme = scheme
100100

101101
def var_step_euler(self, var_0, dt, milstein=True):

pyfeng/heston_mc.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,15 @@ class HestonMcAndersen2008(HestonMcABC):
162162
>>> spot = 100
163163
>>> sigma, vov, mr, rho, texp = 0.04, 1, 0.5, -0.9, 10
164164
>>> m = pfex.HestonMcAndersen2008(sigma, vov=vov, mr=mr, rho=rho)
165-
>>> m.set_mc_params(n_path=1e5, dt=1/8, rn_seed=123456)
165+
>>> m.set_num_params(n_path=1e5, dt=1/8, rn_seed=123456)
166166
>>> m.price(strike, spot, texp)
167167
>>> # true price: 44.330, 13.085, 0.296
168168
array([44.31943535, 13.09371251, 0.29580431])
169169
"""
170170
psi_c = 1.5 # parameter used by the Andersen QE scheme
171171
scheme = 4
172172

173-
def set_mc_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, scheme=4):
173+
def set_num_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, scheme=4):
174174
"""
175175
Set MC parameters
176176
@@ -184,7 +184,7 @@ def set_mc_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, sc
184184
References:
185185
- Andersen L (2008) Simple and efficient simulation of the Heston stochastic volatility model. Journal of Computational Finance 11:1–42. https://doi.org/10.21314/JCF.2008.189
186186
"""
187-
super().set_mc_params(n_path, dt, rn_seed, antithetic)
187+
super().set_num_params(n_path, dt, rn_seed, antithetic)
188188
self.scheme = scheme
189189

190190
def var_step_qe(self, var_0, dt):
@@ -305,7 +305,7 @@ class HestonMcGlassermanKim2011(HestonMcABC):
305305
kk = 1 # K for series truncation.
306306
tabulate_x2_z = False
307307

308-
def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, kk=1):
308+
def set_num_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, kk=1):
309309
"""
310310
Set MC parameters
311311
@@ -317,7 +317,7 @@ def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, kk=1):
317317
kk: truncation index
318318
319319
"""
320-
super().set_mc_params(n_path, dt, rn_seed, antithetic=False)
320+
super().set_num_params(n_path, dt, rn_seed, antithetic=False)
321321
self.scheme = scheme
322322
self.kk = kk
323323

@@ -828,14 +828,14 @@ class HestonMcTseWan2013(HestonMcGlassermanKim2011):
828828
>>> spot = 100
829829
>>> sigma, vov, mr, rho, texp = 0.04, 1, 0.5, -0.9, 10
830830
>>> m = pfex.HestonMcTseWan2013(sigma, vov=vov, mr=mr, rho=rho)
831-
>>> m.set_mc_params(n_path=1e4, rn_seed=123456)
831+
>>> m.set_num_params(n_path=1e4, rn_seed=123456)
832832
>>> m.price(strike, spot, texp)
833833
>>> # true price: 44.330, 13.085, 0.296
834834
array([12.08981758, 0.33379748, 42.28798189]) # not close so far
835835
"""
836836
dist = 'ig'
837837

838-
def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, dist=None):
838+
def set_num_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, dist=None):
839839
"""
840840
Set MC parameters
841841
@@ -847,7 +847,7 @@ def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, dist=None
847847
dist: distribution to use for approximation.
848848
'ig' for inverse Gaussian (default), 'ga' for Gamma, 'ln' for LN
849849
"""
850-
super().set_mc_params(n_path, dt, rn_seed, scheme=scheme)
850+
super().set_num_params(n_path, dt, rn_seed, scheme=scheme)
851851
if dist is not None:
852852
self.dist = dist
853853

@@ -891,7 +891,7 @@ class HestonMcChoiKwok2023(HestonMcGlassermanKim2011):
891891

892892
dist = 'ig'
893893

894-
def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, kk=0, dist=None):
894+
def set_num_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, kk=0, dist=None):
895895
"""
896896
Set MC parameters
897897
@@ -903,7 +903,7 @@ def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, scheme=3, kk=0, dis
903903
dist: distribution to use for approximation.
904904
'ig' for inverse Gaussian (default), 'ga' for Gamma, 'ln' for LN
905905
"""
906-
super().set_mc_params(n_path, dt, rn_seed, scheme=scheme, kk=kk)
906+
super().set_num_params(n_path, dt, rn_seed, scheme=scheme, kk=kk)
907907
if dist is not None:
908908
self.dist = dist
909909

pyfeng/ousv.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,26 @@ def cond_spot_sigma(self, vol_0, texp):
156156
sigma_cond = np.sqrt((1 - self.rho**2) * var_mean) / vol_0
157157
return spot_cond, sigma_cond
158158

159+
def avgvar_mv(self, var_0, texp):
160+
"""
161+
Mean and variance of the variance V(t+dt) given V(0) = var_0
162+
(variance is not implemented yet)
163+
164+
Args:
165+
var_0: initial variance
166+
texp: time step
167+
168+
Returns:
169+
mean, variance
170+
"""
171+
172+
mr_t = self.mr * texp
173+
e_mr = np.exp(-mr_t)
174+
x0 = var_0 - self.theta
175+
vv = self.vov**2/2/self.mr + self.theta**2 + \
176+
((x0**2 - self.vov**2/2/self.mr)*(1 + e_mr) + 4*self.theta * x0)*(1 - e_mr)/(2*self.mr*texp)
177+
return vv
178+
159179

160180
class OusvMcTimeStep(OusvMcABC):
161181
"""
@@ -211,7 +231,7 @@ class OusvMcChoi2023(OusvMcABC):
211231

212232
n_sin = 2
213233

214-
def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, antithetic=True, n_sin=2):
234+
def set_num_params(self, n_path=10000, dt=None, rn_seed=None, antithetic=True, n_sin=2):
215235
"""
216236
Set MC parameters
217237
@@ -224,7 +244,7 @@ def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, antithetic=True, n_
224244
assert n_sin % 2 == 0
225245
self.n_sin = n_sin
226246

227-
super().set_mc_params(n_path, dt, rn_seed, antithetic)
247+
super().set_num_params(n_path, dt, rn_seed, antithetic)
228248

229249
@classmethod
230250
def _a2sum(cls, mr_t, ns=0, odd=None):
@@ -444,7 +464,7 @@ def cond_states_step(self, vol_0, dt, zn=None):
444464
cosh = np.cosh(mr_t)
445465
vovn = self.vov * np.sqrt(dt) # normalized vov
446466

447-
x_0 = self.sigma - self.theta
467+
x_0 = vol_0 - self.theta
448468
if zn is None:
449469
x_t = self.vol_step(vol_0, dt) - self.theta
450470
else:
@@ -512,6 +532,11 @@ def cond_states_step(self, vol_0, dt, zn=None):
512532

513533
return x_t, vv_t, uu_t
514534

535+
def unexplained_var_ratio(self, mr_t, ns):
536+
ns = ns or self.n_sin
537+
rv = self._a4sum(mr_t, ns=ns) / self._a4sum(mr_t)
538+
return rv
539+
515540
def vol_path_sin(self, tobs, zn=None):
516541
"""
517542
vol path composed of sin terms
@@ -548,3 +573,21 @@ def vol_path_sin(self, tobs, zn=None):
548573
+ self.vov * np.sqrt(dt) * (an*sin) @ zn[1:,:]
549574

550575
return sigma_path
576+
577+
def price_var_option(self, strike, texp, cp=1):
578+
"""
579+
Price of variance option
580+
581+
Args:
582+
strike:
583+
texp:
584+
cp:
585+
586+
Returns:
587+
588+
"""
589+
df = np.exp(-self.intr * texp)
590+
vol_t, vv_t, uu_t = self.cond_states(self.sigma, texp)
591+
# vv_t is the average variance
592+
price = df * np.fmax(np.sign(cp)*(vv_t[:, None] - strike), 0).mean(axis=0)
593+
return price

pyfeng/sabr_mc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class SabrMcExactCai2017(sabr.SabrABC, sv.CondMcBsmABC):
117117
comb_coef = None
118118
nn = None
119119

120-
def set_mc_params(self, n_path=10000, m_inv=20, m_euler=20, n_euler=35, rn_seed=None, antithetic=True):
120+
def set_num_params(self, n_path=10000, m_inv=20, m_euler=20, n_euler=35, rn_seed=None, antithetic=True):
121121
"""
122122
Set MC parameters
123123

pyfeng/sv32_mc2.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class Sv32McTimeStep(Sv32McABC):
5757
"""
5858
scheme = 1 # Milstein
5959

60-
def set_mc_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, scheme=1):
60+
def set_num_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, scheme=1):
6161
"""
6262
Set MC parameters
6363
@@ -71,13 +71,13 @@ def set_mc_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True, sc
7171
References:
7272
- Andersen L (2008) Simple and efficient simulation of the Heston stochastic volatility model. Journal of Computational Finance 11:1–42. https://doi.org/10.21314/JCF.2008.189
7373
"""
74-
super().set_mc_params(n_path, dt, rn_seed, antithetic)
74+
super().set_num_params(n_path, dt, rn_seed, antithetic)
7575

7676
self.scheme = scheme
7777
mr = self.mr * self.theta
7878
theta = (self.mr + self.vov**2)/mr
7979
self._m_heston = heston_mc.HestonMcAndersen2008(1/self.sigma, self.vov, self.rho, mr, theta)
80-
self._m_heston.set_mc_params(n_path, dt, rn_seed, antithetic, scheme=scheme)
80+
self._m_heston.set_num_params(n_path, dt, rn_seed, antithetic, scheme=scheme)
8181

8282
def var_step_euler(self, var_0, dt, milstein=True):
8383
"""
@@ -155,7 +155,7 @@ class Sv32McExactBaldeaux2012(Sv32McABC):
155155
is_fwd: Bool, true if asset price is forward
156156
"""
157157

158-
def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, antithetic=True, scheme=1):
158+
def set_num_params(self, n_path=10000, dt=None, rn_seed=None, antithetic=True, scheme=1):
159159
"""
160160
Set MC parameters
161161
@@ -169,13 +169,13 @@ def set_mc_params(self, n_path=10000, dt=None, rn_seed=None, antithetic=True, sc
169169
References:
170170
- Andersen L (2008) Simple and efficient simulation of the Heston stochastic volatility model. Journal of Computational Finance 11:1–42. https://doi.org/10.21314/JCF.2008.189
171171
"""
172-
super().set_mc_params(n_path, dt, rn_seed, antithetic)
172+
super().set_num_params(n_path, dt, rn_seed, antithetic)
173173

174174
self.scheme = scheme
175175
mr = self.mr * self.theta
176176
theta = (self.mr + self.vov**2)/mr
177177
self._m_heston = heston_mc.HestonMcAndersen2008(1/self.sigma, self.vov, self.rho, mr, theta)
178-
self._m_heston.set_mc_params(n_path, dt, rn_seed, antithetic, scheme=scheme)
178+
self._m_heston.set_num_params(n_path, dt, rn_seed, antithetic, scheme=scheme)
179179

180180
def laplace(self, bb, var_0, var_t, dt):
181181
phi, _ = self._m_heston.phi_exp(dt)

pyfeng/sv_abc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class CondMcBsmABC(smile.OptSmileABC, abc.ABC):
116116
correct_fwd = True
117117
result = {}
118118

119-
def set_mc_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True):
119+
def set_num_params(self, n_path=10000, dt=0.05, rn_seed=None, antithetic=True):
120120
"""
121121
Set MC parameters
122122
@@ -152,7 +152,7 @@ def tobs(self, texp):
152152
if self.dt is None:
153153
return np.array([texp])
154154
else:
155-
n_dt = np.ceil(texp / (2 * self.dt)) * 2
155+
n_dt = np.ceil(texp / self.dt)
156156
tobs = np.arange(1, n_dt + 1) / n_dt * texp
157157
return tobs
158158

tests/test_heston.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_price_mc(self):
5555
"""
5656
for no in [1, 2, 3]:
5757
m, p, rv = pfex.HestonMcAndersen2008.init_benchmark(no)
58-
m.set_mc_params(n_path=1e5, dt=1/8, rn_seed=123456)
58+
m.set_num_params(n_path=1e5, dt=1/8, rn_seed=123456)
5959
m.correct_fwd = False
6060

6161
vol0 = pf.Bsm(None, intr=m.intr, divr=m.divr).impvol(rv['val'], **rv['args_pricing'])
@@ -65,27 +65,27 @@ def test_price_mc(self):
6565
np.testing.assert_allclose(m.result['spot error'], 0, atol=2e-3)
6666

6767
m, *_ = pfex.HestonMcGlassermanKim2011.init_benchmark(no)
68-
m.set_mc_params(n_path=1e5, rn_seed=123456, kk=10)
68+
m.set_num_params(n_path=1e5, rn_seed=123456, kk=10)
6969
m.correct_fwd = False
7070
vol1 = m.vol_smile(**rv['args_pricing'])
7171
np.testing.assert_allclose(vol0, vol1, atol=5e-3)
7272
np.testing.assert_allclose(m.result['spot error'], 0, atol=2e-3)
7373

7474
m, *_ = pfex.HestonMcTseWan2013.init_benchmark(no)
75-
m.set_mc_params(n_path=1e5, rn_seed=123456, dt=1)
75+
m.set_num_params(n_path=1e5, rn_seed=123456, dt=1)
7676
m.correct_fwd = False
7777
vol1 = m.vol_smile(**rv['args_pricing'])
7878
np.testing.assert_allclose(vol0, vol1, atol=5e-3)
7979
np.testing.assert_allclose(m.result['spot error'], 0, atol=2e-3)
8080

8181
m, *_ = pfex.HestonMcChoiKwok2023.init_benchmark(no)
8282
m.correct_fwd = False
83-
m.set_mc_params(n_path=1e5, rn_seed=123456, kk=10, dt=None)
83+
m.set_num_params(n_path=1e5, rn_seed=123456, kk=10, dt=None)
8484
vol1 = m.vol_smile(**rv['args_pricing'])
8585
np.testing.assert_allclose(vol0, vol1, atol=5e-3)
8686
np.testing.assert_allclose(m.result['spot error'], 0, atol=2e-3)
8787

88-
m.set_mc_params(n_path=1e5, rn_seed=123456, kk=1, dt=1/4)
88+
m.set_num_params(n_path=1e5, rn_seed=123456, kk=1, dt=1/4)
8989
vol1 = m.vol_smile(**rv['args_pricing'])
9090
np.testing.assert_allclose(vol0, vol1, atol=5e-3)
9191
np.testing.assert_allclose(m.result['spot error'], 0, atol=2e-3)

0 commit comments

Comments
 (0)