├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── MANIFEST.in ├── README.md ├── TODO.md ├── notebooks ├── 0_Introduction.ipynb ├── 1_Vanilla.ipynb ├── 2_BasicAmerican.ipynb ├── 3_Bionomial.ipynb ├── 4_MonteCarlo.ipynb ├── 5_Heston.ipynb ├── 6_SpreadOptions.ipynb ├── A1_Tests.ipynb ├── A2_Passing_Arrays.ipynb ├── test.csv ├── tree3dplot,ipynb └── tree3dplot.ipynb ├── pytest ├── .Rhistory ├── examples.py ├── foptions_heston_tests.R ├── foptions_mc_tests.R ├── foptions_mc_tests_final.R ├── foptions_tests.R ├── foptions_trinomial_tests.R ├── garch_ts.csv ├── inno.csv ├── sobol_path_test.csv ├── sobol_scrambled_path_test.csv ├── start_inno.csv ├── test_HNGarch.py ├── test_american_options.py ├── test_base_classes.py ├── test_binomial_options.py ├── test_heston_options.py ├── test_mc.py ├── test_mc_paths.py ├── test_mc_payoffs.py ├── test_passing_arrays.py ├── test_spread_options.py ├── test_trinomial_options.py ├── ts.csv └── wiener_path_test.csv ├── setup.cfg ├── setup.py ├── src └── finoptions │ ├── __init__.py │ ├── base.py │ ├── base_test.py │ ├── basic_american_options │ └── __init__.py │ ├── binomial_tree_options │ └── __init__.py │ ├── heston_nandi_options │ ├── __init__.py │ └── hnGARCH.py │ ├── monte_carlo_options │ ├── __init__.py │ ├── mc_innovations.py │ ├── mc_paths.py │ └── mc_payoffs.py │ ├── spread_options │ ├── __init__.py │ ├── bitree3d.py │ ├── spreadapprox.py │ └── tree_spread.py │ ├── utils.py │ └── vanillaoptions.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | /.env/ 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # vscode 132 | .vscode 133 | 134 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "pytest" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.nosetestsEnabled": false, 7 | "python.testing.pytestEnabled": true, 8 | "python.pythonPath": "/home/bcho/miniconda3/envs/main/bin/python" 9 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ben Cho 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. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include finoptions *.csv 2 | recursive-include finoptions *.docx 3 | recursive-include finoptions *.py 4 | recursive-include finoptions *.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # finoptions 2 | 3 | Python implementation of the R package fOptions for use in energy trading. Changes include coverting the package to OOP as well as Finite Difference Methods for Option greeks for all Options. 4 | 5 | To install package run: 6 | 7 | ``` 8 | pip install finoptions 9 | ``` 10 | 11 | ## Working with finoptions 12 | 13 | Vanilla Options are found at the root of the package. For example, to run a Generalized Black Scholes Option: 14 | 15 | ```python 16 | import finoptions as fo 17 | 18 | opt = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1) 19 | 20 | opt.call() # to get call price 21 | opt.put() # to get put price 22 | opt.summary() # for a printed summary of the option 23 | opt.greeks() # to get the greeks for the option 24 | 25 | # to calculate implied volatility, omit the sigma argument and then 26 | # call the volatility method 27 | opt = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01) 28 | 29 | opt.volatility(2) 30 | ``` 31 | 32 | All options follow the same format for calls, puts, greeks and summaries. GBSOption uses the analytic solution to calculate to the greeks, but for all other options the finite difference method is used. 33 | 34 | ## Calculating Options for Multiple Inputs 35 | 36 | The vanilla options are capable of calculating calls, puts, vols and greeks for multiple inputs at the same time by passing numpy arrays of values as parameters. Currently this only works for the vanilla options. 37 | 38 | ```python 39 | import finoptions as fo 40 | import numpy as np 41 | 42 | opt = fo.GBSOption(10.0, np.arange(5,15), 1.0, 0.02, 0.01, 0.1) 43 | 44 | opt.call() # to get call price 45 | opt.put() # to get put price 46 | opt.summary() # for a printed summary of the option 47 | opt.greeks() # to get the greeks for the option 48 | ``` 49 | 50 | ## Implemented Methods 51 | 52 | ### Vanilla Options 53 | - Black-Scholes-Merton Option 54 | - Black 1976 Option 55 | - Miltersen Schwartz Option 56 | 57 | ### American Options 58 | - Roll-Geske-Whaley Calls on Dividend Paying Stocks 59 | - Barone-Adesi and Whaley Approximation 60 | - The Bjerksund and Stensland (1993) American Approximation Option 61 | 62 | ### Garch Options 63 | - The Heston-Nandi Garch Option Model 64 | 65 | ### Tree Options 66 | - Cox, Ross and Rubinstein (1979) Binomial Tree Model 67 | - Jarrow and Rudd (1983) Binomial Tree Model 68 | - Tian (1993) Binomial Tree Model 69 | - Trinomial Tree Model 70 | 71 | ### Spread Options 72 | - Rubinstein Binomial Tree Generic Spread Option Model 73 | 74 | #### Bionomial Tree Spread Options 75 | - Maximum Spread Option Model 76 | - Minimum Spread Option Model 77 | - Spread Option Model 78 | - Dual-strike Option 79 | - Reverse dual-strike option 80 | - Portfolio options 81 | - Options to exchange one asset for another 82 | - Relative performance options 83 | - Product options 84 | 85 | ### Monte Carlo Options 86 | - Monte Carlo simulation framework (see example) 87 | 88 | ## Notebooks 89 | To see example notebooks, please see github repo found here: 90 | 91 | https://github.com/bbcho/finoptions-dev/tree/main/notebooks 92 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO List 2 | 3 | 1. Add greeks to monte carlo 4 | 1. sobol innovations still return Inf sometimes 5 | 1. Make Monte Carlo Multi-processor 6 | 1. Add normal distro innovation class 7 | 1. Speed up Heston class by removing the for loop for a,b calc 8 | 1. Add exotic options 9 | 1. Make 3D bionomial spread options faster through vectorization 10 | 1. make 3D tree option plotter 11 | 1. add sub-classes for 3d bionomial spread options for different types 12 | 1. find out why test 11 on puts for BionomialSpreadOption is only accurate to 3 decimals -------------------------------------------------------------------------------- /notebooks/0_Introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "source": [ 6 | "# fOptions Package" 7 | ], 8 | "metadata": {} 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "source": [], 13 | "metadata": {} 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "source": [], 18 | "metadata": {} 19 | } 20 | ], 21 | "metadata": { 22 | "interpreter": { 23 | "hash": "1f23843d0517fbd80931cf63cde1ba60ddd02d261472889f538e5c68e74c8896" 24 | }, 25 | "kernelspec": { 26 | "name": "python3", 27 | "display_name": "Python 3.9.5 64-bit" 28 | }, 29 | "language_info": { 30 | "codemirror_mode": { 31 | "name": "ipython", 32 | "version": 3 33 | }, 34 | "file_extension": ".py", 35 | "mimetype": "text/x-python", 36 | "name": "python", 37 | "nbconvert_exporter": "python", 38 | "pygments_lexer": "ipython3", 39 | "version": "3.9.5" 40 | } 41 | }, 42 | "nbformat": 4, 43 | "nbformat_minor": 4 44 | } -------------------------------------------------------------------------------- /notebooks/1_Vanilla.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "source": [ 6 | "# Vanilla Options" 7 | ], 8 | "metadata": {} 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 4, 13 | "source": [ 14 | "%load_ext autoreload\n", 15 | "%autoreload 2\n", 16 | "import finoptions as fo\n", 17 | "import numpy as np" 18 | ], 19 | "outputs": [ 20 | { 21 | "output_type": "stream", 22 | "name": "stdout", 23 | "text": [ 24 | "The autoreload extension is already loaded. To reload it, use:\n", 25 | " %reload_ext autoreload\n" 26 | ] 27 | } 28 | ], 29 | "metadata": {} 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 5, 34 | "source": [ 35 | "opt = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1)" 36 | ], 37 | "outputs": [], 38 | "metadata": {} 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 6, 43 | "source": [ 44 | "opt.call()" 45 | ], 46 | "outputs": [ 47 | { 48 | "output_type": "execute_result", 49 | "data": { 50 | "text/plain": [ 51 | "2.0618470607330366" 52 | ] 53 | }, 54 | "metadata": {}, 55 | "execution_count": 6 56 | } 57 | ], 58 | "metadata": {} 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 7, 63 | "source": [ 64 | "opt.put()" 65 | ], 66 | "outputs": [ 67 | { 68 | "output_type": "execute_result", 69 | "data": { 70 | "text/plain": [ 71 | "0.0029381096953960684" 72 | ] 73 | }, 74 | "metadata": {}, 75 | "execution_count": 7 76 | } 77 | ], 78 | "metadata": {} 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 8, 83 | "source": [ 84 | "opt" 85 | ], 86 | "outputs": [ 87 | { 88 | "output_type": "execute_result", 89 | "data": { 90 | "text/plain": [ 91 | "GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1)" 92 | ] 93 | }, 94 | "metadata": {}, 95 | "execution_count": 8 96 | } 97 | ], 98 | "metadata": {} 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 9, 103 | "source": [ 104 | "?opt.summary" 105 | ], 106 | "outputs": [ 107 | { 108 | "output_type": "stream", 109 | "name": "stdout", 110 | "text": [ 111 | "\u001b[0;31mSignature:\u001b[0m \u001b[0mopt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msummary\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprinter\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 112 | "\u001b[0;31mDocstring:\u001b[0m\n", 113 | "Print summary report of option\n", 114 | "\n", 115 | "Parameters\n", 116 | "----------\n", 117 | "printer : bool\n", 118 | " True to print summary. False to return a string.\n", 119 | "\u001b[0;31mFile:\u001b[0m ~/PROJECTS/finoptions-dev/src/finoptions/base.py\n", 120 | "\u001b[0;31mType:\u001b[0m method\n" 121 | ] 122 | } 123 | ], 124 | "metadata": {} 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 10, 129 | "source": [ 130 | "opt.summary()" 131 | ], 132 | "outputs": [ 133 | { 134 | "output_type": "stream", 135 | "name": "stdout", 136 | "text": [ 137 | "Title: Black Scholes Option Valuation\n", 138 | "\n", 139 | "Parameters:\n", 140 | "\n", 141 | " S = 10.0\n", 142 | " K = 8.0\n", 143 | " t = 1.0\n", 144 | " r = 0.02\n", 145 | " b = 0.01\n", 146 | " sigma = 0.1\n", 147 | "\n", 148 | "Option Price:\n", 149 | "\n", 150 | " call: 2.061847\n", 151 | " put: 0.002938\n" 152 | ] 153 | } 154 | ], 155 | "metadata": {} 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 11, 160 | "source": [ 161 | "print(opt)" 162 | ], 163 | "outputs": [ 164 | { 165 | "output_type": "stream", 166 | "name": "stdout", 167 | "text": [ 168 | "Title: Black Scholes Option Valuation\n", 169 | "\n", 170 | "Parameters:\n", 171 | "\n", 172 | " S = 10.0\n", 173 | " K = 8.0\n", 174 | " t = 1.0\n", 175 | " r = 0.02\n", 176 | " b = 0.01\n", 177 | " sigma = 0.1\n", 178 | "\n", 179 | "Option Price:\n", 180 | "\n", 181 | " call: 2.061847\n", 182 | " put: 0.002938\n" 183 | ] 184 | } 185 | ], 186 | "metadata": {} 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 12, 191 | "source": [ 192 | "opt" 193 | ], 194 | "outputs": [ 195 | { 196 | "output_type": "execute_result", 197 | "data": { 198 | "text/plain": [ 199 | "GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1)" 200 | ] 201 | }, 202 | "metadata": {}, 203 | "execution_count": 12 204 | } 205 | ], 206 | "metadata": {} 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 13, 211 | "source": [ 212 | "opt.delta()" 213 | ], 214 | "outputs": [ 215 | { 216 | "output_type": "execute_result", 217 | "data": { 218 | "text/plain": [ 219 | "0.9815129867062496" 220 | ] 221 | }, 222 | "metadata": {}, 223 | "execution_count": 13 224 | } 225 | ], 226 | "metadata": {} 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": 14, 231 | "source": [ 232 | "opt.greeks()" 233 | ], 234 | "outputs": [ 235 | { 236 | "output_type": "execute_result", 237 | "data": { 238 | "text/plain": [ 239 | "{'delta': 0.9815129867062496,\n", 240 | " 'theta': -0.06850330694238246,\n", 241 | " 'vega': 0.23177898972836455,\n", 242 | " 'rho': 7.7532828063294605,\n", 243 | " 'lambda': 4.760357862611294,\n", 244 | " 'gamma': 0.023177898972836456,\n", 245 | " 'CofC': 9.815129867062497}" 246 | ] 247 | }, 248 | "metadata": {}, 249 | "execution_count": 14 250 | } 251 | ], 252 | "metadata": {} 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 15, 257 | "source": [ 258 | "opt.greeks(call=False)" 259 | ], 260 | "outputs": [ 261 | { 262 | "output_type": "execute_result", 263 | "data": { 264 | "text/plain": [ 265 | "{'delta': -0.008536847042918466,\n", 266 | " 'theta': -0.010676502588218456,\n", 267 | " 'vega': 0.23177898972836455,\n", 268 | " 'rho': -0.08830658012458127,\n", 269 | " 'lambda': -29.055576298922585,\n", 270 | " 'gamma': 0.023177898972836456,\n", 271 | " 'CofC': -0.0853684704291852}" 272 | ] 273 | }, 274 | "metadata": {}, 275 | "execution_count": 15 276 | } 277 | ], 278 | "metadata": {} 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 16, 283 | "source": [ 284 | "opt.get_params()" 285 | ], 286 | "outputs": [ 287 | { 288 | "output_type": "execute_result", 289 | "data": { 290 | "text/plain": [ 291 | "{'S': 10.0, 'K': 8.0, 't': 1.0, 'r': 0.02, 'b': 0.01, 'sigma': 0.1}" 292 | ] 293 | }, 294 | "metadata": {}, 295 | "execution_count": 16 296 | } 297 | ], 298 | "metadata": {} 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": 17, 303 | "source": [ 304 | "vol = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.0, sigma=None)\n", 305 | "print(vol.volatility(3))" 306 | ], 307 | "outputs": [ 308 | { 309 | "output_type": "stream", 310 | "name": "stdout", 311 | "text": [ 312 | "0.5363510394818946\n" 313 | ] 314 | } 315 | ], 316 | "metadata": {} 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": 19, 321 | "source": [ 322 | "fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.0, 0.5363510394818954).call()" 323 | ], 324 | "outputs": [ 325 | { 326 | "output_type": "execute_result", 327 | "data": { 328 | "text/plain": [ 329 | "3.0000000000000044" 330 | ] 331 | }, 332 | "metadata": {}, 333 | "execution_count": 19 334 | } 335 | ], 336 | "metadata": {} 337 | }, 338 | { 339 | "cell_type": "markdown", 340 | "source": [ 341 | "### Black 1977 Option" 342 | ], 343 | "metadata": {} 344 | }, 345 | { 346 | "cell_type": "code", 347 | "execution_count": 20, 348 | "source": [ 349 | "bopt = fo.Black76Option(10.0, 8.0, 1.0, 0.02, 0.1)" 350 | ], 351 | "outputs": [], 352 | "metadata": {} 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 21, 357 | "source": [ 358 | "bopt" 359 | ], 360 | "outputs": [ 361 | { 362 | "output_type": "execute_result", 363 | "data": { 364 | "text/plain": [ 365 | "Black76Option(10.0, 8.0, 1.0, 0.02, 0, 0.1)" 366 | ] 367 | }, 368 | "metadata": {}, 369 | "execution_count": 21 370 | } 371 | ], 372 | "metadata": {} 373 | }, 374 | { 375 | "cell_type": "code", 376 | "execution_count": 22, 377 | "source": [ 378 | "print(bopt)" 379 | ], 380 | "outputs": [ 381 | { 382 | "output_type": "stream", 383 | "name": "stdout", 384 | "text": [ 385 | "Title: Black 1977 Option Valuation\n", 386 | "\n", 387 | "Parameters:\n", 388 | "\n", 389 | " FT = 10.0\n", 390 | " K = 8.0\n", 391 | " t = 1.0\n", 392 | " r = 0.02\n", 393 | " b = 0\n", 394 | " sigma = 0.1\n", 395 | "\n", 396 | "Option Price:\n", 397 | "\n", 398 | " call: 1.96431\n", 399 | " put: 0.003912\n" 400 | ] 401 | } 402 | ], 403 | "metadata": {} 404 | }, 405 | { 406 | "cell_type": "code", 407 | "execution_count": 23, 408 | "source": [ 409 | "bopt.get_params()" 410 | ], 411 | "outputs": [ 412 | { 413 | "output_type": "execute_result", 414 | "data": { 415 | "text/plain": [ 416 | "{'FT': 10.0, 'K': 8.0, 't': 1.0, 'r': 0.02, 'b': 0, 'sigma': 0.1}" 417 | ] 418 | }, 419 | "metadata": {}, 420 | "execution_count": 23 421 | } 422 | ], 423 | "metadata": {} 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": 24, 428 | "source": [ 429 | "bopt.volatility(5)" 430 | ], 431 | "outputs": [ 432 | { 433 | "output_type": "stream", 434 | "name": "stderr", 435 | "text": [ 436 | "/home/bcho/PROJECTS/finoptions-dev/src/finoptions/base.py:547: UserWarning: sigma is not None but calculating implied volatility.\n", 437 | " _warnings.warn(\"sigma is not None but calculating implied volatility.\")\n" 438 | ] 439 | }, 440 | { 441 | "output_type": "execute_result", 442 | "data": { 443 | "text/plain": [ 444 | "1.1917617259924722" 445 | ] 446 | }, 447 | "metadata": {}, 448 | "execution_count": 24 449 | } 450 | ], 451 | "metadata": {} 452 | }, 453 | { 454 | "cell_type": "code", 455 | "execution_count": 25, 456 | "source": [ 457 | "bopt.get_params()" 458 | ], 459 | "outputs": [ 460 | { 461 | "output_type": "execute_result", 462 | "data": { 463 | "text/plain": [ 464 | "{'FT': 10.0, 'K': 8.0, 't': 1.0, 'r': 0.02, 'b': 0, 'sigma': 0.1}" 465 | ] 466 | }, 467 | "metadata": {}, 468 | "execution_count": 25 469 | } 470 | ], 471 | "metadata": {} 472 | }, 473 | { 474 | "cell_type": "code", 475 | "execution_count": 26, 476 | "source": [ 477 | "mopt = fo.MiltersenSchwartzOption(\n", 478 | " Pt=np.exp(-0.05 / 4),\n", 479 | " FT=95,\n", 480 | " K=80,\n", 481 | " t=1 / 4,\n", 482 | " T=1 / 2,\n", 483 | " sigmaS=0.2660,\n", 484 | " sigmaE=0.2490,\n", 485 | " sigmaF=0.0096,\n", 486 | " rhoSE=0.805,\n", 487 | " rhoSF=0.0805,\n", 488 | " rhoEF=0.1243,\n", 489 | " KappaE=1.045,\n", 490 | " KappaF=0.200,\n", 491 | " )" 492 | ], 493 | "outputs": [], 494 | "metadata": {} 495 | }, 496 | { 497 | "cell_type": "code", 498 | "execution_count": 27, 499 | "source": [ 500 | "mopt.call()" 501 | ], 502 | "outputs": [ 503 | { 504 | "output_type": "execute_result", 505 | "data": { 506 | "text/plain": [ 507 | "15.004682511978606" 508 | ] 509 | }, 510 | "metadata": {}, 511 | "execution_count": 27 512 | } 513 | ], 514 | "metadata": {} 515 | }, 516 | { 517 | "cell_type": "code", 518 | "execution_count": 28, 519 | "source": [ 520 | "mopt.get_params()" 521 | ], 522 | "outputs": [ 523 | { 524 | "output_type": "execute_result", 525 | "data": { 526 | "text/plain": [ 527 | "{'Pt': 0.9875778004938814,\n", 528 | " 'FT': 95,\n", 529 | " 'K': 80,\n", 530 | " 't': 0.25,\n", 531 | " 'T': 0.5,\n", 532 | " 'sigmaS': 0.266,\n", 533 | " 'sigmaE': 0.249,\n", 534 | " 'sigmaF': 0.0096,\n", 535 | " 'rhoSE': 0.805,\n", 536 | " 'rhoSF': 0.0805,\n", 537 | " 'rhoEF': 0.1243,\n", 538 | " 'KappaE': 1.045,\n", 539 | " 'KappaF': 0.2}" 540 | ] 541 | }, 542 | "metadata": {}, 543 | "execution_count": 28 544 | } 545 | ], 546 | "metadata": {} 547 | }, 548 | { 549 | "cell_type": "code", 550 | "execution_count": 29, 551 | "source": [ 552 | "mopt.summary()" 553 | ], 554 | "outputs": [ 555 | { 556 | "output_type": "stream", 557 | "name": "stdout", 558 | "text": [ 559 | "Title: Miltersen Schwartz Option Valuation\n", 560 | "\n", 561 | "Parameters:\n", 562 | "\n", 563 | " Pt = 0.9875778004938814\n", 564 | " FT = 95\n", 565 | " K = 80\n", 566 | " t = 0.25\n", 567 | " T = 0.5\n", 568 | " sigmaS = 0.266\n", 569 | " sigmaE = 0.249\n", 570 | " sigmaF = 0.0096\n", 571 | " rhoSE = 0.805\n", 572 | " rhoSF = 0.0805\n", 573 | " rhoEF = 0.1243\n", 574 | " KappaE = 1.045\n", 575 | " KappaF = 0.2\n", 576 | "\n", 577 | "Option Price:\n", 578 | "\n", 579 | " call: 15.004683\n", 580 | " put: 0.191426\n" 581 | ] 582 | } 583 | ], 584 | "metadata": {} 585 | }, 586 | { 587 | "cell_type": "code", 588 | "execution_count": 30, 589 | "source": [ 590 | "mopt" 591 | ], 592 | "outputs": [ 593 | { 594 | "output_type": "execute_result", 595 | "data": { 596 | "text/plain": [ 597 | "MiltersenSchwartzOption(0.9875778004938814, 95, 80, 0.25, 0.5, 0.266, 0.249, 0.0096, 0.805, 0.0805, 0.1243, 1.045, 0.2)" 598 | ] 599 | }, 600 | "metadata": {}, 601 | "execution_count": 30 602 | } 603 | ], 604 | "metadata": {} 605 | }, 606 | { 607 | "cell_type": "code", 608 | "execution_count": 31, 609 | "source": [ 610 | "print(mopt)" 611 | ], 612 | "outputs": [ 613 | { 614 | "output_type": "stream", 615 | "name": "stdout", 616 | "text": [ 617 | "Title: Miltersen Schwartz Option Valuation\n", 618 | "\n", 619 | "Parameters:\n", 620 | "\n", 621 | " Pt = 0.9875778004938814\n", 622 | " FT = 95\n", 623 | " K = 80\n", 624 | " t = 0.25\n", 625 | " T = 0.5\n", 626 | " sigmaS = 0.266\n", 627 | " sigmaE = 0.249\n", 628 | " sigmaF = 0.0096\n", 629 | " rhoSE = 0.805\n", 630 | " rhoSF = 0.0805\n", 631 | " rhoEF = 0.1243\n", 632 | " KappaE = 1.045\n", 633 | " KappaF = 0.2\n", 634 | "\n", 635 | "Option Price:\n", 636 | "\n", 637 | " call: 15.004683\n", 638 | " put: 0.191426\n" 639 | ] 640 | } 641 | ], 642 | "metadata": {} 643 | }, 644 | { 645 | "cell_type": "code", 646 | "execution_count": 35, 647 | "source": [ 648 | "mopt.delta()" 649 | ], 650 | "outputs": [ 651 | { 652 | "output_type": "execute_result", 653 | "data": { 654 | "text/plain": [ 655 | "0.9427019114803326" 656 | ] 657 | }, 658 | "metadata": {}, 659 | "execution_count": 35 660 | } 661 | ], 662 | "metadata": {} 663 | }, 664 | { 665 | "cell_type": "code", 666 | "execution_count": 36, 667 | "source": [ 668 | "mopt.vega()" 669 | ], 670 | "outputs": [ 671 | { 672 | "output_type": "execute_result", 673 | "data": { 674 | "text/plain": [ 675 | "array([ 4.36537183, -0.89387399, 0.06553819])" 676 | ] 677 | }, 678 | "metadata": {}, 679 | "execution_count": 36 680 | } 681 | ], 682 | "metadata": {} 683 | }, 684 | { 685 | "cell_type": "code", 686 | "execution_count": 38, 687 | "source": [ 688 | "mopt.greeks()" 689 | ], 690 | "outputs": [ 691 | { 692 | "output_type": "execute_result", 693 | "data": { 694 | "text/plain": [ 695 | "{'delta': 0.9427019114803326,\n", 696 | " 'theta': -2.1486160364725966,\n", 697 | " 'vega': array([ 4.36537183, -0.89387399, 0.06553819]),\n", 698 | " 'lambda': 5.968582242185818,\n", 699 | " 'gamma': 0.00946707328451062}" 700 | ] 701 | }, 702 | "metadata": {}, 703 | "execution_count": 38 704 | } 705 | ], 706 | "metadata": {} 707 | }, 708 | { 709 | "cell_type": "code", 710 | "execution_count": null, 711 | "source": [], 712 | "outputs": [], 713 | "metadata": {} 714 | } 715 | ], 716 | "metadata": { 717 | "orig_nbformat": 4, 718 | "language_info": { 719 | "name": "python", 720 | "version": "3.9.6", 721 | "mimetype": "text/x-python", 722 | "codemirror_mode": { 723 | "name": "ipython", 724 | "version": 3 725 | }, 726 | "pygments_lexer": "ipython3", 727 | "nbconvert_exporter": "python", 728 | "file_extension": ".py" 729 | }, 730 | "kernelspec": { 731 | "name": "python3", 732 | "display_name": "Python 3.9.6 64-bit ('main': conda)" 733 | }, 734 | "interpreter": { 735 | "hash": "1f23843d0517fbd80931cf63cde1ba60ddd02d261472889f538e5c68e74c8896" 736 | } 737 | }, 738 | "nbformat": 4, 739 | "nbformat_minor": 2 740 | } -------------------------------------------------------------------------------- /notebooks/2_BasicAmerican.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "source": [ 6 | "# Basic American Options" 7 | ], 8 | "metadata": {} 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "source": [ 14 | "%load_ext autoreload\n", 15 | "%autoreload 2\n", 16 | "import finoptions as fo\n", 17 | "import numpy as np" 18 | ], 19 | "outputs": [], 20 | "metadata": {} 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "source": [ 26 | "fo.basic_american_options.BAWAmericanApproxOption" 27 | ], 28 | "outputs": [ 29 | { 30 | "output_type": "execute_result", 31 | "data": { 32 | "text/plain": [ 33 | "finoptions.basic_american_options.BAWAmericanApproxOption" 34 | ] 35 | }, 36 | "metadata": {}, 37 | "execution_count": 2 38 | } 39 | ], 40 | "metadata": {} 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 3, 45 | "source": [ 46 | "ropt = fo.basic_american_options.RollGeskeWhaleyOption(S=80, K=82, t=1/3, td=1/4, r=0.06, D=4, sigma=0.30)" 47 | ], 48 | "outputs": [], 49 | "metadata": {} 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 4, 54 | "source": [ 55 | "ropt.call()" 56 | ], 57 | "outputs": [ 58 | { 59 | "output_type": "execute_result", 60 | "data": { 61 | "text/plain": [ 62 | "4.386030003741627" 63 | ] 64 | }, 65 | "metadata": {}, 66 | "execution_count": 4 67 | } 68 | ], 69 | "metadata": {} 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 5, 74 | "source": [ 75 | "ropt" 76 | ], 77 | "outputs": [ 78 | { 79 | "output_type": "execute_result", 80 | "data": { 81 | "text/plain": [ 82 | "RollGeskeWhaleyOption(80, 82, 0.3333333333333333, 0.25, 0.06, 4, 0.3)" 83 | ] 84 | }, 85 | "metadata": {}, 86 | "execution_count": 5 87 | } 88 | ], 89 | "metadata": {} 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 6, 94 | "source": [ 95 | "print(ropt)" 96 | ], 97 | "outputs": [ 98 | { 99 | "output_type": "stream", 100 | "name": "stdout", 101 | "text": [ 102 | "Title: Roll-Geske-Whaley Calls on Dividend Paying Stocks Valuation\n", 103 | "\n", 104 | "Parameters:\n", 105 | "\n", 106 | " S = 80\n", 107 | " K = 82\n", 108 | " t = 0.3333333333333333\n", 109 | " td = 0.25\n", 110 | " r = 0.06\n", 111 | " D = 4\n", 112 | " sigma = 0.3\n", 113 | "\n", 114 | "Option Price:\n", 115 | "\n", 116 | " call: 4.38603\n", 117 | " Optimal to Exercise Call Option: True\n" 118 | ] 119 | } 120 | ], 121 | "metadata": {} 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 7, 126 | "source": [ 127 | "ropt.delta()" 128 | ], 129 | "outputs": [ 130 | { 131 | "output_type": "execute_result", 132 | "data": { 133 | "text/plain": [ 134 | "0.5022169959258054" 135 | ] 136 | }, 137 | "metadata": {}, 138 | "execution_count": 7 139 | } 140 | ], 141 | "metadata": {} 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 8, 146 | "source": [ 147 | "ropt.greeks()" 148 | ], 149 | "outputs": [ 150 | { 151 | "output_type": "execute_result", 152 | "data": { 153 | "text/plain": [ 154 | "{'delta': 0.5022169959258054,\n", 155 | " 'theta': -3.960344624327405,\n", 156 | " 'vega': 16.59268091864252,\n", 157 | " 'rho': 9.37123587985491,\n", 158 | " 'lambda': 9.160302058989563,\n", 159 | " 'gamma': 0.03356841049986252}" 160 | ] 161 | }, 162 | "metadata": {}, 163 | "execution_count": 8 164 | } 165 | ], 166 | "metadata": {} 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 9, 171 | "source": [ 172 | "ropt.summary()" 173 | ], 174 | "outputs": [ 175 | { 176 | "output_type": "stream", 177 | "name": "stdout", 178 | "text": [ 179 | "Title: Roll-Geske-Whaley Calls on Dividend Paying Stocks Valuation\n", 180 | "\n", 181 | "Parameters:\n", 182 | "\n", 183 | " S = 80\n", 184 | " K = 82\n", 185 | " t = 0.3333333333333333\n", 186 | " td = 0.25\n", 187 | " r = 0.06\n", 188 | " D = 4\n", 189 | " sigma = 0.3\n", 190 | "\n", 191 | "Option Price:\n", 192 | "\n", 193 | " call: 4.38603\n", 194 | " Optimal to Exercise Call Option: True\n" 195 | ] 196 | } 197 | ], 198 | "metadata": {} 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 10, 203 | "source": [ 204 | "rvol = fo.basic_american_options.RollGeskeWhaleyOption(S=80, K=82, t=1/3, td=1/4, r=0.06, D=4, sigma=None)\n", 205 | "rvol.volatility(10)" 206 | ], 207 | "outputs": [ 208 | { 209 | "output_type": "execute_result", 210 | "data": { 211 | "text/plain": [ 212 | "0.6328596836266412" 213 | ] 214 | }, 215 | "metadata": {}, 216 | "execution_count": 10 217 | } 218 | ], 219 | "metadata": {} 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": 11, 224 | "source": [ 225 | "bopt = fo.basic_american_options.BAWAmericanApproxOption(S = 100, K = 90, t = 0.5, r = 0.10, b = 0, sigma = 0.25)" 226 | ], 227 | "outputs": [], 228 | "metadata": {} 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": 12, 233 | "source": [ 234 | "bopt.__name__" 235 | ], 236 | "outputs": [ 237 | { 238 | "output_type": "execute_result", 239 | "data": { 240 | "text/plain": [ 241 | "'BAWAmericanApproxOption'" 242 | ] 243 | }, 244 | "metadata": {}, 245 | "execution_count": 12 246 | } 247 | ], 248 | "metadata": {} 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": 13, 253 | "source": [ 254 | "bopt.call()" 255 | ], 256 | "outputs": [ 257 | { 258 | "output_type": "execute_result", 259 | "data": { 260 | "text/plain": [ 261 | "12.44166279190568" 262 | ] 263 | }, 264 | "metadata": {}, 265 | "execution_count": 13 266 | } 267 | ], 268 | "metadata": {} 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": 14, 273 | "source": [ 274 | "bopt.put()" 275 | ], 276 | "outputs": [ 277 | { 278 | "output_type": "execute_result", 279 | "data": { 280 | "text/plain": [ 281 | "2.7436097188998456" 282 | ] 283 | }, 284 | "metadata": {}, 285 | "execution_count": 14 286 | } 287 | ], 288 | "metadata": {} 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": 15, 293 | "source": [ 294 | "bopt.summary()" 295 | ], 296 | "outputs": [ 297 | { 298 | "output_type": "stream", 299 | "name": "stdout", 300 | "text": [ 301 | "Title: Barone-Adesi and Whaley Approximation Valuation\n", 302 | "\n", 303 | "Parameters:\n", 304 | "\n", 305 | " S = 100\n", 306 | " K = 90\n", 307 | " t = 0.5\n", 308 | " r = 0.1\n", 309 | " b = 0\n", 310 | " sigma = 0.25\n", 311 | "\n", 312 | "Option Price:\n", 313 | "\n", 314 | " call: 12.441663\n", 315 | " put: 2.74361\n" 316 | ] 317 | } 318 | ], 319 | "metadata": {} 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": 16, 324 | "source": [ 325 | "bopt.greeks()" 326 | ], 327 | "outputs": [ 328 | { 329 | "output_type": "execute_result", 330 | "data": { 331 | "text/plain": [ 332 | "{'delta': 0.7359424889435693,\n", 333 | " 'theta': -4.6823920234771945,\n", 334 | " 'vega': 21.328855605606133,\n", 335 | " 'rho': -3.29715279354839,\n", 336 | " 'lambda': 5.9151457586711,\n", 337 | " 'gamma': 0.018471927996037574}" 338 | ] 339 | }, 340 | "metadata": {}, 341 | "execution_count": 16 342 | } 343 | ], 344 | "metadata": {} 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": 17, 349 | "source": [ 350 | "bsopt = fo.basic_american_options.BSAmericanApproxOption(S = 100, K = 90, t = 0.5, r = 0.10, b = 0, sigma = 0.25)" 351 | ], 352 | "outputs": [], 353 | "metadata": {} 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": 18, 358 | "source": [ 359 | "bsopt.call()" 360 | ], 361 | "outputs": [ 362 | { 363 | "output_type": "execute_result", 364 | "data": { 365 | "text/plain": [ 366 | "{'OptionPrice': 12.39888578756754, 'TriggerPrice': 115.27226475479983}" 367 | ] 368 | }, 369 | "metadata": {}, 370 | "execution_count": 18 371 | } 372 | ], 373 | "metadata": {} 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": 19, 378 | "source": [ 379 | "bsopt.put()" 380 | ], 381 | "outputs": [ 382 | { 383 | "output_type": "execute_result", 384 | "data": { 385 | "text/plain": [ 386 | "{'OptionPrice': 2.714363003364042, 'TriggerPrice': 128.0802941719998}" 387 | ] 388 | }, 389 | "metadata": {}, 390 | "execution_count": 19 391 | } 392 | ], 393 | "metadata": {} 394 | }, 395 | { 396 | "cell_type": "code", 397 | "execution_count": 20, 398 | "source": [ 399 | "bsopt.delta()" 400 | ], 401 | "outputs": [ 402 | { 403 | "output_type": "execute_result", 404 | "data": { 405 | "text/plain": [ 406 | "0.735785404308647" 407 | ] 408 | }, 409 | "metadata": {}, 410 | "execution_count": 20 411 | } 412 | ], 413 | "metadata": {} 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": 21, 418 | "source": [ 419 | "bsopt.vega()" 420 | ], 421 | "outputs": [ 422 | { 423 | "output_type": "execute_result", 424 | "data": { 425 | "text/plain": [ 426 | "21.127946527008707" 427 | ] 428 | }, 429 | "metadata": {}, 430 | "execution_count": 21 431 | } 432 | ], 433 | "metadata": {} 434 | }, 435 | { 436 | "cell_type": "code", 437 | "execution_count": 22, 438 | "source": [ 439 | "bsopt.greeks()" 440 | ], 441 | "outputs": [ 442 | { 443 | "output_type": "execute_result", 444 | "data": { 445 | "text/plain": [ 446 | "{'delta': 0.735785404308647,\n", 447 | " 'theta': -4.554175462491147,\n", 448 | " 'vega': 21.127946527008707,\n", 449 | " 'rho': -3.686679020784406,\n", 450 | " 'lambda': 5.934286490858919,\n", 451 | " 'gamma': 0.018292906571849885}" 452 | ] 453 | }, 454 | "metadata": {}, 455 | "execution_count": 22 456 | } 457 | ], 458 | "metadata": {} 459 | }, 460 | { 461 | "cell_type": "code", 462 | "execution_count": 23, 463 | "source": [ 464 | "bsopt.summary()" 465 | ], 466 | "outputs": [ 467 | { 468 | "output_type": "stream", 469 | "name": "stdout", 470 | "text": [ 471 | "Title: The Bjerksund and Stensland (1993) American Approximation Option Valuation\n", 472 | "\n", 473 | "Parameters:\n", 474 | "\n", 475 | " S = 100\n", 476 | " K = 90\n", 477 | " t = 0.5\n", 478 | " r = 0.1\n", 479 | " b = 0\n", 480 | " sigma = 0.25\n", 481 | "\n", 482 | "Option Price:\n", 483 | "\n", 484 | " call: 12.398886, trigger: 115.272265\n", 485 | " put: 2.714363, trigger: 128.080294\n" 486 | ] 487 | } 488 | ], 489 | "metadata": {} 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": 24, 494 | "source": [ 495 | "bsopt = fo.basic_american_options.BSAmericanApproxOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1).call()" 496 | ], 497 | "outputs": [], 498 | "metadata": {} 499 | }, 500 | { 501 | "cell_type": "code", 502 | "execution_count": 25, 503 | "source": [ 504 | "bsopt" 505 | ], 506 | "outputs": [ 507 | { 508 | "output_type": "execute_result", 509 | "data": { 510 | "text/plain": [ 511 | "{'OptionPrice': 2.061847060733921, 'TriggerPrice': 18.598685762314446}" 512 | ] 513 | }, 514 | "metadata": {}, 515 | "execution_count": 25 516 | } 517 | ], 518 | "metadata": {} 519 | }, 520 | { 521 | "cell_type": "code", 522 | "execution_count": null, 523 | "source": [], 524 | "outputs": [], 525 | "metadata": {} 526 | } 527 | ], 528 | "metadata": { 529 | "orig_nbformat": 4, 530 | "language_info": { 531 | "name": "python", 532 | "version": "3.9.6", 533 | "mimetype": "text/x-python", 534 | "codemirror_mode": { 535 | "name": "ipython", 536 | "version": 3 537 | }, 538 | "pygments_lexer": "ipython3", 539 | "nbconvert_exporter": "python", 540 | "file_extension": ".py" 541 | }, 542 | "kernelspec": { 543 | "name": "python3", 544 | "display_name": "Python 3.9.6 64-bit ('main': conda)" 545 | }, 546 | "interpreter": { 547 | "hash": "1f23843d0517fbd80931cf63cde1ba60ddd02d261472889f538e5c68e74c8896" 548 | } 549 | }, 550 | "nbformat": 4, 551 | "nbformat_minor": 2 552 | } -------------------------------------------------------------------------------- /notebooks/6_SpreadOptions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 3, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "import finoptions as fo\n", 12 | "import numpy as np" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 4, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "S1 = 122\n", 22 | "S2 = 120\n", 23 | "K = 3\n", 24 | "r = 0.1\n", 25 | "b1 = 0\n", 26 | "b2 = 0\n", 27 | "n = 10\n", 28 | "sigma1 = 0.2\n", 29 | "sigma2 = 0.2\n", 30 | "rho = -0.5\n", 31 | "t = 0.1\n", 32 | "otype = \"european\"" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 14, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "opt = fo.spread_options.SpreadApproxOption(S1=S1, S2=S2, K=K, r=r, b1=b1, b2=b2, sigma1=sigma1, sigma2=sigma2, t=t, rho=rho)" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 15, 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "data": { 51 | "text/plain": [ 52 | "4.75300566834511" 53 | ] 54 | }, 55 | "execution_count": 15, 56 | "metadata": {}, 57 | "output_type": "execute_result" 58 | } 59 | ], 60 | "source": [ 61 | "opt.call()" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 18, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "data": { 71 | "text/plain": [ 72 | "5.743055502094277" 73 | ] 74 | }, 75 | "execution_count": 18, 76 | "metadata": {}, 77 | "output_type": "execute_result" 78 | } 79 | ], 80 | "source": [ 81 | "opt.call() - (opt._S1*np.exp(opt._b1-opt._r*opt._t) - opt._S2*np.exp(opt._b2-opt._r*opt._t)) + opt._K*np.exp(-opt._r*opt._t)" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 19, 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "data": { 91 | "text/plain": [ 92 | "5.743055502094277" 93 | ] 94 | }, 95 | "execution_count": 19, 96 | "metadata": {}, 97 | "output_type": "execute_result" 98 | } 99 | ], 100 | "source": [ 101 | "opt.put()" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 20, 107 | "metadata": {}, 108 | "outputs": [ 109 | { 110 | "name": "stdout", 111 | "output_type": "stream", 112 | "text": [ 113 | "Title: Spread-Option Approximation Model Valuation\n", 114 | "\n", 115 | "Parameters:\n", 116 | "\n", 117 | " S1 = 122\n", 118 | " S2 = 120\n", 119 | " K = 3\n", 120 | " t = 0.1\n", 121 | " r = 0.1\n", 122 | " b1 = 0\n", 123 | " b2 = 0\n", 124 | " sigma1 = 0.2\n", 125 | " sigma2 = 0.2\n", 126 | " rho = -0.5\n", 127 | " Q1 = 1\n", 128 | " Q2 = 1\n", 129 | "\n", 130 | "Option Price:\n", 131 | "\n", 132 | " call: 4.753006\n", 133 | " put: 5.743056\n" 134 | ] 135 | } 136 | ], 137 | "source": [ 138 | "opt.summary()" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 11, 144 | "metadata": {}, 145 | "outputs": [ 146 | { 147 | "data": { 148 | "text/plain": [ 149 | "{'S1': 0.4760055566143096, 'S2': -0.44084450865762326}" 150 | ] 151 | }, 152 | "execution_count": 11, 153 | "metadata": {}, 154 | "output_type": "execute_result" 155 | } 156 | ], 157 | "source": [ 158 | "opt.delta()" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 12, 164 | "metadata": {}, 165 | "outputs": [ 166 | { 167 | "data": { 168 | "text/plain": [ 169 | "{'delta': {'S1': 0.4760055566143096, 'S2': -0.44084450865762326},\n", 170 | " 'theta': -20.888405271750617,\n", 171 | " 'vega': {'sigma1': 10.894424035981855, 'sigma2': 10.369469635732882},\n", 172 | " 'rho': -0.37969559937471276,\n", 173 | " 'lambda': {'S1': 15.294535412729616, 'S2': -13.932566278363348},\n", 174 | " 'gamma': {'S1': 0.036626675021485144, 'S2': 0.03602298474448488}}" 175 | ] 176 | }, 177 | "execution_count": 12, 178 | "metadata": {}, 179 | "output_type": "execute_result" 180 | } 181 | ], 182 | "source": [ 183 | "opt.greeks()" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 27, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "opt = fo.spread_options.BionomialSpreadOption(S1=S1, S2=S2, K=K, r=r, b1=b1, b2=b2, n=n, sigma1=sigma1, sigma2=sigma2, t=t, rho=rho, otype=otype)" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 28, 198 | "metadata": {}, 199 | "outputs": [ 200 | { 201 | "data": { 202 | "text/plain": [ 203 | "3.8122742759583192" 204 | ] 205 | }, 206 | "execution_count": 28, 207 | "metadata": {}, 208 | "output_type": "execute_result" 209 | } 210 | ], 211 | "source": [ 212 | "opt.call()" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 29, 218 | "metadata": {}, 219 | "outputs": [ 220 | { 221 | "data": { 222 | "text/plain": [ 223 | "4.802324373692674" 224 | ] 225 | }, 226 | "execution_count": 29, 227 | "metadata": {}, 228 | "output_type": "execute_result" 229 | } 230 | ], 231 | "source": [ 232 | "opt.put()" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 8, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "data": { 242 | "text/plain": [ 243 | "{'S1': 0.47327879431921505, 'S2': -0.4380516648354369}" 244 | ] 245 | }, 246 | "execution_count": 8, 247 | "metadata": {}, 248 | "output_type": "execute_result" 249 | } 250 | ], 251 | "source": [ 252 | "opt.delta()" 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": 9, 258 | "metadata": {}, 259 | "outputs": [ 260 | { 261 | "data": { 262 | "text/plain": [ 263 | "-20.922148544244617" 264 | ] 265 | }, 266 | "execution_count": 9, 267 | "metadata": {}, 268 | "output_type": "execute_result" 269 | } 270 | ], 271 | "source": [ 272 | "opt.theta()" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 10, 278 | "metadata": {}, 279 | "outputs": [ 280 | { 281 | "data": { 282 | "text/plain": [ 283 | "{'sigma1': 10.78439755140901, 'sigma2': 10.514157830422027}" 284 | ] 285 | }, 286 | "execution_count": 10, 287 | "metadata": {}, 288 | "output_type": "execute_result" 289 | } 290 | ], 291 | "source": [ 292 | "opt.vega()" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": 11, 298 | "metadata": {}, 299 | "outputs": [ 300 | { 301 | "data": { 302 | "text/plain": [ 303 | "-0.3806934342921592" 304 | ] 305 | }, 306 | "execution_count": 11, 307 | "metadata": {}, 308 | "output_type": "execute_result" 309 | } 310 | ], 311 | "source": [ 312 | "opt.rho()" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 12, 318 | "metadata": {}, 319 | "outputs": [ 320 | { 321 | "data": { 322 | "text/plain": [ 323 | "{'S1': 15.167062971340052, 'S2': -13.808013231934071}" 324 | ] 325 | }, 326 | "execution_count": 12, 327 | "metadata": {}, 328 | "output_type": "execute_result" 329 | } 330 | ], 331 | "source": [ 332 | "opt.lamb()" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 13, 338 | "metadata": {}, 339 | "outputs": [ 340 | { 341 | "data": { 342 | "text/plain": [ 343 | "{'S1': 1.2921970811364414e-12, 'S2': 1.1853374179008844e-12}" 344 | ] 345 | }, 346 | "execution_count": 13, 347 | "metadata": {}, 348 | "output_type": "execute_result" 349 | } 350 | ], 351 | "source": [ 352 | "opt.gamma()" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": 14, 358 | "metadata": {}, 359 | "outputs": [ 360 | { 361 | "data": { 362 | "text/plain": [ 363 | "{'S1': 122,\n", 364 | " 'S2': 120,\n", 365 | " 'K': 3,\n", 366 | " 't': 0.1,\n", 367 | " 'r': 0.1,\n", 368 | " 'b1': 0,\n", 369 | " 'b2': 0,\n", 370 | " 'sigma1': 0.2,\n", 371 | " 'sigma2': 0.2,\n", 372 | " 'rho': 0,\n", 373 | " 'Q1': 1,\n", 374 | " 'Q2': 1,\n", 375 | " 'otype': 'european',\n", 376 | " 'n': 50}" 377 | ] 378 | }, 379 | "execution_count": 14, 380 | "metadata": {}, 381 | "output_type": "execute_result" 382 | } 383 | ], 384 | "source": [ 385 | "opt.get_params()" 386 | ] 387 | }, 388 | { 389 | "cell_type": "code", 390 | "execution_count": 15, 391 | "metadata": {}, 392 | "outputs": [ 393 | { 394 | "name": "stdout", 395 | "output_type": "stream", 396 | "text": [ 397 | "Title: Binomial Tree Minimum Spread Option Model Valuation\n", 398 | "\n", 399 | "Parameters:\n", 400 | "\n", 401 | " S1 = 122\n", 402 | " S2 = 120\n", 403 | " K = 3\n", 404 | " t = 0.1\n", 405 | " r = 0.1\n", 406 | " b1 = 0\n", 407 | " b2 = 0\n", 408 | " sigma1 = 0.2\n", 409 | " sigma2 = 0.2\n", 410 | " rho = 0\n", 411 | " Q1 = 1\n", 412 | " Q2 = 1\n", 413 | " otype = european\n", 414 | " n = 50\n", 415 | "\n", 416 | "Option Price:\n", 417 | "\n", 418 | " call-european: 3.806934\n", 419 | " put-european: 4.796984\n" 420 | ] 421 | } 422 | ], 423 | "source": [ 424 | "opt.summary()" 425 | ] 426 | }, 427 | { 428 | "cell_type": "code", 429 | "execution_count": 16, 430 | "metadata": {}, 431 | "outputs": [], 432 | "source": [ 433 | "opt = fo.spread_options.BionomialMinOption(S1=S1, S2=S2, K=K, r=r, b1=b1, b2=b2, n=n, sigma1=sigma1, sigma2=sigma2, t=t, rho=rho, otype=otype)" 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": 17, 439 | "metadata": {}, 440 | "outputs": [ 441 | { 442 | "data": { 443 | "text/plain": [ 444 | "112.48467517218428" 445 | ] 446 | }, 447 | "execution_count": 17, 448 | "metadata": {}, 449 | "output_type": "execute_result" 450 | } 451 | ], 452 | "source": [ 453 | "opt.call()" 454 | ] 455 | }, 456 | { 457 | "cell_type": "code", 458 | "execution_count": 18, 459 | "metadata": {}, 460 | "outputs": [ 461 | { 462 | "data": { 463 | "text/plain": [ 464 | "0.0" 465 | ] 466 | }, 467 | "execution_count": 18, 468 | "metadata": {}, 469 | "output_type": "execute_result" 470 | } 471 | ], 472 | "source": [ 473 | "opt.put()" 474 | ] 475 | }, 476 | { 477 | "cell_type": "code", 478 | "execution_count": 19, 479 | "metadata": {}, 480 | "outputs": [ 481 | { 482 | "name": "stdout", 483 | "output_type": "stream", 484 | "text": [ 485 | "Title: Binomial Tree Minimum Spread Option Model Valuation\n", 486 | "\n", 487 | "Parameters:\n", 488 | "\n", 489 | " S1 = 122\n", 490 | " S2 = 120\n", 491 | " K = 3\n", 492 | " t = 0.1\n", 493 | " r = 0.1\n", 494 | " b1 = 0\n", 495 | " b2 = 0\n", 496 | " sigma1 = 0.2\n", 497 | " sigma2 = 0.2\n", 498 | " rho = 0\n", 499 | " Q1 = 1\n", 500 | " Q2 = 1\n", 501 | " otype = european\n", 502 | " n = 50\n", 503 | "\n", 504 | "Option Price:\n", 505 | "\n", 506 | " call-european: 112.484675\n", 507 | " put-european: 0.0\n" 508 | ] 509 | } 510 | ], 511 | "source": [ 512 | "opt.summary()" 513 | ] 514 | }, 515 | { 516 | "cell_type": "code", 517 | "execution_count": 20, 518 | "metadata": {}, 519 | "outputs": [ 520 | { 521 | "data": { 522 | "text/plain": [ 523 | "{'S1': 0.43805166483625, 'S2': 0.5167710130293401}" 524 | ] 525 | }, 526 | "execution_count": 20, 527 | "metadata": {}, 528 | "output_type": "execute_result" 529 | } 530 | ], 531 | "source": [ 532 | "opt.delta()" 533 | ] 534 | }, 535 | { 536 | "cell_type": "code", 537 | "execution_count": null, 538 | "metadata": {}, 539 | "outputs": [], 540 | "source": [] 541 | }, 542 | { 543 | "cell_type": "code", 544 | "execution_count": null, 545 | "metadata": {}, 546 | "outputs": [], 547 | "source": [] 548 | } 549 | ], 550 | "metadata": { 551 | "interpreter": { 552 | "hash": "b7504fe8a6b99121a6de54ea8e9c9e8d2896400aee6d316cf9c29e3833edd471" 553 | }, 554 | "kernelspec": { 555 | "display_name": "Python 3.9.7 64-bit ('main': conda)", 556 | "name": "python3" 557 | }, 558 | "language_info": { 559 | "codemirror_mode": { 560 | "name": "ipython", 561 | "version": 3 562 | }, 563 | "file_extension": ".py", 564 | "mimetype": "text/x-python", 565 | "name": "python", 566 | "nbconvert_exporter": "python", 567 | "pygments_lexer": "ipython3", 568 | "version": "3.9.7" 569 | }, 570 | "orig_nbformat": 4 571 | }, 572 | "nbformat": 4, 573 | "nbformat_minor": 2 574 | } 575 | -------------------------------------------------------------------------------- /notebooks/A1_Tests.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "source": [ 7 | "import finoptions as fo" 8 | ], 9 | "outputs": [], 10 | "metadata": {} 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "source": [ 16 | "tmp = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01)" 17 | ], 18 | "outputs": [], 19 | "metadata": {} 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "source": [ 25 | "df = type(tmp)" 26 | ], 27 | "outputs": [], 28 | "metadata": {} 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "source": [ 34 | "df.get_params()" 35 | ], 36 | "outputs": [], 37 | "metadata": {} 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 14, 42 | "source": [ 43 | "import numpy as np" 44 | ], 45 | "outputs": [], 46 | "metadata": {} 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 15, 51 | "source": [ 52 | "import scipy" 53 | ], 54 | "outputs": [], 55 | "metadata": {} 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 16, 60 | "source": [ 61 | "from scipy.stats import qmc" 62 | ], 63 | "outputs": [], 64 | "metadata": {} 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 17, 69 | "source": [ 70 | "scipy.__version__" 71 | ], 72 | "outputs": [ 73 | { 74 | "output_type": "execute_result", 75 | "data": { 76 | "text/plain": [ 77 | "'1.6.2'" 78 | ] 79 | }, 80 | "metadata": {}, 81 | "execution_count": 17 82 | } 83 | ], 84 | "metadata": {} 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 34, 89 | "source": [ 90 | "np.random.normal(0,1,10)" 91 | ], 92 | "outputs": [ 93 | { 94 | "output_type": "execute_result", 95 | "data": { 96 | "text/plain": [ 97 | "array([-0.62182833, 0.18758268, 0.02240758, -1.00782389, 0.73914742,\n", 98 | " -0.17836641, -1.2207374 , -2.01569437, 1.46677197, 0.51673168])" 99 | ] 100 | }, 101 | "metadata": {}, 102 | "execution_count": 34 103 | } 104 | ], 105 | "metadata": {} 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "source": [], 111 | "outputs": [], 112 | "metadata": {} 113 | } 114 | ], 115 | "metadata": { 116 | "kernelspec": { 117 | "name": "python3", 118 | "display_name": "Python 3.9.5 64-bit" 119 | }, 120 | "language_info": { 121 | "codemirror_mode": { 122 | "name": "ipython", 123 | "version": 3 124 | }, 125 | "file_extension": ".py", 126 | "mimetype": "text/x-python", 127 | "name": "python", 128 | "nbconvert_exporter": "python", 129 | "pygments_lexer": "ipython3", 130 | "version": "3.9.5" 131 | }, 132 | "interpreter": { 133 | "hash": "1f23843d0517fbd80931cf63cde1ba60ddd02d261472889f538e5c68e74c8896" 134 | } 135 | }, 136 | "nbformat": 4, 137 | "nbformat_minor": 5 138 | } -------------------------------------------------------------------------------- /notebooks/test.csv: -------------------------------------------------------------------------------- 1 | 8.788771450865799,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,19.06304757961394,8.47154094163238,3.2765696961565185,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,38.624497164876175,18.79550516301783,8.11616494750355,2.978484023374466,0.885154084458883,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,63.214964822869234,38.624497164876175,18.502748685362977,7.715763534842473,2.6438847666418845,0.6973172946924978,0.13076378409694747,0.0,0.0,0.0,0.0,0.0,0.0,0.0,92.16751214214759,63.214964822869234,38.624497164876175,18.19373081756946,7.259899761534136,2.2615466139190175,0.5009653496696103,0.0693143932389302,0.004481387797906654,0.0,0.0,0.0,0.0,0.0,0.0,126.25592428353198,92.16751214214759,63.214964822869234,38.624497164876175,17.900783834802912,6.726708495179658,1.8124015664294906,0.30329955389408186,0.02349175066509894,0.0,0.0,0.0,0.0,0.0,0.0,0.0,166.3912474213044,126.25592428353198,92.16751214214759,63.214964822869234,38.624497164876175,17.73890485513961,6.039993913974137,1.2751240683191605,0.12314541258155844,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,213.64613734378912,166.3912474213044,126.25592428353198,92.16751214214759,63.214964822869234,38.624497164876175,17.73890485513961,5.03412016038635,0.6455369314987739,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,269.2835272290244,213.64613734378912,166.3912474213044,126.25592428353198,92.16751214214759,63.214964822869234,38.624497164876175,17.73890485513961,3.3839500895160266,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,269.2835272290244,213.64613734378912,166.3912474213044,126.25592428353198,92.16751214214759,63.214964822869234,38.624497164876175,17.73890485513961,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 -------------------------------------------------------------------------------- /notebooks/tree3dplot,ipynb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbcho/finoptions-dev/d89d4bf857af13ae8c6470a26105910cfe074f5d/notebooks/tree3dplot,ipynb -------------------------------------------------------------------------------- /pytest/examples.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | 3 | import finoptions as fo 4 | import numdifftools as nd 5 | 6 | opt = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1) 7 | 8 | # print(opt.delta()) 9 | 10 | # opt = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1) 11 | 12 | # # # print(opt.greeks()) 13 | 14 | # # vol = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01) 15 | 16 | # # # print(vol.volatility(3).root) 17 | 18 | # # # opt.summary() 19 | 20 | # # # print(opt) 21 | 22 | # print(opt.__repr__()) 23 | 24 | # opt = fo.BlackScholesOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1) 25 | 26 | # # print(opt.greeks()) 27 | 28 | # vol = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01) 29 | 30 | # # print(vol.volatility(3).root) 31 | 32 | # # opt.summary() 33 | 34 | # # print(opt) 35 | 36 | # print(opt.__repr__()) 37 | 38 | # opt = fo.Black76Option(10.0, 8.0, 1.0, 0.02, 0.1) 39 | 40 | # # print(opt.greeks()) 41 | 42 | # vol = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01) 43 | 44 | # # print(vol.volatility(3).root) 45 | 46 | # # opt.summary() 47 | 48 | # # print(opt) 49 | 50 | # print(opt.__repr__()) 51 | 52 | # import numpy as np 53 | 54 | # opt = fo.MiltersenSchwartzOption( 55 | # Pt=np.exp(-0.05 / 4), 56 | # FT=95, 57 | # K=80, 58 | # t=1 / 4, 59 | # T=1 / 2, 60 | # sigmaS=0.2660, 61 | # sigmaE=0.2490, 62 | # sigmaF=0.0096, 63 | # rhoSE=0.805, 64 | # rhoSF=0.0805, 65 | # rhoEF=0.1243, 66 | # KappaE=1.045, 67 | # KappaF=0.200, 68 | # ) 69 | # # print(opt.call()) 70 | 71 | # # print(opt.get_params()) 72 | 73 | # opt.summary() 74 | -------------------------------------------------------------------------------- /pytest/foptions_heston_tests.R: -------------------------------------------------------------------------------- 1 | library(fOptions) 2 | 3 | model = list(lambda = -0.5, omega = 2.3e-6, alpha = 2.9e-6, 4 | beta = 0.85, gamma = 184.25) 5 | S = X = 100 6 | Time.inDays = 252 7 | r.daily = 0.05/Time.inDays 8 | sigma.daily = sqrt((model$omega + model$alpha) / 9 | (1 - model$beta - model$alpha * model$gamma^2)) 10 | data.frame(S, X, r.daily, sigma.daily) 11 | 12 | .fstarHN(phi=20, const=0, model=model, S=S, X=X, Time.inDays = Time.inDays, r.daily = r.daily) 13 | 14 | 15 | integrate(.fstarHN, 0, Inf, const = 1, model = model, 16 | S = S, X = X, Time.inDays = Time.inDays, r.daily = r.daily) 17 | 18 | 19 | HNGOption("c", model = model, S = S, X = X, 20 | Time.inDays = Time.inDays, r.daily = r.daily) 21 | 22 | HNGOption("p", model = model, S = S, X = X, 23 | Time.inDays = Time.inDays, r.daily = r.daily) 24 | 25 | HNGOption("c", model = model, S = S, X = 90, 26 | Time.inDays = Time.inDays, r.daily = r.daily) 27 | 28 | HNGOption("p", model = model, S = S, X = 90, 29 | Time.inDays = Time.inDays, r.daily = r.daily) 30 | 31 | HNGGreeks("Delta", "c", model = model, S = S, X = X, 32 | Time.inDays = Time.inDays, r.daily = r.daily) 33 | 34 | HNGGreeks("Delta", "p", model = model, S = S, X = X, 35 | Time.inDays = Time.inDays, r.daily = r.daily) 36 | 37 | 38 | HNGGreeks("Gamma", "c", model = model, S = S, X = X, 39 | Time.inDays = Time.inDays, r.daily = r.daily) 40 | 41 | HNGGreeks("Gamma", "p", model = model, S = S, X = X, 42 | Time.inDays = Time.inDays, r.daily = r.daily) 43 | .Machine$double.eps^0.25 44 | 45 | 46 | ##### 47 | 48 | library(fOptions) 49 | 50 | 51 | ## hngarchSim - 52 | # Simulate a Heston Nandi Garch(1,1) Process: 53 | # Symmetric Model - Parameters: 54 | model = list(lambda = 4, omega = 8e-5, alpha = 6e-5, 55 | beta = 0.7, gamma = 0, rf = 0) 56 | 57 | n = 500 58 | n.start = 100 59 | inno = rnorm(n) 60 | start.inno = rnorm(n.start) 61 | 62 | write.csv(inno, file='inno.csv', row.names=FALSE) 63 | write.csv(start.inno, file='start_inno.csv', row.names=FALSE) 64 | 65 | ts = hngarchSim(model = model, n = 500, n.start = 100, inno=inno, start.innov = start.inno) 66 | write.csv(ts, file='ts.csv', row.names=FALSE) 67 | 68 | 69 | 70 | par(mfrow = c(2, 1), cex = 0.75) 71 | ts.plot(ts, col = "steelblue", main = "HN Garch Symmetric Model") 72 | grid() 73 | 74 | 75 | mle = hngarchFit(x = ts, symmetric = TRUE) 76 | mle 77 | 78 | 79 | symmetric=TRUE 80 | rfr = model$rf 81 | lambda = model$lambda 82 | omega = model$omega 83 | alpha = model$alpha 84 | beta = model$beta 85 | gam = model$gamma 86 | 87 | # Continue: 88 | params = c(lambda = lambda, omega = omega, alpha = alpha, 89 | beta = beta, gamma = gam, rf = rfr) 90 | 91 | # Transform Parameters and Calculate Start Parameters: 92 | par.omega = -log((1-omega)/omega) # for 2 93 | par.alpha = -log((1-alpha)/alpha) # for 3 94 | par.beta = -log((1-beta)/beta) # for 4 95 | par.start = c(lambda, par.omega, par.alpha, par.beta) 96 | if (!symmetric) par.start = c(par.start, gam) 97 | 98 | # Initial Log Likelihood: 99 | opt = list() 100 | opt$value = .llhHNGarch(par = par.start, 101 | trace = trace, symmetric = symmetric, rfr = rfr, x = x) 102 | opt$estimate = par.start 103 | if (trace) { 104 | print(c(lambda, omega, alpha, beta, gam)) 105 | print(opt$value) 106 | } 107 | 108 | # Estimate Parameters: 109 | opt = nlm(.llhHNGarch, par.start, 110 | trace = trace, symmetric = symmetric, rfr = rfr, x = x) 111 | 112 | 113 | opt 114 | -------------------------------------------------------------------------------- /pytest/foptions_mc_tests.R: -------------------------------------------------------------------------------- 1 | library(fOptions) 2 | 3 | # Monte Carlo Sims 4 | 5 | pathLength <- 30 6 | mcSteps <- 5000 7 | mcLoops <- 50 8 | delta.t = 1/360 9 | TypeFlag <- "c"; S <- 100; X <- 100 10 | Time <- 1/12; sigma <- 0.4; r <- 0.10; b <- 0.1 11 | 12 | 13 | ## First Step: 14 | # Write a function to generate the option's innovations. 15 | # Use scrambled normal Sobol numbers: 16 | sobolInnovations <- function(mcSteps, pathLength, init, ...) { 17 | # Create and return Normal Sobol Innovations: 18 | runif.sobol(mcSteps, pathLength, init, ...) 19 | } 20 | inno <- sobolInnovations(mcSteps, pathLength, init=TRUE) 21 | inno 22 | 23 | # save to csv to test vs python. Python seems to generate different 24 | # sobol sequences, even when unscrambled... 25 | write.table(inno, "sobol.csv", row.names = FALSE, sep=",", col.names=FALSE) 26 | inno <- read.csv("sobol.csv", header=FALSE) 27 | 28 | # scrambled innos 29 | inno <- sobolInnovations(mcSteps, pathLength, init=TRUE, scrambling=2) 30 | 31 | write.table(inno, "sobol_scrambled.csv", row.names = FALSE, sep=",", col.names=FALSE) 32 | inno <- read.csv("sobol.csv", header=FALSE) 33 | 34 | 35 | 36 | 37 | # Second Step: 38 | # Write a function to generate the option's price paths. 39 | # Use a Wiener path: 40 | wienerPath <- function(eps) { 41 | # Note, the option parameters must be globally defined! 42 | # Generate and return the Paths: 43 | (b-sigma*sigma/2)*delta.t + sigma*sqrt(delta.t)*eps 44 | } 45 | 46 | wienerPath(inno) %>% write.table("wiener.csv", row.names = FALSE, sep=",", col.names=FALSE) 47 | 48 | wp <- wienerPath(inno) 49 | 50 | ## Third Step: 51 | # Write a function for the option's payoff 52 | # Example 1: use the payoff for a plain Vanilla Call or Put: 53 | plainVanillaPayoff <- function(path) { 54 | # Note, the option parameters must be globally defined! 55 | # Compute the Call/Put Payoff Value: 56 | ST <- S*exp(sum(path)) 57 | if (TypeFlag == "c") payoff <- exp(-r*Time)*max(ST-X, 0) 58 | if (TypeFlag == "p") payoff <- exp(-r*Time)*max(0, X-ST) 59 | # Return Value: 60 | payoff 61 | } 62 | 63 | 64 | plainVanillaPayoff(t(wp[1,])) 65 | plainVanillaPayoff(t(wp[10,])) 66 | 67 | TypeFlag <- "p" 68 | plainVanillaPayoff(t(wp[1,])) 69 | plainVanillaPayoff(t(wp[10,])) 70 | 71 | X <- 140 72 | plainVanillaPayoff(t(wp[1,])) 73 | plainVanillaPayoff(t(wp[10,])) 74 | 75 | # Example 2: use the payoff for an arithmetic Asian Call or Put: 76 | arithmeticAsianPayoff <- function(path) { 77 | # Note, the option parameters must be globally defined! 78 | # Compute the Call/Put Payoff Value: 79 | SM <- mean(S*exp(cumsum(path))) 80 | if (TypeFlag == "c") payoff <- exp(-r*Time)*max(SM-X, 0) 81 | if (TypeFlag == "p") payoff <- exp(-r*Time)*max(0, X-SM) 82 | # Return Value: 83 | payoff 84 | } 85 | TypeFlag <- "c" 86 | X <- 100 87 | 88 | arithmeticAsianPayoff(t(wp[1,])) 89 | arithmeticAsianPayoff(t(wp[10,])) 90 | 91 | TypeFlag <- "p" 92 | arithmeticAsianPayoff(t(wp[1,])) 93 | arithmeticAsianPayoff(t(wp[10,])) 94 | 95 | X <- 140 96 | arithmeticAsianPayoff(t(wp[1,])) 97 | arithmeticAsianPayoff(t(wp[10,])) 98 | 99 | ## Final Step: 100 | # Set Global Parameters for the plain Vanilla / arithmetic Asian Options: 101 | 102 | pathLength <- 30 103 | mcSteps <- 5000 104 | mcLoops <- 50 105 | delta.t = 1/360 106 | TypeFlag <- "c"; S <- 100; X <- 100 107 | Time <- 1/12; sigma <- 0.4; r <- 0.10; b <- 0.1 108 | 109 | MonteCarloOption = function(delta.t, pathLength, mcSteps, mcLoops, 110 | init = TRUE, innovations.gen, path.gen, payoff.calc, antithetic = TRUE, 111 | standardization = FALSE, trace = TRUE, ...) 112 | { 113 | 114 | # Monte Carlo Simulation: 115 | delta.t <<- delta.t 116 | if (trace) cat("\nMonte Carlo Simulation Path:\n\n") 117 | iteration = rep(0, length = mcLoops) 118 | # MC Iteration Loop: 119 | cat("\nLoop:\t", "No\t") 120 | for ( i in 1:mcLoops ) { 121 | if ( i > 1) init = FALSE 122 | # Generate Innovations: 123 | eps = innovations.gen(mcSteps, pathLength, init = init, ...) 124 | # Use Antithetic Variates if requested: 125 | if (antithetic) 126 | eps = rbind(eps, -eps) 127 | # Standardize Variates if requested: 128 | if (standardization) eps = 129 | (eps-mean(eps))/sqrt(var(as.vector(eps))) 130 | # Calculate for each path the option price: 131 | path = t(path.gen(eps)) 132 | payoff = NULL 133 | k = 0 134 | 135 | for (j in 1:dim(path)[2]) 136 | payoff = c(payoff, payoff.calc(path[, j])) 137 | iteration[i] = mean(payoff) 138 | # Trace the Simualtion if desired: 139 | if (trace) 140 | cat("\nLoop:\t", i, "\t:", iteration[i], sum(iteration)/i ) 141 | } 142 | if (trace) cat("\n") 143 | 144 | # Return Value: 145 | iteration 146 | } 147 | 148 | 149 | 150 | # Do the Asian Simulation with scrambled random numbers: 151 | mc <- MonteCarloOption(delta.t = delta.t, pathLength = pathLength, mcSteps = mcSteps, 152 | mcLoops = mcLoops, init = TRUE, innovations.gen = sobolInnovations, 153 | path.gen = wienerPath, payoff.calc = plainVanillaPayoff, 154 | antithetic = FALSE, standardization = FALSE, trace = TRUE, 155 | scrambling = 2, seed = 4711) 156 | mc 157 | dim(mc) 158 | mc[,1] 159 | -------------------------------------------------------------------------------- /pytest/foptions_mc_tests_final.R: -------------------------------------------------------------------------------- 1 | library(fOptions) 2 | 3 | ## How to perform a Monte Carlo Simulation? 4 | ## First Step: 5 | # Write a function to generate the option's innovations. 6 | # Use scrambled normal Sobol numbers: 7 | sobolInnovations <- function(mcSteps, pathLength, init, ...) { 8 | # Create and return Normal Sobol Innovations: 9 | rnorm.sobol(mcSteps, pathLength, init, ...) 10 | } 11 | ## Second Step: 12 | # Write a function to generate the option's price paths. 13 | # Use a Wiener path: 14 | wienerPath <- function(eps) { 15 | # Note, the option parameters must be globally defined! 16 | 17 | # Generate and return the Paths: 18 | (b-sigma*sigma/2)*delta.t + sigma*sqrt(delta.t)*eps 19 | } 20 | ## Third Step: 21 | # Write a function for the option's payoff 22 | # Example 1: use the payoff for a plain Vanilla Call or Put: 23 | plainVanillaPayoff <- function(path) { 24 | # Note, the option parameters must be globally defined! 25 | # Compute the Call/Put Payoff Value: 26 | ST <- S*exp(sum(path)) 27 | if (TypeFlag == "c") payoff <- exp(-r*Time)*max(ST-X, 0) 28 | if (TypeFlag == "p") payoff <- exp(-r*Time)*max(0, X-ST) 29 | # Return Value: 30 | payoff 31 | } 32 | # Example 2: use the payoff for an arithmetic Asian Call or Put: 33 | arithmeticAsianPayoff <- function(path) { 34 | # Note, the option parameters must be globally defined! 35 | # Compute the Call/Put Payoff Value: 36 | SM <- mean(S*exp(cumsum(path))) 37 | if (TypeFlag == "c") payoff <- exp(-r*Time)*max(SM-X, 0) 38 | if (TypeFlag == "p") payoff <- exp(-r*Time)*max(0, X-SM) 39 | # Return Value: 40 | payoff 41 | } 42 | ## Final Step: 43 | # Set Global Parameters for the plain Vanilla / arithmetic Asian Options: 44 | TypeFlag <- "c"; S <- 100; X <- 100 45 | Time <- 1/12; sigma <- 0.4; r <- 0.10; b <- 0.1 46 | # Do the Asian Simulation with scrambled random numbers: 47 | mc <- MonteCarloOption(delta.t = 1/360, pathLength = 30, mcSteps = 5000, 48 | mcLoops = 50, init = TRUE, innovations.gen = sobolInnovations, 49 | path.gen = wienerPath, payoff.calc = arithmeticAsianPayoff, 50 | antithetic = TRUE, standardization = FALSE, trace = TRUE, 51 | scrambling = 2, seed = 4711) 52 | -------------------------------------------------------------------------------- /pytest/foptions_tests.R: -------------------------------------------------------------------------------- 1 | library(fOptions) 2 | 3 | GBSOption(c('c'), 10, 8, 1, 0.02, 0.01, 0.1) 4 | 5 | GBSOption(c('p'), 10, 8, 1, 0.02, 0.01, 0.1) 6 | 7 | # S = 10 8 | # X = 8 9 | # Time = 1 10 | # r = 0 11 | # b = 0 12 | # sigma = 0.1 13 | # 14 | # d1 = ( log(S/X) + (b+sigma*sigma/2)*Time ) / (sigma*sqrt(Time)) 15 | 16 | p <- GBSOption(c('p'), 10, 8, 1, 0.02, 0.01, 0.1) 17 | 18 | gk <- c('Delta','Theta','Vega','Rho','Lambda','Gamma','CofC') 19 | 20 | for (g in gk) { 21 | put <- GBSGreeks(Selection = g, TypeFlag = c('c'), 10, 8, 1, 0.02, 0.01, 0.1) 22 | call <- GBSGreeks(Selection = g, TypeFlag = c('p'), 10, 8, 1, 0.02, 0.01, 0.1) 23 | 24 | print(paste(g, round(put,6))) 25 | print(paste(g, round(call,6))) 26 | } 27 | 28 | GBSGreeks(Selection = "Delta", TypeFlag = c('c'), 10, 8, 1, 0.02, 0.01, 0.1) 29 | GBSGreeks(Selection = "Delta", TypeFlag = c('p'), 10, 8, 1, 0.02, 0.01, 0.1) 30 | 31 | GBSGreeks(Selection = "Theta", TypeFlag = c('c'), 10, 8, 1, 0.02, 0.01, 0.1) 32 | GBSGreeks(Selection = "Delta", TypeFlag = c('p'), 10, 8, 1, 0.02, 0.01, 0.1) 33 | 34 | GBSVolatility(2.5, c('c'), 10, 8, 1, 0.02, 0.01) 35 | GBSVolatility(2.5, c('p'), 10, 8, 1, 0.02, 0.01) 36 | 37 | 38 | Black76Option(c('c'), 10, 8, 1, 0.02, 0.1) 39 | 40 | 41 | MiltersenSchwartzOption(TypeFlag = "c", Pt = exp(-0.05/4), FT = 95,X = 80, time = 1/4, Time = 1/2, sigmaS = 0.2660, sigmaE = 0.2490,sigmaF = 0.0096, rhoSE = 0.805, rhoSF = 0.0805, rhoEF = 0.1243,KappaE = 1.045, KappaF = 0.200) 42 | MiltersenSchwartzOption(TypeFlag = "p", Pt = exp(-0.05/4), FT = 95,X = 80, time = 1/4, Time = 1/2, sigmaS = 0.2660, sigmaE = 0.2490,sigmaF = 0.0096, rhoSE = 0.805, rhoSF = 0.0805, rhoEF = 0.1243,KappaE = 1.045, KappaF = 0.200) 43 | 44 | 45 | RollGeskeWhaleyOption(S = 80, X = 82, Time2 = 1/3, time1 = 1/4, r = 0.06, D = 4, sigma = 0.30) 46 | 47 | BAWAmericanApproxOption(TypeFlag = "c", S = 100,X = 100, Time = 0.5, r = 0.10, b = 0, sigma = 0.25) 48 | BAWAmericanApproxOption(TypeFlag = "p", S = 100,X = 100, Time = 0.5, r = 0.10, b = 0, sigma = 0.25) 49 | 50 | .bawKp(X=100, Time=0.5, r=0.1, b=0, sigma=0.25) 51 | 52 | BAWAmPutApproxOptiontest <- function(S, X, Time, r, b, sigma) 53 | { 54 | # Internal Function - The Put: 55 | 56 | # Compute: 57 | Sk = .bawKp(X, Time, r, b, sigma) 58 | n = 2*b/sigma^2 59 | K = 2*r/(sigma^2*(1-exp(-r*Time))) 60 | d1 = (log(Sk/X)+(b+sigma^2/2)*Time)/(sigma*sqrt(Time)) 61 | Q1 = (-(n-1)-sqrt((n-1)^2+4*K))/2 62 | a1 = -(Sk/Q1)*(1-exp((b-r)*Time)*CND(-d1)) 63 | if(S > Sk) { 64 | result = GBSOption("p", S, X, Time, r, b, sigma)@price + a1*(S/Sk)^Q1 65 | } else { 66 | result = X-S 67 | } 68 | 69 | # Return Value: 70 | GBSOption("p", S, X, Time, r, b, sigma) 71 | } 72 | BAWAmPutApproxOptiontest( S = 100,X = 100, Time = 0.5, r = 0.10, b = 0, sigma = 0.25) 73 | 74 | CRRBinomialTreeOption(TypeFlag = "ce", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 75 | CRRBinomialTreeOption(TypeFlag = "pe", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 76 | CRRBinomialTreeOption(TypeFlag = "ca", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 77 | CRRBinomialTreeOption(TypeFlag = "pa", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 78 | 79 | 80 | JRBinomialTreeOption(TypeFlag = "ce", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 81 | JRBinomialTreeOption(TypeFlag = "pe", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 82 | JRBinomialTreeOption(TypeFlag = "ca", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 83 | JRBinomialTreeOption(TypeFlag = "pa", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 84 | 85 | TIANBinomialTreeOption(TypeFlag = "ce", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 86 | TIANBinomialTreeOption(TypeFlag = "pe", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 87 | TIANBinomialTreeOption(TypeFlag = "ca", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 88 | TIANBinomialTreeOption(TypeFlag = "pa", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5) 89 | 90 | print(BinomialTreeOption(TypeFlag = "ce", S = 50, X = 40,Time = 5/12, r = 0.1, b = 0.1, sigma = 0.4, n = 5)) 91 | 92 | CRRTree = BinomialTreeOption(TypeFlag = "ce", S = 50, X = 50, 93 | Time = 0.4167, r = 0.1, b = 0.1, sigma = 0.4, n = 8) 94 | BinomialTreePlot(CRRTree, dy = 1, cex = 0.8, ylim = c(-6, 7), 95 | xlab = "n", ylab = "Option Value") 96 | title(main = "Option Tree") 97 | CRRTree -------------------------------------------------------------------------------- /pytest/foptions_trinomial_tests.R: -------------------------------------------------------------------------------- 1 | # Exercise: Trinomial Tree 2 | 3 | # Write a function to compute the call and put price of an 4 | # European or American style option using a trinomial tree 5 | # approach. 6 | 7 | # For original code see here: 8 | # https://r-forge.r-project.org/scm/viewvc.php/pkg/fOptions/demo/xmpDWChapter081.R?view=markup&root=rmetrics&pathrev=130 9 | 10 | TrinomialTreeOption = 11 | function(AmeEurFlag, CallPutFlag, S, X, Time, r, b, sigma, n) 12 | { # A function implemented by Diethelm Wuertz 13 | 14 | # Description: 15 | # Calculates option prices from the Trinomial tree model. 16 | 17 | # Arguments: 18 | # AmeEurFlag - a character value, either "a" or "e" for 19 | # a European or American style option 20 | # CallPutFlag - a character value, either "c" or "p" for 21 | # a call or put option 22 | # S, X, Time, r, b, sigma - the usual option parameters 23 | # n - an integer value, the depth of the tree 24 | 25 | # Value: 26 | # Returns the price of the options. 27 | 28 | # Details: 29 | # Trinomial trees in option pricing are similar to 30 | # binomial trees. Trinomial trees can be used to 31 | # price both European and American options on a single 32 | # underlying asset. 33 | # Because the asset price can move in three directions 34 | # from a given node, compared with only two in a binomial 35 | # tree, the number of time steps can be reduced to attain 36 | # the same accuracy as in the binomial tree. 37 | 38 | # Reference: 39 | # E.G Haug, The Complete Guide to Option Pricing Formulas 40 | # Chapter 3.2 41 | 42 | # FUNCTION: 43 | 44 | # Settings: 45 | OptionValue = rep(0, times=2*n+1) 46 | 47 | # Call-Put Flag: 48 | if (CallPutFlag == "c") z = +1 49 | if (CallPutFlag == "p") z = -1 50 | 51 | # Time Interval: 52 | dt = Time/n 53 | 54 | # Up-and-down jump sizes: 55 | u = exp(+sigma * sqrt(2*dt)) 56 | d = exp(-sigma * sqrt(2*dt)) 57 | 58 | # Probabilities of going up and down: 59 | pu = ((exp(b * dt/2) - exp( -sigma * sqrt(dt/2))) / 60 | (exp(sigma * sqrt(dt/2)) - exp(-sigma * sqrt(dt/2)))) ^ 2 61 | pd = (( exp(sigma * sqrt(dt/2)) - exp( b * dt/2)) / 62 | (exp(sigma * sqrt(dt/2)) - exp(-sigma * sqrt(dt/2)))) ^ 2 63 | 64 | # Probability of staying at the same asset price level: 65 | pm = 1 - pu - pd 66 | Df = exp(-r*dt) 67 | for (i in 0:(2*n)) { 68 | OptionValue[i+1] = max(0, z*(S*u^max(i-n, 0) * 69 | d^max(n*2-n-i, 0) - X))} 70 | 71 | for (j in (n-1):0) { 72 | for (i in 0:(j*2)) { 73 | 74 | # European Type: 75 | if (AmeEurFlag == "e") { 76 | OptionValue[i+1] = ( 77 | pu * OptionValue[i+3] + 78 | pm * OptionValue[i+2] + 79 | pd * OptionValue[i+1]) * Df } 80 | # American Type: 81 | if (AmeEurFlag == "a") { 82 | a <- (z*(S*u^max(i-j, 0) * 83 | d ^ max(j*2-j-i, 0) - X)) 84 | b <- ( 85 | pu * OptionValue[i+3] + 86 | pm * OptionValue[i+2] + 87 | pd * OptionValue[i+1]) * Df 88 | 89 | OptionValue[i+1] = max(a, b) 90 | print(paste(i,j, OptionValue[i+1])) 91 | } } } 92 | TrinomialTree = OptionValue[1] 93 | 94 | # Return Value: 95 | TrinomialTree 96 | } 97 | 98 | # Example: 99 | TrinomialTreeOption(AmeEurFlag = "a", CallPutFlag = "c", S = 100, 100 | X = 100, Time = 3, r = 0.03, b = -0.04, sigma = 0.2, n = 9) 101 | 102 | TrinomialTreeOption(AmeEurFlag = "a", CallPutFlag = "p", S = 100, 103 | X = 110, Time = 0.5, r = 0.1, b = 0.1, sigma = 0.27, n = 30) 104 | 105 | TrinomialTreeOption(AmeEurFlag = "a", CallPutFlag = "c", S = 100, 106 | X = 110, Time = 0.5, r = 0.1, b = 0.1, sigma = 0.27, n = 30) 107 | 108 | TrinomialTreeOption(AmeEurFlag = "e", CallPutFlag = "p", S = 100, 109 | X = 110, Time = 0.5, r = 0.1, b = 0.1, sigma = 0.27, n = 30) 110 | 111 | TrinomialTreeOption(AmeEurFlag = "e", CallPutFlag = "c", S = 100, 112 | X = 110, Time = 0.5, r = 0.1, b = 0.1, sigma = 0.27, n = 30) 113 | 114 | TrinomialTreeOption(AmeEurFlag = "a", CallPutFlag = "c", S = 100, 115 | X = 100, Time = 3, r = 0.03, b = -0.04, sigma = 0.2, n = 9) 116 | 117 | -------------------------------------------------------------------------------- /pytest/garch_ts.csv: -------------------------------------------------------------------------------- 1 | "x" 2 | 0.00154138848886074 3 | -0.020247374741937 4 | 0.0465641725017623 5 | 0.0233631072383166 6 | 0.0112545874086917 7 | 0.00515510487377738 8 | 0.00308323462074515 9 | -0.0184076662823556 10 | -0.0271268613069255 11 | -0.00530769368586318 12 | 0.0273769784084675 13 | -0.00175403141225864 14 | 0.00096065995194433 15 | -1.15775477159993e-05 16 | 0.0171661933592477 17 | 0.0327644823803297 18 | 0.00587747070895487 19 | 0.00348775033110529 20 | -0.000101051606683798 21 | -0.0130738954336094 22 | 0.013137880336366 23 | -0.00537379924133675 24 | 0.0257242984718568 25 | -0.00463300834763627 26 | -0.0160035871943988 27 | -0.0383887726039346 28 | -0.00946214417441655 29 | -0.0205945663738912 30 | 0.0233917900040973 31 | 0.0169622121242698 32 | -0.0597379939910443 33 | -0.033131321718824 34 | 0.0146935213472885 35 | 0.00875571182238205 36 | 0.0207776798630918 37 | 0.0205499919414893 38 | -0.0312467411427282 39 | 0.0595645552237156 40 | -0.0553769159763128 41 | 0.0276035195530905 42 | 0.00394005110540522 43 | -0.00412980335839731 44 | -0.0119053851406373 45 | 0.00997933433356861 46 | 0.00991320784301125 47 | 0.0435191767769406 48 | 0.0271435019727357 49 | 0.0374346335124015 50 | 0.0190576051945774 51 | 0.00678405175868762 52 | 0.0240466346341444 53 | 0.0197078594965778 54 | 0.00381002421014243 55 | -0.0139047242119774 56 | 0.0318338751696487 57 | 0.00676359205008353 58 | 0.0114935630567996 59 | -0.0209934979674114 60 | -0.0331484718635034 61 | -0.0034024475021514 62 | 0.027977007272521 63 | 0.0244203212922361 64 | -0.029909925359657 65 | 0.00632374702103717 66 | -0.0136818586266523 67 | -0.0236024228627558 68 | 0.031368247625301 69 | 0.00474857669295276 70 | 0.0353093251961946 71 | -0.00756949467832656 72 | 0.0391073332680775 73 | -0.0303749091679863 74 | -0.000584714415974265 75 | 0.0230685022881162 76 | 0.0026535850140492 77 | 0.0157455201318429 78 | 0.0056476104879925 79 | -0.0243400790776482 80 | 0.0105610189977298 81 | 0.0252488391741517 82 | 0.00211668916080853 83 | -0.00950692761674875 84 | 0.00136581859791106 85 | 0.013081181584301 86 | -0.00569901417910563 87 | 0.00246651883245582 88 | 0.0134341870047622 89 | 0.00809065445705534 90 | -0.00136186125783936 91 | -0.00409428239367772 92 | -0.0057927386487287 93 | 0.000370965634052516 94 | 0.0265500811300572 95 | -0.00989712227943999 96 | 0.02466581760936 97 | 0.0093570625997044 98 | -0.0115197141326363 99 | -0.00242936595729182 100 | 0.00459380163051719 101 | -0.00195452927142519 102 | -0.0176743026764011 103 | 0.0251195487913139 104 | -0.00324422298572973 105 | 0.00481012237871973 106 | -0.0116220185728257 107 | -0.000235664848182403 108 | -0.0132001647131454 109 | -0.0287630135137727 110 | -0.0137550915063577 111 | 0.0198371757624232 112 | -0.000777464886680679 113 | 0.0049233195144419 114 | -0.00352463306674997 115 | -0.0140227697157418 116 | -0.0162190359407705 117 | 0.00517711596382526 118 | -0.0155281609962759 119 | 0.0185837475316363 120 | 0.0113762308226365 121 | 0.00401757116059086 122 | 0.0171793338682297 123 | -0.0045267841395594 124 | -0.041070321761717 125 | 0.0298337729010399 126 | 0.0149213725254347 127 | -0.0197786530577085 128 | -0.00304127331453903 129 | -0.00730156651139032 130 | -0.0664249189253763 131 | -0.0421403397190472 132 | 0.00672190503686108 133 | -0.0105265311437189 134 | 0.036962724312689 135 | -0.0983081936393472 136 | -0.00295295888542247 137 | 0.0117582796919472 138 | -0.00136816019979303 139 | 0.0260260121786755 140 | -0.0359749337093006 141 | -0.0150238833228868 142 | 0.0203252409339207 143 | 0.00220552523631182 144 | 0.00883044132469987 145 | -0.0116272527188201 146 | -0.0369901189355786 147 | -0.00950651986188603 148 | 0.00918001978407398 149 | 0.0147824135063955 150 | -0.0114847785512944 151 | 0.0103131053052097 152 | 0.00161518288804869 153 | 0.00189833575403888 154 | -0.0184820107951195 155 | 0.0301861969780638 156 | 0.00702269528383291 157 | -0.0202338167714361 158 | -0.0174124785344051 159 | -0.0187419649302267 160 | 0.00670509947879193 161 | 0.00564339829467258 162 | 0.000985060536714423 163 | -0.00585348809394405 164 | -0.0133012271913133 165 | -0.000825739953676115 166 | -0.0256497952990083 167 | 0.0719980031565108 168 | 0.0116129494898042 169 | -0.0100056024649101 170 | -0.020543596400271 171 | 0.0178309592255787 172 | -0.00495928152922726 173 | 0.010673060193103 174 | 0.00569830755628196 175 | -0.0105215807963815 176 | -0.0248056849404396 177 | -0.00679190013649451 178 | 0.0193678482310515 179 | 0.00701877135112768 180 | -0.00567725026150406 181 | 0.00404618979296387 182 | 0.0105409677460771 183 | -0.00341312878418994 184 | 0.0223646322208003 185 | 0.0193217335365454 186 | -0.01468218762536 187 | 0.0197173558255664 188 | 0.00951616162649293 189 | 0.0234162568038925 190 | 0.02393943818187 191 | 0.0214375345984986 192 | -0.00303518017482997 193 | -0.0132260718340115 194 | 0.0165172097248129 195 | -0.0176896859115824 196 | 0.00752336884552323 197 | -0.017934959426391 198 | 0.0108892460710346 199 | -0.00391290196540349 200 | -0.0107962691082597 201 | 0.00256894312895376 202 | -0.0198563048952336 203 | 0.021932405205322 204 | 0.00797323837613359 205 | -0.026106032871327 206 | -0.0146491717414308 207 | -0.0224934858757966 208 | -0.0407764647199228 209 | 0.00912957733043039 210 | 0.0277522066326325 211 | 0.0320103482963659 212 | 0.0253691943614999 213 | 0.00356660929483655 214 | 0.0109240888171712 215 | 0.019530954476272 216 | 0.0441556242254739 217 | 0.0384456960087166 218 | -0.0120200362708764 219 | 0.0371049420058503 220 | 0.0378780099376536 221 | 0.019465385841236 222 | 0.0157861039435658 223 | 0.0628095358869046 224 | 0.0123898393743676 225 | 0.00721598725084072 226 | -0.0139731012638922 227 | 0.00600007590431748 228 | -0.00247073120747683 229 | 0.0143859467781276 230 | -0.00648448957372663 231 | 0.0156805899320757 232 | -0.0334106938506894 233 | -0.00498149432132885 234 | 0.0191470677761569 235 | -0.0162796273872874 236 | -0.0103783540488269 237 | 0.0569592561181176 238 | 0.00783597592447063 239 | -0.0429767038512506 240 | -0.0235155209941471 241 | 0.0255906958988891 242 | 0.0118648427371753 243 | -0.0446841884445852 244 | -0.0433110780005687 245 | -0.0276694990894342 246 | 0.00968579168636546 247 | -0.0164762375603251 248 | 0.0163388397157851 249 | -0.0122176510884788 250 | -0.00167664947223823 251 | -0.0113668891459001 252 | 0.0176975604378775 253 | -0.0274647141335319 254 | 0.0112050081502896 255 | -0.00516542877710432 256 | -0.00184437143605343 257 | 0.00798353435548725 258 | -0.0115842837089225 259 | 0.0351996496516473 260 | 0.00116549104341096 261 | 0.0104706249605716 262 | 0.0329897481181104 263 | 0.00201982302794046 264 | -0.00314652804449619 265 | -0.00362973545878634 266 | -0.0165466810037155 267 | 0.0211319324927307 268 | 0.0287166132828017 269 | 0.0164927703884963 270 | -0.0121482392372489 271 | 0.00701449523990643 272 | 0.0228425140521879 273 | 0.0225898573678164 274 | 0.0122667736019032 275 | 0.0216683344153182 276 | 0.0115416462715696 277 | 0.011459354863385 278 | 0.00271912594683263 279 | 0.0028837751842838 280 | 0.0407131263453358 281 | 0.0342825481389583 282 | 0.00897804686385318 283 | -0.0151230589225336 284 | -0.0240863517361895 285 | -0.0232491695839757 286 | -0.00211479350280303 287 | -0.017211036668305 288 | -0.0259758011946222 289 | -0.0067228489572919 290 | 0.0220238512618444 291 | 0.0264048505041752 292 | 0.0234642146736966 293 | 0.0472007232180593 294 | 0.0217318990887382 295 | 0.0126888791666443 296 | 0.0257958180574198 297 | -0.00312062606565903 298 | 0.0201223224550341 299 | 0.0285167306065384 300 | -0.0182145686462002 301 | 0.00121561874188637 302 | 0.00519486473099482 303 | -0.0113220649631067 304 | -0.00722608856864642 305 | -0.00647045772216375 306 | -0.0184936468647417 307 | -0.0134083429403588 308 | -0.00453190941608032 309 | 0.000829995724102256 310 | -0.0132162655646313 311 | -0.00135580323083923 312 | 0.0335839941631228 313 | -0.0129366312865396 314 | 0.00485381121787068 315 | 0.0144041051102373 316 | 0.0110987146905707 317 | -0.0178104238885121 318 | -0.0202221230645002 319 | -0.00819320957955445 320 | 0.000772569810268615 321 | -0.0284404639214185 322 | -0.0159284887082869 323 | 0.0119659387257874 324 | -0.0127142949645607 325 | -0.0106437900288411 326 | -0.0307859720208069 327 | -0.0202692899465116 328 | 0.015799034621692 329 | -0.0101194352907825 330 | 0.00281721972393537 331 | -0.0381939383822631 332 | -0.00134047966407873 333 | -0.0123970827520311 334 | -0.0342152653172784 335 | 0.00108230730870611 336 | 0.0221664086886956 337 | -0.0299707446312104 338 | 0.00269024333165588 339 | -0.0214681102261333 340 | -0.00256463760016591 341 | 0.0212763168867943 342 | -0.0602129131899393 343 | -0.000665323378222078 344 | -0.00592131833264465 345 | 0.0299888369186561 346 | -0.00397484480106236 347 | -0.0159395489687264 348 | 0.0196174607333605 349 | 0.0241108909047676 350 | -0.056784097533349 351 | 0.0121523500986235 352 | -0.0300718950212546 353 | 0.00398463046406581 354 | -0.00385715176223552 355 | -0.00748496859208025 356 | -0.00844485248015013 357 | 0.00246727496002058 358 | -0.00191725679517551 359 | -0.0189308685721338 360 | 0.00481298173376195 361 | -0.00707585347018065 362 | 0.00605452579402461 363 | 0.0158031677104165 364 | 0.010728239637117 365 | -0.00248269985955495 366 | 0.00313886937914746 367 | -0.00615316136989765 368 | -0.0224712681289395 369 | -0.019801049510642 370 | -0.0018438544032561 371 | 0.00846949106447689 372 | 0.00678766172818086 373 | 0.00114128034241218 374 | -0.0128906116297184 375 | 0.00269398018039998 376 | -0.0191977442928448 377 | 0.00199396847321971 378 | -0.00330258573681497 379 | -0.00670962239648256 380 | -0.027608621057255 381 | -0.0103869823689816 382 | 0.000249861426747834 383 | 0.0124094643076958 384 | -0.0108092551628072 385 | 0.0242243699737823 386 | 0.0100127044491275 387 | -0.00150401282270274 388 | -0.0014248244213873 389 | -0.00634119195926065 390 | 0.0177859754986379 391 | -0.00112942759291154 392 | 0.0102415372455574 393 | 0.0138093796545686 394 | 0.0119484571790335 395 | -0.00181013264511039 396 | 0.00532313379944211 397 | -0.00664150665776999 398 | 0.00928998148910094 399 | -0.0161051227906647 400 | -0.0176737428737465 401 | -0.00889874162912639 402 | -0.020991443968138 403 | -0.00495997891832776 404 | 0.0266689229624258 405 | 0.0230207149542258 406 | -0.00222428962781216 407 | -0.0188889447480549 408 | 0.0117056862024105 409 | -0.0145693630800274 410 | -0.0188384105401825 411 | -0.00827146519623686 412 | 0.0236941664681426 413 | -0.044214997217633 414 | 0.0124433033873464 415 | -0.0178660278008108 416 | -0.0179449223063707 417 | 0.00796849549343209 418 | -0.00249590629759828 419 | 0.0207035712444285 420 | 0.0193620737131095 421 | -1.08182132439912e-06 422 | -0.03037540814085 423 | 0.0278158376558224 424 | 0.012447715980909 425 | 0.0317152382062804 426 | 0.0452730629869563 427 | 0.0172665222112682 428 | -0.0236148417607027 429 | 0.0212785250308472 430 | -0.00192785357985251 431 | 0.0355297993845703 432 | -0.0529911129721679 433 | 0.0236601363679256 434 | -0.0046704439926782 435 | -0.00395259645570495 436 | 0.00515669529437631 437 | 0.0472498664883819 438 | 0.0211621683927085 439 | 0.0227557159540843 440 | 0.0282537574256198 441 | 0.0135941805220496 442 | -0.0075676500288397 443 | 0.0306047253223942 444 | 0.0118279619281601 445 | -0.00441531827618808 446 | -0.00618246688350681 447 | -0.00460932009029668 448 | -0.0151235960929036 449 | 0.0158844495670051 450 | 0.00847861185432921 451 | 0.016941414439287 452 | -0.0105950141464494 453 | -0.0081444385676899 454 | 0.00798290681123995 455 | -0.00417386913364683 456 | 0.0212752502631321 457 | 0.0276496821347283 458 | 0.00204061031006454 459 | -0.00509391290751394 460 | 0.0124776016978875 461 | 0.0279119078487482 462 | -0.0263422480172621 463 | 0.0453491384600626 464 | 0.0244104523469157 465 | -0.033159884018419 466 | -0.00891892406978889 467 | 0.0493584810919336 468 | 0.0258955902032139 469 | -0.0332928180136064 470 | 0.0540861506355423 471 | 0.0137307694523157 472 | 0.0414020555827064 473 | -0.0026034306124303 474 | 0.00430780313526858 475 | 0.00741896424017712 476 | -0.0522656986109614 477 | 0.0364369692624227 478 | -0.0382350512122907 479 | 0.00837003521572572 480 | 0.00979722380528459 481 | 0.00876062522365644 482 | 0.0321721302682329 483 | 1.75008545613003e-06 484 | 0.0140625808699055 485 | 0.00104859538374851 486 | 0.0152942779626923 487 | 0.015830734109373 488 | -0.0148426704877075 489 | 0.0181891556955163 490 | 0.0132418304485945 491 | 0.0312703591949604 492 | -0.0179372922948289 493 | 0.0138704124917705 494 | 0.00757870443458061 495 | -0.00853360312176649 496 | -0.0258581601795298 497 | 0.005505058456781 498 | 0.00345397399863486 499 | 0.0222248924903131 500 | -0.0441107744307838 501 | -0.013803667346057 502 | -------------------------------------------------------------------------------- /pytest/inno.csv: -------------------------------------------------------------------------------- 1 | "x" 2 | 0.59560725164514 3 | -0.122775000356086 4 | 0.369935748315223 5 | -0.97380044372184 6 | -0.38273402790342 7 | -0.447559881059592 8 | -0.193457917676801 9 | 0.293929009682658 10 | -0.995974683298836 11 | 0.111263982939771 12 | 0.387869644803568 13 | 0.228649735446886 14 | 1.09451673120459 15 | 0.485013653870564 16 | -0.18900187889743 17 | -0.0510462247219355 18 | 0.582585275257501 19 | 0.442839257994123 20 | -0.643768900263204 21 | 0.938725051716162 22 | 2.01224761971194 23 | 0.804607381496211 24 | 0.204296238594288 25 | -0.0191944652853611 26 | 0.71151114131628 27 | -1.02959628863021 28 | -1.55931967587791 29 | -0.27240912157705 30 | 1.15237076574261 31 | -0.409820061307498 32 | -0.520547268429555 33 | 0.327454282763898 34 | -0.0497778390496901 35 | 0.773724274009607 36 | 2.31881031302578 37 | 0.303173207922117 38 | 0.951593346691686 39 | 0.431298058888767 40 | 0.783445335537387 41 | 0.4067588867127 42 | -0.471372589990866 43 | 0.252637903447949 44 | 1.17172778456543 45 | -0.142524020398925 46 | -0.0072877287199684 47 | -0.87049047436418 48 | 0.609127666220082 49 | 0.10488294589594 50 | -1.08294552899387 51 | -0.0953580052847071 52 | -1.18933067596192 53 | -0.704779259137055 54 | 0.577401334315559 55 | 0.570800913156662 56 | 0.640617774107707 57 | -0.758265768735599 58 | -2.6784729422196 59 | -2.0998562727667 60 | -1.12672162944679 61 | -0.141814835920696 62 | -1.14214896843292 63 | -1.15230836518487 64 | -0.360615132828673 65 | -2.0283686319227 66 | 0.977100431300604 67 | -1.51428903259077 68 | 1.01823057824195 69 | -1.41409593962342 70 | -1.11311486946508 71 | 0.5213025917559 72 | 0.565343117021142 73 | -1.63101243470154 74 | -1.39658524195259 75 | -0.432997875060616 76 | -0.447386528365521 77 | -1.80413917730578 78 | -1.53442350071052 79 | -0.854796325281372 80 | 0.181854369868659 81 | 0.923647755502591 82 | -0.546814306643648 83 | -1.06565784524795 84 | -0.0299005713489422 85 | 0.144119512495073 86 | 0.756570659978947 87 | -1.48776220633369 88 | 1.7150290700433 89 | 1.0695775909392 90 | 0.195221478579657 91 | -0.932415434197518 92 | -0.500591600055168 93 | -0.893429721554531 94 | -0.792193366656076 95 | -0.17562150957397 96 | 1.65537659650371 97 | 0.19588436289349 98 | 1.53507940122915 99 | -0.0983636050543402 100 | -2.35753997718145 101 | -1.19356269151118 102 | 0.815947923482998 103 | 0.0318105684475044 104 | 0.591155691232449 105 | 0.867744735228823 106 | -0.784759007799063 107 | -0.232348154597313 108 | -0.913259520833728 109 | -0.876904451597879 110 | 0.0234107629286534 111 | -0.904060798518993 112 | -0.791527998190204 113 | -0.0818331496021214 114 | 1.0028359225255 115 | -0.618241008985032 116 | 0.559404567852787 117 | -0.371143169838554 118 | 1.10908232584626 119 | -0.305741323370244 120 | -1.17081476914453 121 | -0.146308577472481 122 | 0.755789483472173 123 | 0.530874442592148 124 | 0.919146320536264 125 | 0.378801857295667 126 | -0.61734894620834 127 | 0.396032131111039 128 | 0.495385780088892 129 | 0.102471637072149 130 | 0.578256095830488 131 | 0.306513562062066 132 | -0.689919184066814 133 | 0.210051546835385 134 | -0.0111904662394842 135 | 0.347375286611594 136 | -0.395021093659389 137 | -1.54633354976557 138 | -0.675492980009338 139 | -0.215059855137678 140 | 0.156943586827199 141 | 0.170211627186109 142 | 2.13728564987725 143 | 1.07282785331087 144 | -0.556996459283694 145 | 1.69752647640451 146 | 0.399954841812991 147 | -0.678348762151226 148 | -0.120932458099565 149 | 1.34950221962859 150 | -0.181404555191572 151 | -0.469175415463587 152 | -0.269419818152088 153 | 1.23260120101988 154 | 1.54106602322438 155 | 0.655144149762568 156 | -0.0162763459460394 157 | -0.273192201871193 158 | 1.22537814848904 159 | 2.3403831903582 160 | 0.693080900319584 161 | 1.17756496161953 162 | 0.366635080229931 163 | -0.337142855173411 164 | -1.71502782859838 165 | 0.623765828600006 166 | -1.39919476207767 167 | -1.20607824681535 168 | -1.04686335401606 169 | -0.386221101190153 170 | 0.39608093507128 171 | 1.12189615782166 172 | 0.407079694066652 173 | -1.12345046241902 174 | -0.28391007442705 175 | 0.598477830927631 176 | -0.170208996809879 177 | -0.0401768879594662 178 | -0.806189697244948 179 | 0.673526297282232 180 | 1.37282493798896 181 | -0.250201796521532 182 | -0.847441936816285 183 | -2.20465678481622 184 | -2.60821803212759 185 | -0.401139199697786 186 | 0.125358754543852 187 | -0.564759074602777 188 | 0.416515910090514 189 | 0.651329298829723 190 | 0.315410213906448 191 | -0.680115899163993 192 | -1.90895995325063 193 | 1.58036954700206 194 | 0.939825000192217 195 | -1.70383513045197 196 | -0.964204661275912 197 | 0.441897744627511 198 | 0.305149677123554 199 | 0.0580748468629945 200 | 0.436315105689116 201 | 1.08579144446013 202 | 0.536309347648282 203 | 0.508794734622219 204 | -1.36798158750776 205 | -0.642460634145081 206 | -1.39604270093824 207 | -0.374512063843145 208 | 0.86139148981497 209 | -0.611832528290929 210 | -1.37805817091259 211 | 0.016288111565743 212 | 1.33413902946145 213 | -0.371975711843584 214 | -0.508282782600387 215 | -0.0544078695079856 216 | -0.450438368688885 217 | 0.931342479450387 218 | 3.02267381446166 219 | -0.401448118835287 220 | 0.0414613844588287 221 | -0.201827036137499 222 | -0.890683910158497 223 | 1.50739205389221 224 | 1.40146569731326 225 | 1.16549103861475 226 | 0.818653295786869 227 | -0.568411025848684 228 | 0.560854403428894 229 | -1.14670751176621 230 | -2.02712747629154 231 | -0.949684561151546 232 | 0.99724837092895 233 | 0.383737488144514 234 | 2.14911901835681 235 | 0.559906932572918 236 | -0.945628064857986 237 | -1.06578315311149 238 | 0.134078871030879 239 | -1.31082234177659 240 | -0.546498981649535 241 | 0.0883007446048119 242 | -1.33754184530751 243 | 1.5506501650274 244 | -1.29723911576903 245 | -2.16688245749879 246 | 1.95825373765474 247 | 0.0594991307683923 248 | -0.418390340154755 249 | -1.64268993928413 250 | 2.63003514625507 251 | -0.903128671089493 252 | -0.75380906576402 253 | -0.730516566457027 254 | 0.396963027995848 255 | -0.00579790092248087 256 | 0.634287091980026 257 | -0.372721630154665 258 | -0.850913172420682 259 | 1.14377275750696 260 | 0.150982057811957 261 | 0.044676522689603 262 | -0.0974593206685775 263 | 0.477722383374692 264 | -1.62917162907499 265 | 0.746932780638713 266 | -0.107542140066224 267 | -0.460256588523791 268 | 0.643382748379724 269 | 0.862366233676036 270 | 0.00236766821186794 271 | -0.0646224131981323 272 | -0.462869064867997 273 | 0.462046011210368 274 | 1.08296319854446 275 | -1.62301632095542 276 | -0.486732806988028 277 | -0.12632245705783 278 | 0.315875663010655 279 | -0.578793657964151 280 | -0.177648297291755 281 | -0.885398128077338 282 | 1.26317189715737 283 | 0.751852964679383 284 | -0.0254562269817065 285 | -0.317276099651033 286 | 0.381900381475088 287 | 0.110962162735679 288 | 0.430358256677537 289 | 0.44975828861385 290 | -1.37434963393782 291 | -0.077015301681253 292 | -0.241395715871432 293 | 0.931347650538042 294 | -0.849262369703687 295 | -1.37204112986623 296 | 0.96897637309009 297 | 1.15077727080515 298 | 0.726209502809803 299 | 0.169739313053327 300 | 0.210062677484419 301 | -0.74110719320685 302 | -0.862774740376789 303 | 0.608212809824683 304 | -1.93023208981145 305 | 0.53322393056674 306 | 0.281736689382421 307 | 0.336705048879736 308 | -0.33288883480739 309 | -1.68778362751283 310 | 0.187537029908165 311 | 0.911108690747516 312 | 0.258368181676661 313 | 1.74201484752838 314 | 0.546334256488127 315 | -1.19797220048536 316 | -1.1464047022321 317 | -0.180735341538217 318 | -0.502989674218675 319 | 1.01810575641288 320 | 1.6650322202707 321 | 0.618945222808868 322 | -1.22699780453777 323 | -0.790766608667489 324 | -1.41057398789658 325 | 0.362545496615605 326 | 0.290299741347529 327 | -0.114457708341611 328 | -0.115839907409155 329 | 0.0209392654869578 330 | 0.360412023833736 331 | 0.200279603168777 332 | -0.781467395147241 333 | -0.253025115618023 334 | -0.472593452101399 335 | 1.06140743634832 336 | -1.03395386564384 337 | 0.0533993249016618 338 | 0.290793840718742 339 | 0.6122352406494 340 | -1.976762824452 341 | 0.447288171089833 342 | 0.266661289616527 343 | 0.805380822674196 344 | -1.24404765517016 345 | -1.58892396230611 346 | 0.507964032828997 347 | 1.70770048812385 348 | -1.92607539212915 349 | -1.29713737430997 350 | -0.773498410759741 351 | 0.936142294889372 352 | -0.323988450221223 353 | 1.25500818381487 354 | -1.19498895571788 355 | -0.388197441549206 356 | -0.543231253394217 357 | -0.558939285996794 358 | -0.500300912507305 359 | -1.37921773486159 360 | 0.673252963187748 361 | 0.115534700199268 362 | 0.999003537503997 363 | -0.585631090144366 364 | 1.27249247721271 365 | 1.05384541921652 366 | 0.575098526214746 367 | -0.761296280683503 368 | -0.437865582845981 369 | 0.960599614389948 370 | -0.34949047880089 371 | 2.07915290231597 372 | -0.722604262709541 373 | -1.14674359588582 374 | 0.468766352879265 375 | -0.0265574032006447 376 | -0.731141765217439 377 | 1.72583840304406 378 | 0.678974364057371 379 | -1.31710058074928 380 | 1.24404392133054 381 | -0.672606610689223 382 | 0.0193935680852448 383 | -0.534356249677535 384 | -0.438630976526519 385 | 0.658997869304966 386 | -0.0229993145677747 387 | -0.726471582468659 388 | 0.118244156732754 389 | 0.517960562098681 390 | -0.923049770553205 391 | -0.0313694691822342 392 | -1.92611086196193 393 | 0.017442209679717 394 | -0.651173988403306 395 | 0.531727783479887 396 | 0.279091182025133 397 | 0.943378318683549 398 | -0.0905805574337852 399 | 1.83718286904883 400 | -1.38188006437489 401 | 1.36034009278849 402 | -0.836726744953523 403 | -0.217673854583859 404 | -0.835395042751727 405 | -0.202052851377913 406 | 0.499986632149327 407 | -0.143009693773059 408 | -0.121520138028929 409 | 0.249369428087077 410 | 0.694323346114701 411 | -0.695463545958366 412 | 0.00371732578377573 413 | -1.07293611613503 414 | 0.436075014550465 415 | -0.36528621334882 416 | 0.278989074391104 417 | 0.590087797364013 418 | -0.936531192915323 419 | 1.47610700252413 420 | 2.38194044353476 421 | -0.893763661031605 422 | 1.03875371402446 423 | 0.0341513968895918 424 | -0.446209622019815 425 | 0.265762483121639 426 | -1.47913968004627 427 | -0.609733843266152 428 | -0.303650210276382 429 | 0.575879536627456 430 | -0.468592010350757 431 | -0.118293442857208 432 | 0.289613513753987 433 | 1.10471448924292 434 | 0.22033146628811 435 | -1.22739456341512 436 | -1.05889413790242 437 | -0.784910027885834 438 | -2.02478995740955 439 | 0.373509682891883 440 | -1.34025773067815 441 | 1.01270156511695 442 | 0.916371954260764 443 | 0.304299615528321 444 | -0.768418869785988 445 | -1.03224584816777 446 | 0.696161749924569 447 | -0.467976988076528 448 | -0.885033644688318 449 | -0.308437468740934 450 | -1.75572060162529 451 | -1.42659773930594 452 | -0.0829917946056845 453 | 0.741236455712942 454 | 0.83256066085154 455 | -0.151548012766086 456 | 0.435320114599704 457 | 1.08234614406948 458 | 1.07454037762233 459 | -0.304729912525929 460 | -1.14725838584142 461 | 0.67131194240729 462 | -0.860172079954381 463 | -0.761499475149155 464 | -0.52008377702845 465 | -0.161711703105502 466 | 0.921237115803053 467 | -0.768495448724967 468 | -0.784799915882076 469 | 1.90313586760281 470 | 0.378901049179564 471 | -0.0392152865061475 472 | 0.491355854991042 473 | 0.379608897493758 474 | 1.24752080251397 475 | -1.09439615175903 476 | 0.118404757623984 477 | 1.70085192043077 478 | 1.25537376916164 479 | 0.125682254234964 480 | 1.15959623133069 481 | 0.236800532733132 482 | 0.7927946731602 483 | 1.10988082154055 484 | 0.329256891710581 485 | -0.651911229283135 486 | 0.778365822417771 487 | 0.358194678745559 488 | -0.0424046680413187 489 | -0.167987954398264 490 | 1.73697165765364 491 | -1.55417737875073 492 | -0.697887305182565 493 | -0.800658471495847 494 | -0.267824233823864 495 | 1.90294065897026 496 | 1.10749097771026 497 | -0.385691789939852 498 | -1.14683092136753 499 | 0.724852777982156 500 | 1.28961305826872 501 | -0.961453794791604 502 | -------------------------------------------------------------------------------- /pytest/start_inno.csv: -------------------------------------------------------------------------------- 1 | "x" 2 | 2.5042570441622 3 | -0.800320635113187 4 | 1.08245214902142 5 | 0.113684312869128 6 | -1.35887140295131 7 | 1.55770618561252 8 | 0.600385084851433 9 | 0.620873838657888 10 | -0.522367944095462 11 | 0.77666427909311 12 | -1.1109451940442 13 | -0.465522523046992 14 | -0.400074262774994 15 | -0.0180265344752725 16 | 0.666247607296863 17 | 1.21013695146841 18 | 1.38219998100707 19 | 1.11444672742914 20 | 0.41664071036489 21 | 0.116029199819664 22 | 0.525982044701858 23 | 0.658505919661777 24 | 1.47764292178823 25 | 0.22565372016616 26 | -0.195608637836338 27 | -0.282930299991352 28 | 1.21286997813057 29 | -0.427959246151434 30 | -1.36302392655479 31 | 0.440756493564525 32 | -0.42851960669193 33 | -0.054028885447072 34 | -0.715239851117811 35 | -2.63521223489945 36 | 1.6517311076215 37 | 2.77050274120134 38 | -0.166248168336346 39 | 0.478233591594354 40 | -0.515925440746589 41 | -0.282939645748896 42 | 0.908549407600339 43 | 1.47571524823728 44 | 0.201487549085186 45 | -1.777629227931 46 | -0.362739103864105 47 | 1.18829394166451 48 | 0.435829773594653 49 | 0.468916220469103 50 | -0.936181948140319 51 | 1.34429689585391 52 | -0.0226230002834127 53 | -0.570031359027282 54 | 0.660516488690929 55 | 0.280067671165821 56 | 0.337736755023115 57 | -0.859743454849888 58 | -0.211816593282208 59 | 0.138306615508166 60 | 0.26125769851912 61 | -1.16232067974642 62 | 2.12546301519035 63 | -1.29166745946815 64 | -0.591460135299583 65 | 0.324380879916555 66 | -1.71039330546752 67 | -0.542480547767191 68 | -1.00252496238311 69 | 1.80862457652081 70 | 0.336734721186806 71 | 1.39592439614427 72 | -0.942315434936408 73 | -0.535412837823263 74 | 1.55448234095938 75 | 0.660544600653462 76 | 1.00246224101927 77 | 1.71702015844134 78 | -0.216728867929119 79 | 0.84880462952109 80 | -0.168736353717319 81 | 0.223681813988948 82 | -0.250231945987265 83 | -1.04967482073722 84 | -0.594193393194545 85 | -0.260072132442667 86 | 1.51105946988673 87 | -0.822248930984054 88 | 0.0894204542326369 89 | -0.692209002827572 90 | -1.57812189760046 91 | -0.0269541278059558 92 | -1.58013010895346 93 | 0.462655836719508 94 | -0.793922767736863 95 | 1.07356470153382 96 | -0.441310785925589 97 | 0.291681965744259 98 | -0.515355343671783 99 | -1.01808702990525 100 | -0.360700229674227 101 | -0.562265257474772 102 | -------------------------------------------------------------------------------- /pytest/test_HNGarch.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | import pandas as pd 5 | 6 | 7 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 8 | 9 | import finoptions as fo 10 | 11 | ts = pd.read_csv("./pytest/garch_ts.csv").x 12 | ts = np.array(ts) 13 | 14 | lamb = 4 15 | omega = 8e-5 16 | alpha = 6e-5 17 | beta = 0.7 18 | gamma = 0 19 | rf = 0 20 | 21 | rfr = rf 22 | 23 | par_omega = -np.log((1 - omega) / omega) 24 | par_alpha = -np.log((1 - alpha) / alpha) 25 | par_beta = -np.log((1 - beta) / beta) 26 | 27 | trace = False 28 | symmetric = True 29 | 30 | 31 | def test_llhHNGarch(): 32 | opt = fo.heston_nandi_options._llhHNGarch( 33 | [lamb, par_omega, par_alpha, par_beta, 0], trace, symmetric, rfr, ts, True 34 | ) 35 | assert np.allclose(opt.llhHNGarch, -1226.474), "HNGarch test 1 failed" 36 | 37 | 38 | def test_hngarchSim(): 39 | ts = np.genfromtxt("./pytest/ts.csv")[1:] 40 | inno = np.genfromtxt("./pytest/inno.csv")[1:] 41 | start_inno = np.genfromtxt("./pytest/start_inno.csv")[1:] 42 | 43 | x = fo.heston_nandi_options.hngarch_sim( 44 | lamb=4, 45 | omega=8e-5, 46 | alpha=6e-5, 47 | beta=0.7, 48 | gamma=0, 49 | rf=0, 50 | n=500, 51 | n_start=100, 52 | inno=inno, 53 | inno_start=start_inno, 54 | ) 55 | 56 | assert np.allclose(ts, x), "HNGarchSim test 1 failed" 57 | 58 | x = fo.heston_nandi_options.hngarch_sim( 59 | lamb=4, omega=8e-5, alpha=6e-5, beta=0.7, gamma=0, rf=0, n=500 60 | ) 61 | 62 | assert x.shape[0] == 500, "HNGarchSim test 2 failed" 63 | -------------------------------------------------------------------------------- /pytest/test_american_options.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | 5 | 6 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 7 | 8 | import finoptions as fo 9 | 10 | 11 | def test_RollGeskeWhaleyOption(): 12 | opt = fo.basic_american_options.RollGeskeWhaleyOption( 13 | S=80, K=82, t=1 / 3, td=1 / 4, r=0.06, D=4, sigma=0.30 14 | ) 15 | 16 | assert ( 17 | round(opt.call(), 5) == 4.38603 18 | ), "RollGeskeWhaleyOption call price does not match fOptions. RollGeskeWhaleyOption(S=80, K=82, t=1/3, td=1/4, r=0.06, D=4, sigma=0.30) should equal 4.38603" 19 | 20 | 21 | def test_BAWAmericanApproxOption(): 22 | opt = fo.basic_american_options.BAWAmericanApproxOption( 23 | S=100, K=90, t=0.5, r=0.10, b=0, sigma=0.25 24 | ) 25 | 26 | assert ( 27 | round(opt.call(), 5) == 12.44166 28 | ), "BAWAmericanApproxOption call price does not match fOptions. BAWAmericanApproxOption(S = 100, K = 90, t = 0.5, r = 0.10, b = 0, sigma = 0.25).call() should equal 12.44166" 29 | 30 | assert ( 31 | round(opt.put(), 5) == 2.74361 32 | ), "BAWAmericanApproxOption put price does not match fOptions. BAWAmericanApproxOption(S = 100, K = 90, t = 0.5, r = 0.10, b = 0, sigma = 0.25).put() should equal 2.74361" 33 | 34 | 35 | def test_BSAmericanApproxOption(): 36 | 37 | opt = fo.basic_american_options.BSAmericanApproxOption( 38 | S=100, K=90, t=0.5, r=0.10, b=0, sigma=0.25 39 | ) 40 | 41 | assert ( 42 | round(opt.call()["OptionPrice"], 5) == 12.39889 43 | ), "BSAmericanApproxOption call price does not match fOptions. BSAmericanApproxOption(S=100, K=90, t=0.5, r=0.10, b=0, sigma=0.25).call()['OptionPrice'] should equal 12.39889" 44 | 45 | assert ( 46 | round(opt.call()["TriggerPrice"], 5) == 115.27226 47 | ), "BSAmericanApproxOption call trigger price does not match fOptions. BSAmericanApproxOption(S=100, K=90, t=0.5, r=0.10, b=0, sigma=0.25).call()['TriggerPrice'] should equal 115.27226" 48 | 49 | assert ( 50 | round(opt.put()["OptionPrice"], 6) == 2.714363 51 | ), "BSAmericanApproxOption put price does not match fOptions. BSAmericanApproxOption(S=100, K=90, t=0.5, r=0.10, b=0, sigma=0.25).call()['OptionPrice'] should equal 2.714363" 52 | 53 | assert ( 54 | round(opt.put()["TriggerPrice"], 5) == 128.08029 55 | ), "BSAmericanApproxOption put trigger price does not match fOptions. BSAmericanApproxOption(S=100, K=90, t=0.5, r=0.10, b=0, sigma=0.25).call()['TriggerPrice'] should equal 128.08029" 56 | -------------------------------------------------------------------------------- /pytest/test_base_classes.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | 5 | 6 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 7 | 8 | import finoptions as fo 9 | 10 | 11 | # def test_Option(): 12 | # b = fo.Option(0, 0, 0, 0, 0, 0) 13 | 14 | # assert isinstance(b, fo.Option), "Option object failed init" 15 | 16 | # assert isinstance( 17 | # b.get_params(), dict 18 | # ), "Option.get_params failed to return dictionary of values" 19 | 20 | 21 | def test_GBSOption(): 22 | 23 | opt = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1) 24 | 25 | assert ( 26 | round(opt.call(), 6) == 2.061847 27 | ), "GBSOption call price does not match fOptions. GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1).call() should equal 2.061847" 28 | 29 | assert ( 30 | round(opt.put(), 8) == 0.00293811 31 | ), "GBSOption put price does not match fOptions. GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1).put() should equal 0.00293811" 32 | 33 | # test greeks, rounded to 6 decimal points 34 | greeks = { 35 | True: { 36 | "delta": 0.981513, 37 | "theta": -0.068503, 38 | "vega": 0.231779, 39 | "rho": 7.753283, 40 | "lambda": 4.760358, 41 | "gamma": 0.023178, 42 | "CofC": 9.81513, 43 | }, 44 | False: { 45 | "delta": -0.008537, 46 | "theta": -0.010677, 47 | "vega": 0.231779, 48 | "rho": -0.088307, 49 | "lambda": -29.055576, 50 | "gamma": 0.023178, 51 | "CofC": -0.085368, 52 | }, 53 | } 54 | 55 | for op in greeks.keys(): 56 | print(opt) 57 | test_greeks = opt.greeks(call=op) 58 | if op == True: 59 | type = "call" 60 | else: 61 | type = "put" 62 | for cp in greeks[op].keys(): 63 | 64 | my_val = round(test_greeks[cp], 6) 65 | control_val = round(greeks[op][cp], 6) 66 | assert ( 67 | my_val == control_val 68 | ), f"GBSOption {cp} calculation for a {type} option does not match fOptions. GBSOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1) should result in {cp} of {control_val}" 69 | 70 | # test implied volatility method 71 | vol = fo.GBSOption(10.0, 8.0, 1.0, 0.02, 0.01) 72 | assert ( 73 | round(vol.volatility(2.5, call=True), 6) == 0.342241 74 | ), "GBSOption implied volatility calculation does not match fOptions for a call option. GBSOption(10.0, 8.0, 1.0, 0.02, 0.01).volatility(3) should equal 0.342241" 75 | assert ( 76 | round(vol.volatility(2.5, call=False), 6) == 1.016087 77 | ), "GBSOption implied volatility calculation does not match fOptions for a call option. GBSOption(10.0, 8.0, 1.0, 0.02, 0.01).volatility(3) should equal 1.016087" 78 | 79 | assert isinstance( 80 | opt.summary(printer=False), str 81 | ), "GBSOption.summary() failed to produce string." 82 | 83 | 84 | def test_BlackScholesOption(): 85 | 86 | opt = fo.BlackScholesOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1) 87 | 88 | assert ( 89 | round(opt.call(), 6) == 2.061847 90 | ), "BlackScholesOption call price does not match fOptions. BlackScholesOption(10.0, 8.0, 1.0, 0.02, 0.01, 0.1).call() should equal 2.061847" 91 | 92 | 93 | def test_Black76sOption(): 94 | 95 | opt = fo.Black76Option(10.0, 8.0, 1.0, 0.02, 0.1) 96 | 97 | assert ( 98 | round(opt.call(), 6) == 1.96431 99 | ), "Black76Option call price does not match fOptions. Black76Option(10.0, 8.0, 1.0, 0.02, 0.1).call() should equal 1.96431" 100 | 101 | 102 | def test_MiltersenSchwartzOption(): 103 | opt = fo.MiltersenSchwartzOption( 104 | Pt=np.exp(-0.05 / 4), 105 | FT=95, 106 | K=80, 107 | t=1 / 4, 108 | T=1 / 2, 109 | sigmaS=0.2660, 110 | sigmaE=0.2490, 111 | sigmaF=0.0096, 112 | rhoSE=0.805, 113 | rhoSF=0.0805, 114 | rhoEF=0.1243, 115 | KappaE=1.045, 116 | KappaF=0.200, 117 | ) 118 | 119 | assert ( 120 | round(opt.call(), 5) == 15.00468 121 | ), "MiltersenSchwartzOption call price does not match fOptions. MiltersenSchwartzOption(Pt=np.exp(-0.05/4), FT=95, K=80, t=1/4, T=1/2, sigmaS=0.2660, sigmaE=0.2490, sigmaF=0.0096, rhoSE=0.805, rhoSF=0.0805, rhoEF=0.1243, KappaE=1.045, KappaF=0.200).call() should equal 15.00468" 122 | 123 | assert ( 124 | round(opt.put(), 6) == 0.191426 125 | ), "MiltersenSchwartzOption put price does not match fOptions. MiltersenSchwartzOption(Pt=np.exp(-0.05/4), FT=95, K=80, t=1/4, T=1/2, sigmaS=0.2660, sigmaE=0.2490, sigmaF=0.0096, rhoSE=0.805, rhoSF=0.0805, rhoEF=0.1243, KappaE=1.045, KappaF=0.200).put() should equal 0.191426" 126 | 127 | assert isinstance( 128 | opt.summary(printer=False), str 129 | ), "MiltersenSchwartzOption.summary() failed to produce string." 130 | 131 | assert isinstance( 132 | opt.get_params(), dict 133 | ), "MiltersenSchwartzOption.get_params failed to return dictionary of values" 134 | 135 | 136 | def test_FDM_greeks(): 137 | opt = fo.GBSOption(10.0, 8.0, 1, 0.02, 0.0, 0.1) 138 | greeks = ["delta", "theta", "gamma", "lamb", "vega", "rho"] 139 | 140 | for g in greeks: 141 | for call in [True, False]: 142 | greek_func = getattr(opt, g) 143 | if g not in ["gamma", "vega"]: 144 | test = np.allclose( 145 | greek_func(call=call, method="analytic"), 146 | greek_func(call=call, method="fdm"), 147 | rtol=0.0001, 148 | ) 149 | else: 150 | test = np.allclose( 151 | greek_func(method="analytic"), 152 | greek_func(method="fdm"), 153 | rtol=0.0001, 154 | ) 155 | 156 | assert ( 157 | test 158 | ), f"FDM greek calc for {g} with call={call} did not match analytics solution from GBSOption. analytic={greek_func(method='analytic')}, fdm={greek_func(method='fdm')}" 159 | -------------------------------------------------------------------------------- /pytest/test_binomial_options.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | from matplotlib import figure 5 | 6 | 7 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 8 | 9 | import finoptions as fo 10 | 11 | 12 | def test_CRR_Tree(): 13 | 14 | opt = fo.binomial_tree_options.CRRBinomialTreeOption( 15 | S=50, K=40, t=5 / 12, r=0.1, b=0.1, sigma=0.4, n=5, type="european" 16 | ) 17 | 18 | assert np.allclose( 19 | opt.call(), 12.62964 20 | ), "CRRBinomialTreeOption call-euro price does not match fOptions. CRRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).call() should equal 12.62964" 21 | assert np.allclose( 22 | opt.put(), 0.9972167 23 | ), "CRRBinomialTreeOption put-euro price does not match fOptions. CRRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).put() should equal 0.9972167" 24 | 25 | opt = fo.binomial_tree_options.CRRBinomialTreeOption( 26 | S=50, K=40, t=5 / 12, r=0.1, b=0.1, sigma=0.4, n=5, type="american" 27 | ) 28 | 29 | assert np.allclose( 30 | opt.call(), 12.62964 31 | ), "CRRBinomialTreeOption call-amer price does not match fOptions. CRRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).call(type='american') should equal 12.62964" 32 | assert np.allclose( 33 | opt.put(), 1.016134 34 | ), "CRRBinomialTreeOption put-amer price does not match fOptions. CRRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).put(type='american') should equal 1.016134" 35 | 36 | assert isinstance(opt.plot(), figure.Figure), "Binomial tree plotter not working" 37 | 38 | 39 | def test_JR_Tree(): 40 | 41 | opt = fo.binomial_tree_options.JRBinomialTreeOption( 42 | S=50, K=40, t=5 / 12, r=0.1, b=0.1, sigma=0.4, n=5, type="european" 43 | ) 44 | 45 | assert np.allclose( 46 | opt.call(), 12.63021 47 | ), "JRBinomialTreeOption call-euro price does not match fOptions. JRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).call() should equal 12.63021" 48 | assert np.allclose( 49 | opt.put(), 1.001478 50 | ), "JRBinomialTreeOption put-euro price does not match fOptions. JRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).put() should equal 1.001478" 51 | 52 | opt = fo.binomial_tree_options.JRBinomialTreeOption( 53 | S=50, K=40, t=5 / 12, r=0.1, b=0.1, sigma=0.4, n=5, type="american" 54 | ) 55 | 56 | assert np.allclose( 57 | opt.call(), 12.63021 58 | ), "JRBinomialTreeOption call-amer price does not match fOptions. JRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).call(type='american') should equal 12.63021" 59 | assert np.allclose( 60 | opt.put(), 1.021516 61 | ), "JRBinomialTreeOption put-amer price does not match fOptions. JRBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).put(type='american') should equal 1.021516" 62 | 63 | 64 | def test_TIAN_Tree(): 65 | 66 | opt = fo.binomial_tree_options.TIANBinomialTreeOption( 67 | S=50, K=40, t=5 / 12, r=0.1, b=0.1, sigma=0.4, n=5, type="european" 68 | ) 69 | 70 | assert np.allclose( 71 | opt.call(), 12.36126 72 | ), "TIANBinomialTreeOption call-euro price does not match fOptions. TIANBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).call() should equal 12.36126" 73 | assert np.allclose( 74 | opt.put(), 0.7288429 75 | ), "TIANBinomialTreeOption put-euro price does not match fOptions. TIANBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).put() should equal 0.7288429" 76 | 77 | opt = fo.binomial_tree_options.TIANBinomialTreeOption( 78 | S=50, K=40, t=5 / 12, r=0.1, b=0.1, sigma=0.4, n=5, type="american" 79 | ) 80 | 81 | assert np.allclose( 82 | opt.call(), 12.36126 83 | ), "TIANBinomialTreeOption call-amer price does not match fOptions. TIANBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).call(type='american') should equal 12.36126" 84 | assert np.allclose( 85 | opt.put(), 0.7666983 86 | ), "TIANBinomialTreeOption put-amer price does not match fOptions. TIANBinomialTreeOption(S=50, K=40, t=5/12, r=0.1, b=0.1, sigma=0.4).put(type='american') should equal 0.7666983" 87 | 88 | 89 | def test_CRR_tree(): 90 | 91 | rm = np.array( 92 | [ 93 | [12.62964, 17.695763, 23.976793, 31.360265, 39.68471, 49.065609], 94 | [0.00000, 7.627502, 11.528673, 16.781187, 23.32114, 30.699123], 95 | [0.00000, 0.000000, 3.739973, 6.315909, 10.33195, 16.120045], 96 | [0.00000, 0.000000, 0.000000, 1.151023, 2.28782, 4.547363], 97 | [0.00000, 0.000000, 0.000000, 0.000000, 0.00000, 0.000000], 98 | [0.00000, 0.000000, 0.000000, 0.000000, 0.00000, 0.000000], 99 | ] 100 | ) 101 | print(rm) 102 | opt = fo.binomial_tree_options.CRRBinomialTreeOption( 103 | S=50, K=40, t=5 / 12, r=0.1, b=0.1, sigma=0.4, n=5, type="european" 104 | ) 105 | 106 | assert np.allclose( 107 | rm, opt.call(tree=True) 108 | ), "CRRBionomialTreeOption matrix tree for a call option does not match fOptions" 109 | -------------------------------------------------------------------------------- /pytest/test_heston_options.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | 5 | 6 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 7 | 8 | import finoptions as fo 9 | 10 | phi = 20 11 | # const = 1 12 | lamb = -0.5 13 | omega = 2.3e-6 14 | alpha = 2.9e-6 15 | beta = 0.85 16 | gamma = 184.25 17 | S = 100 18 | K = 100 19 | t = 252 20 | r = 0.05 / t 21 | 22 | 23 | def test_fHN(): 24 | test = fo.heston_nandi_options._fHN( 25 | phi=phi, 26 | const=1, 27 | lamb=lamb, 28 | omega=omega, 29 | alpha=alpha, 30 | beta=beta, 31 | gamma=gamma, 32 | S=S, 33 | K=K, 34 | t=t, 35 | r=r, 36 | real=True, 37 | ) 38 | 39 | assert np.allclose(test, 0.01201465), "fstarHN failed test 1" 40 | 41 | test = fo.heston_nandi_options._fHN( 42 | phi=phi, 43 | const=0, 44 | lamb=lamb, 45 | omega=omega, 46 | alpha=alpha, 47 | beta=beta, 48 | gamma=gamma, 49 | S=S, 50 | K=K, 51 | t=t, 52 | r=r, 53 | real=True, 54 | ) 55 | 56 | assert np.allclose(test, 0.0001524204), "fstarHN failed test 2" 57 | 58 | 59 | def test_heston_option_value(): 60 | 61 | opt = fo.heston_nandi_options.HestonNandiOption( 62 | S=S, K=K, t=t, r=r, lamb=lamb, omega=omega, alpha=alpha, beta=beta, gamma=gamma 63 | ) 64 | 65 | test = opt.call() 66 | assert np.allclose(test, 8.9921), "Heston Option failed test 1" 67 | 68 | test = opt.put() 69 | assert np.allclose(test, 4.115042), "Heston Option failed test 2" 70 | 71 | opt = fo.heston_nandi_options.HestonNandiOption( 72 | S=S, K=90, t=t, r=r, lamb=lamb, omega=omega, alpha=alpha, beta=beta, gamma=gamma 73 | ) 74 | 75 | test = opt.call() 76 | assert np.allclose(test, 15.85447), "Heston Option failed test 1" 77 | 78 | test = opt.put() 79 | assert np.allclose(test, 1.465121), "Heston Option failed test 2" 80 | 81 | 82 | def test_heston_delta_gamma(): 83 | 84 | opt = fo.heston_nandi_options.HestonNandiOption( 85 | S=S, K=K, t=t, r=r, lamb=lamb, omega=omega, alpha=alpha, beta=beta, gamma=gamma 86 | ) 87 | 88 | test = opt.delta(call=True) 89 | assert np.allclose(test, 0.6739534), "Heston Greek failed test 1" 90 | 91 | test = opt.delta(call=False) 92 | assert np.allclose(test, -0.3260466), "Heston Greek failed test 2" 93 | 94 | test = opt.gamma() 95 | assert np.allclose(test, 0.02211149), "Heston Greek failed test 3" 96 | -------------------------------------------------------------------------------- /pytest/test_mc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as _np 4 | 5 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 6 | 7 | 8 | import finoptions as fo 9 | 10 | 11 | def test_monte_carlo(): 12 | 13 | S = 100 14 | K = 100 15 | t = 1 / 12 16 | sigma = 0.4 17 | r = 0.10 18 | b = 0.1 19 | 20 | path_length = 30 21 | mc_samples = 5000 22 | mc_loops = 50 23 | 24 | eps = _np.genfromtxt( 25 | "./pytest/sobol_scrambled_path_test.csv", delimiter="," 26 | ) # load sobol paths from R since python version is slighly different in third path 27 | 28 | inno = fo.monte_carlo_options.NormalSobolInnovations 29 | path = fo.monte_carlo_options.WienerPath 30 | payoff = fo.monte_carlo_options.PlainVanillaPayoff 31 | 32 | mc = fo.monte_carlo_options.MonteCarloOption( 33 | mc_loops, 34 | path_length, 35 | mc_samples, 36 | S, 37 | K, 38 | t, 39 | r, 40 | b, 41 | sigma, 42 | inno, 43 | path, 44 | payoff, 45 | # eps=eps, 46 | trace=False, 47 | antithetic=True, 48 | standardization=False, 49 | ) 50 | 51 | opt = fo.GBSOption(S, K, t, r, b, sigma) 52 | 53 | assert _np.allclose( 54 | opt.call(), _np.mean(mc.call()), rtol=1e-2 55 | ), "Monte Carlo Plain Vanilla call failed" 56 | assert _np.allclose( 57 | opt.put(), _np.mean(mc.put()), rtol=1e-2 58 | ), "Monte Carlo Plain Vanilla put failed" 59 | 60 | # test standardization - seems to produce worse results for Plain Vanilla 61 | mc = fo.monte_carlo_options.MonteCarloOption( 62 | mc_loops, 63 | path_length, 64 | mc_samples, 65 | # dt, 66 | S, 67 | K, 68 | t, 69 | r, 70 | b, 71 | sigma, 72 | inno, 73 | path, 74 | payoff, 75 | # eps=eps, 76 | trace=False, 77 | antithetic=True, 78 | standardization=True, 79 | ) 80 | 81 | assert _np.allclose( 82 | opt.call(), _np.mean(mc.call()), rtol=1e-2 83 | ), "Monte Carlo Plain Vanilla call with standardization failed" 84 | assert _np.allclose( 85 | opt.put(), _np.mean(mc.put()), rtol=1e-2 86 | ), "Monte Carlo Plain Vanilla put with standardization failed" 87 | 88 | 89 | if __name__ == "__main__": 90 | 91 | test_monte_carlo() 92 | -------------------------------------------------------------------------------- /pytest/test_mc_paths.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | from matplotlib import figure 5 | 6 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 7 | 8 | import finoptions as fo 9 | 10 | 11 | def test_WienerPath(): 12 | eps = np.genfromtxt( 13 | "./pytest/sobol_path_test.csv", delimiter="," 14 | ) # load sobol paths from R since python version is slighly different in third path 15 | rpaths = np.genfromtxt( 16 | "./pytest/wiener_path_test.csv", delimiter="," 17 | ) # results to test against 18 | paths = fo.monte_carlo_options.WienerPath(eps, 0.4, 1 / 360, 0.1).generate_path() 19 | 20 | assert np.allclose( 21 | paths, rpaths 22 | ), "WienerPath results do not match fOptions from R." 23 | -------------------------------------------------------------------------------- /pytest/test_mc_payoffs.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as _np 4 | 5 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 6 | 7 | 8 | import finoptions as fo 9 | 10 | 11 | def test_PlainVanillaPayoff(): 12 | 13 | S = 100 14 | K = 100 15 | t = 1 / 12 16 | sigma = 0.4 17 | r = 0.10 18 | b = 0.1 19 | 20 | dt = 1 / 360 21 | 22 | eps = _np.genfromtxt( 23 | "./pytest/sobol_path_test.csv", delimiter="," 24 | ) # load sobol paths from R since python version is slighly different in third path 25 | 26 | path = fo.monte_carlo_options.WienerPath(eps, sigma, dt, b) 27 | print(path.generate_path()) 28 | payoff = fo.monte_carlo_options.PlainVanillaPayoff( 29 | path=path, S=S, K=K, t=t, sigma=sigma, r=r, b=b 30 | ) 31 | 32 | # test two call options, elements 1 and 10 from fOptions 33 | # using weiner paths generated from sobol innovations 34 | assert _np.allclose( 35 | payoff.call()[[0, 9]], [37.11255, 37.83272] 36 | ), "PlainVanillaPayoff no matching R's fOptions." 37 | 38 | # test two put options, elements 1 and 10 from fOptions 39 | # using weiner paths generated from sobol innovations 40 | assert _np.allclose( 41 | payoff.put()[[0, 9]], [0, 0] 42 | ), "PlainVanillaPayoff no matching R's fOptions." 43 | 44 | # make K bigger so that the option value is > 0 45 | payoff = fo.monte_carlo_options.PlainVanillaPayoff( 46 | path=path, S=S, K=140, t=t, sigma=sigma, r=r, b=b 47 | ) 48 | 49 | assert _np.allclose( 50 | payoff.put()[[0, 9]], [2.555497, 1.835328] 51 | ), "PlainVanillaPayoff no matching R's fOptions." 52 | 53 | 54 | def test_ArimeticAsianPayoff(): 55 | S = 100 56 | K = 100 57 | t = 1 / 12 58 | sigma = 0.4 59 | r = 0.10 60 | b = 0.1 61 | 62 | dt = 1 / 360 63 | 64 | eps = _np.genfromtxt( 65 | "./pytest/sobol_path_test.csv", delimiter="," 66 | ) # load sobol paths from R since python version is slighly different in third path 67 | 68 | path = fo.monte_carlo_options.WienerPath(eps, sigma, dt, b) 69 | 70 | payoff = fo.monte_carlo_options.ArithmeticAsianPayoff( 71 | path=path, S=S, K=K, t=t, sigma=sigma, r=r, b=b 72 | ) 73 | 74 | # test two call options, elements 1 and 10 from fOptions 75 | # using weiner paths generated from sobol innovations 76 | assert _np.allclose( 77 | payoff.call()[[0, 9]], [18.19441, 20.43197] 78 | ), "ArithmeticAsianPayoff not matching R's fOptions." 79 | 80 | # test two put options, elements 1 and 10 from fOptions 81 | # using weiner paths generated from sobol innovations 82 | assert _np.allclose( 83 | payoff.put()[[0, 9]], [0, 0] 84 | ), "ArithmeticAsianPayoff not matching R's fOptions." 85 | 86 | # make K bigger so that the option value is > 0 87 | payoff = fo.monte_carlo_options.ArithmeticAsianPayoff( 88 | path=path, S=S, K=140, t=t, sigma=sigma, r=r, b=b 89 | ) 90 | 91 | assert _np.allclose( 92 | payoff.put()[[0, 9]], [21.47364, 19.23608] 93 | ), "ArithmeticAsianPayoff not matching R's fOptions." 94 | 95 | 96 | if __name__ == "__main__": 97 | test_PlainVanillaPayoff() 98 | -------------------------------------------------------------------------------- /pytest/test_passing_arrays.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | 5 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 6 | 7 | import finoptions as fo 8 | 9 | 10 | def test_vanilla_array(): 11 | #fmt: off 12 | opt_test = [ 13 | (fo, "GBSOption", {"S": 10.0, "t": 1.0, "r": 0.02, "b": 0.01, "sigma": 0.2}, {'vol':True}), 14 | (fo, "BlackScholesOption", {"S": 10.0, "t": 1.0, "r": 0.02, "b": 0.01, "sigma": 0.2}, {'vol':True}), 15 | (fo, "Black76Option", {"FT": 10.0, "t": 1.0, "r": 0.02, "sigma": 0.2}, {'vol':True}), 16 | (fo, "MiltersenSchwartzOption", dict(Pt=np.exp(-0.05 / 4), FT=10, t=1 / 4, T=1 / 2, sigmaS=0.2660, sigmaE=0.2490, sigmaF=0.0096, 17 | rhoSE=0.805, rhoSF=0.0805, rhoEF=0.1243, KappaE=1.045,KappaF=0.200), {'vol':False}) 18 | ] 19 | 20 | K = np.arange(8, 12) 21 | 22 | for test in opt_test: 23 | # create object with array of K 24 | opt_array = getattr(test[0], test[1])(K=K, **test[2]) 25 | 26 | # create empty list to hold object where elements of K are pass individually 27 | opt = [] 28 | for k_ind in K: 29 | # create object for each element of K 30 | opt.append(getattr(test[0], test[1])(K=k_ind, **test[2])) 31 | 32 | for i, _ in enumerate(opt): 33 | assert np.allclose( 34 | opt_array.call()[i], opt[i].call() 35 | ), f"{opt[i].__name__} failed call() test for passing arrays for K = {K[i]}" 36 | 37 | for i, _ in enumerate(opt): 38 | assert np.allclose( 39 | opt_array.put()[i], opt[i].put() 40 | ), f"{opt[i].__name__} failed put() test for passing arrays for K = {K[i]}" 41 | 42 | if test[3]['vol'] == True: 43 | for i, _ in enumerate(opt): 44 | assert np.allclose( 45 | opt_array.volatility(K - 5)[i], opt[i].volatility(K[i] - 5) 46 | ), f"{opt[i].__name__} failed volatility() test for passing arrays for K = {K[i]}" 47 | 48 | # test greeks 49 | for gk in ["delta", "theta", "vega", "rho", "gamma", "lamb"]: 50 | for i, _ in enumerate(opt): 51 | if (gk != 'rho') & (opt[i].__name__ != "MiltersenSchwartzOption"): 52 | print(opt[i].__name__, gk, getattr(opt_array, gk)()[i], getattr(opt[i], gk)()) 53 | assert np.allclose( 54 | getattr(opt_array, gk)()[i], getattr(opt[i], gk)() 55 | ), f"{opt[i].__name__} failed {gk}() test for passing arrays for K = {K[i]}" 56 | 57 | 58 | if __name__ == "__main__": 59 | test_vanilla_array() 60 | -------------------------------------------------------------------------------- /pytest/test_spread_options.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | 5 | 6 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 7 | 8 | import finoptions as fo 9 | 10 | S1 = 122 11 | S2 = 120 12 | K = 3 13 | r = 0.1 14 | b1 = 0 15 | b2 = 0 16 | n = 100 17 | 18 | testsBi = [ 19 | dict( 20 | params=dict(sigma1=0.2, sigma2=0.2, rho=-0.5, t=0.1, otype="european"), 21 | vals=dict(call=4.7554), 22 | ), 23 | dict( 24 | params=dict(sigma1=0.2, sigma2=0.2, rho=0, t=0.1, otype="european"), 25 | vals=dict(call=3.8008), 26 | ), 27 | dict( 28 | params=dict(sigma1=0.2, sigma2=0.2, rho=0.5, t=0.1, otype="european"), 29 | vals=dict(call=2.5551), 30 | ), 31 | dict( 32 | params=dict(sigma1=0.2, sigma2=0.2, rho=-0.5, t=0.5, otype="european"), 33 | vals=dict(call=10.7566), 34 | ), 35 | dict( 36 | params=dict(sigma1=0.2, sigma2=0.2, rho=0, t=0.5, otype="european"), 37 | vals=dict(call=8.7080), 38 | ), 39 | dict( 40 | params=dict(sigma1=0.2, sigma2=0.2, rho=0.5, t=0.5, otype="european"), 41 | vals=dict(call=6.0286), 42 | ), 43 | ############## 44 | dict( 45 | params=dict(sigma1=0.25, sigma2=0.2, rho=-0.5, t=0.1, otype="european"), 46 | vals=dict(call=5.4297), 47 | ), 48 | dict( 49 | params=dict(sigma1=0.25, sigma2=0.2, rho=0, t=0.1, otype="european"), 50 | vals=dict(call=4.3732), 51 | ), 52 | dict( 53 | params=dict(sigma1=0.25, sigma2=0.2, rho=0.5, t=0.1, otype="european"), 54 | vals=dict(call=3.0098), 55 | ), 56 | dict( 57 | params=dict(sigma1=0.25, sigma2=0.2, rho=-0.5, t=0.5, otype="european"), 58 | vals=dict(call=12.2031), 59 | ), 60 | dict( 61 | params=dict(sigma1=0.25, sigma2=0.2, rho=0, t=0.5, otype="european"), 62 | vals=dict(call=9.9377), 63 | ), 64 | dict( 65 | params=dict(sigma1=0.25, sigma2=0.2, rho=0.5, t=0.5, otype="european"), 66 | vals=dict(call=7.0097), 67 | ), 68 | ################ 69 | ################ 70 | dict( 71 | params=dict(sigma1=0.2, sigma2=0.2, rho=-0.5, t=0.1, otype="american"), 72 | vals=dict(call=4.7630), 73 | ), 74 | dict( 75 | params=dict(sigma1=0.2, sigma2=0.2, rho=0, t=0.1, otype="american"), 76 | vals=dict(call=3.8067), 77 | ), 78 | dict( 79 | params=dict(sigma1=0.2, sigma2=0.2, rho=0.5, t=0.1, otype="american"), 80 | vals=dict(call=2.5590), 81 | ), 82 | dict( 83 | params=dict(sigma1=0.2, sigma2=0.2, rho=-0.5, t=0.5, otype="american"), 84 | vals=dict(call=10.8754), 85 | ), 86 | dict( 87 | params=dict(sigma1=0.2, sigma2=0.2, rho=0, t=0.5, otype="american"), 88 | vals=dict(call=8.8029), 89 | ), 90 | dict( 91 | params=dict(sigma1=0.2, sigma2=0.2, rho=0.5, t=0.5, otype="american"), 92 | vals=dict(call=6.0939), 93 | ), 94 | ############## 95 | dict( 96 | params=dict(sigma1=0.25, sigma2=0.2, rho=-0.5, t=0.1, otype="american"), 97 | vals=dict(call=5.4385), 98 | ), 99 | dict( 100 | params=dict(sigma1=0.25, sigma2=0.2, rho=0, t=0.1, otype="american"), 101 | vals=dict(call=4.3802), 102 | ), 103 | dict( 104 | params=dict(sigma1=0.25, sigma2=0.2, rho=0.5, t=0.1, otype="american"), 105 | vals=dict(call=3.0145), 106 | ), 107 | dict( 108 | params=dict(sigma1=0.25, sigma2=0.2, rho=-0.5, t=0.5, otype="american"), 109 | vals=dict(call=12.3383), 110 | ), 111 | dict( 112 | params=dict(sigma1=0.25, sigma2=0.2, rho=0, t=0.5, otype="american"), 113 | vals=dict(call=10.0468), 114 | ), 115 | dict( 116 | params=dict(sigma1=0.25, sigma2=0.2, rho=0.5, t=0.5, otype="american"), 117 | vals=dict(call=7.0858), 118 | ), 119 | ] 120 | 121 | 122 | # sigma1 = 0.25 123 | # sigma2 = 0.2 124 | # rho = -0.5 125 | # t = 0.1 126 | # otype = "european" 127 | 128 | 129 | def test_binomial_spread(): 130 | 131 | for i, test in enumerate(testsBi): 132 | 133 | opt = fo.spread_options.BionomialSpreadOption( 134 | S1=S1, 135 | S2=S2, 136 | K=K, 137 | r=r, 138 | b1=b1, 139 | b2=b2, 140 | n=n, 141 | **test["params"], 142 | ) 143 | testval = opt.call() 144 | assert np.allclose( 145 | round(testval, 4), test["vals"]["call"] 146 | ), f"Bionomial Spread Option call test {str(i)} failed. Params = {test['params']}. Test = {test['vals']['call']}. Return {testval}" 147 | 148 | if test["params"]["otype"] == "european": 149 | # uses put-call parity relationship for euro options to test puts. Does not apply to american options 150 | # fmt: off 151 | val = opt.call() - (opt._S1*np.exp((opt._b1-opt._r)*opt._t) - opt._S2*np.exp((opt._b2-opt._r)*opt._t)) + opt._K*np.exp(-opt._r*opt._t) 152 | testval = opt.put() 153 | call = test["vals"]["call"] 154 | assert np.allclose( 155 | round(testval, 3), round(val, 3) 156 | ), f"Bionomial Spread Option put test {str(i)}, {call} failed. Params = {test['params']}. Test = {val}. Return {testval}" 157 | # fmt: on 158 | 159 | 160 | testsApprox = [ 161 | dict( 162 | params=dict(sigma1=0.2, sigma2=0.2, rho=-0.5, t=0.1), 163 | vals=dict(call=4.7530), 164 | ), 165 | dict( 166 | params=dict(sigma1=0.2, sigma2=0.2, rho=0, t=0.1), 167 | vals=dict(call=3.7970), 168 | ), 169 | dict( 170 | params=dict(sigma1=0.2, sigma2=0.2, rho=0.5, t=0.1), 171 | vals=dict(call=2.5537), 172 | ), 173 | dict( 174 | params=dict(sigma1=0.2, sigma2=0.2, rho=-0.5, t=0.5), 175 | vals=dict(call=10.7517), 176 | ), 177 | dict( 178 | params=dict(sigma1=0.2, sigma2=0.2, rho=0, t=0.5), 179 | vals=dict(call=8.7020), 180 | ), 181 | dict( 182 | params=dict(sigma1=0.2, sigma2=0.2, rho=0.5, t=0.5), 183 | vals=dict(call=6.0257), 184 | ), 185 | ############## 186 | dict( 187 | params=dict(sigma1=0.25, sigma2=0.2, rho=-0.5, t=0.1), 188 | vals=dict(call=5.4275), 189 | ), 190 | dict( 191 | params=dict(sigma1=0.25, sigma2=0.2, rho=0, t=0.1), 192 | vals=dict(call=4.3712), 193 | ), 194 | dict( 195 | params=dict(sigma1=0.25, sigma2=0.2, rho=0.5, t=0.1), 196 | vals=dict(call=3.0086), 197 | ), 198 | dict( 199 | params=dict(sigma1=0.25, sigma2=0.2, rho=-0.5, t=0.5), 200 | vals=dict(call=12.1941), 201 | ), 202 | dict( 203 | params=dict(sigma1=0.25, sigma2=0.2, rho=0, t=0.5), 204 | vals=dict(call=9.9340), 205 | ), 206 | dict( 207 | params=dict(sigma1=0.25, sigma2=0.2, rho=0.5, t=0.5), 208 | vals=dict(call=7.0067), 209 | ), 210 | ] 211 | 212 | 213 | def test_approx_spread(): 214 | 215 | for i, test in enumerate(testsApprox): 216 | 217 | opt = fo.spread_options.SpreadApproxOption( 218 | S1=S1, 219 | S2=S2, 220 | K=K, 221 | r=r, 222 | b1=b1, 223 | b2=b2, 224 | **test["params"], 225 | ) 226 | testval = opt.call() 227 | assert np.allclose( 228 | round(testval, 4), test["vals"]["call"] 229 | ), f"Approximate Spread Option call test {str(i)} failed. Params = {test['params']}. Test = {test['vals']['call']}. Return {testval}" 230 | 231 | testval = opt.put() 232 | 233 | # fmt: off 234 | val = opt.call() - (opt._S1*np.exp(opt._b1-opt._r*opt._t) - opt._S2*np.exp(opt._b2-opt._r*opt._t)) + opt._K*np.exp(-opt._r*opt._t) 235 | assert np.allclose( 236 | round(testval, 4), val 237 | ), f"Approximate Spread Option put test {str(i)} failed. Params = {test['params']}. Test = {test['vals']['call']}. Return {testval}" 238 | # fmt: on 239 | 240 | 241 | if __name__ == "__main__": 242 | test_binomial_spread() 243 | -------------------------------------------------------------------------------- /pytest/test_trinomial_options.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | 5 | 6 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../src/") 7 | 8 | import finoptions as fo 9 | 10 | S = 100 11 | K = 110 12 | t = 0.5 13 | r = 0.1 14 | b = 0.1 15 | sigma = 0.27 16 | n = 30 17 | 18 | 19 | def test_trinomial_options(): 20 | 21 | opt = fo.binomial_tree_options.TrinomialTreeOption( 22 | S=S, K=K, t=t, r=r, b=b, sigma=sigma, n=n, type="american" 23 | ) 24 | 25 | test = opt.put() 26 | assert np.allclose(test, 11.64931), "Trinomial Tree American Put Failed" 27 | 28 | test = opt.call() 29 | assert np.allclose(test, 5.657877), "Trinomial Tree American Call Failed" 30 | 31 | opt = fo.binomial_tree_options.TrinomialTreeOption( 32 | S=S, K=K, t=t, r=r, b=b, sigma=sigma, n=n, type="european" 33 | ) 34 | 35 | test = opt.put() 36 | assert np.allclose(test, 10.29311), "Trinomial Tree European Put Failed" 37 | 38 | test = opt.call() 39 | assert np.allclose(test, 5.657877), "Trinomial Tree European Call Failed" 40 | -------------------------------------------------------------------------------- /pytest/ts.csv: -------------------------------------------------------------------------------- 1 | "x" 2 | 0.0125986125030751 3 | -0.000898160626245687 4 | 0.00796453696133219 5 | -0.0160346417807084 6 | -0.00580806454581281 7 | -0.00688612772427623 8 | -0.00219362442423193 9 | 0.00645052680215675 10 | -0.0161541315700463 11 | 0.00349864844313362 12 | 0.00832866593684379 13 | 0.00535199285037447 14 | 0.0203670685934618 15 | 0.0107431149106552 16 | -0.00213624277156731 17 | 0.00038588407799498 18 | 0.0114834127190306 19 | 0.00915064681138005 20 | -0.0101457507156965 21 | 0.0182043769274863 22 | 0.0396160618622518 23 | 0.0215918042178708 24 | 0.00674948820649198 25 | 0.00138359298730998 26 | 0.0156815550764378 27 | -0.0186758099017815 28 | -0.0300554885672748 29 | -0.00412267298363434 30 | 0.0260957528738558 31 | -0.00701188591180288 32 | -0.00898806686380462 33 | 0.00802989877973642 34 | 0.000494498570852998 35 | 0.0154228880664135 36 | 0.0446643650926278 37 | 0.0102933683733728 38 | 0.0242235241154394 39 | 0.0117925017684561 40 | 0.0183924779581633 41 | 0.0101683858944694 42 | -0.00776169381883097 43 | 0.00631277415533772 44 | 0.0230033164018877 45 | -0.00124999030644416 46 | 0.00130933995424925 47 | -0.014560955735709 48 | 0.01297425519832 49 | 0.00338566303424886 50 | -0.0183030160413437 51 | -0.000338836536675906 52 | -0.0207545001305093 53 | -0.0125966173495637 54 | 0.0130561211643459 55 | 0.0125820143605619 56 | 0.0136597892693679 57 | -0.0129279401102653 58 | -0.0497557922673241 59 | -0.0550671972940746 60 | -0.0299197678090976 61 | -0.000850993454687426 62 | -0.0260016948939452 63 | -0.0257050495940598 64 | -0.00634850265516148 65 | -0.0429629461472237 66 | 0.0279771857787155 67 | -0.0348654979892296 68 | 0.0283700982035041 69 | -0.0320303945209333 70 | -0.0251287672203375 71 | 0.0149375459945177 72 | 0.0147291570788543 73 | -0.0328977838209402 74 | -0.0307229545306333 75 | -0.00814104621919744 76 | -0.00801533738366225 77 | -0.0362142869145147 78 | -0.0347904674688084 79 | -0.0189510337718891 80 | 0.00658803520958326 81 | 0.0221051002163861 82 | -0.00993806238020865 83 | -0.0202467107769713 84 | 0.00114898584807694 85 | 0.00441758627761783 86 | 0.0156819834190859 87 | -0.0268954432497725 88 | 0.0389275347219356 89 | 0.0281662216560129 90 | 0.00683520300081711 91 | -0.0183722229774049 92 | -0.00891702850962933 93 | -0.0166105129207682 94 | -0.0145724784631357 95 | -0.00191687258665872 96 | 0.0333482971799676 97 | 0.00641201041542824 98 | 0.033755386069456 99 | -0.000152185610553295 100 | -0.0481432341165095 101 | -0.0292841242872615 102 | 0.0238943798661662 103 | 0.00314330590734574 104 | 0.0151264970131694 105 | 0.0201398121330797 106 | -0.0146753214294266 107 | -0.00308787882216865 108 | -0.0162796373810392 109 | -0.0158649524357399 110 | 0.00208329732750851 111 | -0.0157595658372262 112 | -0.0139532300918257 113 | -6.47949680812345e-05 114 | 0.020168305868896 115 | -0.0105966282192054 116 | 0.0122918794173465 117 | -0.00560034987358413 118 | 0.0218129054319657 119 | -0.00448482560082605 120 | -0.0207721345088382 121 | -0.00132086506583601 122 | 0.016046549899305 123 | 0.0117647810571888 124 | 0.0188455405029793 125 | 0.00892950870429748 126 | -0.0102229028262859 127 | 0.00883819877282948 128 | 0.0104212330247882 129 | 0.00318006418797621 130 | 0.011451519864167 131 | 0.00673919699532464 132 | -0.0108808534440777 133 | 0.00508061452679869 134 | 0.00104190276192134 135 | 0.00717180234575433 136 | -0.00560473417363205 137 | -0.025414590493894 138 | -0.0122943726093754 139 | -0.00271302566758591 140 | 0.00449014588225767 141 | 0.00449615451669219 142 | 0.039483848744102 143 | 0.02809821935571 144 | -0.0108901412616372 145 | 0.0393691060384272 146 | 0.0121131976752778 147 | -0.0132216962833927 148 | -0.00075257195853785 149 | 0.0287238482285916 150 | -0.002053072958308 151 | -0.00787676089824538 152 | -0.00373703916529024 153 | 0.0245181813583674 154 | 0.0331484998080295 155 | 0.0169198115017973 156 | 0.00151248849202464 157 | -0.00388118778212724 158 | 0.0250078674638886 159 | 0.0501459064663961 160 | 0.0212809158034361 161 | 0.0313764197779013 162 | 0.0112251149329116 163 | -0.00553384326189805 164 | -0.0340613475067233 165 | 0.0170280479870847 166 | -0.0291898244041458 167 | -0.0259757075412606 168 | -0.0223284331857285 169 | -0.00677099035383774 170 | 0.0103332997276925 171 | 0.0243985930308128 172 | 0.0103461248815902 173 | -0.0208775492498515 174 | -0.00418415122426109 175 | 0.0133793731000602 176 | -0.00179588673350194 177 | 0.000630513097290922 178 | -0.0131567614649965 179 | 0.0138585465450356 180 | 0.0269902122851296 181 | -0.00348132493044034 182 | -0.0151632912517912 183 | -0.0422672781179926 184 | -0.0638282184513846 185 | -0.00854415433319429 186 | 0.00642781046020878 187 | -0.0114738928026464 188 | 0.0116158859878743 189 | 0.0157408483952515 190 | 0.00820181483517747 191 | -0.0117839733726264 192 | -0.0355193118931094 193 | 0.0397115702938575 194 | 0.0259551496070002 195 | -0.0383748662665318 196 | -0.0220221171090155 197 | 0.0131282618152015 198 | 0.00889428913941021 199 | 0.0029791797459959 200 | 0.0101514039881869 201 | 0.0221473622716974 202 | 0.0124132682842337 203 | 0.011451321437742 204 | -0.0245818542491722 205 | -0.0117809278492888 206 | -0.0268332738404359 207 | -0.00632428480663999 208 | 0.0195943700408188 209 | -0.0109226080182043 210 | -0.0259783636618366 211 | 0.00225209660475236 212 | 0.028730005991035 213 | -0.0062080764442263 214 | -0.00874479465211268 215 | 0.00048599321692149 216 | -0.00705337254587505 217 | 0.0185180191521965 218 | 0.05959611410261 219 | -0.00840806878637325 220 | 0.00394739926107315 221 | -0.00254014945852957 222 | -0.0177025926756668 223 | 0.0344927107091989 224 | 0.0348679126121108 225 | 0.0303531048874673 226 | 0.0217534794136972 227 | -0.0108549968655815 228 | 0.0138890632058391 229 | -0.0218611493439961 230 | -0.0413802290003903 231 | -0.0215303762681743 232 | 0.0264652313132143 233 | 0.0111910687046157 234 | 0.0486511650719461 235 | 0.0174451269177891 236 | -0.0204652910586958 237 | -0.0226097845425819 238 | 0.0051809456360597 239 | -0.0259924694884731 240 | -0.0102013481693037 241 | 0.00365078630387793 242 | -0.0249406518295163 243 | 0.0351927271514166 244 | -0.0281739042426183 245 | -0.0492262451715436 246 | 0.0569021926032133 247 | 0.00508397721064601 248 | -0.008142190283299 249 | -0.0365779911301657 250 | 0.0686826414521124 251 | -0.0239066387108009 252 | -0.0179844053521108 253 | -0.0161671978452193 254 | 0.0118404056672009 255 | 0.0018439817735217 256 | 0.0147806513230781 257 | -0.00586399311636856 258 | -0.0148803228714359 259 | 0.0238881717073148 260 | 0.00482027542899198 261 | 0.00238870043811626 262 | -0.000429096835956968 263 | 0.00987156152542413 264 | -0.0278445493901077 265 | 0.0179226386684318 266 | -0.000499366140305234 267 | -0.00750602855274617 268 | 0.0137222868423898 269 | 0.0177847504354592 270 | 0.00155057023682603 271 | 0.000175884477771079 272 | -0.0070051479651727 273 | 0.00949880856451132 274 | 0.0204767928850899 275 | -0.0297694746181886 276 | -0.00886714890310538 277 | -0.000887242344622772 278 | 0.00780684030795223 279 | -0.00953620861829598 280 | -0.00192479379078628 281 | -0.0147256669827368 282 | 0.0252844048871814 283 | 0.0172096133966292 284 | 0.00113036592804237 285 | -0.00461546109390977 286 | 0.00845651032404617 287 | 0.00333163091427702 288 | 0.00883883310248409 289 | 0.00914278651263822 290 | -0.0229042021937655 291 | 7.90031127981361e-05 292 | -0.00315610066207801 293 | 0.0185414486713627 294 | -0.0148602587826997 295 | -0.0253021041872107 296 | 0.0226397896558919 297 | 0.026484328020687 298 | 0.0178431121249884 299 | 0.00538529394345471 300 | 0.00575844305061934 301 | -0.0126124112001744 302 | -0.0150148129121338 303 | 0.0133731851040051 304 | -0.0355602497302854 305 | 0.0148768140802463 306 | 0.0081955248394447 307 | 0.00867411995089587 308 | -0.00499707524939509 309 | -0.0304609832489816 310 | 0.00620004095793801 311 | 0.0206824741775111 312 | 0.00710500829478773 313 | 0.0358116719104067 314 | 0.0147434106693544 315 | -0.0241134734575913 316 | -0.0235424705029527 317 | -0.0020415390095067 318 | -0.0087593885401606 319 | 0.021974982498926 320 | 0.0359143343973029 321 | 0.0165791318495248 322 | -0.0250208379808288 323 | -0.0157915808776478 324 | -0.0287902234503345 325 | 0.0104734069685228 326 | 0.00805867602387781 327 | -0.000681602973889494 328 | -0.000752748504953371 329 | 0.00173043880484229 330 | 0.00766554828994108 331 | 0.00475400118173025 332 | -0.012307895424675 333 | -0.0032632993489732 334 | -0.00709745960704415 335 | 0.0199838016838747 336 | -0.0183107248218357 337 | 0.00266885039495844 338 | 0.00696074225783079 339 | 0.0125927837720728 340 | -0.0350192330941192 341 | 0.0127096191929006 342 | 0.00774234934439721 343 | 0.0181590286933259 344 | -0.0236070940534704 345 | -0.0322833930593978 346 | 0.0141784127268018 347 | 0.0394875403718459 348 | -0.044548142059655 349 | -0.0318859500658609 350 | -0.0174889735492455 351 | 0.0252009860445467 352 | -0.00539430060387343 353 | 0.029099787440773 354 | -0.0248058882643981 355 | -0.00676461902907344 356 | -0.00974000504917167 357 | -0.00971679366522148 358 | -0.00830756053642618 359 | -0.0249540911852679 360 | 0.0161024316427225 361 | 0.00406766384257716 362 | 0.0209005613240932 363 | -0.0101515790131965 364 | 0.0264405972183969 365 | 0.0240212750715846 366 | 0.0141463792427485 367 | -0.0139323406247348 368 | -0.00722115706541233 369 | 0.020188070043902 370 | -0.00538963024508707 371 | 0.0413164392611754 372 | -0.0152622447246972 373 | -0.0242563456702454 374 | 0.0128991735026278 375 | 0.00128245019391419 376 | -0.0130873536046139 377 | 0.0358921431983208 378 | 0.0178519135121825 379 | -0.0269994559049208 380 | 0.0305043915039024 381 | -0.0134501387344576 382 | 0.00236186052202063 383 | -0.00925925404162254 384 | -0.00710383018214983 385 | 0.0140437145231419 386 | 0.00100818475091391 387 | -0.0119244382980505 388 | 0.00357447024153559 389 | 0.0105854520029156 390 | -0.0152670675531236 391 | 0.000832909263299057 392 | -0.0336417343536407 393 | 0.00253554814742975 394 | -0.0120503499853045 395 | 0.0126233042687783 396 | 0.00710331894396459 397 | 0.0193301568141244 398 | -0.000237022867497021 399 | 0.0357766370676357 400 | -0.0296306202047917 401 | 0.034558758513568 402 | -0.0179077663388064 403 | -0.00289270042117675 404 | -0.0160037598973069 405 | -0.00247875314904835 406 | 0.0114479656903634 407 | -0.00127124029216119 408 | -0.000881255511734581 409 | 0.00572277792614381 410 | 0.0133834874151523 411 | -0.0112151414998321 412 | 0.00141005290193533 413 | -0.0177786526493485 414 | 0.00985982983589146 415 | -0.00543407310449832 416 | 0.00642341734986764 417 | 0.0117904742434182 418 | -0.015547150355511 419 | 0.0294028386912628 420 | 0.053046507993717 421 | -0.0214004668166288 422 | 0.0290491616343883 423 | 0.00323073563165846 424 | -0.00797279985389495 425 | 0.00734904177231135 426 | -0.02775782636175 427 | -0.0115029296459827 428 | -0.00461867900597451 429 | 0.0130353793683088 430 | -0.00758861145342053 431 | -0.000807159576520386 432 | 0.00658925966514655 433 | 0.0209197283816057 434 | 0.00576457761798832 435 | -0.0214253735747561 436 | -0.0198467808023006 437 | -0.0146413390360094 438 | -0.0399052969573343 439 | 0.0117960362223711 440 | -0.0285669049711493 441 | 0.0260548907527107 442 | 0.0232023296338617 443 | 0.00881482688824114 444 | -0.0143044424067542 445 | -0.0194915269092222 446 | 0.0163316262075323 447 | -0.00788103856017101 448 | -0.0158136150989007 449 | -0.00455399528331426 450 | -0.0319964129092602 451 | -0.0304244319268106 452 | 0.000288250855191478 453 | 0.0180801959617532 454 | 0.0193739626631678 455 | -0.00142114685203597 456 | 0.0100843531996071 457 | 0.0220085129758586 458 | 0.0231828481185341 459 | -0.00460358939811723 460 | -0.0210345584112755 461 | 0.0156455279097509 462 | -0.0157462511150658 463 | -0.0137812703154298 464 | -0.00881867224077328 465 | -0.00163196334703807 466 | 0.0185158655420886 467 | -0.0133504308766607 468 | -0.0137227470429623 469 | 0.0386474082753406 470 | 0.0112507228471508 471 | 0.00107077386993278 472 | 0.0117231039256805 473 | 0.00902012461175041 474 | 0.0251015135393935 475 | -0.020866436680405 476 | 0.00430844210779378 477 | 0.035407190518542 478 | 0.0310341417521517 479 | 0.00511980165521379 480 | 0.0268049655375663 481 | 0.00715322921035721 482 | 0.0179911636876346 483 | 0.024226987614586 484 | 0.00870734241342904 485 | -0.0114002218531215 486 | 0.0167730793322622 487 | 0.00856422190219338 488 | 0.000626734059372022 489 | -0.00173164956744145 490 | 0.0319656848735657 491 | -0.0321273929296349 492 | -0.0142842956271582 493 | -0.0159286370432774 494 | -0.00392572924139809 495 | 0.0403305850713262 496 | 0.0291658041718309 497 | -0.00690413493304 498 | -0.0232899564493382 499 | 0.01815917069125 500 | 0.0294932581545452 501 | -0.0195278781587762 502 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="finoptions", 8 | version="0.1.5", 9 | author="Ben Cho", 10 | license="MIT License", # Chose a license from here: https://help.github.com/articles/licensing-a-repository 11 | author_email="ben.cho@gmail.com", 12 | description="Energy derivatives (futures, options etc...)", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | packages=setuptools.find_packages("src"), 16 | package_dir={"": "src"}, 17 | package_data={"": ["*.csv", "*.json", "*.geojson"]}, 18 | keywords=[ 19 | "Energy", 20 | "Risk", 21 | "Crude", 22 | "Trading", 23 | "Petroleum", 24 | "Oil", 25 | "Refinery", 26 | "Refined Products", 27 | "Products", 28 | ], 29 | url="https://github.com/bbcho/finoptions-dev", 30 | # download_url="https://github.com/bbcho/finoptions-dev/archive/refs/heads/main.zip", 31 | install_requires=["scipy>=1.7", "numpy", "numdifftools", "matplotlib"], 32 | include_package_data=True, 33 | classifiers=[ 34 | "Development Status :: 3 - Alpha", # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" as the current state of your package 35 | "License :: OSI Approved :: MIT License", 36 | "Programming Language :: Python :: 3", 37 | "Operating System :: OS Independent", 38 | "Intended Audience :: Financial and Insurance Industry", 39 | ], 40 | python_requires=">=3.6", 41 | ) 42 | -------------------------------------------------------------------------------- /src/finoptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | from .vanillaoptions import * 3 | 4 | from . import basic_american_options 5 | from . import binomial_tree_options 6 | from . import heston_nandi_options 7 | from . import monte_carlo_options 8 | from . import spread_options 9 | 10 | 11 | def docstring_from(source): 12 | """ 13 | Decorator to be used to copy the docstring from the source class/class method 14 | to the target. Useful for class composition. 15 | """ 16 | 17 | def wrapper(target): 18 | target.__doc__ = source.__doc__ 19 | return target 20 | 21 | return wrapper 22 | -------------------------------------------------------------------------------- /src/finoptions/base_test.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import numpy as _np 3 | from math import atan as _atan 4 | import copy as _copy 5 | import numdifftools as _nd 6 | import sys as _sys 7 | import warnings as _warnings 8 | from scipy.optimize import root_scalar as _root_scalar 9 | from scipy.optimize import root as _root 10 | import scipy.optimize as _opt 11 | 12 | 13 | class NormalDistFunctions: 14 | def _NDF(self, x): 15 | """ 16 | Calculate the normal distribution function of x 17 | 18 | Parameters 19 | ---------- 20 | 21 | x : float 22 | Value to calculate the normal distribution function for x. 23 | 24 | Returns 25 | ------- 26 | float 27 | 28 | References 29 | ---------- 30 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 31 | """ 32 | 33 | return _np.exp(-x * x / 2) / _np.sqrt(8 * _atan(1)) 34 | 35 | def _CND(self, x): 36 | """ 37 | Calculate the cumulated normal distribution function of x 38 | 39 | Parameters 40 | ---------- 41 | 42 | x : float 43 | Value to calculate the cumulated normal distribution function for x. 44 | 45 | Returns 46 | ------- 47 | float 48 | 49 | References 50 | ---------- 51 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 52 | """ 53 | 54 | k = 1 / (1 + 0.2316419 * abs(x)) 55 | a1 = 0.319381530 56 | a2 = -0.356563782 57 | a3 = 1.781477937 58 | a4 = -1.821255978 59 | a5 = 1.330274429 60 | result = ( 61 | self._NDF(x) 62 | * (a1 * k + a2 * k ** 2 + a3 * k ** 3 + a4 * k ** 4 + a5 * k ** 5) 63 | - 0.5 64 | ) 65 | result = 0.5 - result * _np.sign(x) 66 | 67 | return result 68 | 69 | def _CBND(self, x1, x2, rho): 70 | """ 71 | Calculate the cumulative bivariate normal distribution function. 72 | 73 | Haug E.G., The Complete Guide to Option Pricing Formulas 74 | 75 | Compute: 76 | Take care for the limit rho = +/- 1 77 | """ 78 | 79 | a = x1 80 | b = x2 81 | if abs(rho) == 1: 82 | rho = rho - (1e-12) * _np.sign(rho) 83 | # cat("\n a - b - rho :"); print(c(a,b,rho)) 84 | X = [0.24840615, 0.39233107, 0.21141819, 0.03324666, 0.00082485334] 85 | y = [0.10024215, 0.48281397, 1.0609498, 1.7797294, 2.6697604] 86 | a1 = a / _np.sqrt(2 * (1 - rho ** 2)) 87 | b1 = b / _np.sqrt(2 * (1 - rho ** 2)) 88 | if (a <= 0) & (b <= 0) & (rho <= 0): 89 | Sum1 = 0 90 | for I in range(0, 5): 91 | for j in range(0, 5): 92 | Sum1 = Sum1 + X[I] * X[j] * _np.exp( 93 | a1 * (2 * y[I] - a1) 94 | + b1 * (2 * y[j] - b1) 95 | + 2 * rho * (y[I] - a1) * (y[j] - b1) 96 | ) 97 | result = _np.sqrt(1 - rho ** 2) / _np.pi * Sum1 98 | return result 99 | 100 | if (a <= 0) & (b >= 0) & (rho >= 0): 101 | result = self._CND(a) - self._CBND(a, -b, -rho) 102 | return result 103 | 104 | if (a >= 0) & (b <= 0) & (rho >= 0): 105 | result = self._CND(b) - self._CBND(-a, b, -rho) 106 | return result 107 | 108 | if (a >= 0) & (b >= 0) & (rho <= 0): 109 | result = self._CND(a) + self._CND(b) - 1 + self._CBND(-a, -b, rho) 110 | return result 111 | 112 | if (a * b * rho) >= 0: 113 | rho1 = ( 114 | (rho * a - b) 115 | * _np.sign(a) 116 | / _np.sqrt(a ** 2 - 2 * rho * a * b + b ** 2) 117 | ) 118 | rho2 = ( 119 | (rho * b - a) 120 | * _np.sign(b) 121 | / _np.sqrt(a ** 2 - 2 * rho * a * b + b ** 2) 122 | ) 123 | delta = (1 - _np.sign(a) * _np.sign(b)) / 4 124 | result = self._CBND(a, 0, rho1) + self._CBND(b, 0, rho2) - delta 125 | return result 126 | 127 | 128 | class Display: 129 | """ 130 | Class for printing summaries of Options classes 131 | 132 | Parameters 133 | ---------- 134 | opt : Option class 135 | Option class with Option values to calculate greeks from 136 | call : bool 137 | True if to include call option price, False to exclude. By default True. 138 | put : bool 139 | True if to include put option price, False to exclude. By default True. 140 | """ 141 | 142 | def __init__(self, opt, call=True, put=True): 143 | if isinstance(opt, Option): 144 | self._opt = opt 145 | self._call = call 146 | self._put = put 147 | else: 148 | raise ValueError("Parameter opt is not of the Option class") 149 | 150 | def _check_string(self, x): 151 | """ 152 | helper function for summary method. Checks to see if the variable x is a numpy 153 | ndarray type and if it's length is greater than 6. If so, shortens the repsentation 154 | so that it fits. 155 | """ 156 | if isinstance(x, _np.ndarray): 157 | if x.shape[0] > 6: 158 | return _np.array2string(x.round(2), threshold=6) 159 | 160 | return x 161 | 162 | def __str__(self): 163 | return self.summary(printer=False) 164 | 165 | def __repr__(self): 166 | out = f"{self.__name__}(" 167 | params = self.get_params() 168 | 169 | for p in params: 170 | out = out + str(self._check_string(params[p])) + ", " 171 | 172 | # get rid of trailing comman and close pararenthesis 173 | out = out[:-2] 174 | out += ")" 175 | 176 | return out 177 | 178 | def summary(self, printer=True): 179 | """ 180 | Print summary report of option 181 | 182 | Parameters 183 | ---------- 184 | printer : bool 185 | True to print summary. False to return a string. 186 | """ 187 | out = f"Title: {self._opt.__title__} Valuation\n\nParameters:\n\n" 188 | 189 | params = self._opt.get_params() 190 | 191 | for p in params: 192 | out += f" {p} = {self._opt._check_string(params[p])}\n" 193 | 194 | try: 195 | # for printing arrays of calls and puts (converts to string first) 196 | if isinstance(self._opt.call(), _np.ndarray): 197 | price = f"\nOption Price:\n\n " 198 | 199 | if self._call == True: 200 | c = self._check_string(self._opt.call().round(2)) 201 | price += f"call: {c}\n " 202 | if self._put == True: 203 | p = self._check_string(self._opt.put().round(2)) 204 | price += f"put: {p}" 205 | else: 206 | price = f"\nOption Price:\n\n " 207 | if self._call == True: 208 | price += f"call: {round(self._opt.call(),6)}\n " 209 | if self._put == True: 210 | price += f"put: {round(self._opt.put(),6)}" 211 | out += price 212 | except: 213 | raise ValueError("call or put option methods failed") 214 | 215 | if printer == True: 216 | print(out) 217 | else: 218 | return out 219 | 220 | 221 | class Option(ABC): 222 | """ 223 | Base interface class for all options 224 | 225 | """ 226 | 227 | @property 228 | @abstractmethod 229 | def __name__(self): 230 | # abstract attribute 231 | pass 232 | 233 | @property 234 | @abstractmethod 235 | def __title__(self): 236 | # abstract attribute 237 | pass 238 | 239 | # @abstractmethod 240 | # def __str__(self): 241 | # pass 242 | 243 | # @abstractmethod 244 | # def __repr__(self): 245 | # pass 246 | 247 | def __str__(self): 248 | return self.summary(printer=False) 249 | 250 | def __repr__(self): 251 | out = f"{self.__name__}(" 252 | params = self.get_params() 253 | 254 | for p in params: 255 | out = out + str(self._check_string(params[p])) + ", " 256 | 257 | # get rid of trailing comman and close pararenthesis 258 | out = out[:-2] 259 | out += ")" 260 | 261 | return out 262 | 263 | def get_params(self): 264 | return { 265 | "S": self._S, 266 | "K": self._K, 267 | "t": self._t, 268 | "r": self._r, 269 | "b": self._b, 270 | "sigma": self._sigma, 271 | } 272 | 273 | def _check_sigma(self, func): 274 | if self._sigma is None: 275 | raise ValueError(f"sigma not defined. Required for {func}() method") 276 | 277 | def copy(self): 278 | return _copy.deepcopy(self) 279 | 280 | # @abstractmethod 281 | # def get_params(self): 282 | # pass 283 | 284 | def set_param(self, x: str, value: float): 285 | """ 286 | Method to change a parameter once the class hass been initiatized. 287 | 288 | Parameters 289 | ---------- 290 | x : str 291 | Name of parameter to change 292 | value : float 293 | New value to give to parameter 294 | 295 | Returns 296 | ------- 297 | None 298 | """ 299 | 300 | tmp = self.get_params() 301 | tmp[x] = value 302 | 303 | self.__init__(**tmp) 304 | 305 | @abstractmethod 306 | def summary(self): 307 | pass 308 | 309 | @abstractmethod 310 | def delta(self): 311 | pass 312 | 313 | @abstractmethod 314 | def theta(self): 315 | pass 316 | 317 | @abstractmethod 318 | def vega(self): 319 | pass 320 | 321 | @abstractmethod 322 | def rho(self): 323 | pass 324 | 325 | @abstractmethod 326 | def lamb(self): 327 | pass 328 | 329 | @abstractmethod 330 | def gamma(self): 331 | pass 332 | 333 | @abstractmethod 334 | def volatility(self): 335 | pass 336 | 337 | 338 | class ImpliedVolatility: 339 | """ 340 | Class used to calculate implied volatility for Option classes 341 | 342 | Parameters 343 | ---------- 344 | opt : Option class 345 | Option class with Option values to calculate greeks from 346 | 347 | """ 348 | 349 | def __init__(self, opt): 350 | if isinstance(opt, Option): 351 | self._opt = opt 352 | else: 353 | raise ValueError("Parameter opt is not of the Option class") 354 | 355 | def _max_array(self, *args): 356 | """ 357 | helper function to get largest ndarray. Assumes at 358 | least one array. 359 | """ 360 | maxArray = _np.array([0]) 361 | for a in args: 362 | if isinstance(a, _np.ndarray): 363 | if maxArray.size < a.size: 364 | maxArray = a 365 | 366 | return maxArray 367 | 368 | def _func(sigma, obj, call, price): 369 | """ 370 | helper function to be used for volatility root finding. 371 | """ 372 | temp = obj.copy() 373 | temp.set_param("sigma", sigma) 374 | if call == True: 375 | return price - temp.call() 376 | else: 377 | return price - temp.put() 378 | 379 | def _check_array(self, *args): 380 | """ 381 | helper function to return True if any args past are numpy ndarrays 382 | """ 383 | for a in args: 384 | # allows size 1 arrays. somethings greeks return size 1 arrays 385 | if isinstance(a, _np.ndarray) and a.size > 1: 386 | return True 387 | 388 | return False 389 | 390 | def _volatility( 391 | self, 392 | price: float, 393 | call: bool = True, 394 | tol=_sys.float_info.epsilon, 395 | maxiter=10000, 396 | verbose=False, 397 | _func=_func, 398 | ): 399 | """ 400 | Compute the implied volatility of the GBSOption. 401 | 402 | Parameters 403 | ---------- 404 | price : float 405 | Current price of the option 406 | call : bool 407 | Returns implied volatility for call option if True, else returns implied volatility for put options. By default True. 408 | tol : float 409 | max tolerance to fit the price to. By default system tolerance. 410 | maxiter : int 411 | number of iterations to run to fit price. 412 | verbose : bool 413 | True to return full optimization details from root finder function. False to just return the implied volatility numbers. 414 | 415 | Returns 416 | ------- 417 | float 418 | 419 | Example 420 | ------- 421 | """ 422 | if self._opt._sigma is not None: 423 | _warnings.warn("sigma is not None but calculating implied volatility.") 424 | 425 | # check to see if arrays were past vs scalars. 426 | if self._check_array(price, *self._opt.get_params().values()): 427 | # if arrays, use root function 428 | a = self._opt._max_array(price, *self._opt.get_params().values()) 429 | if verbose == True: 430 | sol = _root(_func, args=(self._opt, call, price), x0=_np.ones_like(a)) 431 | else: 432 | sol = _root(_func, args=(self._opt, call, price), x0=_np.ones_like(a)).x 433 | else: 434 | # if scalars use root_scalar function 435 | if verbose == True: 436 | sol = _root_scalar( 437 | _func, 438 | (self._opt, call, price), 439 | bracket=[-10, 10], 440 | xtol=tol, 441 | maxiter=maxiter, 442 | ) 443 | # sol = _opt.brentq(_func, 1, 10000, xtol=tol, maxiter=maxiter) 444 | else: 445 | sol = _root_scalar( 446 | _func, 447 | (self._opt, call, price), 448 | bracket=[-10, 10], 449 | xtol=tol, 450 | maxiter=maxiter, 451 | ).root 452 | 453 | return sol 454 | 455 | 456 | if __name__ == "__main__": 457 | 458 | class TestOpt(Option): 459 | __name__ = "test" 460 | __repr__ = "test" 461 | 462 | opt = TestOpt() 463 | -------------------------------------------------------------------------------- /src/finoptions/heston_nandi_options/__init__.py: -------------------------------------------------------------------------------- 1 | from ..base import Option as _Option 2 | from ..vanillaoptions import GreeksFDM as _GreeksFDM 3 | import numpy as _np 4 | from scipy.optimize import minimize as _minimize 5 | from scipy.stats import norm as _norm 6 | from dataclasses import dataclass 7 | from scipy.integrate import quad as _quad 8 | # from .hnGARCH import * 9 | 10 | # def _HNGCharacteristics(lamb, omega, alpha, beta, gamma, S, K, t_in_days, r_daily, call=True): 11 | # """ 12 | # Characteristics function for Heston Nandi Option 13 | # """ 14 | 15 | # premium = HNGOption(TypeFlag, model, S, X, Time.inDays, r.daily) 16 | # delta = HNGGreeks("Delta", TypeFlag, model, S, X, Time.inDays, r.daily) 17 | # gamma = HNGGreeks("Gamma", TypeFlag, model, S, X, Time.inDays, r.daily) 18 | 19 | # # Return Value: 20 | # list(premium = premium, delta = delta, gamma = gamma) 21 | 22 | _eps = 1.49e-8 23 | low_lim = 0 24 | high_lim = _np.inf 25 | 26 | class HNGGreeks(_GreeksFDM): 27 | pass 28 | 29 | 30 | class HestonNandiOption: # _Option 31 | """ 32 | Option class for the Heston-Nandi Garch Option Model. 33 | 34 | Parameters 35 | ---------- 36 | S : float 37 | Level or index price. 38 | K : float 39 | Strike price. 40 | t : float 41 | Time-to-maturity in days. i.e. 1/252 for 1 business day, 5/252 for 1 week etc... 42 | r : float 43 | Daily rate of interest. i.e. 0.25/252 means about 0.001% per day. 44 | lamb : float 45 | 46 | omega : float 47 | The GARCH model parameter which specifies the constant coefficient of the variance 48 | equation. 49 | alpha : float 50 | The GARCH model parameter which specifies the autoregressive coefficient 51 | beta : float 52 | The GARCH model parameter which specifies the variance coefficient. 53 | gamma : float 54 | The GARCH model parameter which specifies the asymmetry coefficient. 55 | 56 | Notes 57 | ----- 58 | 59 | 60 | Returns 61 | ------- 62 | HestonNandiOption object. 63 | 64 | Example 65 | ------- 66 | >>> import finoptions as fo 67 | >>> opt = fo.heston_nandi_options.HestonNandiOption(S=80, K=82, t=1/3, td=1/4, r=0.06, D=4, sigma=0.30) 68 | >>> opt.call() 69 | >>> opt.greeks(call=True) 70 | 71 | References 72 | ---------- 73 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 74 | """ 75 | 76 | __name__ = "HestonNandiOption" 77 | __title__ = "The Heston-Nandi Garch Option Model" 78 | 79 | def __init__( 80 | self, 81 | S: float, 82 | K: float, 83 | t: float, 84 | r: float, 85 | lamb: float = None, 86 | omega: float = None, 87 | alpha: float = None, 88 | beta: float = None, 89 | gamma: float = None, 90 | model=None, 91 | ): 92 | 93 | self._S = S 94 | self._K = K 95 | self._t = t 96 | self._r = r 97 | self._lamb = lamb 98 | self._omega = omega 99 | self._alpha = alpha 100 | self._beta = beta 101 | self._gamma = gamma 102 | 103 | def call(self): 104 | """ 105 | # Integrate: 106 | call1 = integrate(.fstarHN, 0, Inf, const = 1, model = model, 107 | S = S, X = X, Time.inDays = Time.inDays, r.daily = r.daily) 108 | 109 | # Compute Call Price: 110 | call.price = S/2 + exp(-r.daily*Time.inDays) * call1$value - 111 | X * exp(-r.daily*Time.inDays) * ( 1/2 + call2$value ) 112 | 113 | """ 114 | # fmt: off 115 | call1 = _quad( 116 | _fHN, low_lim, high_lim, 117 | args=(1, self._lamb, self._omega, self._alpha, self._beta, self._gamma, self._S, self._K, self._t, self._r), 118 | epsabs=_eps, epsrel=_eps, 119 | ) 120 | call2 = _quad( 121 | _fHN, low_lim, high_lim, 122 | args=(0, self._lamb, self._omega, self._alpha, self._beta, self._gamma, self._S, self._K, self._t, self._r), 123 | epsabs=_eps, epsrel=_eps, 124 | ) 125 | # fmt: on 126 | 127 | # Compute Call Price: 128 | price = ( 129 | self._S / 2 130 | + _np.exp(-self._r * self._t) * call1[0] 131 | - self._K * _np.exp(-self._r * self._t) * (1 / 2 + call2[0]) 132 | ) 133 | 134 | return price 135 | 136 | def put(self): 137 | return self.call() + self._K * _np.exp(-self._r * self._t) - self._S 138 | 139 | 140 | def delta(self, call=True): 141 | # Integrate: 142 | # fmt: off 143 | delta1 = _quad( 144 | _fdeltaHN, low_lim, high_lim, 145 | args=(1, self._lamb, self._omega, self._alpha, self._beta, self._gamma, self._S, self._K, self._t, self._r), 146 | epsabs=_eps, epsrel=_eps, 147 | ) 148 | 149 | delta2 = _quad( 150 | _fdeltaHN, low_lim, high_lim, 151 | args=(0, self._lamb, self._omega, self._alpha, self._beta, self._gamma, self._S, self._K, self._t, self._r), 152 | epsabs=_eps, epsrel=_eps, 153 | ) 154 | 155 | # Compute Call and Put Delta : 156 | greek = 1/2 + _np.exp(-self._r*self._t) * delta1[0] - self._K * _np.exp(-self._r*self._t) * delta2[0] 157 | if call==False: 158 | greek = greek - 1 159 | 160 | return greek 161 | 162 | def gamma(self): 163 | # Integrate: 164 | # fmt: off 165 | gamma1 = _quad( 166 | _fgammaHN, low_lim, high_lim, 167 | args=(1, self._lamb, self._omega, self._alpha, self._beta, self._gamma, self._S, self._K, self._t, self._r), 168 | epsabs=_eps, epsrel=_eps, 169 | ) 170 | 171 | gamma2 = _quad( 172 | _fgammaHN, low_lim, high_lim, 173 | args=(0, self._lamb, self._omega, self._alpha, self._beta, self._gamma, self._S, self._K, self._t, self._r), 174 | epsabs=_eps, epsrel=_eps, 175 | ) 176 | 177 | # Compute Call and Put Delta : 178 | greek = _np.exp(-self._r*self._t) * gamma1[0] - self._K * _np.exp(-self._r*self._t) * gamma2[0] 179 | 180 | return greek 181 | 182 | 183 | def _fHN(phi, const, lamb, omega, alpha, beta, gamma, S, K, t, r, real=True): 184 | """ 185 | real=True to return fstarHN, real=False to return fHN 186 | """ 187 | 188 | # Internal Function: 189 | 190 | # Model Parameters: 191 | gamma = gamma + lamb + 1 / 2 192 | lamb = -1 / 2 193 | sigma2 = (omega + alpha) / (1 - beta - alpha * gamma ** 2) 194 | # Function to be integrated: 195 | cphi0 = phi * _np.array([1j]) 196 | cphi = cphi0 + const 197 | a = cphi * r 198 | b = lamb * cphi + cphi * cphi / 2 199 | # fmt: off 200 | for i in range(1, t): 201 | a = a + cphi*r + b*omega - _np.log(1-2*alpha*b)/2 202 | b = cphi*(lamb+gamma) - gamma**2/2 + beta*b + 0.5*(cphi-gamma)**2/(1-2*alpha*b) 203 | 204 | if real == True: 205 | f = _np.real(_np.exp(-cphi0*_np.log(K)+cphi*_np.log(S)+a+b*sigma2 )/cphi0)/_np.pi 206 | else: 207 | f = _np.exp(-cphi0*_np.log(K)+cphi*_np.log(S)+a+b*sigma2 )/cphi0/_np.pi 208 | 209 | # Return Value: 210 | return f 211 | # fmt: on 212 | 213 | 214 | def _fdeltaHN(phi, const, lamb, omega, alpha, beta, gamma, S, K, t, r): 215 | # Function to be integrated: 216 | cphi0 = phi * _np.array([1j]) 217 | cphi = cphi0 + const 218 | fdelta = cphi * _fHN(phi, const, lamb, omega, alpha, beta, gamma, S, K, t, r, real=False) / S 219 | 220 | # Return Value: 221 | return _np.real(fdelta) 222 | 223 | 224 | def _fgammaHN(phi, const, lamb, omega, alpha, beta, gamma, S, K, t, r): 225 | # Function to be integrated: 226 | cphi0 = phi * _np.array([1j]) 227 | cphi = cphi0 + const 228 | fgamma = ( 229 | cphi * (cphi - 1) * _fHN(phi, const, lamb, omega, alpha, beta, gamma, S, K, t, r, real=False) / S ** 2 230 | ) 231 | 232 | # Return Value: 233 | return _np.real(fgamma) 234 | 235 | @dataclass 236 | class ParamFit: 237 | llhHNGarch: float 238 | z: _np.array 239 | h: _np.array 240 | 241 | def __sub__(self,b): 242 | return self.llhHNGarch - b.llhHNGarch 243 | 244 | 245 | def _llhHNGarch(x0, trace, symmetric, rfr, x, ret_obj=False): 246 | 247 | h = x.copy() 248 | z = x.copy() 249 | 250 | lamb = x0[0] 251 | omega = x0[1] 252 | alpha = x0[2] 253 | beta = x0[3] 254 | gamma = x0[4] 255 | 256 | # Transform - to keep them between 0 and 1: 257 | omega = 1 / (1 + _np.exp(-omega)) 258 | alpha = 1 / (1 + _np.exp(-alpha)) 259 | beta = 1 / (1 + _np.exp(-beta)) 260 | 261 | # Add gamma if selected: 262 | if ~symmetric: 263 | gam = gamma 264 | else: 265 | gam = 0 266 | 267 | # HN Garch Filter: 268 | h[0] = (omega + alpha) / (1 - alpha * gam * gam - beta) 269 | z[0] = (x[0] - rfr - lamb * h[0]) / _np.sqrt(h[0]) 270 | 271 | # fmt: off 272 | for i in range(1,len(z)): 273 | h[i] = omega + alpha * ( z[i-1] - gam * _np.sqrt(h[i-1]) )**2 + beta * h[i-1] 274 | z[i] = ( x[i] - rfr - lamb*h[i] ) / _np.sqrt(h[i]) 275 | # fmt: on 276 | 277 | # Calculate Log - Likelihood for Normal Distribution: 278 | llhHNGarch = -_np.sum(_np.log(_norm.pdf(z) / _np.sqrt(h))) 279 | if trace: 280 | print("Parameter Estimate\n") 281 | print(lamb, omega, alpha, beta, gam) 282 | 283 | if ret_obj: 284 | params = ParamFit(llhHNGarch, z, h) 285 | else: 286 | params = llhHNGarch 287 | 288 | # Return Value: 289 | return params 290 | 291 | 292 | def hngarch_fit(x, lamb = -0.5, omega = None, alpha = None, beta = 0.1, gamma = 0, rf = 0, symmetric = True, trace = True, **kw_nlm): 293 | # A function implemented by Diethelm Wuertz 294 | 295 | # Description: 296 | # Fits Heston-Nandi Garch(1,1) time series model 297 | 298 | # Parameters: 299 | if omega is None: 300 | omega = _np.var(x) 301 | if alpha is None: 302 | alpha = 0.1*omega 303 | 304 | rfr = rf 305 | gam = gamma 306 | 307 | # Continue: 308 | params = dict(lamb = lamb, omega = omega, alpha = alpha, 309 | beta = beta, gamma = gam, rf = rfr) 310 | 311 | # Transform Parameters and Calculate Start Parameters: 312 | par_omega = -_np.log((1-omega)/omega) # for 2 313 | par_alpha = -_np.log((1-alpha)/alpha) # for 3 314 | par_beta = -_np.log((1-beta)/beta) # for 4 315 | par_start = [lamb, par_omega, par_alpha, par_beta] 316 | if ~symmetric: 317 | par_start.append(gam) 318 | 319 | # Initial Log Likelihood: 320 | opt = dict() 321 | opt['value'] = _llhHNGarch(x0 = par_start, trace = trace, symmetric = symmetric, rfr = rfr, x = x) 322 | opt['estimate'] = par_start 323 | 324 | if trace: 325 | print(lamb, omega, alpha, beta, gam) 326 | print(opt['value']) 327 | 328 | # Estimate Parameters: 329 | res = _minimize(_llhHNGarch, par_start, args=(trace, symmetric, rfr, x), method='L-BFGS-B', **kw_nlm) 330 | 331 | # Log-Likelihood: 332 | opt['minimum'] = -res.fun + len(x)*_np.sqrt(2*_np.pi) 333 | # opt['params'] = params 334 | # opt['symmetric'] = symmetric 335 | opt['estimate'] = res.x 336 | 337 | # LLH, h, and z for Final Estimates: 338 | final = _llhHNGarch(opt['estimate'], trace = False, symmetric=symmetric, rfr=rfr, x=x, ret_obj=True) 339 | opt['h'] = final.h 340 | opt['z'] = final.z 341 | 342 | # Backtransform Estimated parameters: 343 | lamb = opt['estimate'][0] 344 | omega = (1 / (1+_np.exp(-opt['estimate'][1]))) 345 | opt['estimate'][1] = omega 346 | 347 | alpha = (1 / (1+_np.exp(-opt['estimate'][2]))) 348 | opt['estimate'][2] = alpha 349 | 350 | beta = (1 / (1+_np.exp(-opt['estimate'][3]))) 351 | opt['estimate'][3] = beta 352 | 353 | if symmetric: 354 | opt['estimate'][4] = 0 355 | gam = opt['estimate'][4] 356 | 357 | # names(opt$estimate) = c("lambda", "omega", "alpha", "beta", "gamma") 358 | 359 | # Add to Output: 360 | opt['model'] = dict(lamb = lamb, omega = omega, alpha = alpha, 361 | beta = beta, gamma = gam, rf = rfr) 362 | # opt['x'] = x 363 | 364 | # Statistics - Printing: 365 | opt['persistence'] = beta + alpha*gam*gam 366 | opt['sigma2'] = ( omega + alpha ) / ( 1 - opt['persistence'] ) 367 | 368 | # Print Estimated Parameters: 369 | if (trace): 370 | print(opt['estimate']) 371 | 372 | opt['title'] = "Heston-Nandi Garch Parameter Estimation" 373 | 374 | return opt 375 | 376 | 377 | def hngarch_sim(lamb = -0.5, omega = None, alpha = None, beta = 0.1, gamma = 0, rf = 0, n = 500, n_start = 0, inno=None, inno_start=None): 378 | """ 379 | Description: 380 | Simulates a HN-GARCH time series with user supplied innovations. 381 | 382 | Details: 383 | The function simulates a Heston Nandi Garch(1,1) process with 384 | structure parameters specified through the parameters 385 | lambda, omega, alpha, beta, gamma, rf. 386 | The function returns the simulated time series points 387 | neglecting those from the first "start_innov" innovations. 388 | 389 | Parameters 390 | ---------- 391 | lamb : float 392 | omega : float 393 | alpha : float 394 | beta : float 395 | gamma : float 396 | rf : float 397 | n : int 398 | n_start : int 399 | inno : 400 | inno_start : 401 | 402 | Example: 403 | x = hngarch() 404 | plot(100*x, type="l", xlab="Day numbers", 405 | ylab="Daily Returns %", main="Heston Nandi GARCH") 406 | S0 = 1 407 | plot(S0*exp(cumsum(x)), type="l", xlab="Day Numbers", 408 | ylab="Daily Prices", main="Heston Nandi GARCH") } 409 | """ 410 | # FUNCTION: 411 | 412 | # Innovations: 413 | if inno is None: 414 | inno = _np.random.normal(0, 1, n) 415 | if (inno_start is None) & (n_start > 0): 416 | inno_start = _np.random.normal(0, 1, n_start) 417 | 418 | if inno_start is not None: 419 | x = _np.concatenate((inno_start, inno)) 420 | else: 421 | x = inno.copy() 422 | h = x.copy() 423 | Z = x.copy() 424 | 425 | nt = n_start + n 426 | 427 | # Recursion: 428 | h[0] = ( omega + alpha )/( 1 - alpha*gamma*gamma - beta ) 429 | x[0] = rf + lamb*h[0] + _np.sqrt(h[0]) * Z[0] 430 | for i in range(1,nt): 431 | h[i] = omega + alpha*(Z[i-1] - gamma*_np.sqrt(h[i-1]))**2 + beta*h[i-1] 432 | x[i] = rf + lamb*h[i] + _np.sqrt(h[i]) * Z[i] 433 | 434 | # Series: 435 | x = x[(n_start):] 436 | 437 | # Return Value: 438 | return x -------------------------------------------------------------------------------- /src/finoptions/heston_nandi_options/hnGARCH.py: -------------------------------------------------------------------------------- 1 | from ..base import Option as _Option 2 | from ..vanillaoptions import GreeksFDM as _GreeksFDM 3 | import numpy as _np 4 | from scipy.integrate import quad as _quad 5 | from scipy.stats import norm as _norm 6 | 7 | from dataclasses import dataclass 8 | 9 | 10 | @dataclass 11 | class ParamFit: 12 | llhHNGarch: float 13 | Z: _np.array 14 | h: _np.array 15 | 16 | 17 | def _llhHNGarch(x, lamb, omega, alpha, beta, gamma, trace, symmetric, rfr): 18 | 19 | h = x 20 | Z = x 21 | 22 | # Transform - to keep them between 0 and 1: 23 | omega = 1 / (1 + _np.exp(-omega)) 24 | alpha = 1 / (1 + _np.exp(-alpha)) 25 | beta = 1 / (1 + _np.exp(-beta)) 26 | 27 | # Add gamma if selected: 28 | if ~symmetric: 29 | gam = gamma 30 | else: 31 | gam = 0 32 | 33 | # HN Garch Filter: 34 | h[1] = (omega + alpha) / (1 - alpha * gam * gam - beta) 35 | Z[1] = (x[1] - rfr - lamb * h[1]) / _np.sqrt(h[1]) 36 | # fmt: off 37 | for i in range(1,len(Z)): 38 | h[i] = omega + alpha * ( Z[i-1] - gam * _np.sqrt(h[i-1]) )**2 + beta * h[i-1] 39 | Z[i] = ( x[i] - rfr - lamb*h[i] ) / _np.sqrt(h[i]) 40 | # fmt: on 41 | 42 | # Calculate Log - Likelihood for Normal Distribution: 43 | llhHNGarch = -_np.sum(_np.log(_norm.pdf(Z) / _np.sqrt(h))) 44 | if trace: 45 | print("Parameter Estimate\n") 46 | print(lamb, omega, alpha, beta, gam) 47 | 48 | params = ParamFit() 49 | params.llhHNGarch = llhHNGarch 50 | params.Z = Z 51 | params.h = h 52 | 53 | # Return Value: 54 | return params 55 | 56 | 57 | class HNGarch: 58 | def __init__(self): 59 | pass 60 | -------------------------------------------------------------------------------- /src/finoptions/monte_carlo_options/__init__.py: -------------------------------------------------------------------------------- 1 | from ..base import GreeksFDM, Option as _Option 2 | from ..utils import docstring_from 3 | from ..vanillaoptions import GBSOption as _GBSOption 4 | 5 | from .mc_innovations import * 6 | from .mc_paths import * 7 | from .mc_payoffs import * 8 | 9 | import numpy as _np 10 | import warnings as _warnings 11 | 12 | 13 | class MonteCarloOption: # _Option 14 | """ 15 | Class for the valuation of options using Monte Carlo methods using passed innovation, path and 16 | payoff calculation classes that inherit from the classes Innovations, Path and Payoff respectively. 17 | Parent classes are defined within this module. See example below. 18 | 19 | Parameters 20 | ---------- 21 | mc_paths : int 22 | Number of payoff paths to generate per mc_loop. Total number of monte carlo sample paths 23 | is mc_loops * mc_paths. 24 | mc_loops : int 25 | Number of times to generate mc_path number of monte carlo paths. Any positive number. 26 | Total number of monte carlo sample paths is mc_loops * mc_paths. 27 | path_length : int 28 | Number of time steps to maturity t. dt = t/path_length. 29 | S : float 30 | Level or index price. 31 | K : float 32 | Strike price. 33 | t : float 34 | Time-to-maturity in fractional years. i.e. 1/12 for 1 month, 1/252 for 1 business day, 1.0 for 1 year. 35 | r : float 36 | Risk-free-rate in decimal format (i.e. 0.01 for 1%). 37 | b : float 38 | Annualized cost-of-carry rate, e.g. 0.1 means 10%. 39 | sigma : float 40 | Annualized volatility of the underlying asset. Optional if calculating implied volatility. 41 | Required otherwise. By default None. 42 | Innovation : Innovations class 43 | An Innovations subclass that has a sample_innovation method for generating innovations for 44 | the path generation step. sample_innovation method must return an numpy array of shape 45 | (mc_paths, path_length). See parent definition below. 46 | Path : Path subclass 47 | A Path subclass that has a generate_path method for generating monte carlo paths using the 48 | innovations of the Innovation class. generate_path method must return a numpy array of 49 | shape (mc_paths, path_length). See parent definition below. 50 | Payoff : Payoff sublass 51 | A Payoff subclass that has both the call and put methods for calculation strike payoffs 52 | at option maturity. Class must be able to calculate the payoffs based on a numpy array 53 | of monte carlo paths and call and put methods must return numpy arrays of shape 54 | (mc_paths,). See parent definition below. 55 | trace : bool 56 | If True, it will return the average option value per mc_loop as well as the cumulative 57 | average of these averages. Only useful with mc_loops > 1. By default False. 58 | antithetic : bool 59 | If True, innovations generated from the Innovations class is combined with it's negative 60 | version (i.e. -1 * Innovations) to center the innovations around zero. Note if set to 61 | True, this will double the number of mc_paths. By default False. 62 | standardization : bool 63 | If True, the first (mean) and second (standard deviation) moments of the generated 64 | innovations are made to match 0 and 1 respectively to account for errors in a pseudo 65 | random number generator. By default False. 66 | eps : numpy array 67 | pregenerated innovations to be used instead of one generated by an innovation class. Note 68 | that the innovation class must still be passed. By default None. For testing purposes only. 69 | 70 | Notes 71 | ----- 72 | Including a cost of carry term b, the model can 73 | used to price European Options on: 74 | 75 | b = r stocks (Black and Scholes’ stock option model) 76 | b = r - q stocks and stock indexes paying a continuous dividend yield q (Merton’s stock option model) 77 | b = 0 futures (Black’s futures option model) 78 | b = r - rf currency options with foreign interst rate rf (Garman and Kohlhagen’s currency option model) 79 | 80 | Parent Classes 81 | -------------- 82 | 83 | class Innovations(ABC): 84 | def __init__(self, mc_paths, path_length, eps=None): 85 | self.mc_paths = mc_paths 86 | self.path_length = path_length 87 | self._eps = eps # for testing only 88 | 89 | @abstractmethod 90 | def sample_innovation(self, init=False): 91 | pass # must return a numpy array of shape (mc_paths, path_length) 92 | 93 | class Path(ABC): 94 | def __init__(self, epsilon, sigma, dt, b): 95 | self.epsilon = epsilon # array of innovations 96 | self.sigma = sigma 97 | self.dt = dt # calculated dt from t/path_length 98 | self.b = b 99 | 100 | @abstractmethod 101 | def generate_path(self): 102 | pass # must return a numpy array of shape (mc_paths, path_length) 103 | 104 | class Payoff(ABC): 105 | def __init__(self, path, S: float, K: float, t: float, r: float, b: float, sigma: float): 106 | self.path = path 107 | self.S = S 108 | self.K = K 109 | self.t = t 110 | self.r = r 111 | self.b = b 112 | self.sigma = sigma 113 | 114 | @abstractmethod 115 | def call(self): 116 | pass # must return an average payoff for each mc_loop 117 | 118 | @abstractmethod 119 | def put(self): 120 | pass # must return an average payoff for each mc_loop 121 | 122 | Returns 123 | ------- 124 | MonteCarloOption object. 125 | 126 | Example 127 | ------- 128 | >>> import finoptions as fo 129 | >>> from scipy.stats import qmc, norm 130 | >>> import numpy as np 131 | >>> from finoptions.monte_carlo_options import Innovations, Path, Payoff 132 | >>> S = 100 133 | >>> K = 100 134 | >>> t = 1 / 12 135 | >>> sigma = 0.4 136 | >>> r = 0.10 137 | >>> b = 0.1 138 | >>> path_length = 30 139 | >>> mc_paths = 5000 140 | >>> mc_loops = 50 141 | 142 | >>> class NormalSobolInnovations(Innovations): 143 | def sample_innovation(self, scramble=True): 144 | sobol = qmc.Sobol(self.path_length, scramble=scramble).random(self.mc_paths) 145 | if scramble == False: 146 | # add new sample since if not scrambled first row is zero which leads to -inf when normalized 147 | sobol = sobol[1:] 148 | sobol = np.append( 149 | sobol, 150 | qmc.Sobol(self.path_length, scramble=scramble).fast_forward(self.mc_paths).random(1), 151 | axis=0, 152 | ) 153 | sobol = norm.ppf(sobol) 154 | return sobol 155 | 156 | >>> class WienerPath(Path): 157 | def generate_path(self, **kwargs): 158 | return (self.b - (self.sigma ** 2) / 2) * self.dt + self.sigma * np.sqrt(self.dt) * self.epsilon 159 | 160 | >>> class PlainVanillaPayoff(Payoff): 161 | def call(self): 162 | St = self.S * np.exp(np.sum(self.path.generate_path(), axis=1)) 163 | return np.exp(-self.r * self.t) * np.maximum(St - self.K, 0) 164 | 165 | def put(self): 166 | St = self.S * np.exp(np.sum(self.path.generate_path(), axis=1)) 167 | return np.exp(-self.r * self.t) * np.maximum(self.K - St, 0) 168 | 169 | >>> opt = fo.monte_carlo_options.MonteCarloOption( 170 | mc_loops, path_length, mc_paths, 171 | S, K, t, r, b, sigma, 172 | NormalSobolInnovations, WienerPath, PlainVanillaPayoff, 173 | trace=False, antithetic=True, standardization=False 174 | ) 175 | >>> print(opt.call().mean()) 176 | >>> print(opt.put().mean()) 177 | >>> opt.greeks(call=True) # not working yet 178 | 179 | References 180 | ---------- 181 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 182 | 183 | """ 184 | 185 | __name__ = "MCOption" 186 | __title__ = "Monte Carlo Simulation Option" 187 | 188 | def __init__( 189 | self, 190 | mc_loops: int, 191 | path_length: int, 192 | mc_paths: int, 193 | S: float, 194 | K: float, 195 | t: float, 196 | r: float, 197 | b: float, 198 | sigma: float = None, 199 | Innovation: Innovations = None, 200 | Path=None, 201 | Payoff=None, 202 | trace=False, 203 | antithetic=False, 204 | standardization=False, 205 | eps=None, 206 | **kwargs, 207 | ): 208 | 209 | self._mc_loops = mc_loops 210 | self._path_length = path_length 211 | self._mc_paths = mc_paths 212 | self._dt = t / path_length 213 | self._S = S 214 | self._K = K 215 | self._t = t 216 | self._r = r 217 | self._b = b 218 | self._sigma = sigma 219 | self._Innovation = Innovation(mc_paths, path_length, eps) 220 | self._Path = Path 221 | self._Payoff = Payoff 222 | self._trace = trace 223 | self._antithetic = antithetic 224 | self._standardization = standardization 225 | self._eps = eps 226 | self._kwargs = kwargs 227 | 228 | def call(self): 229 | """ 230 | Returns an array of the average option call price per Monte Carlo Loop (mc_loop). Final option value 231 | is the average of the returned array. 232 | 233 | Returns 234 | ------- 235 | numpy array 236 | 237 | Example 238 | ------- 239 | >>> import finoptions as fo 240 | >>> inno = fo.monte_carlo_options.NormalSobolInnovations 241 | >>> path = fo.monte_carlo_options.WienerPath 242 | >>> payoff = fo.monte_carlo_options.PlainVanillaPayoff 243 | >>> opt = fo.monte_carlo_options.MonteCarloOption(50, 30, 5000, 244 | 100, 100, 1/12, 0.1, 0.1, 0.4, inno, path, payoff) 245 | >>> opt.call() 246 | 247 | References 248 | ---------- 249 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 250 | """ 251 | iteration = self._sim_mc(call=True) 252 | if self._trace: 253 | self._print_trace(iteration) 254 | 255 | return iteration 256 | 257 | def put(self): 258 | """ 259 | Returns an array of the average option out price per Monte Carlo Loop (mc_loop). Final option value 260 | is the average of the returned array. 261 | 262 | Returns 263 | ------- 264 | numpy array 265 | 266 | Example 267 | ------- 268 | >>> import finoptions as fo 269 | >>> inno = fo.monte_carlo_options.NormalSobolInnovations 270 | >>> path = fo.monte_carlo_options.WienerPath 271 | >>> payoff = fo.monte_carlo_options.PlainVanillaPayoff 272 | >>> opt = fo.monte_carlo_options.MonteCarloOption(50, 30, 5000, 273 | 100, 100, 1/12, 0.1, 0.1, 0.4, inno, path, payoff) 274 | >>> opt.put() 275 | 276 | References 277 | ---------- 278 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 279 | """ 280 | iteration = self._sim_mc(call=False) 281 | if self._trace: 282 | self._print_trace(iteration) 283 | 284 | return iteration 285 | 286 | def _print_trace(self, iteration): 287 | print("\nMonte Carlo Simulation Path:\n\n") 288 | print("\nLoop:\t", "No\t") 289 | 290 | for i, _ in enumerate(iteration): 291 | print( 292 | "\nLoop:\t", 293 | i, 294 | "\t:", 295 | iteration[i], 296 | _np.sum(iteration) / (i + 1), 297 | end="", 298 | ) 299 | print("\n") 300 | 301 | def _sim_mc(self, call=True): 302 | 303 | iteration = _np.zeros(self._mc_loops) 304 | 305 | # MC Iteration Loop: 306 | 307 | for i in range(self._mc_loops): 308 | # # if ( i > 1) init = FALSE 309 | # Generate Innovations: 310 | eps = self._Innovation.sample_innovation() 311 | 312 | # Use Antithetic Variates if requested: 313 | if self._antithetic: 314 | eps = _np.concatenate((eps, -eps)) 315 | # # Standardize Variates if requested: 316 | if self._standardization: 317 | eps = (eps - _np.mean(eps)) / _np.std(eps) 318 | 319 | # eps = (eps-mean(eps))/sqrt(var(as.vector(eps))) 320 | 321 | # Calculate for each path the option price: 322 | path = self._Path(eps, self._sigma, self._dt, self._b) 323 | 324 | # so I think the original fOptions function has an error. It calcs 325 | # the payoff along the wrong dimensions such that it only calcs 326 | # along path_length number of samples vs mc_paths. I think the t() 327 | # is the problem. 328 | 329 | if call == True: 330 | payoff = self._Payoff( 331 | path, self._S, self._K, self._t, self._r, self._b, self._sigma 332 | ).call() 333 | else: 334 | payoff = self._Payoff( 335 | path, self._S, self._K, self._t, self._r, self._b, self._sigma 336 | ).put() 337 | 338 | tmp = _np.mean(payoff) 339 | 340 | if tmp == _np.inf: 341 | _warnings.warn(f"Warning: mc_loop {i} returned Inf.") 342 | return (eps, path, payoff) 343 | 344 | iteration[i] = tmp 345 | 346 | return iteration 347 | -------------------------------------------------------------------------------- /src/finoptions/monte_carlo_options/mc_innovations.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from scipy.stats import qmc as _qmc, norm as _norm 3 | import numpy as _np 4 | 5 | 6 | class Innovations(ABC): 7 | def __init__(self, mc_paths, path_length, eps=None): 8 | self.mc_paths = mc_paths 9 | self.path_length = path_length 10 | self._eps = eps # for testing only 11 | 12 | @abstractmethod 13 | def sample_innovation(self, init=False): 14 | pass 15 | 16 | 17 | class NormalSobolInnovations(Innovations): 18 | """ 19 | Normal Sobol Innovation Class for use with Monte Carlo Options Class 20 | 21 | Parameters 22 | ---------- 23 | mc_paths : int 24 | Number of monte carlo samples per loop - total number of samples is mc_paths * mc_loops 25 | path_length : int 26 | Path length should be a power of 2 (i.e. 2**m) to generate stable paths. See 27 | statsmodels.stats.qmc.Sobol for more details. 28 | 29 | Returns 30 | ------- 31 | Innovation Class 32 | """ 33 | 34 | def sample_innovation(self, scramble=True): 35 | sobol = self._get_sobol(scramble) 36 | 37 | # avoid inf 38 | while _np.abs(_np.max(sobol)) == _np.inf: 39 | sobol = self._get_sobol(scramble) 40 | 41 | if self._eps is None: 42 | return sobol 43 | else: 44 | # for testing only since fOptions sobol innovations 45 | # return different data from statsmodels 46 | return self._eps 47 | 48 | def _get_sobol(self, scramble): 49 | sobol = _qmc.Sobol(self.path_length, scramble=scramble).random(self.mc_paths) 50 | if scramble == False: 51 | # add new sample since if not scrambled first row is zero which leads to -inf when normalized 52 | sobol = sobol[1:] 53 | sobol = _np.append( 54 | sobol, 55 | _qmc.Sobol(self.path_length, scramble=scramble) 56 | .fast_forward(self.mc_paths) 57 | .random(1), 58 | axis=0, 59 | ) 60 | sobol = _norm.ppf(sobol) 61 | 62 | return sobol 63 | -------------------------------------------------------------------------------- /src/finoptions/monte_carlo_options/mc_paths.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import numpy as _np 3 | 4 | 5 | class Path(ABC): 6 | def __init__(self, epsilon, sigma, dt, b): 7 | self.epsilon = epsilon 8 | self.sigma = sigma 9 | self.dt = dt 10 | self.b = b 11 | 12 | @abstractmethod 13 | def generate_path(self): 14 | pass 15 | 16 | 17 | class WienerPath(Path): 18 | """ 19 | Generate Wiener Paths for use with Monte Carlo Options 20 | 21 | Parameters 22 | ---------- 23 | epsilon : numpy array 24 | epilson for paths as numpy array. Output from Innovation class sample_innovation function 25 | sigma : float 26 | annualized volatility sigma used to generate paths 27 | dt : float 28 | time step dt to generate paths 29 | b : float 30 | Annualized cost-of-carry rate, e.g. 0.1 means 10% 31 | """ 32 | 33 | def generate_path(self, **kwargs): 34 | # fmt: off 35 | return (self.b - (self.sigma ** 2) / 2) * self.dt + self.sigma * _np.sqrt(self.dt) * self.epsilon 36 | # fmt: on 37 | -------------------------------------------------------------------------------- /src/finoptions/monte_carlo_options/mc_payoffs.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import numpy as _np 3 | 4 | 5 | class Payoff(ABC): 6 | def __init__( 7 | self, path, S: float, K: float, t: float, r: float, b: float, sigma: float 8 | ): 9 | self.path = path 10 | self.S = S 11 | self.K = K 12 | self.t = t 13 | self.r = r 14 | self.b = b 15 | self.sigma = sigma 16 | 17 | @abstractmethod 18 | def call(self): 19 | pass 20 | 21 | @abstractmethod 22 | def put(self): 23 | pass 24 | 25 | 26 | class PlainVanillaPayoff(Payoff): 27 | def call(self): 28 | St = self.S * _np.exp(_np.sum(self.path.generate_path(), axis=1)) 29 | return _np.exp(-self.r * self.t) * _np.maximum(St - self.K, 0) 30 | 31 | def put(self): 32 | St = self.S * _np.exp(_np.sum(self.path.generate_path(), axis=1)) 33 | return _np.exp(-self.r * self.t) * _np.maximum(self.K - St, 0) 34 | 35 | 36 | class ArithmeticAsianPayoff(Payoff): 37 | def call(self): 38 | Sm = self.S * _np.exp(_np.cumsum(self.path.generate_path(), axis=1)) 39 | Sm = _np.mean(Sm, axis=1) 40 | return _np.exp(-self.r * self.t) * _np.maximum(Sm - self.K, 0) 41 | 42 | def put(self): 43 | Sm = self.S * _np.exp(_np.cumsum(self.path.generate_path(), axis=1)) 44 | Sm = _np.mean(Sm, axis=1) 45 | return _np.exp(-self.r * self.t) * _np.maximum(self.K - Sm, 0) 46 | -------------------------------------------------------------------------------- /src/finoptions/spread_options/__init__.py: -------------------------------------------------------------------------------- 1 | from .bitree3d import * 2 | from .spreadapprox import * 3 | 4 | # from .tree_spread import * 5 | -------------------------------------------------------------------------------- /src/finoptions/spread_options/spreadapprox.py: -------------------------------------------------------------------------------- 1 | import numpy as _np 2 | 3 | from finoptions.spread_options.bitree3d import BionomialSpreadAllTypes 4 | from ..base import GreeksFDM, Option as _Option 5 | from ..utils import docstring_from 6 | 7 | class SpreadApproxOption(_Option): 8 | """ 9 | Rubinstein (1994) published a method to construct a 3-dimensional binomial 10 | model that can be used to price most types of options that depend on two 11 | assets - both American and European. 12 | 13 | Notes 14 | ----- 15 | This model includes a cost of carry term b, the model can 16 | used to price European and American Options on: 17 | 18 | b = r stocks 19 | b = r - q stocks and stock indexes paying a continuous dividend yield q 20 | b = 0 futures 21 | b = r - rf currency options with foreign interst rate rf 22 | 23 | Parameters 24 | ---------- 25 | S1 : float 26 | Level or index price of asset 1. 27 | S2 : float 28 | Level or index price of asset 2. 29 | t : float 30 | Time-to-maturity in fractional years. i.e. 1/12 for 1 month, 1/252 for 1 business day, 1.0 for 1 year. 31 | r : float 32 | Risk-free-rate in decimal format (i.e. 0.01 for 1%). 33 | b1 : float 34 | Annualized cost-of-carry rate for asset 1, e.g. 0.1 means 10% 35 | b2 : float 36 | Annualized cost-of-carry rate for asset 2, e.g. 0.1 means 10% 37 | sigma1 : float 38 | Annualized volatility of the underlying asset 1. Optional if calculating implied volatility. 39 | Required otherwise. By default None. 40 | sigma2 : float 41 | Annualized volatility of the underlying asset 2. Optional if calculating implied volatility. 42 | Required otherwise. By default None. 43 | rho : float 44 | Correlation between asset 1 and asset 2. 45 | K : float 46 | Strike price. By default None. 47 | K2 : float 48 | Strike price. By default None. 49 | Q1 : float 50 | Weighting factor for asset 1 for use in payoff formula. By default 1. 51 | Q2 : float 52 | Weighting factor for asset 2 for use in payoff formula. By default 1. 53 | otype : str 54 | "european" to price European options, "american" to price American options. By default "european" 55 | n : int 56 | Number of time steps to use. By default 5. 57 | 58 | Returns 59 | ------- 60 | BinomialSpreadOption object. 61 | 62 | Example 63 | ------- 64 | >>> import finoptions as fo 65 | >>> opt = fo.spread_options.SpreadApproxOption(S1=122, S2=120, K=3, r=0.1, b1=0, b2=0, sigma1=0.2, sigma2=0.25, rho=0.5) 66 | >>> opt.call() 67 | >>> opt.put() 68 | >>> opt.greeks(call=True) 69 | 70 | References 71 | ---------- 72 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 73 | """ 74 | 75 | __name__ = "SpreadApproxOption" 76 | __title__ = "Spread-Option Approximation Model" 77 | 78 | def __init__( 79 | self, 80 | S1: float, 81 | S2: float, 82 | K: float, 83 | t: float, 84 | r: float, 85 | b1: float, 86 | b2: float, 87 | sigma1: float, 88 | sigma2: float, 89 | rho: float, 90 | Q1: float = 1, 91 | Q2: float = 1, 92 | ): 93 | self._S1 = S1 94 | self._S2 = S2 95 | self._Q1 = Q1 96 | self._Q2 = Q2 97 | self._K = K 98 | self._t = t 99 | self._r = r 100 | self._b1 = b1 101 | self._b2 = b2 102 | self._sigma1 = sigma1 103 | self._sigma2 = sigma2 104 | self._rho = rho 105 | 106 | self._e1 = _np.exp((b1 - r) * t) 107 | self._e2 = _np.exp((b2 - r) * t) 108 | 109 | self._S = (Q1 * S1 * self._e1) / (Q2 * S2 * self._e2 + K * _np.exp(-r * t)) 110 | 111 | F = (Q2 * S2 * self._e2) / (Q2 * S2 * self._e2 + K * _np.exp(-r * t)) 112 | 113 | self._sigma = _np.sqrt( 114 | sigma1 ** 2 + (sigma2 * F) ** 2 - 2 * rho * sigma1 * sigma2 * F 115 | ) 116 | 117 | self._d1 = (_np.log(self._S) + 0.5 * self._sigma ** 2 * t) / ( 118 | self._sigma * _np.sqrt(t) 119 | ) 120 | 121 | self._d2 = self._d1 - self._sigma * _np.sqrt(t) 122 | 123 | self._greeks = GreeksFDM(self) 124 | 125 | def call(self): 126 | """ 127 | Returns the calculated price of a call option using the Spread 128 | Option Approximation model. 129 | 130 | Returns 131 | ------- 132 | float 133 | 134 | Example 135 | ------- 136 | >>> import finoptions as fo 137 | >>> opt = fo.spread_options.SpreadApproxOption(S1=122, S2=120, K=3, r=0.1, b1=0, b2=0, sigma1=0.2, sigma2=0.25, rho=0.5) 138 | >>> opt.call() 139 | 140 | References 141 | ---------- 142 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 143 | """ 144 | # fmt: off 145 | result = (self._Q2*self._S2*self._e2 + self._K*_np.exp(-self._r*self._t)) \ 146 | * (self._S*self._CND(self._d1) - self._CND(self._d2)) 147 | 148 | return result 149 | 150 | def put(self): 151 | """ 152 | Returns the calculated price of a put option using the Spread 153 | Option Approximation model. 154 | 155 | Returns 156 | ------- 157 | float 158 | 159 | Example 160 | ------- 161 | >>> import finoptions as fo 162 | >>> opt = fo.spread_options.SpreadApproxOption(S1=122, S2=120, K=3, r=0.1, b1=0, b2=0, sigma1=0.2, sigma2=0.25, rho=0.5) 163 | >>> opt.put() 164 | 165 | References 166 | ---------- 167 | [1] Haug E.G., The Complete Guide to Option Pricing Formulas 168 | """ 169 | # fmt: off 170 | result = (self._Q2*self._S2*self._e2 + self._K*_np.exp(-self._r*self._t)) \ 171 | * (-self._S*self._CND(-self._d1) + self._CND(-self._d2)) 172 | 173 | return result 174 | 175 | @docstring_from(GreeksFDM.delta) 176 | def delta(self, call: bool = True): 177 | 178 | fd1 = self._greeks._make_partial_der("S1", call, self, n=1) 179 | fd2 = self._greeks._make_partial_der("S2", call, self, n=1) 180 | 181 | # multiple by 1 to return float vs array for single element arrays. Multi-element arrays returned as normal 182 | out = dict( 183 | S1 = fd1(self._S1) * 1, 184 | S2 = fd2(self._S2) * 1, 185 | ) 186 | 187 | return out 188 | 189 | @docstring_from(GreeksFDM.theta) 190 | def theta(self, call: bool = True): 191 | return self._greeks.theta(call=call) 192 | 193 | @docstring_from(GreeksFDM.vega) 194 | def vega(self): 195 | # same for both call and put options 196 | fd1 = self._greeks._make_partial_der("sigma1", True, self, n=1) 197 | fd2 = self._greeks._make_partial_der("sigma2", True, self, n=1) 198 | 199 | out = dict( 200 | sigma1 = fd1(self._sigma1) * 1, 201 | sigma2 = fd2(self._sigma2) * 1 202 | ) 203 | 204 | # multiple by 1 to return float vs array for single element arrays. Multi-element arrays returned as normal 205 | return out 206 | 207 | @docstring_from(GreeksFDM.rho) 208 | def rho(self, call: bool = True): 209 | return self._greeks.rho(call=call) 210 | 211 | @docstring_from(GreeksFDM.lamb) 212 | def lamb(self, call: bool = True): 213 | if call == True: 214 | price = self.call() 215 | else: 216 | price = self.put() 217 | 218 | out = dict( 219 | S1 = self.delta(call=call)['S1'] * self._S1 / price, 220 | S2 = self.delta(call=call)['S2'] * self._S2 / price 221 | ) 222 | return out 223 | 224 | @docstring_from(GreeksFDM.gamma) 225 | def gamma(self): 226 | # same for both call and put options 227 | fd1 = self._greeks._make_partial_der("S1", True, self, n=2) 228 | fd2 = self._greeks._make_partial_der("S2", True, self, n=2) 229 | 230 | out = dict( 231 | S1 = fd1(self._S1) * 1, 232 | S2 = fd2(self._S2) * 1 233 | ) 234 | # multiple by 1 to return float vs array for single element arrays. Multi-element arrays returned as normal 235 | return out 236 | 237 | @docstring_from(GreeksFDM.greeks) 238 | def greeks(self, call: bool = True): 239 | gk = { 240 | "delta": self.delta(call), 241 | "theta": self.theta(call), 242 | "vega": self.vega(), 243 | "rho": self.rho(call), 244 | "lambda": self.lamb(call), 245 | "gamma": self.gamma(), 246 | } 247 | 248 | return gk 249 | 250 | def get_params(self): 251 | return { 252 | "S1": self._S1, 253 | "S2": self._S2, 254 | "K": self._K, 255 | "t": self._t, 256 | "r": self._r, 257 | "b1": self._b1, 258 | "b2": self._b2, 259 | "sigma1": self._sigma1, 260 | "sigma2": self._sigma2, 261 | "rho": self._rho, 262 | "Q1": self._Q1, 263 | "Q2": self._Q2, 264 | } 265 | 266 | -------------------------------------------------------------------------------- /src/finoptions/spread_options/tree_spread.py: -------------------------------------------------------------------------------- 1 | import numpy as _np 2 | 3 | 4 | class TrinomialSpreadOption: 5 | __name__ = "TrinomialSpreadOption" 6 | __title__ = "Trinomial Tree Spread Option Model" 7 | 8 | def __init__( 9 | self, 10 | S1: float, 11 | S2: float, 12 | K: float, 13 | t: float, 14 | r: float, 15 | b: float, 16 | sigma1: float, 17 | sigma2: float, 18 | rho: float, 19 | type: str = "european", 20 | n: int = 5, 21 | ): 22 | self._S1 = S1 23 | self._S2 = S2 24 | self._K = K 25 | self._t = t 26 | self._r = r 27 | self._b = b 28 | self._sigma1 = sigma1 29 | self._sigma2 = sigma2 30 | self._rho = rho 31 | self._n = n 32 | self._type = type 33 | 34 | def call(self): 35 | return self._calc_price(z=1, type=self._type, tree=False) 36 | 37 | def _calc_price(self, z, type, tree): 38 | n = self._n 39 | S1 = self._S1 40 | sd1 = self._sigma1 41 | S2 = self._S2 42 | sd2 = self._sigma2 43 | rho = self._rho 44 | dt = self._t / n 45 | r = self._r 46 | b = self._b 47 | 48 | pm = _np.array([ 49 | [1,4,1], 50 | [4,16,4], 51 | [1,4,1] 52 | ])/36 53 | 54 | sdp = sd1 * sd1 + sd2 * sd2 55 | # fmt: off 56 | lam1 = 0.5 * (sdp + _np.sqrt(sdp * sdp - 4 * (1 - rho * rho) * sd1 * sd1 * sd2 * sd2)) 57 | lam2 = 0.5 * (sdp - _np.sqrt(sdp * sdp - 4 * (1 - rho * rho) * sd1 * sd1 * sd2 * sd2)) 58 | the = _np.arctan((lam1-sd1*sd1)/(rho*sd1*sd2)) 59 | Df = _np.exp(b-r*dt) 60 | h = _np.exp(-1*_np.sin(the)*_np.sqrt(lam2)* _np.sqrt(3 * dt) - 0.5*sd1*sd1*dt) 61 | v = _np.exp(_np.cos(the)*_np.sqrt(lam1)* _np.sqrt(3 * dt) - 0.5*sd1*sd1*dt) 62 | 63 | S1_T = S1*self._get_gauss(h,v,n) 64 | 65 | h = _np.exp(_np.cos(the)*_np.sqrt(lam2)* _np.sqrt(3 * dt) - 0.5*sd2*sd2*dt) 66 | v = _np.exp(_np.sin(the)*_np.sqrt(lam1)* _np.sqrt(3 * dt) - 0.5*sd2*sd2*dt) 67 | 68 | S2_T = S2*self._get_gauss(h,v,n) 69 | 70 | OptionValue = _np.maximum(0,((S1_T-S2_T)-self._K)*z) 71 | 72 | OptionValue = self._euro(OptionValue, n, Df, pm) 73 | 74 | OptionValue = _np.sum(_np.concatenate(_np.multiply(OptionValue[-1][n-1:n+2, n-1:n+2],pm)))*Df 75 | 76 | return OptionValue 77 | 78 | def _euro(self, OptionValue, n, Df, pm): 79 | tr = OptionValue.copy() 80 | old = tr.copy() 81 | 82 | tr = list() 83 | 84 | # step back in time from t=T to t=0 85 | for j in _np.arange(1,n): 86 | 87 | # create new smaller matrix for expected value cale from passed, larger matrix 88 | latest = _np.zeros((2*(n-j)+1,2*(n-j)+1)) 89 | latest = self._calc_nodes(old, latest, Df, pm) 90 | 91 | old = latest.copy() 92 | # create new matrix of same size as original matrix with new matrix 93 | # centered inside and stack 94 | latest = _np.pad(latest, (j, j)) 95 | 96 | tr.append(latest) 97 | 98 | return tr 99 | 100 | def _calc_nodes(self, tr, ntr, Df, pm): 101 | # loop through elements 1 element away from the edge and calc 102 | # weighted sum 103 | for i in range(1,tr.shape[0]-1): 104 | for j in range(1,tr.shape[0]-1): 105 | ntr[i-1,j-1] = _np.sum(_np.concatenate(_np.multiply(tr[i-1:i+2, j-1:j+2],pm)))*Df 106 | return ntr 107 | 108 | 109 | 110 | 111 | 112 | def _get_gauss(self, h, v, n): 113 | """ 114 | Calculate the joint innovation for one of the assets 115 | """ 116 | # fmt: off 117 | OptionValue1 = _np.power(h, _np.arange(0, 2 * n + 1)) \ 118 | * (1 / h) ** _np.arange(2 * n, -1, -1) 119 | OptionValue1 = _np.broadcast_to(OptionValue1, (2 * n + 1, 2 * n + 1)) 120 | 121 | OptionValue2 = _np.power(v, _np.arange(0, 2 * n + 1)) \ 122 | * (1 / v) ** _np.arange(2 * n, -1, -1) 123 | OptionValue2 = _np.broadcast_to(_np.flip(OptionValue2), (2 * n + 1, 2 * n + 1)).T 124 | # fmt: on 125 | 126 | OptionValue = OptionValue2 * OptionValue1 127 | 128 | return OptionValue 129 | 130 | 131 | if __name__ == "__main__": 132 | 133 | opt = TrinomialSpreadOption(70, 60, 0, 1 / 12, 0.03, 0.03, 0.2, 0.1, 0.5, n=100) 134 | ret = opt._calc_price(1, type="euro", tree=False) 135 | 136 | print(ret) 137 | -------------------------------------------------------------------------------- /src/finoptions/utils.py: -------------------------------------------------------------------------------- 1 | def docstring_from(source): 2 | """ 3 | Decorator to be used to copy the docstring from the source class/class method 4 | to the target. Useful for class composition. 5 | """ 6 | 7 | def wrapper(target): 8 | target.__doc__ = source.__doc__ 9 | return target 10 | 11 | return wrapper 12 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import pandas as _pd 2 | import numpy as _np 3 | 4 | 5 | def check_iter(it): 6 | # try: 7 | # _ = (e for e in it) 8 | # except TypeError: 9 | # print(it, "is not iterable") 10 | 11 | return hasattr(it, "__iter__") 12 | 13 | --------------------------------------------------------------------------------