Skip to content

Commit 2b48e7d

Browse files
authored
Merge pull request #75 from PyFE/ousv
Ousv Improvement
2 parents f0d6441 + a89e5b4 commit 2b48e7d

File tree

4 files changed

+43
-112
lines changed

4 files changed

+43
-112
lines changed

.idea/PyFENG.iml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyfeng/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
BsmBasketJsu,
2828
)
2929
from .multiasset_mc import BsmNdMc, NormNdMc
30+
from .ousv import OusvIFT, OusvCondMC
3031

3132
# Basket-Asian from ASP 2021
3233
from .multiasset_Ju2002 import BsmBasketAsianJu2002, BsmContinuousAsianJu2002

pyfeng/ex.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# SV models (CMC, AE) from ASP 2021
22
from .heston_mc import HestonCondMc, HestonCondMcQE, HestonMcAe
33
from .sv32_mc import Sv32CondMcQE, Sv32McAe
4-
from .ousv import OusvIft, OusvIft, OusvCondMC
54
from .garch import GarchCondMC, GarchApproxUncor
65
from .sabr_int import SabrCondQuad

pyfeng/ousv.py

Lines changed: 35 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from . import sv_abc as sv
44

55

6-
class OusvIft(sv.SvABC):
6+
class OusvIFT(sv.SvABC):
77
"""
88
The implementation of Schobel & Zhu (1998)'s inverse FT pricing formula for European
99
options the Ornstein-Uhlenbeck driven stochastic volatility process.
@@ -12,10 +12,10 @@ class OusvIft(sv.SvABC):
1212
1313
Examples:
1414
>>> import pyfeng as pf
15-
>>> model = pf.OusvIft(0.2, mr=4, vov=0.1, rho=-0.7, intr=0.09531)
15+
>>> model = pf.OusvIFT(0.2, mr=4, vov=0.1, rho=-0.7, intr=0.09531)
1616
>>> model.price(100, 100, texp=np.array([1, 5, 10]))
1717
array([13.21493, 40.79773, 62.76312])
18-
>>> model = pf.OusvIft(0.25, mr=8, vov=0.3, rho=-0.6, intr=0.09531)
18+
>>> model = pf.OusvIFT(0.25, mr=8, vov=0.3, rho=-0.6, intr=0.09531)
1919
>>> model.price(np.array([90, 100, 110]), 100, texp=1)
2020
array([21.41873, 15.16798, 10.17448])
2121
"""
@@ -49,123 +49,73 @@ def D_B_C(self, s1, s2, s3, texp):
4949

5050
return D, B, C
5151

52-
def f_1(self, phi, fwd, texp):
52+
def f_1(self, phi, texp):
5353
# implement the formula (12)
5454
mr, theta, vov, rho = self.mr, self.theta, self.vov, self.rho
55+
5556
tmp = 1 + 1j * phi
5657
s1 = 0.5 * tmp * (-tmp * (1 - rho ** 2) + (1 - 2 * mr * rho / vov))
5758
s2 = tmp * mr * theta * rho / vov
5859
s3 = 0.5 * tmp * rho / vov
5960

60-
res = 1j * phi * np.log(fwd) - 0.5 * rho * (1 + 1j * phi) * (
61-
self.sigma ** 2 / vov + vov * texp
62-
)
61+
res = -0.5 * rho * tmp * (self.sigma ** 2 / vov + vov * texp)
6362
D, B, C = self.D_B_C(s1, s2, s3, texp)
64-
res += 0.5 * D * self.sigma ** 2 + B * self.sigma + C
63+
res += (D/2 * self.sigma + B) * self.sigma + C
6564
return np.exp(res)
6665

67-
def f_2(self, phi, fwd, texp):
66+
def f_2(self, phi, texp):
6867
# implement the formula (13)
6968
mr, theta, vov, rho = self.mr, self.theta, self.vov, self.rho
7069

71-
s1 = 0.5 * (phi ** 2 * (1 - rho ** 2) + 1j * phi * (1 - 2 * mr * rho / vov))
70+
s1 = 0.5 * phi * (phi * (1 - rho ** 2) + 1j * (1 - 2 * mr * rho / vov))
7271
s2 = 1j * phi * mr * theta * rho / vov
7372
s3 = 0.5 * 1j * phi * rho / vov
7473

75-
res = 1j * phi * np.log(fwd) - 0.5 * 1j * phi * rho * (
76-
self.sigma ** 2 / vov + vov * texp
77-
)
74+
res = -0.5 * 1j * phi * rho * (self.sigma ** 2 / vov + vov * texp)
7875
D, B, C = self.D_B_C(s1, s2, s3, texp)
79-
res += 0.5 * D * self.sigma ** 2 + B * self.sigma + C
76+
res += (D/2 * self.sigma + B) * self.sigma + C
8077
return np.exp(res)
8178

8279
def price(self, strike, spot, texp, cp=1):
8380
# implement the formula (14) and (15)
8481
fwd, df, _ = self._fwd_factor(spot, texp)
8582

86-
log_k = np.log(strike)
87-
J, h = 100001, 0.001
83+
kk = strike / fwd
84+
log_k = np.log(kk)
85+
J, h = 100001, 0.001 # need to take these as parameters
8886
phi = (np.arange(J)[:, None] + 1) * h # shape=(J,1)
89-
ff1 = 0.5 + 1 / np.pi * scint.simps(
90-
(self.f_1(phi, fwd, texp) * np.exp(-1j * phi * log_k) / (1j * phi)).real,
91-
dx=h,
92-
axis=0,
93-
)
94-
ff2 = 0.5 + 1 / np.pi * scint.simps(
95-
(self.f_2(phi, fwd, texp) * np.exp(-1j * phi * log_k) / (1j * phi)).real,
96-
dx=h,
97-
axis=0,
98-
)
9987

100-
price = np.where(
101-
cp > 0, fwd * ff1 - strike * ff2, strike * (1 - ff2) - fwd * (1 - ff1)
102-
)
88+
ff = self.f_1(phi, texp) - kk * self.f_2(phi, texp)
89+
90+
## Need to convert using iFFT later
91+
price = scint.simps(
92+
(ff * np.exp(-1j * phi * log_k) / (1j * phi)).real,
93+
dx=h, axis=0,
94+
) / np.pi
95+
96+
price += (1 - kk) / 2 * np.where(cp > 0, 1, -1)
97+
10398
if len(price) == 1:
10499
price = price[0]
105100

106-
return df * price
101+
return df * fwd * price
107102

108103

109104
class OusvCondMC(sv.SvABC, sv.CondMcBsmABC):
110105
"""
111106
OUSV model with conditional Monte-Carlo simulation
112-
The SDE of SV is: dsigma_t = mr (theta - sigma_t) dt + vov dB_T
107+
The SDE of SV is: d sigma_t = mr (theta - sigma_t) dt + vov dB_T
113108
"""
114109

115-
def _bm_incr(self, tobs, cum=False, n_path=None):
116-
"""
117-
Calculate incremental Brownian Motions
118-
119-
Args:
120-
tobs: observation times (array). 0 is not included.
121-
cum: return cumulative values if True
122-
n_path: number of paths. If None (default), use the stored one.
123-
124-
Returns:
125-
price path (time, path)
126-
"""
127-
# dt = np.diff(np.atleast_1d(tobs), prepend=0)
128-
n_dt = len(tobs)
129-
130-
tobs_lag = tobs[:-1]
131-
tobs_lag = np.insert(tobs_lag, 0, 0)
132-
bm_var = np.exp(2 * self.mr * tobs) - np.exp(2 * self.mr * tobs_lag)
133-
n_path = n_path or self.n_path
134-
135-
if self.antithetic:
136-
# generate random number in the order of path, time, asset and transposed
137-
# in this way, the same paths are generated when increasing n_path
138-
bm_incr = self.rng.normal(size=(int(n_path / 2), n_dt)).T * np.sqrt(
139-
bm_var[:, None]
140-
)
141-
bm_incr = np.stack([bm_incr, -bm_incr], axis=-1).reshape((-1, n_path))
142-
else:
143-
# bm_incr = np.random.randn(n_path, n_dt).T * np.sqrt(bm_var[:, None])
144-
bm_incr = self.rng.normal(size=(n_path, n_dt)).T * np.sqrt(bm_var[:, None])
145-
146-
if cum:
147-
np.cumsum(bm_incr, axis=0, out=bm_incr)
148-
149-
return bm_incr
150-
151110
def vol_paths(self, tobs):
152-
"""
153-
sigma_t = np.exp(-mr * tobs) * (sigma0 - theta * mr + vov / np.sqrt(2 * mr) * bm) + theta * mr
154-
Args:
155-
tobs: observation time (array)
156-
mr: coefficient of dt
157-
theta: the long term average
158-
mu: rn-derivative
111+
# 2d array of (time, path) including t=0
112+
exp_tobs = np.exp(self.mr * tobs)
159113

160-
Returns: volatility path (time, path) including the value at t=0
161-
"""
162-
bm_path = self._bm_incr(tobs, cum=True) # B_s (0 <= s <= 1)
114+
bm_path = self._bm_incr(exp_tobs**2 - 1, cum=True) # B_s (0 <= s <= 1)
163115
sigma_t = self.theta + (
164116
self.sigma - self.theta + self.vov / np.sqrt(2 * self.mr) * bm_path
165-
) * np.exp(-self.mr * tobs[:, None])
166-
sigma_t = np.insert(
167-
sigma_t, 0, np.array([self.sigma] * sigma_t.shape[1]), axis=0
168-
)
117+
) / exp_tobs[:, None]
118+
sigma_t = np.insert(sigma_t, 0, self.sigma, axis=0)
169119
return sigma_t
170120

171121
def cond_fwd_vol(self, texp):
@@ -193,39 +143,13 @@ def cond_fwd_vol(self, texp):
193143
self.rho
194144
* (
195145
(sigma_final ** 2 - self.sigma ** 2) / (2 * self.vov)
196-
- self.vov * 0.5 * texp
146+
- self.vov * texp / 2
197147
- self.mr * self.theta / self.vov * int_sigma
198-
+ (self.mr / self.vov - self.rho * 0.5) * int_var
148+
+ (self.mr / self.vov - self.rho / 2) * int_var
199149
)
200150
) # scaled by initial value
201151

202-
vol_cond = rhoc * np.sqrt(int_var / texp)
152+
# scaled by initial volatility
153+
vol_cond = rhoc * np.sqrt(int_var) / (self.sigma * np.sqrt(texp))
203154

204155
return fwd_cond, vol_cond
205-
206-
def price(self, strike, spot, texp, cp=1):
207-
"""
208-
Calculate option price based on BSM
209-
Args:
210-
strike: strike price
211-
spot: spot price
212-
texp: time to maturity
213-
cp: cp=1 if call option else put option
214-
215-
Returns: price
216-
"""
217-
price = []
218-
texp = [texp] if isinstance(texp, (int, float)) else texp
219-
for t in texp:
220-
kk = strike / spot
221-
kk = np.atleast_1d(kk)
222-
223-
fwd_cond, vol_cond = self.cond_fwd_vol(t)
224-
225-
base_model = self.base_model(vol_cond)
226-
price_grid = base_model.price(kk[:, None], fwd_cond, texp=t, cp=cp)
227-
228-
# np.set_printoptions(suppress=True, precision=6)
229-
price.append(spot * np.mean(price_grid, axis=1)) # in cond_fwd_vol, S_0 = 1
230-
231-
return np.array(price).T

0 commit comments

Comments
 (0)