3
3
from . import sv_abc as sv
4
4
5
5
6
- class OusvIft (sv .SvABC ):
6
+ class OusvIFT (sv .SvABC ):
7
7
"""
8
8
The implementation of Schobel & Zhu (1998)'s inverse FT pricing formula for European
9
9
options the Ornstein-Uhlenbeck driven stochastic volatility process.
@@ -12,10 +12,10 @@ class OusvIft(sv.SvABC):
12
12
13
13
Examples:
14
14
>>> 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)
16
16
>>> model.price(100, 100, texp=np.array([1, 5, 10]))
17
17
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)
19
19
>>> model.price(np.array([90, 100, 110]), 100, texp=1)
20
20
array([21.41873, 15.16798, 10.17448])
21
21
"""
@@ -49,123 +49,73 @@ def D_B_C(self, s1, s2, s3, texp):
49
49
50
50
return D , B , C
51
51
52
- def f_1 (self , phi , fwd , texp ):
52
+ def f_1 (self , phi , texp ):
53
53
# implement the formula (12)
54
54
mr , theta , vov , rho = self .mr , self .theta , self .vov , self .rho
55
+
55
56
tmp = 1 + 1j * phi
56
57
s1 = 0.5 * tmp * (- tmp * (1 - rho ** 2 ) + (1 - 2 * mr * rho / vov ))
57
58
s2 = tmp * mr * theta * rho / vov
58
59
s3 = 0.5 * tmp * rho / vov
59
60
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 )
63
62
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
65
64
return np .exp (res )
66
65
67
- def f_2 (self , phi , fwd , texp ):
66
+ def f_2 (self , phi , texp ):
68
67
# implement the formula (13)
69
68
mr , theta , vov , rho = self .mr , self .theta , self .vov , self .rho
70
69
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 ))
72
71
s2 = 1j * phi * mr * theta * rho / vov
73
72
s3 = 0.5 * 1j * phi * rho / vov
74
73
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 )
78
75
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
80
77
return np .exp (res )
81
78
82
79
def price (self , strike , spot , texp , cp = 1 ):
83
80
# implement the formula (14) and (15)
84
81
fwd , df , _ = self ._fwd_factor (spot , texp )
85
82
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
88
86
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
- )
99
87
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
+
103
98
if len (price ) == 1 :
104
99
price = price [0 ]
105
100
106
- return df * price
101
+ return df * fwd * price
107
102
108
103
109
104
class OusvCondMC (sv .SvABC , sv .CondMcBsmABC ):
110
105
"""
111
106
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
113
108
"""
114
109
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
-
151
110
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 )
159
113
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)
163
115
sigma_t = self .theta + (
164
116
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 )
169
119
return sigma_t
170
120
171
121
def cond_fwd_vol (self , texp ):
@@ -193,39 +143,13 @@ def cond_fwd_vol(self, texp):
193
143
self .rho
194
144
* (
195
145
(sigma_final ** 2 - self .sigma ** 2 ) / (2 * self .vov )
196
- - self .vov * 0.5 * texp
146
+ - self .vov * texp / 2
197
147
- 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
199
149
)
200
150
) # scaled by initial value
201
151
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 ))
203
154
204
155
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