stochvolmodels 1.0.25__tar.gz → 1.0.27__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.25 → stochvolmodels-1.0.27}/PKG-INFO +14 -2
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/README.md +13 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/pyproject.toml +1 -4
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/__init__.py +7 -4
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/data/option_chain.py +3 -4
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_heston_sv_pricer.py +0 -2
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_lognormal_sv_pricer.py +48 -1
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_pricing_options_on_qvar.py +2 -3
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py +2 -2
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/gmm_pricer.py +1 -55
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/hawkes_jd_pricer.py +1 -1
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/heston_pricer.py +3 -2
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/logsv_params.py +6 -5
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv_pricer.py +152 -15
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/tests/bsm_mgf_pricer.py +1 -1
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/funcs.py +29 -1
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/mc_payoffs.py +0 -1
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/mgf_pricer.py +0 -1
- stochvolmodels-1.0.25/stochvolmodels/data/fetch_option_chain.py +0 -176
- stochvolmodels-1.0.25/stochvolmodels/examples/run_gmm_fit.py +0 -41
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/LICENSE.txt +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/data/__init__.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/data/test_option_chain.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/quick_run_lognormal_sv_pricer.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_heston.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/__init__.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/__init__.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/bachelier.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/bsm.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/tdist.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_affine_expansion.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_core.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/__init__.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/affine_expansion.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/vol_moments_ode.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/model_pricer.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/tdist_pricer.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/tests/__init__.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/tests/qv_pricer.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/__init__.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/config.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/plots.py +0 -0
- {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/var_swap_pricer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: stochvolmodels
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.27
|
|
4
4
|
Summary: Implementation of stochastic volatility models for option pricing
|
|
5
5
|
License: LICENSE.txt
|
|
6
6
|
Keywords: volatility,options,Black-Scholes,Heston,Monte-Carlo
|
|
@@ -32,7 +32,6 @@ Requires-Dist: numpy (>=1.22.4)
|
|
|
32
32
|
Requires-Dist: pandas (>=0.19)
|
|
33
33
|
Requires-Dist: scipy (>=1.3)
|
|
34
34
|
Requires-Dist: seaborn (>=0.11.2)
|
|
35
|
-
Requires-Dist: statsmodels (>=0.13.0)
|
|
36
35
|
Project-URL: Documentation, https://github.com/ArturSepp/StochVolModels
|
|
37
36
|
Project-URL: Issues, https://github.com/ArturSepp/StochVolModels/issues
|
|
38
37
|
Project-URL: Personal website, https://artursepp.com
|
|
@@ -75,6 +74,19 @@ Close using
|
|
|
75
74
|
git clone https://github.com/ArturSepp/StochVolModels.git
|
|
76
75
|
```
|
|
77
76
|
|
|
77
|
+
Core dependencies:
|
|
78
|
+
python = ">=3.8",
|
|
79
|
+
numba = ">=0.56.4",
|
|
80
|
+
numpy = ">=1.22.4",
|
|
81
|
+
scipy = ">=1.10",
|
|
82
|
+
pandas = ">=2.2.0",
|
|
83
|
+
matplotlib = ">=3.2.2",
|
|
84
|
+
seaborn = ">=0.12.2"
|
|
85
|
+
|
|
86
|
+
Optional dependencies:
|
|
87
|
+
qis ">=2.1.38" (for running code in my_papers and volatility_book)
|
|
88
|
+
|
|
89
|
+
|
|
78
90
|
# Table of contents
|
|
79
91
|
1. [Model Interface](#introduction)
|
|
80
92
|
1. [Log-normal stochastic volatility model](#logsv)
|
|
@@ -34,6 +34,19 @@ Close using
|
|
|
34
34
|
git clone https://github.com/ArturSepp/StochVolModels.git
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
Core dependencies:
|
|
38
|
+
python = ">=3.8",
|
|
39
|
+
numba = ">=0.56.4",
|
|
40
|
+
numpy = ">=1.22.4",
|
|
41
|
+
scipy = ">=1.10",
|
|
42
|
+
pandas = ">=2.2.0",
|
|
43
|
+
matplotlib = ">=3.2.2",
|
|
44
|
+
seaborn = ">=0.12.2"
|
|
45
|
+
|
|
46
|
+
Optional dependencies:
|
|
47
|
+
qis ">=2.1.38" (for running code in my_papers and volatility_book)
|
|
48
|
+
|
|
49
|
+
|
|
37
50
|
# Table of contents
|
|
38
51
|
1. [Model Interface](#introduction)
|
|
39
52
|
1. [Log-normal stochastic volatility model](#logsv)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "stochvolmodels"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.27"
|
|
4
4
|
description = "Implementation of stochastic volatility models for option pricing"
|
|
5
5
|
license = "LICENSE.txt"
|
|
6
6
|
authors = ["Artur Sepp <artursepp@gmail.com>"]
|
|
@@ -37,7 +37,6 @@ python = ">=3.8"
|
|
|
37
37
|
numba = ">=0.55"
|
|
38
38
|
numpy = ">=1.22.4"
|
|
39
39
|
scipy = ">=1.3"
|
|
40
|
-
statsmodels = ">=0.13.0"
|
|
41
40
|
pandas = ">=0.19"
|
|
42
41
|
matplotlib = ">=3.5.2"
|
|
43
42
|
seaborn = ">=0.11.2"
|
|
@@ -45,5 +44,3 @@ seaborn = ">=0.11.2"
|
|
|
45
44
|
[build-system]
|
|
46
45
|
requires = ["poetry-core>=1.0.0"]
|
|
47
46
|
build-backend = "poetry.core.masonry.api"
|
|
48
|
-
|
|
49
|
-
|
|
@@ -22,7 +22,8 @@ from stochvolmodels.utils.funcs import (
|
|
|
22
22
|
to_flat_np_array,
|
|
23
23
|
update_kwargs,
|
|
24
24
|
ncdf,
|
|
25
|
-
npdf
|
|
25
|
+
npdf,
|
|
26
|
+
find_nearest
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
from stochvolmodels.pricers.analytic.bsm import (
|
|
@@ -105,14 +106,16 @@ from stochvolmodels.pricers.logsv_pricer import (
|
|
|
105
106
|
LOGSV_BTC_PARAMS,
|
|
106
107
|
LogSVPricer,
|
|
107
108
|
LogsvModelCalibrationType,
|
|
108
|
-
ConstraintsType
|
|
109
|
+
ConstraintsType,
|
|
110
|
+
CalibrationEngine,
|
|
111
|
+
get_randoms_for_chain_valuation,
|
|
112
|
+
logsv_mc_chain_pricer_fixed_randoms
|
|
109
113
|
)
|
|
110
114
|
from stochvolmodels.pricers.logsv.logsv_params import LogSvParams
|
|
111
115
|
|
|
112
116
|
from stochvolmodels.pricers.gmm_pricer import (
|
|
113
117
|
GmmParams,
|
|
114
|
-
GmmPricer
|
|
115
|
-
plot_gmm_pdfs
|
|
118
|
+
GmmPricer
|
|
116
119
|
)
|
|
117
120
|
|
|
118
121
|
from stochvolmodels.pricers.tdist_pricer import (
|
|
@@ -8,16 +8,15 @@ data is provided as:
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from typing import Tuple, Optional
|
|
13
|
-
|
|
14
|
-
import pandas as pd
|
|
15
14
|
from numba.typed import List
|
|
16
15
|
|
|
17
16
|
import stochvolmodels.pricers.analytic.bsm as bsm
|
|
18
|
-
from stochvolmodels.utils.var_swap_pricer import compute_var_swap_strike
|
|
19
|
-
from stochvolmodels.pricers.factor_hjm.rate_core import get_default_swap_term_structure, swap_rate
|
|
20
17
|
import stochvolmodels.pricers.analytic.bachelier as bachel
|
|
18
|
+
from stochvolmodels.utils.var_swap_pricer import compute_var_swap_strike
|
|
19
|
+
from stochvolmodels.pricers.factor_hjm.rate_core import get_default_swap_term_structure, swap_rate
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@dataclass
|
{stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_lognormal_sv_pricer.py
RENAMED
|
@@ -17,6 +17,8 @@ class UnitTests(Enum):
|
|
|
17
17
|
COMPARE_MODEL_VOLS_TO_MC = 4
|
|
18
18
|
PLOT_FIT_TO_BITCOIN_OPTION_CHAIN = 5
|
|
19
19
|
CALIBRATE_MODEL_TO_BTC_OPTIONS = 6
|
|
20
|
+
MC_WITH_FIXED_RANDOMS = 7
|
|
21
|
+
CALIBRATE_MODEL_TO_BTC_OPTIONS_WITH_MC = 8
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
def run_unit_test(unit_test: UnitTests):
|
|
@@ -105,12 +107,57 @@ def run_unit_test(unit_test: UnitTests):
|
|
|
105
107
|
print(btc_calibrated_params)
|
|
106
108
|
logsv_pricer.plot_model_ivols_vs_bid_ask(option_chain=btc_option_chain,
|
|
107
109
|
params=btc_calibrated_params)
|
|
110
|
+
|
|
111
|
+
elif unit_test == UnitTests.MC_WITH_FIXED_RANDOMS:
|
|
112
|
+
btc_option_chain = sv.get_btc_test_chain_data()
|
|
113
|
+
W0s, W1s, dts = sv.get_randoms_for_chain_valuation(ttms=btc_option_chain.ttms,
|
|
114
|
+
nb_path=10000,
|
|
115
|
+
nb_steps_per_year=360,
|
|
116
|
+
seed=10)
|
|
117
|
+
print(dts)
|
|
118
|
+
params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=2.18, beta=0.15, volvol=2.0)
|
|
119
|
+
vol_backbone_etas = params.get_vol_backbone_etas(ttms=btc_option_chain.ttms)
|
|
120
|
+
args = dict(ttms=btc_option_chain.ttms,
|
|
121
|
+
forwards=btc_option_chain.forwards,
|
|
122
|
+
discfactors=btc_option_chain.discfactors,
|
|
123
|
+
strikes_ttms=btc_option_chain.strikes_ttms,
|
|
124
|
+
optiontypes_ttms=btc_option_chain.optiontypes_ttms,
|
|
125
|
+
W0s=W0s,
|
|
126
|
+
W1s=W1s,
|
|
127
|
+
dts=dts,
|
|
128
|
+
v0=params0.sigma0,
|
|
129
|
+
theta=params0.theta,
|
|
130
|
+
kappa1=params0.kappa1,
|
|
131
|
+
kappa2=params0.kappa2,
|
|
132
|
+
beta=params0.beta,
|
|
133
|
+
volvol=params0.volvol,
|
|
134
|
+
vol_backbone_etas=vol_backbone_etas)
|
|
135
|
+
option_prices_ttm, option_std_ttm = sv.logsv_mc_chain_pricer_fixed_randoms(**args)
|
|
136
|
+
print(option_prices_ttm)
|
|
137
|
+
|
|
138
|
+
option_prices_ttm, option_std_ttm = sv.logsv_mc_chain_pricer_fixed_randoms(**args)
|
|
139
|
+
print(option_prices_ttm)
|
|
140
|
+
|
|
141
|
+
elif unit_test == UnitTests.CALIBRATE_MODEL_TO_BTC_OPTIONS_WITH_MC:
|
|
142
|
+
btc_option_chain = sv.get_btc_test_chain_data()
|
|
143
|
+
params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=2.18, beta=0.15, volvol=2.0)
|
|
144
|
+
btc_calibrated_params = logsv_pricer.calibrate_model_params_to_chain(option_chain=btc_option_chain,
|
|
145
|
+
params0=params0,
|
|
146
|
+
model_calibration_type=LogsvModelCalibrationType.PARAMS4,
|
|
147
|
+
constraints_type=sv.ConstraintsType.INVERSE_MARTINGALE,
|
|
148
|
+
calibration_engine=sv.CalibrationEngine.MC,
|
|
149
|
+
nb_path=100000,
|
|
150
|
+
seed=7)
|
|
151
|
+
print(btc_calibrated_params)
|
|
152
|
+
logsv_pricer.plot_model_ivols_vs_bid_ask(option_chain=btc_option_chain,
|
|
153
|
+
params=btc_calibrated_params)
|
|
154
|
+
|
|
108
155
|
plt.show()
|
|
109
156
|
|
|
110
157
|
|
|
111
158
|
if __name__ == '__main__':
|
|
112
159
|
|
|
113
|
-
unit_test = UnitTests.
|
|
160
|
+
unit_test = UnitTests.CALIBRATE_MODEL_TO_BTC_OPTIONS_WITH_MC
|
|
114
161
|
|
|
115
162
|
is_run_all_tests = False
|
|
116
163
|
if is_run_all_tests:
|
|
@@ -3,7 +3,6 @@ run valuation for options on quadratic variance
|
|
|
3
3
|
"""
|
|
4
4
|
import numpy as np
|
|
5
5
|
import matplotlib.pyplot as plt
|
|
6
|
-
import qis as qis
|
|
7
6
|
import stochvolmodels.data.test_option_chain as chains
|
|
8
7
|
from numba.typed import List
|
|
9
8
|
from stochvolmodels import (LogSVPricer, LogSvParams, compute_analytic_qvar, OptionChain,
|
|
@@ -37,7 +36,7 @@ fig1 = logsv_pricer.plot_model_ivols_vs_mc(option_chain=option_chain,
|
|
|
37
36
|
params=LOGSV_BTC_PARAMS,
|
|
38
37
|
variable_type=VariableType.Q_VAR,
|
|
39
38
|
nb_path=nb_path)
|
|
40
|
-
|
|
39
|
+
fig1.suptitle('Implied variance skew by Log-Normal SV model')
|
|
41
40
|
|
|
42
41
|
# run Heston prices
|
|
43
42
|
heston_pricer = HestonPricer()
|
|
@@ -45,7 +44,7 @@ fig2 = heston_pricer.plot_model_ivols_vs_mc(option_chain=option_chain,
|
|
|
45
44
|
params=BTC_HESTON_PARAMS,
|
|
46
45
|
variable_type=VariableType.Q_VAR,
|
|
47
46
|
nb_path=nb_path)
|
|
48
|
-
|
|
47
|
+
fig2.suptitle('Implied variance skew by Heston SV model')
|
|
49
48
|
|
|
50
49
|
|
|
51
50
|
plt.show()
|
|
@@ -877,7 +877,7 @@ def simulate_logsv_MF(ttms: np.ndarray,
|
|
|
877
877
|
if seed is None:
|
|
878
878
|
seed = 16
|
|
879
879
|
np.random.seed(seed) # fix seed
|
|
880
|
-
nb_steps, dt, grid_t = set_time_grid(ttm=ttm,
|
|
880
|
+
nb_steps, dt, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=360)
|
|
881
881
|
if W is None:
|
|
882
882
|
W0 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path, basis.get_nb_factors())) # TODO: undo
|
|
883
883
|
W1 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path)) # TODO: undo
|
|
@@ -1017,7 +1017,7 @@ def simulate_logsv_futures_MF2(params: MultiFactRateLogSvParams,
|
|
|
1017
1017
|
if seed is None:
|
|
1018
1018
|
seed = 16
|
|
1019
1019
|
np.random.seed(seed) # fix seed
|
|
1020
|
-
nb_steps, dt, grid_t = set_time_grid(ttm=ttm,
|
|
1020
|
+
nb_steps, dt, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=720)
|
|
1021
1021
|
if W is None:
|
|
1022
1022
|
W0 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path, basis.get_nb_factors())) # TODO: undo
|
|
1023
1023
|
W1 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path)) # TODO: undo
|
|
@@ -4,16 +4,12 @@ implementation of gaussian mixture pricer and calibration
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import matplotlib.pyplot as plt
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
|
|
8
|
-
import pandas as pd
|
|
9
|
-
import qis as qis
|
|
10
|
-
import seaborn as sns
|
|
11
7
|
from scipy.optimize import minimize
|
|
12
8
|
from numba import njit
|
|
13
9
|
from numba.typed import List
|
|
14
10
|
from typing import Tuple
|
|
15
11
|
from enum import Enum
|
|
16
|
-
|
|
12
|
+
# project
|
|
17
13
|
import stochvolmodels.pricers.analytic.bsm as bsm
|
|
18
14
|
from stochvolmodels.utils.funcs import to_flat_np_array, timer, npdf
|
|
19
15
|
from stochvolmodels.pricers.model_pricer import ModelParams, ModelPricer
|
|
@@ -263,56 +259,6 @@ def gmm_vanilla_chain_pricer(gmm_weights: np.ndarray,
|
|
|
263
259
|
return model_prices_ttms
|
|
264
260
|
|
|
265
261
|
|
|
266
|
-
def plot_gmm_pdfs(params: GmmParams,
|
|
267
|
-
option_chain0: OptionChain,
|
|
268
|
-
nstdev: float = 10.0,
|
|
269
|
-
titles: List[str] = None,
|
|
270
|
-
axs: List[plt.Subplot] = None
|
|
271
|
-
) -> plt.Figure:
|
|
272
|
-
"""
|
|
273
|
-
plot gmm pdf and model fit
|
|
274
|
-
"""
|
|
275
|
-
stdev = nstdev * params.get_get_avg_vol() * np.sqrt(params.ttm)
|
|
276
|
-
x = np.linspace(-stdev, stdev, 3000)
|
|
277
|
-
state_pdfs, agg_pdf = params.compute_state_pdfs(x=x)
|
|
278
|
-
|
|
279
|
-
columns = []
|
|
280
|
-
for idx in range(len(params.gmm_weights)):
|
|
281
|
-
columns.append(
|
|
282
|
-
f"state-{idx + 1}: mean={params.gmm_mus[idx]:0.2f}, vol={params.gmm_vols[idx]:0.2f}, weight={params.gmm_weights[idx]:0.2f}")
|
|
283
|
-
|
|
284
|
-
state_pdfs = pd.DataFrame(state_pdfs, index=x, columns=columns)
|
|
285
|
-
agg_pdf = pd.Series(agg_pdf, index=x, name='Aggregate PDF')
|
|
286
|
-
df = pd.concat([agg_pdf, state_pdfs], axis=1)
|
|
287
|
-
|
|
288
|
-
kwargs = dict(fontsize=14, framealpha=0.80)
|
|
289
|
-
|
|
290
|
-
if axs is None:
|
|
291
|
-
with sns.axes_style("darkgrid"):
|
|
292
|
-
fig, axs = plt.subplots(1, 2, figsize=(16, 4.5))
|
|
293
|
-
else:
|
|
294
|
-
fig = None
|
|
295
|
-
|
|
296
|
-
qis.plot_line(df=df,
|
|
297
|
-
linestyles=['--'] + ['-'] * len(params.gmm_weights),
|
|
298
|
-
y_limits=(0.0, None),
|
|
299
|
-
xvar_format='{:,.2f}',
|
|
300
|
-
xlabel='log-price',
|
|
301
|
-
first_color_fixed=True,
|
|
302
|
-
ax=axs[0],
|
|
303
|
-
**kwargs)
|
|
304
|
-
axs[0].get_lines()[0].set_linewidth(4.0)
|
|
305
|
-
axs[0].get_legend().get_lines()[0].set_linewidth(4.0)
|
|
306
|
-
qis.set_title(ax=axs[0], title='(A) State PDF and Aggregate Risk-Neutral PDF', **kwargs)
|
|
307
|
-
|
|
308
|
-
gmm_pricer = GmmPricer()
|
|
309
|
-
gmm_pricer.plot_model_ivols_vs_bid_ask(option_chain=option_chain0, params=params,
|
|
310
|
-
is_log_strike_xaxis=True,
|
|
311
|
-
axs=[axs[1]],
|
|
312
|
-
**kwargs)
|
|
313
|
-
return fig
|
|
314
|
-
|
|
315
|
-
|
|
316
262
|
class UnitTests(Enum):
|
|
317
263
|
CALIBRATOR = 1
|
|
318
264
|
|
|
@@ -696,7 +696,7 @@ def simulate_hawkesjd_terminal(ttm: float,
|
|
|
696
696
|
assert lambda_m0.shape[0] == nb_path
|
|
697
697
|
|
|
698
698
|
# vars
|
|
699
|
-
nb_steps, dt, grid_t = set_time_grid(ttm=ttm,
|
|
699
|
+
nb_steps, dt, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=5*360) # need small dt step for large intensities
|
|
700
700
|
W0 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path))
|
|
701
701
|
U_P = -np.log(np.random.uniform(low=1e-16, high=1.0, size=(nb_steps, nb_path)))/dt
|
|
702
702
|
U_M = -np.log(np.random.uniform(low=1e-16, high=1.0, size=(nb_steps, nb_path)))/dt
|
|
@@ -298,7 +298,7 @@ def simulate_heston_x_vol_terminal(ttm: float,
|
|
|
298
298
|
rho: float,
|
|
299
299
|
volvol: float,
|
|
300
300
|
nb_path: int = 100000,
|
|
301
|
-
|
|
301
|
+
nb_steps_per_year: int = 360
|
|
302
302
|
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
303
303
|
|
|
304
304
|
if x0.shape[0] == 1: # initial value
|
|
@@ -316,7 +316,7 @@ def simulate_heston_x_vol_terminal(ttm: float,
|
|
|
316
316
|
else:
|
|
317
317
|
assert qvar0.shape[0] == nb_path
|
|
318
318
|
|
|
319
|
-
nb_steps, dt, grid_t = set_time_grid(ttm=ttm,
|
|
319
|
+
nb_steps, dt, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=nb_steps_per_year)
|
|
320
320
|
w0 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path))
|
|
321
321
|
w1 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path))
|
|
322
322
|
|
|
@@ -348,6 +348,7 @@ class UnitTests(Enum):
|
|
|
348
348
|
MC_COMPARISION = 4
|
|
349
349
|
MC_COMPARISION_QVAR = 5
|
|
350
350
|
|
|
351
|
+
|
|
351
352
|
def run_unit_test(unit_test: UnitTests):
|
|
352
353
|
|
|
353
354
|
import stochvolmodels.data.test_option_chain as chains
|
{stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/logsv_params.py
RENAMED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""
|
|
2
|
+
implementation of log sv params
|
|
3
|
+
"""
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
6
6
|
from numpy import linalg as la
|
|
7
|
-
from
|
|
7
|
+
from dataclasses import dataclass, asdict
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
8
9
|
|
|
9
|
-
from stochvolmodels import VariableType
|
|
10
|
+
from stochvolmodels import VariableType, find_nearest
|
|
10
11
|
from stochvolmodels.pricers.model_pricer import ModelParams
|
|
11
12
|
|
|
12
13
|
|
|
@@ -15,7 +15,7 @@ from enum import Enum
|
|
|
15
15
|
from stochvolmodels.utils.config import VariableType
|
|
16
16
|
import stochvolmodels.utils.mgf_pricer as mgfp
|
|
17
17
|
from stochvolmodels.utils.mc_payoffs import compute_mc_vars_payoff
|
|
18
|
-
from stochvolmodels.utils.funcs import to_flat_np_array, set_time_grid, timer, compute_histogram_data
|
|
18
|
+
from stochvolmodels.utils.funcs import to_flat_np_array, set_time_grid, timer, compute_histogram_data, set_seed
|
|
19
19
|
|
|
20
20
|
# stochvolmodels pricers
|
|
21
21
|
from stochvolmodels.pricers.logsv.logsv_params import LogSvParams
|
|
@@ -44,6 +44,11 @@ class ConstraintsType(Enum):
|
|
|
44
44
|
INVERSE_MARTINGALE_MOMENT4 = 5 # kappa_2 >= 2.0*beta
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
class CalibrationEngine(Enum):
|
|
48
|
+
ANALYTIC = 1
|
|
49
|
+
MC = 2
|
|
50
|
+
|
|
51
|
+
|
|
47
52
|
LOGSV_BTC_PARAMS = LogSvParams(sigma0=0.8376, theta=1.0413, kappa1=3.1844, kappa2=3.058, beta=0.1514, volvol=1.8458)
|
|
48
53
|
|
|
49
54
|
|
|
@@ -95,7 +100,7 @@ class LogSVPricer(ModelPricer):
|
|
|
95
100
|
is_spot_measure=is_spot_measure,
|
|
96
101
|
variable_type=variable_type,
|
|
97
102
|
nb_path=nb_path,
|
|
98
|
-
|
|
103
|
+
nb_steps_per_year=nb_steps or int(360 * np.max(option_chain.ttms)) + 1)
|
|
99
104
|
|
|
100
105
|
def set_vol_scaler(self, option_chain: OptionChain) -> float:
|
|
101
106
|
"""
|
|
@@ -115,6 +120,10 @@ class LogSVPricer(ModelPricer):
|
|
|
115
120
|
is_unit_ttm_vega: bool = False,
|
|
116
121
|
model_calibration_type: LogsvModelCalibrationType = LogsvModelCalibrationType.PARAMS5,
|
|
117
122
|
constraints_type: ConstraintsType = ConstraintsType.UNCONSTRAINT,
|
|
123
|
+
calibration_engine: CalibrationEngine = CalibrationEngine.ANALYTIC,
|
|
124
|
+
nb_path: int = 100000,
|
|
125
|
+
nb_steps: int = 360,
|
|
126
|
+
seed: int = 10,
|
|
118
127
|
**kwargs
|
|
119
128
|
) -> LogSvParams:
|
|
120
129
|
"""
|
|
@@ -169,9 +178,38 @@ class LogSVPricer(ModelPricer):
|
|
|
169
178
|
raise NotImplementedError(f"{model_calibration_type}")
|
|
170
179
|
return fit_params
|
|
171
180
|
|
|
181
|
+
if calibration_engine == CalibrationEngine.MC:
|
|
182
|
+
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
|
+
|
|
172
184
|
def objective(pars: np.ndarray, args: np.ndarray) -> float:
|
|
173
185
|
params = parse_model_params(pars=pars)
|
|
174
|
-
|
|
186
|
+
|
|
187
|
+
if calibration_engine == CalibrationEngine.ANALYTIC:
|
|
188
|
+
model_vols = self.compute_model_ivols_for_chain(option_chain=option_chain, params=params, vol_scaler=vol_scaler)
|
|
189
|
+
|
|
190
|
+
elif calibration_engine == CalibrationEngine.MC:
|
|
191
|
+
option_prices_ttm, option_std_ttm = logsv_mc_chain_pricer_fixed_randoms(ttms=option_chain.ttms,
|
|
192
|
+
forwards=option_chain.forwards,
|
|
193
|
+
discfactors=option_chain.discfactors,
|
|
194
|
+
strikes_ttms=option_chain.strikes_ttms,
|
|
195
|
+
optiontypes_ttms=option_chain.optiontypes_ttms,
|
|
196
|
+
W0s=W0s,
|
|
197
|
+
W1s=W1s,
|
|
198
|
+
dts=dts,
|
|
199
|
+
v0=params.sigma0,
|
|
200
|
+
theta=params.theta,
|
|
201
|
+
kappa1=params.kappa1,
|
|
202
|
+
kappa2=params.kappa2,
|
|
203
|
+
beta=params.beta,
|
|
204
|
+
volvol=params.volvol,
|
|
205
|
+
vol_backbone_etas=params.get_vol_backbone_etas(ttms=option_chain.ttms))
|
|
206
|
+
model_vols = option_chain.compute_model_ivols_from_chain_data(model_prices=option_prices_ttm)
|
|
207
|
+
print(f"option_prices_ttm\n{option_prices_ttm}")
|
|
208
|
+
print(f"model_vols\n{model_vols}")
|
|
209
|
+
|
|
210
|
+
else:
|
|
211
|
+
raise NotImplementedError(f"{calibration_engine}")
|
|
212
|
+
|
|
175
213
|
resid = np.nansum(weights * np.square(to_flat_np_array(model_vols) - market_vols))
|
|
176
214
|
return resid
|
|
177
215
|
|
|
@@ -278,7 +316,7 @@ class LogSVPricer(ModelPricer):
|
|
|
278
316
|
volvol=params.volvol,
|
|
279
317
|
nb_path=nb_path,
|
|
280
318
|
is_spot_measure=is_spot_measure,
|
|
281
|
-
|
|
319
|
+
nb_steps_per_year=nb_steps,
|
|
282
320
|
brownians=brownians,
|
|
283
321
|
**kwargs)
|
|
284
322
|
return sigma_t, grid_t
|
|
@@ -512,7 +550,7 @@ def logsv_mc_chain_pricer(ttms: np.ndarray,
|
|
|
512
550
|
vol_backbone_etas: np.ndarray,
|
|
513
551
|
is_spot_measure: bool = True,
|
|
514
552
|
nb_path: int = 100000,
|
|
515
|
-
|
|
553
|
+
nb_steps_per_year: int = 360,
|
|
516
554
|
variable_type: VariableType = VariableType.LOG_RETURN
|
|
517
555
|
) -> Tuple[List[np.ndarray], List[np.ndarray]]:
|
|
518
556
|
# starting values
|
|
@@ -538,7 +576,7 @@ def logsv_mc_chain_pricer(ttms: np.ndarray,
|
|
|
538
576
|
volvol=volvol,
|
|
539
577
|
vol_backbone_eta=vol_backbone_eta,
|
|
540
578
|
nb_path=nb_path,
|
|
541
|
-
|
|
579
|
+
nb_steps_per_year=nb_steps_per_year,
|
|
542
580
|
is_spot_measure=is_spot_measure)
|
|
543
581
|
ttm0 = ttm
|
|
544
582
|
option_prices, option_std = compute_mc_vars_payoff(x0=x0, sigma0=sigma0, qvar0=qvar0,
|
|
@@ -564,7 +602,7 @@ def simulate_vol_paths(ttm: float,
|
|
|
564
602
|
volvol: float,
|
|
565
603
|
is_spot_measure: bool = True,
|
|
566
604
|
nb_path: int = 100000,
|
|
567
|
-
|
|
605
|
+
nb_steps_per_year: int = 360,
|
|
568
606
|
brownians: np.ndarray = None,
|
|
569
607
|
**kwargs
|
|
570
608
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
@@ -573,7 +611,7 @@ def simulate_vol_paths(ttm: float,
|
|
|
573
611
|
"""
|
|
574
612
|
sigma0 = v0 * np.ones(nb_path)
|
|
575
613
|
|
|
576
|
-
nb_steps, dt, grid_t = set_time_grid(ttm=ttm,
|
|
614
|
+
nb_steps, dt, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=nb_steps_per_year)
|
|
577
615
|
|
|
578
616
|
if brownians is None:
|
|
579
617
|
brownians = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path))
|
|
@@ -586,7 +624,7 @@ def simulate_vol_paths(ttm: float,
|
|
|
586
624
|
vartheta2 = beta*beta + volvol*volvol
|
|
587
625
|
vartheta = np.sqrt(vartheta2)
|
|
588
626
|
vol_var = np.log(sigma0)
|
|
589
|
-
sigma_t = np.zeros((
|
|
627
|
+
sigma_t = np.zeros((nb_steps_per_year + 1, nb_path)) # sigma grid will increase to include the sigma_0 at t0 = 0
|
|
590
628
|
sigma_t[0, :] = sigma0 # keep first value
|
|
591
629
|
for t_, w1_ in enumerate(brownians):
|
|
592
630
|
vol_var = vol_var + ((kappa1 * theta / sigma0 - kappa1) + kappa2*(theta-sigma0) + adj*sigma0 - 0.5*vartheta2) * dt + vartheta*w1_
|
|
@@ -609,7 +647,10 @@ def simulate_logsv_x_vol_terminal(ttm: float,
|
|
|
609
647
|
vol_backbone_eta: float = 1.0,
|
|
610
648
|
is_spot_measure: bool = True,
|
|
611
649
|
nb_path: int = 100000,
|
|
612
|
-
|
|
650
|
+
nb_steps_per_year: int = 360,
|
|
651
|
+
W0: Optional[np.ndarray] = None,
|
|
652
|
+
W1: Optional[np.ndarray] = None,
|
|
653
|
+
dt: Optional[float] = None
|
|
613
654
|
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
614
655
|
"""
|
|
615
656
|
mc simulator for terminal values of log-return, vol sigma0, and qvar for log sv model
|
|
@@ -628,10 +669,16 @@ def simulate_logsv_x_vol_terminal(ttm: float,
|
|
|
628
669
|
sigma0 = sigma0 * np.ones(nb_path)
|
|
629
670
|
else:
|
|
630
671
|
assert sigma0.shape[0] == nb_path
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
672
|
+
if W0 is None and W1 is None:
|
|
673
|
+
nb_steps1, dt, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=nb_steps_per_year)
|
|
674
|
+
print(f"nb_steps1={nb_steps1}, dt={dt}")
|
|
675
|
+
sdt = np.sqrt(dt)
|
|
676
|
+
W0_ = sdt * np.random.normal(0, 1, size=(nb_steps1, nb_path))
|
|
677
|
+
W1_ = sdt * np.random.normal(0, 1, size=(nb_steps1, nb_path))
|
|
678
|
+
else:
|
|
679
|
+
sdt = np.sqrt(dt)
|
|
680
|
+
W0_ = sdt * W0
|
|
681
|
+
W1_ = sdt * W1
|
|
635
682
|
|
|
636
683
|
if is_spot_measure:
|
|
637
684
|
alpha, adj = -1.0, 0.0
|
|
@@ -641,7 +688,7 @@ def simulate_logsv_x_vol_terminal(ttm: float,
|
|
|
641
688
|
vartheta2 = beta*beta + volvol*volvol
|
|
642
689
|
vol_backbone_eta2 = vol_backbone_eta * vol_backbone_eta
|
|
643
690
|
vol_var = np.log(sigma0)
|
|
644
|
-
for t_, (w0, w1) in enumerate(zip(
|
|
691
|
+
for t_, (w0, w1) in enumerate(zip(W0_, W1_)):
|
|
645
692
|
sigma0_2dt = vol_backbone_eta2 * sigma0 * sigma0 * dt
|
|
646
693
|
x0 = x0 + alpha * 0.5 * sigma0_2dt + vol_backbone_eta * sigma0 * w0
|
|
647
694
|
vol_var = vol_var + ((kappa1 * theta / sigma0 - kappa1) + kappa2*(theta-sigma0) + adj*sigma0 - 0.5*vartheta2) * dt + beta*w0+volvol*w1
|
|
@@ -651,6 +698,96 @@ def simulate_logsv_x_vol_terminal(ttm: float,
|
|
|
651
698
|
return x0, sigma0, qvar0
|
|
652
699
|
|
|
653
700
|
|
|
701
|
+
def get_randoms_for_chain_valuation(ttms: np.ndarray,
|
|
702
|
+
nb_path: int = 100000,
|
|
703
|
+
nb_steps_per_year: int = 360,
|
|
704
|
+
seed: int = 10
|
|
705
|
+
) -> Tuple[List[np.ndarray], List[np.ndarray], List[np.ndarray]]:
|
|
706
|
+
"""
|
|
707
|
+
we need to fix random normals for suesequent evaluation using mc slices
|
|
708
|
+
outputs as numpy lists
|
|
709
|
+
"""
|
|
710
|
+
#
|
|
711
|
+
set_seed(seed)
|
|
712
|
+
W0s = List()
|
|
713
|
+
W1s = List()
|
|
714
|
+
dts = List()
|
|
715
|
+
ttm0 = 0.0
|
|
716
|
+
for ttm in ttms:
|
|
717
|
+
# qqq
|
|
718
|
+
nb_steps_, dt, grid_t = set_time_grid(ttm=ttm - ttm0, nb_steps_per_year=nb_steps_per_year)
|
|
719
|
+
W0s.append(np.random.normal(0, 1, size=(nb_steps_, nb_path)))
|
|
720
|
+
W1s.append(np.random.normal(0, 1, size=(nb_steps_, nb_path)))
|
|
721
|
+
dts.append(dt)
|
|
722
|
+
ttm0 = ttm
|
|
723
|
+
return W0s, W1s, dts
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
@njit(cache=False, fastmath=True)
|
|
727
|
+
def logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
|
|
728
|
+
forwards: np.ndarray,
|
|
729
|
+
discfactors: np.ndarray,
|
|
730
|
+
strikes_ttms: Tuple[np.ndarray,...],
|
|
731
|
+
optiontypes_ttms: Tuple[np.ndarray, ...],
|
|
732
|
+
W0s: Tuple[np.ndarray, ...],
|
|
733
|
+
W1s: Tuple[np.ndarray, ...],
|
|
734
|
+
dts: Tuple[np.ndarray, ...],
|
|
735
|
+
v0: float,
|
|
736
|
+
theta: float,
|
|
737
|
+
kappa1: float,
|
|
738
|
+
kappa2: float,
|
|
739
|
+
beta: float,
|
|
740
|
+
volvol: float,
|
|
741
|
+
vol_backbone_etas: np.ndarray,
|
|
742
|
+
is_spot_measure: bool = True,
|
|
743
|
+
variable_type: VariableType = VariableType.LOG_RETURN
|
|
744
|
+
) -> Tuple[List[np.ndarray], List[np.ndarray]]:
|
|
745
|
+
"""
|
|
746
|
+
chain valuation using fixed randoms
|
|
747
|
+
"""
|
|
748
|
+
# starting values
|
|
749
|
+
nb_path = W0s[0].shape[1]
|
|
750
|
+
x0 = np.zeros(nb_path)
|
|
751
|
+
qvar0 = np.zeros(nb_path)
|
|
752
|
+
sigma0 = v0*np.ones(nb_path)
|
|
753
|
+
ttm0 = 0.0
|
|
754
|
+
|
|
755
|
+
# outputs as numpy lists
|
|
756
|
+
option_prices_ttm = List()
|
|
757
|
+
option_std_ttm = List()
|
|
758
|
+
for ttm, forward, discfactor, strikes_ttm, optiontypes_ttm, vol_backbone_eta, W0, W1, dt in zip(ttms, forwards, discfactors,
|
|
759
|
+
strikes_ttms, optiontypes_ttms,
|
|
760
|
+
vol_backbone_etas,
|
|
761
|
+
W0s, W1s, dts):
|
|
762
|
+
x0, sigma0, qvar0 = simulate_logsv_x_vol_terminal(ttm=ttm - ttm0,
|
|
763
|
+
x0=x0,
|
|
764
|
+
sigma0=sigma0,
|
|
765
|
+
qvar0=qvar0,
|
|
766
|
+
theta=theta,
|
|
767
|
+
kappa1=kappa1,
|
|
768
|
+
kappa2=kappa2,
|
|
769
|
+
beta=beta,
|
|
770
|
+
volvol=volvol,
|
|
771
|
+
vol_backbone_eta=vol_backbone_eta,
|
|
772
|
+
nb_path=nb_path,
|
|
773
|
+
dt=dt,
|
|
774
|
+
is_spot_measure=is_spot_measure,
|
|
775
|
+
W0=W0,
|
|
776
|
+
W1=W1)
|
|
777
|
+
ttm0 = ttm
|
|
778
|
+
option_prices, option_std = compute_mc_vars_payoff(x0=x0, sigma0=sigma0, qvar0=qvar0,
|
|
779
|
+
ttm=ttm,
|
|
780
|
+
forward=forward,
|
|
781
|
+
strikes_ttm=strikes_ttm,
|
|
782
|
+
optiontypes_ttm=optiontypes_ttm,
|
|
783
|
+
discfactor=discfactor,
|
|
784
|
+
variable_type=variable_type)
|
|
785
|
+
option_prices_ttm.append(option_prices)
|
|
786
|
+
option_std_ttm.append(option_std)
|
|
787
|
+
|
|
788
|
+
return option_prices_ttm, option_std_ttm
|
|
789
|
+
|
|
790
|
+
|
|
654
791
|
class UnitTests(Enum):
|
|
655
792
|
CHAIN_PRICER = 1
|
|
656
793
|
SLICE_PRICER = 2
|
|
@@ -9,7 +9,7 @@ import seaborn as sns
|
|
|
9
9
|
from typing import Tuple
|
|
10
10
|
from enum import Enum
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
import stochvolmodels.utils.mgf_pricer as mgfp
|
|
13
13
|
from stochvolmodels.pricers.analytic.bsm import infer_bsm_ivols_from_model_chain_prices
|
|
14
14
|
from stochvolmodels.utils.config import VariableType
|
|
15
15
|
|
|
@@ -15,11 +15,13 @@ def to_flat_np_array(input_list: List[np.ndarray]) -> np.ndarray:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@njit(cache=False, fastmath=False)
|
|
18
|
-
def set_time_grid(ttm: float,
|
|
18
|
+
def set_time_grid(ttm: float, nb_steps_per_year: int = 360) -> Tuple[int, float, np.ndarray]:
|
|
19
19
|
"""
|
|
20
20
|
set daily steps
|
|
21
21
|
"""
|
|
22
|
+
nb_steps = int(ttm * nb_steps_per_year) + 1
|
|
22
23
|
grid_t = np.linspace(0.0, ttm, nb_steps + 1)
|
|
24
|
+
# dt = ttm / nb_steps
|
|
23
25
|
dt = grid_t[1] - grid_t[0]
|
|
24
26
|
return nb_steps, dt, grid_t
|
|
25
27
|
|
|
@@ -94,3 +96,29 @@ def ncdf(x: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
|
|
|
94
96
|
@njit(cache=False, fastmath=True)
|
|
95
97
|
def npdf(x: Union[float, np.ndarray], mu: float = 0.0, vol: float = 1.0) -> Union[float, np.ndarray]:
|
|
96
98
|
return np.exp(-0.5*np.square((x-mu)/vol))/(vol*np.sqrt(2.0*np.pi))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def find_nearest(a: np.ndarray,
|
|
102
|
+
value: float,
|
|
103
|
+
is_sorted: bool = True,
|
|
104
|
+
is_equal_or_largest: bool = False
|
|
105
|
+
) -> float:
|
|
106
|
+
"""
|
|
107
|
+
find closest element
|
|
108
|
+
https://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array
|
|
109
|
+
"""
|
|
110
|
+
if is_sorted:
|
|
111
|
+
idx = np.searchsorted(a, value, side="left")
|
|
112
|
+
if is_equal_or_largest: # return the equal or largest element
|
|
113
|
+
return a[idx]
|
|
114
|
+
else:
|
|
115
|
+
if idx > 0 and (idx == len(a) or np.abs(value - a[idx - 1]) < np.abs(value - a[idx])):
|
|
116
|
+
return a[idx - 1]
|
|
117
|
+
else:
|
|
118
|
+
return a[idx]
|
|
119
|
+
else:
|
|
120
|
+
a = np.asarray(a)
|
|
121
|
+
idx = (np.abs(a - value)).argmin()
|
|
122
|
+
return a[idx]
|
|
123
|
+
|
|
124
|
+
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
this module is using option-chain-analytics package
|
|
3
|
-
to fetch OptionChain data with options data
|
|
4
|
-
see https://pypi.org/project/option-chain-analytics
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import pandas as pd
|
|
8
|
-
import numpy as np
|
|
9
|
-
import matplotlib.pyplot as plt
|
|
10
|
-
import qis
|
|
11
|
-
from qis import TimePeriod
|
|
12
|
-
from typing import Dict, Tuple, Optional, Literal
|
|
13
|
-
from numba.typed import List
|
|
14
|
-
from enum import Enum
|
|
15
|
-
|
|
16
|
-
# chain
|
|
17
|
-
from option_chain_analytics import OptionsDataDFs, create_chain_from_from_options_dfs
|
|
18
|
-
from option_chain_analytics.option_chain import SliceColumn, SlicesChain
|
|
19
|
-
|
|
20
|
-
# analytics
|
|
21
|
-
from stochvolmodels.data.option_chain import OptionChain
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def generate_vol_chain_np(chain: SlicesChain,
|
|
25
|
-
value_time: pd.Timestamp,
|
|
26
|
-
days_map: Dict[str, int] = {'1w': 7, '1m': 21},
|
|
27
|
-
delta_bounds: Tuple[Optional[float], Optional[float]] = (-0.1, 0.1),
|
|
28
|
-
is_filtered: bool = True
|
|
29
|
-
) -> OptionChain:
|
|
30
|
-
"""
|
|
31
|
-
given SlicesChain generate OptionChain for calibration inputs
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
ttms, future_prices, discfactors = List(), List(), List()
|
|
35
|
-
optiontypes_ttms, strikes_ttms = List(), List()
|
|
36
|
-
bid_ivs, ask_ivs = List(), List()
|
|
37
|
-
bid_prices, ask_prices = List(), List()
|
|
38
|
-
slice_ids = []
|
|
39
|
-
for label, day in days_map.items():
|
|
40
|
-
next_date = value_time + pd.DateOffset(days=day) # if overlapping next date will be last avilable maturity
|
|
41
|
-
slice_date = chain.get_next_slice_after_date(mat_date=next_date)
|
|
42
|
-
slice_t = chain.expiry_slices[slice_date]
|
|
43
|
-
df = slice_t.get_joint_slice(delta_bounds=delta_bounds, is_filtered=is_filtered)
|
|
44
|
-
if not df.empty:
|
|
45
|
-
slice_ids.append(f"{label}: {slice_t.expiry_id}")
|
|
46
|
-
ttms.append(slice_t.get_ttm())
|
|
47
|
-
future_prices.append(slice_t.get_future_price())
|
|
48
|
-
discfactors.append(1.0)
|
|
49
|
-
strikes_ttms.append(df.index.to_numpy())
|
|
50
|
-
optiontypes_ttms.append(df[SliceColumn.OPTION_TYPE].to_numpy(dtype=str))
|
|
51
|
-
bid_ivs.append(df[SliceColumn.BID_IV].to_numpy())
|
|
52
|
-
ask_ivs.append(df[SliceColumn.ASK_IV].to_numpy())
|
|
53
|
-
bid_prices.append(df[SliceColumn.BID_PRICE].to_numpy())
|
|
54
|
-
ask_prices.append(df[SliceColumn.ASK_PRICE].to_numpy())
|
|
55
|
-
|
|
56
|
-
out = OptionChain(ttms=np.array(ttms),
|
|
57
|
-
forwards=np.array(future_prices),
|
|
58
|
-
discfactors=np.array(discfactors),
|
|
59
|
-
ids=np.array(slice_ids),
|
|
60
|
-
strikes_ttms=strikes_ttms,
|
|
61
|
-
optiontypes_ttms=optiontypes_ttms,
|
|
62
|
-
bid_ivs=bid_ivs,
|
|
63
|
-
ask_ivs=ask_ivs,
|
|
64
|
-
bid_prices=bid_prices,
|
|
65
|
-
ask_prices=ask_prices)
|
|
66
|
-
return out
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def load_option_chain(options_data_dfs: OptionsDataDFs,
|
|
70
|
-
value_time: pd.Timestamp = pd.Timestamp('2023-02-06 08:00:00+00:00'),
|
|
71
|
-
days_map: Dict[str, int] = {'1w': 7, '1m': 21},
|
|
72
|
-
delta_bounds: Tuple[Optional[float], Optional[float]] = (-0.1, 0.1),
|
|
73
|
-
is_filtered: bool = True
|
|
74
|
-
) -> Optional[OptionChain]:
|
|
75
|
-
chain = create_chain_from_from_options_dfs(options_data_dfs=options_data_dfs, value_time=value_time)
|
|
76
|
-
if chain is not None:
|
|
77
|
-
option_chain = generate_vol_chain_np(chain=chain,
|
|
78
|
-
value_time=value_time,
|
|
79
|
-
days_map=days_map,
|
|
80
|
-
delta_bounds=delta_bounds,
|
|
81
|
-
is_filtered=is_filtered)
|
|
82
|
-
else:
|
|
83
|
-
option_chain = None
|
|
84
|
-
|
|
85
|
-
return option_chain
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def sample_option_chain_at_times(options_data_dfs: OptionsDataDFs,
|
|
89
|
-
time_period: TimePeriod,
|
|
90
|
-
freq: str = 'W-FRI',
|
|
91
|
-
days_map: Dict[str, int] = {'1w': 7, '1m': 21},
|
|
92
|
-
delta_bounds: Tuple[Optional[float], Optional[float]] = (-0.1, 0.1),
|
|
93
|
-
hour_offset: int = 8
|
|
94
|
-
) -> Dict[pd.Timestamp, OptionChain]:
|
|
95
|
-
value_times = qis.generate_dates_schedule(time_period=time_period,
|
|
96
|
-
freq=freq,
|
|
97
|
-
hour_offset=hour_offset)
|
|
98
|
-
option_chains = {}
|
|
99
|
-
for value_time in value_times:
|
|
100
|
-
option_chains[value_time] = load_option_chain(options_data_dfs=options_data_dfs,
|
|
101
|
-
value_time=value_time,
|
|
102
|
-
days_map=days_map,
|
|
103
|
-
delta_bounds=delta_bounds,
|
|
104
|
-
is_filtered=True)
|
|
105
|
-
return option_chains
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def load_price_data(options_data_dfs: OptionsDataDFs,
|
|
109
|
-
time_period: TimePeriod = None,
|
|
110
|
-
data: Literal['spot', 'perp', 'funding_rate'] = 'spot',
|
|
111
|
-
freq: Optional[str] = 'D' # to do
|
|
112
|
-
) -> pd.Series:
|
|
113
|
-
#options_data_dfs = OptionsDataDFs(**ts_data_loader_wrapper(ticker=ticker, freq='D', hour_offset=8))
|
|
114
|
-
spot_price = options_data_dfs.get_spot_data()[data]
|
|
115
|
-
if freq is not None:
|
|
116
|
-
spot_price = spot_price.resample(freq).last()
|
|
117
|
-
if time_period is not None:
|
|
118
|
-
spot_price = time_period.locate(spot_price)
|
|
119
|
-
return spot_price
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
class UnitTests(Enum):
|
|
123
|
-
PRINT_CHAIN_DATA = 1
|
|
124
|
-
GENERATE_VOL_CHAIN_NP = 2
|
|
125
|
-
SAMPLE_CHAIN_AT_TIMES = 3
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def run_unit_test(unit_test: UnitTests):
|
|
129
|
-
|
|
130
|
-
ticker = 'BTC' # BTC, ETH
|
|
131
|
-
value_time = pd.Timestamp('2021-10-21 08:00:00+00:00')
|
|
132
|
-
value_time = pd.Timestamp('2023-10-06 08:00:00+00:00')
|
|
133
|
-
|
|
134
|
-
from option_chain_analytics.ts_loaders import ts_data_loader_wrapper
|
|
135
|
-
options_data_dfs = OptionsDataDFs(**ts_data_loader_wrapper(ticker=ticker))
|
|
136
|
-
options_data_dfs.get_start_end_date().print()
|
|
137
|
-
chain = create_chain_from_from_options_dfs(options_data_dfs=options_data_dfs, value_time=value_time)
|
|
138
|
-
|
|
139
|
-
if unit_test == UnitTests.PRINT_CHAIN_DATA:
|
|
140
|
-
for expiry, eslice in chain.expiry_slices.items():
|
|
141
|
-
eslice.print()
|
|
142
|
-
|
|
143
|
-
elif unit_test == UnitTests.GENERATE_VOL_CHAIN_NP:
|
|
144
|
-
option_chain = generate_vol_chain_np(chain=chain,
|
|
145
|
-
value_time=value_time,
|
|
146
|
-
days_map={'1w': 7},
|
|
147
|
-
delta_bounds=(-0.1, 0.1),
|
|
148
|
-
is_filtered=True)
|
|
149
|
-
option_chain.print()
|
|
150
|
-
skews = option_chain.get_chain_skews(delta=0.35)
|
|
151
|
-
print(skews)
|
|
152
|
-
|
|
153
|
-
elif unit_test == UnitTests.SAMPLE_CHAIN_AT_TIMES:
|
|
154
|
-
time_period = qis.TimePeriod('01Jan2023', '31Jan2023', tz='UTC')
|
|
155
|
-
option_chains = sample_option_chain_at_times(options_data_dfs=options_data_dfs,
|
|
156
|
-
time_period=time_period,
|
|
157
|
-
freq='W-FRI',
|
|
158
|
-
hour_offset=9
|
|
159
|
-
)
|
|
160
|
-
for key, chain in option_chains.items():
|
|
161
|
-
print(f"{key}")
|
|
162
|
-
print(chain)
|
|
163
|
-
|
|
164
|
-
plt.show()
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if __name__ == '__main__':
|
|
168
|
-
|
|
169
|
-
unit_test = UnitTests.SAMPLE_CHAIN_AT_TIMES
|
|
170
|
-
|
|
171
|
-
is_run_all_tests = False
|
|
172
|
-
if is_run_all_tests:
|
|
173
|
-
for unit_test in UnitTests:
|
|
174
|
-
run_unit_test(unit_test=unit_test)
|
|
175
|
-
else:
|
|
176
|
-
run_unit_test(unit_test=unit_test)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
example of fitting GMM
|
|
3
|
-
see StochVolModels/examples/run_gmm_fit.py
|
|
4
|
-
"""
|
|
5
|
-
import matplotlib.pyplot as plt
|
|
6
|
-
import seaborn as sns
|
|
7
|
-
import qis as qis
|
|
8
|
-
from stochvolmodels import (get_btc_test_chain_data,
|
|
9
|
-
get_spy_test_chain_data,
|
|
10
|
-
OptionChain, GmmPricer,
|
|
11
|
-
plot_gmm_pdfs)
|
|
12
|
-
|
|
13
|
-
# get test option chain data
|
|
14
|
-
# option_chain = get_btc_test_chain_data()
|
|
15
|
-
option_chain = get_spy_test_chain_data()
|
|
16
|
-
|
|
17
|
-
# run GMM fit
|
|
18
|
-
gmm_pricer = GmmPricer()
|
|
19
|
-
fit_params = gmm_pricer.calibrate_model_params_to_chain(option_chain=option_chain, n_mixtures=4)
|
|
20
|
-
|
|
21
|
-
# illustrate fitted parameters and model fit to market bid-ask
|
|
22
|
-
# plot two ids
|
|
23
|
-
ids = ['2m', '6m']
|
|
24
|
-
n = len(ids)
|
|
25
|
-
with sns.axes_style('darkgrid'):
|
|
26
|
-
fig, axs = plt.subplots(n, 2, figsize=(14, 12), tight_layout=True)
|
|
27
|
-
# axs = qis.to_flat_list(axs)
|
|
28
|
-
current_ax = 0
|
|
29
|
-
|
|
30
|
-
for key, params in fit_params.items():
|
|
31
|
-
print(f"{key}: {params}")
|
|
32
|
-
if key in ids:
|
|
33
|
-
option_chain0 = OptionChain.get_slices_as_chain(option_chain, ids=[key])
|
|
34
|
-
# gmm_pricer.plot_model_ivols_vs_bid_ask(option_chain=option_chain0, params=params, axs=[axs[idx]])
|
|
35
|
-
plot_gmm_pdfs(params=params, option_chain0=option_chain0, axs=axs[current_ax, :])
|
|
36
|
-
qis.set_title(ax=axs[current_ax, 0], title=f"{key}-slice: (A) State PDF and Aggregate Risk-Neutral PDF")
|
|
37
|
-
qis.set_title(ax=axs[current_ax, 1], title=f"{key}-slice: Model to Market Bid/Ask vols")
|
|
38
|
-
current_ax += 1
|
|
39
|
-
|
|
40
|
-
qis.set_suptitle(fig, title='Fit of 4-state GMM to SPY implied vols @ 15_Jul_2022_10_23_09')
|
|
41
|
-
plt.show()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/bachelier.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_core.py
RENAMED
|
File without changes
|
{stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/affine_expansion.py
RENAMED
|
File without changes
|
{stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/vol_moments_ode.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|