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.
- {stochvolmodels-1.1.6/stochvolmodels.egg-info → stochvolmodels-1.1.7}/PKG-INFO +3 -2
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/pyproject.toml +4 -3
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/requirements.txt +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv_pricer.py +10 -8
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/split_simulation.py +188 -22
- stochvolmodels-1.1.7/stochvolmodels/tests/rough_logsv_perf.py +302 -0
- stochvolmodels-1.1.7/stochvolmodels/tests/test_rough_logsv_pricer_regression/test_rough_logsv_pricer_pricing_regression.yml +50 -0
- stochvolmodels-1.1.7/stochvolmodels/tests/test_rough_logsv_pricer_regression.py +50 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7/stochvolmodels.egg-info}/PKG-INFO +3 -2
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels.egg-info/SOURCES.txt +3 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels.egg-info/requires.txt +2 -1
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/.gitignore +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/LICENSE.txt +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/MANIFEST.in +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/README.md +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/forward_var/calibrate_forward_var.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/il_hedging/README.md +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/il_hedging/logsv_figures.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/il_hedging/run_logsv_for_il_payoff.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/inverse_options/README.md +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/inverse_options/compare_net_delta.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/README.md +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/article_figures.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/calibrations.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/compare_admis_reg.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/model_fit_to_options_timeseries.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/moments_vol_qvar.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/ode_sol_in_time.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/steady_state_pdf.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/vol_drift.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/check_kernel.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/gmm_slides.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/plot_gmm.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/q_kernel.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/risk_premia_gmm/run_gmm_fit.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/README.md +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/calibration_fig_5_6_7.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/calibration_fig_8_9.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/t_distribution/illustrations.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/t_distribution/market_data_fit.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/t_distribution/mc_pricer_with_kernel.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/README.md +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/article_figures.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/autocorr_fit.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/load_data.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/ss_distribution_fit.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/vol_beta.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/setup.cfg +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/__init__.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/__init__.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/fetch_option_chain.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/option_chain.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/data/test_option_chain.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/quick_run_lognormal_sv_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_hawkes_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_heston.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_heston_sv_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_lognormal_sv_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_pricing_options_on_qvar.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/__init__.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/__init__.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/bachelier.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/bsm.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/analytic/tdist.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_affine_expansion.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_core.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/gmm_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/hawkes_jd_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/heston_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/__init__.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/affine_expansion.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/logsv_params.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/vol_moments_ode.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/model_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/RoughKernel.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/expm.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/test_kernel_approx.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/tdist_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/tests/__init__.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/tests/bsm_mgf_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/tests/qv_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/__init__.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/config.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/funcs.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/mc_payoffs.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/mgf_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/plots.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/utils/var_swap_pricer.py +0 -0
- {stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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>=
|
|
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.
|
|
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>=
|
|
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
|
+
]
|
|
Binary file
|
|
@@ -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
|
-
|
|
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
|
-
|
|
910
|
-
|
|
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
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/split_simulation.py
RENAMED
|
@@ -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
|
|
197
|
-
|
|
198
|
-
assert y0.shape == weight.shape
|
|
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
|
|
217
|
-
|
|
218
|
-
|
|
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 =
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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 =
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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 =
|
|
313
|
-
|
|
314
|
-
|
|
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.
|
|
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>=
|
|
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>=
|
|
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
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/forward_var/calibrate_forward_var.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/il_hedging/run_logsv_for_il_payoff.py
RENAMED
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/inverse_options/compare_net_delta.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/logsv_model_wtih_quadratic_drift/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/calibration_fig_5_6_7.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/sv_for_factor_hjm/calibration_fig_8_9.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/t_distribution/mc_pricer_with_kernel.py
RENAMED
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/article_figures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/my_papers/volatility_models/ss_distribution_fit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_heston_sv_pricer.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_lognormal_sv_pricer.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/examples/run_pricing_options_on_qvar.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/double_exp_pricer.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/factor_hjm_pricer.py
RENAMED
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_core.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_evaluate.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_factor_basis.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_ivols.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_params.py
RENAMED
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/factor_hjm/rate_logsv_pricer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/affine_expansion.py
RENAMED
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/logsv/vol_moments_ode.py
RENAMED
|
File without changes
|
|
File without changes
|
{stochvolmodels-1.1.6 → stochvolmodels-1.1.7}/stochvolmodels/pricers/rough_logsv/RoughKernel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|