stochvolmodels 1.1.4__tar.gz → 1.1.6__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 (94) hide show
  1. {stochvolmodels-1.1.4/stochvolmodels.egg-info → stochvolmodels-1.1.6}/PKG-INFO +1 -1
  2. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/pyproject.toml +1 -1
  3. stochvolmodels-1.1.6/stochvolmodels/examples/run_hawkes_pricer.py +50 -0
  4. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/examples/run_lognormal_sv_pricer.py +20 -12
  5. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/hawkes_jd_pricer.py +1 -1
  6. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/logsv_pricer.py +40 -18
  7. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/model_pricer.py +5 -1
  8. stochvolmodels-1.1.6/stochvolmodels/pricers/rough_logsv/expm.py +452 -0
  9. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/rough_logsv/split_simulation.py +146 -8
  10. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6/stochvolmodels.egg-info}/PKG-INFO +1 -1
  11. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels.egg-info/SOURCES.txt +2 -0
  12. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/.gitignore +0 -0
  13. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/LICENSE.txt +0 -0
  14. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/MANIFEST.in +0 -0
  15. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/README.md +0 -0
  16. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/forward_var/calibrate_forward_var.py +0 -0
  17. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/il_hedging/README.md +0 -0
  18. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/il_hedging/logsv_figures.py +0 -0
  19. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/il_hedging/run_logsv_for_il_payoff.py +0 -0
  20. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/inverse_options/README.md +0 -0
  21. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/inverse_options/compare_net_delta.py +0 -0
  22. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/README.md +0 -0
  23. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/article_figures.py +0 -0
  24. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/calibrations.py +0 -0
  25. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/compare_admis_reg.py +0 -0
  26. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/model_fit_to_options_timeseries.py +0 -0
  27. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/moments_vol_qvar.py +0 -0
  28. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/ode_sol_in_time.py +0 -0
  29. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/steady_state_pdf.py +0 -0
  30. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/logsv_model_wtih_quadratic_drift/vol_drift.py +0 -0
  31. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/risk_premia_gmm/check_kernel.py +0 -0
  32. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/risk_premia_gmm/gmm_slides.py +0 -0
  33. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/risk_premia_gmm/plot_gmm.py +0 -0
  34. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/risk_premia_gmm/q_kernel.py +0 -0
  35. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/risk_premia_gmm/run_gmm_fit.py +0 -0
  36. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/sv_for_factor_hjm/README.md +0 -0
  37. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/sv_for_factor_hjm/calibration_fig_5_6_7.py +0 -0
  38. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/sv_for_factor_hjm/calibration_fig_8_9.py +0 -0
  39. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/t_distribution/illustrations.py +0 -0
  40. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/t_distribution/market_data_fit.py +0 -0
  41. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/t_distribution/mc_pricer_with_kernel.py +0 -0
  42. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/volatility_models/README.md +0 -0
  43. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/volatility_models/article_figures.py +0 -0
  44. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/volatility_models/autocorr_fit.py +0 -0
  45. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/volatility_models/load_data.py +0 -0
  46. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/volatility_models/ss_distribution_fit.py +0 -0
  47. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/my_papers/volatility_models/vol_beta.py +0 -0
  48. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/requirements.txt +0 -0
  49. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/setup.cfg +0 -0
  50. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/__init__.py +0 -0
  51. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/data/__init__.py +0 -0
  52. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/data/fetch_option_chain.py +0 -0
  53. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/data/option_chain.py +0 -0
  54. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/data/test_option_chain.py +0 -0
  55. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/examples/quick_run_lognormal_sv_pricer.py +0 -0
  56. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/examples/run_heston.py +0 -0
  57. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/examples/run_heston_sv_pricer.py +0 -0
  58. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/examples/run_pricing_options_on_qvar.py +0 -0
  59. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/__init__.py +0 -0
  60. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/analytic/__init__.py +0 -0
  61. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/analytic/bachelier.py +0 -0
  62. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/analytic/bsm.py +0 -0
  63. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/analytic/tdist.py +0 -0
  64. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py +0 -0
  65. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py +0 -0
  66. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/rate_affine_expansion.py +0 -0
  67. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/rate_core.py +0 -0
  68. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py +0 -0
  69. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py +0 -0
  70. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py +0 -0
  71. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py +0 -0
  72. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py +0 -0
  73. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/gmm_pricer.py +0 -0
  74. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/heston_pricer.py +0 -0
  75. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/logsv/__init__.py +0 -0
  76. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/logsv/affine_expansion.py +0 -0
  77. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/logsv/logsv_params.py +0 -0
  78. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/logsv/vol_moments_ode.py +0 -0
  79. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/rough_logsv/RoughKernel.py +0 -0
  80. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/rough_logsv/test_kernel_approx.py +0 -0
  81. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/pricers/tdist_pricer.py +0 -0
  82. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/tests/__init__.py +0 -0
  83. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/tests/bsm_mgf_pricer.py +0 -0
  84. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/tests/qv_pricer.py +0 -0
  85. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/utils/__init__.py +0 -0
  86. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/utils/config.py +0 -0
  87. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/utils/funcs.py +0 -0
  88. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/utils/mc_payoffs.py +0 -0
  89. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/utils/mgf_pricer.py +0 -0
  90. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/utils/plots.py +0 -0
  91. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels/utils/var_swap_pricer.py +0 -0
  92. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels.egg-info/dependency_links.txt +0 -0
  93. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels.egg-info/requires.txt +0 -0
  94. {stochvolmodels-1.1.4 → stochvolmodels-1.1.6}/stochvolmodels.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stochvolmodels
3
- Version: 1.1.4
3
+ Version: 1.1.6
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>
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "stochvolmodels"
10
- version = "1.1.4"
10
+ version = "1.1.6"
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"}
@@ -0,0 +1,50 @@
1
+
2
+ # built in
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ from scipy.optimize import minimize
6
+ from numba.typed import List
7
+ from typing import Tuple, Optional, Dict, Any
8
+ from dataclasses import dataclass, asdict
9
+ from scipy.integrate import solve_ivp
10
+ from scipy.integrate._ivp.ivp import OdeResult
11
+ from enum import Enum
12
+
13
+ # stochvolmodels pricers
14
+ import stochvolmodels.utils.mgf_pricer as mgfp
15
+ from stochvolmodels.utils.config import VariableType
16
+ from stochvolmodels.utils.mc_payoffs import compute_mc_vars_payoff
17
+ from stochvolmodels.pricers.model_pricer import ModelPricer, ModelParams
18
+ from stochvolmodels.utils.funcs import to_flat_np_array, set_time_grid, timer, set_seed
19
+
20
+ # data
21
+ from stochvolmodels.data.option_chain import OptionChain
22
+ from stochvolmodels import OptionChain, HawkesJDPricer, HawkesJDParams
23
+
24
+ pricer = HawkesJDPricer()
25
+
26
+ params = HawkesJDParams(sigma=0.1,
27
+ shift_p=0.25, # positive jump threshold
28
+ mean_p=0.00,
29
+ shift_m=-0.25,
30
+ mean_m=-0.00,
31
+ lambda_p=1.0,
32
+ theta_p=0.01,
33
+ kappa_p=300.0,
34
+ beta1_p=0.0,
35
+ beta2_p=0.0,
36
+ lambda_m=1.0,
37
+ theta_m=0.01,
38
+ kappa_m=300.0,
39
+ beta1_m=0.0,
40
+ beta2_m=0.0)
41
+
42
+ option_chain = OptionChain.get_uniform_chain(ttms=np.array([1.0/12.0]),
43
+ ids=np.array(['1m']),
44
+ forwards=np.array([100.0]),
45
+ strikes=100.0*np.linspace(0.5, 1.5, 30))
46
+
47
+ pricer.plot_model_ivols(option_chain=option_chain,
48
+ params=params)
49
+
50
+ plt.show()
@@ -11,7 +11,7 @@ from enum import Enum
11
11
  import stochvolmodels as sv
12
12
  from stochvolmodels.utils import plots as plot
13
13
  from stochvolmodels import LogSVPricer, LogSvParams, OptionChain, LogsvModelCalibrationType
14
-
14
+ # from uuid import uuid4
15
15
 
16
16
  class LocalTests(Enum):
17
17
  COMPUTE_MODEL_PRICES = 1
@@ -142,8 +142,8 @@ def run_local_test(local_test: LocalTests):
142
142
  nb_path=10000,
143
143
  nb_steps_per_year=360,
144
144
  seed=10)
145
- params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=0.0, beta=0.15, volvol=2.0)
146
- params0.H = 0.3
145
+ params0 = LogSvParams(sigma0=0.377, theta=0.347, kappa1=1.29, kappa2=1.93, beta=2.45, volvol=1.81)
146
+ params0.H = 0.1
147
147
  params0.approximate_kernel(T=btc_option_chain.ttms[-1])
148
148
 
149
149
  option_prices_ttm, option_std_ttm = sv.rough_logsv_mc_chain_pricer_fixed_randoms(ttms=btc_option_chain.ttms,
@@ -167,9 +167,9 @@ def run_local_test(local_test: LocalTests):
167
167
  elif local_test == LocalTests.BENCHM_ROUGH_PRICER:
168
168
  btc_option_chain = sv.get_btc_test_chain_data()
169
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
170
+ params0 = LogSvParams(sigma0=0.377, theta=0.347, kappa1=1.29, kappa2=1.93, beta=2.45, volvol=1.81)
171
+ nb_path = 10000
172
+ H = 0.1
173
173
  seed = 1
174
174
 
175
175
  def rough_vol():
@@ -268,23 +268,31 @@ def run_local_test(local_test: LocalTests):
268
268
 
269
269
  elif local_test == LocalTests.CALIBRATE_MODEL_TO_BTC_OPTIONS_WITH_MC:
270
270
  btc_option_chain = sv.get_btc_test_chain_data()
271
- params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=0.0, beta=0.15, volvol=2.0)
272
- params0.H = 0.3
271
+ params0 = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=2.18, beta=0.15, volvol=2.0)
272
+ params0.H = 0.2
273
273
  params0.approximate_kernel(T=btc_option_chain.ttms[-1])
274
274
  btc_calibrated_params = logsv_pricer.calibrate_model_params_to_chain(option_chain=btc_option_chain,
275
275
  params0=params0,
276
276
  model_calibration_type=LogsvModelCalibrationType.PARAMS4,
277
277
  constraints_type=sv.ConstraintsType.INVERSE_MARTINGALE,
278
278
  calibration_engine=sv.CalibrationEngine.ROUGH_MC,
279
- nb_path=100000,
279
+ nb_path=5000,
280
280
  seed=7)
281
281
  print(btc_calibrated_params)
282
- logsv_pricer.plot_model_ivols_vs_bid_ask(option_chain=btc_option_chain,
283
- params=btc_calibrated_params)
282
+ fig = logsv_pricer.plot_model_ivols_vs_bid_ask(option_chain=btc_option_chain,
283
+ params=btc_calibrated_params,
284
+ mode='mc',
285
+ use_rough_mc=True,
286
+ seed=16,
287
+ nb_steps=360
288
+ )
289
+
290
+ # uuid = str(uuid4())
291
+ # plot.save_fig(fig=fig, local_path=r"c:/temp/", file_name=f"fit_quality_{uuid}")
284
292
 
285
293
  plt.show()
286
294
 
287
295
 
288
296
  if __name__ == '__main__':
289
297
 
290
- run_local_test(local_test=LocalTests.BENCHM_ROUGH_PRICER)
298
+ run_local_test(local_test=LocalTests.ROUGH_MC_WITH_FIXED_RANDOMS)
@@ -827,4 +827,4 @@ def run_local_test(local_test: LocalTests):
827
827
 
828
828
  if __name__ == '__main__':
829
829
 
830
- run_local_test(local_test=LocalTests.MC_COMPARISION)
830
+ run_local_test(local_test=LocalTests.SLICE_PRICER)
@@ -87,22 +87,44 @@ class LogSVPricer(ModelPricer):
87
87
  **kwargs
88
88
  ) -> (List[np.ndarray], List[np.ndarray]):
89
89
  vol_backbone_etas = params.get_vol_backbone_etas(ttms=option_chain.ttms)
90
- return logsv_mc_chain_pricer(v0=params.sigma0,
91
- theta=params.theta,
92
- kappa1=params.kappa1,
93
- kappa2=params.kappa2,
94
- beta=params.beta,
95
- volvol=params.volvol,
96
- vol_backbone_etas=vol_backbone_etas,
97
- ttms=option_chain.ttms,
98
- forwards=option_chain.forwards,
99
- discfactors=option_chain.discfactors,
100
- strikes_ttms=option_chain.strikes_ttms,
101
- optiontypes_ttms=option_chain.optiontypes_ttms,
102
- is_spot_measure=is_spot_measure,
103
- variable_type=variable_type,
104
- nb_path=nb_path,
105
- nb_steps_per_year=nb_steps or int(360 * np.max(option_chain.ttms)) + 1)
90
+ if 'use_rough_mc' in kwargs and kwargs['use_rough_mc']:
91
+ assert 'seed' in kwargs
92
+ seed = kwargs['seed']
93
+ Z0, Z1, grid_ttms = get_randoms_for_rough_vol_chain_valuation(ttms=option_chain.ttms, nb_path=nb_path,
94
+ nb_steps_per_year=nb_steps, seed=seed)
95
+ return rough_logsv_mc_chain_pricer_fixed_randoms(ttms=option_chain.ttms,
96
+ forwards=option_chain.forwards,
97
+ discfactors=option_chain.discfactors,
98
+ strikes_ttms=option_chain.strikes_ttms,
99
+ optiontypes_ttms=option_chain.optiontypes_ttms,
100
+ Z0=Z0,
101
+ Z1=Z1,
102
+ sigma0=params.sigma0,
103
+ theta=params.theta,
104
+ kappa1=params.kappa1,
105
+ kappa2=params.kappa2,
106
+ beta=params.beta,
107
+ orthog_vol=params.volvol,
108
+ weights=params.weights,
109
+ nodes=params.nodes,
110
+ timegrids=grid_ttms)
111
+ else:
112
+ return logsv_mc_chain_pricer(v0=params.sigma0,
113
+ theta=params.theta,
114
+ kappa1=params.kappa1,
115
+ kappa2=params.kappa2,
116
+ beta=params.beta,
117
+ volvol=params.volvol,
118
+ vol_backbone_etas=vol_backbone_etas,
119
+ ttms=option_chain.ttms,
120
+ forwards=option_chain.forwards,
121
+ discfactors=option_chain.discfactors,
122
+ strikes_ttms=option_chain.strikes_ttms,
123
+ optiontypes_ttms=option_chain.optiontypes_ttms,
124
+ is_spot_measure=is_spot_measure,
125
+ variable_type=variable_type,
126
+ nb_path=nb_path,
127
+ nb_steps_per_year=nb_steps or int(360 * np.max(option_chain.ttms)) + 1)
106
128
 
107
129
  def set_vol_scaler(self, option_chain: OptionChain) -> float:
108
130
  """
@@ -884,8 +906,8 @@ def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
884
906
  nodes_vec = np.repeat(nodes[:, None], nb_path,axis=1)
885
907
  log_spot_str, vol_str, qv_str = log_spot_full_combined(nodes_vec, weight_vec, v0_vec, theta, kappa1, kappa2, log_s0,
886
908
  v_init, rho, volvol, timegrid, nb_path, Z0_, Z1_)
887
- print(f"Number of paths with negative vol: {np.sum(weights @ vol_str < 0.0)}")
888
- print(f"Mean spot Strand: {np.mean(np.exp(log_spot_str))}")
909
+ print(f"Number of paths with negative vol: {np.sum(weights @ vol_str < 0.0)}, nan vol: {np.count_nonzero(np.isnan(weights @ vol_str))}")
910
+ print(f"Mean spot Strand: {np.mean(np.exp(log_spot_str))}, nan spots: {np.count_nonzero(np.isnan(log_spot_str))}")
889
911
 
890
912
  option_prices, option_std = compute_mc_vars_payoff(x0=log_spot_str, sigma0=vol_str, qvar0=qv_str,
891
913
  ttm=ttm,
@@ -331,7 +331,11 @@ class ModelPricer(ABC):
331
331
  plot model slice_t vols
332
332
  optimized for 2*2 figure
333
333
  """
334
- model_ivols = self.compute_model_ivols_for_chain(option_chain=option_chain, params=params, **kwargs)
334
+ if 'mode' in kwargs and kwargs['mode'] == 'mc':
335
+ model_ivols = self.compute_mc_chain_implied_vols(option_chain=option_chain, params=params, **kwargs)[3]
336
+ else:
337
+ model_ivols = self.compute_model_ivols_for_chain(option_chain=option_chain, params=params, **kwargs)
338
+
335
339
 
336
340
  num_slices = len(option_chain.ttms)
337
341
  with sns.axes_style('darkgrid'):
@@ -0,0 +1,452 @@
1
+ import numpy as np
2
+ import math
3
+ import cmath
4
+ from numba import njit, prange
5
+
6
+
7
+ # ---------- small complex helpers (Numba-friendly) ----------
8
+
9
+ @njit(cache=True)
10
+ def _cabs(z):
11
+ return math.hypot(z.real, z.imag)
12
+
13
+ @njit(cache=True)
14
+ def _csqrt(z):
15
+ # principal square root
16
+ r = _cabs(z)
17
+ if r == 0.0:
18
+ return 0.0 + 0.0j
19
+ # sqrt in polar form
20
+ theta = math.atan2(z.imag, z.real)
21
+ sr = math.sqrt(r)
22
+ ht = 0.5 * theta
23
+ return complex(sr * math.cos(ht), sr * math.sin(ht))
24
+
25
+ @njit(cache=True)
26
+ def _ccbrt(z):
27
+ # principal cube root
28
+ r = _cabs(z)
29
+ if r == 0.0:
30
+ return 0.0 + 0.0j
31
+ theta = math.atan2(z.imag, z.real)
32
+ cr = r ** (1.0 / 3.0)
33
+ tt = theta / 3.0
34
+ return complex(cr * math.cos(tt), cr * math.sin(tt))
35
+
36
+
37
+ # ---------- Step 1: cubic roots for p(r)=0 ----------
38
+
39
+ @njit(cache=True)
40
+ def _cubic_roots_monic(a2, a1, a0):
41
+ """
42
+ Solve r^3 + a2*r^2 + a1*r + a0 = 0 (monic cubic).
43
+ Returns 3 complex roots (principal-branch Cardano).
44
+ """
45
+ # Depress: r = y - a2/3
46
+ one_third = 1.0 / 3.0
47
+ shift = a2 * one_third
48
+
49
+ p = a1 - (a2 * a2) * one_third
50
+ q = (2.0 * a2 * a2 * a2) / 27.0 - (a2 * a1) / 3.0 + a0
51
+
52
+ # Discriminant: Δ = (q/2)^2 + (p/3)^3
53
+ q2 = 0.5 * q
54
+ p3 = p * one_third
55
+ Delta = q2 * q2 + p3 * p3 * p3 # complex-safe
56
+
57
+ sqrtD = _csqrt(Delta)
58
+ u = _ccbrt(-q2 + sqrtD)
59
+ v = _ccbrt(-q2 - sqrtD)
60
+
61
+ y1 = u + v
62
+
63
+ # cube roots of unity:
64
+ # y2 = -(u+v)/2 + i*sqrt(3)/2*(u-v)
65
+ # y3 = -(u+v)/2 - i*sqrt(3)/2*(u-v)
66
+ half = 0.5
67
+ s3_2 = 0.5 * math.sqrt(3.0)
68
+
69
+ uv = u - v
70
+ y2 = -half * y1 + complex(-s3_2 * uv.imag, s3_2 * uv.real) # i*s3_2*(u-v)
71
+ y3 = -half * y1 - complex(-s3_2 * uv.imag, s3_2 * uv.real)
72
+
73
+ r1 = y1 - shift
74
+ r2 = y2 - shift
75
+ r3 = y3 - shift
76
+ return r1, r2, r3
77
+
78
+
79
+ # ---------- Step 2: solve Vandermonde for c0,c1,c2 ----------
80
+
81
+ @njit(cache=True)
82
+ def _solve_3x3_complex(M, b):
83
+ """
84
+ Solve M x = b for 3x3 complex M with partial pivoting.
85
+ Returns x (complex length-3).
86
+ """
87
+ A = M.copy()
88
+ rhs = b.copy()
89
+
90
+ # Forward elimination
91
+ for k in range(3):
92
+ # pivot
93
+ piv = k
94
+ best = _cabs(A[k, k])
95
+ for i in range(k + 1, 3):
96
+ v = _cabs(A[i, k])
97
+ if v > best:
98
+ best = v
99
+ piv = i
100
+ if piv != k:
101
+ # swap rows
102
+ for j in range(3):
103
+ tmp = A[k, j]
104
+ A[k, j] = A[piv, j]
105
+ A[piv, j] = tmp
106
+ tmp = rhs[k]
107
+ rhs[k] = rhs[piv]
108
+ rhs[piv] = tmp
109
+
110
+ akk = A[k, k]
111
+ # If akk is (near) zero, this can blow up; caller should avoid repeated roots.
112
+ for i in range(k + 1, 3):
113
+ factor = A[i, k] / akk
114
+ A[i, k] = 0.0 + 0.0j
115
+ for j in range(k + 1, 3):
116
+ A[i, j] = A[i, j] - factor * A[k, j]
117
+ rhs[i] = rhs[i] - factor * rhs[k]
118
+
119
+ # Back substitution
120
+ x = np.empty(3, dtype=np.complex128)
121
+ for i in range(2, -1, -1):
122
+ s = rhs[i]
123
+ for j in range(i + 1, 3):
124
+ s = s - A[i, j] * x[j]
125
+ x[i] = s / A[i, i]
126
+ return x
127
+
128
+
129
+ # ---------- Step 3: build e^A = c0 I + c1 A + c2 A^2 ----------
130
+
131
+ @njit(cache=True)
132
+ def expA_n3_numba(kappa, lambdas, w, tol=1e-10):
133
+ """
134
+ Computes exp(A) for A = -kappa * 1*w^T - diag(lambdas), where 1=(1,1,1)^T.
135
+ Steps:
136
+ 1) build cubic p(r)=det(rI-A) and find roots r1,r2,r3
137
+ 2) solve Vandermonde for c0,c1,c2 so exp(A)=c0 I + c1 A + c2 A^2
138
+ 3) evaluate polynomial in A
139
+
140
+ Returns: 3x3 float64 matrix.
141
+ """
142
+ l1, l2, l3 = lambdas[0], lambdas[1], lambdas[2]
143
+ w1, w2, w3 = w[0], w[1], w[2]
144
+
145
+ # --- Build monic cubic coefficients for p(r)=0:
146
+ # p(r) = (r+λ1)(r+λ2)(r+λ3) + κ[ w1(r+λ2)(r+λ3) + w2(r+λ1)(r+λ3) + w3(r+λ1)(r+λ2) ]
147
+ s1 = l1 + l2 + l3
148
+ s2 = l1*l2 + l1*l3 + l2*l3
149
+ s3 = l1*l2*l3
150
+
151
+ W = w1 + w2 + w3
152
+ B = w1*(l2+l3) + w2*(l1+l3) + w3*(l1+l2)
153
+ C = w1*(l2*l3) + w2*(l1*l3) + w3*(l1*l2)
154
+
155
+ a2 = s1 + kappa * W
156
+ a1 = s2 + kappa * B
157
+ a0 = s3 + kappa * C
158
+
159
+ r1, r2, r3 = _cubic_roots_monic(a2 + 0.0j, a1 + 0.0j, a0 + 0.0j)
160
+
161
+ # Guard against repeated/near-repeated roots (Vandermonde gets unstable)
162
+ if _cabs(r1 - r2) < tol or _cabs(r1 - r3) < tol or _cabs(r2 - r3) < tol:
163
+ # Simple fallback: scaling-and-squaring with truncated Taylor (kept Numba-friendly)
164
+ # This is not "steps 1-2-3" anymore, but prevents crashes when roots collide.
165
+ A = np.zeros((3, 3), dtype=np.float64)
166
+ for i in range(3):
167
+ for j in range(3):
168
+ A[i, j] = -kappa * w[j]
169
+ A[i, i] -= lambdas[i]
170
+
171
+ # scale
172
+ # crude bound on ||A||_1
173
+ colsum_max = 0.0
174
+ for j in range(3):
175
+ s = 0.0
176
+ for i in range(3):
177
+ s += abs(A[i, j])
178
+ if s > colsum_max:
179
+ colsum_max = s
180
+ s_pow = 0
181
+ if colsum_max > 0.5:
182
+ s_pow = int(math.ceil(math.log(colsum_max / 0.5) / math.log(2.0)))
183
+
184
+ As = A / (2.0 ** s_pow)
185
+
186
+ # exp(As) via Taylor up to N terms
187
+ E = np.eye(3, dtype=np.float64)
188
+ term = np.eye(3, dtype=np.float64)
189
+ N = 30
190
+ for n in range(1, N + 1):
191
+ # term = term @ As / n
192
+ newterm = np.zeros((3, 3), dtype=np.float64)
193
+ for i in range(3):
194
+ for j in range(3):
195
+ acc = 0.0
196
+ for k in range(3):
197
+ acc += term[i, k] * As[k, j]
198
+ newterm[i, j] = acc / n
199
+ term = newterm
200
+ E += term
201
+
202
+ # square back
203
+ for _ in range(s_pow):
204
+ EE = np.zeros((3, 3), dtype=np.float64)
205
+ for i in range(3):
206
+ for j in range(3):
207
+ acc = 0.0
208
+ for k in range(3):
209
+ acc += E[i, k] * E[k, j]
210
+ EE[i, j] = acc
211
+ E = EE
212
+ return E
213
+
214
+ # Vandermonde: [1 r r^2][c0,c1,c2]^T = exp(r)
215
+ V = np.empty((3, 3), dtype=np.complex128)
216
+ b = np.empty(3, dtype=np.complex128)
217
+
218
+ roots = (r1, r2, r3)
219
+ for i in range(3):
220
+ r = roots[i]
221
+ V[i, 0] = 1.0 + 0.0j
222
+ V[i, 1] = r
223
+ V[i, 2] = r * r
224
+ b[i] = np.exp(r)
225
+
226
+ c = _solve_3x3_complex(V, b)
227
+ c0, c1, c2 = c[0], c[1], c[2]
228
+
229
+ # Build A (real)
230
+ A = np.zeros((3, 3), dtype=np.float64)
231
+ for i in range(3):
232
+ for j in range(3):
233
+ A[i, j] = -kappa * w[j]
234
+ A[i, i] -= lambdas[i]
235
+
236
+ # A^2
237
+ A2 = np.zeros((3, 3), dtype=np.float64)
238
+ for i in range(3):
239
+ for j in range(3):
240
+ acc = 0.0
241
+ for k in range(3):
242
+ acc += A[i, k] * A[k, j]
243
+ A2[i, j] = acc
244
+
245
+ # E = c0 I + c1 A + c2 A^2 (complex -> take real part)
246
+ E = np.zeros((3, 3), dtype=np.float64)
247
+ for i in range(3):
248
+ for j in range(3):
249
+ val = c1 * (A[i, j] + 0.0j) + c2 * (A2[i, j] + 0.0j)
250
+ if i == j:
251
+ val += c0
252
+ E[i, j] = val.real # imaginary parts should be ~0 for real A
253
+ return E
254
+
255
+ @njit(cache=True)
256
+ def expA_n1_numba (kappa, lambdas, w):
257
+ """
258
+ n=1: A = (lambda1 + kappa*w1). Returns scalar
259
+ lambdas: shape (1,)
260
+ w: shape (1,)
261
+ """
262
+ a = -(lambdas[0] + kappa * w[0])
263
+ out = math.exp(a)
264
+ return out
265
+
266
+ @njit(cache=True)
267
+ def _sinhc(z, tol=1e-12):
268
+ """sinh(z)/z with small-z series for stability (Numba-friendly)."""
269
+ if abs(z) < tol:
270
+ z2 = z * z
271
+ return 1.0 + 22 / 6.0 + (z2 *z2) / 120.0
272
+ return cmath.sinh(z) / z
273
+
274
+
275
+ @njit(cache=True)
276
+ def expA_n2_numba(kappa, lambdas, w):
277
+ """n=2: A = -kappa*1*w^T - diag(lambdas). Returns 2x2 exp(A).
278
+ lambdas: shape (2,)
279
+ w: shape (2,)
280
+ """
281
+ l1, l2 = lambdas[0], lambdas [1]
282
+ w1, w2 = w[0], w[1]
283
+
284
+ # A entries
285
+ a11 = -l1 - kappa * w1
286
+ a12 = -kappa * w2
287
+ a21 = -kappa * w1
288
+ a22 = -l2 - kappa * w2
289
+
290
+ tr = a11 + a22
291
+ det = a11 * a22 - a12 * a21
292
+
293
+ half_tr = 0.5 * tr
294
+ Delta = cmath.sqrt((half_tr * half_tr) - det + 0.0j)
295
+
296
+ ehalf = math.exp(half_tr) # half_tr is real
297
+ c = cmath.cosh(Delta)
298
+ s_over = _sinhc(Delta)
299
+
300
+ # B = A - (tr/2) I
301
+ b11 = a11 - half_tr
302
+ b22 = a22 - half_tr
303
+ b12 = a12
304
+ b21 = a21
305
+
306
+ # exp(A) = e^{tr/2} [ c I + (sinh(Delta)/Delta) * B ]
307
+ E11 = ehalf * (c + s_over * (b11 + 0.0j))
308
+ E22 = ehalf * (c + s_over * (b22 + 0.0j))
309
+ E12 = ehalf * (s_over * (b12 + 0.0j))
310
+ E21 = ehalf * (s_over * (b21 + 0.0j))
311
+
312
+ out = np.empty((2, 2), dtype = np.float64)
313
+ out[0, 0] = E11.real
314
+ out[0, 1] = E12.real
315
+ out[1, 0] = E21.real
316
+ out[1, 1] = E22.real
317
+ return out
318
+
319
+
320
+
321
+ @njit(parallel=True, cache=True)
322
+ def batch_expA_n3(kappas: np.ndarray, lambdas_all: np.ndarray, w_all: np.ndarray):
323
+ """
324
+ kappas: shape (N,)
325
+ lambdas_all: shape (N,3)
326
+ w_all: shape (N,3)
327
+ returns: shape (N,3,3)
328
+ """
329
+ N = kappas.shape[-1]
330
+ out = np.empty((N,3,3), dtype=np.float64)
331
+ for i in prange(N):
332
+ out[i] = expA_n3_numba(kappas[i], lambdas_all[i], w_all[i])
333
+ return out
334
+
335
+
336
+ @njit(parallel=True, cache=True)
337
+ def batch_expA_n2(kappas: np.ndarray, lambdas_all: np.ndarray, w_all: np.ndarray):
338
+ """
339
+ kappas: shape (N,)
340
+ lambdas_all: shape (N,2)
341
+ w_all: shape (N,2)
342
+ returns: shape (N,2,2)
343
+ """
344
+ N = kappas.shape[-1]
345
+ out = np.empty((N,2,2), dtype=np.float64)
346
+ for i in prange(N):
347
+ out[i] = expA_n2_numba(kappas[i], lambdas_all[i], w_all[i])
348
+ return out
349
+
350
+
351
+ @njit(parallel=True, cache=True)
352
+ def batch_expA_n1(kappas: np.ndarray, lambdas_all: np.ndarray, w_all: np.ndarray):
353
+ """
354
+ kappas: shape (N,)
355
+ lambdas_all: shape (N,1)
356
+ w_all: shape (N,1)
357
+ returns: shape (N,1,1)
358
+ """
359
+ N = kappas.shape[-1]
360
+ out = np.empty((N,1,1), dtype=np.float64)
361
+ for i in prange(N):
362
+ out[i,0,0] = expA_n1_numba(kappas[i], lambdas_all[i], w_all[i])
363
+ return out
364
+
365
+
366
+
367
+ @njit(parallel=True, cache=True)
368
+ def batch_expA(kappas: np.ndarray, lambdas_all: np.ndarray, w_all: np.ndarray):
369
+ """
370
+ kappas: shape (N,)
371
+ lambdas_all: shape (N,d)
372
+ w_all: shape (N,d)
373
+ returns: shape (N,d,d)
374
+ """
375
+ d = lambdas_all.shape[-1]
376
+ if d==1:
377
+ return batch_expA_n1(kappas, lambdas_all, w_all)
378
+ elif d==2:
379
+ return batch_expA_n2(kappas, lambdas_all, w_all)
380
+ elif d==3:
381
+ return batch_expA_n3(kappas, lambdas_all, w_all)
382
+ else:
383
+ raise ValueError("Only d=1,2,3 are supported")
384
+
385
+
386
+ #########################################################################
387
+ @njit(cache=True)
388
+ def invA_rank1_numba_general(kappa, lambdas, w, tol=1e-14):
389
+ """Analytic inverse for A = -kappa*1*w^T - diag(lambdas), any n>=1.
390
+
391
+ Inputs:
392
+ kappa: float
393
+ lambdas: (n,) float64, diagonal entries of L (must be nonzero)
394
+ w: (n.) float64
395
+ tol: singularity tolerance for denom
396
+
397
+ Returns:
398
+ Ainv: (n,n) float64. If singular/ill-conditioned, returns all-Nan matrix.
399
+ """
400
+ n = lambdas.shape[0]
401
+ Ainv = np.empty((n, n), dtype=np.float64)
402
+
403
+ # d_i = 1/lambda_i, z_i = w_i/lambda_i, s = sum z_i
404
+ d = np.empty(n, dtype=np.float64)
405
+ z = np.empty(n, dtype=np.float64)
406
+
407
+ s = 0.0
408
+ for i in range(n):
409
+ lam = lambdas[i]
410
+ if lam == 0.0:
411
+ # singular because L^{-1} doesn't exist
412
+ for r in range(n):
413
+ for c in range(n):
414
+ Ainv[r, c] = np.nan
415
+ return Ainv
416
+ di = 1.0 / lam
417
+ d[i] = di
418
+ zi = w[i] * di
419
+ z[i] = zi
420
+ s += zi
421
+
422
+ denom = 1.0 + kappa * s
423
+ if abs(denom) < tol:
424
+ for r in range(n):
425
+ for c in range(n):
426
+ Ainv[r, c] = np.nan
427
+ return Ainv
428
+
429
+ alpha = kappa / denom
430
+ for i in range(n):
431
+ di = d[i]
432
+ for j in range(n):
433
+ val = alpha * di * z[j]
434
+ if i == j:
435
+ val -= di
436
+ Ainv[i, j] = val
437
+ return Ainv
438
+
439
+
440
+ @njit(parallel=True, cache=True)
441
+ def batch_invA(kappas: np.ndarray, lambdas_all: np.ndarray, w_all: np.ndarray):
442
+ """
443
+ kappas: shape (N,)
444
+ lambdas_all: shape (N,3)
445
+ w_all: shape (N,3)
446
+ returns: shape (N,3,3)
447
+ """
448
+ N, d = w_all.shape
449
+ out = np.empty((N,d,d), dtype=np.float64)
450
+ for i in prange(N):
451
+ out[i] = invA_rank1_numba_general(kappas[i], lambdas_all[i], w_all[i])
452
+ return out
@@ -6,13 +6,14 @@ from scipy.linalg import expm
6
6
  from numba import njit
7
7
  from numba import njit, objmode
8
8
 
9
- # from stochvolmodels.examples.run_pricing_options_on_qvar import nb_path
9
+ from stochvolmodels.pricers.rough_logsv.expm import batch_expA, batch_invA
10
10
 
11
11
 
12
12
  # from stochvolmodels.utils.config import VariableType
13
13
  # from RoughKernel import european_rule
14
14
  # from stochvolmodels.pricers.logsv_pricer import LogSvParams
15
15
 
16
+
16
17
  @njit(cache=False, fastmath=True)
17
18
  def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
18
19
  z0: np.ndarray, weight: np.ndarray, h: float):
@@ -22,7 +23,7 @@ def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: flo
22
23
  ----------
23
24
  nodes : (fixed argument) exponents x_i, array of size (n,)
24
25
  v0 : (fixed argument) array of size (n, nb_path)
25
- theta : long-run level, scaled by lambda, scalar
26
+ theta : long-run level, scalar
26
27
  kappa1 : linear mean-reversion speed, scalar
27
28
  kappa2 : quadratic mean-reversion speed, scalar
28
29
  z0 : initial values, array of size (n, nb_path)
@@ -34,16 +35,32 @@ def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: flo
34
35
  Array of size (n, nb_path)
35
36
  """
36
37
  assert nodes.shape == v0.shape == z0.shape == weight.shape
37
- n = weight.shape[0]
38
+ n = z0.shape[0]
38
39
  z0w = np.sum(weight * z0, axis=0)
40
+ g0 = (kappa1 + kappa2*z0w)*(theta-z0w)
41
+ k1 = -nodes * (z0 - v0)
42
+ for j in range(n):
43
+ k1[j] += g0
44
+ k1 *= 0.5 * h
45
+
46
+ zmid = z0 + k1
47
+ zmidw = np.sum(weight * zmid, axis=0)
48
+ gmid = (kappa1 + kappa2 * zmidw) * (theta - zmidw)
49
+ k2 = -nodes * (zmid - v0)
50
+ for j in range(n):
51
+ k2[j] += gmid
52
+ k2 *= h
53
+ Dzh = z0 + k2
39
54
 
40
55
  # s1 = -nodes[:, None] * (z0 - v0) * h + (kappa1 + kappa2 * z0w) * (theta - z0w) * h
41
56
  # s2 = -nodes[:, None] * (z0 + 0.5 * s1 - v0) * h + (kappa1 + kappa2 * (z0w + 0.5 * s1)) * (theta - (z0w + 0.5 * s1)) * h
42
57
  # Dzh = z0 + s2
43
58
 
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
59
+
60
+
61
+ # z1 = -nodes * (z0 - v0) * h + (kappa1 + kappa2 * z0w) * (theta - z0w) * h
62
+ # z2 = -nodes * (z0 + 0.5 * z1 - v0) * h + (kappa1 + kappa2 * (z0w + 0.5 * z1)) * (theta - (z0w + 0.5 * z1)) * h
63
+ # Dzh = z0 + z2
47
64
 
48
65
  # lamda_vec = np.repeat(lamda, n)
49
66
  # theta_vec = np.repeat(theta, n)[:, None]
@@ -57,6 +74,124 @@ def drift_ode_solve(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: flo
57
74
 
58
75
  return Dzh
59
76
 
77
+
78
+ @njit(cache=False, fastmath=True)
79
+ def drift_ode_solve2(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
80
+ z0: np.ndarray, weight: np.ndarray, h: float):
81
+ """
82
+
83
+ Parameters
84
+ ----------
85
+ nodes : (fixed argument) exponents x_i, array of size (n,)
86
+ v0 : (fixed argument) array of size (n, nb_path)
87
+ theta : long-run level, scalar
88
+ kappa1 : linear mean-reversion speed, scalar
89
+ kappa2 : quadratic mean-reversion speed, scalar
90
+ z0 : initial values, array of size (n, nb_path)
91
+ weight : wieghts, array of size (n, )
92
+ h : step size
93
+
94
+ Returns
95
+ -------
96
+ Array of size (n, nb_path)
97
+ """
98
+ assert nodes.shape == v0.shape == z0.shape == weight.shape
99
+ n, nb_path = weight.shape
100
+
101
+ # --- k1 ---
102
+ z0w = np.sum(weight * z0, axis=0)
103
+ c1 = (kappa1 + kappa2 * z0w) * (theta - z0w)
104
+ s1 = -nodes * (z0 - v0) + c1
105
+
106
+ # --- k2 ---
107
+ z_tmp = z0 + 0.5 * h * s1
108
+ z1w = np.sum(weight * z_tmp, axis=0)
109
+ c2 = (kappa1 + kappa2 * z1w) * (theta - z1w)
110
+ s2 = -nodes * (z_tmp - v0) + c2
111
+
112
+ # --- k3 ---
113
+ z_tmp = z0 + 0.5 * h * s2
114
+ z2w = np.sum(weight * z_tmp, axis=0)
115
+ c3 = (kappa1 + kappa2 * z2w) * (theta - z2w)
116
+ s3 = -nodes * (z_tmp - v0) + c3
117
+
118
+ # --- k4 ---
119
+ z_tmp = z0 + h * s3
120
+ z3w = np.sum(weight * z_tmp, axis=0)
121
+ c4 = (kappa1 + kappa2 * z3w) * (theta - z3w)
122
+ s4 = -nodes * (z_tmp - v0) + c4
123
+
124
+ zh = z0 + (h / 6.0) * (s1 + 2.0 * s2 + 2.0 * s3 + s4)
125
+
126
+ return zh
127
+
128
+
129
+ @njit(cache=False, fastmath=True)
130
+ def drift_ode_solve3(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
131
+ z0: np.ndarray, weight: np.ndarray, h: float):
132
+ """
133
+
134
+ Parameters
135
+ ----------
136
+ nodes : (fixed argument) exponents x_i, array of size (n,)
137
+ v0 : (fixed argument) array of size (n, nb_path)
138
+ theta : long-run level, scalar
139
+ kappa1 : linear mean-reversion speed, scalar
140
+ kappa2 : quadratic mean-reversion speed, scalar
141
+ z0 : initial values, array of size (n, nb_path)
142
+ weight : wieghts, array of size (n, )
143
+ h : step size
144
+
145
+ Returns
146
+ -------
147
+ Array of size (n, nb_path)
148
+ """
149
+ assert nodes.shape == v0.shape == z0.shape == weight.shape
150
+ n, nb_path = weight.shape
151
+ z0w = np.sum(weight * z0, axis=0)
152
+ kappa = kappa1 + kappa2 * z0w
153
+
154
+ b_ = np.zeros((nb_path, n))
155
+ for k in range(n):
156
+ b_[:, k] = kappa * theta + nodes[k] * v0[k]
157
+
158
+ eAh = batch_expA(kappa, nodes.T * h, weight.T * h)
159
+ I = np.identity(n)
160
+ invA = batch_invA(kappa, nodes.T, weight.T)
161
+ tmp2 = np.zeros((nb_path, n, n))
162
+ for i in range(n):
163
+ for j in range(n):
164
+ for k in range(n):
165
+ tmp2[:,i,j] += invA[:,i,k] * (eAh[:, k, j] - I[k,j])
166
+
167
+ Dzh_v1 = np.zeros((nb_path, n))
168
+ for i in range(n):
169
+ for j in range(n):
170
+ Dzh_v1[:, i] += eAh[:, i, j] * z0[j, :] + tmp2[:, i, j] * b_[:, j]
171
+ # for p in range(nb_path):
172
+ # Dzh_v1[p] = eAh[p] @ z0[..., p] + tmp2[p] @ b_[p]
173
+
174
+ # def ff2():
175
+ # lamda = kappa1 + kappa2 * z0w
176
+ # coeff0_vec = np.repeat(kappa1 * theta, n)
177
+ # Dzh_v2 = np.zeros_like(z0)
178
+ # for p in range(nb_path):
179
+ # diag_x = np.diag(nodes[:, p])
180
+ # lamda_vec = np.repeat(lamda[p], n)
181
+ # A = -np.outer(lamda_vec, weight[:, p]) - diag_x # matrix of size (n,n)
182
+ # b = coeff0_vec + diag_x @ v0[:, p]
183
+ # eAh_v2 = expm(A * h)
184
+ # Dzh_v2[:, p] = eAh_v2 @ z0[:, p] + (np.linalg.inv(A) @ (eAh_v2 - I)) @ b
185
+ #
186
+ # return Dzh_v2
187
+ #
188
+ # Dzh_v2 = ff2()
189
+ # diff = np.linalg.norm(Dzh_v2-Dzh_v1.T)
190
+ # assert diff < 1e-12
191
+
192
+ return Dzh_v1.T
193
+
194
+
60
195
  @njit(cache=False, fastmath=True)
61
196
  def diffus_sde_solve(y0: np.ndarray, weight: np.ndarray, volvol: float, h: float, nb_path: int,
62
197
  z_rand: np.ndarray):
@@ -101,9 +236,9 @@ def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1:
101
236
  -------
102
237
 
103
238
  """
104
- D_inn = drift_ode_solve(nodes, v0, theta, kappa1, kappa2, v_init, weight, 0.5 * h)
239
+ D_inn = drift_ode_solve2(nodes, v0, theta, kappa1, kappa2, v_init, weight, 0.5 * h)
105
240
  S_inn = diffus_sde_solve(D_inn, weight, volvol, h, nb_path, z_rand)
106
- sol = drift_ode_solve(nodes, v0, theta, kappa1, kappa2, S_inn, weight, 0.5 * h)
241
+ sol = drift_ode_solve2(nodes, v0, theta, kappa1, kappa2, S_inn, weight, 0.5 * h)
107
242
 
108
243
  return sol
109
244
 
@@ -124,6 +259,9 @@ def log_spot_full_solve2(nodes: np.ndarray, weight: np.ndarray,
124
259
  assert z0.shape == (nb_path,) and z1.shape == (nb_path,)
125
260
 
126
261
  vol_h = drift_diffus_strand(nodes, v0, theta, kappa1, kappa2, volvol, v, weight, h, nb_path, z0)
262
+ w_vol_h = np.sum(weight * vol_h, axis=0)
263
+ idx_bad = np.nonzero(np.logical_or(np.isnan(w_vol_h), w_vol_h <= 0.0))[0]
264
+ vol_h[:, idx_bad] = 1e-6
127
265
 
128
266
  wlam = weight * nodes
129
267
  vw = np.sum(weight * v, axis=0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stochvolmodels
3
- Version: 1.1.4
3
+ Version: 1.1.6
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>
@@ -47,6 +47,7 @@ stochvolmodels/data/fetch_option_chain.py
47
47
  stochvolmodels/data/option_chain.py
48
48
  stochvolmodels/data/test_option_chain.py
49
49
  stochvolmodels/examples/quick_run_lognormal_sv_pricer.py
50
+ stochvolmodels/examples/run_hawkes_pricer.py
50
51
  stochvolmodels/examples/run_heston.py
51
52
  stochvolmodels/examples/run_heston_sv_pricer.py
52
53
  stochvolmodels/examples/run_lognormal_sv_pricer.py
@@ -76,6 +77,7 @@ stochvolmodels/pricers/logsv/affine_expansion.py
76
77
  stochvolmodels/pricers/logsv/logsv_params.py
77
78
  stochvolmodels/pricers/logsv/vol_moments_ode.py
78
79
  stochvolmodels/pricers/rough_logsv/RoughKernel.py
80
+ stochvolmodels/pricers/rough_logsv/expm.py
79
81
  stochvolmodels/pricers/rough_logsv/split_simulation.py
80
82
  stochvolmodels/pricers/rough_logsv/test_kernel_approx.py
81
83
  stochvolmodels/tests/__init__.py
File without changes
File without changes