├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── calcbsimpvol ├── __init__.py ├── data │ ├── __init__.py │ ├── cl_20171115.json.zip │ ├── reference_sample.json.zip │ └── spy_20190118.json.zip ├── docs │ ├── Makefile │ ├── make.bat │ └── source │ │ ├── README.rst │ │ ├── _static │ │ ├── copybutton.js │ │ └── img │ │ │ ├── calc_ivol_vs_reference_close_up.png │ │ │ ├── calc_ivol_vs_reference_close_up_calls.png │ │ │ ├── calc_ivol_vs_reference_close_up_puts.png │ │ │ ├── mlb_blsimpv_vs_reference.png │ │ │ ├── mlb_builtin_vs_matrixwise.png │ │ │ ├── mlb_builtin_vs_matrixwise_cleaned_for_nan_both.png │ │ │ ├── mlb_rational_vs_reference.png │ │ │ ├── mlb_rational_vs_reference_cleaned.png │ │ │ ├── python_vs_matlab_clean.png │ │ │ └── python_vs_reference_cleaned.png │ │ ├── api.rst │ │ ├── conf.py │ │ ├── data.rst │ │ ├── index.rst │ │ ├── license.rst │ │ └── results.rst ├── examples │ ├── __init__.py │ ├── calcBSImpVol_mlab │ │ ├── LICENSE │ │ └── calcBSImpVol.m │ ├── example1.py │ ├── example2.py │ ├── example3.py │ ├── mlb_reference_example.m │ └── runex.m ├── src │ ├── __init__.py │ └── calcbsimpvol.py └── tests │ ├── __init__.py │ ├── test_examples.py │ └── test_utilities.py ├── requirements.txt └── setup.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _bkp 2 | .todo 3 | .idea 4 | a.bat 5 | a27.bat 6 | .env 7 | .venv/ 8 | venv/ 9 | commands.sh 10 | todo.txt 11 | 12 | # Created by https://www.gitignore.io/api/python,matlab,pycharm,windows 13 | # Edit at https://www.gitignore.io/?templates=python,matlab,pycharm,windows 14 | 15 | ### Matlab ### 16 | # Windows default autosave extension 17 | *.asv 18 | 19 | # OSX / *nix default autosave extension 20 | *.m~ 21 | 22 | # Compiled MEX binaries (all platforms) 23 | *.mex* 24 | 25 | # Packaged app and toolbox files 26 | *.mlappinstall 27 | *.mltbx 28 | 29 | # Generated helpsearch folders 30 | helpsearch*/ 31 | 32 | # Simulink code generation folders 33 | slprj/ 34 | sccprj/ 35 | 36 | # Matlab code generation folders 37 | codegen/ 38 | 39 | # Simulink autosave extension 40 | *.autosave 41 | 42 | # Octave session info 43 | octave-workspace 44 | 45 | ### PyCharm ### 46 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 47 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 48 | 49 | # User-specific stuff 50 | .idea/**/workspace.xml 51 | .idea/**/tasks.xml 52 | .idea/**/usage.statistics.xml 53 | .idea/**/dictionaries 54 | .idea/**/shelf 55 | 56 | # Generated files 57 | .idea/**/contentModel.xml 58 | 59 | # Sensitive or high-churn files 60 | .idea/**/dataSources/ 61 | .idea/**/dataSources.ids 62 | .idea/**/dataSources.local.xml 63 | .idea/**/sqlDataSources.xml 64 | .idea/**/dynamic.xml 65 | .idea/**/uiDesigner.xml 66 | .idea/**/dbnavigator.xml 67 | 68 | # Gradle 69 | .idea/**/gradle.xml 70 | .idea/**/libraries 71 | 72 | # Gradle and Maven with auto-import 73 | # When using Gradle or Maven with auto-import, you should exclude module files, 74 | # since they will be recreated, and may cause churn. Uncomment if using 75 | # auto-import. 76 | # .idea/modules.xml 77 | # .idea/*.iml 78 | # .idea/modules 79 | 80 | # CMake 81 | cmake-build-*/ 82 | 83 | # Mongo Explorer plugin 84 | .idea/**/mongoSettings.xml 85 | 86 | # File-based project format 87 | *.iws 88 | 89 | # IntelliJ 90 | out/ 91 | 92 | # mpeltonen/sbt-idea plugin 93 | .idea_modules/ 94 | 95 | # JIRA plugin 96 | atlassian-ide-plugin.xml 97 | 98 | # Cursive Clojure plugin 99 | .idea/replstate.xml 100 | 101 | # Crashlytics plugin (for Android Studio and IntelliJ) 102 | com_crashlytics_export_strings.xml 103 | crashlytics.properties 104 | crashlytics-build.properties 105 | fabric.properties 106 | 107 | # Editor-based Rest Client 108 | .idea/httpRequests 109 | 110 | # Android studio 3.1+ serialized cache file 111 | .idea/caches/build_file_checksums.ser 112 | 113 | ### PyCharm Patch ### 114 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 115 | 116 | # *.iml 117 | # modules.xml 118 | # .idea/misc.xml 119 | # *.ipr 120 | 121 | # Sonarlint plugin 122 | .idea/sonarlint 123 | 124 | ### Python ### 125 | # Byte-compiled / optimized / DLL files 126 | __pycache__/ 127 | *.py[cod] 128 | *$py.class 129 | 130 | # C extensions 131 | *.so 132 | 133 | # Distribution / packaging 134 | .Python 135 | build/ 136 | develop-eggs/ 137 | dist/ 138 | downloads/ 139 | eggs/ 140 | .eggs/ 141 | lib/ 142 | lib64/ 143 | parts/ 144 | sdist/ 145 | var/ 146 | wheels/ 147 | share/python-wheels/ 148 | *.egg-info/ 149 | .installed.cfg 150 | *.egg 151 | MANIFEST 152 | 153 | # PyInstaller 154 | # Usually these files are written by a python script from a template 155 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 156 | *.manifest 157 | *.spec 158 | 159 | # Installer logs 160 | pip-log.txt 161 | pip-delete-this-directory.txt 162 | 163 | # Unit test / coverage reports 164 | htmlcov/ 165 | .tox/ 166 | .nox/ 167 | .coverage 168 | .coverage.* 169 | .cache 170 | nosetests.xml 171 | coverage.xml 172 | *.cover 173 | .hypothesis/ 174 | .pytest_cache/ 175 | 176 | # Translations 177 | *.mo 178 | *.pot 179 | 180 | # Django stuff: 181 | *.log 182 | local_settings.py 183 | db.sqlite3 184 | 185 | # Flask stuff: 186 | instance/ 187 | .webassets-cache 188 | 189 | # Scrapy stuff: 190 | .scrapy 191 | 192 | # Sphinx documentation 193 | docs/_build/ 194 | 195 | # PyBuilder 196 | target/ 197 | 198 | # Jupyter Notebook 199 | .ipynb_checkpoints 200 | 201 | # IPython 202 | profile_default/ 203 | ipython_config.py 204 | 205 | # pyenv 206 | .python-version 207 | 208 | # celery beat schedule file 209 | celerybeat-schedule 210 | 211 | # SageMath parsed files 212 | *.sage.py 213 | 214 | # Environments 215 | .env 216 | .venv 217 | env/ 218 | venv/ 219 | ENV/ 220 | env.bak/ 221 | venv.bak/ 222 | 223 | # Spyder project settings 224 | .spyderproject 225 | .spyproject 226 | 227 | # Rope project settings 228 | .ropeproject 229 | 230 | # mkdocs documentation 231 | /site 232 | 233 | # mypy 234 | .mypy_cache/ 235 | .dmypy.json 236 | dmypy.json 237 | 238 | # Pyre type checker 239 | .pyre/ 240 | 241 | ### Python Patch ### 242 | .venv/ 243 | 244 | ### Windows ### 245 | # Windows thumbnail cache files 246 | Thumbs.db 247 | ehthumbs.db 248 | ehthumbs_vista.db 249 | 250 | # Dump file 251 | *.stackdump 252 | 253 | # Folder config file 254 | [Dd]esktop.ini 255 | 256 | # Recycle Bin used on file shares 257 | $RECYCLE.BIN/ 258 | 259 | # Windows Installer files 260 | *.cab 261 | *.msi 262 | *.msix 263 | *.msm 264 | *.msp 265 | 266 | # Windows shortcuts 267 | *.lnk 268 | 269 | # End of https://www.gitignore.io/api/python,matlab,pycharm,windows 270 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | # - "2.6" 5 | # - "2.7" # fails 6 | - "3.4" 7 | - "3.5" 8 | - "3.5-dev" 9 | - "3.6" 10 | - "3.6-dev" 11 | - "3.7" 12 | - "3.7-dev" 13 | - "3.8" 14 | - "3.8-dev" 15 | # PyPy versions 16 | # - "pypy3.5" 17 | # pypy built dependencies for SciPy and Matplotlib 18 | before_install: 19 | - sudo apt-get install -y libblas-dev 20 | - sudo apt-get install -y liblapack-dev 21 | - sudo apt-get install -y gfortran 22 | - sudo apt-get install -y libfreetype6-dev 23 | # python dependencies 24 | install: 25 | - pip install -r requirements.txt 26 | - pip install . 27 | # command to run tests 28 | script: 29 | - pytest -v 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Erkan Demiralay 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. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the README 2 | include *.md 3 | 4 | # Include the license file 5 | include LICENSE 6 | 7 | # Include the data files 8 | include calcbsimpvol//data//*.json 9 | 10 | # Include .m-code 11 | include *.m -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build Status](https://travis-ci.com/erkandem/calcbsimpvol.svg?token=EM8YQfR9wuLvQFQzBZ5o&branch=master)](https://travis-ci.com/erkandem/calcbsimpvol) 3 | ![](https://img.shields.io/badge/License-MIT-blue.svg) 4 | ![](https://img.shields.io/badge/Python-3.4%20%7C%203.5%20%7C%203.6%20%7C%203.7%20%7C%203.8%20%7C%20PyPy3-blue.svg) 5 | [![](https://img.shields.io/badge/PyPi-v1.14.0-blue.svg)](https://pypi.org/project/calcbsimpvol/) 6 | 7 | # calcbsimpvol 8 | 9 | *Calculate Black-Scholes Implied Volatility - Vectorwise* 10 | 11 | ---------------------- 12 | 13 | * `:)` native python code 14 | * `:)` lightweight footprint 15 | * `:)` sample data included 16 | * `:(` not suited for single / low number of options 17 | * `:(` code reads un-pythonic 18 | * `:(` not yet thoroughly tested 19 | 20 | ## Getting started 21 | 22 | ### Requirements 23 | 24 | * Python 3.x (currently) or PyPy3 25 | * NumPy 26 | * SciPy 27 | * (MatPlotLib to visualize results in some examples) 28 | 29 | ### Installation 30 | 31 | While the code consists of single digit functions, 32 | I recommend using the `pip install` way to get the code. 33 | That way you would take advantage of bug fixes, updates, 34 | and possible extensions. 35 | 36 | ```bash 37 | $ pip install calcbsimpvol 38 | ``` 39 | 40 | ### Example 41 | 42 | Pass your `args` bundled in a `dict`. 43 | 44 | ```python 45 | from calcbsimpvol import calcbsimpvol 46 | import numpy as np 47 | 48 | S = np.asarray(100) 49 | K_value = np.arange(40, 160, 25) 50 | K = np.ones((np.size(K_value), 1)) 51 | K[:, 0] = K_value 52 | tau_value = np.arange(0.25, 1.01, 0.25) 53 | tau = np.ones((np.size(tau_value), 1)) 54 | tau[:, 0] = tau_value 55 | r = np.asarray(0.01) 56 | q = np.asarray(0.03) 57 | cp = np.asarray(1) 58 | P = [[59.35, 34.41, 10.34, 0.50, 0.01], 59 | [58.71, 33.85, 10.99, 1.36, 0.14], 60 | [58.07, 33.35, 11.50, 2.12, 0.40], 61 | [57.44, 32.91, 11.90, 2.77, 0.70]] 62 | 63 | P = np.asarray(P) 64 | [K, tau] = np.meshgrid(K, tau) 65 | 66 | sigma = calcbsimpvol(dict(cp=cp, P=P, S=S, K=K, tau=tau, r=r, q=q)) 67 | print(sigma) 68 | 69 | # [[ nan, nan, 0.20709362, 0.21820954, 0.24188675], 70 | # [ nan, 0.22279836, 0.20240934, 0.21386148, 0.23738982], 71 | # [ nan, 0.22442837, 0.1987048 , 0.21063506, 0.23450013], 72 | # [ nan, 0.22188111, 0.19564657, 0.20798285, 0.23045406]] 73 | 74 | ``` 75 | 76 | More usage examples are available in [example3.py](https://github.com/erkandem/calcbsimpvol) 77 | (additional sample data required which is available at [GitHub Repo](https://github.com/erkandem/calcbsimpvol) 78 | 79 | ## Performance 80 | ``` 81 | Design a test. 82 | Get the results you want. 83 | ``` 84 | 85 | * `k_max = 10` (default) 86 | * `tolerance = 10E-12` (default) 87 | * linear regression steps are commented out (default) 88 | 89 | ```bash 90 | # assuming you did install it already 91 | git clone https://github.com/erkandem/calcbsimpvol.git 92 | cd calcbsimpvol 93 | python examples/example3.py --steps 100 --mode reference 94 | ``` 95 | 96 | 97 | * 15 µs per option 98 | * 41 ms per surface 99 | 100 | tested with 3.6, 3.7 and PyPy3 101 | ```bash 102 | matlab -nodisplay -nosplash -nodesktop -r "run('mlb_reference_example.m');" 103 | ``` 104 | 105 | * 12 µs per option 106 | * 34 ms per surface 107 | 108 | 109 | Obviously, these values are per core (i5 4210U 1.7 GHz). 110 | 111 | 112 | ## Notes 113 | Good Python code reads like a novel. Right? So should math. 114 | I preferred short math-like variable names in this case. 115 | That makes the code less readable compared to other Python code 116 | but the docstrings should make up for the lack of readability. 117 | 118 | Originally, I left the camelCase function name and spelling in place but eventually got annoyed. 119 | > calcbsimpvol it is 120 | 121 | 122 | ## Code Origin 123 | 124 | * first thought of by Li (2006) (see References) 125 | * implemented and published by Mark Whirdy as MATLAB .m-code (see References) 126 | * numpyified from `.m` to `.py` by me 127 | 128 | 129 | ## Contact 130 | * email: [erkan.dem@pm.me](mailto:erkan.dem@pm.me) 131 | * documentation: [erkandem.github.io/calcbsimpvol/](https://erkandem.github.io/calcbsimpvol/) 132 | * source: [github.com/erkandem/calcbsimpvol](https://github.com/erkandem/calcbsimpvol) 133 | * issues: [github.com/erkandem/calcbsimpvol/issues](https://github.com/erkandem/calcbsimpvol/issues) 134 | 135 | ## ToDos 136 | * make the code compatible with `Python 2` 137 | * make it `PyPy` compatible 138 | 139 | 140 | 141 | ## References 142 | 1) Li, 2006, "You Don't Have to Bother Newton for Implied Volatility" 143 | 144 | [http://papers.ssrn.com/sol3/papers.cfm?abstract_id=952727](http://papers.ssrn.com/sol3/papers.cfm?abstract_id=952727) 145 | 146 | 2) MATLAB source code available at: 147 | 148 | [https://www.mathworks.com/matlabcentral/fileexchange/41473-calcbsimpvol-cp-p-s-k-t-r-q](https://www.mathworks.com/matlabcentral/fileexchange/41473-calcbsimpvol-cp-p-s-k-t-r-q) 149 | 150 | ## License 151 | The included Python code is licensed under `MIT` [License](https://github.com/calcbsimpvol/calcbsimpvol/LICENCE) 152 | 153 | The Code by Mark Whirdy is licensed under `MIT` [License](https://github.com/erkandem/calcbsimpvol/calcBSImpVol_mlab/LICENSE) 154 | 155 | The translation is not related or endorsed by the original author. 156 | -------------------------------------------------------------------------------- /calcbsimpvol/__init__.py: -------------------------------------------------------------------------------- 1 | from .src import calcbsimpvol -------------------------------------------------------------------------------- /calcbsimpvol/data/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Notes on Data Sets 3 | =================== 4 | While it is easy to make up some random data this will not result in 5 | something meaningful since BS is not linear. These data sets are obfuscated 6 | but have the structure real world data would have...if it were real data. 7 | 8 | Some notes on the keys of the JSON data files: 9 | 10 | cl_20171115.json 11 | ^^^^^^^^^^^^^^^^^^^ 12 | Data on crude oil with expiry on 15. Nov, 2017 13 | 14 | **d20161209**: Data of 09. Dec, 2016 15 | 16 | **S**: underlying price 17 | 18 | **cp**: call[+1] or put[-1] 19 | 20 | **K**: strike price 21 | 22 | **P**: option price 23 | 24 | **tau**: Time until expiry in years 25 | 26 | **moneyness**: Here: log(S/K) 27 | 28 | **q**: Here: fictional yield from reinvesting cash above margin threshold for the risk free rate 29 | 30 | **r**: risk free rate estimated from treasuries for that specific `time until expiry` 31 | 32 | **mlb_rational**: result obtained from `calcBsImpVol.m` with additional proprietary filtering, if needed 33 | 34 | **delta**: a raw delta estimate calculated from the iVol from `mlb_rational` 35 | 36 | 37 | 38 | reference_sample.json 39 | ^^^^^^^^^^^^^^^^^^^^^^ 40 | Data taken out of a data collection of a third-party vendor (indicated by (-#-)) and added columns from calculations 41 | 42 | **d20170921**: Data of 21. Sep, 2017 43 | 44 | **cp**: (-#-) 45 | 46 | **P**: (-#-) 47 | 48 | **S**: (-#-) 49 | 50 | **K**: (-#-) 51 | 52 | **tau**: calculated 53 | 54 | **r**: calculated 55 | 56 | **q**: trailing 12 month dividend yield ESTIMATE 57 | 58 | **py_rational**: iVol calculated obtained from calc_ivol.py 59 | 60 | **ref_iv_clean(-#-)**: iVol stated in the third-party reference supplied; set to NaN where ``ref_iv_is_interpolated`` was ``true`` 61 | 62 | **ref_iv_is_interpolated**: proprietary model based interpolation/extrapolation by third party vendor 63 | 64 | **mlb_blsimpv_clean**: iVol calculated from the built in function in MATLAB; set to NaN where ``ref_iv_is_interpolated`` was `true` 65 | 66 | **mlb_rational_clean**: iVol calculated from calcBsImpVol with additional proprietary filtering, if needed; set to NaN where ``ref_iv_is_interpolated`` was `true` 67 | 68 | **py_rational_clean**: raw data obtained from calc_ivol.py; set to NaN where ``ref_iv_is_interpolated`` was ``true`` 69 | 70 | 71 | spy_20190118.json: 72 | ^^^^^^^^^^^^^^^^^^^ 73 | SPY options data with expiry on 18. Jan, 2019 74 | 75 | **d20171226**: Data of 26. Dec, 2017 76 | 77 | **q**: trailing 12 month dividend yield ESTIMATE 78 | 79 | """ 80 | 81 | 82 | -------------------------------------------------------------------------------- /calcbsimpvol/data/cl_20171115.json.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/data/cl_20171115.json.zip -------------------------------------------------------------------------------- /calcbsimpvol/data/reference_sample.json.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/data/reference_sample.json.zip -------------------------------------------------------------------------------- /calcbsimpvol/data/spy_20190118.json.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/data/spy_20190118.json.zip -------------------------------------------------------------------------------- /calcbsimpvol/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = ../../../calcbsimpvol-docs 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /calcbsimpvol/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | pandoc -f markdown -t rst -o calcbsimpvol/docs/source/README.rst README.md 3 | ECHO %CD% 4 | pip uninstall --yes calcbsimpvol 5 | pip install . 6 | 7 | pushd %~dp0 8 | 9 | REM Command file for Sphinx documentation 10 | 11 | if "%SPHINXBUILD%" == "" ( 12 | set SPHINXBUILD=sphinx-build 13 | ) 14 | set SOURCEDIR=source 15 | set BUILDDIR=../../../calcbsimpvol-docs 16 | 17 | if "%1" == "" goto help 18 | 19 | %SPHINXBUILD% >NUL 2>NUL 20 | if errorlevel 9009 ( 21 | echo. 22 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 23 | echo.installed, then set the SPHINXBUILD environment variable to point 24 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 25 | echo.may add the Sphinx directory to PATH. 26 | echo. 27 | echo.If you don't have Sphinx installed, grab it from 28 | echo.http://sphinx-doc.org/ 29 | exit /b 1 30 | ) 31 | 32 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | goto end 34 | 35 | :help 36 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 37 | 38 | :end 39 | popd 40 | -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/README.rst: -------------------------------------------------------------------------------- 1 | |Build Status| |image1| |image2| |image3| 2 | 3 | calcbsimpvol 4 | ============ 5 | 6 | *Calculate Black-Scholes Implied Volatility - Vectorwise* 7 | 8 | -------------- 9 | 10 | - ``:)`` native python code 11 | - ``:)`` lightweight footprint 12 | - ``:)`` sample data included 13 | - ``:(`` not suited for single / low number of options 14 | - ``:(`` code reads un-pythonic 15 | - ``:(`` not yet thoroughly tested 16 | 17 | Getting started 18 | --------------- 19 | 20 | Requirements 21 | ~~~~~~~~~~~~ 22 | 23 | - Python 3.x (currently) or PyPy3 24 | - NumPy 25 | - SciPy 26 | - (MatPlotLib to visualize results in some examples) 27 | 28 | Installation 29 | ~~~~~~~~~~~~ 30 | 31 | While the code consists of single digit functions, I recommend using the 32 | ``pip install`` way to get the code. That way you would take advantage 33 | of bug fixes, updates, and possible extensions. 34 | 35 | .. code:: bash 36 | 37 | $ pip install calcbsimpvol 38 | 39 | Example 40 | ~~~~~~~ 41 | 42 | Pass your ``args`` bundled in a ``dict``. 43 | 44 | .. code:: python 45 | 46 | from calcbsimpvol import calcbsimpvol 47 | import numpy as np 48 | 49 | S = np.asarray(100) 50 | K_value = np.arange(40, 160, 25) 51 | K = np.ones((np.size(K_value), 1)) 52 | K[:, 0] = K_value 53 | tau_value = np.arange(0.25, 1.01, 0.25) 54 | tau = np.ones((np.size(tau_value), 1)) 55 | tau[:, 0] = tau_value 56 | r = np.asarray(0.01) 57 | q = np.asarray(0.03) 58 | cp = np.asarray(1) 59 | P = [[59.35, 34.41, 10.34, 0.50, 0.01], 60 | [58.71, 33.85, 10.99, 1.36, 0.14], 61 | [58.07, 33.35, 11.50, 2.12, 0.40], 62 | [57.44, 32.91, 11.90, 2.77, 0.70]] 63 | 64 | P = np.asarray(P) 65 | [K, tau] = np.meshgrid(K, tau) 66 | 67 | sigma = calcbsimpvol(dict(cp=cp, P=P, S=S, K=K, tau=tau, r=r, q=q)) 68 | print(sigma) 69 | 70 | # [[ nan, nan, 0.20709362, 0.21820954, 0.24188675], 71 | # [ nan, 0.22279836, 0.20240934, 0.21386148, 0.23738982], 72 | # [ nan, 0.22442837, 0.1987048 , 0.21063506, 0.23450013], 73 | # [ nan, 0.22188111, 0.19564657, 0.20798285, 0.23045406]] 74 | 75 | More usage examples are available in 76 | `example3.py `__ (additional 77 | sample data required which is available at `GitHub 78 | Repo `__ 79 | 80 | Performance 81 | ----------- 82 | 83 | :: 84 | 85 | Design a test. 86 | Get the results you want. 87 | 88 | - ``k_max = 10`` (default) 89 | - ``tolerance = 10E-12`` (default) 90 | - linear regression steps are commented out (default) 91 | 92 | .. code:: bash 93 | 94 | # assuming you did install it already 95 | git clone https://github.com/erkandem/calcbsimpvol.git 96 | cd calcbsimpvol 97 | python examples/example3.py --steps 100 --mode reference 98 | 99 | - 15 µs per option 100 | - 41 ms per surface 101 | 102 | tested with 3.6, 3.7 and PyPy3 103 | 104 | .. code:: bash 105 | 106 | matlab -nodisplay -nosplash -nodesktop -r "run('mlb_reference_example.m');" 107 | 108 | - 12 µs per option 109 | - 34 ms per surface 110 | 111 | Obviously, these values are per core (i5 4210U 1.7 GHz). 112 | 113 | Notes 114 | ----- 115 | 116 | Good Python code reads like a novel. Right? So should math. I preferred 117 | short math-like variable names in this case. That makes the code less 118 | readable compared to other Python code but the docstrings should make up 119 | for the lack of readability. 120 | 121 | Originally, I left the camelCase function name and spelling in place but 122 | eventually got annoyed. > calcbsimpvol it is 123 | 124 | Code Origin 125 | ----------- 126 | 127 | - first thought of by Li (2006) (see References) 128 | - implemented and published by Mark Whirdy as MATLAB .m-code (see 129 | References) 130 | - numpyified from ``.m`` to ``.py`` by me 131 | 132 | Contact 133 | ------- 134 | 135 | - email: erkan.dem@pm.me 136 | - documentation: 137 | `erkandem.github.io/calcbsimpvol/ `__ 138 | - source: 139 | `github.com/erkandem/calcbsimpvol `__ 140 | - issues: 141 | `github.com/erkandem/calcbsimpvol/issues `__ 142 | 143 | ToDos 144 | ----- 145 | 146 | - make the code compatible with ``Python 2`` 147 | - make it ``PyPy`` compatible 148 | 149 | References 150 | ---------- 151 | 152 | 1) Li, 2006, “You Don’t Have to Bother Newton for Implied Volatility” 153 | 154 | http://papers.ssrn.com/sol3/papers.cfm?abstract_id=952727 155 | 156 | 2) MATLAB source code available at: 157 | 158 | https://www.mathworks.com/matlabcentral/fileexchange/41473-calcbsimpvol-cp-p-s-k-t-r-q 159 | 160 | License 161 | ------- 162 | 163 | The included Python code is licensed under ``MIT`` 164 | `License `__ 165 | 166 | The Code by Mark Whirdy is licensed under ``MIT`` 167 | `License `__ 168 | 169 | The translation is not related or endorsed by the original author. 170 | 171 | .. |Build Status| image:: https://travis-ci.com/erkandem/calcbsimpvol.svg?token=EM8YQfR9wuLvQFQzBZ5o&branch=master 172 | :target: https://travis-ci.com/erkandem/calcbsimpvol 173 | .. |image1| image:: https://img.shields.io/badge/License-MIT-blue.svg 174 | .. |image2| image:: https://img.shields.io/badge/Python-3.4%20%7C%203.5%20%7C%203.6%20%7C%203.7%20%7C%203.8%20%7C%20PyPy3-blue.svg 175 | .. |image3| image:: https://img.shields.io/badge/PyPi-v1.14.0-blue.svg 176 | :target: https://pypi.org/project/calcbsimpvol/ 177 | -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/copybutton.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | /* Add a [>>>] button on the top-right corner of code samples to hide 3 | * the >>> and ... prompts and the output and thus make the code 4 | * copyable. */ 5 | var div = $('.highlight-python .highlight,' + 6 | '.highlight-python3 .highlight' 7 | ) 8 | var pre = div.find('pre'); 9 | 10 | // get the styles from the current theme 11 | pre.parent().parent().css('position', 'relative'); 12 | var hide_text = 'Hide the prompts and output'; 13 | var show_text = 'Show the prompts and output'; 14 | var border_width = pre.css('border-top-width'); 15 | var border_style = pre.css('border-top-style'); 16 | var border_color = pre.css('border-top-color'); 17 | var button_styles = { 18 | 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0', 19 | 'border-color': border_color, 'border-style': border_style, 20 | 'border-width': border_width, 'color': border_color, 'text-size': '75%', 21 | 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em', 22 | 'border-radius': '0 3px 0 0' 23 | } 24 | 25 | // create and add the button to all the code blocks that contain >>> 26 | div.each(function(index) { 27 | var jthis = $(this); 28 | if (jthis.find('.gp').length > 0) { 29 | var button = $('>>>'); 30 | button.css(button_styles) 31 | button.attr('title', hide_text); 32 | button.data('hidden', 'false'); 33 | jthis.prepend(button); 34 | } 35 | // tracebacks (.gt) contain bare text elements that need to be 36 | // wrapped in a span to work with .nextUntil() (see later) 37 | jthis.find('pre:has(.gt)').contents().filter(function() { 38 | return ((this.nodeType == 3) && (this.data.trim().length > 0)); 39 | }).wrap(''); 40 | }); 41 | 42 | // define the behavior of the button when it's clicked 43 | $('.copybutton').click(function(e){ 44 | e.preventDefault(); 45 | var button = $(this); 46 | if (button.data('hidden') === 'false') { 47 | // hide the code output 48 | button.parent().find('.go, .gp, .gt').hide(); 49 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden'); 50 | button.css('text-decoration', 'line-through'); 51 | button.attr('title', show_text); 52 | button.data('hidden', 'true'); 53 | } else { 54 | // show the code output 55 | button.parent().find('.go, .gp, .gt').show(); 56 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible'); 57 | button.css('text-decoration', 'none'); 58 | button.attr('title', hide_text); 59 | button.data('hidden', 'false'); 60 | } 61 | }); 62 | }); 63 | 64 | -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/calc_ivol_vs_reference_close_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/calc_ivol_vs_reference_close_up.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/calc_ivol_vs_reference_close_up_calls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/calc_ivol_vs_reference_close_up_calls.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/calc_ivol_vs_reference_close_up_puts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/calc_ivol_vs_reference_close_up_puts.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/mlb_blsimpv_vs_reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/mlb_blsimpv_vs_reference.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/mlb_builtin_vs_matrixwise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/mlb_builtin_vs_matrixwise.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/mlb_builtin_vs_matrixwise_cleaned_for_nan_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/mlb_builtin_vs_matrixwise_cleaned_for_nan_both.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/mlb_rational_vs_reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/mlb_rational_vs_reference.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/mlb_rational_vs_reference_cleaned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/mlb_rational_vs_reference_cleaned.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/python_vs_matlab_clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/python_vs_matlab_clean.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/_static/img/python_vs_reference_cleaned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/docs/source/_static/img/python_vs_reference_cleaned.png -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | --- 3 | 4 | .. autofunction:: src.calcbsimpvol.calcbsimpvol 5 | 6 | .. autofunction:: src.calcbsimpvol._fcnv 7 | 8 | .. autofunction:: src.calcbsimpvol._fcnN 9 | 10 | .. autofunction:: src.calcbsimpvol._fcnn 11 | 12 | -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sys 4 | sys.path.insert(0, os.path.abspath('../..')) 5 | from recommonmark.parser import CommonMarkParser 6 | 7 | source_suffix = ['.rst', '.md'] 8 | 9 | # -- Project information ----------------------------------------------------- 10 | project = 'calcbsimpvol' 11 | copyright = '2018, Erkan Demiralay' 12 | author = 'Erkan Demiralay' 13 | version = '1.14.0' 14 | release = '1.14.0' 15 | # -- General configuration --------------------------------------------------- 16 | 17 | # needs_sphinx = '1.0' 18 | extensions = [ 19 | 'sphinx.ext.coverage', 20 | 'sphinx.ext.viewcode', 21 | 'sphinx.ext.githubpages', 22 | 'sphinx.ext.autodoc', 23 | 'sphinx.ext.napoleon', 24 | 'sphinx_copybutton', 25 | ] 26 | 27 | templates_path = ['_templates'] 28 | source_suffix = ['.rst', '.md'] 29 | master_doc = 'index' 30 | 31 | language = None 32 | exclude_patterns = ['_bkp'] 33 | pygments_style = None 34 | 35 | source_parsers = { 36 | '.md': CommonMarkParser, 37 | } 38 | 39 | # -- Options for HTML output ------------------------------------------------- 40 | # html_theme = 'classic' 41 | html_theme = 'sphinx_rtd_theme' 42 | 43 | # html_theme_options = {} 44 | html_static_path = ['_static'] 45 | # html_sidebars = {} 46 | # -- Options for HTMLHelp output --------------------------------------------- 47 | 48 | # Output file base name for HTML help builder. 49 | htmlhelp_basename = 'calcbsimpvol' 50 | 51 | # -- Options for LaTeX output ------------------------------------------------ 52 | latex_elements = { 53 | # The paper size ('letterpaper' or 'a4paper'). 54 | # 55 | # 'papersize': 'letterpaper', 56 | 57 | # The font size ('10pt', '11pt' or '12pt'). 58 | # 59 | # 'pointsize': '10pt', 60 | 61 | # Additional stuff for the LaTeX preamble. 62 | # 63 | # 'preamble': '', 64 | 65 | # Latex figure (float) alignment 66 | # 67 | # 'figure_align': 'htbp', 68 | } 69 | 70 | # Grouping the document tree into LaTeX files. List of tuples 71 | # (source start file, target name, title, 72 | # author, documentclass [howto, manual, or own class]). 73 | latex_documents = [ 74 | (master_doc, 'calcbsimpvol.tex', 'calcbsimpvol Documentation', 75 | 'Erkan Demiralay', 'manual'), 76 | ] 77 | 78 | 79 | # -- Options for manual page output ------------------------------------------ 80 | 81 | # One entry per manual page. List of tuples 82 | # (source start file, name, description, authors, manual section). 83 | man_pages = [ 84 | (master_doc, 'calcbsimpvol', 'calcbsimpvol Documentation', 85 | [author], 1) 86 | ] 87 | 88 | 89 | # -- Options for Texinfo output ---------------------------------------------- 90 | 91 | # Grouping the document tree into Texinfo files. List of tuples 92 | # (source start file, target name, title, author, 93 | # dir menu entry, description, category) 94 | texinfo_documents = [ 95 | (master_doc, 'calcBSImpVol', 'calcBSImpVol Documentation', 96 | author, 'calcBSImpVol', 'One line description of project.', 97 | 'Miscellaneous'), 98 | ] 99 | 100 | 101 | # -- Options for Epub output ------------------------------------------------- 102 | 103 | # Bibliographic Dublin Core info. 104 | epub_title = project 105 | 106 | # The unique identifier of the text. This can be a ISBN number 107 | # or the project homepage. 108 | # 109 | # epub_identifier = '' 110 | 111 | # A unique identification for the text. 112 | # 113 | # epub_uid = '' 114 | 115 | # A list of files that should not be packed into the epub file. 116 | epub_exclude_files = ['search.html'] 117 | 118 | 119 | # -- Extension configuration ------------------------------------------------- -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/data.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. automodule:: data -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. calcbsimpvol documentation master file, created by 2 | sphinx-quickstart on Tue Dec 18 15:52:53 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: README.rst 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :caption: Contents: 12 | 13 | README 14 | api 15 | results 16 | data 17 | license 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ------- 3 | 4 | .. include:: ../../../LICENSE 5 | -------------------------------------------------------------------------------- /calcbsimpvol/docs/source/results.rst: -------------------------------------------------------------------------------- 1 | Results and (some) Discussion. 2 | ==================================== 3 | 4 | It was important to me to port the behavior of the ``calcBsImpVol``. 5 | An easy way to get the results visualized without using pytest and simialer 6 | great tools is a unity plot. 7 | 8 | The matrices/arrays which contain the values to be compared are flattened 9 | to row vectors and scattered against each other. Any significant deviation will then 10 | stand out from the line created by ``y = x`` 11 | 12 | Couple of things which are left to point out in relation to Fig. 1, 2, 3 and 4: 13 | 14 | Since I can't know the assumptions made for the risk free rate and dividend yield 15 | I had to come up with somewhat reasonable assumptions on my own. 16 | As such, I picked the T-Notes yields corresponding with the options time until expiry as the risk free rate and the 17 | trailing 12m dividend payout as the dividend yield. 18 | 19 | Fig 4.1, 4.2 and 4.3 illustrate that deviation is primarily an issue for calls. 20 | 21 | bsimpv, calc_ivol and calcBsImpVol use closed form formulas designed to return 22 | the implied volatility of european options. The reference method however uses 23 | a numerical approach. american style options should generally be more 24 | expensive or equally priced to european style options. 25 | 26 | A numerical approach seems to express that better for calls. Hence the 27 | implied volatility is underestimated by closed form approach. 28 | 29 | About he puts with ``iVol ~ 0.5``? Tell us. 30 | 31 | 32 | ``reference`` vs ``calcBSImvol()``, ``bsimpv()`` and ``calcbsimpvol()`` 33 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 34 | 35 | .. figure:: _static/img/mlb_blsimpv_vs_reference.png 36 | :width: 350px 37 | :align: center 38 | :height: 262px 39 | :alt: mlb_blsimpv_vs_reference 40 | :figclass: align-center 41 | 42 | Fig. 1 - reference vs results of MATLAB builtin ``blsimpv()`` 43 | 44 | .. figure:: _static/img/mlb_rational_vs_reference.png 45 | :width: 350px 46 | :align: center 47 | :height: 262px 48 | :alt: mlb_rational_vs_reference 49 | :figclass: align-center 50 | 51 | Fig. 2 - reference vs results of MATLAB ``calcBSImpVol()`` 52 | 53 | 54 | .. figure:: _static/img/mlb_rational_vs_reference_cleaned.png 55 | :width: 350px 56 | :align: center 57 | :height: 262px 58 | :alt: mlb_rational_vs_reference_cleaned 59 | :figclass: align-center 60 | 61 | Fig. 3 - reference vs Python ``calcBSImpVol()`` - adjusted for ``NaN`` 62 | 63 | 64 | 65 | .. figure:: _static/img/python_vs_reference_cleaned.png 66 | :width: 350px 67 | :align: center 68 | :height: 262px 69 | :alt: python_vs_reference_cleaned 70 | :figclass: align-center 71 | 72 | Fig. 4 - reference vs Python ``calcbsimpvol()`` 73 | 74 | 75 | .. figure:: _static/img/python_vs_reference_cleaned.png 76 | :width: 350px 77 | :align: center 78 | :height: 262px 79 | :alt: python_vs_reference_cleaned 80 | :figclass: align-center 81 | 82 | Fig. 4.1 - reference vs Python ``calcbsimpvol()`` - close up 83 | 84 | .. figure:: _static/img/calc_ivol_vs_reference_close_up_calls.png 85 | :width: 350px 86 | :align: center 87 | :height: 262px 88 | :alt: calc_ivol_vs_reference_close_up_calls 89 | :figclass: align-center 90 | 91 | Fig. 4.2 - reference vs Python ``calcbsimpvol()`` - only calls 92 | 93 | .. figure:: _static/img/calc_ivol_vs_reference_close_up_puts.png 94 | :width: 350px 95 | :align: center 96 | :height: 262px 97 | :alt: calc_ivol_vs_reference_close_up_puts 98 | :figclass: align-center 99 | 100 | Fig. 4.3 - reference vs Python ``calc_ivol()`` - only puts 101 | 102 | 103 | ``calcBSImvol`` vs ``bsimpv()`` vs ``calc_ivol`` 104 | """""""""""""""""""""""""""""""""""""""""""""""" 105 | Fig. 5 shows that ``calcBSImvol()`` offers an extended calculation range compared to 106 | ``bsimpv()``. The ``NaN`` values returned from ``bsimpv()`` were set to ``zero`` in order to create Fig. 5. 107 | 108 | Removing the "silent evindence" of ``NaN``s and ``zero``s returns Fig. 6. 109 | 110 | Fig. 7 completes the picture. 111 | 112 | .. figure:: _static/img/mlb_builtin_vs_matrixwise.png 113 | :width: 350px 114 | :align: center 115 | :height: 262px 116 | :alt: mlb_builtin_vs_matrixwise 117 | :figclass: align-center 118 | 119 | Fig. 5 - MATLAB builtin ``blsimpv()`` vs MATLAB ``calcBSImpVol()`` 120 | 121 | 122 | 123 | .. figure:: _static/img/mlb_builtin_vs_matrixwise_cleaned_for_nan_both.png 124 | :width: 350px 125 | :align: center 126 | :height: 262px 127 | :alt: mlb_builtin_vs_matrixwise_cleaned_for_nan_both 128 | :figclass: align-center 129 | 130 | Fig. 6 - MATLAB builtin ``blsimpv()`` vs MATLAB ``calcBSImpVol()`` - adjusted for ``NaN`` 131 | 132 | 133 | 134 | .. figure:: _static/img/python_vs_matlab_clean.png 135 | :width: 350px 136 | :align: center 137 | :height: 262px 138 | :alt: python_vs_matlab_clean 139 | :figclass: align-center 140 | 141 | Fig. 7 - Python ``calcbsimpvol()`` vs MATLAB ``calcBSImVol()`` 142 | 143 | 144 | -------------------------------------------------------------------------------- /calcbsimpvol/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/examples/__init__.py -------------------------------------------------------------------------------- /calcbsimpvol/examples/calcBSImpVol_mlab/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Mark Whirdy 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /calcbsimpvol/examples/calcBSImpVol_mlab/calcBSImpVol.m: -------------------------------------------------------------------------------- 1 | function [sigma,C] = calcBSImpVol(cp,P,S,K,T,r,q) 2 | % 3 | % [sigma,C] = calcBSImpVol(cp,P,S,K,T,r,q) 4 | % 5 | % Calculates Black-Scholes Implied Volatility Surface for an Option Price Matrix. 6 | % Uses Li's Rational Function Approximator for the Initial Estimate, followed by 7 | % 3rd-Order Householder's Root Finder (i.e. using vega,vomma & ultima) for greater 8 | % convergence rate and wider domain-of-convergence relative to Newton-Raphson. Both 9 | % Li's Approximator and the Root Finder are calculated matrix-wise (i.e. 10 | % fully vectorized) for increased efficiency. 11 | % 12 | % 13 | % Input Parameters 14 | % cp Call[+1],Put[-1] [m x n],[1 x 1] 15 | % P Option Price Matrix [m x n] 16 | % S Underlying Price [1 x 1] 17 | % K Strike Price [m x n] 18 | % T Time to Expiry [m x n] 19 | % r Continuous Risk-Free Rate [m x n],[1 x 1] 20 | % q Continuos Div Yield [m x n],[1 x 1] 21 | % 22 | % Output Parameters 23 | % sigma Implied Volatility [m x n] 24 | % C Convergence Flag [m x n] 25 | % 26 | % 27 | % 28 | % Example 1 29 | % S = 100; K = (40:25:160)'; T = (0.25:0.25:1)'; % Define Key Variables 30 | % r = 0.01; q = 0.03; 31 | % cp = 1; % i.e. call 32 | % P = ... 33 | % [[59.3526805861312,34.4154741312210,10.3406451776045,0.501199199160055,0.0101623685145268;]; 34 | % [58.7107005379958,33.8563481863964,10.9917759513981,1.36915029885860,0.143324063090580;]; 35 | % [58.0742593358310,33.3567195106962,11.5012247391034,2.12859686975881,0.400045353619436;]; 36 | % [57.4444414750070,32.9126689586500,11.9027988146544,2.77274776123341,0.708059729236718;];]; 37 | % [mK,mT] = meshgrid(K,T); 38 | % [sigma,C] = calcBSImpVol(cp,P,S,mK,mT,r,q); 39 | % mesh(mK,mT,sigma); 40 | % 41 | % Example 2 42 | % S = 100; K = (40:25:160)'; T = (0.25:0.25:1)'; % Define Key Variables 43 | % cp = [ones(4,3),-ones(4,2)]; % [Calls[4,3],Puts[4,2]] 44 | % R = 0.01*repmat([1.15;1.10;1.05;1],1,5); % 45 | % Q = 0.03*repmat([1.3;1.2;1.1;1],1,5); 46 | % P = ... 47 | % [[59.1445725607811,34.2167401269277,10.1798771553458,16.1224863211251,40.5779719086946]; 48 | % [58.4355054500906,33.5945826994415,10.7977275764632,17.4776751735401,41.1533978186314]; 49 | % [57.8694061672804,33.1636044111551,11.3636963648521,18.6294544130139,41.7369813312724]; 50 | % [57.4444414750070,32.9126689586500,11.9027988146694,19.5839252875422,42.2704830992694]]; 51 | % [mK,mT] = meshgrid(K,T); 52 | % [sigma,C] = calcBSImpVol(cp,P,S,mK,mT,R,Q); 53 | % mesh(mK,mT,sigma); 54 | % hold on; scatter3(mK(:),mT(:),sigma(:),60,[0,0,0],'filled'); hold off 55 | % xlabel('Strike'); ylabel('Expiry'); zlabel('Volatility'); 56 | % 57 | % References: 58 | % 1) Li, 2006, "You Don't Have to Bother Newton for Implied Volatility" 59 | % http://papers.ssrn.com/sol3/papers.cfm?abstract_id=952727 60 | % 2) http://en.wikipedia.org/wiki/Householder's_method 61 | % 3) http://en.wikipedia.org/wiki/Greeks_(finance) 62 | % 63 | 64 | 65 | %% APPLY LI's RATIONAL-FUNCTION APPROXIMATOR 66 | 67 | [g,h] = size(P); 68 | if isscalar(r); r = r*ones(g,h); end 69 | if isscalar(q); q = q*ones(g,h); end 70 | if isscalar(cp); cp = cp*ones(g,h); end 71 | 72 | p = [-0.969271876255; 0.097428338274; 1.750081126685]; 73 | 74 | m = ones(1,1,14); 75 | m(:) = [... 76 | 6.268456292246; 77 | -6.284840445036; 78 | 30.068281276567; 79 | -11.780036995036; 80 | -2.310966989723; 81 | -11.473184324152; 82 | -230.101682610568; 83 | 86.127219899668; 84 | 3.730181294225; 85 | -13.954993561151; 86 | 261.950288864225; 87 | 20.090690444187; 88 | -50.117067019539; 89 | 13.723711519422]; 90 | m = m(ones(g,1),ones(1,h),:); % Repmat to size [g,h] 91 | 92 | n = ones(1,1,14); 93 | n(:) = [... 94 | -0.068098378725; 95 | 0.440639436211; 96 | -0.263473754689; 97 | -5.792537721792; 98 | -5.267481008429; 99 | 4.714393825758; 100 | 3.529944137559; 101 | -23.636495876611; 102 | -9.020361771283; 103 | 14.749084301452; 104 | -32.570660102526; 105 | 76.398155779133; 106 | 41.855161781749; 107 | -12.150611865704]; 108 | n = n(ones(g,1),ones(1,h),:); % Repmat to size [g,h] 109 | 110 | i = ones(1,1,14); 111 | i(:) = [0,1,0,1,2,0,1,2,3,0,1,2,3,4]; 112 | i = i(ones(g,1),ones(1,h),:); % Repmat to size [g,h] 113 | j = ones(1,1,14); 114 | j(:) = [1,0,2,1,0,3,2,1,0,4,3,2,1,0]; 115 | j = j(ones(g,1),ones(1,h),:); % Repmat to size [g,h] 116 | 117 | 118 | x = log(S.*exp((r-q).*(T))./K); % Calculate Normalized Moneyness Measure 119 | P(cp==-1) = P(cp==-1) + S.*exp(-q(cp==-1).*T(cp==-1)) - K(cp==-1).*exp(-r(cp==-1).*T(cp==-1)); % Convert Put to Call by Parity Relation 120 | P = max(P, 0); % |ed ensure a positive price 121 | 122 | c = P./(S.*exp(-q.*(T))); % Normalized Call Price 123 | 124 | x = x(:,:,ones(1,14)); % Repmat to 3d size 14 125 | c = c(:,:,ones(1,14)); % Repmat to 3d size 14 126 | 127 | % Rational Function - Eqn(19) of Li 2006 128 | fcnv = @(p,m,n,i,j,x,c)(p(1).*x(:,:,1) + p(2).*sqrt(c(:,:,1)) + p(3).*c(:,:,1) + (sum(n.*((x.^i).*(sqrt(c).^j)),3))./(1 + sum(m.*((x.^i).*(sqrt(c).^j)),3))); 129 | v1_fixed = fcnv(p,m,n,i,j,x,max(c, 0)); % D- Domain (x<=-1) |ed added max(..., 0) 130 | v2_fixed = fcnv(p,m,n,i,j,-x,max(exp(x).*c + 1 -exp(x), 0)); % Reflection for D+ Domain (x>1) |ed added max(..., 0) 131 | v = zeros(g,h); v(x(:,:,1)<=0)=v1_fixed(x(:,:,1)<=0); v(x(:,:,1)>0)=v2_fixed(x(:,:,1)>0); 132 | 133 | % Domain-of-Approximation is x={-0.5,+0.5},v={0,1},x/v={-2,2} 134 | domainFilter = x(:,:,1)>=-0.5 & x(:,:,1)<=0.5 & v > 0 & v <1 & (x(:,:,1)./v)<=2 & (x(:,:,1)./v)>=-2; 135 | 136 | sigma = v./sqrt(T); % v = sigma.*(sqrt(T)); 137 | sigma(~domainFilter) = 0.8; % use 0.8 arbtrarily as best vol-guess for out-of-domain points 138 | 139 | %% HOUSEHOLDER ROOT-FINDER FOR INCREASED CONVERGENCE 140 | 141 | d1fcn = @(sig,C)((log(S./K(C)) + (r(C)-q(C)+sig(C).^2*0.5).*(T(C)))./(sig(C).*sqrt(T(C)))); 142 | d2fcn = @(sig,C)((log(S./K(C)) + (r(C)-q(C)-sig(C).^2*0.5).*(T(C)))./(sig(C).*sqrt(T(C)))); 143 | callfcn = @(sig,C)( +exp(-q(C).*T(C)).*S.*fcnN(d1fcn(sig,C)) - exp(-r(C).*T(C)).*K(C).*fcnN(d2fcn(sig,C)) ); 144 | 145 | vegafcn = @(sig,C)(S.*exp(-q(C).*(T(C))).*fcnn(d1fcn(sig,C)).*(sqrt(T(C)))); 146 | vommafcn = @(sig,C)(S.*exp(-q(C).*(T(C))).*fcnn(d1fcn(sig,C)).*(sqrt(T(C))).*d1fcn(sig,C).*d2fcn(sig,C)./sig(C)); 147 | ultimafcn = @(sig,C)(-S.*exp(-q(C).*(T(C))).*fcnn(d1fcn(sig,C)).*(sqrt(T(C))).*(d1fcn(sig,C).*d2fcn(sig,C).*(1-d1fcn(sig,C).*d2fcn(sig,C))+d1fcn(sig,C).^2+d2fcn(sig,C).^2)./(sig(C).^2)); 148 | 149 | tolMat=1e-12; 150 | k_max = 10; % 10 Householder Iterations 151 | objfcn = @(sig,C)(P(C) - callfcn(sig,C)); 152 | 153 | C = true(size(P(:))); err = objfcn(sigma,C); % calculate initial error 154 | C = abs(err)>tolMat; % Convergence Matrix 155 | 156 | k = 1; % Initialize Count 157 | while any(C) && k<=k_max % Iterate until sooner of Convergence or Count-limit 158 | 159 | % Calculate Derivatives (Greeks) 160 | vega = vegafcn(sigma,C); %f'(x_n) 161 | vomma = vommafcn(sigma,C); %f''(x_n) 162 | ultima = ultimafcn(sigma,C); %f'''(x_n) 163 | 164 | % % Newton Raphson Method x_n+1 = x_n + f(x_n)/f'(x_n) 165 | % sigma = sigma + (err(C)./vega) ; 166 | 167 | % % Halley Method x_n+1 = x_n - f(x_n)/( f'(x_n) - f(x_n)*f''(x_n)/2*f'(x_n)) 168 | % sigma = sigma - err(C)./(-vega-(-err(C).*vomma./(-2.*vega))); 169 | 170 | % Householder Method x_n+1 = x_n - f(x_n)/( f'(x_n) - f(x_n)*f''(x_n)/2*f'(x_n)) 171 | sigma(C) = sigma(C) - (6.*err(C).*vega.^2 + 3.*err(C).^2.*vomma)./(-6.*vega.^3 - 6.*err(C).*vega.*vomma - err(C).^2.*ultima); 172 | 173 | % Update Error 174 | err(C) = objfcn(sigma,C); % 175 | 176 | % Ascertain Convergence to Tolerance 177 | C = abs(err)>tolMat; % Convergence Matrix 178 | 179 | % Increment Count 180 | k = k + 1; 181 | end 182 | 183 | sigma(C) = NaN; % any remaining sigma are not worth calculating 184 | 185 | 186 | end 187 | 188 | 189 | %% Gaussian Subfunctions 190 | 191 | function p=fcnN(x) 192 | p=0.5*(1.+erf(x./sqrt(2))); 193 | end 194 | % 195 | function p=fcnn(x) 196 | p=exp(-0.5*x.^2)./sqrt(2*pi); 197 | end -------------------------------------------------------------------------------- /calcbsimpvol/examples/example1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from calcbsimpvol import calcbsimpvol 3 | 4 | 5 | def calcbsimpvol_example_1(verbose=False): 6 | S = np.asarray(100) 7 | K_value = np.arange(40, 160, 25) 8 | K = np.ones((np.size(K_value), 1)) 9 | K[:, 0] = K_value 10 | tau_value = np.arange(0.25, 1.01, 0.25) 11 | tau = np.ones((np.size(tau_value), 1)) 12 | tau[:, 0] = tau_value 13 | r = np.asarray(0.01) 14 | q = np.asarray(0.03) 15 | cp = np.asarray(1) 16 | P = [[59.35, 34.41, 10.34, 0.50, 0.01], 17 | [58.71, 33.85, 10.99, 1.36, 0.14], 18 | [58.07, 33.35, 11.50, 2.12, 0.40], 19 | [57.44, 32.91, 11.90, 2.77, 0.70]] 20 | 21 | P = np.asarray(P) 22 | [K, tau] = np.meshgrid(K, tau) 23 | 24 | sigma = calcbsimpvol(dict(cp=cp, P=P, S=S, K=K, tau=tau, r=r, q=q)) 25 | if verbose: 26 | print(sigma) 27 | return sigma 28 | 29 | 30 | if __name__ == '__main__': 31 | result = calcbsimpvol_example_1(verbose=True) 32 | -------------------------------------------------------------------------------- /calcbsimpvol/examples/example2.py: -------------------------------------------------------------------------------- 1 | from calcbsimpvol import calcbsimpvol 2 | import numpy as np 3 | 4 | 5 | def calcbsimpvol_example_2(verbose=False): 6 | P = [[59.14, 34.21, 10.17, 16.12, 40.58], 7 | [58.43, 33.59, 10.79, 17.47, 41.15], 8 | [57.87, 33.16, 11.363, 18.63, 41.74], 9 | [57.44, 32.91, 11.90, 19.58, 42.27]] 10 | P = np.asarray(P) 11 | S = np.asarray(100) 12 | K = np.arange(40, 160, 25) 13 | tau = np.arange(0.25, 1.01, 0.25) 14 | K, tau = np.meshgrid(K, tau) 15 | cp = np.hstack((np.ones((4, 3)), -1 * np.ones((4, 2)))) # [Calls[4, 3], Puts[4, 2]] 16 | 17 | r = 0.01 * np.ones((4, 5)) * np.asarray([1.15, 1.10, 1.05, 1]).reshape((4, 1)) 18 | q = 0.03 * np.ones((4, 5)) * np.asarray([1.3, 1.2, 1.1, 1]).reshape((4, 1)) 19 | 20 | sigma = calcbsimpvol(dict(cp=cp, P=P, S=S, K=K, tau=tau, r=r, q=q)) 21 | if verbose: 22 | print(sigma) 23 | return sigma 24 | 25 | 26 | if __name__ == '__main__': 27 | result = calcbsimpvol_example_2(verbose=True) 28 | -------------------------------------------------------------------------------- /calcbsimpvol/examples/example3.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from time import time 4 | import numpy as np 5 | import warnings 6 | warnings.filterwarnings("ignore") 7 | import optparse 8 | import zipfile 9 | try: 10 | import matplotlib.pyplot as plt 11 | from mpl_toolkits.mplot3d import Axes3D # used for 3D plotting 12 | except ImportWarning: 13 | print('Could not import matplotlib - plots won\'t be created') 14 | 15 | # assumes that the package was installed in currently used environment 16 | from calcbsimpvol import calcbsimpvol 17 | 18 | 19 | def scatter3d(x, y, z, x_label=None, y_label=None, z_label=None): 20 | fig = plt.figure() 21 | ax = fig.add_subplot(111, projection='3d') 22 | ax.scatter(x, y, z) 23 | ax.set_xlabel(x_label) 24 | ax.set_ylabel(y_label) 25 | ax.set_zlabel(z_label) 26 | 27 | plt.show() 28 | return 29 | 30 | 31 | def scatter2d(x, y, x_key=None, y_key=None, x_label=None, y_label=None): 32 | """convenience""" 33 | 34 | if x_label is None: 35 | x_label = x_key 36 | if y_label is None: 37 | y_label = y_key 38 | 39 | fig = plt.figure() 40 | ax = fig.gca() 41 | ax.scatter(x, y) 42 | box = [ax.get_ylim()[0], ax.get_ylim()[1], ax.get_xlim()[0], ax.get_xlim()[1]] 43 | new_size = [np.min(box), np.max(box)] 44 | ax.plot(new_size, new_size,) 45 | ax.set_xlim(new_size) 46 | ax.set_ylim(new_size) 47 | ax.set_xlabel(x_label) 48 | ax.set_ylabel(y_label) 49 | plt.show() 50 | 51 | 52 | def load_data(file_path): 53 | with zipfile.ZipFile(file_path, 'r') as zf: 54 | file_names = zf.namelist() 55 | if len(file_names) != 1: 56 | raise ValueError('expected archive to have only one file') 57 | if file_names[0] not in file_path: 58 | raise ValueError('expected file name to be part of the archive name') 59 | data_string = zf.read(file_names[0]) 60 | return data_string 61 | 62 | 63 | def calc(file_path, steps=1, do_return=False): 64 | data_string = load_data(file_path) 65 | data = json.loads(data_string) 66 | container = dict() 67 | for day in data: 68 | m = data[day] 69 | c = dict() 70 | c['cp'] = np.asarray(m['cp']) 71 | c['P'] = np.asarray(m['P']) 72 | c['S'] = np.asarray(m['S'][0]) 73 | c['K'] = np.asarray(m['K']) 74 | c['tau'] = np.asarray(m['tau']) 75 | c['r'] = np.asarray(m['r']) 76 | c['q'] = np.asarray(m['q']) 77 | 78 | feed_keys = ['cp', 'P', 'K', 'tau', 'r', 'q'] 79 | for key in feed_keys: 80 | c[key] = np.reshape(c[key], (np.size(c[key]), 1)) 81 | container[day] = c.copy() 82 | 83 | elapsed = dict() 84 | array_size = dict() 85 | 86 | for _step in range(steps): 87 | elapsed[_step] = dict() 88 | array_size[_step] = dict() 89 | for day in container: 90 | t_zero = time() 91 | if _step == 0: 92 | container[day]['py_rational'] = calcbsimpvol(container[day]) 93 | else: 94 | calcbsimpvol(container[day]) 95 | elapsed[_step][day] = time() - t_zero 96 | array_size[_step][day] = np.size(container[day]['cp']) 97 | 98 | _days = len(container.keys()) 99 | _options_per_step = np.sum([array_size[0][day] for day in array_size[0]]) 100 | _average_options_per_day = _options_per_step/_days 101 | _total_elapsed_array = [0] * steps 102 | for _step in range(steps): 103 | _total_elapsed_array[_step] = np.sum([elapsed[_step][day] for day in elapsed[_step]]) 104 | 105 | _total_elapsed = np.sum(_total_elapsed_array) 106 | _min = np.min(_total_elapsed_array) # s 107 | 108 | print('steps {}'.format(steps)) 109 | print('days per step: {}'.format(_days)) 110 | print('total elapsed time: {} ms'.format(np.round(_total_elapsed * 1000))) 111 | print('total options: {} '.format(_options_per_step * steps)) 112 | print('(best) average time per option: {} µs'.format(np.round((_min / _options_per_step) * 1000 * 1000 ))) 113 | print('') 114 | print('options per step: {}'.format(_options_per_step)) 115 | print('minimum elapsed time for 1 step: {} ms'.format(np.round(_min * 1000))) 116 | print('') 117 | print('average time per option: {} µs'.format(np.round((_total_elapsed/(steps * _options_per_step)) * 1000 * 1000))) 118 | if do_return: 119 | return container 120 | 121 | 122 | def main(file_path, steps): 123 | results = calc(file_path=file_path, steps=steps, do_return=True) 124 | reference_data = json.loads(load_data(file_path)) 125 | 126 | # 'reference' sample has only one single day (complete surface) 127 | # but 'cl' and 'spy' have multiple days for a single expiry 128 | if p.mode == 'reference': 129 | day_to_plot = (list(results.keys()))[0] # there is one day of data 130 | else: 131 | day_to_plot = (list(results.keys()))[5] # pick a day =) 132 | 133 | if p.mode == 'reference': 134 | selector = reference_data[day_to_plot]['cp'] == np.asarray(1) # compare only calls, `-1` for puts 135 | try: 136 | scatter2d(x=np.asarray(reference_data[day_to_plot]['ref_iv_clean'])[selector], 137 | y=np.asarray(results[day_to_plot]['py_rational'])[selector], 138 | x_key='ref_iv_clean', 139 | y_key='py_rational') 140 | except NameError: 141 | print('Skipping plot part - could not executes `scatter2d` in example3.') 142 | m = np.log(results[day_to_plot]['S'] / results[day_to_plot]['K']) 143 | try: 144 | scatter3d(x=m, 145 | y=results[day_to_plot]['tau'], 146 | z=results[day_to_plot]['py_rational'], 147 | x_label='log(F/K)', y_label='tau = T - t_0', z_label='sigma') 148 | except NameError: 149 | print('Skipping plot part - could not executes `scatter3d` in example3.') 150 | 151 | # next plot is note supposed to be used for quality measurement 152 | # it was simply a way to check visually whether the Python translation correlates 153 | # with the MATLAB implementation 154 | else: 155 | try: 156 | scatter2d(x=reference_data[day_to_plot]['mlb_rational'], 157 | y=results[day_to_plot]['py_rational'], 158 | x_key='mlb_rational', 159 | y_key='py_rational') 160 | except NameError: 161 | print('Skipping plot part - could not executes `scatter3d` in example3.') 162 | 163 | 164 | if __name__ == '__main__': 165 | parser = optparse.OptionParser() 166 | parser.add_option( 167 | '-m', '--mode', 168 | action='store', 169 | dest='mode', 170 | help='select a mode to run a test with the included data. ie: spy ', 171 | default='reference' 172 | ) 173 | 174 | parser.add_option( 175 | '-s', '--steps', 176 | action='store', dest='steps', 177 | help='run it how often? default: 100', 178 | default=100, 179 | type=int 180 | ) 181 | p, args = parser.parse_args() 182 | if p.mode == 'reference': 183 | file_path = os.path.join('..', 'data', 'reference_sample.json.zip') 184 | # these have reference data, however the data is not from a third party 185 | elif p.mode == 'spy': 186 | file_path = os.path.join('..', 'data', 'spy_20190118.json.zip') 187 | elif p.mode == 'cl': 188 | file_path = os.path.join('..', 'data', 'cl_20171115.json.zip') 189 | else: 190 | raise ValueError('must be run with an argument (`reference`, `spy`, `cl`)') 191 | 192 | main(file_path=file_path, steps=p.steps) 193 | -------------------------------------------------------------------------------- /calcbsimpvol/examples/mlb_reference_example.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/examples/mlb_reference_example.m -------------------------------------------------------------------------------- /calcbsimpvol/examples/runex.m: -------------------------------------------------------------------------------- 1 | % This is MATLAB script to run similiar examples in both MATLAB and Python 2 | % The Examples are derived from the original MATLAB .m-code distribution of the function/package 3 | % Having said that, this file is only of value if you have a both interpreters installed 4 | % and run this particular script from within MATLAB 5 | % 6 | % depending on which distribution of python was installed and 7 | % how it was installed (think of symlinks in linux dists) 8 | % try to replace 'python' with 'python3', 'python2', 'python_xy' 9 | % within ``system(sprintf('python %s', file_path))`` 10 | 11 | %% Example 1 12 | root_path = pwd(); 13 | addpath(fullfile(root_path, 'calcBSImpVol_mlab')) 14 | S = 100; K = (40:25:160)'; T = (0.25:0.25:1)'; % Define Key Variables 15 | r = 0.01; q = 0.03; 16 | cp = 1; % i.e. call 17 | P = [[59.35, 34.41, 10.34, 0.50, 0.01] 18 | [58.71, 33.85, 10.99, 1.36, 0.14] 19 | [58.07, 33.35, 11.50, 2.12, 0.40] 20 | [57.44, 32.91, 11.90, 2.77, 0.70]]; 21 | 22 | [mK, mT] = meshgrid(K, T); 23 | [sigma, ~] = calcBSImpVol(cp, P, S, mK, mT, r, q); 24 | disp(sigma) 25 | 26 | % MLB 27 | % NaN NaN 0.2071 0.2182 0.2419 28 | % NaN 0.2228 0.2024 0.2139 0.2374 29 | % NaN 0.2244 NaN 0.2106 0.2345 30 | % NaN 0.2219 0.1956 0.2080 0.2305 31 | 32 | 33 | file_path = fullfile(root_path, 'example1.py'); 34 | system(sprintf('python3 %s', file_path)) 35 | 36 | % python 37 | %[[ nan nan 0.20709362 0.21820954 0.24188675] 38 | % [ nan 0.22279836 0.20240934 0.21386148 0.23738982] 39 | % [ nan 0.22442837 0.1987048 0.21063506 0.23450013] 40 | % [ nan 0.22188111 0.19564657 0.20798285 0.23045406]] 41 | 42 | %% Example 2 43 | root_path = pwd(); 44 | addpath(fullfile(root_path, 'calcBSImpVol_mlab')) 45 | 46 | S = 100; K = (40:25:160)'; T = (0.25:0.25:1)'; % Define Key Variables 47 | cp = [ones(4, 3),-ones(4, 2)]; % [Calls[4, 3], Puts[4, 2]] 48 | R = 0.01 * repmat([1.15; 1.10; 1.05; 1], 1, 5); % 49 | Q = 0.03 * repmat([1.3; 1.2; 1.1; 1], 1, 5); 50 | P = [[59.14, 34.21, 10.17, 16.12, 40.58] 51 | [58.43, 33.59, 10.79, 17.47, 41.15] 52 | [57.87, 33.16, 11.363, 18.63, 41.74] 53 | [57.44, 32.91, 11.90, 19.58, 42.27]]; 54 | [mK, mT] = meshgrid(K,T); 55 | [sigma, ~] = calcBSImpVol(cp, P, S, mK, mT, R, Q); 56 | disp(sigma) 57 | 58 | % MLB 59 | % NaN NaN 0.2063 0.2181 0.2467 60 | % NaN 0.2257 0.2021 0.2139 0.2373 61 | % 0.2952 0.2256 0.1987 0.2110 0.2348 62 | % NaN 0.2219 0.1956 0.2079 0.2310 63 | 64 | 65 | file_path = fullfile(root_path, 'example2.py'); 66 | system(sprintf('python3 %s', file_path)) 67 | 68 | % python 69 | %[[ nan nan 0.20632041 0.21805647 0.24672859] 70 | % [ nan 0.22571652 0.20213163 0.21393441 0.23734226] 71 | % [0.29522239 0.22556393 0.19872376 0.21099555 0.23484719] 72 | % [ nan 0.22188111 0.19564657 0.20794493 0.23099783]] 73 | -------------------------------------------------------------------------------- /calcbsimpvol/src/__init__.py: -------------------------------------------------------------------------------- 1 | from .calcbsimpvol import ( 2 | calcbsimpvol, 3 | _core, 4 | _fcnv, 5 | _fcnN, 6 | _fcnn 7 | ) 8 | -------------------------------------------------------------------------------- /calcbsimpvol/src/calcbsimpvol.py: -------------------------------------------------------------------------------- 1 | from scipy.special import erf 2 | # it is common to use `import numpy as np` 3 | # but what don't you do to save 3 characters ... 4 | from numpy import ( 5 | # --- logical 6 | any, 7 | bitwise_not, 8 | logical_and, 9 | 10 | # --- constants / data type 11 | nan, 12 | pi, 13 | ndarray, 14 | 15 | # --- creation 16 | asarray, 17 | zeros, 18 | ones, 19 | full, 20 | 21 | # --- ops 22 | absolute, 23 | shape, 24 | size, 25 | maximum, 26 | log, 27 | exp, 28 | sqrt, 29 | sum, 30 | 31 | # --- juggling 32 | reshape 33 | ) 34 | 35 | 36 | def calcbsimpvol(arg_dict): 37 | """ 38 | calculates implied volatility surface or smile. Translated from MATLAB code. 39 | As it is a bare-metal package I would suggest to write an adapter class to feed the function. 40 | 41 | Args: 42 | arg_dict(dict): 43 | cp (ndarray): int.....Call = [+1], Put = [-1]...[m x n] or [1 x 1] 44 | P (ndarray): float...Option Price Matrix...[m x n] 45 | S (ndarray): float...Underlying Price...[1 x 1] 46 | K (ndarray): float...Strike Price...[m x n] 47 | tau (ndarray): float...Time to Expiry in Years...[m x n] 48 | r (ndarray): float...Continuous Risk-Free Rate...[m x n] or [1 x 1] 49 | q (ndarray): float...Continuous Dividend Yield...[m x n] or [1 x 1] 50 | 51 | Returns: 52 | - **iv** (ndarray) – float...The Implied Volatility...[m x n] 53 | 54 | Examples: 55 | This is the first example which is also included separate file within 56 | the examples folder. 57 | 58 | .. code-block:: python 59 | 60 | from calcbsimpvol import calcbsimpvol 61 | import numpy as np 62 | S = np.asarray(100) 63 | K_value = np.arange(40, 160, 25) 64 | K = np.ones((np.size(K_value), 1)) 65 | K[:, 0] = K_value 66 | tau_value = np.arange(0.25, 1.01, 0.25) 67 | tau = np.ones((np.size(tau_value), 1)) 68 | tau[:, 0] = tau_value 69 | r = np.asarray(0.01) 70 | q = np.asarray(0.03) 71 | cp = np.asarray(1) 72 | P = [[59.35, 34.41, 10.34, 0.50, 0.01], 73 | [58.71, 33.85, 10.99, 1.36, 0.14], 74 | [58.07, 33.35, 11.50, 2.12, 0.40], 75 | [57.44, 32.91, 11.90, 2.77, 0.70]] 76 | P = np.asarray(P) 77 | [K, tau] = np.meshgrid(K, tau) 78 | sigma = calcbsimpvol(dict(cp=cp, P=P, S=S, K=K, tau=tau, r=r, q=q)) 79 | print(sigma) 80 | # [[ nan, nan, 0.20709362, 0.21820954, 0.24188675], 81 | # [ nan, 0.22279836, 0.20240934, 0.21386148, 0.23738982], 82 | # [ nan, 0.22442837, 0.1987048 , 0.21063506, 0.23450013], 83 | # [ nan, 0.22188111, 0.19564657, 0.20798285, 0.23045406]] 84 | 85 | 86 | .. code-block:: python 87 | 88 | #%% Plotting the results 89 | from mpl_toolkits.mplot3d import Axes3D 90 | import matplotlib.pyplot as plt 91 | m = np.log(S/K) 92 | fig = plt.figure() 93 | ax = fig.add_subplot(111, projection='3d') 94 | ax.scatter(m, tau, sigma) 95 | ax.set_xlabel('log Moneyness') 96 | ax.set_ylabel('Time Until Expiry') 97 | ax.set_zlabel('implied Volatility [% p.a.]') 98 | plt.show() 99 | 100 | 101 | This is the second example which is also included separate file within 102 | the examples folder. 103 | 104 | 105 | .. code-block:: python 106 | 107 | from calcbsimpvol import calcbsimpvol 108 | import numpy as np 109 | P = [[59.14, 34.21, 10.17, 16.12, 40.58], 110 | [58.43, 33.59, 10.79, 17.47, 41.15], 111 | [57.87, 33.16, 11.363, 18.63, 41.74], 112 | [57.44, 32.91, 11.90, 19.58, 42.27]] 113 | P = np.asarray(P) 114 | S = np.asarray(100) 115 | K = np.arange(40, 160, 25) 116 | tau = np.arange(0.25, 1.01, 0.25) 117 | K, tau = np.meshgrid(K, tau) 118 | cp = np.hstack((np.ones((4, 3)), -1 * np.ones((4, 2)))) # [Calls[4,3], Puts[4,2]] 119 | r = 0.01 * np.ones((4, 5)) * np.asarray([1.15, 1.10, 1.05, 1]).reshape((4, 1)) 120 | q = 0.03 * np.ones((4, 5)) * np.asarray([1.3, 1.2, 1.1, 1]).reshape((4, 1)) 121 | sigma = calcbsimpvol(dict(cp=cp, P=P, S=S, K=K, tau=tau, r=r, q=q)) 122 | print(sigma) 123 | # [[0.27911614, 0.23659105, 0.20714849, 0.21834553, 0.24225733], 124 | # [0.2797917 , 0.2315275 , 0.20249323, 0.21436123, 0.23823301], 125 | # [0.27436779, 0.22679618, 0.1987485 , 0.21097384, 0.23450519], 126 | # [0.26918174, 0.22235213, 0.19572994, 0.20807133, 0.2310324 ]] 127 | 128 | 129 | .. code-block:: python 130 | 131 | #%% Plotting the results 132 | from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import 133 | import matplotlib.pyplot as plt 134 | m = np.log(S/K) 135 | fig = plt.figure() 136 | ax = fig.add_subplot(111, projection='3d') 137 | ax.scatter(m, tau, sigma) 138 | ax.set_xlabel('log Moneyness') 139 | ax.set_ylabel('Time Until Expiry') 140 | ax.set_zlabel('implied Volatility [% p.a.]') 141 | plt.show() 142 | 143 | 144 | References: 145 | 1) Li, 2006, "You Don't Have to Bother Newton for Implied Volatility" 146 | http://papers.ssrn.com/sol3/papers.cfm?abstract_id=952727 147 | 2) http://en.wikipedia.org/wiki/Householder's_method 148 | 3) http://en.wikipedia.org/wiki/Greeks_(finance) 149 | 4) https://www.mathworks.com/matlabcentral/fileexchange/41473-calcbsimpvol-cp-p-s-k-t-r-q 150 | 151 | """ 152 | # rather have a dict or class instead of seven variables 153 | feed_keys = ['cp', 'P', 'S', 'K', 'tau', 'r', 'q'] 154 | for key in feed_keys: 155 | if type(arg_dict[key]) is not ndarray: 156 | arg_dict[key] = asarray(arg_dict[key]) 157 | # convert to column vector 158 | if len(shape(arg_dict[key])) == 1 and key is not 'S': 159 | arg_dict[key] = reshape(arg_dict[key], (size(arg_dict[key]), 1)) 160 | 161 | # create short pointers/variables for data inside arg_dict 162 | cp = arg_dict['cp'] 163 | P = arg_dict['P'] 164 | S = arg_dict['S'] 165 | K = arg_dict['K'] 166 | tau = arg_dict['tau'] 167 | r = arg_dict['r'] 168 | q = arg_dict['q'] 169 | 170 | gh = shape(P) 171 | g = gh[0] 172 | h = gh[1] 173 | 174 | # for simplicity reasons a risk free rate and or dividend yield can be 175 | # supplied as a scalar value or previously modeled and supplied as an array / vector 176 | if size(r) == 1: 177 | r = r * ones((g, h)) 178 | if size(q) == 1: 179 | q = q * ones((g, h)) 180 | if size(cp) == 1: 181 | cp = cp * ones((g, h)) 182 | 183 | p = asarray([-0.969271876255, 0.097428338274, 1.750081126685]) 184 | m_values = [ 185 | 6.268456292246, 186 | -6.284840445036, 187 | 30.068281276567, 188 | -11.780036995036, 189 | -2.310966989723, 190 | -11.473184324152, 191 | -230.101682610568, 192 | 86.127219899668, 193 | 3.730181294225, 194 | -13.954993561151, 195 | 261.950288864225, 196 | 20.090690444187, 197 | -50.117067019539, 198 | 13.723711519422 199 | ] 200 | 201 | m = ones((g, h, 14)) 202 | for zetta in range(14): 203 | m[:, :, zetta] = ones((g, h)) * m_values[zetta] 204 | 205 | n_values = [ 206 | -0.068098378725, 207 | 0.440639436211, 208 | -0.263473754689, 209 | -5.792537721792, 210 | -5.267481008429, 211 | 4.714393825758, 212 | 3.529944137559, 213 | -23.636495876611, 214 | -9.020361771283, 215 | 14.749084301452, 216 | -32.570660102526, 217 | 76.398155779133, 218 | 41.855161781749, 219 | -12.150611865704 220 | ] 221 | 222 | n = ones((g, h, 14)) 223 | for zetta in range(14): 224 | n[:, :, zetta] = ones((g, h)) * n_values[zetta] 225 | 226 | i_values = [0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4] 227 | i = ones((g, h, 14)) 228 | for zetta in range(14): 229 | i[:, :, zetta] = ones((g, h)) * i_values[zetta] 230 | 231 | j_values = [1, 0, 2, 1, 0, 3, 2, 1, 0, 4, 3, 2, 1, 0] 232 | j = ones((g, h, 14)) 233 | for zetta in range(14): 234 | j[:, :, zetta] = ones((g, h)) * j_values[zetta] 235 | 236 | P[cp == -1] = P[cp == -1] + S * exp(-q[cp == -1] * tau[cp == -1]) - K[cp == -1] * exp(-r[cp == -1] * tau[cp == -1]) 237 | P = maximum(P, 0) 238 | 239 | c_values = P / (S * exp(-q * tau)) 240 | x_values = log(S * exp((r - q) * tau) / K) 241 | 242 | c = ones((shape(c_values)[0], shape(c_values)[1], 14)) 243 | x = ones((shape(x_values)[0], shape(x_values)[1], 14)) 244 | 245 | for zetta in range(shape(x)[2]): 246 | x[:, :, zetta] = x_values 247 | c[:, :, zetta] = c_values 248 | 249 | v1_fixed = _fcnv(p, m, n, i, j, x, maximum(c, 0)) # D- Domain (x < 1) 250 | v2_fixed = _fcnv(p, m, n, i, j, -x, maximum(exp(x) * c + 1 - exp(x), 0)) # D+ Domain (x > 1) 251 | 252 | v = zeros((g, h)) 253 | v[x[:, :, 0] <= 0] = v1_fixed[x[:, :, 0] <= 0] 254 | v[x[:, :, 0] > 0] = v2_fixed[x[:, :, 0] > 0] 255 | 256 | # Domain-of-Approximation is x = {-0.5, +0.5}, v = {0, 1}, x/v = {-2, 2} 257 | domain_filter = logical_and( 258 | logical_and(logical_and(x[:, :, 0] >= -0.5, x[:, :, 0] <= 0.5), logical_and(v > 0, v < 1)), 259 | logical_and((x[:, :, 0] / v) <= 2, (x[:, :, 0] / v) >= -2)) 260 | 261 | not_domain = bitwise_not(domain_filter) 262 | v[not_domain] = 0.8 263 | sigma = v / sqrt(tau) 264 | 265 | # Householder's root-finder 266 | k_max = 10 267 | tolerance = asarray(1e-12) 268 | 269 | sigma = sigma.flatten() 270 | P = P.flatten() 271 | S = S.flatten() 272 | K = K.flatten() 273 | tau = tau.flatten() 274 | r = r.flatten() 275 | q = q.flatten() 276 | 277 | C = full((shape(P)), True, dtype=bool) 278 | s = _core(P, S, K, tau, r, q, sigma, C) 279 | 280 | e = s['obj'] 281 | C = absolute(e) > tolerance 282 | k = 1 283 | 284 | while logical_and(any(C), k <= k_max): 285 | 286 | s = _core(P, S, K, tau, r, q, sigma, C) 287 | numerator = (6 * e[C] * s['vega'] ** 2 + 3 * e[C] ** 2 * s['vomma']) 288 | denominator = (-6 * s['vega'] ** 3 - 6 * e[C] * s['vega'] * s['vomma'] - e[C] ** 2 * s['ultima']) 289 | 290 | sigma[C] = sigma[C] - (numerator / denominator) 291 | s = _core(P, S, K, tau, r, q, sigma, C) 292 | e[C] = s['obj'] 293 | 294 | C = absolute(e) > tolerance 295 | k = k + 1 296 | 297 | sigma[C] = nan 298 | sigma = reshape(sigma, (g, h)) 299 | return sigma 300 | 301 | 302 | def _core(P, S, K, tau, r, q, sigma, C): 303 | """ calculation part, takes ndarrays (column vector) 304 | S: float, scalar 305 | P, K, tau, r, q, sig: float, [n x 1] 306 | C: boolean, [n x 1] 307 | 308 | """ 309 | denominator = (sigma[C] * sqrt(tau[C])) 310 | d1 = (log(S / K[C]) + (r[C] - q[C] + sigma[C] ** 2 * 0.5) * (tau[C])) / denominator 311 | d2 = (log(S / K[C]) + (r[C] - q[C] - sigma[C] ** 2 * 0.5) * (tau[C])) / denominator 312 | 313 | fcnN_d1 = _fcnN(d1) 314 | fcnN_d2 = _fcnN(d2) 315 | 316 | fcnn_d1 = _fcnn(d1) 317 | fcnn_d2 = _fcnn(d2) 318 | 319 | call = exp(-q[C] * tau[C]) * S * fcnN_d1 - exp(-r[C] * tau[C]) * K[C] * fcnN_d2 320 | obj = (P[C] - call) 321 | 322 | vega = S * exp(-q[C] * (tau[C])) * fcnn_d1 * (sqrt(tau[C])) 323 | vomma = vega * d1 * d2 / sigma[C] 324 | ultima = -1 * vega * (d1 * d2 * (1 - d1 * d2) + d1 ** 2 + d2 ** 2) / (sigma[C] ** 2) 325 | 326 | return { 327 | 'd1': d1, 328 | 'd2': d2, 329 | 'fcnN_d1': fcnN_d1, 330 | 'fcnN_d2': fcnN_d2, 331 | 'fcnn_d1': fcnn_d1, 332 | 'fcnn_d2': fcnn_d2, 333 | 'call': call, 334 | 'obj': obj, 335 | 'vega': vega, 336 | 'vomma': vomma, 337 | 'ultima': ultima 338 | } 339 | 340 | 341 | def _fcnv(p, m, n, i, j, x, c): 342 | """ Eq. (19), Li (2006) """ 343 | return p[0] * x[:, :, 0] + p[1] * sqrt(c[:, :, 0]) + p[2] * c[:, :, 0] + ( 344 | sum(n * ((x ** i) * (sqrt(c) ** j)), 2)) / (1 + sum(m * ((x ** i) * (sqrt(c) ** j)), 2)) 345 | 346 | 347 | def _fcnN(x): 348 | """cumulative density function (cdf) of normal distribution """ 349 | return 0.5 * (1. + erf(x / sqrt(2))) 350 | 351 | 352 | def _fcnn(x): 353 | """probability density function (pdf) of normal distribution """ 354 | return exp(-0.5 * x ** 2) / sqrt(2 * pi) 355 | 356 | -------------------------------------------------------------------------------- /calcbsimpvol/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkandem/calcbsimpvol/2ccae56dce825f22c32cd89bb9506cb002afa16e/calcbsimpvol/tests/__init__.py -------------------------------------------------------------------------------- /calcbsimpvol/tests/test_examples.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from calcbsimpvol.examples.example1 import calcbsimpvol_example_1 4 | from calcbsimpvol.examples.example2 import calcbsimpvol_example_2 5 | 6 | expected_result_example_1 = np.asarray([ 7 | [np.nan, np.nan, 0.20709362, 0.21820954, 0.24188675], 8 | [np.nan, 0.22279836, 0.20240934, 0.21386148, 0.23738982], 9 | [np.nan, 0.22442837, 0.19870480, 0.21063506, 0.23450013], 10 | [np.nan, 0.22188111, 0.19564657, 0.20798285, 0.23045406] 11 | ]) 12 | 13 | expected_result_example_2 = np.asarray([ 14 | [np.nan, np.nan, 0.20632041, 0.21805647, 0.24672859], 15 | [np.nan, 0.22571652, 0.20213163, 0.21393441, 0.23734226], 16 | [0.29522239, 0.22556393, 0.19872376, 0.21099555, 0.23484719], 17 | [np.nan, 0.22188111, 0.19564657, 0.20794493, 0.23099783] 18 | ]) 19 | 20 | MOE = 1/100000 21 | 22 | 23 | def test_example_1(): 24 | assert pytest.approx(calcbsimpvol_example_1(), abs=MOE, nan_ok=True) == expected_result_example_1 25 | 26 | 27 | def test_example_2(): 28 | assert pytest.approx(calcbsimpvol_example_2(), abs=MOE, nan_ok=True) == expected_result_example_2 29 | -------------------------------------------------------------------------------- /calcbsimpvol/tests/test_utilities.py: -------------------------------------------------------------------------------- 1 | """ 2 | # list of test to be created 3 | 4 | # Variables 5 | # T - expiry date 6 | # t_0 - observation date 7 | # tau - time to expiry = (T - t_0) / 365 8 | # q - continious dividend yield 9 | # r - continious risk free rate 10 | 11 | ## test equity index 12 | # q(tau), r(tau) 13 | ### a) matrix-wise (surface) 14 | ### b) vector wise (single expiry) 15 | 16 | ### I) dividend paying 17 | ### II) non dividend paying 18 | 19 | 20 | ## test futures contracts 21 | # q(tau) == r(tau) 22 | ### b) vector wise (single expiry) 23 | """ 24 | import pytest 25 | from scipy.stats import norm 26 | from calcbsimpvol.src import _fcnN, _fcnn 27 | 28 | 29 | @pytest.mark.parametrize( 30 | 'x', [point / 10 for point in range(-5 * 10, -5 * 10, 1)] 31 | ) 32 | def test__fcnN(x): 33 | assert _fcnN(x) == pytest.approx(norm.cdf(x), rel=0.000000000001) 34 | 35 | 36 | @pytest.mark.parametrize( 37 | 'x', [point / 10 for point in range(-5 * 10, 5 * 10, 1)] 38 | ) 39 | def test__fcnn(x): 40 | assert _fcnn(x) == pytest.approx(norm.pdf(x), rel=0.000000000001) 41 | 42 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | numpy 3 | # for PyPy3.5: scipy>=1.1.0, <1.3.3 4 | scipy>=1.1.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os import path 3 | from io import open 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | # Get the long description from the README file 8 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 9 | long_description = f.read() 10 | 11 | setup( 12 | name='calcbsimpvol', 13 | version='1.14.0', 14 | license='MIT', 15 | description='Calculate Black Scholes Implied Volatility - Vectorwise ', 16 | long_description=long_description, 17 | long_description_content_type='text/markdown', 18 | url='https://erkandem.github.io/calcbsimpvol/', 19 | author='Erkan Demiralay', 20 | author_email='erkan.dem@pm.me', 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Intended Audience :: Developers', 24 | 'Intended Audience :: Education', 25 | 'Intended Audience :: Financial and Insurance Industry', 26 | 'Intended Audience :: Science/Research', 27 | 'Topic :: Office/Business :: Financial ', 28 | 'Topic :: Office/Business :: Financial :: Spreadsheet', 29 | "Operating System :: OS Independent", 30 | 'License :: OSI Approved :: MIT License', 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.4', 33 | 'Programming Language :: Python :: 3.5', 34 | 'Programming Language :: Python :: 3.6', 35 | 'Programming Language :: Python :: 3.7', 36 | ], 37 | keywords='options implied volatility option iv ivol options-on-futures ivsurface black-scholes', 38 | install_requires=['numpy', 'scipy', 'matplotlib'], 39 | packages=find_packages(exclude=['calcbsimpvol.tests*', 'calcbsimpvol.docs']), 40 | package_data={'calcbsimpvol.data': ['*.json']}, 41 | project_urls={ 42 | 'Documentation': 'https://erkandem.github.io/calcbsimpvol/', 43 | 'Bug Reports': 'https://github.com/erkandem/calcbsimpvol/issues', 44 | 'Source': 'https://github.com/erkandem/calcbsimpvol', 45 | }, 46 | ) 47 | --------------------------------------------------------------------------------