sdevpy 1.0.9__tar.gz → 1.1__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.
- sdevpy-1.1/LICENSE +21 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/PKG-INFO +25 -2
- {sdevpy-1.0.9 → sdevpy-1.1}/pyproject.toml +17 -8
- sdevpy-1.1/sdevpy/__init__.py +26 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/analytics/bachelier.py +1 -4
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/analytics/black.py +15 -3
- sdevpy-1.1/sdevpy/instruments/constants.py +20 -0
- sdevpy-1.1/sdevpy/logger.py +42 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/keras/learningschedules.py +1 -1
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/market/correlations.py +4 -2
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/market/eqforward.py +14 -21
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/market/eqvolsurface.py +52 -36
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/market/yieldcurve.py +14 -16
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/constants.py +3 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/integration.py +0 -1
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/interpolation.py +14 -7
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/optimization.py +26 -6
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/rand/pathconstruction.py +2 -1
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/specialfunctions.py +3 -3
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/models/assetmodels.py +10 -14
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/models/multiasset_heston.py +1 -1
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/mcpricer.py +59 -11
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/payoffs/basic.py +1 -1
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/payoffs/vanillas.py +10 -29
- sdevpy-1.1/sdevpy/montecarlo/smoothers.py +23 -0
- sdevpy-1.1/sdevpy/pde/forwardpde.py +303 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/pde/pdeschemes.py +61 -57
- sdevpy-1.1/sdevpy/tensorflow/tf_analytics.py +19 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tensorflow/tf_metrics.py +6 -4
- sdevpy-1.1/sdevpy/tests/test_algos.py +112 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_analytics.py +10 -9
- sdevpy-1.1/sdevpy/tests/test_backtesting.py +58 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_dates.py +10 -11
- sdevpy-1.1/sdevpy/tests/test_files.py +93 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_impliedvol.py +70 -29
- sdevpy-1.1/sdevpy/tests/test_impliedvol_ml.py +135 -0
- sdevpy-1.1/sdevpy/tests/test_integration.py +24 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_interpolation.py +4 -4
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_localvol.py +92 -97
- sdevpy-1.1/sdevpy/tests/test_localvol_calib.py +200 -0
- sdevpy-1.1/sdevpy/tests/test_marketdata.py +90 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_mc.py +11 -6
- sdevpy-1.1/sdevpy/tests/test_meanreversion.py +161 -0
- sdevpy-1.1/sdevpy/tests/test_models.py +49 -0
- sdevpy-1.1/sdevpy/tests/test_pde.py +186 -0
- sdevpy-1.1/sdevpy/tests/test_restapi.py +77 -0
- sdevpy-1.1/sdevpy/tests/test_specialfunctions.py +43 -0
- sdevpy-1.1/sdevpy/tests/test_timegrids.py +98 -0
- sdevpy-1.1/sdevpy/tests/test_tree.py +77 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_yieldcurves.py +36 -3
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/timeseries/backtesting.py +1 -1
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tree/trees.py +9 -9
- sdevpy-1.1/sdevpy/utilities/algos.py +81 -0
- sdevpy-1.1/sdevpy/utilities/constants.py +2 -0
- sdevpy-1.1/sdevpy/utilities/dates.py +54 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/filemanager.py +0 -12
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/jsonmanager.py +6 -7
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/pydotnet.py +1 -0
- sdevpy-1.1/sdevpy/utilities/restapi.py +42 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/scalendar.py +9 -15
- sdevpy-1.1/sdevpy/utilities/timegrids.py +217 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/tools.py +2 -2
- sdevpy-1.1/sdevpy/utilities/xmlmanager.py +29 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/impliedvol.py +33 -44
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/impliedvol_calib.py +14 -28
- sdevpy-1.1/sdevpy/volatility/impliedvol/impliedvol_factory.py +66 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/biexp.py +14 -4
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/cubicvol.py +9 -6
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/fbsabr.py +2 -2
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/logmix.py +219 -74
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/mcheston.py +2 -2
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/mcsabr.py +3 -3
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/mczabr.py +2 -2
- sdevpy-1.1/sdevpy/volatility/impliedvol/models/svi.py +122 -0
- sdevpy-1.1/sdevpy/volatility/impliedvol/models/tssvi1.py +276 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/tssvi2.py +76 -37
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/vsvi.py +43 -31
- sdevpy-1.1/sdevpy/volatility/impliedvol/numerical_impliedvol.py +134 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/optionsurface.py +16 -6
- sdevpy-1.1/sdevpy/volatility/impliedvol/parametric_impliedvol.py +29 -0
- sdevpy-1.1/sdevpy/volatility/localvol/black_calib.py +66 -0
- sdevpy-1.0.9/sdevpy/volatility/localvol/dupire.py → sdevpy-1.1/sdevpy/volatility/localvol/dupire_calib.py +125 -35
- sdevpy-1.1/sdevpy/volatility/localvol/localvol.py +336 -0
- sdevpy-1.1/sdevpy/volatility/localvol/localvol_factory.py +244 -0
- sdevpy-1.1/sdevpy/volatility/localvol/lvsection_calib.py +352 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/mlsurfacegen/sabrgenerator.py +2 -1
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy.egg-info/PKG-INFO +25 -2
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy.egg-info/SOURCES.txt +22 -32
- sdevpy-1.0.9/sdevpy/__init__.py +0 -21
- sdevpy-1.0.9/sdevpy/montecarlo/mcrun.py +0 -102
- sdevpy-1.0.9/sdevpy/montecarlo/smoothers.py +0 -40
- sdevpy-1.0.9/sdevpy/pde/forwardpde.py +0 -267
- sdevpy-1.0.9/sdevpy/projects/aad/aad_mc.py +0 -282
- sdevpy-1.0.9/sdevpy/projects/aad/aad_mc_nd.py +0 -349
- sdevpy-1.0.9/sdevpy/projects/chat_gpt2.py +0 -76
- sdevpy-1.0.9/sdevpy/projects/datafiles.py +0 -28
- sdevpy-1.0.9/sdevpy/projects/raschka/ch2_working_with_text.py +0 -161
- sdevpy-1.0.9/sdevpy/projects/raschka/ch3_coding_attention.py +0 -259
- sdevpy-1.0.9/sdevpy/projects/raschka/ch4_gpt_model.py +0 -200
- sdevpy-1.0.9/sdevpy/projects/raschka/ch5_loadgpt2.py +0 -119
- sdevpy-1.0.9/sdevpy/projects/raschka/ch5_pretraining.py +0 -263
- sdevpy-1.0.9/sdevpy/projects/raschka/ch7_instruction_finetuning.py +0 -26
- sdevpy-1.0.9/sdevpy/projects/raschka/raschka_datasetloader.py +0 -80
- sdevpy-1.0.9/sdevpy/projects/raschka/raschka_dnn.py +0 -40
- sdevpy-1.0.9/sdevpy/projects/raschka/raschka_gpt_download.py +0 -155
- sdevpy-1.0.9/sdevpy/projects/set_limits.py +0 -24
- sdevpy-1.0.9/sdevpy/projects/stovol/stovolgen.py +0 -87
- sdevpy-1.0.9/sdevpy/projects/stovol/stovolplot.py +0 -81
- sdevpy-1.0.9/sdevpy/projects/stovol/stovoltrain.py +0 -269
- sdevpy-1.0.9/sdevpy/projects/stovolinverse/stovolinvgen.py +0 -82
- sdevpy-1.0.9/sdevpy/projects/stovolinverse/stovolinvtrain.py +0 -374
- sdevpy-1.0.9/sdevpy/projects/update_db.py +0 -26
- sdevpy-1.0.9/sdevpy/tests/test_algos.py +0 -125
- sdevpy-1.0.9/sdevpy/tests/test_marketdata.py +0 -48
- sdevpy-1.0.9/sdevpy/tests/test_pde.py +0 -116
- sdevpy-1.0.9/sdevpy/tests/test_timegrids.py +0 -49
- sdevpy-1.0.9/sdevpy/utilities/algos.py +0 -115
- sdevpy-1.0.9/sdevpy/utilities/constants.py +0 -6
- sdevpy-1.0.9/sdevpy/utilities/dates.py +0 -31
- sdevpy-1.0.9/sdevpy/utilities/network.py +0 -95
- sdevpy-1.0.9/sdevpy/utilities/speriods.py +0 -25
- sdevpy-1.0.9/sdevpy/utilities/timegrids.py +0 -142
- sdevpy-1.0.9/sdevpy/volatility/__init__.py +0 -0
- sdevpy-1.0.9/sdevpy/volatility/impliedvol/__init__.py +0 -0
- sdevpy-1.0.9/sdevpy/volatility/impliedvol/models/__init__.py +0 -0
- sdevpy-1.0.9/sdevpy/volatility/impliedvol/models/gsvi.py +0 -67
- sdevpy-1.0.9/sdevpy/volatility/impliedvol/models/svi.py +0 -96
- sdevpy-1.0.9/sdevpy/volatility/impliedvol/models/tssvi1.py +0 -198
- sdevpy-1.0.9/sdevpy/volatility/localvol/__init__.py +0 -0
- sdevpy-1.0.9/sdevpy/volatility/localvol/localvol.py +0 -192
- sdevpy-1.0.9/sdevpy/volatility/localvol/localvol_calib.py +0 -307
- sdevpy-1.0.9/sdevpy/volatility/localvol/localvol_factory.py +0 -201
- sdevpy-1.0.9/sdevpy/volatility/mlsurfacegen/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/README.md +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/analytics/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/analytics/americantree.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/datasets.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/keras/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/keras/callbacks.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/keras/learningmodel.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/keras/topology.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/attention.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/chat.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/datasets.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/gpt.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/instructions.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/modelconverter.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/textgen.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/tokenizers.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/machinelearning/llms/training.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/market/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/market/fixings.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/market/spot.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/metrics.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/rand/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/rand/correlations.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/rand/rng.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/regression.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/sets.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/maths/tridiag.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/models/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/pathgenerator.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/payoffs/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/payoffs/cashflows.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/montecarlo/payoffs/exotics.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/pde/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/settings.py +0 -0
- {sdevpy-1.0.9/sdevpy/projects → sdevpy-1.1/sdevpy/tensorflow}/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tensorflow/tf_black.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_cointegration.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/tests/test_utils.py +0 -0
- {sdevpy-1.0.9/sdevpy/projects/aad → sdevpy-1.1/sdevpy/thirdparty}/__init__.py +0 -0
- {sdevpy-1.0.9/sdevpy/projects/raschka → sdevpy-1.1/sdevpy/timeseries}/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/timeseries/cointegration.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/timeseries/meanreversion.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/timeseries/timeseriestools.py +0 -0
- {sdevpy-1.0.9/sdevpy/projects/stovol → sdevpy-1.1/sdevpy/tree}/__init__.py +0 -0
- {sdevpy-1.0.9/sdevpy/projects/stovolinverse → sdevpy-1.1/sdevpy/utilities}/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/book.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/clipboard.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/utilities/timer.py +0 -0
- {sdevpy-1.0.9/sdevpy/tensorflow → sdevpy-1.1/sdevpy/volatility}/__init__.py +0 -0
- {sdevpy-1.0.9/sdevpy/thirdparty → sdevpy-1.1/sdevpy/volatility/impliedvol}/__init__.py +0 -0
- {sdevpy-1.0.9/sdevpy/timeseries → sdevpy-1.1/sdevpy/volatility/impliedvol/models}/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/impliedvol/models/sabr.py +0 -0
- {sdevpy-1.0.9/sdevpy/tree → sdevpy-1.1/sdevpy/volatility/localvol}/__init__.py +0 -0
- {sdevpy-1.0.9/sdevpy/utilities → sdevpy-1.1/sdevpy/volatility/mlsurfacegen}/__init__.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/mlsurfacegen/fbsabrgenerator.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/mlsurfacegen/mchestongenerator.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/mlsurfacegen/mcsabrgenerator.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/mlsurfacegen/mczabrgenerator.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/mlsurfacegen/smilegenerator.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy/volatility/mlsurfacegen/stovolfactory.py +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy.egg-info/dependency_links.txt +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy.egg-info/requires.txt +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/sdevpy.egg-info/top_level.txt +0 -0
- {sdevpy-1.0.9 → sdevpy-1.1}/setup.cfg +0 -0
sdevpy-1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2023] [Sebastien Gurrieri]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdevpy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1
|
|
4
4
|
Summary: Python package for Finance
|
|
5
5
|
Author-email: Sebastien Gurrieri <sebgur@gmail.com>
|
|
6
|
-
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) [2023] [Sebastien Gurrieri]
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Project-URL: Git, https://github.com/sebgur/SDev.Python
|
|
7
28
|
Project-URL: SDev Finance, http://sdev-finance.com/
|
|
8
29
|
Classifier: Programming Language :: Python :: 3
|
|
9
30
|
Classifier: Operating System :: OS Independent
|
|
10
31
|
Requires-Python: >=3.6
|
|
11
32
|
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
12
34
|
Requires-Dist: pandas
|
|
13
35
|
Requires-Dist: numpy
|
|
14
36
|
Requires-Dist: scipy
|
|
@@ -18,6 +40,7 @@ Requires-Dist: pandas_market_calendars
|
|
|
18
40
|
Requires-Dist: openpyxl
|
|
19
41
|
Requires-Dist: colorlog
|
|
20
42
|
Requires-Dist: scikit-learn
|
|
43
|
+
Dynamic: license-file
|
|
21
44
|
|
|
22
45
|
# SDev.Python
|
|
23
46
|
|
|
@@ -4,8 +4,9 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sdevpy"
|
|
7
|
-
version = "1.
|
|
8
|
-
license
|
|
7
|
+
version = "1.1"
|
|
8
|
+
license = {file = "LICENSE"}
|
|
9
|
+
#license-files = []
|
|
9
10
|
authors = [{ name="Sebastien Gurrieri", email="sebgur@gmail.com" }]
|
|
10
11
|
description = "Python package for Finance"
|
|
11
12
|
readme = "README.md"
|
|
@@ -21,6 +22,10 @@ dependencies = ["pandas", "numpy", "scipy", "matplotlib", "holidays",
|
|
|
21
22
|
#dependencies = ["pyperclip", "tensorflow", "scikit-learn",
|
|
22
23
|
# "tensorflow_probability", "silence_tensorflow"]
|
|
23
24
|
|
|
25
|
+
[project.urls]
|
|
26
|
+
"Git" = "https://github.com/sebgur/SDev.Python"
|
|
27
|
+
"SDev Finance" = "http://sdev-finance.com/"
|
|
28
|
+
|
|
24
29
|
[tool.setuptools.packages.find]
|
|
25
30
|
where = ["."]
|
|
26
31
|
include = ["sdevpy*"] # Only include these
|
|
@@ -41,14 +46,18 @@ ignore = ["E401", "I001"]
|
|
|
41
46
|
[tool.coverage.run]
|
|
42
47
|
omit = [
|
|
43
48
|
"sdevpy/**/__init__.py",
|
|
44
|
-
"sdevpy/
|
|
49
|
+
"sdevpy/tests/test.py",
|
|
50
|
+
"sdevpy/logger.py",
|
|
51
|
+
"sdevpy/settings.py",
|
|
52
|
+
"sdevpy/utilities/constants.py",
|
|
53
|
+
"sdevpy/utilities/pydotnet.py",
|
|
54
|
+
"sdevpy/tensorflow/*",
|
|
55
|
+
"sdevpy/machinelearning/*",
|
|
56
|
+
"sdevpy/volatility/mlsurfacegen/*"
|
|
45
57
|
]
|
|
46
58
|
|
|
47
59
|
[tool.coverage.report]
|
|
48
60
|
exclude_lines = [
|
|
49
|
-
"if __name__ ==
|
|
61
|
+
"if __name__ == .__main__.:",
|
|
62
|
+
"pragma: no cov"
|
|
50
63
|
]
|
|
51
|
-
|
|
52
|
-
[project.urls]
|
|
53
|
-
"Git page" = "https://github.com/sebgur/SDev.Python"
|
|
54
|
-
"SDev Finance" = "http://sdev-finance.com/"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
__version__ = '1.0.5'
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# import colorlog
|
|
10
|
+
|
|
11
|
+
# handler = colorlog.StreamHandler()
|
|
12
|
+
# handler.setFormatter(colorlog.ColoredFormatter(
|
|
13
|
+
# "%(log_color)s%(levelname)-8s%(reset)s %(name)s - %(message)s",
|
|
14
|
+
# log_colors={
|
|
15
|
+
# "DEBUG": "cyan",
|
|
16
|
+
# "INFO": "green",
|
|
17
|
+
# "WARNING": "yellow",
|
|
18
|
+
# "ERROR": "red",
|
|
19
|
+
# "CRITICAL": "bold_red",
|
|
20
|
+
# }
|
|
21
|
+
# ))
|
|
22
|
+
|
|
23
|
+
# root = logging.getLogger()
|
|
24
|
+
# root.addHandler(handler)
|
|
25
|
+
# root.setLevel(logging.WARNING)
|
|
26
|
+
# logging.getLogger("sdevpy").setLevel(logging.DEBUG)
|
|
@@ -8,10 +8,9 @@ from scipy.optimize import minimize_scalar
|
|
|
8
8
|
def price(expiry: npt.ArrayLike, strike: npt.ArrayLike, is_call: npt.ArrayLike, fwd: npt.ArrayLike,
|
|
9
9
|
vol: npt.ArrayLike) -> npt.NDArray[np.float64]:
|
|
10
10
|
""" Option price under the Bachelier model """
|
|
11
|
-
stdev = vol * expiry
|
|
11
|
+
stdev = vol * np.sqrt(expiry)
|
|
12
12
|
d = (fwd - strike) / stdev
|
|
13
13
|
wd = np.where(is_call, d, -d)
|
|
14
|
-
# wd = d if is_call else -d
|
|
15
14
|
return stdev * (wd * norm.cdf(wd) + norm.pdf(d))
|
|
16
15
|
|
|
17
16
|
|
|
@@ -70,8 +69,6 @@ def implied_vol(expiry: npt.ArrayLike, strike: npt.ArrayLike, is_call: npt.Array
|
|
|
70
69
|
strike = np.asarray(strike, dtype=float)
|
|
71
70
|
fwd_price = np.asarray(fwd_price, dtype=float)
|
|
72
71
|
is_call = np.asarray(is_call, dtype=bool)
|
|
73
|
-
# expiry = float(expiry)
|
|
74
|
-
# fwd = float(fwd)
|
|
75
72
|
|
|
76
73
|
#### ATM branch ####
|
|
77
74
|
m = fwd - strike
|
|
@@ -9,7 +9,6 @@ from sdevpy.utilities.tools import isiterable
|
|
|
9
9
|
def price(expiry: npt.ArrayLike, strike: npt.ArrayLike, is_call: npt.ArrayLike, fwd: npt.ArrayLike,
|
|
10
10
|
vol: npt.ArrayLike) -> npt.NDArray[np.float64]:
|
|
11
11
|
""" Option price under the Black-Scholes model """
|
|
12
|
-
# w = 1.0 if is_call else -1.0
|
|
13
12
|
w = np.where(is_call, 1.0, -1.0)
|
|
14
13
|
s = vol * np.sqrt(expiry)
|
|
15
14
|
d1 = np.log(fwd / strike) / s + 0.5 * s
|
|
@@ -17,12 +16,25 @@ def price(expiry: npt.ArrayLike, strike: npt.ArrayLike, is_call: npt.ArrayLike,
|
|
|
17
16
|
return w * (fwd * norm.cdf(w * d1) - strike * norm.cdf(w * d2))
|
|
18
17
|
|
|
19
18
|
|
|
19
|
+
def price_straddles(expiry: npt.ArrayLike, strike: npt.ArrayLike, fwd: npt.ArrayLike,
|
|
20
|
+
vol: npt.ArrayLike) -> npt.NDArray[np.float64]:
|
|
21
|
+
""" Straddle price under the Black model """
|
|
22
|
+
call = price(expiry, strike, True, fwd, vol)
|
|
23
|
+
put = price(expiry, strike, False, fwd, vol)
|
|
24
|
+
return call + put
|
|
25
|
+
|
|
26
|
+
|
|
20
27
|
def implied_vol(expiry: float, strike: float, is_call: bool, fwd: float, fwd_price: float) -> float:
|
|
21
28
|
""" Direct method by numerical inversion using Brent.
|
|
22
29
|
Non-vectorized due to solver. """
|
|
23
|
-
|
|
30
|
+
# Trial config
|
|
31
|
+
options = {'xtol': 1e-6, 'maxiter': 100, 'disp': False}
|
|
24
32
|
xmin = 1e-6
|
|
25
|
-
xmax =
|
|
33
|
+
xmax = 2.0
|
|
34
|
+
# # Original config
|
|
35
|
+
# options = {'xtol': 1e-4, 'maxiter': 100, 'disp': False}
|
|
36
|
+
# xmin = 1e-6
|
|
37
|
+
# xmax = 1.0
|
|
26
38
|
|
|
27
39
|
def error(vol):
|
|
28
40
|
premium = price(expiry, strike, is_call, fwd, vol)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class OptionType(Enum):
|
|
5
|
+
CALL = 0
|
|
6
|
+
PUT = 1
|
|
7
|
+
STRADDLE = 2
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def string_to_optiontype(s: str) -> OptionType:
|
|
11
|
+
""" Convert string to OptionType """
|
|
12
|
+
match s.lower():
|
|
13
|
+
case 'call':
|
|
14
|
+
return OptionType.CALL
|
|
15
|
+
case 'put':
|
|
16
|
+
return OptionType.PUT
|
|
17
|
+
case 'straddle':
|
|
18
|
+
return OptionType.STRADDLE
|
|
19
|
+
case _:
|
|
20
|
+
raise ValueError(f"Invalid option type: {s}")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import logging, colorlog
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
LOG_COLORS = {"DEBUG": "cyan", "INFO": "green", "WARNING": "yellow", "ERROR": "red",
|
|
5
|
+
"CRITICAL": "bold_red"}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def string_to_logging_level(level_str: str):
|
|
9
|
+
level_str = level_str.lower()
|
|
10
|
+
match level_str:
|
|
11
|
+
case 'debug':
|
|
12
|
+
return logging.DEBUG
|
|
13
|
+
case 'info':
|
|
14
|
+
return logging.INFO
|
|
15
|
+
case 'warning':
|
|
16
|
+
return logging.WARNING
|
|
17
|
+
case 'error':
|
|
18
|
+
return logging.ERROR
|
|
19
|
+
case 'critical':
|
|
20
|
+
return logging.CRITICAL
|
|
21
|
+
case _:
|
|
22
|
+
raise ValueError(f"Unsupported logging level: {level_str}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def configure(root_level: str='warning', sdevpy_level: str='debug',
|
|
26
|
+
module_display: str='none') -> None:
|
|
27
|
+
""" Configure logger. Levels can be: debug, info, warning, error and critical. """
|
|
28
|
+
handler = colorlog.StreamHandler()
|
|
29
|
+
|
|
30
|
+
match module_display.lower():
|
|
31
|
+
case 'none':
|
|
32
|
+
module_str = "%(log_color)s%(levelname)-1s%(reset)s | %(message)s"
|
|
33
|
+
case 'partial':
|
|
34
|
+
module_str = "%(log_color)s%(levelname)-1s%(reset)s | %(module)s | %(message)s"
|
|
35
|
+
case 'full':
|
|
36
|
+
module_str = "%(log_color)s%(levelname)-1s%(reset)s | %(name)s | %(message)s"
|
|
37
|
+
case _:
|
|
38
|
+
raise ValueError(f"Unsupported module display mode: {module_display}")
|
|
39
|
+
|
|
40
|
+
handler.setFormatter(colorlog.ColoredFormatter(module_str, log_colors=LOG_COLORS))
|
|
41
|
+
logging.basicConfig(level=string_to_logging_level(root_level), handlers=[handler], force=True)
|
|
42
|
+
logging.getLogger("sdevpy").setLevel(string_to_logging_level(sdevpy_level))
|
|
@@ -57,13 +57,15 @@ def add_correlations(date: dt.datetime, names1: list[str], names2: list[str], va
|
|
|
57
57
|
f.write(f"{name1}-{name2},{value}\n")
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def data_file(date: dt.datetime, **kwargs):
|
|
60
|
+
def data_file(date: dt.datetime, **kwargs) -> str:
|
|
61
|
+
""" Data file for correlations """
|
|
61
62
|
folder = kwargs.get('folder', test_data_folder())
|
|
62
63
|
file = Path(folder) / (date.strftime(dts.DATE_FILE_FORMAT) + ".csv")
|
|
63
64
|
return file
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
def test_data_folder():
|
|
67
|
+
def test_data_folder() -> str:
|
|
68
|
+
""" Test data folder for correlations """
|
|
67
69
|
folder = Path(__file__).parent.parent.parent / "datasets" / "marketdata" / "correlations"
|
|
68
70
|
folder.mkdir(parents=True, exist_ok=True)
|
|
69
71
|
return folder
|
|
@@ -2,7 +2,8 @@ import os, json
|
|
|
2
2
|
import datetime as dt
|
|
3
3
|
import numpy as np
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from sdevpy.utilities import dates
|
|
5
|
+
from sdevpy.utilities import dates as dts
|
|
6
|
+
from sdevpy.utilities import timegrids
|
|
6
7
|
from sdevpy.maths import interpolation as itp
|
|
7
8
|
from sdevpy.market import yieldcurve as ycrv
|
|
8
9
|
from sdevpy.market.spot import get_spots
|
|
@@ -16,17 +17,9 @@ def get_forward_curves(names, valdate, **kwargs):
|
|
|
16
17
|
file = data_file(name, valdate, **kwargs)
|
|
17
18
|
data = eqforwarddata_from_file(file)
|
|
18
19
|
curve = EqForwardCurve(valdate=valdate, interp_var='forward', interp_type='cubicspline')
|
|
19
|
-
|
|
20
|
-
curve.calibrate(data, spot)#, yieldcurve)
|
|
20
|
+
curve.calibrate(data, spot)
|
|
21
21
|
fwd_curves.append(curve)
|
|
22
22
|
|
|
23
|
-
# drifts = np.asarray([0.02, 0.05, 0.04])
|
|
24
|
-
# fwd_curves_old = []
|
|
25
|
-
# for s, mu in zip(spots, drifts):
|
|
26
|
-
# # Use the default variable trick to circumvent late binding in python loops
|
|
27
|
-
# # Otherwise, all the lambda functions will effectively be the same
|
|
28
|
-
# fwd_curves_old.append(lambda t, s=s, mu=mu: s * np.exp(mu * t))
|
|
29
|
-
|
|
30
23
|
return fwd_curves
|
|
31
24
|
|
|
32
25
|
|
|
@@ -51,12 +44,12 @@ class EqForwardData:
|
|
|
51
44
|
def dump_data(self):
|
|
52
45
|
pillars = []
|
|
53
46
|
for expiry, forward in zip(self.expiries, self.forwards, strict=True):
|
|
54
|
-
expiry_str = expiry.strftime(
|
|
47
|
+
expiry_str = expiry.strftime(dts.DATE_FORMAT)
|
|
55
48
|
pillar = {'expiry': expiry_str, 'forward': forward}
|
|
56
49
|
pillars.append(pillar)
|
|
57
50
|
|
|
58
|
-
data = {'name': self.name, 'valdate': self.valdate.strftime(
|
|
59
|
-
'snapdate': self.snapdate.strftime(
|
|
51
|
+
data = {'name': self.name, 'valdate': self.valdate.strftime(dts.DATE_FORMAT),
|
|
52
|
+
'snapdate': self.snapdate.strftime(dts.DATETIME_FORMAT), 'pillars': pillars}
|
|
60
53
|
return data
|
|
61
54
|
|
|
62
55
|
|
|
@@ -80,7 +73,7 @@ class EqForwardCurve:
|
|
|
80
73
|
# Check consistency
|
|
81
74
|
for d in self.dates:
|
|
82
75
|
if d <= self.valdate:
|
|
83
|
-
raise RuntimeError(f"Expiry before or at valuation date: {d.strftime(
|
|
76
|
+
raise RuntimeError(f"Expiry before or at valuation date: {d.strftime(dts.DATE_FORMAT)}")
|
|
84
77
|
|
|
85
78
|
# Calibrate
|
|
86
79
|
if self.interp_var == 'yield':
|
|
@@ -133,11 +126,11 @@ def eqforwarddata_from_file(file):
|
|
|
133
126
|
# Convert date strings into dates
|
|
134
127
|
for pillar in pillars:
|
|
135
128
|
date_str = pillar.get('expiry')
|
|
136
|
-
date = dt.datetime.strptime(date_str,
|
|
129
|
+
date = dt.datetime.strptime(date_str, dts.DATE_FORMAT)
|
|
137
130
|
pillar['expiry'] = date
|
|
138
131
|
|
|
139
|
-
data = EqForwardData(dt.datetime.strptime(valdate,
|
|
140
|
-
name=name, snapdate=dt.datetime.strptime(snapdate,
|
|
132
|
+
data = EqForwardData(dt.datetime.strptime(valdate, dts.DATE_FORMAT), pillars,
|
|
133
|
+
name=name, snapdate=dt.datetime.strptime(snapdate, dts.DATETIME_FORMAT))
|
|
141
134
|
return data
|
|
142
135
|
|
|
143
136
|
|
|
@@ -145,7 +138,7 @@ def data_file(name, date, **kwargs):
|
|
|
145
138
|
folder = kwargs.get('folder', test_data_folder())
|
|
146
139
|
name_folder = os.path.join(folder, name)
|
|
147
140
|
os.makedirs(name_folder, exist_ok=True)
|
|
148
|
-
file = os.path.join(name_folder, date.strftime(
|
|
141
|
+
file = os.path.join(name_folder, date.strftime(dts.DATE_FILE_FORMAT) + ".json")
|
|
149
142
|
return file
|
|
150
143
|
|
|
151
144
|
|
|
@@ -161,7 +154,7 @@ if __name__ == "__main__":
|
|
|
161
154
|
import matplotlib.pyplot as plt
|
|
162
155
|
|
|
163
156
|
name = "ABC"
|
|
164
|
-
valdate = dt.datetime(
|
|
157
|
+
valdate = dt.datetime(2025, 12, 15)
|
|
165
158
|
|
|
166
159
|
# Generate a sample to start from
|
|
167
160
|
spot = 100.0
|
|
@@ -174,7 +167,7 @@ if __name__ == "__main__":
|
|
|
174
167
|
pillars = [{'expiry': d, 'forward': f} for d, f in zip(expiries, zfwds, strict=True)]
|
|
175
168
|
data = EqForwardData(valdate, pillars, name=name)
|
|
176
169
|
file = data_file(name, valdate)
|
|
177
|
-
data.dump(file)
|
|
170
|
+
# data.dump(file)
|
|
178
171
|
|
|
179
172
|
# Get data from existing file
|
|
180
173
|
test_data = eqforwarddata_from_file(file)
|
|
@@ -185,7 +178,7 @@ if __name__ == "__main__":
|
|
|
185
178
|
curve.calibrate(test_data, spot, yieldcurve)
|
|
186
179
|
|
|
187
180
|
# Interpolate and display
|
|
188
|
-
test_dates = [
|
|
181
|
+
test_dates = [dts.advance(valdate, str(n) + 'm') for n in range(1, 150)]
|
|
189
182
|
test_fwds = curve.value(test_dates)
|
|
190
183
|
|
|
191
184
|
# Original data
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import os, json, logging
|
|
2
2
|
import datetime as dt
|
|
3
3
|
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from sdevpy.utilities import dates
|
|
6
7
|
from sdevpy.utilities import timegrids
|
|
7
8
|
from sdevpy.analytics import black
|
|
8
|
-
|
|
9
|
+
from sdevpy.market.eqforward import EqForwardCurve, get_forward_curves
|
|
10
|
+
log = logging.getLogger(Path(__file__).stem)
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class EqVolSurfaceData:
|
|
@@ -20,46 +22,58 @@ class EqVolSurfaceData:
|
|
|
20
22
|
|
|
21
23
|
# Extract
|
|
22
24
|
self.expiries = np.asarray([s['expiry'] for s in sections])
|
|
23
|
-
self.forwards = np.asarray([s['forward'] for s in sections])
|
|
24
25
|
self.input_strikes = [np.asarray(s['strikes']) for s in sections]
|
|
25
26
|
self.vols = [np.asarray(s['vols']) for s in sections]
|
|
26
27
|
|
|
27
28
|
# Size checks
|
|
28
29
|
n_times = len(self.expiries)
|
|
29
|
-
if any(len(x) != n_times for x in (self.
|
|
30
|
+
if any(len(x) != n_times for x in (self.input_strikes, self.vols)):
|
|
30
31
|
raise ValueError("Incompatible size along time direction between expiries, forwards, strikes and vols")
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# self.abs_strikes = self.forwards * self.input_strikes # Old way assuming numpy matrix
|
|
37
|
-
# The code below is a quick non-tested implementation to cater for the non-matrix case.
|
|
38
|
-
# To be tested if/when case presents itself.
|
|
39
|
-
log.warning("Relative strike conversion should be tested")
|
|
40
|
-
self.abs_strikes = []
|
|
41
|
-
for i in range(n_times):
|
|
42
|
-
self.abs_strikes.append(self.forwards[i] * self.input_strikes[i])
|
|
43
|
-
else:
|
|
44
|
-
raise ValueError(f"Strike input type not supported yet: {self.strike_input_type}")
|
|
45
|
-
|
|
46
|
-
# Calculate prices
|
|
47
|
-
self.call_prices = []
|
|
33
|
+
def get_prices(self, fwd_curve: EqForwardCurve, option_type: str='call') -> npt.ArrayLike:
|
|
34
|
+
option_type_lw = option_type.lower()
|
|
35
|
+
prices = []
|
|
36
|
+
abs_strikes = self.get_strikes(fwd_curve, to_type='absolute')
|
|
48
37
|
for exp_idx, expiry in enumerate(self.expiries):
|
|
49
38
|
t = timegrids.model_time(self.valdate, expiry)
|
|
50
|
-
fwd =
|
|
51
|
-
strikes =
|
|
39
|
+
fwd = fwd_curve.value(expiry)
|
|
40
|
+
strikes = abs_strikes[exp_idx]
|
|
52
41
|
vols = self.vols[exp_idx]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
42
|
+
match option_type_lw:
|
|
43
|
+
case 'call':
|
|
44
|
+
price = black.price(t, strikes, True, fwd, vols)
|
|
45
|
+
case 'put':
|
|
46
|
+
price = black.price(t, strikes, False, fwd, vols)
|
|
47
|
+
case 'straddle':
|
|
48
|
+
price = black.price(t, strikes, True, fwd, vols)
|
|
49
|
+
price = price + black.price(t, strikes, False, fwd, vols)
|
|
50
|
+
case _:
|
|
51
|
+
raise ValueError(f"Invalid option type: {option_type}")
|
|
52
|
+
|
|
53
|
+
prices.append(price)
|
|
54
|
+
return prices
|
|
55
|
+
|
|
56
|
+
def get_strikes(self, fwd_curve: EqForwardCurve=None, to_type: str='absolute') -> npt.ArrayLike:
|
|
57
|
+
""" Retrieve strikes, absolute or relative """
|
|
58
|
+
to_type_lw = to_type.lower()
|
|
59
|
+
if to_type_lw == self.strike_input_type:
|
|
60
|
+
return self.input_strikes
|
|
61
|
+
else: # Need conversion
|
|
62
|
+
if fwd_curve is None:
|
|
63
|
+
raise ValueError(f"Forward curve required for strike conversion but None given: {self.name}")
|
|
64
|
+
|
|
65
|
+
# Need to loop over expiries because not all expiries must have the same number of strikes.
|
|
66
|
+
# Therefore we cannot put the strikes into numpy arrays.
|
|
67
|
+
fwds = fwd_curve.value(self.expiries)
|
|
68
|
+
n_times = len(self.expiries)
|
|
69
|
+
if to_type_lw == 'absolute' and self.strike_input_type == 'relative':
|
|
70
|
+
conv_strikes = [self.input_strikes[i] * fwds[i] for i in range(n_times)]
|
|
71
|
+
elif to_type_lw == 'relative' and self.strike_input_type == 'absolute':
|
|
72
|
+
conv_strikes = [self.input_strikes[i] / fwds[i] for i in range(n_times)]
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError(f"Unknown strike type {to_type}: expected absolute or relative")
|
|
75
|
+
|
|
76
|
+
return conv_strikes
|
|
63
77
|
|
|
64
78
|
def dump(self, file, indent=2):
|
|
65
79
|
data = self.dump_data()
|
|
@@ -70,7 +84,7 @@ class EqVolSurfaceData:
|
|
|
70
84
|
sections = []
|
|
71
85
|
for i, expiry in enumerate(self.expiries):
|
|
72
86
|
expiry_str = expiry.strftime(dates.DATE_FORMAT)
|
|
73
|
-
section = {'expiry': expiry_str, '
|
|
87
|
+
section = {'expiry': expiry_str, 'strikes': self.input_strikes[i].tolist(),
|
|
74
88
|
'vols': self.vols[i].tolist()}
|
|
75
89
|
sections.append(section)
|
|
76
90
|
|
|
@@ -93,7 +107,6 @@ class EqVolSurfaceData:
|
|
|
93
107
|
for i in range(n_exp):
|
|
94
108
|
print(sep)
|
|
95
109
|
print(f"Expiry {i+1}/{n_exp}: {self.expiries[i].strftime(dates.DATE_FORMAT)}")
|
|
96
|
-
print(f"Forward: {self.forwards[i]:,.{n_digits}f}")
|
|
97
110
|
with np.printoptions(precision=n_digits):
|
|
98
111
|
print("Strikes", self.input_strikes[i])
|
|
99
112
|
print("Vols", self.vols[i])
|
|
@@ -158,9 +171,12 @@ if __name__ == "__main__":
|
|
|
158
171
|
# file = data_file(folder, name, valdate)
|
|
159
172
|
# surface_data.dump(file)
|
|
160
173
|
|
|
174
|
+
# Get forward curve
|
|
175
|
+
fwd_curve = get_forward_curves([name], valdate)[0]
|
|
176
|
+
|
|
161
177
|
# Get data from existing file
|
|
162
|
-
file = data_file(
|
|
178
|
+
file = data_file(name, valdate, folder=folder)
|
|
163
179
|
surface_data = eqvolsurfacedata_from_file(file)
|
|
164
|
-
print(surface_data.get_strikes('absolute'))
|
|
165
|
-
print(surface_data.get_strikes('relative'))
|
|
180
|
+
print(surface_data.get_strikes(to_type='absolute'))
|
|
181
|
+
print(surface_data.get_strikes(fwd_curve=fwd_curve, to_type='relative'))
|
|
166
182
|
surface_data.pretty_print(4)
|
|
@@ -4,7 +4,8 @@ import datetime as dt
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from sdevpy.utilities import
|
|
7
|
+
from sdevpy.utilities import dates as dts
|
|
8
|
+
from sdevpy.utilities import timegrids
|
|
8
9
|
from sdevpy.maths import interpolation as itp
|
|
9
10
|
|
|
10
11
|
|
|
@@ -75,13 +76,13 @@ class InterpolatedYieldCurve(YieldCurve):
|
|
|
75
76
|
# Set data in interpolation
|
|
76
77
|
if self.interp_var == YieldCurveVariable.ZERORATE:
|
|
77
78
|
times = timegrids.model_time(self.valdate, self.dates)
|
|
78
|
-
data_y = -np.log(dfs) / times
|
|
79
|
+
data_y = -np.log(self.dfs) / times
|
|
79
80
|
elif self.interp_var in [YieldCurveVariable.DISCOUNT, YieldCurveVariable.LOG_DISCOUNT]:
|
|
80
81
|
times = [0.0]
|
|
81
82
|
times.extend(timegrids.model_time(self.valdate, self.dates))
|
|
82
83
|
times = np.asarray(times)
|
|
83
84
|
ext_dfs = [1.0]
|
|
84
|
-
ext_dfs.extend(dfs)
|
|
85
|
+
ext_dfs.extend(self.dfs)
|
|
85
86
|
ext_dfs = np.asarray(ext_dfs)
|
|
86
87
|
if self.interp_var == YieldCurveVariable.DISCOUNT:
|
|
87
88
|
data_y = ext_dfs
|
|
@@ -128,13 +129,13 @@ class InterpolatedYieldCurve(YieldCurve):
|
|
|
128
129
|
raise RuntimeError("Failure to set curve interpolation")
|
|
129
130
|
|
|
130
131
|
def dump_data(self):
|
|
131
|
-
data = {'name': self.name, 'valdate': self.valdate.strftime(
|
|
132
|
-
'snapdate': self.snapdate.strftime(
|
|
132
|
+
data = {'name': self.name, 'valdate': self.valdate.strftime(dts.DATE_FORMAT),
|
|
133
|
+
'snapdate': self.snapdate.strftime(dts.DATETIME_FORMAT),
|
|
133
134
|
'interp_var': self.interp_var_str, 'interp_type': self.interp_type}
|
|
134
135
|
|
|
135
136
|
pillars = []
|
|
136
137
|
for expiry, df in zip(self.dates, self.dfs, strict=True):
|
|
137
|
-
pillar = {'expiry': expiry.strftime(
|
|
138
|
+
pillar = {'expiry': expiry.strftime(dts.DATE_FORMAT), 'df': df}
|
|
138
139
|
pillars.append(pillar)
|
|
139
140
|
|
|
140
141
|
data['pillars'] = pillars
|
|
@@ -147,26 +148,24 @@ class YieldCurveVariable(Enum):
|
|
|
147
148
|
LOG_DISCOUNT = 2
|
|
148
149
|
|
|
149
150
|
|
|
150
|
-
def get_yieldcurve(name, date, **kwargs):
|
|
151
|
+
def get_yieldcurve(name: str, date: dt.datetime, **kwargs):
|
|
151
152
|
file = data_file(name, date, **kwargs)
|
|
152
153
|
curve = yieldcurve_from_file(file)
|
|
153
154
|
return curve
|
|
154
155
|
|
|
155
156
|
|
|
156
|
-
def yieldcurve_from_file(file):
|
|
157
|
+
def yieldcurve_from_file(file: str):
|
|
157
158
|
with open(file) as f:
|
|
158
159
|
data = json.load(f)
|
|
159
160
|
|
|
160
161
|
name = data.get('name')
|
|
161
162
|
valdate = data.get('valdate')
|
|
162
|
-
snapdate = data.get('snapdate')
|
|
163
|
-
# Claude says to do the below but this fails in UT
|
|
164
|
-
# snapdate = dt.datetime.strptime(data.get('snapdate'), dates.DATE_FILE_FORMAT)
|
|
163
|
+
snapdate = dt.datetime.strptime(data.get('snapdate'), dts.DATETIME_FORMAT)
|
|
165
164
|
interp_var = data.get('interp_var')
|
|
166
165
|
interp_type = data.get('interp_type')
|
|
167
166
|
pillars = data.get('pillars')
|
|
168
167
|
|
|
169
|
-
valdate = dt.datetime.strptime(valdate,
|
|
168
|
+
valdate = dt.datetime.strptime(valdate, dts.DATE_FORMAT)
|
|
170
169
|
curve = InterpolatedYieldCurve(valdate=valdate, interp_var=interp_var, interp_type=interp_type,
|
|
171
170
|
name=name, snapdate=snapdate)
|
|
172
171
|
|
|
@@ -174,7 +173,7 @@ def yieldcurve_from_file(file):
|
|
|
174
173
|
pillar_dates, pillar_dfs = [], []
|
|
175
174
|
for pillar in pillars:
|
|
176
175
|
date_str = pillar.get('expiry')
|
|
177
|
-
date = dt.datetime.strptime(date_str,
|
|
176
|
+
date = dt.datetime.strptime(date_str, dts.DATE_FORMAT)
|
|
178
177
|
df = pillar.get('df')
|
|
179
178
|
pillar_dates.append(date)
|
|
180
179
|
pillar_dfs.append(df)
|
|
@@ -187,7 +186,7 @@ def data_file(name, date, **kwargs):
|
|
|
187
186
|
folder = kwargs.get('folder', test_data_folder())
|
|
188
187
|
name_folder = os.path.join(folder, name)
|
|
189
188
|
os.makedirs(name_folder, exist_ok=True)
|
|
190
|
-
file = os.path.join(name_folder, date.strftime(
|
|
189
|
+
file = os.path.join(name_folder, date.strftime(dts.DATE_FILE_FORMAT) + ".json")
|
|
191
190
|
return file
|
|
192
191
|
|
|
193
192
|
|
|
@@ -201,7 +200,6 @@ def test_data_folder():
|
|
|
201
200
|
|
|
202
201
|
if __name__ == "__main__":
|
|
203
202
|
import matplotlib.pyplot as plt
|
|
204
|
-
from sdevpy.utilities import dates
|
|
205
203
|
|
|
206
204
|
valdate = dt.datetime(2026, 2, 15)
|
|
207
205
|
|
|
@@ -223,7 +221,7 @@ if __name__ == "__main__":
|
|
|
223
221
|
curve.set_data(zdates, dfs)
|
|
224
222
|
|
|
225
223
|
# Interpolate and display
|
|
226
|
-
test_dates = [
|
|
224
|
+
test_dates = [dts.advance(valdate, str(n) + 'm') for n in range(1, 600)]
|
|
227
225
|
test_dfs = curve.discount(test_dates)
|
|
228
226
|
test_times = timegrids.model_time(valdate, test_dates)
|
|
229
227
|
test_zrs = -np.log(test_dfs) / test_times
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import math
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
FLOAT_INFTY = float('inf')
|
|
@@ -17,6 +18,8 @@ C_SQRT2PI = 2.50662827459518
|
|
|
17
18
|
C_2_SQRTPI = 1.12837916709551257390
|
|
18
19
|
k_2powneg32 = 2.3283064365387e-10 # Normalization factor for Sobol
|
|
19
20
|
|
|
21
|
+
TWO_PI = 2.0 * math.pi
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
if __name__ == "__main__":
|
|
22
25
|
print(f"FLOAT_INFTY: {FLOAT_INFTY}")
|