sdevpy 1.0.6__tar.gz → 1.0.8__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.0.6 → sdevpy-1.0.8}/PKG-INFO +8 -1
- sdevpy-1.0.8/pyproject.toml +43 -0
- sdevpy-1.0.8/sdevpy/__init__.py +21 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/analytics/americantree.py +7 -8
- sdevpy-1.0.8/sdevpy/analytics/bachelier.py +140 -0
- sdevpy-1.0.8/sdevpy/analytics/black.py +105 -0
- sdevpy-1.0.8/sdevpy/cointegration/back_testing.py +224 -0
- sdevpy-1.0.8/sdevpy/cointegration/coint_trading.py +677 -0
- sdevpy-1.0.8/sdevpy/cointegration/data_io.py +14 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/cointegration/mean_reversion.py +117 -128
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/cointegration/model_settings.py +6 -5
- sdevpy-1.0.8/sdevpy/cointegration/utils.py +132 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/machinelearning/datasets.py +12 -9
- {sdevpy-1.0.6/sdevpy/machinelearning → sdevpy-1.0.8/sdevpy/machinelearning/keras}/callbacks.py +1 -3
- {sdevpy-1.0.6/sdevpy/machinelearning → sdevpy-1.0.8/sdevpy/machinelearning/keras}/learningmodel.py +8 -6
- {sdevpy-1.0.6/sdevpy/machinelearning → sdevpy-1.0.8/sdevpy/machinelearning/keras}/learningschedules.py +3 -4
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/attention.py +3 -5
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/chat.py +2 -2
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/datasets.py +0 -1
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/gpt.py +7 -6
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/instructions.py +8 -8
- sdevpy-1.0.6/sdevpy/llms/ModelConverter.py → sdevpy-1.0.8/sdevpy/machinelearning/llms/modelconverter.py +3 -5
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/training.py +2 -1
- sdevpy-1.0.8/sdevpy/market/correlations.py +76 -0
- sdevpy-1.0.8/sdevpy/market/eqforward.py +197 -0
- sdevpy-1.0.8/sdevpy/market/eqvolsurface.py +166 -0
- sdevpy-1.0.8/sdevpy/market/fixings.py +181 -0
- sdevpy-1.0.8/sdevpy/market/spot.py +81 -0
- sdevpy-1.0.8/sdevpy/market/yieldcurve.py +246 -0
- sdevpy-1.0.8/sdevpy/maths/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/maths/constants.py +28 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/interpolation.py +73 -35
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/optimization.py +30 -27
- sdevpy-1.0.8/sdevpy/maths/rand/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/maths/rand/correlations.py +44 -0
- sdevpy-1.0.8/sdevpy/maths/rand/pathconstruction.py +180 -0
- sdevpy-1.0.8/sdevpy/maths/rand/rng.py +148 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/regression.py +1 -1
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/specialfunctions.py +1 -0
- sdevpy-1.0.8/sdevpy/models/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/models/assetmodels.py +90 -0
- sdevpy-1.0.8/sdevpy/models/multiasset_heston.py +37 -0
- sdevpy-1.0.8/sdevpy/montecarlo/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/montecarlo/mcpricer.py +188 -0
- sdevpy-1.0.8/sdevpy/montecarlo/mcrun.py +102 -0
- sdevpy-1.0.8/sdevpy/montecarlo/pathgenerator.py +31 -0
- sdevpy-1.0.8/sdevpy/montecarlo/payoffs/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/montecarlo/payoffs/basic.py +669 -0
- sdevpy-1.0.8/sdevpy/montecarlo/payoffs/cashflows.py +32 -0
- sdevpy-1.0.8/sdevpy/montecarlo/payoffs/exotics.py +96 -0
- sdevpy-1.0.8/sdevpy/montecarlo/payoffs/vanillas.py +76 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/montecarlo/smoothers.py +1 -9
- sdevpy-1.0.8/sdevpy/pde/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/pde/forwardpde.py +16 -19
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/pde/pdeschemes.py +1 -4
- sdevpy-1.0.8/sdevpy/projects/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/projects/aad/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/aad/aad_mc.py +2 -1
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/aad/aad_mc_nd.py +4 -4
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/chat_gpt2.py +3 -3
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/datafiles.py +1 -1
- sdevpy-1.0.8/sdevpy/projects/raschka/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/ch2_working_with_text.py +3 -3
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/ch3_coding_attention.py +3 -3
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/ch4_gpt_model.py +3 -3
- sdevpy-1.0.6/sdevpy/projects/raschka/ch5_loadGPT2.py → sdevpy-1.0.8/sdevpy/projects/raschka/ch5_loadgpt2.py +2 -2
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/ch5_pretraining.py +7 -7
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/ch7_instruction_finetuning.py +2 -2
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/raschka_datasetloader.py +2 -2
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/raschka_dnn.py +1 -1
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/raschka/raschka_gpt_download.py +1 -1
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/set_limits.py +4 -7
- sdevpy-1.0.8/sdevpy/projects/stovol/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/stovol/stovolgen.py +3 -3
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/stovol/stovolplot.py +1 -2
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/stovol/stovoltrain.py +3 -4
- sdevpy-1.0.8/sdevpy/projects/stovolinverse/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/stovolinverse/stovolinvgen.py +4 -3
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/stovolinverse/stovolinvtrain.py +8 -8
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/projects/update_db.py +1 -6
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/settings.py +7 -6
- sdevpy-1.0.8/sdevpy/tensorflow/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/tensorflow/tf_black.py +4 -4
- sdevpy-1.0.8/sdevpy/tests/test.py +82 -0
- sdevpy-1.0.8/sdevpy/tests/test_algos.py +125 -0
- sdevpy-1.0.8/sdevpy/tests/test_analytics.py +132 -0
- sdevpy-1.0.8/sdevpy/tests/test_dates.py +58 -0
- sdevpy-1.0.8/sdevpy/tests/test_impliedvol.py +172 -0
- sdevpy-1.0.8/sdevpy/tests/test_interpolation.py +54 -0
- sdevpy-1.0.8/sdevpy/tests/test_localvol.py +0 -0
- sdevpy-1.0.8/sdevpy/tests/test_marketdata.py +48 -0
- sdevpy-1.0.8/sdevpy/tests/test_mc.py +85 -0
- sdevpy-1.0.8/sdevpy/tests/test_pde.py +116 -0
- sdevpy-1.0.8/sdevpy/tests/test_timegrids.py +49 -0
- sdevpy-1.0.8/sdevpy/tests/test_utils.py +67 -0
- sdevpy-1.0.8/sdevpy/tests/test_yieldcurves.py +49 -0
- sdevpy-1.0.8/sdevpy/thirdparty/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/__init__.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/constants.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/erf_cody.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/exceptions.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/lets_be_rational.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/normaldistribution.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/rationalcubic.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/__init__.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black/__init__.py +1 -0
- sdevpy-1.0.8/sdevpy/thirdparty/py_vollib/black/greeks/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black/greeks/analytical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black/greeks/numerical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black/implied_volatility.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes/__init__.py +1 -0
- sdevpy-1.0.8/sdevpy/thirdparty/py_vollib/black_scholes/greeks/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes/greeks/analytical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes/greeks/numerical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes/implied_volatility.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes_merton/__init__.py +1 -0
- sdevpy-1.0.8/sdevpy/thirdparty/py_vollib/black_scholes_merton/greeks/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes_merton/greeks/analytical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes_merton/greeks/numerical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/black_scholes_merton/implied_volatility.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/helpers/__init__.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/helpers/constants.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/helpers/distributions.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/helpers/doctest_helper.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/helpers/exceptions.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/helpers/numerical_greeks.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/__init__.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black/__init__.py +1 -0
- sdevpy-1.0.8/sdevpy/thirdparty/py_vollib/ref_python/black/greeks/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black/greeks/analytical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black/greeks/numerical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black/implied_volatility.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes/__init__.py +1 -0
- sdevpy-1.0.8/sdevpy/thirdparty/py_vollib/ref_python/black_scholes/greeks/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes/greeks/analytical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes/greeks/numerical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes/implied_volatility.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes_merton/__init__.py +1 -0
- sdevpy-1.0.8/sdevpy/thirdparty/py_vollib/ref_python/black_scholes_merton/greeks/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes_merton/greeks/analytical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes_merton/greeks/numerical.py +1 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_vollib/ref_python/black_scholes_merton/implied_volatility.py +1 -0
- sdevpy-1.0.8/sdevpy/timeseries/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/timeseries/backtesting.py +86 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/timeseries/cointegration.py +20 -23
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/timeseries/meanreversion.py +47 -48
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/timeseries/timeseriestools.py +15 -18
- sdevpy-1.0.8/sdevpy/tree/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/tree/trees.py +1 -16
- sdevpy-1.0.8/sdevpy/utilities/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/algos.py +5 -3
- sdevpy-1.0.8/sdevpy/utilities/book.py +51 -0
- sdevpy-1.0.8/sdevpy/utilities/dates.py +31 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/network.py +5 -2
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/pydotnet.py +8 -6
- sdevpy-1.0.8/sdevpy/utilities/scalendar.py +286 -0
- sdevpy-1.0.8/sdevpy/utilities/speriods.py +25 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/timegrids.py +35 -6
- sdevpy-1.0.8/sdevpy/utilities/tools.py +52 -0
- sdevpy-1.0.8/sdevpy/volatility/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy/models → sdevpy-1.0.8/sdevpy/volatility/impliedvol}/impliedvol.py +9 -5
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/impliedvol_calib.py +137 -0
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/volatility/impliedvol}/models/biexp.py +57 -64
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/cubicvol.py +205 -0
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/fbsabr.py +182 -0
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/gsvi.py +67 -0
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/logmix.py +459 -0
- {sdevpy-1.0.6/sdevpy/analytics → sdevpy-1.0.8/sdevpy/volatility/impliedvol/models}/mcheston.py +78 -82
- {sdevpy-1.0.6/sdevpy/analytics → sdevpy-1.0.8/sdevpy/volatility/impliedvol/models}/mcsabr.py +96 -99
- {sdevpy-1.0.6/sdevpy/analytics → sdevpy-1.0.8/sdevpy/volatility/impliedvol/models}/mczabr.py +83 -86
- {sdevpy-1.0.6/sdevpy/analytics → sdevpy-1.0.8/sdevpy/volatility/impliedvol/models}/sabr.py +14 -11
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/volatility/impliedvol}/models/svi.py +18 -29
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/tssvi1.py +195 -0
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/tssvi2.py +183 -0
- sdevpy-1.0.6/sdevpy/models/svivol.py → sdevpy-1.0.8/sdevpy/volatility/impliedvol/models/vsvi.py +51 -50
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/optionsurface.py +167 -0
- sdevpy-1.0.8/sdevpy/volatility/impliedvol/zerosurface.py +216 -0
- sdevpy-1.0.8/sdevpy/volatility/localvol/__init__.py +0 -0
- sdevpy-1.0.8/sdevpy/volatility/localvol/localvol.py +92 -0
- sdevpy-1.0.8/sdevpy/volatility/localvol/localvol_calib.py +303 -0
- sdevpy-1.0.8/sdevpy/volatility/localvol/localvol_factory.py +201 -0
- sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy/volsurfacegen → sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen}/fbsabrgenerator.py +4 -4
- {sdevpy-1.0.6/sdevpy/volsurfacegen → sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen}/mchestongenerator.py +6 -6
- {sdevpy-1.0.6/sdevpy/volsurfacegen → sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen}/mcsabrgenerator.py +4 -4
- {sdevpy-1.0.6/sdevpy/volsurfacegen → sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen}/mczabrgenerator.py +5 -5
- {sdevpy-1.0.6/sdevpy/volsurfacegen → sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen}/sabrgenerator.py +32 -42
- {sdevpy-1.0.6/sdevpy/volsurfacegen → sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen}/smilegenerator.py +4 -5
- {sdevpy-1.0.6/sdevpy/volsurfacegen → sdevpy-1.0.8/sdevpy/volatility/mlsurfacegen}/stovolfactory.py +5 -5
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy.egg-info/PKG-INFO +8 -1
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy.egg-info/SOURCES.txt +114 -50
- sdevpy-1.0.8/sdevpy.egg-info/requires.txt +9 -0
- sdevpy-1.0.8/sdevpy.egg-info/top_level.txt +1 -0
- sdevpy-1.0.6/pyproject.toml +0 -30
- sdevpy-1.0.6/sdevpy/analytics/bachelier.py +0 -81
- sdevpy-1.0.6/sdevpy/analytics/black.py +0 -64
- sdevpy-1.0.6/sdevpy/analytics/fbsabr.py +0 -184
- sdevpy-1.0.6/sdevpy/cointegration/back_testing.py +0 -233
- sdevpy-1.0.6/sdevpy/cointegration/black_analytics.py +0 -45
- sdevpy-1.0.6/sdevpy/cointegration/coint_trading.py +0 -839
- sdevpy-1.0.6/sdevpy/cointegration/data_io.py +0 -225
- sdevpy-1.0.6/sdevpy/cointegration/implied_vol.py +0 -116
- sdevpy-1.0.6/sdevpy/cointegration/plotting.py +0 -296
- sdevpy-1.0.6/sdevpy/cointegration/run_unit_test.py +0 -572
- sdevpy-1.0.6/sdevpy/cointegration/utils.py +0 -477
- sdevpy-1.0.6/sdevpy/market/volsurface.py +0 -21
- sdevpy-1.0.6/sdevpy/maths/constants.py +0 -7
- sdevpy-1.0.6/sdevpy/maths/rand.py +0 -99
- sdevpy-1.0.6/sdevpy/models/localvol.py +0 -74
- sdevpy-1.0.6/sdevpy/models/localvol_calib.py +0 -301
- sdevpy-1.0.6/sdevpy/models/localvol_factory.py +0 -109
- sdevpy-1.0.6/sdevpy/montecarlo/singlefactormc.py +0 -11
- sdevpy-1.0.6/sdevpy/test.py +0 -66
- sdevpy-1.0.6/sdevpy/timeseries/backtesting.py +0 -91
- sdevpy-1.0.6/sdevpy/tools/utils.py +0 -42
- sdevpy-1.0.6/sdevpy.egg-info/requires.txt +0 -2
- sdevpy-1.0.6/sdevpy.egg-info/top_level.txt +0 -2
- {sdevpy-1.0.6 → sdevpy-1.0.8}/README.md +0 -0
- {sdevpy-1.0.6/sdevpy/thirdparty/py_vollib/black/greeks → sdevpy-1.0.8/sdevpy/analytics}/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy/thirdparty/py_vollib/black_scholes/greeks → sdevpy-1.0.8/sdevpy/cointegration}/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy/thirdparty/py_vollib/black_scholes_merton/greeks → sdevpy-1.0.8/sdevpy/machinelearning}/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy/thirdparty/py_vollib/ref_python/black/greeks → sdevpy-1.0.8/sdevpy/machinelearning/keras}/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy/machinelearning → sdevpy-1.0.8/sdevpy/machinelearning/keras}/topology.py +0 -0
- {sdevpy-1.0.6/sdevpy/thirdparty/py_vollib/ref_python/black_scholes/greeks → sdevpy-1.0.8/sdevpy/machinelearning/llms}/__init__.py +0 -0
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/textgen.py +0 -0
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/machinelearning}/llms/tokenizers.py +0 -0
- {sdevpy-1.0.6/sdevpy/thirdparty/py_vollib/ref_python/black_scholes_merton/greeks → sdevpy-1.0.8/sdevpy/market}/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/integration.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/metrics.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/sets.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/maths/tridiag.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/tensorflow/tf_metrics.py +0 -0
- {sdevpy-1.0.6/sdevpy → sdevpy-1.0.8/sdevpy/tests}/__init__.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy/thirdparty/py_lets_be_rational/numba_helper.py +0 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/clipboard.py +0 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/constants.py +0 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/filemanager.py +0 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/jsonmanager.py +0 -0
- {sdevpy-1.0.6/sdevpy/tools → sdevpy-1.0.8/sdevpy/utilities}/timer.py +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/sdevpy.egg-info/dependency_links.txt +0 -0
- {sdevpy-1.0.6 → sdevpy-1.0.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdevpy
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.8
|
|
4
4
|
Summary: Python package for Finance
|
|
5
5
|
Author-email: Sebastien Gurrieri <sebgur@gmail.com>
|
|
6
6
|
Project-URL: Git page, https://github.com/sebgur/SDev.Python
|
|
@@ -11,6 +11,13 @@ Requires-Python: >=3.6
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: pandas
|
|
13
13
|
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: scipy
|
|
15
|
+
Requires-Dist: matplotlib
|
|
16
|
+
Requires-Dist: holidays
|
|
17
|
+
Requires-Dist: pandas_market_calendars
|
|
18
|
+
Requires-Dist: openpyxl
|
|
19
|
+
Requires-Dist: colorlog
|
|
20
|
+
Requires-Dist: scikit-learn
|
|
14
21
|
|
|
15
22
|
# SDev.Python
|
|
16
23
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sdevpy"
|
|
7
|
+
version = "1.0.8"
|
|
8
|
+
license-files = []
|
|
9
|
+
authors = [{ name="Sebastien Gurrieri", email="sebgur@gmail.com" }]
|
|
10
|
+
description = "Python package for Finance"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.6"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
dependencies = ["pandas", "numpy", "scipy", "matplotlib", "holidays",
|
|
18
|
+
"pandas_market_calendars", "openpyxl", "colorlog",
|
|
19
|
+
"scikit-learn"
|
|
20
|
+
]
|
|
21
|
+
#dependencies = ["pyperclip", "tensorflow", "scikit-learn",
|
|
22
|
+
# "tensorflow_probability", "silence_tensorflow"]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
where = ["."]
|
|
26
|
+
include = ["sdevpy*"] # Only include these
|
|
27
|
+
exclude = ["sdevpy/volatility/impliedvol/models/tssvi2*"]
|
|
28
|
+
#exclude = ["notebooks*", "spreadsheets*"] # Exclude these
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.package-data]
|
|
31
|
+
"sdevpy" = ["datasets/**/*", "notebooks/**/*.ipynb"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 120
|
|
35
|
+
target-version = "py313"
|
|
36
|
+
|
|
37
|
+
[tool.ruff.lint]
|
|
38
|
+
select = ["E", "F", "N", "W", "UP", "B"]
|
|
39
|
+
ignore = ["E401", "I001"]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
"Git page" = "https://github.com/sebgur/SDev.Python"
|
|
43
|
+
"SDev Finance" = "http://sdev-finance.com/"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__version__ = '1.0.5'
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import colorlog
|
|
5
|
+
|
|
6
|
+
handler = colorlog.StreamHandler()
|
|
7
|
+
handler.setFormatter(colorlog.ColoredFormatter(
|
|
8
|
+
"%(log_color)s%(levelname)-8s%(reset)s %(name)s - %(message)s",
|
|
9
|
+
log_colors={
|
|
10
|
+
"DEBUG": "cyan",
|
|
11
|
+
"INFO": "green",
|
|
12
|
+
"WARNING": "yellow",
|
|
13
|
+
"ERROR": "red",
|
|
14
|
+
"CRITICAL": "bold_red",
|
|
15
|
+
}
|
|
16
|
+
))
|
|
17
|
+
|
|
18
|
+
root = logging.getLogger()
|
|
19
|
+
root.addHandler(handler)
|
|
20
|
+
root.setLevel(logging.WARNING)
|
|
21
|
+
logging.getLogger("sdevpy").setLevel(logging.DEBUG)
|
|
@@ -4,17 +4,18 @@ import time
|
|
|
4
4
|
import matplotlib.pyplot as plt
|
|
5
5
|
from sdevpy.analytics import black
|
|
6
6
|
from sdevpy.tree import trees
|
|
7
|
+
from sdevpy.tree.trees import Payoff
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
def option_price(ttm, strike, is_call, is_american, spot, vol
|
|
10
|
-
method='trinomial', n_steps =
|
|
10
|
+
def option_price(ttm: float, strike: float, is_call: bool, is_american: bool, spot: float, vol: float,
|
|
11
|
+
rf_rate: float, div_rate: float, disc_rate: float, method: str='trinomial', n_steps: int=30):
|
|
11
12
|
""" Price of an American option using binomial or trinomial trees under Black-Scholes model """
|
|
12
13
|
payoff = Payoff(ttm, strike, is_call, is_american)
|
|
13
14
|
return price(payoff, spot, vol, rf_rate, div_rate, disc_rate, method, n_steps)
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def price(payoff, spot, vol, rf_rate, div_rate, disc_rate,
|
|
17
|
-
method='trinomial', n_steps =
|
|
17
|
+
def price(payoff, spot: float, vol: float, rf_rate: float, div_rate: float, disc_rate: float,
|
|
18
|
+
method: str='trinomial', n_steps: int=30):
|
|
18
19
|
""" Price of a payoff using binomial or trinomial trees under Black-Scholes model """
|
|
19
20
|
if method == 'binomial':
|
|
20
21
|
tree = trees.BinomialTree(n_steps)
|
|
@@ -85,7 +86,6 @@ if __name__ == "__main__":
|
|
|
85
86
|
bin_p = [100.0 * (x / cf - 1.0) for x in bin_p]
|
|
86
87
|
tri_p = [100.0 * (x / cf - 1.0) for x in tri_p]
|
|
87
88
|
|
|
88
|
-
|
|
89
89
|
# Plot the results
|
|
90
90
|
plt.figure(figsize=(10, 6))
|
|
91
91
|
plt.plot(bin_t, bin_p, label='Binomial Tree Price')
|
|
@@ -93,11 +93,10 @@ if __name__ == "__main__":
|
|
|
93
93
|
if not payoff.is_american:
|
|
94
94
|
plt.plot(cf_t, cf_p, label='Vanilla CF Price')
|
|
95
95
|
# # plt.plot(steps_range, trinomial_prices, label='Trinomial Tree Price')
|
|
96
|
-
# plt.hlines(bs_price, steps_range[0], steps_range[-1], colors='r', linestyles='dashed',
|
|
96
|
+
# plt.hlines(bs_price, steps_range[0], steps_range[-1], colors='r', linestyles='dashed',
|
|
97
|
+
# label='Black-Scholes Price')
|
|
97
98
|
plt.title('Convergence to Black-Scholes Price for Call Options')
|
|
98
99
|
plt.xlabel('Runtime')
|
|
99
100
|
plt.ylabel('Option Price')
|
|
100
101
|
plt.legend()
|
|
101
102
|
plt.show()
|
|
102
|
-
|
|
103
|
-
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
""" Utilities for Bachelier model """
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
from scipy.stats import norm
|
|
5
|
+
from scipy.optimize import minimize_scalar
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def price(expiry: npt.ArrayLike, strike: npt.ArrayLike, is_call: npt.ArrayLike, fwd: npt.ArrayLike,
|
|
9
|
+
vol: npt.ArrayLike) -> npt.ArrayLike:
|
|
10
|
+
""" Option price under the Bachelier model """
|
|
11
|
+
stdev = vol * expiry**0.5
|
|
12
|
+
d = (fwd - strike) / stdev
|
|
13
|
+
wd = np.where(is_call, d, -d)
|
|
14
|
+
# wd = d if is_call else -d
|
|
15
|
+
return stdev * (wd * norm.cdf(wd) + norm.pdf(d))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def price_straddles(expiry: npt.ArrayLike, strike: npt.ArrayLike, fwd: npt.ArrayLike,
|
|
19
|
+
vol: npt.ArrayLike) -> npt.ArrayLike:
|
|
20
|
+
""" Straddle price under the Bachelier model.
|
|
21
|
+
Note: we could improve the speed by writing the code in-line instead of
|
|
22
|
+
calling the price() function twice """
|
|
23
|
+
expiries_ = np.asarray(expiry).reshape(-1, 1)
|
|
24
|
+
prices = []
|
|
25
|
+
for i, exp_row in enumerate(expiries_):
|
|
26
|
+
k_prices = []
|
|
27
|
+
for j, k in enumerate(strike[i]):
|
|
28
|
+
iv = vol[i, j]
|
|
29
|
+
call_price = price(exp_row, k, True, fwd, iv)
|
|
30
|
+
put_price = price(exp_row, k, False, fwd, iv)
|
|
31
|
+
k_prices.append(call_price[0] + put_price[0])
|
|
32
|
+
prices.append(k_prices)
|
|
33
|
+
|
|
34
|
+
return np.asarray(prices)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def implied_vol_jaeckel(expiry: float, strike: float, is_call: bool, fwd: float, fwd_price: float) -> float:
|
|
38
|
+
""" P. Jaeckel's method in "Implied Normal Volatility", 6th Jun. 2017.
|
|
39
|
+
This is the original, non-vectorized. """
|
|
40
|
+
m = fwd - strike
|
|
41
|
+
abs_m = np.abs(m)
|
|
42
|
+
# Special case at ATM
|
|
43
|
+
if abs_m < 1e-8:
|
|
44
|
+
return fwd_price * np.sqrt(2.0 * np.pi) / np.sqrt(expiry)
|
|
45
|
+
|
|
46
|
+
# General case
|
|
47
|
+
tilde_phi_star_c = -0.001882039271
|
|
48
|
+
theta = 1.0 if is_call else -1.0
|
|
49
|
+
|
|
50
|
+
tilde_phi_star = -np.abs(fwd_price - np.maximum(theta * m, 0.0)) / abs_m
|
|
51
|
+
em5 = 1e-5
|
|
52
|
+
|
|
53
|
+
if tilde_phi_star < tilde_phi_star_c:
|
|
54
|
+
g = 1.0 / (tilde_phi_star - 0.5)
|
|
55
|
+
g2 = g**2
|
|
56
|
+
em3 = 1e-3
|
|
57
|
+
num = 0.032114372355 - g2 * (0.016969777977 - g2 * (2.6207332461 * em3
|
|
58
|
+
- 9.6066952861 * em5 * g2))
|
|
59
|
+
den = 1.0 - g2 * (0.6635646938 - g2 * (0.14528712196 - 0.010472855461 * g2))
|
|
60
|
+
eta_bar = num / den
|
|
61
|
+
xb = g * (eta_bar * g2 + 1.0 / np.sqrt(2.0 * np.pi))
|
|
62
|
+
else:
|
|
63
|
+
h = np.sqrt(-np.log(-tilde_phi_star))
|
|
64
|
+
num = 9.4883409779 - h * (9.6320903635 - h * (0.58556997323 + 2.1464093351 * h))
|
|
65
|
+
den = 1.0 - h * (0.65174820867 + h * (1.5120247828 + 6.6437847132 * em5 * h))
|
|
66
|
+
xb = num / den
|
|
67
|
+
|
|
68
|
+
q = (norm.cdf(xb) + norm.pdf(xb) / xb - tilde_phi_star) / norm.pdf(xb)
|
|
69
|
+
xb2 = xb**2
|
|
70
|
+
num = 3.0 * q * xb2 * (2.0 - q * xb * (2.0 + xb2))
|
|
71
|
+
den = 6.0 + q * xb * (-12.0 + xb * (6.0 * q + xb * (-6.0 + q * xb * (3.0 + xb2))))
|
|
72
|
+
xs = xb + num / den
|
|
73
|
+
sigma = abs_m / (np.abs(xs) * np.sqrt(expiry))
|
|
74
|
+
return sigma
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def implied_vol(expiry: npt.ArrayLike, strike: npt.ArrayLike, is_call: npt.ArrayLike,
|
|
78
|
+
fwd: npt.ArrayLike, fwd_price: npt.ArrayLike) -> npt.NDArray[np.float64]:
|
|
79
|
+
""" P. Jaeckel's method in "Implied Normal Volatility", 6th Jun. 2017.
|
|
80
|
+
Vectorized version (vectorization not in Jaeckel). """
|
|
81
|
+
strike = np.asarray(strike, dtype=float)
|
|
82
|
+
fwd_price = np.asarray(fwd_price, dtype=float)
|
|
83
|
+
is_call = np.asarray(is_call, dtype=bool)
|
|
84
|
+
|
|
85
|
+
#### ATM branch ####
|
|
86
|
+
m = fwd - strike
|
|
87
|
+
abs_m = np.abs(m)
|
|
88
|
+
atm_mask = (abs_m < 1e-8)
|
|
89
|
+
atm_result = fwd_price * np.sqrt(2.0 * np.pi / expiry)
|
|
90
|
+
|
|
91
|
+
#### General case ####
|
|
92
|
+
tilde_phi_star_c = -0.001882039271
|
|
93
|
+
theta = np.where(is_call, 1.0, -1.0)
|
|
94
|
+
tilde_phi_star = -np.abs(fwd_price - np.maximum(theta * m, 0.0)) / np.where(atm_mask, 1.0, abs_m)
|
|
95
|
+
em5 = 1e-5
|
|
96
|
+
mask_low = (tilde_phi_star < tilde_phi_star_c)
|
|
97
|
+
|
|
98
|
+
# Branch = lower
|
|
99
|
+
g = 1.0 / (tilde_phi_star - 0.5)
|
|
100
|
+
g2 = g**2
|
|
101
|
+
em3 = 1e-3
|
|
102
|
+
num_low = 0.032114372355 - g2 * (0.016969777977 - g2 * (2.6207332461 * em3 - 9.6066952861 * em5 * g2))
|
|
103
|
+
den_low = 1.0 - g2 * (0.6635646938 - g2 * (0.14528712196 - 0.010472855461 * g2))
|
|
104
|
+
eta_bar = num_low / den_low
|
|
105
|
+
xb_low = g * (eta_bar * g2 + 1.0 / np.sqrt(2.0 * np.pi))
|
|
106
|
+
|
|
107
|
+
# Branch = higher
|
|
108
|
+
# Note: substitute a safe value where mask_low is True to avoid sqrt(log(large))
|
|
109
|
+
safe_tps = np.where(mask_low, -0.001, tilde_phi_star)
|
|
110
|
+
h = np.sqrt(-np.log(-safe_tps))
|
|
111
|
+
num_high = 9.4883409779 - h * (9.6320903635 - h * (0.58556997323 + 2.1464093351 * h))
|
|
112
|
+
den_high = 1.0 - h * (0.65174820867 + h * (1.5120247828 + 6.6437847132 * em5 * h))
|
|
113
|
+
xb_high = num_high / den_high
|
|
114
|
+
|
|
115
|
+
# Join lower/higher
|
|
116
|
+
xb = np.where(mask_low, xb_low, xb_high)
|
|
117
|
+
q = (norm.cdf(xb) + norm.pdf(xb) / xb - tilde_phi_star) / norm.pdf(xb)
|
|
118
|
+
xb2 = xb**2
|
|
119
|
+
num = 3.0 * q * xb2 * (2.0 - q * xb * (2.0 + xb2))
|
|
120
|
+
den = 6.0 + q * xb * (-12.0 + xb * (6.0 * q + xb * (-6.0 + q * xb * (3.0 + xb2))))
|
|
121
|
+
xs = xb + num / den
|
|
122
|
+
sigma = abs_m / (np.abs(xs) * np.sqrt(expiry))
|
|
123
|
+
|
|
124
|
+
# Join ATM/OTM
|
|
125
|
+
return np.where(atm_mask, atm_result, sigma)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def implied_vol_solve(expiry: float, strike: float, is_call: bool, fwd: float, fwd_price: float) -> float:
|
|
129
|
+
""" Direct method by numerical inversion using Brent """
|
|
130
|
+
options = {'xtol': 1e-4, 'maxiter': 100, 'disp': False}
|
|
131
|
+
xmin = 1e-6
|
|
132
|
+
xmax = 1.0
|
|
133
|
+
|
|
134
|
+
def error(vol):
|
|
135
|
+
premium = price(expiry, strike, is_call, fwd, vol)
|
|
136
|
+
return (premium - fwd_price) ** 2
|
|
137
|
+
|
|
138
|
+
res = minimize_scalar(fun=error, bracket=(xmin, xmax), options=options, method='brent')
|
|
139
|
+
|
|
140
|
+
return res.x
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
""" Utilities for Black-Scholes model """
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
from scipy.stats import norm
|
|
5
|
+
from scipy.optimize import minimize_scalar
|
|
6
|
+
from sdevpy.thirdparty.py_vollib.black import implied_volatility as jaeckel
|
|
7
|
+
from sdevpy.utilities.tools import isiterable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def price(expiry: npt.ArrayLike, strike: npt.ArrayLike, is_call: npt.ArrayLike, fwd: npt.ArrayLike,
|
|
11
|
+
vol: npt.ArrayLike) -> npt.NDArray[np.float64]:
|
|
12
|
+
""" Option price under the Black-Scholes model """
|
|
13
|
+
# w = 1.0 if is_call else -1.0
|
|
14
|
+
w = np.where(is_call, 1.0, -1.0)
|
|
15
|
+
s = vol * np.sqrt(expiry)
|
|
16
|
+
d1 = np.log(fwd / strike) / s + 0.5 * s
|
|
17
|
+
d2 = d1 - s
|
|
18
|
+
return w * (fwd * norm.cdf(w * d1) - strike * norm.cdf(w * d2))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def implied_vol(expiry: float, strike: float, is_call: bool, fwd: float, fwd_price: float) -> float:
|
|
22
|
+
""" Direct method by numerical inversion using Brent.
|
|
23
|
+
Non-vectorized due to solver. """
|
|
24
|
+
options = {'xtol': 1e-4, 'maxiter': 100, 'disp': False}
|
|
25
|
+
xmin = 1e-6
|
|
26
|
+
xmax = 1.0
|
|
27
|
+
|
|
28
|
+
def error(vol):
|
|
29
|
+
premium = price(expiry, strike, is_call, fwd, vol)
|
|
30
|
+
return (premium - fwd_price) ** 2
|
|
31
|
+
|
|
32
|
+
res = minimize_scalar(fun=error, bracket=(xmin, xmax), options=options, method='brent')
|
|
33
|
+
return res.x
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def implied_vols(expiry: float, strike: npt.ArrayLike, is_call: bool, fwd: float,
|
|
37
|
+
fwd_price: npt.ArrayLike) -> npt.ArrayLike:
|
|
38
|
+
""" Black implied volatility for vector of strikes/prices """
|
|
39
|
+
if isiterable(strike) and isiterable(fwd_price):
|
|
40
|
+
ivs = [implied_vol(expiry, k, is_call, fwd, p) for k, p in zip(strike, fwd_price, strict=True)]
|
|
41
|
+
return np.asarray(ivs)
|
|
42
|
+
elif not isiterable(strike) and not isiterable(fwd_price):
|
|
43
|
+
return implied_vol(expiry, strike, is_call, fwd, fwd_price)
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError("Incompatible shapes between strikes and prices")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def implied_vol_newton(expiry: float, strike: npt.ArrayLike, is_call: bool, fwd: float,
|
|
49
|
+
fwd_price: npt.ArrayLike, tol: float=1e-8, max_iter: int=50) -> npt.ArrayLike:
|
|
50
|
+
""" Using vectorized Newton-Raphson, with faster convergence than Brent.
|
|
51
|
+
However, this method can struggle for very small vegas, so we may want to switch
|
|
52
|
+
to another method (maybe Brent above) below a certain vega threshold.
|
|
53
|
+
Or to switch to Halley's method.
|
|
54
|
+
To be investigated if speed becomes a bottleneck or Brent's method has issues. """
|
|
55
|
+
strike = np.asarray(strike, dtype=float)
|
|
56
|
+
fwd_price = np.asarray(fwd_price, dtype=float)
|
|
57
|
+
vol = np.full_like(fwd_price, 0.25) # Initial guess: flat 25%
|
|
58
|
+
sqrt_t = np.sqrt(expiry)
|
|
59
|
+
for _ in range(max_iter):
|
|
60
|
+
s = vol * sqrt_t
|
|
61
|
+
d1 = np.log(fwd / strike) / s + 0.5 * s
|
|
62
|
+
vega = fwd * norm.pdf(d1) * sqrt_t
|
|
63
|
+
diff = price(expiry, strike, is_call, fwd, vol) - fwd_price
|
|
64
|
+
vol -= diff / vega
|
|
65
|
+
vol = np.maximum(vol, 1e-8) # Keep vol positive
|
|
66
|
+
if np.all(np.abs(diff) < tol):
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
# if len(strike) == 1 and len(fwd_price) == 1 and len(vol) == 1: # Return a scalar if inputs are scalar
|
|
70
|
+
# return vol[0]
|
|
71
|
+
# else:
|
|
72
|
+
# return vol
|
|
73
|
+
return (vol.item() if vol.ndim == 0 or vol.size ==1 else vol)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def implied_vol_jaeckel(expiry: float, strike: float, is_call: bool, fwd: float, fwd_price: float) -> float:
|
|
77
|
+
""" Black-Scholes implied volatility using P. Jaeckel's 'Let's be rational' method,
|
|
78
|
+
from package py_vollib. Install with pip install py_vollib or at
|
|
79
|
+
https://pypi.org/project/py_vollib/. Unfortunately we found it has instabilities
|
|
80
|
+
near ATM. """
|
|
81
|
+
flag = 'c' if is_call else 'p'
|
|
82
|
+
p = fwd_price
|
|
83
|
+
iv = jaeckel.implied_volatility_of_undiscounted_option_price(p, fwd, strike, expiry, flag)
|
|
84
|
+
return iv
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
EXPIRY = 1.0
|
|
89
|
+
VOL = 0.25
|
|
90
|
+
IS_CALL = True
|
|
91
|
+
NUM_POINTS = 100
|
|
92
|
+
# FWD = 100
|
|
93
|
+
# K = 100
|
|
94
|
+
# p = price(EXPIRY, K, IS_CALL, FWD, VOL)
|
|
95
|
+
# iv = implied_vol(EXPIRY, K, IS_CALL, FWD, p)
|
|
96
|
+
# print(iv)
|
|
97
|
+
f_space = np.linspace(100, 120, NUM_POINTS)
|
|
98
|
+
k_space = np.linspace(20, 2180, NUM_POINTS)
|
|
99
|
+
prices = price(EXPIRY, k_space, IS_CALL, f_space, VOL)
|
|
100
|
+
# print(prices)
|
|
101
|
+
implied_vols = []
|
|
102
|
+
for i, k in enumerate(k_space):
|
|
103
|
+
implied_vols.append(implied_vol(EXPIRY, k, IS_CALL, f_space[i], prices[i]))
|
|
104
|
+
|
|
105
|
+
# print(implied_vols)
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
# import statsmodels.api as sm
|
|
4
|
+
# from sdevpy.cointegration import coint_trading as ct
|
|
5
|
+
from sdevpy.cointegration import utils as ut
|
|
6
|
+
# from sdevpy.cointegration import data_io as myio
|
|
7
|
+
# from sdevpy.cointegration import mean_reversion as my_mean_rev
|
|
8
|
+
import math
|
|
9
|
+
from tqdm import tqdm # for console progress bar
|
|
10
|
+
|
|
11
|
+
# input string
|
|
12
|
+
# "['EURUSD Curncy', 'GBPUSD Curncy', 'JPYUSD Curncy', 'SGDUSD Curncy', 'CNHUSD Curncy']"
|
|
13
|
+
# we need to remove all "[", "]" and "'", then split by "," and then create a list
|
|
14
|
+
def name_list_string_to_name_list(name_list_str):
|
|
15
|
+
res_str = name_list_str.replace(" '", "")
|
|
16
|
+
res_str = res_str.replace("'", "")
|
|
17
|
+
res_str = res_str.replace('[', '')
|
|
18
|
+
res_str = res_str.replace(']', '')
|
|
19
|
+
name_list = list(res_str.split(','))
|
|
20
|
+
return name_list
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def weigths_list_string_to_float_list(weigths_list_str):
|
|
24
|
+
res_str = weigths_list_str.replace('[', '')
|
|
25
|
+
res_str = res_str.replace(']', '')
|
|
26
|
+
float_list = [float(idx) for idx in res_str.split(',')]
|
|
27
|
+
return float_list
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Given a fixed basket, from date, trade date, now date
|
|
31
|
+
# compute the sharpe ratio, z_score, 5D realized return and 10D realized return from trade date
|
|
32
|
+
def back_test_one_trade(from_, trade_date, now, name_list, weights_xxxusd, zscore_trade_date, df_fx_spot):
|
|
33
|
+
df_fx_name_list_xxxusd = df_fx_spot[name_list]
|
|
34
|
+
|
|
35
|
+
# compute the basket up to NOW, because we need to compute the realized 5D and 10D returns
|
|
36
|
+
basket_up_to_now = ut.compute_basket(df_fx_name_list_xxxusd.loc[from_:now], weights_xxxusd)
|
|
37
|
+
|
|
38
|
+
# compute the stdev as if we were on the TRADE DATE, this is used for computing the return in terms of ZScore
|
|
39
|
+
stdev_up_to_trade_date = np.std(basket_up_to_now.loc[:trade_date])
|
|
40
|
+
|
|
41
|
+
basket_from_trade_date_to_now = basket_up_to_now.loc[trade_date:now]
|
|
42
|
+
|
|
43
|
+
basket_2d_rtns_from_trade_date = ut.compute_x_day_historical_returns_in_SD(basket_from_trade_date_to_now, 2,
|
|
44
|
+
stdev_up_to_trade_date).iloc[0]
|
|
45
|
+
basket_5d_rtns_from_trade_date = ut.compute_x_day_historical_returns_in_SD(basket_from_trade_date_to_now, 5,
|
|
46
|
+
stdev_up_to_trade_date).iloc[0]
|
|
47
|
+
basket_10d_rtns_from_trade_date = ut.compute_x_day_historical_returns_in_SD(basket_from_trade_date_to_now, 10,
|
|
48
|
+
stdev_up_to_trade_date).iloc[0]
|
|
49
|
+
|
|
50
|
+
min_max_res_2d = ut.min_max_return_x_days_in_SD(basket_from_trade_date_to_now, 2, stdev_up_to_trade_date).iloc[0]
|
|
51
|
+
min_2d_rtns_in_sd = min_max_res_2d['min']
|
|
52
|
+
max_2d_rtns_in_sd = min_max_res_2d['max']
|
|
53
|
+
|
|
54
|
+
min_max_res_5d = ut.min_max_return_x_days_in_SD(basket_from_trade_date_to_now, 5, stdev_up_to_trade_date).iloc[0]
|
|
55
|
+
min_5d_rtns_in_sd = min_max_res_5d['min']
|
|
56
|
+
max_5d_rtns_in_sd = min_max_res_5d['max']
|
|
57
|
+
|
|
58
|
+
min_max_res_10d = ut.min_max_return_x_days_in_SD(basket_from_trade_date_to_now, 10, stdev_up_to_trade_date).iloc[0]
|
|
59
|
+
min_10d_rtns_in_sd = min_max_res_10d['min']
|
|
60
|
+
max_10d_rtns_in_sd = min_max_res_10d['max']
|
|
61
|
+
|
|
62
|
+
if math.isnan(basket_10d_rtns_from_trade_date):
|
|
63
|
+
raise Exception('Trade DATE is less than 10 days ago.')
|
|
64
|
+
|
|
65
|
+
if zscore_trade_date > 0:
|
|
66
|
+
# flip the return sign if zscore is above 0, because we sell. The basket goes down and we earn
|
|
67
|
+
basket_2d_rtns_from_trade_date = -basket_2d_rtns_from_trade_date
|
|
68
|
+
basket_5d_rtns_from_trade_date = -basket_5d_rtns_from_trade_date
|
|
69
|
+
basket_10d_rtns_from_trade_date = -basket_10d_rtns_from_trade_date
|
|
70
|
+
|
|
71
|
+
# so the max draw down is the negative of the max returns
|
|
72
|
+
max_2d_draw_down_in_sd = -max_2d_rtns_in_sd
|
|
73
|
+
max_5d_draw_down_in_sd = -max_5d_rtns_in_sd
|
|
74
|
+
max_10d_draw_down_in_sd = -max_10d_rtns_in_sd
|
|
75
|
+
else:
|
|
76
|
+
# we buy so that max draw down is teh min returns
|
|
77
|
+
max_2d_draw_down_in_sd = min_2d_rtns_in_sd
|
|
78
|
+
max_5d_draw_down_in_sd = min_5d_rtns_in_sd
|
|
79
|
+
max_10d_draw_down_in_sd = min_10d_rtns_in_sd
|
|
80
|
+
|
|
81
|
+
#if the max draw down is a positive number, we floor it to 0 to show there is no loss
|
|
82
|
+
max_2d_draw_down_in_sd = np.minimum(max_2d_draw_down_in_sd, 0.0)
|
|
83
|
+
max_5d_draw_down_in_sd = np.minimum(max_5d_draw_down_in_sd, 0.0)
|
|
84
|
+
max_10d_draw_down_in_sd = np.minimum(max_10d_draw_down_in_sd, 0.0)
|
|
85
|
+
|
|
86
|
+
res_dict = {'2D Realized Rtns from Trade Date in SD': basket_2d_rtns_from_trade_date,
|
|
87
|
+
'5D Realized Rtns from Trade Date in SD': basket_5d_rtns_from_trade_date,
|
|
88
|
+
'10D Realized Rtns from Trade Date in SD': basket_10d_rtns_from_trade_date,
|
|
89
|
+
'2D max draw down in SD': max_2d_draw_down_in_sd,
|
|
90
|
+
'5D max draw down in SD': max_5d_draw_down_in_sd,
|
|
91
|
+
'10D max draw down in SD': max_10d_draw_down_in_sd,
|
|
92
|
+
'basket stdev on Trade Date': stdev_up_to_trade_date
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return res_dict
|
|
96
|
+
|
|
97
|
+
# res_df_filtered - output from coint_trading.filter_cointegration_basket
|
|
98
|
+
def back_test_many_trades(res_df_filtered, now, df_fx_spot):
|
|
99
|
+
back_test_res = []
|
|
100
|
+
|
|
101
|
+
num_rows = len(res_df_filtered)
|
|
102
|
+
|
|
103
|
+
for idx in tqdm(range(num_rows)):
|
|
104
|
+
from_ = res_df_filtered['From'].iloc[idx]
|
|
105
|
+
trade_date = res_df_filtered['Today'].iloc[idx]
|
|
106
|
+
trade_date = pd.to_datetime(trade_date, format='%Y-%m-%d')
|
|
107
|
+
|
|
108
|
+
name_str = res_df_filtered['currency pairs'].iloc[idx]
|
|
109
|
+
name_list = name_list_string_to_name_list(name_str)
|
|
110
|
+
|
|
111
|
+
weights_xxxusd_str = res_df_filtered['unadj weights in xxxusd'].iloc[idx]
|
|
112
|
+
weights_xxxusd = weigths_list_string_to_float_list(weights_xxxusd_str)
|
|
113
|
+
|
|
114
|
+
sharpe_5d = res_df_filtered['5D Sharpe Ratio'].iloc[idx]
|
|
115
|
+
sd_on_trade_date = res_df_filtered['SD Current'].iloc[idx]
|
|
116
|
+
|
|
117
|
+
stop_loss_in_sd = res_df_filtered['Stop Loss in SD'].iloc[idx]
|
|
118
|
+
|
|
119
|
+
half_life_in_days = res_df_filtered['half life in days'].iloc[idx]
|
|
120
|
+
|
|
121
|
+
range_in_sd_current = res_df_filtered['Range in SD current'].iloc[idx]
|
|
122
|
+
|
|
123
|
+
abs_sd_on_trade_date = np.abs(sd_on_trade_date)
|
|
124
|
+
|
|
125
|
+
one_month_trace_5pct = res_df_filtered['+/- 1 month trace (5%)'].iloc[idx]
|
|
126
|
+
one_month_trace_10pct = res_df_filtered['+/- 1 month trace (10%)'].iloc[idx]
|
|
127
|
+
one_month_eigen_5pct = res_df_filtered['+/- 1 month eigen (5%)'].iloc[idx]
|
|
128
|
+
one_month_eigen_10pct = res_df_filtered['+/- 1 month eigen (10%)'].iloc[idx]
|
|
129
|
+
|
|
130
|
+
res_dict = back_test_one_trade(from_, trade_date, now, name_list, weights_xxxusd,
|
|
131
|
+
sd_on_trade_date, df_fx_spot)
|
|
132
|
+
|
|
133
|
+
basket_stdev = res_dict['basket stdev on Trade Date']
|
|
134
|
+
|
|
135
|
+
mean_rev_level = res_df_filtered['mean_rev_level'].iloc[idx]
|
|
136
|
+
|
|
137
|
+
back_test_res.append((from_, trade_date, now, name_list, weights_xxxusd, sharpe_5d,
|
|
138
|
+
sd_on_trade_date, abs_sd_on_trade_date, stop_loss_in_sd,
|
|
139
|
+
res_dict['2D Realized Rtns from Trade Date in SD'],
|
|
140
|
+
res_dict['5D Realized Rtns from Trade Date in SD'],
|
|
141
|
+
res_dict['10D Realized Rtns from Trade Date in SD'],
|
|
142
|
+
res_dict['2D max draw down in SD'],
|
|
143
|
+
res_dict['5D max draw down in SD'],
|
|
144
|
+
res_dict['10D max draw down in SD'],
|
|
145
|
+
basket_stdev, mean_rev_level, half_life_in_days, range_in_sd_current,
|
|
146
|
+
one_month_trace_5pct, one_month_trace_10pct,
|
|
147
|
+
one_month_eigen_5pct, one_month_eigen_10pct))
|
|
148
|
+
|
|
149
|
+
#--- end of for ind in tqdm(res_df_filtered.index):
|
|
150
|
+
|
|
151
|
+
res_df = pd.DataFrame(back_test_res, columns =['from_', 'Trade Date', 'Now', 'currency pairs',
|
|
152
|
+
'unadj weights in xxxusd', 'Sharpe Ratio on Trade Date',
|
|
153
|
+
'Z Score on Trade Date', 'Abs Z Score on Trade Date',
|
|
154
|
+
'Distance to max/min SD', '2D Realized Rtns in SD',
|
|
155
|
+
'5D Realized Rtns in SD', '10D Realized Rtns in SD',
|
|
156
|
+
'2D max DD in SD', '5D max DD in SD', '10D max DD in SD',
|
|
157
|
+
'basket stdev on Trade Date', 'mean_rev_level on Trade Date',
|
|
158
|
+
'half life in days', 'Range in SD current',
|
|
159
|
+
'+/- 1 month trace (5%)', '+/- 1 month trace (10%)',
|
|
160
|
+
'+/- 1 month eigen (5%)', '+/- 1 month eigen (10%)'])
|
|
161
|
+
|
|
162
|
+
return res_df
|
|
163
|
+
|
|
164
|
+
# Take the results of back_test_many_trades and then compute the back test diagnostics
|
|
165
|
+
def one_back_test_summary_table(back_test_res_df, start_date, end_date, sharpe_threshold, zscore_threshold):
|
|
166
|
+
upper_sd_condition = back_test_res_df['Z Score on Trade Date'] > zscore_threshold
|
|
167
|
+
lower_sd_condition = back_test_res_df['Z Score on Trade Date'] < -zscore_threshold
|
|
168
|
+
sharpe_condition = back_test_res_df['Sharpe Ratio on Trade Date'] > sharpe_threshold
|
|
169
|
+
|
|
170
|
+
start_date_condition = back_test_res_df['Trade Date'] > pd.Timestamp(start_date)
|
|
171
|
+
end_date_condition = back_test_res_df['Trade Date'] < pd.Timestamp(end_date)
|
|
172
|
+
|
|
173
|
+
# -----------apply conditions
|
|
174
|
+
my_trade_df = back_test_res_df[sharpe_condition]
|
|
175
|
+
my_trade_df = my_trade_df[upper_sd_condition | lower_sd_condition]
|
|
176
|
+
my_trade_df = my_trade_df[start_date_condition]
|
|
177
|
+
my_trade_df = my_trade_df[end_date_condition]
|
|
178
|
+
|
|
179
|
+
total = len(my_trade_df)
|
|
180
|
+
|
|
181
|
+
output_table = []
|
|
182
|
+
|
|
183
|
+
column_list = ['2D Realized Rtns in SD', '5D Realized Rtns in SD', '10D Realized Rtns in SD']
|
|
184
|
+
|
|
185
|
+
for column in column_list:
|
|
186
|
+
positive_return_condition = my_trade_df[column] > 0.0
|
|
187
|
+
num_pos_rtn = len(my_trade_df[positive_return_condition])
|
|
188
|
+
num_neg_rtn = len(my_trade_df[~positive_return_condition])
|
|
189
|
+
|
|
190
|
+
pos_rtn_pct = round(num_pos_rtn/total, 3)
|
|
191
|
+
neg_rtn_pct = round(num_neg_rtn/total, 3)
|
|
192
|
+
|
|
193
|
+
rtns_median = np.median(my_trade_df[column])
|
|
194
|
+
rtns_mean = np.mean(my_trade_df[column])
|
|
195
|
+
rtns_std = np.std(my_trade_df[column])
|
|
196
|
+
|
|
197
|
+
first_3_char = column[:3]
|
|
198
|
+
|
|
199
|
+
output_table.append((start_date,
|
|
200
|
+
end_date,
|
|
201
|
+
sharpe_threshold,
|
|
202
|
+
zscore_threshold,
|
|
203
|
+
first_3_char,
|
|
204
|
+
pos_rtn_pct,
|
|
205
|
+
neg_rtn_pct,
|
|
206
|
+
total,
|
|
207
|
+
rtns_median,
|
|
208
|
+
rtns_mean,
|
|
209
|
+
rtns_std))
|
|
210
|
+
|
|
211
|
+
res_df = pd.DataFrame(output_table, columns =['Period Start',
|
|
212
|
+
'Period End',
|
|
213
|
+
'Sharpe threshold',
|
|
214
|
+
'SD threshold',
|
|
215
|
+
'Rtns type',
|
|
216
|
+
'Pos %',
|
|
217
|
+
'Neg %',
|
|
218
|
+
'Total',
|
|
219
|
+
'Rtns median',
|
|
220
|
+
'Rtns mean',
|
|
221
|
+
'Rtns stdev'
|
|
222
|
+
])
|
|
223
|
+
|
|
224
|
+
return res_df, my_trade_df
|