stochvolmodels 1.0.28__tar.gz → 1.0.30__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/PKG-INFO +1 -1
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/pyproject.toml +1 -1
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/__init__.py +3 -3
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/data/fetch_option_chain.py +1 -1
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/examples/run_lognormal_sv_pricer.py +114 -5
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/logsv/logsv_params.py +14 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/logsv_pricer.py +118 -6
- stochvolmodels-1.0.30/stochvolmodels/pricers/rough_logsv/RoughKernel.py +1210 -0
- stochvolmodels-1.0.30/stochvolmodels/pricers/rough_logsv/split_simulation.py +159 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/LICENSE.txt +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/README.md +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/data/__init__.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/data/option_chain.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/data/test_option_chain.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/examples/quick_run_lognormal_sv_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/examples/run_heston.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/examples/run_heston_sv_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/examples/run_pricing_options_on_qvar.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/__init__.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/analytic/__init__.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/analytic/bachelier.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/analytic/bsm.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/analytic/tdist.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/rate_affine_expansion.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/rate_core.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/gmm_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/hawkes_jd_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/heston_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/logsv/__init__.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/logsv/affine_expansion.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/logsv/vol_moments_ode.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/model_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/tdist_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/tests/__init__.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/tests/bsm_mgf_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/tests/qv_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/utils/__init__.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/utils/config.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/utils/funcs.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/utils/mc_payoffs.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/utils/mgf_pricer.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/utils/plots.py +0 -0
- {stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/utils/var_swap_pricer.py +0 -0
|
@@ -109,7 +109,9 @@ from stochvolmodels.pricers.logsv_pricer import (
|
|
|
109
109
|
ConstraintsType,
|
|
110
110
|
CalibrationEngine,
|
|
111
111
|
get_randoms_for_chain_valuation,
|
|
112
|
-
|
|
112
|
+
get_randoms_for_rough_vol_chain_valuation,
|
|
113
|
+
logsv_mc_chain_pricer_fixed_randoms,
|
|
114
|
+
rough_logsv_mc_chain_pricer_fixed_randoms
|
|
113
115
|
)
|
|
114
116
|
from stochvolmodels.pricers.logsv.logsv_params import LogSvParams
|
|
115
117
|
|
|
@@ -159,5 +161,3 @@ from stochvolmodels.utils.plots import (
|
|
|
159
161
|
|
|
160
162
|
|
|
161
163
|
from stochvolmodels.pricers.logsv.vol_moments_ode import compute_analytic_qvar
|
|
162
|
-
|
|
163
|
-
from stochvolmodels.data.option_chain import generate_vol_chain_np
|
|
@@ -105,7 +105,7 @@ def sample_option_chain_at_times(options_data_dfs: OptionsDataDFs,
|
|
|
105
105
|
|
|
106
106
|
def load_price_data(options_data_dfs: OptionsDataDFs,
|
|
107
107
|
time_period: TimePeriod = None,
|
|
108
|
-
data: Literal['
|
|
108
|
+
data: Literal['close', 'perp', 'funding_rate'] = 'close',
|
|
109
109
|
freq: Optional[str] = 'D' # to do
|
|
110
110
|
) -> pd.Series:
|
|
111
111
|
#options_data_dfs = OptionsDataDFs(**ts_data_loader_wrapper(ticker=ticker, freq='D', hour_offset=8))
|
{stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/examples/run_lognormal_sv_pricer.py
RENAMED
|
@@ -5,7 +5,6 @@ run few unit test to illustrate implementation of log-normal sv model analytics
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
from enum import Enum
|
|
8
|
-
|
|
9
8
|
import stochvolmodels as sv
|
|
10
9
|
from stochvolmodels import LogSVPricer, LogSvParams, OptionChain, LogsvModelCalibrationType
|
|
11
10
|
|
|
@@ -19,6 +18,8 @@ class UnitTests(Enum):
|
|
|
19
18
|
CALIBRATE_MODEL_TO_BTC_OPTIONS = 6
|
|
20
19
|
MC_WITH_FIXED_RANDOMS = 7
|
|
21
20
|
CALIBRATE_MODEL_TO_BTC_OPTIONS_WITH_MC = 8
|
|
21
|
+
ROUGH_MC_WITH_FIXED_RANDOMS = 9
|
|
22
|
+
BENCHM_ROUGH_PRICER = 10
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def run_unit_test(unit_test: UnitTests):
|
|
@@ -124,9 +125,115 @@ def run_unit_test(unit_test: UnitTests):
|
|
|
124
125
|
option_prices_ttm, option_std_ttm = sv.logsv_mc_chain_pricer_fixed_randoms(**args)
|
|
125
126
|
print(option_prices_ttm)
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
|
|
129
|
+
elif unit_test == UnitTests.ROUGH_MC_WITH_FIXED_RANDOMS:
|
|
130
|
+
btc_option_chain = sv.get_btc_test_chain_data()
|
|
131
|
+
Z0, Z1, grid_ttms = sv.get_randoms_for_rough_vol_chain_valuation(ttms=btc_option_chain.ttms,
|
|
132
|
+
nb_path=10000,
|
|
133
|
+
nb_steps_per_year=360,
|
|
134
|
+
seed=10)
|
|
135
|
+
params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=0.0, beta=0.15, volvol=2.0)
|
|
136
|
+
params0.H = 0.3
|
|
137
|
+
params0.approximate_kernel(T=btc_option_chain.ttms[-1], N=3)
|
|
138
|
+
|
|
139
|
+
option_prices_ttm, option_std_ttm = sv.rough_logsv_mc_chain_pricer_fixed_randoms(ttms=btc_option_chain.ttms,
|
|
140
|
+
forwards=btc_option_chain.forwards,
|
|
141
|
+
discfactors=btc_option_chain.discfactors,
|
|
142
|
+
strikes_ttms=btc_option_chain.strikes_ttms,
|
|
143
|
+
optiontypes_ttms=btc_option_chain.optiontypes_ttms,
|
|
144
|
+
Z0=Z0,
|
|
145
|
+
Z1=Z1,
|
|
146
|
+
sigma0=params0.sigma0,
|
|
147
|
+
theta=params0.theta,
|
|
148
|
+
kappa1=params0.kappa1,
|
|
149
|
+
kappa2=params0.kappa2,
|
|
150
|
+
beta=params0.beta,
|
|
151
|
+
orthog_vol=params0.volvol,
|
|
152
|
+
weights=params0.weights,
|
|
153
|
+
nodes=params0.nodes,
|
|
154
|
+
timegrids=grid_ttms)
|
|
128
155
|
print(option_prices_ttm)
|
|
129
156
|
|
|
157
|
+
elif unit_test == UnitTests.BENCHM_ROUGH_PRICER:
|
|
158
|
+
btc_option_chain = OptionChain.get_uniform_chain(ttms=np.array([0.083, 0.25]),
|
|
159
|
+
ids=np.array(['1m', '3m']),
|
|
160
|
+
strikes=np.linspace(0.5, 1.5, 21))
|
|
161
|
+
params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=0.0, beta=0.15, volvol=2.0)
|
|
162
|
+
nb_path = 100000
|
|
163
|
+
H = 0.3
|
|
164
|
+
N = 3
|
|
165
|
+
|
|
166
|
+
def rough_vol():
|
|
167
|
+
params1 = LogSvParams.copy(params0)
|
|
168
|
+
params1.H = 0.3
|
|
169
|
+
params1.approximate_kernel(T=btc_option_chain.ttms[-1], N=N)
|
|
170
|
+
|
|
171
|
+
Z0, Z1, grid_ttms = sv.get_randoms_for_rough_vol_chain_valuation(ttms=btc_option_chain.ttms,
|
|
172
|
+
nb_path=nb_path,
|
|
173
|
+
nb_steps_per_year=360,
|
|
174
|
+
seed=10)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
option_prices_ttm, option_std_ttm = sv.rough_logsv_mc_chain_pricer_fixed_randoms(ttms=btc_option_chain.ttms,
|
|
178
|
+
forwards=btc_option_chain.forwards,
|
|
179
|
+
discfactors=btc_option_chain.discfactors,
|
|
180
|
+
strikes_ttms=btc_option_chain.strikes_ttms,
|
|
181
|
+
optiontypes_ttms=btc_option_chain.optiontypes_ttms,
|
|
182
|
+
Z0=Z0,
|
|
183
|
+
Z1=Z1,
|
|
184
|
+
sigma0=params0.sigma0,
|
|
185
|
+
theta=params0.theta,
|
|
186
|
+
kappa1=params0.kappa1,
|
|
187
|
+
kappa2=params0.kappa2,
|
|
188
|
+
beta=params0.beta,
|
|
189
|
+
orthog_vol=params0.volvol,
|
|
190
|
+
weights=params1.weights,
|
|
191
|
+
nodes=params1.nodes,
|
|
192
|
+
timegrids=grid_ttms)
|
|
193
|
+
model_ivols_ttms = btc_option_chain.compute_model_ivols_from_chain_data(option_prices_ttm)
|
|
194
|
+
return model_ivols_ttms
|
|
195
|
+
|
|
196
|
+
def regular_vol():
|
|
197
|
+
W0s, W1s, dts = sv.get_randoms_for_chain_valuation(ttms=btc_option_chain.ttms,
|
|
198
|
+
nb_path=nb_path,
|
|
199
|
+
nb_steps_per_year=360,
|
|
200
|
+
seed=10)
|
|
201
|
+
vol_backbone_etas = params.get_vol_backbone_etas(ttms=btc_option_chain.ttms)
|
|
202
|
+
args = dict(ttms=btc_option_chain.ttms,
|
|
203
|
+
forwards=btc_option_chain.forwards,
|
|
204
|
+
discfactors=btc_option_chain.discfactors,
|
|
205
|
+
strikes_ttms=btc_option_chain.strikes_ttms,
|
|
206
|
+
optiontypes_ttms=btc_option_chain.optiontypes_ttms,
|
|
207
|
+
W0s=W0s,
|
|
208
|
+
W1s=W1s,
|
|
209
|
+
dts=dts,
|
|
210
|
+
v0=params0.sigma0,
|
|
211
|
+
theta=params0.theta,
|
|
212
|
+
kappa1=params0.kappa1,
|
|
213
|
+
kappa2=params0.kappa2,
|
|
214
|
+
beta=params0.beta,
|
|
215
|
+
volvol=params0.volvol,
|
|
216
|
+
vol_backbone_etas=vol_backbone_etas)
|
|
217
|
+
option_prices_ttm, option_std_ttm = sv.logsv_mc_chain_pricer_fixed_randoms(**args)
|
|
218
|
+
model_ivols_ttms = btc_option_chain.compute_model_ivols_from_chain_data(option_prices_ttm)
|
|
219
|
+
|
|
220
|
+
return model_ivols_ttms
|
|
221
|
+
|
|
222
|
+
ivols_rough_logsv = rough_vol()
|
|
223
|
+
ivols_logsv = regular_vol()
|
|
224
|
+
|
|
225
|
+
nb_slices = btc_option_chain.ttms.size
|
|
226
|
+
fig, axs = plt.subplots(1, nb_slices, figsize=(4*nb_slices, 3), tight_layout=True)
|
|
227
|
+
|
|
228
|
+
for i in range(nb_slices):
|
|
229
|
+
ax = axs[i] if nb_slices>1 else axs
|
|
230
|
+
ax.plot(btc_option_chain.strikes_ttms[i], ivols_logsv[i], label="LOG_SV", marker="*")
|
|
231
|
+
ax.plot(btc_option_chain.strikes_ttms[i], ivols_rough_logsv[i], label="ROUGH_LOG_SV", marker="o")
|
|
232
|
+
ax.set_title("Expiry: " + btc_option_chain.ids[i])
|
|
233
|
+
ax.legend()
|
|
234
|
+
fig.suptitle(f"Conventional LogSV model vs Rough LogSV, H={H:.2f} via {N}f Markovian approximation",
|
|
235
|
+
color="darkblue")
|
|
236
|
+
|
|
130
237
|
elif unit_test == UnitTests.CALIBRATE_MODEL_TO_BTC_OPTIONS:
|
|
131
238
|
btc_option_chain = sv.get_btc_test_chain_data()
|
|
132
239
|
params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=2.18, beta=0.15, volvol=2.0)
|
|
@@ -140,12 +247,14 @@ def run_unit_test(unit_test: UnitTests):
|
|
|
140
247
|
|
|
141
248
|
elif unit_test == UnitTests.CALIBRATE_MODEL_TO_BTC_OPTIONS_WITH_MC:
|
|
142
249
|
btc_option_chain = sv.get_btc_test_chain_data()
|
|
143
|
-
params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=
|
|
250
|
+
params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=0.0, beta=0.15, volvol=2.0)
|
|
251
|
+
params0.H = 0.3
|
|
252
|
+
params0.approximate_kernel(T=btc_option_chain.ttms[-1], N=3)
|
|
144
253
|
btc_calibrated_params = logsv_pricer.calibrate_model_params_to_chain(option_chain=btc_option_chain,
|
|
145
254
|
params0=params0,
|
|
146
255
|
model_calibration_type=LogsvModelCalibrationType.PARAMS4,
|
|
147
256
|
constraints_type=sv.ConstraintsType.INVERSE_MARTINGALE,
|
|
148
|
-
calibration_engine=sv.CalibrationEngine.
|
|
257
|
+
calibration_engine=sv.CalibrationEngine.ROUGH_MC,
|
|
149
258
|
nb_path=100000,
|
|
150
259
|
seed=7)
|
|
151
260
|
print(btc_calibrated_params)
|
|
@@ -157,7 +266,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
|
157
266
|
|
|
158
267
|
if __name__ == '__main__':
|
|
159
268
|
|
|
160
|
-
unit_test = UnitTests.
|
|
269
|
+
unit_test = UnitTests.BENCHM_ROUGH_PRICER
|
|
161
270
|
|
|
162
271
|
is_run_all_tests = False
|
|
163
272
|
if is_run_all_tests:
|
{stochvolmodels-1.0.28 → stochvolmodels-1.0.30}/stochvolmodels/pricers/logsv/logsv_params.py
RENAMED
|
@@ -9,6 +9,7 @@ from typing import Optional, Dict, Any
|
|
|
9
9
|
|
|
10
10
|
from stochvolmodels import VariableType, find_nearest
|
|
11
11
|
from stochvolmodels.pricers.model_pricer import ModelParams
|
|
12
|
+
from stochvolmodels.pricers.rough_logsv.RoughKernel import european_rule
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@dataclass
|
|
@@ -23,10 +24,23 @@ class LogSvParams(ModelParams):
|
|
|
23
24
|
beta: float = -1.0
|
|
24
25
|
volvol: float = 1.0
|
|
25
26
|
vol_backbone: pd.Series = None
|
|
27
|
+
H: float = 0.5
|
|
28
|
+
weights: np.ndarray = None
|
|
29
|
+
nodes: np.ndarray = None
|
|
26
30
|
|
|
27
31
|
def __post_init__(self):
|
|
28
32
|
if self.kappa2 is None:
|
|
29
33
|
self.kappa2 = self.kappa1 / self.theta
|
|
34
|
+
assert 1e-4 < self.H <= 0.5
|
|
35
|
+
|
|
36
|
+
def approximate_kernel(self, T: float, N: int):
|
|
37
|
+
assert 1 <= N <= 5 # not keen to use large N
|
|
38
|
+
if N > 1 and self.H<0.49:
|
|
39
|
+
self.nodes, self.weights = european_rule(self.H, N, T)
|
|
40
|
+
else:
|
|
41
|
+
self.weights = np.array([1.0])
|
|
42
|
+
self.nodes = np.array([1e-3])
|
|
43
|
+
|
|
30
44
|
|
|
31
45
|
def to_dict(self) -> Dict[str, Any]:
|
|
32
46
|
return asdict(self)
|
|
@@ -23,6 +23,7 @@ import stochvolmodels.pricers.logsv.affine_expansion as afe
|
|
|
23
23
|
from stochvolmodels.pricers.model_pricer import ModelPricer
|
|
24
24
|
from stochvolmodels.pricers.logsv.affine_expansion import ExpansionOrder
|
|
25
25
|
from stochvolmodels.pricers.logsv.vol_moments_ode import fit_model_vol_backbone_to_varswaps
|
|
26
|
+
from stochvolmodels.pricers.rough_logsv.split_simulation import log_spot_full_combined
|
|
26
27
|
|
|
27
28
|
# data
|
|
28
29
|
from stochvolmodels.data.option_chain import OptionChain
|
|
@@ -47,6 +48,7 @@ class ConstraintsType(Enum):
|
|
|
47
48
|
class CalibrationEngine(Enum):
|
|
48
49
|
ANALYTIC = 1
|
|
49
50
|
MC = 2
|
|
51
|
+
ROUGH_MC = 3
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
LOGSV_BTC_PARAMS = LogSvParams(sigma0=0.8376, theta=1.0413, kappa1=3.1844, kappa2=3.058, beta=0.1514, volvol=1.8458)
|
|
@@ -154,21 +156,30 @@ class LogSVPricer(ModelPricer):
|
|
|
154
156
|
kappa1=params0.kappa1,
|
|
155
157
|
kappa2=params0.kappa2,
|
|
156
158
|
beta=pars[2],
|
|
157
|
-
volvol=pars[3]
|
|
159
|
+
volvol=pars[3],
|
|
160
|
+
H=params0.H,
|
|
161
|
+
nodes=params0.nodes,
|
|
162
|
+
weights=params0.weights)
|
|
158
163
|
elif model_calibration_type == LogsvModelCalibrationType.PARAMS5:
|
|
159
164
|
fit_params = LogSvParams(sigma0=pars[0],
|
|
160
165
|
theta=pars[1],
|
|
161
166
|
kappa1=pars[2],
|
|
162
167
|
kappa2=None,
|
|
163
168
|
beta=pars[3],
|
|
164
|
-
volvol=pars[4]
|
|
169
|
+
volvol=pars[4],
|
|
170
|
+
H=params0.H,
|
|
171
|
+
nodes=params0.nodes,
|
|
172
|
+
weights=params0.weights)
|
|
165
173
|
elif model_calibration_type == LogsvModelCalibrationType.PARAMS_WITH_VARSWAP_FIT:
|
|
166
174
|
fit_params = LogSvParams(sigma0=params0.sigma0,
|
|
167
175
|
theta=params0.theta,
|
|
168
176
|
kappa1=params0.kappa1,
|
|
169
177
|
kappa2=params0.kappa2,
|
|
170
178
|
beta=pars[0],
|
|
171
|
-
volvol=pars[1]
|
|
179
|
+
volvol=pars[1],
|
|
180
|
+
H=params0.H,
|
|
181
|
+
nodes=params0.nodes,
|
|
182
|
+
weights=params0.weights)
|
|
172
183
|
# set model backbone
|
|
173
184
|
vol_backbone = fit_model_vol_backbone_to_varswaps(log_sv_params=fit_params,
|
|
174
185
|
varswap_strikes=varswap_strikes)
|
|
@@ -180,7 +191,9 @@ class LogSVPricer(ModelPricer):
|
|
|
180
191
|
|
|
181
192
|
if calibration_engine == CalibrationEngine.MC:
|
|
182
193
|
W0s, W1s, dts = get_randoms_for_chain_valuation(ttms=option_chain.ttms, nb_path=nb_path, nb_steps_per_year=nb_steps, seed=seed)
|
|
183
|
-
|
|
194
|
+
if calibration_engine == CalibrationEngine.ROUGH_MC:
|
|
195
|
+
Z0, Z1, grid_ttms = get_randoms_for_rough_vol_chain_valuation(ttms=option_chain.ttms, nb_path=nb_path,
|
|
196
|
+
nb_steps_per_year=nb_steps, seed=seed)
|
|
184
197
|
def objective(pars: np.ndarray, args: np.ndarray) -> float:
|
|
185
198
|
params = parse_model_params(pars=pars)
|
|
186
199
|
|
|
@@ -207,6 +220,25 @@ class LogSVPricer(ModelPricer):
|
|
|
207
220
|
# print(f"option_prices_ttm\n{option_prices_ttm}")
|
|
208
221
|
# print(f"model_vols\n{model_vols}")
|
|
209
222
|
|
|
223
|
+
elif calibration_engine == CalibrationEngine.ROUGH_MC:
|
|
224
|
+
option_prices_ttm, option_std_ttm = rough_logsv_mc_chain_pricer_fixed_randoms(ttms=option_chain.ttms,
|
|
225
|
+
forwards=option_chain.forwards,
|
|
226
|
+
discfactors=option_chain.discfactors,
|
|
227
|
+
strikes_ttms=option_chain.strikes_ttms,
|
|
228
|
+
optiontypes_ttms=option_chain.optiontypes_ttms,
|
|
229
|
+
Z0=Z0,
|
|
230
|
+
Z1=Z1,
|
|
231
|
+
sigma0=params.sigma0,
|
|
232
|
+
theta=params.theta,
|
|
233
|
+
kappa1=params.kappa1,
|
|
234
|
+
kappa2=params.kappa2,
|
|
235
|
+
beta=params.beta,
|
|
236
|
+
orthog_vol=params.volvol,
|
|
237
|
+
weights=params.weights,
|
|
238
|
+
nodes=params.nodes,
|
|
239
|
+
timegrids=grid_ttms)
|
|
240
|
+
model_vols = option_chain.compute_model_ivols_from_chain_data(model_prices=option_prices_ttm)
|
|
241
|
+
|
|
210
242
|
else:
|
|
211
243
|
raise NotImplementedError(f"{calibration_engine}")
|
|
212
244
|
|
|
@@ -698,13 +730,14 @@ def simulate_logsv_x_vol_terminal(ttm: float,
|
|
|
698
730
|
return x0, sigma0, qvar0
|
|
699
731
|
|
|
700
732
|
|
|
733
|
+
|
|
701
734
|
def get_randoms_for_chain_valuation(ttms: np.ndarray,
|
|
702
735
|
nb_path: int = 100000,
|
|
703
736
|
nb_steps_per_year: int = 360,
|
|
704
737
|
seed: int = 10
|
|
705
738
|
) -> Tuple[List[np.ndarray], List[np.ndarray], List[np.ndarray]]:
|
|
706
739
|
"""
|
|
707
|
-
we need to fix random normals for
|
|
740
|
+
we need to fix random normals for subsequent evaluation using mc slices
|
|
708
741
|
outputs as numpy lists
|
|
709
742
|
"""
|
|
710
743
|
#
|
|
@@ -722,6 +755,23 @@ def get_randoms_for_chain_valuation(ttms: np.ndarray,
|
|
|
722
755
|
ttm0 = ttm
|
|
723
756
|
return W0s, W1s, dts
|
|
724
757
|
|
|
758
|
+
def get_randoms_for_rough_vol_chain_valuation(ttms: np.ndarray,
|
|
759
|
+
nb_path: int = 100000,
|
|
760
|
+
nb_steps_per_year: int = 360,
|
|
761
|
+
seed: int = 10
|
|
762
|
+
) -> Tuple[np.ndarray, np.ndarray, List[np.ndarray]]:
|
|
763
|
+
set_seed(seed)
|
|
764
|
+
grid_ttms = List()
|
|
765
|
+
nb_steps_ttms = np.zeros_like(ttms).astype(int)
|
|
766
|
+
for i, ttm in enumerate(ttms):
|
|
767
|
+
nb_steps, dt, grid_t = set_time_grid(ttm, nb_steps_per_year)
|
|
768
|
+
nb_steps_ttms[i] = nb_steps
|
|
769
|
+
grid_ttms.append(grid_t)
|
|
770
|
+
Z0 = np.random.normal(0, 1, size=(nb_steps_ttms[-1], nb_path))
|
|
771
|
+
Z1 = np.random.normal(0, 1, size=(nb_steps_ttms[-1], nb_path))
|
|
772
|
+
|
|
773
|
+
return Z0, Z1, grid_ttms
|
|
774
|
+
|
|
725
775
|
|
|
726
776
|
@njit(cache=False, fastmath=True)
|
|
727
777
|
def logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
|
|
@@ -787,6 +837,68 @@ def logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
|
|
|
787
837
|
|
|
788
838
|
return option_prices_ttm, option_std_ttm
|
|
789
839
|
|
|
840
|
+
def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
|
|
841
|
+
forwards: np.ndarray,
|
|
842
|
+
discfactors: np.ndarray,
|
|
843
|
+
strikes_ttms: Tuple[np.ndarray, ...],
|
|
844
|
+
optiontypes_ttms: Tuple[np.ndarray, ...],
|
|
845
|
+
Z0: np.ndarray,
|
|
846
|
+
Z1: np.ndarray,
|
|
847
|
+
sigma0: float,
|
|
848
|
+
theta: float,
|
|
849
|
+
kappa1: float,
|
|
850
|
+
kappa2: float,
|
|
851
|
+
beta: float,
|
|
852
|
+
orthog_vol: float,
|
|
853
|
+
weights: np.ndarray,
|
|
854
|
+
nodes: np.ndarray,
|
|
855
|
+
timegrids: List[np.ndarray],
|
|
856
|
+
variable_type: VariableType = VariableType.LOG_RETURN
|
|
857
|
+
) -> Tuple[List[np.ndarray], List[np.ndarray]]:
|
|
858
|
+
assert weights.shape == nodes.shape and weights.ndim == 1
|
|
859
|
+
assert kappa2 == 0.0
|
|
860
|
+
N = nodes.size
|
|
861
|
+
v0 = sigma0 / np.sum(weights) * np.ones((N,))
|
|
862
|
+
|
|
863
|
+
# need to redenote coefficients
|
|
864
|
+
lamda = kappa1
|
|
865
|
+
theta = kappa1 * theta
|
|
866
|
+
volvol = np.sqrt(beta ** 2 + orthog_vol ** 2)
|
|
867
|
+
rho = beta / volvol
|
|
868
|
+
|
|
869
|
+
nb_path = Z0.shape[1]
|
|
870
|
+
v0_vec = np.repeat(v0[:, None], nb_path, axis=1)
|
|
871
|
+
v_init = v0_vec.copy()
|
|
872
|
+
log_s0 = 0.0
|
|
873
|
+
|
|
874
|
+
# outputs as numpy lists
|
|
875
|
+
option_prices_ttm = List()
|
|
876
|
+
option_std_ttm = List()
|
|
877
|
+
for ttm, forward, discfactor, strikes_ttm, optiontypes_ttm, timegrid in zip(ttms, forwards,
|
|
878
|
+
discfactors,
|
|
879
|
+
strikes_ttms,
|
|
880
|
+
optiontypes_ttms,
|
|
881
|
+
timegrids):
|
|
882
|
+
nb_steps = timegrid.size - 1
|
|
883
|
+
Z0_ = Z0[:nb_steps]
|
|
884
|
+
Z1_ = Z1[:nb_steps]
|
|
885
|
+
log_spot_str, vol_str, qv_str = log_spot_full_combined(nodes, weights, v0_vec, theta, lamda, log_s0, v_init,
|
|
886
|
+
rho, volvol, timegrid, nb_path, Z0_, Z1_)
|
|
887
|
+
print(f"Number of paths with negative vol: {np.sum(weights @ vol_str < 0.0)}")
|
|
888
|
+
print(f"Mean spot Strand: {np.mean(np.exp(log_spot_str))}")
|
|
889
|
+
|
|
890
|
+
option_prices, option_std = compute_mc_vars_payoff(x0=log_spot_str, sigma0=vol_str, qvar0=qv_str,
|
|
891
|
+
ttm=ttm,
|
|
892
|
+
forward=forward,
|
|
893
|
+
strikes_ttm=strikes_ttm,
|
|
894
|
+
optiontypes_ttm=optiontypes_ttm,
|
|
895
|
+
discfactor=discfactor,
|
|
896
|
+
variable_type=variable_type)
|
|
897
|
+
option_prices_ttm.append(option_prices)
|
|
898
|
+
option_std_ttm.append(option_std)
|
|
899
|
+
|
|
900
|
+
return option_prices_ttm, option_std_ttm
|
|
901
|
+
|
|
790
902
|
|
|
791
903
|
class UnitTests(Enum):
|
|
792
904
|
CHAIN_PRICER = 1
|
|
@@ -865,7 +977,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
|
865
977
|
fig = logsv_pricer.plot_model_ivols_vs_mc(option_chain=option_chain,
|
|
866
978
|
params=LOGSV_BTC_PARAMS,
|
|
867
979
|
variable_type=VariableType.Q_VAR)
|
|
868
|
-
|
|
980
|
+
|
|
869
981
|
elif unit_test == UnitTests.VOL_PATHS:
|
|
870
982
|
logsv_pricer = LogSVPricer()
|
|
871
983
|
nb_path = 10
|