stochvolmodels 1.1.6__tar.gz → 1.1.7__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 (97) hide show
  1. {stochvolmodels-1.1.6/stochvolmodels.egg-info → stochvolmodels-1.1.7}/PKG-INFO +3 -2
  2. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/pyproject.toml +4 -3
  3. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/requirements.txt +0 -0
  4. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv_pricer.py +10 -8
  5. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/split_simulation.py +188 -22
  6. stochvolmodels-1.1.7/stochvolmodels/tests/rough_logsv_perf.py +302 -0
  7. stochvolmodels-1.1.7/stochvolmodels/tests/test_rough_logsv_pricer_regression/test_rough_logsv_pricer_pricing_regression.yml +50 -0
  8. stochvolmodels-1.1.7/stochvolmodels/tests/test_rough_logsv_pricer_regression.py +50 -0
  9. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7/stochvolmodels.egg-info}/PKG-INFO +3 -2
  10. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels.egg-info/SOURCES.txt +3 -0
  11. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels.egg-info/requires.txt +2 -1
  12. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/.gitignore +0 -0
  13. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/LICENSE.txt +0 -0
  14. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/MANIFEST.in +0 -0
  15. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/README.md +0 -0
  16. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/forward_var/calibrate_forward_var.py +0 -0
  17. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/il_hedging/README.md +0 -0
  18. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/il_hedging/logsv_figures.py +0 -0
  19. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/il_hedging/run_logsv_for_il_payoff.py +0 -0
  20. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/inverse_options/README.md +0 -0
  21. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/inverse_options/compare_net_delta.py +0 -0
  22. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/README.md +0 -0
  23. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/article_figures.py +0 -0
  24. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/calibrations.py +0 -0
  25. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/compare_admis_reg.py +0 -0
  26. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/model_fit_to_options_timeseries.py +0 -0
  27. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/moments_vol_qvar.py +0 -0
  28. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/ode_sol_in_time.py +0 -0
  29. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/steady_state_pdf.py +0 -0
  30. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/vol_drift.py +0 -0
  31. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/check_kernel.py +0 -0
  32. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/gmm_slides.py +0 -0
  33. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/plot_gmm.py +0 -0
  34. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/q_kernel.py +0 -0
  35. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/run_gmm_fit.py +0 -0
  36. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/README.md +0 -0
  37. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/calibration_fig_5_6_7.py +0 -0
  38. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/calibration_fig_8_9.py +0 -0
  39. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/t_distribution/illustrations.py +0 -0
  40. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/t_distribution/market_data_fit.py +0 -0
  41. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/t_distribution/mc_pricer_with_kernel.py +0 -0
  42. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/README.md +0 -0
  43. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/article_figures.py +0 -0
  44. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/autocorr_fit.py +0 -0
  45. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/load_data.py +0 -0
  46. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/ss_distribution_fit.py +0 -0
  47. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/vol_beta.py +0 -0
  48. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/setup.cfg +0 -0
  49. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/__init__.py +0 -0
  50. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/__init__.py +0 -0
  51. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/fetch_option_chain.py +0 -0
  52. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/option_chain.py +0 -0
  53. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/test_option_chain.py +0 -0
  54. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/quick_run_lognormal_sv_pricer.py +0 -0
  55. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_hawkes_pricer.py +0 -0
  56. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_heston.py +0 -0
  57. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_heston_sv_pricer.py +0 -0
  58. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_lognormal_sv_pricer.py +0 -0
  59. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_pricing_options_on_qvar.py +0 -0
  60. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/__init__.py +0 -0
  61. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/__init__.py +0 -0
  62. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/bachelier.py +0 -0
  63. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/bsm.py +0 -0
  64. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/tdist.py +0 -0
  65. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py +0 -0
  66. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py +0 -0
  67. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_affine_expansion.py +0 -0
  68. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_core.py +0 -0
  69. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py +0 -0
  70. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py +0 -0
  71. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py +0 -0
  72. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py +0 -0
  73. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py +0 -0
  74. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/gmm_pricer.py +0 -0
  75. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/hawkes_jd_pricer.py +0 -0
  76. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/heston_pricer.py +0 -0
  77. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/__init__.py +0 -0
  78. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/affine_expansion.py +0 -0
  79. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/logsv_params.py +0 -0
  80. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/vol_moments_ode.py +0 -0
  81. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/model_pricer.py +0 -0
  82. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/RoughKernel.py +0 -0
  83. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/expm.py +0 -0
  84. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/test_kernel_approx.py +0 -0
  85. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/tdist_pricer.py +0 -0
  86. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/tests/__init__.py +0 -0
  87. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/tests/bsm_mgf_pricer.py +0 -0
  88. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/tests/qv_pricer.py +0 -0
  89. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/__init__.py +0 -0
  90. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/config.py +0 -0
  91. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/funcs.py +0 -0
  92. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/mc_payoffs.py +0 -0
  93. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/mgf_pricer.py +0 -0
  94. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/plots.py +0 -0
  95. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/var_swap_pricer.py +0 -0
  96. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels.egg-info/dependency_links.txt +0 -0
  97. {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/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.6
3
+ Version: 1.1.7
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>
@@ -727,9 +727,10 @@ Requires-Dist: jupyterlab>=3.0.0; extra == "jupyter"
727
727
  Requires-Dist: ipykernel>=6.0.0; extra == "jupyter"
728
728
  Requires-Dist: ipywidgets>=8.0.0; extra == "jupyter"
729
729
  Provides-Extra: dev
730
- Requires-Dist: pytest>=7.0.0; extra == "dev"
730
+ Requires-Dist: pytest>=8.4.2; extra == "dev"
731
731
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
732
732
  Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
733
+ Requires-Dist: pytest-regressions>=2.8.3; extra == "dev"
733
734
  Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
734
735
  Requires-Dist: black>=22.0.0; extra == "dev"
735
736
  Requires-Dist: flake8>=5.0.0; extra == "dev"
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "stochvolmodels"
10
- version = "1.1.6"
10
+ version = "1.1.7"
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"}
@@ -95,9 +95,10 @@ jupyter = [
95
95
 
96
96
  # Development tools and testing
97
97
  dev = [
98
- "pytest>=7.0.0",
98
+ "pytest>=8.4.2",
99
99
  "pytest-cov>=4.0.0",
100
100
  "pytest-mock>=3.10.0",
101
+ "pytest-regressions>=2.8.3",
101
102
  "pytest-xdist>=3.0.0",
102
103
  "black>=22.0.0",
103
104
  "flake8>=5.0.0",
@@ -276,4 +277,4 @@ exclude = [
276
277
  "venv",
277
278
  ".eggs",
278
279
  "*.egg",
279
- ]
280
+ ]
@@ -875,12 +875,14 @@ def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
875
875
  weights: np.ndarray,
876
876
  nodes: np.ndarray,
877
877
  timegrids: List[np.ndarray],
878
- variable_type: VariableType = VariableType.LOG_RETURN
878
+ variable_type: VariableType = VariableType.LOG_RETURN,
879
+ debug: bool = True
879
880
  ) -> Tuple[List[np.ndarray], List[np.ndarray]]:
880
881
  assert weights.shape == nodes.shape and weights.ndim == 1
881
882
  # assert kappa2 == 0.0
882
883
  N = nodes.size
883
- v0 = sigma0 / np.sum(weights) * np.ones((N,))
884
+ dtype = weights.dtype
885
+ v0 = np.full((N,), sigma0 / np.sum(weights), dtype=dtype)
884
886
 
885
887
  # need to redenote coefficients
886
888
  volvol = np.sqrt(beta ** 2 + orthog_vol ** 2)
@@ -889,7 +891,9 @@ def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
889
891
  nb_path = Z0.shape[1]
890
892
  v0_vec = np.repeat(v0[:, None], nb_path, axis=1)
891
893
  v_init = v0_vec.copy()
892
- log_s0 = 0.0
894
+ log_s0 = dtype.type(0.0)
895
+ weight_vec = np.repeat(weights[:, None], nb_path, axis=1)
896
+ nodes_vec = np.repeat(nodes[:, None], nb_path, axis=1)
893
897
 
894
898
  # outputs as numpy lists
895
899
  option_prices_ttm = List()
@@ -902,12 +906,11 @@ def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
902
906
  nb_steps = timegrid.size - 1
903
907
  Z0_ = Z0[:nb_steps]
904
908
  Z1_ = Z1[:nb_steps]
905
- weight_vec = np.repeat(weights[:, None], nb_path,axis=1)
906
- nodes_vec = np.repeat(nodes[:, None], nb_path,axis=1)
907
909
  log_spot_str, vol_str, qv_str = log_spot_full_combined(nodes_vec, weight_vec, v0_vec, theta, kappa1, kappa2, log_s0,
908
910
  v_init, rho, volvol, timegrid, nb_path, Z0_, Z1_)
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))}")
911
+ if debug:
912
+ 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))}")
913
+ print(f"Mean spot Strand: {np.mean(np.exp(log_spot_str))}, nan spots: {np.count_nonzero(np.isnan(log_spot_str))}")
911
914
 
912
915
  option_prices, option_std = compute_mc_vars_payoff(x0=log_spot_str, sigma0=vol_str, qvar0=qv_str,
913
916
  ttm=ttm,
@@ -921,7 +924,6 @@ def rough_logsv_mc_chain_pricer_fixed_randoms(ttms: np.ndarray,
921
924
 
922
925
  return option_prices_ttm, option_std_ttm
923
926
 
924
-
925
927
  class LocalTests(Enum):
926
928
  CHAIN_PRICER = 1
927
929
  SLICE_PRICER = 2
@@ -126,6 +126,43 @@ def drift_ode_solve2(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: fl
126
126
  return zh
127
127
 
128
128
 
129
+ @njit(cache=False, fastmath=True)
130
+ def drift_ode_solve2_f32(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
131
+ z0: np.ndarray, weight: np.ndarray, h: float):
132
+ """
133
+ Float32-friendly variant of drift_ode_solve2 (avoids float64 literals promoting intermediates).
134
+ """
135
+ assert nodes.shape == v0.shape == z0.shape == weight.shape
136
+ n, nb_path = weight.shape
137
+
138
+ half = np.float32(0.5)
139
+ two = np.float32(2.0)
140
+ six = np.float32(6.0)
141
+
142
+ z0w = np.sum(weight * z0, axis=0)
143
+ c1 = (kappa1 + kappa2 * z0w) * (theta - z0w)
144
+ s1 = -nodes * (z0 - v0) + c1
145
+
146
+ z_tmp = z0 + (half * h) * s1
147
+ z1w = np.sum(weight * z_tmp, axis=0)
148
+ c2 = (kappa1 + kappa2 * z1w) * (theta - z1w)
149
+ s2 = -nodes * (z_tmp - v0) + c2
150
+
151
+ z_tmp = z0 + (half * h) * s2
152
+ z2w = np.sum(weight * z_tmp, axis=0)
153
+ c3 = (kappa1 + kappa2 * z2w) * (theta - z2w)
154
+ s3 = -nodes * (z_tmp - v0) + c3
155
+
156
+ z_tmp = z0 + h * s3
157
+ z3w = np.sum(weight * z_tmp, axis=0)
158
+ c4 = (kappa1 + kappa2 * z3w) * (theta - z3w)
159
+ s4 = -nodes * (z_tmp - v0) + c4
160
+
161
+ zh = z0 + (h / six) * (s1 + two * s2 + two * s3 + s4)
162
+
163
+ return zh
164
+
165
+
129
166
  @njit(cache=False, fastmath=True)
130
167
  def drift_ode_solve3(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
131
168
  z0: np.ndarray, weight: np.ndarray, h: float):
@@ -193,9 +230,9 @@ def drift_ode_solve3(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: fl
193
230
 
194
231
 
195
232
  @njit(cache=False, fastmath=True)
196
- def diffus_sde_solve(y0: np.ndarray, weight: np.ndarray, volvol: float, h: float, nb_path: int,
197
- z_rand: np.ndarray):
198
- assert y0.shape == weight.shape and y0.shape[-1] == nb_path
233
+ def diffus_sde_solve_f64(y0: np.ndarray, weight: np.ndarray, volvol: float, h: float, nb_path: int,
234
+ z_rand: np.ndarray):
235
+ assert y0.shape == weight.shape and y0.shape[-1] == nb_path
199
236
  assert z_rand.shape == (nb_path,)
200
237
  weight_sum = np.sum(weight, axis=0)
201
238
  volvol_ = volvol * weight_sum
@@ -212,10 +249,11 @@ def diffus_sde_solve(y0: np.ndarray, weight: np.ndarray, volvol: float, h: float
212
249
 
213
250
  return Yh_vec
214
251
 
252
+
215
253
  @njit(cache=False, fastmath=True)
216
- def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
217
- volvol: float, v_init: np.ndarray, weight: np.ndarray, h: float,
218
- nb_path: int, z_rand: np.ndarray):
254
+ def drift_diffus_strand_f64(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
255
+ volvol: float, v_init: np.ndarray, weight: np.ndarray, h: float,
256
+ nb_path: int, z_rand: np.ndarray):
219
257
  """
220
258
 
221
259
  Parameters
@@ -237,18 +275,18 @@ def drift_diffus_strand(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1:
237
275
 
238
276
  """
239
277
  D_inn = drift_ode_solve2(nodes, v0, theta, kappa1, kappa2, v_init, weight, 0.5 * h)
240
- S_inn = diffus_sde_solve(D_inn, weight, volvol, h, nb_path, z_rand)
278
+ S_inn = diffus_sde_solve_f64(D_inn, weight, volvol, h, nb_path, z_rand)
241
279
  sol = drift_ode_solve2(nodes, v0, theta, kappa1, kappa2, S_inn, weight, 0.5 * h)
242
280
 
243
281
  return sol
244
282
 
245
283
 
246
284
  @njit(cache=False, fastmath=True)
247
- def log_spot_full_solve2(nodes: np.ndarray, weight: np.ndarray,
248
- v0: np.ndarray, y0: np.ndarray,
249
- theta: float, kappa1: float, kappa2: float, log_s: np.ndarray, v: np.ndarray, y: np.ndarray,
250
- rho: float, volvol: float, h: float, nb_path: int,
251
- z0: np.ndarray, z1: np.ndarray):
285
+ def log_spot_full_solve2_f64(nodes: np.ndarray, weight: np.ndarray,
286
+ v0: np.ndarray, y0: np.ndarray,
287
+ theta: float, kappa1: float, kappa2: float, log_s: np.ndarray, v: np.ndarray, y: np.ndarray,
288
+ rho: float, volvol: float, h: float, nb_path: int,
289
+ z0: np.ndarray, z1: np.ndarray):
252
290
  # raise ValueError
253
291
  assert nodes.shape == weight.shape and weight.ndim == 2
254
292
  assert v.shape == weight.shape and v.shape[-1] == nb_path
@@ -258,7 +296,7 @@ def log_spot_full_solve2(nodes: np.ndarray, weight: np.ndarray,
258
296
  assert y0.shape == (1, nb_path)
259
297
  assert z0.shape == (nb_path,) and z1.shape == (nb_path,)
260
298
 
261
- vol_h = drift_diffus_strand(nodes, v0, theta, kappa1, kappa2, volvol, v, weight, h, nb_path, z0)
299
+ vol_h = drift_diffus_strand_f64(nodes, v0, theta, kappa1, kappa2, volvol, v, weight, h, nb_path, z0)
262
300
  w_vol_h = np.sum(weight * vol_h, axis=0)
263
301
  idx_bad = np.nonzero(np.logical_or(np.isnan(w_vol_h), w_vol_h <= 0.0))[0]
264
302
  vol_h[:, idx_bad] = 1e-6
@@ -290,12 +328,13 @@ def log_spot_full_solve2(nodes: np.ndarray, weight: np.ndarray,
290
328
 
291
329
  return vol_h, y_h, log_spot_h
292
330
 
331
+
293
332
  @njit(cache=False, fastmath=True)
294
- def log_spot_full_combined(nodes: np.ndarray, weight: np.ndarray,
295
- v0: np.ndarray,
296
- theta: float, kappa1: float, kappa2: float, log_s0: float, v_init: np.ndarray,
297
- rho: float, volvol: float, timegrid: np.ndarray, nb_path: int,
298
- Z0: np.ndarray, Z1: np.ndarray):
333
+ def log_spot_full_combined_f64(nodes: np.ndarray, weight: np.ndarray,
334
+ v0: np.ndarray,
335
+ theta: float, kappa1: float, kappa2: float, log_s0: float, v_init: np.ndarray,
336
+ rho: float, volvol: float, timegrid: np.ndarray, nb_path: int,
337
+ Z0: np.ndarray, Z1: np.ndarray):
299
338
  h = timegrid[1] - timegrid[0]
300
339
  # assert np.all(np.isclose(np.diff(timegrid)[1:], h)) and timegrid[0] == 0.0
301
340
  # assert Z0.shape == (timegrid.size - 1, nb_path) and Z1.shape == (timegrid.size - 1, nb_path)
@@ -306,11 +345,138 @@ def log_spot_full_combined(nodes: np.ndarray, weight: np.ndarray,
306
345
  y_h = np.zeros((1, nb_path))
307
346
  log_spot_h = np.ones((1, nb_path)) * log_s0
308
347
 
309
- # a, b, c = solve_coeffs(nodes, weight, v0[:, 0], theta, lamda, rho, volvol)
348
+ for idx, _ in enumerate(timegrid[:-1]):
349
+ vol_h, y_h, log_spot_h = log_spot_full_solve2_f64(nodes, weight, v0, y0, theta, kappa1, kappa2,
350
+ log_spot_h, vol_h, y_h, rho, volvol, h, nb_path,
351
+ Z0[idx], Z1[idx])
352
+
353
+ return log_spot_h, vol_h, y_h
354
+
355
+
356
+ @njit(cache=False, fastmath=True)
357
+ def diffus_sde_solve_f32(y0: np.ndarray, weight: np.ndarray, weight_sum: np.ndarray,
358
+ volvol_weight_sum: np.ndarray, h: float, sqrt_h: float, nb_path: int,
359
+ z_rand: np.ndarray):
360
+ assert y0.shape == weight.shape and y0.shape[-1] == nb_path
361
+ assert z_rand.shape == (nb_path,)
362
+ yw = np.sum(weight * y0, axis=0)
363
+
364
+ half = np.float32(0.5)
365
+ one = np.float32(1.0)
366
+ dW = z_rand * sqrt_h
367
+ Yh = yw * np.exp(-half * volvol_weight_sum * volvol_weight_sum * h + volvol_weight_sum * dW)
368
+
369
+ Q = one / weight_sum * (Yh - yw)
370
+ Yh_vec = y0.copy()
371
+ for i in range(Yh_vec.shape[0]):
372
+ Yh_vec[i] += Q
373
+
374
+ return Yh_vec
375
+
376
+
377
+ @njit(cache=False, fastmath=True)
378
+ def drift_diffus_strand_f32(nodes: np.ndarray, v0: np.ndarray, theta: float, kappa1: float, kappa2: float,
379
+ volvol_weight_sum: np.ndarray, v_init: np.ndarray, weight: np.ndarray, h: float,
380
+ sqrt_h: float, weight_sum: np.ndarray, nb_path: int, z_rand: np.ndarray):
381
+ half = np.float32(0.5)
382
+ D_inn = drift_ode_solve2_f32(nodes, v0, theta, kappa1, kappa2, v_init, weight, half * h)
383
+ S_inn = diffus_sde_solve_f32(D_inn, weight, weight_sum, volvol_weight_sum, h, sqrt_h, nb_path, z_rand)
384
+ sol = drift_ode_solve2_f32(nodes, v0, theta, kappa1, kappa2, S_inn, weight, half * h)
385
+
386
+ return sol
387
+
388
+
389
+ @njit(cache=False, fastmath=True)
390
+ def log_spot_full_solve2_f32(nodes: np.ndarray, weight: np.ndarray, wlam: np.ndarray, w_inv: np.ndarray,
391
+ w_lam_v0: np.ndarray, weight_sum: np.ndarray, v0: np.ndarray, y0: np.ndarray,
392
+ theta: float, kappa1: float, kappa2: float, log_s: np.ndarray, v: np.ndarray, y: np.ndarray,
393
+ rho: float, rho_comp: float, inv_volvol: float, volvol_weight_sum: np.ndarray,
394
+ h: float, sqrt_h: float, nb_path: int, z0: np.ndarray, z1: np.ndarray):
395
+ assert nodes.shape == weight.shape and weight.ndim == 2
396
+ assert v.shape == weight.shape and v.shape[-1] == nb_path
397
+ assert y.shape == (1, nb_path)
398
+ assert log_s.shape == (1, nb_path)
399
+ assert v0.shape == weight.shape and v0.shape[-1] == nb_path
400
+ assert y0.shape == (1, nb_path)
401
+ assert z0.shape == (nb_path,) and z1.shape == (nb_path,)
402
+
403
+ half = np.float32(0.5)
404
+ eps = np.float32(1e-6)
405
+
406
+ vol_h = drift_diffus_strand_f32(nodes, v0, theta, kappa1, kappa2, volvol_weight_sum, v, weight, h, sqrt_h, weight_sum, nb_path, z0)
407
+ volw_h = np.sum(weight * vol_h, axis=0)
408
+ idx_bad = np.nonzero(np.logical_or(np.isnan(volw_h), volw_h <= 0.0))[0]
409
+ vol_h[:, idx_bad] = eps
410
+
411
+ vw = np.sum(weight * v, axis=0)
412
+
413
+ c1 = half
414
+ c2 = half
415
+
416
+ sq_vw = np.square(vw)
417
+ sq_vhw = np.square(volw_h)
418
+
419
+ w_lam_vol = np.sum(wlam * v, axis=0)
420
+ w_lam_vol_h = np.sum(wlam * vol_h, axis=0)
421
+
422
+ term1 = inv_volvol * (((volw_h - vw) / h + c1 * w_lam_vol + c2 * w_lam_vol_h - w_lam_v0) * w_inv
423
+ - kappa1 * theta + (kappa1 - kappa2 * theta) * (c1 * vw + c2 * volw_h)
424
+ + kappa2 * (c1 * sq_vw + c2 * sq_vhw)) * h
425
+
426
+ term2 = c1 * h * sq_vw + c2 * h * sq_vhw
427
+ log_spot_h = log_s - half * term2 + rho * term1 + rho_comp * np.sqrt(term2) * z1
428
+
429
+ y_h = y + half * h * (vw * vw + volw_h * volw_h)
430
+
431
+ return vol_h, y_h, log_spot_h
432
+
433
+
434
+ @njit(cache=False, fastmath=True)
435
+ def log_spot_full_combined_f32(nodes: np.ndarray, weight: np.ndarray,
436
+ v0: np.ndarray,
437
+ theta: float, kappa1: float, kappa2: float, log_s0: float, v_init: np.ndarray,
438
+ rho: float, volvol: float, timegrid: np.ndarray, nb_path: int,
439
+ Z0: np.ndarray, Z1: np.ndarray):
440
+ one = np.float32(1.0)
441
+ h = timegrid[1] - timegrid[0]
442
+ sqrt_h = np.sqrt(h)
443
+ wlam = weight * nodes
444
+ weight_sum = np.sum(weight, axis=0)
445
+ w_inv = one / weight_sum
446
+ w_lam_v0 = np.sum(wlam * v0, axis=0)
447
+ rho_comp = np.sqrt(one - rho * rho)
448
+ inv_volvol = one / volvol
449
+ volvol_weight_sum = volvol * weight_sum
450
+
451
+ y0 = np.zeros_like(v0[:1, :])
452
+
453
+ vol_h = v_init.copy()
454
+ y_h = np.zeros_like(y0)
455
+ log_spot_h = np.empty_like(y0)
456
+ log_spot_h[:] = log_s0
310
457
 
311
458
  for idx, _ in enumerate(timegrid[:-1]):
312
- vol_h, y_h, log_spot_h = log_spot_full_solve2(nodes, weight, v0, y0, theta, kappa1, kappa2,
313
- log_spot_h, vol_h, y_h, rho, volvol, h, nb_path,
314
- Z0[idx], Z1[idx])
459
+ vol_h, y_h, log_spot_h = log_spot_full_solve2_f32(nodes, weight, wlam, w_inv, w_lam_v0, weight_sum, v0, y0,
460
+ theta, kappa1, kappa2, log_spot_h, vol_h, y_h, rho,
461
+ rho_comp, inv_volvol, volvol_weight_sum, h, sqrt_h,
462
+ nb_path, Z0[idx], Z1[idx])
315
463
 
316
464
  return log_spot_h, vol_h, y_h
465
+
466
+
467
+ def log_spot_full_combined(nodes: np.ndarray, weight: np.ndarray,
468
+ v0: np.ndarray,
469
+ theta: float, kappa1: float, kappa2: float, log_s0: float, v_init: np.ndarray,
470
+ rho: float, volvol: float, timegrid: np.ndarray, nb_path: int,
471
+ Z0: np.ndarray, Z1: np.ndarray):
472
+ """
473
+ Dispatch to a float64-stable kernel by default; use float32 kernel only when all inputs are float32.
474
+ """
475
+ if (nodes.dtype == np.float32 and weight.dtype == np.float32 and v0.dtype == np.float32 and
476
+ timegrid.dtype == np.float32 and Z0.dtype == np.float32 and Z1.dtype == np.float32):
477
+ return log_spot_full_combined_f32(nodes, weight, v0, theta, kappa1, kappa2, log_s0, v_init,
478
+ rho, volvol, timegrid, nb_path, Z0, Z1)
479
+ return log_spot_full_combined_f64(nodes, weight, v0, theta, kappa1, kappa2, log_s0, v_init,
480
+ rho, volvol, timegrid, nb_path, Z0, Z1)
481
+
482
+
@@ -0,0 +1,302 @@
1
+ """
2
+ Benchmark rough LogSV pricing over parameter perturbations.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import gc
8
+ import time
9
+ import tracemalloc
10
+ import threading
11
+ from dataclasses import dataclass
12
+ from typing import List, Optional, Tuple
13
+
14
+ import numpy as np
15
+ import pandas as pd
16
+
17
+ import stochvolmodels.pricers.logsv_pricer as sv
18
+ from stochvolmodels import LogSvParams
19
+ from stochvolmodels.data.test_option_chain import get_btc_test_chain_data
20
+ from stochvolmodels.utils.funcs import set_time_grid
21
+
22
+ import psutil
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class PerfResult:
27
+ name: str
28
+ cpu_seconds: float
29
+ mem_current_kib: float
30
+ mem_peak_kib: float
31
+ rss_before_kib: Optional[float]
32
+ rss_after_kib: Optional[float]
33
+ rss_delta_kib: Optional[float]
34
+ rss_peak_delta_kib: Optional[float]
35
+ rss_peak_kib: Optional[float]
36
+ checksum: float
37
+
38
+
39
+ def _perturb_value(value: float, rel: float) -> float:
40
+ return value * (1.0 + rel)
41
+
42
+
43
+ def build_param_variants(base: LogSvParams,
44
+ count: int = 100,
45
+ rel_scale: float = 0.02,
46
+ seed: int = 123,
47
+ param_dtype: Optional[np.dtype] = None) -> List[Tuple[str, LogSvParams]]:
48
+ """Create a deterministic list of slightly perturbed parameter sets."""
49
+ if param_dtype is not None:
50
+ param_dtype = np.dtype(param_dtype)
51
+ rng = np.random.default_rng(seed)
52
+ keys = ("sigma0", "theta", "kappa1", "kappa2", "beta", "volvol")
53
+ variants: List[Tuple[str, LogSvParams]] = []
54
+
55
+ for i in range(count):
56
+ params = LogSvParams.copy(base)
57
+ deltas = rng.uniform(-rel_scale, rel_scale, size=len(keys))
58
+ # Ensure a non-zero perturbation for each variant.
59
+ if np.all(np.abs(deltas) < 1e-12):
60
+ deltas[0] = rel_scale
61
+ for key, rel in zip(keys, deltas):
62
+ value = _perturb_value(getattr(base, key), rel)
63
+ if param_dtype is not None:
64
+ value = param_dtype.type(value)
65
+ setattr(params, key, value)
66
+ # Keep kernel approximation constant across variants.
67
+ params.H = base.H
68
+ params.weights = np.array(base.weights, copy=True)
69
+ params.nodes = np.array(base.nodes, copy=True)
70
+ variants.append((f"var-{i:03d}", params))
71
+ return variants
72
+
73
+
74
+ def _rough_price_once(option_chain,
75
+ Z0: Optional[np.ndarray],
76
+ Z1: Optional[np.ndarray],
77
+ grid_ttms,
78
+ params: LogSvParams,
79
+ nb_path: int,
80
+ seed: int,
81
+ random_mode: str,
82
+ float32_mode: bool):
83
+ sigma0 = params.sigma0
84
+ theta = params.theta
85
+ kappa1 = params.kappa1
86
+ kappa2 = params.kappa2
87
+ beta = params.beta
88
+ volvol = params.volvol
89
+ if float32_mode:
90
+ sigma0 = np.float32(sigma0)
91
+ theta = np.float32(theta)
92
+ kappa1 = np.float32(kappa1)
93
+ kappa2 = np.float32(kappa2)
94
+ beta = np.float32(beta)
95
+ volvol = np.float32(volvol)
96
+ if random_mode == "seeded":
97
+ return sv.rough_logsv_mc_chain_pricer_seeded(
98
+ ttms=option_chain.ttms,
99
+ forwards=option_chain.forwards,
100
+ discfactors=option_chain.discfactors,
101
+ strikes_ttms=option_chain.strikes_ttms,
102
+ optiontypes_ttms=option_chain.optiontypes_ttms,
103
+ nb_path=nb_path,
104
+ seed=seed,
105
+ sigma0=sigma0,
106
+ theta=theta,
107
+ kappa1=kappa1,
108
+ kappa2=kappa2,
109
+ beta=beta,
110
+ orthog_vol=volvol,
111
+ weights=params.weights,
112
+ nodes=params.nodes,
113
+ timegrids=grid_ttms,
114
+ )
115
+ return sv.rough_logsv_mc_chain_pricer_fixed_randoms(
116
+ ttms=option_chain.ttms,
117
+ forwards=option_chain.forwards,
118
+ discfactors=option_chain.discfactors,
119
+ strikes_ttms=option_chain.strikes_ttms,
120
+ optiontypes_ttms=option_chain.optiontypes_ttms,
121
+ Z0=Z0,
122
+ Z1=Z1,
123
+ sigma0=sigma0,
124
+ theta=theta,
125
+ kappa1=kappa1,
126
+ kappa2=kappa2,
127
+ beta=beta,
128
+ orthog_vol=volvol,
129
+ weights=params.weights,
130
+ nodes=params.nodes,
131
+ timegrids=grid_ttms,
132
+ )
133
+
134
+
135
+ def _get_rss_kib() -> Optional[float]:
136
+ if psutil is None:
137
+ return None
138
+ return psutil.Process().memory_info().rss / 1024.0
139
+
140
+
141
+ class _RssSampler:
142
+ def __init__(self, interval_sec: float):
143
+ self.interval_sec = interval_sec
144
+ self.max_rss_kib: Optional[float] = None
145
+ self._stop = threading.Event()
146
+ self._thread: Optional[threading.Thread] = None
147
+
148
+ def _run(self) -> None:
149
+ proc = psutil.Process()
150
+ while not self._stop.is_set():
151
+ rss = proc.memory_info().rss / 1024.0
152
+ if self.max_rss_kib is None or rss > self.max_rss_kib:
153
+ self.max_rss_kib = rss
154
+ time.sleep(self.interval_sec)
155
+
156
+ def start(self) -> None:
157
+ if psutil is None:
158
+ return
159
+ self._stop.clear()
160
+ self._thread = threading.Thread(target=self._run, daemon=True)
161
+ self._thread.start()
162
+
163
+ def stop(self) -> None:
164
+ if self._thread is None:
165
+ return
166
+ self._stop.set()
167
+ self._thread.join()
168
+
169
+
170
+ def run_rough_logsv_loop_bench(nb_path: int = 50000,
171
+ nb_steps_per_year: int = 240,
172
+ seed: int = 10,
173
+ dtype: np.dtype = np.float32,
174
+ warmup: int = 1,
175
+ variants: int = 100,
176
+ rss_sample_interval: float = 0.01,
177
+ random_mode: str = "fixed",
178
+ float32_mode: bool = True) -> pd.DataFrame:
179
+ option_chain = get_btc_test_chain_data()
180
+
181
+ if random_mode not in ("fixed", "seeded"):
182
+ raise ValueError("random_mode must be 'fixed' or 'seeded'")
183
+
184
+ Z0 = None
185
+ Z1 = None
186
+ grid_ttms: List[np.ndarray] = []
187
+ if random_mode == "fixed":
188
+ try:
189
+ Z0, Z1, grid_ttms = sv.get_randoms_for_rough_vol_chain_valuation(
190
+ ttms=option_chain.ttms,
191
+ nb_path=nb_path,
192
+ nb_steps_per_year=nb_steps_per_year,
193
+ seed=seed,
194
+ dtype=dtype,
195
+ chunk_size=2048,
196
+ use_legacy_rng=False,
197
+ )
198
+ except TypeError:
199
+ # Backward-compatibility: older signatures may not support dtype/chunk_size/use_legacy_rng.
200
+ Z0, Z1, grid_ttms = sv.get_randoms_for_rough_vol_chain_valuation(
201
+ ttms=option_chain.ttms,
202
+ nb_path=nb_path,
203
+ nb_steps_per_year=nb_steps_per_year,
204
+ seed=seed,
205
+ )
206
+ if Z0.dtype != dtype:
207
+ Z0 = Z0.astype(dtype, copy=False)
208
+ if Z1.dtype != dtype:
209
+ Z1 = Z1.astype(dtype, copy=False)
210
+ if float32_mode:
211
+ grid_ttms = [np.asarray(g, dtype=np.float32) for g in grid_ttms]
212
+ else:
213
+ for ttm in option_chain.ttms:
214
+ _, _, grid_t = set_time_grid(ttm=ttm, nb_steps_per_year=nb_steps_per_year)
215
+ if float32_mode:
216
+ grid_t = np.asarray(grid_t, dtype=np.float32)
217
+ grid_ttms.append(grid_t)
218
+
219
+ base_params = LogSvParams(sigma0=0.8, theta=1.0, kappa1=2.21, kappa2=2.18, beta=0.15, volvol=2.0)
220
+ base_params.H = 0.1
221
+ base_params.approximate_kernel(T=option_chain.ttms[-1])
222
+ if float32_mode:
223
+ base_params.weights = base_params.weights.astype(np.float32, copy=False)
224
+ base_params.nodes = base_params.nodes.astype(np.float32, copy=False)
225
+ param_dtype = np.float32 if float32_mode else None
226
+ variants = build_param_variants(base=base_params, count=variants, param_dtype=param_dtype)
227
+
228
+ # Warm up numba kernels before timing.
229
+ for _ in range(max(0, warmup)):
230
+ _rough_price_once(option_chain, Z0, Z1, grid_ttms, variants[0][1], nb_path, seed, random_mode, float32_mode)
231
+
232
+ tracemalloc.start()
233
+ results: List[PerfResult] = []
234
+
235
+ for name, params in variants:
236
+ gc.collect()
237
+ tracemalloc.reset_peak()
238
+ mem_before, _ = tracemalloc.get_traced_memory()
239
+ rss_before = _get_rss_kib()
240
+ sampler = None
241
+ if rss_sample_interval > 0.0 and psutil is not None:
242
+ sampler = _RssSampler(interval_sec=rss_sample_interval)
243
+ sampler.start()
244
+ cpu_start = time.process_time()
245
+
246
+ option_prices_ttm, option_std_ttm = _rough_price_once(option_chain, Z0, Z1, grid_ttms, params, nb_path, seed, random_mode, float32_mode)
247
+
248
+ cpu_end = time.process_time()
249
+ if sampler is not None:
250
+ sampler.stop()
251
+ mem_after, mem_peak = tracemalloc.get_traced_memory()
252
+ rss_after = _get_rss_kib()
253
+ rss_peak_kib = None
254
+ if sampler is not None and sampler.max_rss_kib is not None:
255
+ rss_peak_kib = sampler.max_rss_kib
256
+ elif rss_before is not None and rss_after is not None:
257
+ rss_peak_kib = max(rss_before, rss_after)
258
+
259
+ # Basic checksum to keep outputs used.
260
+ checksum = 0.0
261
+ for arr in option_prices_ttm:
262
+ checksum += float(np.sum(np.asarray(arr)))
263
+
264
+ results.append(
265
+ PerfResult(
266
+ name=name,
267
+ cpu_seconds=cpu_end - cpu_start,
268
+ mem_current_kib=(mem_after - mem_before) / 1024.0,
269
+ mem_peak_kib=(mem_peak - mem_before) / 1024.0,
270
+ rss_before_kib=rss_before,
271
+ rss_after_kib=rss_after,
272
+ rss_delta_kib=None if rss_before is None or rss_after is None else rss_after - rss_before,
273
+ rss_peak_delta_kib=None if rss_before is None or rss_peak_kib is None else rss_peak_kib - rss_before,
274
+ rss_peak_kib=rss_peak_kib,
275
+ checksum=checksum,
276
+ )
277
+ )
278
+
279
+ del option_prices_ttm, option_std_ttm
280
+
281
+ tracemalloc.stop()
282
+
283
+ df = pd.DataFrame([r.__dict__ for r in results])
284
+ cols = [
285
+ "name",
286
+ "cpu_seconds",
287
+ "mem_current_kib",
288
+ "mem_peak_kib",
289
+ "rss_before_kib",
290
+ "rss_after_kib",
291
+ "rss_peak_kib",
292
+ "rss_delta_kib",
293
+ "rss_peak_delta_kib",
294
+ "checksum",
295
+ ]
296
+ return df[cols]
297
+
298
+
299
+ if __name__ == "__main__":
300
+ df_results = run_rough_logsv_loop_bench(rss_sample_interval=0.01)
301
+ pd.set_option("display.max_columns", None)
302
+ print(df_results.to_string(index=False))
@@ -0,0 +1,50 @@
1
+ option_prices_ttm:
2
+ - - 50.19425805335426
3
+ - 85.78123633760005
4
+ - 116.91358931762213
5
+ - 162.4580622457345
6
+ - 372.81329329569274
7
+ - 802.611749231318
8
+ - 834.7478904032646
9
+ - 667.207094669157
10
+ - 552.2749423049975
11
+ - 507.62643625299205
12
+ - 215.6090504645614
13
+ - 174.53589766819024
14
+ - - 48.450698138440806
15
+ - 69.10380918456832
16
+ - 159.1452950831655
17
+ - 237.7658653770458
18
+ - 652.8141266564039
19
+ - 832.9187768846742
20
+ - 1466.3894567786515
21
+ - 894.7443513553394
22
+ - 636.5643533548471
23
+ - 487.1065297015045
24
+ - 386.2625424682371
25
+ - 254.2266569174367
26
+ - 133.31669638376397
27
+ - - 27.33253410210627
28
+ - 45.98892670127133
29
+ - 79.5589437813465
30
+ - 171.06432274681012
31
+ - 283.42795051502344
32
+ - 493.07730134022853
33
+ - 969.2189310032159
34
+ - 2424.1298433093352
35
+ - 1598.9572854456496
36
+ - 1198.0540185632092
37
+ - 950.6801089319849
38
+ - 782.6615652726292
39
+ - 564.2747342734503
40
+ - 232.11136113922865
41
+ - 175.48117302952474
42
+ - - 25.50036622791895
43
+ - 48.60144007274547
44
+ - 735.5501966187034
45
+ - 2115.478840582238
46
+ - 964.2203951674677
47
+ - 540.4372248348861
48
+ - 255.43422271814464
49
+ - 46.56074959115207
50
+ - 31.62313590270595
@@ -0,0 +1,50 @@
1
+ import numpy as np
2
+
3
+ import stochvolmodels as sv
4
+ from stochvolmodels import LogSVPricer, LogSvParams, LogsvModelCalibrationType
5
+
6
+
7
+ def _array_list(values):
8
+ return [np.asarray(item).tolist() for item in values]
9
+
10
+
11
+ def test_rough_logsv_pricer_pricing_regression(data_regression) -> None:
12
+ btc_option_chain = sv.get_btc_test_chain_data()
13
+ Z0, Z1, grid_ttms = sv.get_randoms_for_rough_vol_chain_valuation(
14
+ ttms=btc_option_chain.ttms,
15
+ nb_path=10000,
16
+ nb_steps_per_year=360,
17
+ seed=10,
18
+ )
19
+ params0 = LogSvParams(
20
+ sigma0=0.377,
21
+ theta=0.347,
22
+ kappa1=1.29,
23
+ kappa2=1.93,
24
+ beta=2.45,
25
+ volvol=1.81,
26
+ )
27
+ params0.H = 0.1
28
+ params0.approximate_kernel(T=btc_option_chain.ttms[-1])
29
+
30
+ option_prices_ttm, _option_std_ttm = sv.rough_logsv_mc_chain_pricer_fixed_randoms(
31
+ ttms=btc_option_chain.ttms,
32
+ forwards=btc_option_chain.forwards,
33
+ discfactors=btc_option_chain.discfactors,
34
+ strikes_ttms=btc_option_chain.strikes_ttms,
35
+ optiontypes_ttms=btc_option_chain.optiontypes_ttms,
36
+ Z0=Z0,
37
+ Z1=Z1,
38
+ sigma0=params0.sigma0,
39
+ theta=params0.theta,
40
+ kappa1=params0.kappa1,
41
+ kappa2=params0.kappa2,
42
+ beta=params0.beta,
43
+ orthog_vol=params0.volvol,
44
+ weights=params0.weights,
45
+ nodes=params0.nodes,
46
+ timegrids=grid_ttms,
47
+ )
48
+
49
+ data_regression.check({"option_prices_ttm": _array_list(option_prices_ttm)})
50
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stochvolmodels
3
- Version: 1.1.6
3
+ Version: 1.1.7
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>
@@ -727,9 +727,10 @@ Requires-Dist: jupyterlab>=3.0.0; extra == "jupyter"
727
727
  Requires-Dist: ipykernel>=6.0.0; extra == "jupyter"
728
728
  Requires-Dist: ipywidgets>=8.0.0; extra == "jupyter"
729
729
  Provides-Extra: dev
730
- Requires-Dist: pytest>=7.0.0; extra == "dev"
730
+ Requires-Dist: pytest>=8.4.2; extra == "dev"
731
731
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
732
732
  Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
733
+ Requires-Dist: pytest-regressions>=2.8.3; extra == "dev"
733
734
  Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
734
735
  Requires-Dist: black>=22.0.0; extra == "dev"
735
736
  Requires-Dist: flake8>=5.0.0; extra == "dev"
@@ -83,6 +83,9 @@ stochvolmodels/pricers/rough_logsv/test_kernel_approx.py
83
83
  stochvolmodels/tests/__init__.py
84
84
  stochvolmodels/tests/bsm_mgf_pricer.py
85
85
  stochvolmodels/tests/qv_pricer.py
86
+ stochvolmodels/tests/rough_logsv_perf.py
87
+ stochvolmodels/tests/test_rough_logsv_pricer_regression.py
88
+ stochvolmodels/tests/test_rough_logsv_pricer_regression/test_rough_logsv_pricer_pricing_regression.yml
86
89
  stochvolmodels/utils/__init__.py
87
90
  stochvolmodels/utils/config.py
88
91
  stochvolmodels/utils/funcs.py
@@ -14,9 +14,10 @@ fsspec>=2024.12.0
14
14
  stochvolmodels[jupyter,numerical,performance,research,visualization]
15
15
 
16
16
  [dev]
17
- pytest>=7.0.0
17
+ pytest>=8.4.2
18
18
  pytest-cov>=4.0.0
19
19
  pytest-mock>=3.10.0
20
+ pytest-regressions>=2.8.3
20
21
  pytest-xdist>=3.0.0
21
22
  black>=22.0.0
22
23
  flake8>=5.0.0
File without changes
File without changes