stochvolmodels 1.1.2__tar.gz → 1.1.3__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 (92) hide show
  1. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/.gitignore +2 -0
  2. {stochvolmodels-1.1.2/stochvolmodels.egg-info → stochvolmodels-1.1.3}/PKG-INFO +15 -1
  3. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/README.md +15 -1
  4. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/pyproject.toml +1 -1
  5. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/examples/run_lognormal_sv_pricer.py +32 -15
  6. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/logsv/logsv_params.py +4 -1
  7. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/logsv_pricer.py +7 -7
  8. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/rough_logsv/split_simulation.py +57 -38
  9. stochvolmodels-1.1.3/stochvolmodels/pricers/rough_logsv/test_kernel_approx.py +16 -0
  10. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3/stochvolmodels.egg-info}/PKG-INFO +15 -1
  11. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels.egg-info/SOURCES.txt +1 -0
  12. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/LICENSE.txt +0 -0
  13. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/MANIFEST.in +0 -0
  14. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/forward_var/calibrate_forward_var.py +0 -0
  15. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/il_hedging/README.md +0 -0
  16. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/il_hedging/logsv_figures.py +0 -0
  17. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/il_hedging/run_logsv_for_il_payoff.py +0 -0
  18. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/inverse_options/README.md +0 -0
  19. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/inverse_options/compare_net_delta.py +0 -0
  20. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/README.md +0 -0
  21. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/article_figures.py +0 -0
  22. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/calibrations.py +0 -0
  23. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/compare_admis_reg.py +0 -0
  24. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/model_fit_to_options_timeseries.py +0 -0
  25. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/moments_vol_qvar.py +0 -0
  26. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/ode_sol_in_time.py +0 -0
  27. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/steady_state_pdf.py +0 -0
  28. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/logsv_model_wtih_quadratic_drift/vol_drift.py +0 -0
  29. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/risk_premia_gmm/check_kernel.py +0 -0
  30. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/risk_premia_gmm/gmm_slides.py +0 -0
  31. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/risk_premia_gmm/plot_gmm.py +0 -0
  32. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/risk_premia_gmm/q_kernel.py +0 -0
  33. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/risk_premia_gmm/run_gmm_fit.py +0 -0
  34. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/sv_for_factor_hjm/README.md +0 -0
  35. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/sv_for_factor_hjm/calibration_fig_5_6_7.py +0 -0
  36. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/sv_for_factor_hjm/calibration_fig_8_9.py +0 -0
  37. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/t_distribution/illustrations.py +0 -0
  38. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/t_distribution/market_data_fit.py +0 -0
  39. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/t_distribution/mc_pricer_with_kernel.py +0 -0
  40. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/volatility_models/README.md +0 -0
  41. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/volatility_models/article_figures.py +0 -0
  42. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/volatility_models/autocorr_fit.py +0 -0
  43. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/volatility_models/load_data.py +0 -0
  44. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/volatility_models/ss_distribution_fit.py +0 -0
  45. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/my_papers/volatility_models/vol_beta.py +0 -0
  46. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/requirements.txt +0 -0
  47. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/setup.cfg +0 -0
  48. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/__init__.py +0 -0
  49. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/data/__init__.py +0 -0
  50. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/data/fetch_option_chain.py +0 -0
  51. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/data/option_chain.py +0 -0
  52. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/data/test_option_chain.py +0 -0
  53. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/examples/quick_run_lognormal_sv_pricer.py +0 -0
  54. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/examples/run_heston.py +0 -0
  55. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/examples/run_heston_sv_pricer.py +0 -0
  56. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/examples/run_pricing_options_on_qvar.py +0 -0
  57. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/__init__.py +0 -0
  58. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/analytic/__init__.py +0 -0
  59. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/analytic/bachelier.py +0 -0
  60. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/analytic/bsm.py +0 -0
  61. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/analytic/tdist.py +0 -0
  62. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py +0 -0
  63. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py +0 -0
  64. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/rate_affine_expansion.py +0 -0
  65. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/rate_core.py +0 -0
  66. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py +0 -0
  67. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py +0 -0
  68. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py +0 -0
  69. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py +0 -0
  70. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py +0 -0
  71. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/gmm_pricer.py +0 -0
  72. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/hawkes_jd_pricer.py +0 -0
  73. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/heston_pricer.py +0 -0
  74. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/logsv/__init__.py +0 -0
  75. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/logsv/affine_expansion.py +0 -0
  76. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/logsv/vol_moments_ode.py +0 -0
  77. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/model_pricer.py +0 -0
  78. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/rough_logsv/RoughKernel.py +0 -0
  79. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/pricers/tdist_pricer.py +0 -0
  80. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/tests/__init__.py +0 -0
  81. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/tests/bsm_mgf_pricer.py +0 -0
  82. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/tests/qv_pricer.py +0 -0
  83. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/utils/__init__.py +0 -0
  84. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/utils/config.py +0 -0
  85. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/utils/funcs.py +0 -0
  86. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/utils/mc_payoffs.py +0 -0
  87. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/utils/mgf_pricer.py +0 -0
  88. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/utils/plots.py +0 -0
  89. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels/utils/var_swap_pricer.py +0 -0
  90. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels.egg-info/dependency_links.txt +0 -0
  91. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels.egg-info/requires.txt +0 -0
  92. {stochvolmodels-1.1.2 → stochvolmodels-1.1.3}/stochvolmodels.egg-info/top_level.txt +0 -0
@@ -131,3 +131,5 @@ dmypy.json
131
131
 
132
132
  # Pyre type checker
133
133
  .pyre/
134
+ /resources
135
+ /volatility_book
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stochvolmodels
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: Python implementation of pricing analytics and Monte Carlo simulations for stochastic volatility models including log-normal SV model, Heston
5
5
  Author-email: Artur Sepp <artursepp@gmail.com>
6
6
  Maintainer-email: Artur Sepp <artursepp@gmail.com>, Parviz Rakhmonov <ParvizRZ@gmail.com>
@@ -1144,3 +1144,17 @@ Special thanks to co-authors and collaborators:
1144
1144
  - Alexander Lipton
1145
1145
 
1146
1146
  For additional research and advanced analytics, see the companion modules and papers included in this package.
1147
+
1148
+ ## BibTeX Citations for StochVolModels (Stochastic Volatility Models) Package
1149
+
1150
+ If you use StochVolModels in your research, please cite it as:
1151
+
1152
+ ```bibtex
1153
+ @software{stochvolmodels2024,
1154
+ author={Sepp, Artur},
1155
+ title={StochVolModels: Python implementation of pricing analytics and Monte Carlo simulations for stochastic volatility models},
1156
+ year={2024},
1157
+ url={https://github.com/ArturSepp/StochVolModels},
1158
+ }
1159
+ ```
1160
+
@@ -391,4 +391,18 @@ Special thanks to co-authors and collaborators:
391
391
  - Vladimir Lucic
392
392
  - Alexander Lipton
393
393
 
394
- For additional research and advanced analytics, see the companion modules and papers included in this package.
394
+ For additional research and advanced analytics, see the companion modules and papers included in this package.
395
+
396
+ ## BibTeX Citations for StochVolModels (Stochastic Volatility Models) Package
397
+
398
+ If you use StochVolModels in your research, please cite it as:
399
+
400
+ ```bibtex
401
+ @software{stochvolmodels2024,
402
+ author={Sepp, Artur},
403
+ title={StochVolModels: Python implementation of pricing analytics and Monte Carlo simulations for stochastic volatility models},
404
+ year={2024},
405
+ url={https://github.com/ArturSepp/StochVolModels},
406
+ }
407
+ ```
408
+
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "stochvolmodels"
10
- version = "1.1.2"
10
+ version = "1.1.3"
11
11
  description = "Python implementation of pricing analytics and Monte Carlo simulations for stochastic volatility models including log-normal SV model, Heston"
12
12
  readme = "README.md"
13
13
  license = {file = "LICENSE.txt"}
@@ -4,8 +4,12 @@ run few unit test to illustrate implementation of log-normal sv model analytics
4
4
 
5
5
  import numpy as np
6
6
  import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ import pandas as pd
9
+ import matplotlib.ticker as mticker
7
10
  from enum import Enum
8
11
  import stochvolmodels as sv
12
+ from stochvolmodels.utils import plots as plot
9
13
  from stochvolmodels import LogSVPricer, LogSvParams, OptionChain, LogsvModelCalibrationType
10
14
 
11
15
 
@@ -22,6 +26,7 @@ class LocalTests(Enum):
22
26
  BENCHM_ROUGH_PRICER = 10
23
27
 
24
28
 
29
+
25
30
  def run_local_test(local_test: LocalTests):
26
31
  """Run local tests for development and debugging purposes.
27
32
 
@@ -160,23 +165,23 @@ def run_local_test(local_test: LocalTests):
160
165
  print(option_prices_ttm)
161
166
 
162
167
  elif local_test == LocalTests.BENCHM_ROUGH_PRICER:
163
- btc_option_chain = OptionChain.get_uniform_chain(ttms=np.array([0.083, 0.25]),
164
- ids=np.array(['1m', '3m']),
165
- strikes=np.linspace(0.5, 1.5, 21))
166
- params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=0.0, beta=0.15, volvol=2.0)
167
- nb_path = 100000
168
- H = 0.3
169
- N = 3
168
+ btc_option_chain = sv.get_btc_test_chain_data()
169
+ # params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=1.0, beta=0.15, volvol=1.0)
170
+ params0 = LogSvParams(sigma0=1.32, theta=0.47, kappa1=9.98, kappa2=2.0, beta=0.45, volvol=0.83)
171
+ nb_path = 1000000
172
+ H = 0.4
173
+ N = 2
174
+ seed = 1
170
175
 
171
176
  def rough_vol():
172
177
  params1 = LogSvParams.copy(params0)
173
- params1.H = 0.3
178
+ params1.H = H
174
179
  params1.approximate_kernel(T=btc_option_chain.ttms[-1], N=N)
175
180
 
176
181
  Z0, Z1, grid_ttms = sv.get_randoms_for_rough_vol_chain_valuation(ttms=btc_option_chain.ttms,
177
182
  nb_path=nb_path,
178
183
  nb_steps_per_year=360,
179
- seed=10)
184
+ seed=seed)
180
185
 
181
186
 
182
187
  option_prices_ttm, option_std_ttm = sv.rough_logsv_mc_chain_pricer_fixed_randoms(ttms=btc_option_chain.ttms,
@@ -228,16 +233,28 @@ def run_local_test(local_test: LocalTests):
228
233
  ivols_logsv = regular_vol()
229
234
 
230
235
  nb_slices = btc_option_chain.ttms.size
231
- fig, axs = plt.subplots(1, nb_slices, figsize=(4*nb_slices, 3), tight_layout=True)
236
+ assert nb_slices == 4
237
+
238
+ with sns.axes_style('darkgrid'):
239
+ fig, axs = plt.subplots(2, 2, figsize=(15, 9), tight_layout=True)
240
+ axs = plot.to_flat_list(axs)
232
241
 
233
242
  for i in range(nb_slices):
234
- ax = axs[i] if nb_slices>1 else axs
235
- ax.plot(btc_option_chain.strikes_ttms[i], ivols_logsv[i], label="LOG_SV", marker="*")
236
- ax.plot(btc_option_chain.strikes_ttms[i], ivols_rough_logsv[i], label="ROUGH_LOG_SV", marker="o")
243
+ data = pd.DataFrame({"Rough Log-SV": ivols_rough_logsv[i],
244
+ "Log-SV": ivols_logsv[i]},
245
+ index=np.log(
246
+ btc_option_chain.strikes_ttms[i] / btc_option_chain.forwards[i]))
247
+ ax = axs[i]
248
+ sns.lineplot(data, ax=ax, markers=["o"])
237
249
  ax.set_title("Expiry: " + btc_option_chain.ids[i])
250
+ ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda z, _: '{:.0%}'.format(z)))
251
+ ax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda z, _: '{:.2f}'.format(z)))
238
252
  ax.legend()
239
- fig.suptitle(f"Conventional LogSV model vs Rough LogSV, H={H:.2f} via {N}f Markovian approximation",
240
- color="darkblue")
253
+ fig.suptitle(f"Conventional LogSV model vs Rough LogSV, H={H:.2f} via {N}f Markovian approximation\n"
254
+ f"{params0.to_str()}",
255
+ color = "darkblue", fontsize = 14)
256
+
257
+ # fig.savefig("c:/temp/rough_vs_conven_vol.pdf")
241
258
 
242
259
  elif local_test == LocalTests.CALIBRATE_MODEL_TO_BTC_OPTIONS:
243
260
  btc_option_chain = sv.get_btc_test_chain_data()
@@ -35,7 +35,10 @@ class LogSvParams(ModelParams):
35
35
 
36
36
  def approximate_kernel(self, T: float, N: int):
37
37
  assert 1 <= N <= 5 # not keen to use large N
38
- if N > 1 and self.H<0.49:
38
+ if self.H >= 0.4:
39
+ N = N if N<=2 else 2
40
+ self.nodes, self.weights = european_rule(self.H, N, T)
41
+ elif N > 1 and self.H<0.49:
39
42
  self.nodes, self.weights = european_rule(self.H, N, T)
40
43
  else:
41
44
  self.weights = np.array([1.0])
@@ -703,7 +703,7 @@ def simulate_logsv_x_vol_terminal(ttm: float,
703
703
  assert sigma0.shape[0] == nb_path
704
704
  if W0 is None and W1 is None:
705
705
  nb_steps1, dt, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=nb_steps_per_year)
706
- print(f"nb_steps1={nb_steps1}, dt={dt}")
706
+ # print(f"nb_steps1={nb_steps1}, dt={dt}")
707
707
  sdt = np.sqrt(dt)
708
708
  W0_ = sdt * np.random.normal(0, 1, size=(nb_steps1, nb_path))
709
709
  W1_ = sdt * np.random.normal(0, 1, size=(nb_steps1, nb_path))
@@ -760,7 +760,7 @@ def get_randoms_for_rough_vol_chain_valuation(ttms: np.ndarray,
760
760
  nb_steps_per_year: int = 360,
761
761
  seed: int = 10
762
762
  ) -> Tuple[np.ndarray, np.ndarray, List[np.ndarray]]:
763
- set_seed(seed)
763
+ np.random.seed(seed)
764
764
  grid_ttms = List()
765
765
  nb_steps_ttms = np.zeros_like(ttms).astype(int)
766
766
  for i, ttm in enumerate(ttms):
@@ -856,13 +856,11 @@ def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
856
856
  variable_type: VariableType = VariableType.LOG_RETURN
857
857
  ) -> Tuple[List[np.ndarray], List[np.ndarray]]:
858
858
  assert weights.shape == nodes.shape and weights.ndim == 1
859
- assert kappa2 == 0.0
859
+ # assert kappa2 == 0.0
860
860
  N = nodes.size
861
861
  v0 = sigma0 / np.sum(weights) * np.ones((N,))
862
862
 
863
863
  # need to redenote coefficients
864
- lamda = kappa1
865
- theta = kappa1 * theta
866
864
  volvol = np.sqrt(beta ** 2 + orthog_vol ** 2)
867
865
  rho = beta / volvol
868
866
 
@@ -882,8 +880,10 @@ def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
882
880
  nb_steps = timegrid.size - 1
883
881
  Z0_ = Z0[:nb_steps]
884
882
  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_)
883
+ weight_vec = np.repeat(weights[:, None], nb_path,axis=1)
884
+ nodes_vec = np.repeat(nodes[:, None], nb_path,axis=1)
885
+ log_spot_str, vol_str, qv_str = log_spot_full_combined(nodes_vec, weight_vec, v0_vec, theta, kappa1, kappa2, log_s0,
886
+ v_init, rho, volvol, timegrid, nb_path, Z0_, Z1_)
887
887
  print(f"Number of paths with negative vol: {np.sum(weights @ vol_str < 0.0)}")
888
888
  print(f"Mean spot Strand: {np.mean(np.exp(log_spot_str))}")
889
889
 
@@ -14,7 +14,7 @@ from numba import njit, objmode
14
14
  # from stochvolmodels.pricers.logsv_pricer import LogSvParams
15
15
 
16
16
  @njit(cache=False, fastmath=True)
17
- def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, lamda: float,
17
+ def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
18
18
  z0: np.ndarray, weight: np.ndarray, h: float):
19
19
  """
20
20
 
@@ -23,7 +23,8 @@ def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, lamda: floa
23
23
  nodes : (fixed argument) exponents x_i, array of size (n,)
24
24
  v0 : (fixed argument) array of size (n, nb_path)
25
25
  theta : long-run level, scaled by lambda, scalar
26
- lamda : mean-reversion speed, scalar
26
+ kappa1 : linear mean-reversion speed, scalar
27
+ kappa2 : quadratic mean-reversion speed, scalar
27
28
  z0 : initial values, array of size (n, nb_path)
28
29
  weight : wieghts, array of size (n, )
29
30
  h : step size
@@ -32,30 +33,39 @@ def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, lamda: floa
32
33
  -------
33
34
  Array of size (n, nb_path)
34
35
  """
35
- assert nodes.shape == v0.shape[:-1] == z0.shape[:-1] == weight.shape
36
- n = weight.size
37
- lamda_vec = np.repeat(lamda, n)
38
- theta_vec = np.repeat(theta, n)[:, None]
39
- diag_x = np.diag(nodes)
40
- I = np.identity(n)
41
- A = -np.outer(lamda_vec, weight) - diag_x # matrix of size (n, n)
42
- b = theta_vec + diag_x @ v0 # vector of size (n, nb_path)
43
- with objmode(eAh='float64[:, ::1]'):
44
- eAh = expm(A * h)
45
- Dzh = eAh @ z0 + (np.linalg.inv(A) @ (eAh - I)) @ b # vector of size (n, nb_path)
36
+ assert nodes.shape == v0.shape == z0.shape == weight.shape
37
+ n = weight.shape[0]
38
+ z0w = np.sum(weight * z0, axis=0)
39
+
40
+ # s1 = -nodes[:, None] * (z0 - v0) * h + (kappa1 + kappa2 * z0w) * (theta - z0w) * h
41
+ # s2 = -nodes[:, None] * (z0 + 0.5 * s1 - v0) * h + (kappa1 + kappa2 * (z0w + 0.5 * s1)) * (theta - (z0w + 0.5 * s1)) * h
42
+ # Dzh = z0 + s2
43
+
44
+ z1 = -nodes * (z0 - v0) * h + (kappa1 + kappa2 * z0w) * (theta - z0w) * h
45
+ z2 = -nodes * (z0 + 0.5 * z1 - v0) * h + (kappa1 + kappa2 * (z0w + 0.5 * z1)) * (theta - (z0w + 0.5 * z1)) * h
46
+ Dzh = z0 + z2
47
+
48
+ # lamda_vec = np.repeat(lamda, n)
49
+ # theta_vec = np.repeat(theta, n)[:, None]
50
+ # diag_x = np.diag(nodes)
51
+ # I = np.identity(n)
52
+ # A = -np.outer(lamda_vec, weight) - diag_x # matrix of size (n, n)
53
+ # b = theta_vec + diag_x @ v0 # vector of size (n, nb_path)
54
+ # # with objmode(eAh='float64[:, ::1]'):
55
+ # eAh = expm(A * h)
56
+ # Dzh = eAh @ z0 + (np.linalg.inv(A) @ (eAh - I)) @ b # vector of size (n, nb_path)
46
57
 
47
58
  return Dzh
48
59
 
49
60
  @njit(cache=False, fastmath=True)
50
61
  def diffus_sde_solve(y0: np.ndarray, weight: np.ndarray, volvol: float, h: float, nb_path: int,
51
62
  z_rand: np.ndarray):
52
- assert y0.shape[:-1] == weight.shape and y0.shape[-1] == nb_path
63
+ assert y0.shape == weight.shape and y0.shape[-1] == nb_path
53
64
  assert z_rand.shape == (nb_path,)
54
- n = weight.size
55
- weight_sum = np.sum(weight)
65
+ weight_sum = np.sum(weight, axis=0)
56
66
  volvol_ = volvol * weight_sum
57
67
 
58
- yw = np.dot(weight, y0)
68
+ yw = np.sum(weight * y0, axis=0)
59
69
 
60
70
  dW = z_rand * np.sqrt(h)
61
71
  Yh = yw * np.exp(-0.5 * volvol_ ** 2 * h + volvol_ * dW)
@@ -68,7 +78,7 @@ def diffus_sde_solve(y0: np.ndarray, weight: np.ndarray, volvol: float, h: float
68
78
  return Yh_vec
69
79
 
70
80
  @njit(cache=False, fastmath=True)
71
- def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, lamda: float,
81
+ def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
72
82
  volvol: float, v_init: np.ndarray, weight: np.ndarray, h: float,
73
83
  nb_path: int, z_rand: np.ndarray):
74
84
  """
@@ -78,7 +88,8 @@ def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, lamda:
78
88
  nodes : exponents x_i, array of size (n,)
79
89
  v0 : (fixed argument) array of size (n, nb_path)
80
90
  theta : long-run level, scaled by lambda, scalar
81
- lamda : mean-reversion speed, scalar
91
+ kappa1 : linear mean-reversion speed, scalar
92
+ kappa2 : quadratic mean-reversion speed, scalar
82
93
  volvol : total volatility. in other words, vartheta. scalar
83
94
  v_init : initial values, array of size (n, nb_path)
84
95
  weight : (fixed argument) wieghts, array of size (n, )
@@ -90,9 +101,9 @@ def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, lamda:
90
101
  -------
91
102
 
92
103
  """
93
- D_inn = drift_ode_solve(nodes, v0, theta, lamda, v_init, weight, 0.5 * h)
104
+ D_inn = drift_ode_solve(nodes, v0, theta, kappa1, kappa2, v_init, weight, 0.5 * h)
94
105
  S_inn = diffus_sde_solve(D_inn, weight, volvol, h, nb_path, z_rand)
95
- sol = drift_ode_solve(nodes, v0, theta, lamda, S_inn, weight, 0.5 * h)
106
+ sol = drift_ode_solve(nodes, v0, theta, kappa1, kappa2, S_inn, weight, 0.5 * h)
96
107
 
97
108
  return sol
98
109
 
@@ -100,33 +111,41 @@ def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, lamda:
100
111
  @njit(cache=False, fastmath=True)
101
112
  def log_spot_full_solve2(nodes: np.ndarray, weight: np.ndarray,
102
113
  v0: np.ndarray, y0: np.ndarray,
103
- theta: float, lamda: float, log_s: np.ndarray, v: np.ndarray, y: np.ndarray,
114
+ theta: float, kappa1: float, kappa2: float, log_s: np.ndarray, v: np.ndarray, y: np.ndarray,
104
115
  rho: float, volvol: float, h: float, nb_path: int,
105
116
  z0: np.ndarray, z1: np.ndarray):
106
117
  # raise ValueError
107
- assert nodes.shape == weight.shape and weight.ndim == 1
108
- assert v.shape[:-1] == weight.shape and v.shape[-1] == nb_path
118
+ assert nodes.shape == weight.shape and weight.ndim == 2
119
+ assert v.shape == weight.shape and v.shape[-1] == nb_path
109
120
  assert y.shape == (1, nb_path)
110
121
  assert log_s.shape == (1, nb_path)
111
- assert v0.shape[:-1] == weight.shape and v0.shape[-1] == nb_path
122
+ assert v0.shape == weight.shape and v0.shape[-1] == nb_path
112
123
  assert y0.shape == (1, nb_path)
113
124
  assert z0.shape == (nb_path,) and z1.shape == (nb_path,)
114
125
 
115
- vol_h = drift_diffus_strand(nodes, v0, theta, lamda, volvol, v, weight, h, nb_path, z0)
126
+ vol_h = drift_diffus_strand(nodes, v0, theta, kappa1, kappa2, volvol, v, weight, h, nb_path, z0)
116
127
 
117
- ww = weight[None, :]
118
- wlam = (weight * nodes)[None, :]
119
- vw = ww @ v
120
- volw_h = ww @ vol_h
121
- w_inv = 1.0 / np.sum(weight)
128
+ wlam = weight * nodes
129
+ vw = np.sum(weight * v, axis=0)
130
+ volw_h = np.sum(weight * vol_h, axis=0)
131
+ w_inv = 1.0 / np.sum(weight, axis=0)
122
132
 
123
133
  c1 = 0.5
124
134
  c2 = 0.5
125
135
  rho_comp = np.sqrt(1.0 - rho * rho)
126
136
 
127
- term1 = 1.0 / volvol * ((volw_h - vw + c1 * h * wlam @ v + c2 * h * wlam @ vol_h - wlam @ v0 * h) * w_inv
128
- - theta * h + lamda * c1 * h * vw + lamda * c2 * h * volw_h)
129
- term2 = c1 * h * np.square(vw) + c2 * h * np.square(volw_h)
137
+ sq_vw = np.square(vw)
138
+ sq_vhw = np.square(volw_h)
139
+
140
+ w_lam_vol = np.sum(wlam * v, axis=0)
141
+ w_lam_vol_h = np.sum(wlam * vol_h, axis=0)
142
+ w_lam_v0 = np.sum(wlam * v0, axis=0)
143
+
144
+ term1 = 1.0 / volvol * (((volw_h - vw) / h + c1 * w_lam_vol + c2 * w_lam_vol_h - w_lam_v0) * w_inv
145
+ - kappa1 * theta + (kappa1 - kappa2 * theta) * (c1 * vw + c2 * volw_h)
146
+ + kappa2 * (c1 * sq_vw + c2 * sq_vhw)) * h
147
+
148
+ term2 = c1 * h * sq_vw + c2 * h * sq_vhw
130
149
  log_spot_h = log_s - 0.5 * term2 + rho * term1 + rho_comp * np.sqrt(term2) * z1
131
150
 
132
151
  y_h = y + 0.5 * h * (vw * vw + volw_h * volw_h) # don't need it but keep for plotting
@@ -136,12 +155,12 @@ def log_spot_full_solve2(nodes: np.ndarray, weight: np.ndarray,
136
155
  @njit(cache=False, fastmath=True)
137
156
  def log_spot_full_combined(nodes: np.ndarray, weight: np.ndarray,
138
157
  v0: np.ndarray,
139
- theta: float, lamda: float, log_s0: float, v_init: np.ndarray,
158
+ theta: float, kappa1: float, kappa2: float, log_s0: float, v_init: np.ndarray,
140
159
  rho: float, volvol: float, timegrid: np.ndarray, nb_path: int,
141
160
  Z0: np.ndarray, Z1: np.ndarray):
142
161
  h = timegrid[1] - timegrid[0]
143
- assert np.all(np.isclose(np.diff(timegrid)[1:], h)) and timegrid[0] == 0.0
144
- assert Z0.shape == (timegrid.size - 1, nb_path) and Z1.shape == (timegrid.size - 1, nb_path)
162
+ # assert np.all(np.isclose(np.diff(timegrid)[1:], h)) and timegrid[0] == 0.0
163
+ # assert Z0.shape == (timegrid.size - 1, nb_path) and Z1.shape == (timegrid.size - 1, nb_path)
145
164
 
146
165
  y0 = np.zeros((1, nb_path))
147
166
 
@@ -152,7 +171,7 @@ def log_spot_full_combined(nodes: np.ndarray, weight: np.ndarray,
152
171
  # a, b, c = solve_coeffs(nodes, weight, v0[:, 0], theta, lamda, rho, volvol)
153
172
 
154
173
  for idx, _ in enumerate(timegrid[:-1]):
155
- vol_h, y_h, log_spot_h = log_spot_full_solve2(nodes, weight, v0, y0, theta, lamda,
174
+ vol_h, y_h, log_spot_h = log_spot_full_solve2(nodes, weight, v0, y0, theta, kappa1, kappa2,
156
175
  log_spot_h, vol_h, y_h, rho, volvol, h, nb_path,
157
176
  Z0[idx], Z1[idx])
158
177
 
@@ -0,0 +1,16 @@
1
+ import numpy as np
2
+ from stochvolmodels import LogSvParams
3
+
4
+ if __name__ == "__main__":
5
+ H = 0.4464700054758044
6
+ N = 2
7
+ T = 1.
8
+
9
+ params0 = LogSvParams(sigma0 = 1.32, theta=0.47, kappa1=4.0, kappa2=2.0, beta=0.45, volvol=0.83)
10
+ for N in [1, 2, 3]:
11
+ for H in np.linspace(0.3, 0.49, 150):
12
+ params0.H = H
13
+ print(f" *** N={N}, H={H} *** ")
14
+ params0.approximate_kernel(T=T, N=N)
15
+ print(params0.weights)
16
+ print(params0.nodes)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stochvolmodels
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: Python implementation of pricing analytics and Monte Carlo simulations for stochastic volatility models including log-normal SV model, Heston
5
5
  Author-email: Artur Sepp <artursepp@gmail.com>
6
6
  Maintainer-email: Artur Sepp <artursepp@gmail.com>, Parviz Rakhmonov <ParvizRZ@gmail.com>
@@ -1144,3 +1144,17 @@ Special thanks to co-authors and collaborators:
1144
1144
  - Alexander Lipton
1145
1145
 
1146
1146
  For additional research and advanced analytics, see the companion modules and papers included in this package.
1147
+
1148
+ ## BibTeX Citations for StochVolModels (Stochastic Volatility Models) Package
1149
+
1150
+ If you use StochVolModels in your research, please cite it as:
1151
+
1152
+ ```bibtex
1153
+ @software{stochvolmodels2024,
1154
+ author={Sepp, Artur},
1155
+ title={StochVolModels: Python implementation of pricing analytics and Monte Carlo simulations for stochastic volatility models},
1156
+ year={2024},
1157
+ url={https://github.com/ArturSepp/StochVolModels},
1158
+ }
1159
+ ```
1160
+
@@ -77,6 +77,7 @@ stochvolmodels/pricers/logsv/logsv_params.py
77
77
  stochvolmodels/pricers/logsv/vol_moments_ode.py
78
78
  stochvolmodels/pricers/rough_logsv/RoughKernel.py
79
79
  stochvolmodels/pricers/rough_logsv/split_simulation.py
80
+ stochvolmodels/pricers/rough_logsv/test_kernel_approx.py
80
81
  stochvolmodels/tests/__init__.py
81
82
  stochvolmodels/tests/bsm_mgf_pricer.py
82
83
  stochvolmodels/tests/qv_pricer.py
File without changes