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.
Files changed (49) hide show
  1. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/PKG-INFO +14 -2
  2. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/README.md +13 -0
  3. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/pyproject.toml +1 -4
  4. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/__init__.py +7 -4
  5. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/data/option_chain.py +3 -4
  6. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_heston_sv_pricer.py +0 -2
  7. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_lognormal_sv_pricer.py +48 -1
  8. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_pricing_options_on_qvar.py +2 -3
  9. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py +2 -2
  10. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/gmm_pricer.py +1 -55
  11. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/hawkes_jd_pricer.py +1 -1
  12. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/heston_pricer.py +3 -2
  13. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/logsv_params.py +6 -5
  14. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv_pricer.py +152 -15
  15. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/tests/bsm_mgf_pricer.py +1 -1
  16. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/funcs.py +29 -1
  17. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/mc_payoffs.py +0 -1
  18. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/mgf_pricer.py +0 -1
  19. stochvolmodels-1.0.25/stochvolmodels/data/fetch_option_chain.py +0 -176
  20. stochvolmodels-1.0.25/stochvolmodels/examples/run_gmm_fit.py +0 -41
  21. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/LICENSE.txt +0 -0
  22. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/data/__init__.py +0 -0
  23. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/data/test_option_chain.py +0 -0
  24. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/quick_run_lognormal_sv_pricer.py +0 -0
  25. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/examples/run_heston.py +0 -0
  26. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/__init__.py +0 -0
  27. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/__init__.py +0 -0
  28. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/bachelier.py +0 -0
  29. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/bsm.py +0 -0
  30. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/analytic/tdist.py +0 -0
  31. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py +0 -0
  32. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py +0 -0
  33. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_affine_expansion.py +0 -0
  34. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_core.py +0 -0
  35. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py +0 -0
  36. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py +0 -0
  37. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py +0 -0
  38. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py +0 -0
  39. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/__init__.py +0 -0
  40. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/affine_expansion.py +0 -0
  41. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/logsv/vol_moments_ode.py +0 -0
  42. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/model_pricer.py +0 -0
  43. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/pricers/tdist_pricer.py +0 -0
  44. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/tests/__init__.py +0 -0
  45. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/tests/qv_pricer.py +0 -0
  46. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/__init__.py +0 -0
  47. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/config.py +0 -0
  48. {stochvolmodels-1.0.25 → stochvolmodels-1.0.27}/stochvolmodels/utils/plots.py +0 -0
  49. {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.25
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.25"
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
@@ -1,8 +1,6 @@
1
1
 
2
2
  import numpy as np
3
3
  import matplotlib.pyplot as plt
4
- from enum import Enum
5
-
6
4
  import stochvolmodels as sv
7
5
  from stochvolmodels import HestonPricer, HestonParams, OptionChain, BTC_HESTON_PARAMS
8
6
 
@@ -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.COMPUTE_MODEL_PRICES
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
- qis.set_suptitle(fig1, title='Implied variance skew by Log-Normal SV model')
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
- qis.set_suptitle(fig2, title='Implied variance skew by Heston SV model')
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, nb_steps=int(np.ceil(year_days*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, year_days=720)
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, nb_steps=int(5*360)) # need small dt step for large intensities
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
- nb_steps: int = 360
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, nb_steps=nb_steps)
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
@@ -1,12 +1,13 @@
1
- from dataclasses import dataclass, asdict
2
- from typing import Optional, Dict, Any
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 qis import find_nearest
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
- nb_steps=nb_steps or int(360*np.max(option_chain.ttms))+1)
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
- model_vols = self.compute_model_ivols_for_chain(option_chain=option_chain, params=params, vol_scaler=vol_scaler)
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
- nb_steps=nb_steps,
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
- nb_steps: int = 360,
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
- nb_steps=nb_steps,
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
- nb_steps: int = 360,
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, nb_steps=nb_steps)
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((nb_steps+1, nb_path)) # sigma grid will increase to include the sigma_0 at t0 = 0
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
- nb_steps: int = 360
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
- nb_steps, dt, grid_t = set_time_grid(ttm=ttm, nb_steps=nb_steps)
633
- W0 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path))
634
- W1 = np.sqrt(dt) * np.random.normal(0, 1, size=(nb_steps, nb_path))
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(W0, W1)):
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
- from stochvolmodels.pricers.analytic import mgf_pricer as mgfp
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, nb_steps: int = 360) -> Tuple[int, float, np.ndarray]:
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
+
@@ -4,7 +4,6 @@ Montecarlo analytics for option pay-off computations
4
4
 
5
5
  import numpy as np
6
6
  from numba import njit
7
-
8
7
  from stochvolmodels.utils.config import VariableType
9
8
 
10
9
 
@@ -5,7 +5,6 @@ generic analytics for option pricing using Fourier transforms of payoffs
5
5
  import numpy as np
6
6
  from numba import njit
7
7
  from typing import Tuple
8
-
9
8
  from stochvolmodels.utils.config import VariableType
10
9
 
11
10
 
@@ -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()