├── .gitignore ├── LICENSE ├── README.md ├── images ├── call_delta_price.png ├── christmas_tree.png ├── gamma_s.gif ├── long_call_price_interactive.png ├── long_call_theta_price.png ├── long_put_price_volatility.png ├── long_vanna_interactive.png ├── long_vega_static.png ├── price_vega_l.gif ├── short_call.png ├── short_gamma_static.png ├── short_rho_strike.png ├── short_zomma_interactive.png └── straddle.png ├── optionvisualizer ├── __init__.py ├── animated_gifs.py ├── barriers.py ├── greeks.py ├── greeks_2d.py ├── greeks_3d.py ├── multi_payoffs.py ├── option_formulas.py ├── sensitivities.py ├── simple_payoffs.py ├── utilities.py ├── visualizer.py └── visualizer_params.py ├── pyproject.toml ├── setup.cfg ├── setup.py └── tests ├── vis_unittest_analytical_sens.py ├── vis_unittest_graphical.py └── vis_unittest_numerical_sens.py /.gitignore: -------------------------------------------------------------------------------- 1 | Workings 2 | pypi 3 | pypi_toml 4 | .vscode 5 | **/__pycache__ 6 | **/*.pyc 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | pip-wheel-metadata/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | db.sqlite3-journal 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 GBERESEARCH 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # optionvisualizer 2 | ## Visualize option prices and sensitivities 3 | 4 |   5 | 6 | Each of the functions comes with default parameters and can be run without arguments for ease of illustration or they can be specified individually. Sensitivities can be calculated both analytically and numerically. 7 | 8 |   9 | 10 | ### Installation 11 | Install from PyPI: 12 | ``` 13 | $ pip install optionvisualizer 14 | ``` 15 | 16 |   17 | 18 | Install in a new environment using Python venv: 19 | 20 | Create base environment of Python 3.13 21 | ``` 22 | $ py -3.13 -m venv .venv 23 | ``` 24 | Activate new environment 25 | ``` 26 | $ .venv\scripts\activate 27 | ``` 28 | Ensure pip is up to date 29 | ``` 30 | $ (.venv) python -m pip install --upgrade pip 31 | ``` 32 | Install Spyder 33 | ``` 34 | $ (.venv) python -m pip install spyder 35 | ``` 36 | Install package 37 | ``` 38 | $ (.venv) python -m pip install optionvisualizer 39 | ``` 40 | 41 |   42 | 43 | ### Setup 44 | Import visualizer and initialise an Option object 45 | 46 | ``` 47 | from optionvisualizer.visualizer import Visualizer 48 | opt = Visualizer() 49 | ``` 50 | 51 |   52 | 53 | ### Black-Scholes option pricing and sensitivities: 54 | - Price: option price 55 | - Delta: sensitivity of option price to changes in asset price 56 | - Gamma: sensitivity of delta to changes in asset price 57 | - Vega: sensitivity of option price to changes in volatility 58 | - Theta: sensitivity of option price to changes in time to maturity 59 | - Rho: sensitivity of option price to changes in the risk free rate 60 | - Vomma: sensitivity of vega to changes in volatility; Volga 61 | - Vanna: sensitivity of delta to changes in volatility / of vega to changes in asset price 62 | - Charm: sensitivity of delta to changes in time to maturity aka Delta Bleed 63 | - Zomma: sensitivity of gamma to changes in volatility 64 | - Speed: sensitivity of gamma to changes in asset price; 3rd derivative of option price wrt spot 65 | - Color: sensitivity of gamma to changes in time to maturity; GammaTheta 66 | - Ultima: sensitivity of vomma to changes in volatility; 3rd derivative of option price wrt volatility 67 | - Vega Bleed: sensitivity of vega to changes in time to maturity 68 | 69 | ``` 70 | opt.option_data(option_value='price', S=3477, K=3400, T=0.5, r=0.005, q=0, sigma=0.3, option='put') 71 | ``` 72 | 73 | ``` 74 | opt.sensitivities(greek='delta', S=3477, K=3400, T=0.5, r=0.005, q=0, sigma=0.3, option='put') 75 | ``` 76 | 77 |   78 | 79 | ### 2D greeks graphs: 80 | #### Charts of 3 options showing price, vol or time against: 81 | - option value 82 | - delta 83 | - gamma 84 | - vega 85 | - theta 86 | 87 |   88 | 89 | #### Long Call Delta vs Price 90 | ``` 91 | opt.visualize(risk=True, x_plot='price', y_plot='delta', G1=100, G2=100, G3=100, T1=0.05, T2=0.15, T3=0.25) 92 | ``` 93 | ![call_delta_price](images/call_delta_price.png) 94 | 95 |   96 | 97 | #### Long Call Theta vs Price 98 | ``` 99 | opt.visualize(risk=True, x_plot='price', y_plot='theta', G1=100, G2=100, G3=100, T1=0.05, T2=0.15, T3=0.25) 100 | ``` 101 | ![long_call_theta_price](images/long_call_theta_price.png) 102 | 103 |   104 | 105 | #### Charts of 4 options showing price, strike and vol against rho 106 | #### Short Rho vs Strike 107 | ``` 108 | opt.visualize(risk=True, x_plot='strike', y_plot='rho', direction='short') 109 | ``` 110 | ![short_rho_strike](images/short_rho_strike.png) 111 | 112 |   113 | 114 | #### You can also convert into an animated gif 115 | ``` 116 | opt.animated_gif(graphtype='2D', x_plot='price', y_plot='vega', direction='long', T=1, gif_folder='images/greeks_2d', gif_filename='price_vega_l') 117 | ``` 118 | ![price_vega_l](images/price_vega_l.gif) 119 | 120 |   121 | 122 | ### 3D greeks graphs: 123 | #### Each of the greeks above can be plotted showing Time to Expiration against Strike or Volatility 124 | 125 |   126 | 127 | #### Using matplotlib: 128 | 129 | #### Long Vega 130 | ``` 131 | opt.visualize(risk=True, graphtype='3D', greek='vega', S=50) 132 | ``` 133 | ![long_vega_static](images/long_vega_static.png) 134 | 135 |   136 | 137 | #### Short Gamma 138 | ``` 139 | opt.visualize(risk=True, graphtype='3D', greek='gamma', direction='short') 140 | ``` 141 | 142 | ![short_gamma_static](images/short_gamma_static.png) 143 | 144 |   145 | 146 | #### Or using plotly display a graph that can be rotated and zoomed: 147 | #### Long Call Price 148 | ``` 149 | opt.visualize(risk=True, graphtype='3D', greek='price', colorscheme='Plasma', interactive=True) 150 | ``` 151 | ![long_call_price_interactive](images/long_call_price_interactive.png) 152 | 153 |   154 | 155 | #### Long Put Price against Volatility 156 | ``` 157 | opt.visualize(risk=True, graphtype='3D', greek='price', axis='vol', option='put', interactive=True) 158 | ``` 159 | 160 | ![long_put_price_volatility](images/long_put_price_volatility.png) 161 | 162 |   163 | 164 | #### Long Vanna 165 | ``` 166 | opt.visualize(risk=True, graphtype='3D', greek='vanna', sigma=0.4, interactive=True) 167 | ``` 168 | ![long_vanna_interactive](images/long_vanna_interactive.png) 169 | 170 |   171 | 172 | #### Short Zomma 173 | ``` 174 | opt.visualize(risk=True, graphtype='3D', greek='zomma', direction='short', interactive=True) 175 | ``` 176 | ![short_zomma_interactive](images/short_zomma_interactive.png) 177 | 178 |   179 | 180 | #### You can also convert into an animated gif 181 | ``` 182 | opt.animated_gif(graphtype='3D', greek='gamma', direction='short', gif_folder='images/greeks_3d',gif_filename='gamma_s', gif_min_dist=9.0, gif_max_dist=9.1, gif_min_elev=25, gif_max_elev=26, spacegrain=1000, colorscheme='seismic') 183 | ``` 184 | ![gamma_s](images/gamma_s.gif) 185 | 186 |   187 | 188 | ### Option strategy Payoff graphs: 189 | - call / put 190 | - stock 191 | - forward 192 | - collar 193 | - call / put spread 194 | - backspread 195 | - ratio vertical spread 196 | - straddle 197 | - strangle 198 | - butterfly 199 | - christmas tree 200 | - iron butterfly 201 | - iron condor 202 | 203 |   204 | 205 | #### Short Call: 206 | ``` 207 | opt.visualize(risk=False, payoff_type='call', S=90, K=95, T=0.75, r=0.05, q=0, sigma=0.3, direction='short', value=True) 208 | ``` 209 | ![short_call](images/short_call.png) 210 | 211 |   212 | 213 | #### Long Straddle: 214 | ``` 215 | opt.visualize(risk=False, payoff_type='straddle', mpl_style='ggplot') 216 | ``` 217 | ![straddle](images/straddle.png) 218 | 219 |   220 | 221 | #### Short Christmas Tree: 222 | ``` 223 | opt.visualize(risk=False, payoff_type='christmas tree', value=True, direction='short') 224 | ``` 225 | ![christmas_tree](images/christmas_tree.png) 226 | 227 |   228 | 229 | The following volumes served as a reference for the formulas and charts: 230 | * [The Complete Guide to Option Pricing Formulas, 2nd Ed, E. G. Haug] 231 | * [Option Volatility & Pricing, S. Natenburg] 232 | 233 | [The Complete Guide to Option Pricing Formulas, 2nd Ed, E. G. Haug]: 234 | [Option Volatility & Pricing, S. Natenburg]: 235 | -------------------------------------------------------------------------------- /images/call_delta_price.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/call_delta_price.png -------------------------------------------------------------------------------- /images/christmas_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/christmas_tree.png -------------------------------------------------------------------------------- /images/gamma_s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/gamma_s.gif -------------------------------------------------------------------------------- /images/long_call_price_interactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/long_call_price_interactive.png -------------------------------------------------------------------------------- /images/long_call_theta_price.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/long_call_theta_price.png -------------------------------------------------------------------------------- /images/long_put_price_volatility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/long_put_price_volatility.png -------------------------------------------------------------------------------- /images/long_vanna_interactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/long_vanna_interactive.png -------------------------------------------------------------------------------- /images/long_vega_static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/long_vega_static.png -------------------------------------------------------------------------------- /images/price_vega_l.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/price_vega_l.gif -------------------------------------------------------------------------------- /images/short_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/short_call.png -------------------------------------------------------------------------------- /images/short_gamma_static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/short_gamma_static.png -------------------------------------------------------------------------------- /images/short_rho_strike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/short_rho_strike.png -------------------------------------------------------------------------------- /images/short_zomma_interactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/short_zomma_interactive.png -------------------------------------------------------------------------------- /images/straddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GBERESEARCH/optionvisualizer/315b8a63246a9e41af79a188e67161e1f9cf9e71/images/straddle.png -------------------------------------------------------------------------------- /optionvisualizer/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.2.4" 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /optionvisualizer/animated_gifs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create Animated Gifs 3 | 4 | """ 5 | import glob 6 | import math 7 | import os 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | from PIL import Image 11 | from optionvisualizer.greeks import Greeks 12 | # pylint: disable=invalid-name, consider-using-f-string, unused-variable 13 | 14 | class Gif(): 15 | """ 16 | Create Animated Gifs 17 | 18 | """ 19 | @classmethod 20 | def animated_2D_gif(cls, params: dict) -> None: 21 | """ 22 | Create an animated gif of the selected pair of parameters. 23 | 24 | Parameters 25 | ---------- 26 | params : Dict 27 | gif_folder : Str 28 | The folder to save the files into. The default is 29 | 'images/greeks'. 30 | gif_filename : Str 31 | The filename for the animated gif. The default is 'greek'. 32 | T : Float 33 | Time to Maturity. The default is 0.5 (6 months). 34 | steps : Int 35 | Number of images to combine. The default is 40. 36 | x_plot : Str 37 | The x-axis variable ('price', 'strike' or 'vol'). 38 | The default is 'price'. 39 | y_plot : Str 40 | The y-axis variable ('value', 'delta', 'gamma', 'vega' 41 | or 'theta'). The default is 'delta. 42 | S : Float 43 | Underlying Stock Price. The default is 100. 44 | G1 : Float 45 | Strike Price of option 1. The default is 90. 46 | G2 : Float 47 | Strike Price of option 2. The default is 100. 48 | G3 : Float 49 | Strike Price of option 3. The default is 110. 50 | r : Float 51 | Interest Rate. The default is 0.05 (5%). 52 | q : Float 53 | Dividend Yield. The default is 0. 54 | sigma : Float 55 | Implied Volatility. The default is 0.2 (20%). 56 | option : Str 57 | Option type, Put or Call. The default is 'call' 58 | direction : Str 59 | Whether the payoff is long or short. The default is 'long'. 60 | size2d : Tuple 61 | Figure size for matplotlib chart. The default is (6, 4). 62 | mpl_style : Str 63 | Matplotlib style template for 2D risk charts and payoffs. 64 | The default is 'seaborn-v0_8-darkgrid'. 65 | num_sens : Bool 66 | Whether to calculate numerical or analytical sensitivity. 67 | The default is False. 68 | 69 | Returns 70 | ------- 71 | Saves an animated gif. 72 | 73 | """ 74 | params['gif']=True 75 | 76 | # Set up folders to save files 77 | params = cls._gif_defaults_setup(params=params) 78 | 79 | # split the countdown from T to maturity in steps equal steps 80 | time_steps = np.linspace(params['T'], 0.001, params['steps']) 81 | 82 | # create a plot for each time_step 83 | for counter, step in enumerate(time_steps): 84 | 85 | # create filenumber and filename 86 | filenumber = '{:03d}'.format(counter) 87 | filename = '{}, {}'.format( 88 | params['gif_filename'], filenumber) 89 | 90 | params['T'] = step 91 | params['T1'] = step 92 | params['T2'] = step 93 | params['T3'] = step 94 | 95 | # call the greeks_graphs_2d function to create graph 96 | fig, ax = Greeks.greeks_graphs_2D( 97 | params=params) 98 | 99 | # save the image as a file 100 | plt.savefig('{}/{}/img{}.png'.format( 101 | params['gif_folder'], params['gif_filename'], filename), 102 | dpi=50) 103 | 104 | # close the image object 105 | plt.close() 106 | 107 | cls._create_animation(params) 108 | 109 | 110 | @classmethod 111 | def animated_3D_gif(cls, params: dict) -> None: 112 | """ 113 | Create an animated gif of the selected greek 3D graph. 114 | 115 | Parameters 116 | ---------- 117 | params : Dict 118 | S : Float 119 | Underlying Stock Price. The default is 100. 120 | r : Float 121 | Interest Rate. The default is 0.05 (5%). 122 | q : Float 123 | Dividend Yield. The default is 0. 124 | sigma : Float 125 | Implied Volatility. The default is 0.2 (20%). 126 | option : Str 127 | Option type, Put or Call. The default is 'call' 128 | direction : Str 129 | Whether the payoff is long or short. The default is 'long'. 130 | notebook : Bool 131 | Whether the function is being run in an IPython notebook and 132 | hence whether it should output in line or to an HTML file. 133 | The default is False. 134 | colorscheme : Str 135 | The matplotlib colormap or plotly colorscale to use. The 136 | default is 'jet' (which works in both). 137 | colorintensity : Float 138 | The alpha value indicating level of transparency / 139 | intensity. The default is 1. 140 | size3d : Tuple 141 | Figure size for matplotlib chart. The default is (15, 12). 142 | axis : Str 143 | Whether the x-axis is 'price' or 'vol'. The default 144 | is 'price'. 145 | spacegrain : Int 146 | Number of points in each axis linspace argument for 3D 147 | graphs. The default is 100. 148 | azim : Float 149 | L-R view angle for 3D graphs. The default is -50. 150 | elev : Float 151 | Elevation view angle for 3D graphs. The default is 20. 152 | greek : Str 153 | The sensitivity to be charted. Select from 'delta', 'gamma', 154 | 'vega', 'theta', 'rho', 'vomma', 'vanna', 'zomma', 'speed', 155 | 'color', 'ultima', 'vega_bleed', 'charm'. The default is 156 | 'delta' 157 | num_sens : Bool 158 | Whether to calculate numerical or analytical sensitivity. 159 | The default is False. 160 | gif_folder : Str 161 | The folder to save the files into. The default is 162 | 'images/greeks'. 163 | gif_filename : Str 164 | The filename for the animated gif. The default is 'greek'. 165 | gif_frame_update : Int 166 | The number of degrees of rotation between each frame used to 167 | construct the animated gif. The default is 2. 168 | gif_min_dist : Float 169 | The minimum zoom distance. The default is 9.0. 170 | gif_max_dist : Float 171 | The maximum zoom distance. The default is 10.0. 172 | gif_min_elev : Float 173 | The minimum elevation. The default is 10.0. 174 | gif_max_elev : Float 175 | The maximum elevation. The default is 60.0. 176 | gif_start_azim : Float 177 | The azimuth to start the gif from. The default is 0. 178 | gif_end_azim : Float 179 | The azimuth to end the gif on. The default is 360. 180 | gif_dpi : Int 181 | The image resolution to save. The default is 50 dpi. 182 | gif_ms : Int 183 | The time to spend on each frame in the gif. The default is 184 | 100ms. 185 | 186 | Returns 187 | ------- 188 | Saves an animated gif. 189 | 190 | """ 191 | 192 | params = cls._gif_defaults_setup(params=params) 193 | 194 | fig, ax, params['titlename'], \ 195 | params['title_font_scale'] = Greeks.greeks_graphs_3D( 196 | params=params) 197 | 198 | # set the range for horizontal rotation 199 | params['azim_range'] = ( 200 | params['gif_end_azim'] - params['gif_start_azim']) 201 | 202 | # set number of frames for the animated gif as the range of horizontal 203 | # rotation divided by the number of degrees rotation between frames 204 | params['steps'] = math.floor( 205 | params['azim_range']/params['gif_frame_update']) 206 | 207 | # a viewing perspective is composed of an elevation, distance, and 208 | # azimuth define the range of values we'll cycle through for the 209 | # distance of the viewing perspective 210 | params['dist_range'] = np.arange( 211 | params['gif_min_dist'], 212 | params['gif_max_dist'], 213 | (params['gif_max_dist']-params['gif_min_dist'])/params['steps']) 214 | 215 | # define the range of values we'll cycle through for the elevation of 216 | # the viewing perspective 217 | params['elev_range'] = np.arange( 218 | params['gif_max_elev'], 219 | params['gif_min_elev'], 220 | (params['gif_min_elev']-params['gif_max_elev'])/params['steps']) 221 | 222 | # now create the individual frames that will be combined later into the 223 | # animation 224 | for idx, azimuth in enumerate( 225 | range(params['gif_start_azim'], 226 | params['gif_end_azim'], 227 | params['gif_frame_update'])): 228 | 229 | # pan down, rotate around, and zoom out 230 | ax.azim = float(azimuth) 231 | ax.elev = params['elev_range'][idx] 232 | ax.dist = params['dist_range'][idx] 233 | 234 | # set the figure title 235 | st = fig.suptitle(params['titlename'], 236 | fontsize=params['title_font_scale'], 237 | fontweight=0, 238 | color='black', 239 | style='italic', 240 | y=1.02) 241 | 242 | st.set_y(0.98) 243 | fig.subplots_adjust(top=1) 244 | 245 | # save the image as a png file 246 | plt.savefig('{}/{}/img{:03d}.png'.format( 247 | params['gif_folder'], 248 | params['gif_filename'], 249 | azimuth), 250 | dpi=params['gif_dpi']) 251 | 252 | # close the image object 253 | plt.close() 254 | 255 | cls._create_animation(params) 256 | 257 | 258 | @staticmethod 259 | def _gif_defaults_setup(params: dict) -> dict: 260 | 261 | if params['gif_folder'] is None: 262 | params['gif_folder'] = params['gif_folder_'+params['graphtype']] 263 | if params['gif_filename'] is None: 264 | params['gif_filename'] = params['gif_filename_'+params['graphtype']] 265 | 266 | params['working_folder'] = '{}/{}'.format( 267 | params['gif_folder'], params['gif_filename']) 268 | if not os.path.exists(params['working_folder']): 269 | os.makedirs(params['working_folder']) 270 | 271 | return params 272 | 273 | 274 | @staticmethod 275 | def _create_animation(params: dict) -> None: 276 | 277 | # load all the static images into a list then save as an animated gif 278 | gif_filepath = '{}/{}.gif'.format( 279 | params['gif_folder'], params['gif_filename']) 280 | images = ([Image.open(image) for image in sorted( 281 | glob.glob('{}/*.png'.format(params['working_folder'])))]) 282 | gif = images[0] 283 | gif.info['duration'] = params['gif_ms'] #milliseconds per frame 284 | gif.info['loop'] = 0 #how many times to loop (0=infinite) 285 | gif.save(fp=gif_filepath, format='gif', save_all=True, 286 | append_images=images[1:]) 287 | -------------------------------------------------------------------------------- /optionvisualizer/barriers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Calculate barrier option 3 | 4 | """ 5 | 6 | from optionvisualizer.utilities import Utils 7 | # pylint: disable=invalid-name 8 | 9 | class Barrier(): 10 | """ 11 | Calculate barrier option 12 | 13 | """ 14 | @classmethod 15 | def barrier_price(cls, params: dict) -> tuple[float, dict]: 16 | """ 17 | Return the Barrier option price 18 | 19 | Parameters 20 | ---------- 21 | S : Float 22 | Underlying Stock Price. The default is 100. 23 | K : Float 24 | Strike Price. The default is 100. 25 | H : Float 26 | Barrier Level. The default is 105. 27 | R : Float 28 | Rebate. The default is 0. 29 | T : Float 30 | Time to Maturity. The default is 0.25 (3 months). 31 | r : Float 32 | Interest Rate. The default is 0.05 (5%). 33 | q : Float 34 | Dividend Yield. The default is 0. 35 | sigma : Float 36 | Implied Volatility. The default is 0.2 (20%). 37 | barrier_direction : Str 38 | Up or Down. The default is 'up'. 39 | knock : Str 40 | knock-in or knock-out. The default is 'in'. 41 | option : Str 42 | Option type, Put or Call. The default is 'call' 43 | default : Bool 44 | Whether the function is being called directly (in which 45 | case values that are not supplied are set to default 46 | values) or used within a graph call where they have 47 | already been updated. 48 | 49 | Returns 50 | ------- 51 | Float 52 | Barrier option price. 53 | 54 | """ 55 | 56 | # Down and In Call 57 | if (params['barrier_direction'] == 'down' 58 | and params['knock'] == 'in' 59 | and params['option'] == 'call'): 60 | 61 | opt_barrier_payoff = cls._di_call(params) 62 | 63 | 64 | # Up and In Call 65 | if (params['barrier_direction'] == 'up' 66 | and params['knock'] == 'in' 67 | and params['option'] == 'call'): 68 | 69 | opt_barrier_payoff = cls._ui_call(params) 70 | 71 | 72 | # Down and In Put 73 | if (params['barrier_direction'] == 'down' 74 | and params['knock'] == 'in' 75 | and params['option'] == 'put'): 76 | 77 | opt_barrier_payoff = cls._di_put(params) 78 | 79 | 80 | # Up and In Put 81 | if (params['barrier_direction'] == 'up' 82 | and params['knock'] == 'in' 83 | and params['option'] == 'put'): 84 | 85 | opt_barrier_payoff = cls._ui_put(params) 86 | 87 | 88 | # Down and Out Call 89 | if (params['barrier_direction'] == 'down' 90 | and params['knock'] == 'out' 91 | and params['option'] == 'call'): 92 | 93 | opt_barrier_payoff = cls._do_call(params) 94 | 95 | 96 | # Up and Out Call 97 | if (params['barrier_direction'] == 'up' 98 | and params['knock'] == 'out' 99 | and params['option'] == 'call'): 100 | 101 | opt_barrier_payoff = cls._uo_call(params) 102 | 103 | 104 | # Down and Out Put 105 | if (params['barrier_direction'] == 'down' 106 | and params['knock'] == 'out' 107 | and params['option'] == 'put'): 108 | 109 | opt_barrier_payoff = cls._do_put(params) 110 | 111 | # Up and Out Put 112 | if (params['barrier_direction'] == 'up' 113 | and params['knock'] == 'out' 114 | and params['option'] == 'put'): 115 | 116 | opt_barrier_payoff = cls._uo_put(params) 117 | 118 | return opt_barrier_payoff, params 119 | 120 | 121 | @staticmethod 122 | def _di_call(params: dict) -> float: 123 | 124 | params['eta'] = 1 125 | params['phi'] = 1 126 | 127 | barrier_factors, params = Utils.barrier_factors(params=params) 128 | 129 | if params['K'] > params['H']: 130 | opt_barrier_payoff = ( 131 | barrier_factors['C'] + barrier_factors['E']) 132 | if params['K'] < params['H']: 133 | opt_barrier_payoff = ( 134 | barrier_factors['A'] - barrier_factors['B'] 135 | + barrier_factors['D'] + barrier_factors['E']) 136 | 137 | return opt_barrier_payoff 138 | 139 | 140 | @staticmethod 141 | def _ui_call(params: dict) -> float: 142 | 143 | params['eta'] = -1 144 | params['phi'] = 1 145 | 146 | barrier_factors, params = Utils.barrier_factors(params=params) 147 | 148 | if params['K'] > params['H']: 149 | opt_barrier_payoff = ( 150 | barrier_factors['A'] + barrier_factors['E']) 151 | if params['K'] < params['H']: 152 | opt_barrier_payoff = ( 153 | barrier_factors['B'] - barrier_factors['C'] 154 | + barrier_factors['D'] + barrier_factors['E']) 155 | 156 | return opt_barrier_payoff 157 | 158 | 159 | @staticmethod 160 | def _di_put(params: dict) -> float: 161 | 162 | params['eta'] = 1 163 | params['phi'] = -1 164 | 165 | barrier_factors, params = Utils.barrier_factors(params=params) 166 | 167 | if params['K'] > params['H']: 168 | opt_barrier_payoff = ( 169 | barrier_factors['B'] - barrier_factors['C'] 170 | + barrier_factors['D'] + barrier_factors['E']) 171 | if params['K'] < params['H']: 172 | opt_barrier_payoff = ( 173 | barrier_factors['A'] + barrier_factors['E']) 174 | 175 | return opt_barrier_payoff 176 | 177 | 178 | @staticmethod 179 | def _ui_put(params: dict) -> float: 180 | 181 | params['eta'] = -1 182 | params['phi'] = -1 183 | 184 | barrier_factors, params = Utils.barrier_factors(params=params) 185 | 186 | if params['K'] > params['H']: 187 | opt_barrier_payoff = ( 188 | barrier_factors['A'] - barrier_factors['B'] 189 | + barrier_factors['D'] + barrier_factors['E']) 190 | if params['K'] < params['H']: 191 | opt_barrier_payoff = ( 192 | barrier_factors['C'] + barrier_factors['E']) 193 | 194 | return opt_barrier_payoff 195 | 196 | 197 | @staticmethod 198 | def _do_call(params: dict) -> float: 199 | 200 | params['eta'] = 1 201 | params['phi'] = 1 202 | 203 | barrier_factors, params = Utils.barrier_factors(params=params) 204 | 205 | if params['K'] > params['H']: 206 | opt_barrier_payoff = ( 207 | barrier_factors['A'] - barrier_factors['C'] 208 | + barrier_factors['F']) 209 | if params['K'] < params['H']: 210 | opt_barrier_payoff = ( 211 | barrier_factors['B'] - barrier_factors['D'] 212 | + barrier_factors['F']) 213 | 214 | return opt_barrier_payoff 215 | 216 | 217 | @staticmethod 218 | def _uo_call(params: dict) -> float: 219 | 220 | params['eta'] = -1 221 | params['phi'] = 1 222 | 223 | barrier_factors, params = Utils.barrier_factors(params=params) 224 | 225 | if params['K'] > params['H']: 226 | opt_barrier_payoff = barrier_factors['F'] 227 | if params['K'] < params['H']: 228 | opt_barrier_payoff = ( 229 | barrier_factors['A'] - barrier_factors['B'] 230 | + barrier_factors['C'] - barrier_factors['D'] 231 | + barrier_factors['F']) 232 | 233 | return opt_barrier_payoff 234 | 235 | 236 | @staticmethod 237 | def _do_put(params: dict) -> float: 238 | 239 | params['eta'] = 1 240 | params['phi'] = -1 241 | 242 | barrier_factors, params = Utils.barrier_factors(params=params) 243 | 244 | if params['K'] > params['H']: 245 | opt_barrier_payoff = ( 246 | barrier_factors['A'] - barrier_factors['B'] 247 | + barrier_factors['C'] - barrier_factors['D'] 248 | + barrier_factors['F']) 249 | if params['K'] < params['H']: 250 | opt_barrier_payoff = barrier_factors['F'] 251 | 252 | return opt_barrier_payoff 253 | 254 | 255 | @staticmethod 256 | def _uo_put(params: dict) -> float: 257 | 258 | params['eta'] = -1 259 | params['phi'] = -1 260 | 261 | barrier_factors, params = Utils.barrier_factors(params=params) 262 | 263 | if params['K'] > params['H']: 264 | opt_barrier_payoff = ( 265 | barrier_factors['B'] - barrier_factors['D'] 266 | + barrier_factors['F']) 267 | if params['K'] < params['H']: 268 | opt_barrier_payoff = ( 269 | barrier_factors['A'] - barrier_factors['C'] 270 | + barrier_factors['F']) 271 | 272 | return opt_barrier_payoff 273 | -------------------------------------------------------------------------------- /optionvisualizer/greeks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Display 2D and 3D Greeks graphs 3 | 4 | """ 5 | import matplotlib.figure as mplfig 6 | from matplotlib import axes 7 | from optionvisualizer.sensitivities import Sens 8 | from optionvisualizer.greeks_2d import Greeks_2D 9 | from optionvisualizer.greeks_3d import Greeks_3D 10 | 11 | # pylint: disable=invalid-name 12 | 13 | class Greeks(): 14 | """ 15 | Display 2D and 3D Greeks graphs 16 | 17 | """ 18 | 19 | @staticmethod 20 | def greeks_graphs_2D( 21 | params: dict) -> tuple[mplfig.Figure, axes.Axes] | dict | None: 22 | """ 23 | Plot chosen 2D greeks graph. 24 | 25 | 26 | Parameters 27 | ---------- 28 | x_plot : Str 29 | The x-axis variable ('price', 'strike', 'vol' or 30 | 'time'). The default is 'price'. 31 | y_plot : Str 32 | The y-axis variable ('value', 'delta', 'gamma', 'vega' 33 | or 'theta'). The default is 'delta. 34 | S : Float 35 | Underlying Stock Price. The default is 100. 36 | G1 : Float 37 | Strike Price of option 1. The default is 90. 38 | G2 : Float 39 | Strike Price of option 2. The default is 100. 40 | G3 : Float 41 | Strike Price of option 3. The default is 110. 42 | T : Float 43 | Time to Maturity. The default is 0.25 (3 months). 44 | T1 : Float 45 | Time to Maturity of option 1. The default is 0.25 46 | (3 months). 47 | T2 : Float 48 | Time to Maturity of option 1. The default is 0.25 49 | (3 months). 50 | T3 : Float 51 | Time to Maturity of option 1. The default is 0.25 52 | (3 months). 53 | time_shift : Float 54 | Difference between T1 and T2 in rho graphs. The default 55 | is 0.25 (3 months). 56 | r : Float 57 | Interest Rate. The default is 0.05 (5%). 58 | q : Float 59 | Dividend Yield. The default is 0. 60 | sigma : Float 61 | Implied Volatility. The default is 0.2 (20%). 62 | option : Str 63 | Option type, Put or Call. The default is 'call' 64 | direction : Str 65 | Whether the payoff is long or short. The default is 'long'. 66 | size2d : Tuple 67 | Figure size for matplotlib chart. The default is (6, 4). 68 | mpl_style : Str 69 | Matplotlib style template for 2D risk charts and payoffs. 70 | The default is 'seaborn-v0_8-darkgrid'. 71 | num_sens : Bool 72 | Whether to calculate numerical or analytical sensitivity. 73 | The default is False. 74 | 75 | Returns 76 | ------- 77 | Runs method to create data for 2D greeks graph. 78 | 79 | """ 80 | 81 | if params['gif'] or params['graph_figure']: 82 | fig, ax = Greeks_2D.vis_greeks_2D(params=params) 83 | return fig, ax 84 | 85 | if params['data_output']: 86 | data_dict = Greeks_2D.vis_greeks_2D(params=params) 87 | return data_dict 88 | 89 | return Greeks_2D.vis_greeks_2D(params=params) 90 | 91 | 92 | @staticmethod 93 | def greeks_graphs_3D( 94 | params: dict) -> tuple[mplfig.Figure, axes.Axes, str, int] | dict | None: 95 | """ 96 | Plot chosen 3D greeks graph. 97 | 98 | Parameters 99 | ---------- 100 | greek : Str 101 | The sensitivity to be charted. Select from 'delta', 'gamma', 102 | 'vega', 'theta', 'rho', 'vomma', 'vanna', 'zomma', 'speed', 103 | 'color', 'ultima', 'vega_bleed', 'charm'. The default is 104 | 'delta' 105 | S : Float 106 | Underlying Stock Price. The default is 100. 107 | r : Float 108 | Interest Rate. The default is 0.05 (5%). 109 | q : Float 110 | Dividend Yield. The default is 0. 111 | sigma : Float 112 | Implied Volatility. The default is 0.2 (20%). 113 | option : Str 114 | Option type, Put or Call. The default is 'call' 115 | interactive : Bool 116 | Whether to show matplotlib (False) or plotly(True) graph. 117 | The default is False. 118 | notebook : Bool 119 | Whether the function is being run in an IPython notebook and 120 | hence whether it should output in line or to an HTML file. 121 | The default is False. 122 | colorscheme : Str 123 | The matplotlib colormap or plotly colorscale to use. The 124 | default is 'jet' (which works in both). 125 | colorintensity : Float 126 | The alpha value indicating level of transparency / 127 | intensity. The default is 1. 128 | size3d : Tuple 129 | Figure size for matplotlib chart. The default is (15, 12). 130 | direction : Str 131 | Whether the payoff is long or short. The default is 'long'. 132 | axis : Str 133 | Whether the x-axis is 'price' or 'vol'. The default 134 | is 'price'. 135 | spacegrain : Int 136 | Number of points in each axis linspace argument for 3D 137 | graphs. The default is 100. 138 | azim : Float 139 | L-R view angle for 3D graphs. The default is -50. 140 | elev : Float 141 | Elevation view angle for 3D graphs. The default is 20. 142 | num_sens : Bool 143 | Whether to calculate numerical or analytical sensitivity. 144 | The default is False. 145 | gif : Bool 146 | Whether to create an animated gif. The default is False. 147 | 148 | Returns 149 | ------- 150 | Runs method to display 3D greeks graph. 151 | 152 | """ 153 | 154 | # Select the input name and method name from the greek 155 | # dictionary 156 | for greek_label in params['greek_dict'].keys(): 157 | 158 | # If the greek is the same for call or put, set the option 159 | # value to 'Call / Put' 160 | if params['greek'] in params['equal_greeks']: 161 | params['option_title'] = 'Call / Put' 162 | else: 163 | params['option_title'] = params['option'].title() 164 | 165 | # For the specified greek 166 | if params['greek'] == greek_label: 167 | 168 | # Prepare the graph axes 169 | graph_params = Greeks_3D.graph_space_prep(params=params) 170 | 171 | if params['axis'] == 'price': 172 | 173 | # Select the individual greek method from sensitivities 174 | graph_params['z'] = Sens.sensitivities_static( 175 | params=params, S=graph_params['x'], K=params['S'], 176 | T=graph_params['y'], r=params['r'], 177 | q=params['q'], sigma=params['sigma'], 178 | option=params['option'], greek=params['greek'], 179 | price_shift=0.25, vol_shift=0.001, 180 | ttm_shift=(1 / 365), num_sens=params['num_sens']) 181 | 182 | if params['axis'] == 'vol': 183 | 184 | # Select the individual greek method from sensitivities 185 | graph_params['z'] = Sens.sensitivities_static( 186 | params=params, S=params['S'], K=params['S'], 187 | T=graph_params['y'], r=params['r'], q=params['q'], 188 | sigma=graph_params['x'], option=params['option'], 189 | greek=params['greek'], price_shift=0.25, 190 | vol_shift=0.001, ttm_shift=(1 / 365), 191 | num_sens=params['num_sens']) 192 | 193 | # Run the 3D visualisation method 194 | if params['gif']: 195 | fig, ax, titlename, title_font_scale = Greeks_3D.vis_greeks_3D( 196 | graph_params=graph_params, params=params) 197 | return fig, ax, titlename, title_font_scale 198 | 199 | if params['data_output']: 200 | data_dict = Greeks_3D.vis_greeks_3D( 201 | graph_params=graph_params, params=params) 202 | return data_dict 203 | 204 | return Greeks_3D.vis_greeks_3D( 205 | graph_params=graph_params, params=params) 206 | -------------------------------------------------------------------------------- /optionvisualizer/greeks_2d.py: -------------------------------------------------------------------------------- 1 | """ 2 | Display 2D Greeks graphs 3 | 4 | """ 5 | 6 | #from matplotlib import pylab 7 | import matplotlib as mpl 8 | import matplotlib.figure as mplfig 9 | import matplotlib.pyplot as plt 10 | from mpl_toolkits.mplot3d.axes3d import Axes3D # pylint: disable=unused-import 11 | import numpy as np 12 | import plotly.graph_objects as go 13 | from matplotlib import axes 14 | from plotly.offline import plot 15 | from optionvisualizer.sensitivities import Sens 16 | 17 | # pylint: disable=invalid-name 18 | 19 | class Greeks_2D(): 20 | """ 21 | Display 2D Greeks graphs 22 | 23 | """ 24 | 25 | @classmethod 26 | def vis_greeks_2D( 27 | cls, 28 | params: dict) -> tuple[mplfig.Figure, axes.Axes] | dict | go.Figure | None: 29 | """ 30 | Creates data for 2D greeks graph. 31 | 32 | Returns 33 | ------- 34 | Runs method to graph using Matplotlib. 35 | 36 | """ 37 | 38 | # create arrays of (default is 1000) equally spaced points for a range 39 | # of strike prices, volatilities and maturities 40 | params['SA'] = np.linspace( 41 | params['strike_min'] * params['S'], 42 | params['strike_max'] * params['S'], 43 | params['linspace_granularity']) 44 | params['sigmaA'] = np.linspace( 45 | params['vol_min'], 46 | params['vol_max'], 47 | params['linspace_granularity']) 48 | params['TA'] = np.linspace( 49 | params['time_min'], 50 | params['time_max'], 51 | params['linspace_granularity']) 52 | 53 | # y-axis parameters other than rho require 3 options to be 54 | # graphed 55 | if params['y_plot'] in params['y_name_dict'].keys(): 56 | 57 | params = cls._non_rho_data(params) 58 | 59 | # rho requires 4 options to be graphed 60 | if params['y_plot'] == 'rho': 61 | 62 | params = cls._rho_data(params) 63 | 64 | # Convert the x-plot and y-plot values to axis labels 65 | xlabel = params['label_dict'][str(params['x_plot'])] 66 | ylabel = params['label_dict'][str(params['y_plot'])] 67 | 68 | # If the greek is rho or the same for a call or a put, set the 69 | # option name to 'Call / Put' 70 | if params['y_plot'] in [params['equal_greeks'], 'rho']: 71 | params['option'] = 'Call / Put' 72 | 73 | # Create chart title as direction plus option type plus y-plot 74 | # vs x-plot 75 | title = (str(params['direction'].title()) 76 | +' ' 77 | +str(params['option'].title()) 78 | +' ' 79 | +params['y_plot'].title() 80 | +' vs ' 81 | +params['x_plot'].title()) 82 | 83 | # Set the x-axis array as price, vol or time 84 | x_name = str(params['x_plot']) 85 | if x_name in params['x_name_dict'].keys(): 86 | xarray = (params[str(params['x_name_dict'][x_name])] * 87 | params['x_scale_dict'][x_name]) 88 | 89 | vis_params = { 90 | 'x_plot':params['x_plot'], 91 | 'yarray1':params['C1'], 92 | 'yarray2':params['C2'], 93 | 'yarray3':params['C3'], 94 | 'yarray4':params['C4'], 95 | 'xarray':xarray, 96 | 'label1':params['label1'], 97 | 'label2':params['label2'], 98 | 'label3':params['label3'], 99 | 'label4':params['label4'], 100 | 'color1':params['option1_color'], 101 | 'color2':params['option2_color'], 102 | 'color3':params['option3_color'], 103 | 'color4':params['option4_color'], 104 | 'xlabel':xlabel, 105 | 'ylabel':ylabel, 106 | 'title':title, 107 | 'size2d':params['size2d'], 108 | 'mpl_style':params['mpl_style'], 109 | 'gif':params['gif'], 110 | 'graph_figure':params['graph_figure'] 111 | } 112 | 113 | 114 | 115 | # Plot 3 option charts 116 | if params['y_plot'] in params['y_name_dict'].keys(): 117 | vis_params = cls._graph_range_2d( 118 | vis_params=vis_params, rho_graph=False) 119 | if params['interactive']: 120 | if params['data_output']: 121 | return { 122 | 'params': params, 123 | 'vis_params': vis_params 124 | } 125 | return cls._vis_greeks_plotly( 126 | vis_params=vis_params, params=params) 127 | 128 | if params['gif'] or params['graph_figure']: 129 | fig, ax = cls._vis_greeks_mpl( 130 | vis_params=vis_params, params=params) 131 | return fig, ax 132 | 133 | return cls._vis_greeks_mpl( 134 | vis_params=vis_params, params=params) 135 | 136 | # Plot Rho charts 137 | if params['y_plot'] == 'rho': 138 | vis_params = cls._graph_range_2d( 139 | vis_params=vis_params, rho_graph=True) 140 | if params['interactive']: 141 | if params['data_output']: 142 | return { 143 | 'params': params, 144 | 'vis_params': vis_params 145 | } 146 | return cls._vis_greeks_plotly( 147 | vis_params=vis_params, params=params) 148 | 149 | vis_params.update({'gif':False}) 150 | if params['graph_figure']: 151 | fig, ax = cls._vis_greeks_mpl( 152 | vis_params=vis_params, params=params) 153 | return fig, ax 154 | return cls._vis_greeks_mpl( 155 | vis_params=vis_params, params=params) 156 | 157 | return print("Please select a valid pair") 158 | 159 | 160 | @classmethod 161 | def _non_rho_data(cls, params: dict) -> dict: 162 | 163 | for opt in [1, 2, 3]: 164 | if params['x_plot'] == 'price': 165 | 166 | # For price we set S to the array SA 167 | params['C'+str(opt)] = Sens.sensitivities_static( 168 | params=params, S=params['SA'], 169 | K=params['G'+str(opt)], T=params['T'+str(opt)], 170 | r=params['r'], q=params['q'], sigma=params['sigma'], 171 | option=params['option'], 172 | greek=params['y_name_dict'][params['y_plot']], 173 | price_shift=0.25, vol_shift=0.001, 174 | ttm_shift=(1 / 365), rate_shift=0.0001, 175 | num_sens=params['num_sens']) 176 | 177 | if params['x_plot'] == 'vol': 178 | 179 | # For vol we set sigma to the array sigmaA 180 | params['C'+str(opt)] = Sens.sensitivities_static( 181 | params=params, S=params['S'], 182 | K=params['G'+str(opt)], T=params['T'+str(opt)], 183 | r=params['r'], q=params['q'], sigma=params['sigmaA'], 184 | option=params['option'], 185 | greek=params['y_name_dict'][params['y_plot']], 186 | price_shift=0.25, vol_shift=0.001, 187 | ttm_shift=(1 / 365), rate_shift=0.0001) 188 | 189 | if params['x_plot'] == 'time': 190 | 191 | # For time we set T to the array TA 192 | params['C'+str(opt)] = Sens.sensitivities_static( 193 | params=params, S=params['S'], 194 | K=params['G'+str(opt)], T=params['TA'], r=params['r'], 195 | q=params['q'], sigma=params['sigma'], 196 | option=params['option'], 197 | greek=params['y_name_dict'][params['y_plot']], 198 | price_shift=0.25, vol_shift=0.001, 199 | ttm_shift=(1 / 365), rate_shift=0.0001) 200 | 201 | # Reverse the option value if direction is 'short' 202 | if params['direction'] == 'short': 203 | for opt in [1, 2, 3]: 204 | params['C'+str(opt)] = -params['C'+str(opt)] 205 | 206 | # Call strike_tenor_label method to assign labels to chosen 207 | # strikes and tenors 208 | params = cls._strike_tenor_label(params) 209 | 210 | return params 211 | 212 | 213 | @staticmethod 214 | def _rho_data(params: dict) -> dict: 215 | 216 | # Set T1 and T2 to the specified time and shifted time 217 | params['T1'] = params['T'] 218 | params['T2'] = params['T'] + params['time_shift'] 219 | 220 | # 2 Tenors 221 | tenor_type = {1:1, 2:2, 3:1, 4:2} 222 | 223 | # And call and put for each tenor 224 | opt_type = {1:'call', 2:'call', 3:'put', 4:'put'} 225 | for opt in [1, 2, 3, 4]: 226 | if params['x_plot'] == 'price': 227 | 228 | # For price we set S to the array SA 229 | params['C'+str(opt)] = Sens.sensitivities_static( 230 | params=params, S=params['SA'], K=params['G2'], 231 | T=params['T'+str(tenor_type[opt])], r=params['r'], 232 | q=params['q'], sigma=params['sigma'], 233 | option=opt_type[opt], greek=params['y_plot'], 234 | price_shift=0.25, vol_shift=0.001, ttm_shift=(1 / 365), 235 | rate_shift=0.0001) 236 | 237 | if params['x_plot'] == 'strike': 238 | 239 | # For strike we set K to the array SA 240 | params['C'+str(opt)] = Sens.sensitivities_static( 241 | params=params, S=params['S'], K=params['SA'], 242 | T=params['T'+str(tenor_type[opt])], r=params['r'], 243 | q=params['q'], sigma=params['sigma'], 244 | option=opt_type[opt], greek=params['y_plot'], 245 | price_shift=0.25, vol_shift=0.001, ttm_shift=(1 / 365), 246 | rate_shift=0.0001) 247 | 248 | if params['x_plot'] == 'vol': 249 | 250 | # For vol we set sigma to the array sigmaA 251 | params['C'+str(opt)] = Sens.sensitivities_static( 252 | params=params, S=params['S'], K=params['G2'], 253 | T=params['T'+str(tenor_type[opt])], r=params['r'], 254 | q=params['q'], sigma=params['sigmaA'], 255 | option=opt_type[opt], greek=params['y_plot'], 256 | price_shift=0.25, vol_shift=0.001, ttm_shift=(1 / 365), 257 | rate_shift=0.0001) 258 | 259 | # Reverse the option value if direction is 'short' 260 | if params['direction'] == 'short': 261 | for opt in [1, 2, 3, 4]: 262 | params['C'+str(opt)] = -params['C'+str(opt)] 263 | 264 | # Assign the option labels 265 | params['label1'] = str(int(params['T1'] * 365))+' Day Call' 266 | params['label2'] = str(int(params['T2'] * 365))+' Day Call' 267 | params['label3'] = str(int(params['T1'] * 365))+' Day Put' 268 | params['label4'] = str(int(params['T2'] * 365))+' Day Put' 269 | 270 | return params 271 | 272 | 273 | @staticmethod 274 | def _strike_tenor_label(params: dict) -> dict: 275 | """ 276 | Assign labels to chosen strikes and tenors in 2D greeks graph 277 | 278 | Returns 279 | ------- 280 | Str 281 | Labels for each of the 3 options in 2D greeks graph. 282 | 283 | """ 284 | strike_label = {} 285 | for strike, strike_value in {'G1':'label1', 286 | 'G2':'label2', 287 | 'G3':'label3'}.items(): 288 | 289 | # If the strike is 100% change name to 'ATM' 290 | if params[str(strike)] == params['S']: 291 | strike_label[strike_value] = 'ATM Strike' 292 | else: 293 | strike_label[strike_value] = ( 294 | str(int(params[strike])) 295 | +' Strike') 296 | 297 | for tenor, tenor_value in {'T1':'label1', 298 | 'T2':'label2', 299 | 'T3':'label3'}.items(): 300 | 301 | # Make each label value the number of days to maturity 302 | # plus the strike level 303 | params[tenor_value] = ( 304 | str(int(params[str(tenor)]*365)) 305 | +' Day ' 306 | +strike_label[str(tenor_value)]) 307 | 308 | return params 309 | 310 | 311 | @staticmethod 312 | def _vis_greeks_mpl( 313 | vis_params: dict, 314 | params: dict) -> tuple[mplfig.Figure, axes.Axes] | None: 315 | """ 316 | Display the 2D greeks chart using matplotlib 317 | 318 | Parameters 319 | ---------- 320 | xarray : Array 321 | x-axis values. 322 | yarray1 : Array 323 | y-axis values for option 1. 324 | yarray2 : Array 325 | y-axis values for option 2. 326 | yarray3 : Array 327 | y-axis values for option 3. 328 | yarray4 : Array 329 | y-axis values for option 4. 330 | label1 : Str 331 | Option 1 label. 332 | label2 : Str 333 | Option 2 label. 334 | label3 : Str 335 | Option 3 label. 336 | label4 : Str 337 | Option 4 label. 338 | xlabel : Str 339 | x-axis label. 340 | ylabel : Str 341 | y-axis label. 342 | title : Str 343 | Chart title. 344 | 345 | Returns 346 | ------- 347 | 2D Greeks chart. 348 | 349 | """ 350 | 351 | # Set style to chosen mpl_style (default is Seaborn Darkgrid) 352 | plt.style.use(vis_params['mpl_style']) 353 | 354 | # Update chart parameters 355 | #pylab.rcParams.update(params['mpl_params']) 356 | mpl.rcParams.update(params['mpl_params']) 357 | 358 | # Create the figure and axes objects 359 | fig, ax = plt.subplots(figsize=vis_params['size2d']) 360 | 361 | # If plotting against time, show time to maturity reducing left 362 | # to right 363 | if vis_params['x_plot'] == 'time': 364 | ax.invert_xaxis() 365 | 366 | # Plot the 1st option 367 | ax.plot(vis_params['xarray'], 368 | vis_params['yarray1'], 369 | vis_params['color1'], 370 | label=vis_params['label1']) 371 | 372 | # Plot the 2nd option 373 | ax.plot(vis_params['xarray'], 374 | vis_params['yarray2'], 375 | vis_params['color2'], 376 | label=vis_params['label2']) 377 | 378 | # Plot the 3rd option 379 | ax.plot(vis_params['xarray'], 380 | vis_params['yarray3'], 381 | vis_params['color3'], 382 | label=vis_params['label3']) 383 | 384 | # 4th option only used in Rho graphs 385 | if vis_params['label4'] is not None: 386 | ax.plot(vis_params['xarray'], 387 | vis_params['yarray4'], 388 | vis_params['color4'], 389 | label=vis_params['label4']) 390 | 391 | # Apply a grid 392 | plt.grid(True) 393 | 394 | # Apply a black border to the chart 395 | #ax.patch.set_edgecolor('black') 396 | #ax.patch.set_linewidth('1') 397 | 398 | fig.patch.set(linewidth=1, edgecolor='black') 399 | 400 | # Set x and y axis labels and title 401 | ax.set(xlabel=vis_params['xlabel'], 402 | ylabel=vis_params['ylabel'], 403 | title=vis_params['title']) 404 | 405 | # Create a legend 406 | ax.legend(loc=0, fontsize=10) 407 | 408 | if vis_params['gif'] or vis_params['graph_figure']: 409 | plt.show() 410 | return fig, ax 411 | 412 | # Display the chart 413 | return plt.show() 414 | 415 | 416 | @classmethod 417 | def _vis_greeks_plotly( 418 | cls, 419 | vis_params: dict, 420 | params: dict) -> go.Figure | None: 421 | """ 422 | Display the 2D greeks chart using plotly 423 | 424 | Parameters 425 | ---------- 426 | xarray : Array 427 | x-axis values. 428 | yarray1 : Array 429 | y-axis values for option 1. 430 | yarray2 : Array 431 | y-axis values for option 2. 432 | yarray3 : Array 433 | y-axis values for option 3. 434 | yarray4 : Array 435 | y-axis values for option 4. 436 | label1 : Str 437 | Option 1 label. 438 | label2 : Str 439 | Option 2 label. 440 | label3 : Str 441 | Option 3 label. 442 | label4 : Str 443 | Option 4 label. 444 | xlabel : Str 445 | x-axis label. 446 | ylabel : Str 447 | y-axis label. 448 | title : Str 449 | Chart title. 450 | 451 | Returns 452 | ------- 453 | 2D Greeks chart. 454 | 455 | """ 456 | 457 | # Create the figure 458 | fig = go.Figure() 459 | 460 | # If plotting against time, show time to maturity reducing left 461 | # to right 462 | if vis_params['x_plot'] == 'time': 463 | fig.update_xaxes(autorange="reversed") 464 | 465 | # Plot the 1st option 466 | fig.add_trace(go.Scatter(x=vis_params['xarray'], 467 | y=vis_params['yarray1'], 468 | line={'color': 'blue'}, 469 | name=vis_params['label1'])) 470 | 471 | # Plot the 2nd option 472 | fig.add_trace(go.Scatter(x=vis_params['xarray'], 473 | y=vis_params['yarray2'], 474 | line={'color': 'red'}, 475 | name=vis_params['label2'])) 476 | 477 | # Plot the 3rd option 478 | fig.add_trace(go.Scatter(x=vis_params['xarray'], 479 | y=vis_params['yarray3'], 480 | line={'color': 'green'}, 481 | name=vis_params['label3'])) 482 | 483 | # 4th option only used in Rho graphs 484 | if vis_params['label4'] is not None: 485 | fig.add_trace(go.Scatter(x=vis_params['xarray'], 486 | y=vis_params['yarray4'], 487 | line={'color': 'orange'}, 488 | name=vis_params['label4'])) 489 | 490 | 491 | fig.update_layout( 492 | title={ 493 | 'text': vis_params['title'], 494 | 'y':0.95, 495 | 'x':0.5, 496 | 'xanchor':'center', 497 | 'yanchor':'top', 498 | 'font':{ 499 | 'size': 20, 500 | 'color': "#f2f5fa" 501 | } 502 | }, 503 | xaxis_title={ 504 | 'text': vis_params['xlabel'], 505 | 'font': { 506 | 'size': 15, 507 | 'color': "#f2f5fa" 508 | } 509 | }, 510 | yaxis_title={ 511 | 'text': vis_params['ylabel'], 512 | 'font': { 513 | 'size': 15, 514 | 'color': "#f2f5fa" 515 | } 516 | }, 517 | font={'color': '#f2f5fa'}, 518 | paper_bgcolor='black', 519 | plot_bgcolor='black', 520 | legend={ 521 | 'x': 0.05, 522 | 'y': 0.95, 523 | 'traceorder': "normal", 524 | 'bgcolor': 'rgba(0, 0, 0, 0)', 525 | 'font': { 526 | 'family': "sans-serif", 527 | 'size': 12, 528 | 'color': "#f2f5fa" 529 | }, 530 | }, 531 | ) 532 | 533 | if params['web_graph'] is False: 534 | fig.update_layout( 535 | autosize=False, 536 | width=800, 537 | height=600 538 | ) 539 | 540 | fig.update_xaxes(showline=True, 541 | linewidth=2, 542 | linecolor='#2a3f5f', 543 | mirror=True, 544 | range = [vis_params['xmin'], vis_params['xmax']], 545 | gridwidth=1, 546 | gridcolor='#2a3f5f', 547 | zeroline=False) 548 | 549 | fig.update_yaxes(showline=True, 550 | linewidth=2, 551 | linecolor='#2a3f5f', 552 | mirror=True, 553 | range = [vis_params['ymin'], vis_params['ymax']], 554 | gridwidth=1, 555 | gridcolor='#2a3f5f', 556 | zeroline=False) 557 | 558 | # If running in an iPython notebook the chart will display 559 | # in line 560 | if params['notebook']: 561 | # If output is sent to Dash 562 | if params['web_graph']: 563 | return fig 564 | 565 | fig.show() 566 | return None 567 | 568 | # Otherwise create an HTML file that opens in a new window 569 | plot(fig, auto_open=True) 570 | return None 571 | 572 | 573 | @staticmethod 574 | def _graph_range_2d( 575 | vis_params: dict, 576 | rho_graph: bool) -> dict: 577 | """ 578 | Set 2D graph ranges 579 | 580 | Parameters 581 | ---------- 582 | vis_params : Dict 583 | Dictionary of parameters. 584 | 585 | Returns 586 | ------- 587 | xmin : Float 588 | x-axis minimum. 589 | xmax : Float 590 | x-axis maximum. 591 | ymin : Float 592 | y-axis minimum. 593 | ymax : Float 594 | y-axis maximum. 595 | 596 | """ 597 | 598 | ranges = {} 599 | ranges['min_x'] = vis_params['xarray'].min() 600 | ranges['max_x'] = vis_params['xarray'].max() 601 | ranges['min_y1'] = vis_params['yarray1'].min() 602 | ranges['max_y1'] = vis_params['yarray1'].max() 603 | ranges['min_y2'] = vis_params['yarray2'].min() 604 | ranges['max_y2'] = vis_params['yarray2'].max() 605 | ranges['min_y3'] = vis_params['yarray3'].min() 606 | ranges['max_y3'] = vis_params['yarray3'].max() 607 | 608 | if rho_graph: 609 | ranges['min_y4'] = vis_params['yarray4'].min() 610 | ranges['max_y4'] = vis_params['yarray4'].max() 611 | else: 612 | ranges['min_y4'] = ranges['min_y1'] 613 | ranges['max_y4'] = ranges['max_y1'] 614 | 615 | x_scale_shift = (ranges['max_x'] - ranges['min_x']) * 0.05 616 | xmin = ranges['min_x'] - x_scale_shift 617 | xmax = ranges['max_x'] + x_scale_shift 618 | y_scale_shift = ( 619 | (max(ranges['max_y1'], 620 | ranges['max_y2'], 621 | ranges['max_y3'], 622 | ranges['max_y4']) 623 | - min(ranges['min_y1'], 624 | ranges['min_y2'], 625 | ranges['min_y3'], 626 | ranges['min_y4'])) 627 | * 0.05) 628 | ymin = min(ranges['min_y1'], 629 | ranges['min_y2'], 630 | ranges['min_y3'], 631 | ranges['min_y4']) - y_scale_shift 632 | ymax = max(ranges['max_y1'], 633 | ranges['max_y2'], 634 | ranges['max_y3'], 635 | ranges['max_y4']) + y_scale_shift 636 | 637 | vis_params['xmin'] = xmin 638 | vis_params['xmax'] = xmax 639 | vis_params['ymin'] = ymin 640 | vis_params['ymax'] = ymax 641 | 642 | return vis_params 643 | -------------------------------------------------------------------------------- /optionvisualizer/greeks_3d.py: -------------------------------------------------------------------------------- 1 | """ 2 | Display 3D Greeks graphs 3 | 4 | """ 5 | import matplotlib.figure as mplfig 6 | import matplotlib.pyplot as plt 7 | from mpl_toolkits.mplot3d.axes3d import Axes3D # pylint: disable=unused-import 8 | import numpy as np 9 | import plotly.graph_objects as go 10 | from matplotlib import axes 11 | from plotly.offline import plot 12 | # pylint: disable=invalid-name 13 | 14 | class Greeks_3D(): 15 | """ 16 | Display 3D Greeks graphs 17 | 18 | """ 19 | 20 | @staticmethod 21 | def graph_space_prep(params:dict) -> dict: 22 | """ 23 | Prepare the axis ranges to be used in 3D graph. 24 | 25 | Parameters 26 | ---------- 27 | greek : Str 28 | The sensitivity to be charted. Select from 'delta', 'gamma', 29 | 'vega', 'theta', 'rho', 'vomma', 'vanna', 'zomma', 'speed', 30 | 'color', 'ultima', 'vega_bleed', 'charm'. The default is 31 | 'delta' 32 | S : Float 33 | Underlying Stock Price. The default is 100. 34 | axis : Str 35 | Whether the x-axis is 'price' or 'vol'. The default 36 | is 'price'. 37 | spacegrain : Int 38 | Number of points in each axis linspace argument for 3D 39 | graphs. The default is 100. 40 | 41 | Returns 42 | ------- 43 | Various 44 | Updated parameters to be used in 3D graph. 45 | 46 | """ 47 | 48 | graph_params = {} 49 | 50 | # Select the strike and Time ranges for each greek from the 3D 51 | # chart ranges dictionary 52 | SA_lower = params['3D_chart_ranges'][str(params['greek'])]['SA_lower'] 53 | SA_upper = params['3D_chart_ranges'][str(params['greek'])]['SA_upper'] 54 | TA_lower = params['3D_chart_ranges'][str(params['greek'])]['TA_lower'] 55 | TA_upper = params['3D_chart_ranges'][str(params['greek'])]['TA_upper'] 56 | 57 | # Set the volatility range from 5% to 50% 58 | sigmaA_lower = 0.05 59 | sigmaA_upper = 0.5 60 | 61 | # create arrays of 100 equally spaced points for the ranges of 62 | # strike prices, volatilities and maturities 63 | SA = np.linspace(SA_lower * params['S'], 64 | SA_upper * params['S'], 65 | int(params['spacegrain'])) 66 | TA = np.linspace(TA_lower, 67 | TA_upper, 68 | int(params['spacegrain'])) 69 | sigmaA = np.linspace(sigmaA_lower, 70 | sigmaA_upper, 71 | int(params['spacegrain'])) 72 | 73 | # set y-min and y-max labels 74 | graph_params['ymin'] = TA_lower 75 | graph_params['ymax'] = TA_upper 76 | graph_params['axis_label2'] = 'Time to Expiration (Days)' 77 | 78 | # set x-min and x-max labels 79 | if params['axis'] == 'price': 80 | graph_params['x'], graph_params['y'] = np.meshgrid(SA, TA) 81 | graph_params['xmin'] = SA_lower 82 | graph_params['xmax'] = SA_upper 83 | graph_params['graph_scale'] = 1 84 | graph_params['axis_label1'] = 'Underlying Price' 85 | 86 | if params['axis'] == 'vol': 87 | graph_params['x'], graph_params['y'] = np.meshgrid(sigmaA, TA) 88 | graph_params['xmin'] = sigmaA_lower 89 | graph_params['xmax'] = sigmaA_upper 90 | graph_params['graph_scale'] = 100 91 | graph_params['axis_label1'] = 'Volatility %' 92 | 93 | return graph_params 94 | 95 | 96 | @classmethod 97 | def vis_greeks_3D( 98 | cls, 99 | graph_params: dict, 100 | params: dict) -> None | dict | tuple[mplfig.Figure, axes.Axes, str, int]: 101 | """ 102 | Display 3D greeks graph. 103 | 104 | Returns 105 | ------- 106 | If 'interactive' is False, a matplotlib static graph. 107 | If 'interactive' is True, a plotly graph that can be rotated 108 | and zoomed. 109 | 110 | """ 111 | 112 | # Reverse the z-axis data if direction is 'short' 113 | if params['direction'] == 'short': 114 | graph_params['z'] = -graph_params['z'] 115 | 116 | graph_params['titlename'] = cls._titlename(params=params) 117 | graph_params['axis_label3'] = str(params['greek'].title()) 118 | 119 | if params['interactive']: 120 | # Create a plotly graph 121 | 122 | # Set the ranges for the contour values and reverse / rescale axes 123 | graph_params = cls._plotly_3D_ranges(graph_params=graph_params) 124 | 125 | if params['data_output']: 126 | return { 127 | 'params': params, 128 | 'graph_params': graph_params 129 | } 130 | 131 | return cls._plotly_3D(graph_params=graph_params, params=params) 132 | 133 | # Otherwise create a matplotlib graph 134 | graph_params = cls._mpl_axis_format(graph_params=graph_params) 135 | 136 | if params['gif']: 137 | fig, ax, titlename, title_font_scale = cls._mpl_3D( 138 | graph_params=graph_params, params=params) 139 | return fig, ax, titlename, title_font_scale 140 | 141 | return cls._mpl_3D(graph_params=graph_params, params=params) 142 | 143 | 144 | @staticmethod 145 | def _titlename(params: dict) -> str: 146 | """ 147 | Create graph title based on option type, direction and greek 148 | 149 | Returns 150 | ------- 151 | Graph title. 152 | 153 | """ 154 | 155 | titlename = str(str(params['direction'].title()) 156 | +' ' 157 | +params['option_title'] 158 | +' Option ' 159 | +str(params['greek'].title())) 160 | 161 | return titlename 162 | 163 | 164 | @staticmethod 165 | def _plotly_3D_ranges(graph_params: dict) -> dict: 166 | """ 167 | Generate contour ranges and format axes for plotly 3D graph 168 | 169 | Returns 170 | ------- 171 | axis ranges 172 | 173 | """ 174 | # Set the ranges for the contour values and reverse / rescale axes 175 | graph_params['x'], graph_params['y'] = ( 176 | graph_params['y'] * 365, 177 | graph_params['x'] * graph_params['graph_scale']) 178 | graph_params['x_start'] = graph_params['ymin'] 179 | graph_params['x_stop'] = graph_params['ymax'] * 360 180 | graph_params['x_size'] = graph_params['x_stop'] / 18 181 | graph_params['y_start'] = graph_params['xmin'] 182 | graph_params['y_stop'] = ( 183 | graph_params['xmax'] * graph_params['graph_scale']) 184 | graph_params['y_size'] = ( 185 | int((graph_params['xmax'] - graph_params['xmin']) / 20)) 186 | graph_params['z_start'] = np.min(graph_params['z']) 187 | graph_params['z_stop'] = np.max(graph_params['z']) 188 | graph_params['z_size'] = ( 189 | int((np.max(graph_params['z']) - np.min(graph_params['z'])) / 10)) 190 | 191 | return graph_params 192 | 193 | 194 | @staticmethod 195 | def _plotly_3D( 196 | graph_params: dict, 197 | params: dict) -> go.Figure | None: 198 | """ 199 | Display 3D greeks graph. 200 | 201 | Returns 202 | ------- 203 | plotly 3D graph 204 | 205 | """ 206 | # create plotly figure object 207 | fig = go.Figure( 208 | data=[go.Surface( 209 | x=graph_params['x'], 210 | y=graph_params['y'], 211 | z=graph_params['z'], 212 | 213 | # set the colorscale to the chosen 214 | # colorscheme 215 | colorscale=params['colorscheme'], 216 | 217 | # Define the contours 218 | contours = { 219 | "x": {"show": True, 220 | "start": graph_params['x_start'], 221 | "end": graph_params['x_stop'], 222 | "size": graph_params['x_size'], 223 | "color": "white"}, 224 | "y": {"show": True, 225 | "start": graph_params['y_start'], 226 | "end": graph_params['y_stop'], 227 | "size": graph_params['y_size'], 228 | "color": "white"}, 229 | "z": {"show": True, 230 | "start": graph_params['z_start'], 231 | "end": graph_params['z_stop'], 232 | "size": graph_params['z_size']} 233 | }, 234 | ) 235 | ] 236 | ) 237 | 238 | # Set initial view position 239 | camera = { 240 | 'eye': { 241 | 'x': 2, 242 | 'y': 1, 243 | 'z': 1 244 | } 245 | } 246 | 247 | # Set x-axis to decrease from left to right 248 | fig.update_scenes(xaxis_autorange="reversed") 249 | 250 | # Set y-axis to increase from left to right 251 | fig.update_scenes(yaxis_autorange="reversed") 252 | fig.update_layout( 253 | scene={ 254 | 'xaxis': { 255 | 'backgroundcolor': "rgb(200, 200, 230)", 256 | 'gridcolor': "white", 257 | 'showbackground': True, 258 | 'zerolinecolor': "white" 259 | }, 260 | 'yaxis': { 261 | 'backgroundcolor': "rgb(230, 200, 230)", 262 | 'gridcolor': "white", 263 | 'showbackground': True, 264 | 'zerolinecolor': "white" 265 | }, 266 | 'zaxis': { 267 | 'backgroundcolor': "rgb(230, 230, 200)", 268 | 'gridcolor': "white", 269 | 'showbackground': True, 270 | 'zerolinecolor': "white" 271 | }, 272 | # Label axes 273 | 'xaxis_title': graph_params['axis_label2'], 274 | 'yaxis_title': graph_params['axis_label1'], 275 | 'zaxis_title': graph_params['axis_label3'] 276 | }, 277 | title={ 278 | 'text': graph_params['titlename'], 279 | 'y': 0.9, 280 | 'x': 0.5, 281 | 'xanchor': 'center', 282 | 'yanchor': 'top', 283 | 'font': { 284 | 'size': 20, 285 | 'color': "black"}, 286 | }, 287 | margin={ 288 | 'l': 65, 289 | 'r': 50, 290 | 'b': 65, 291 | 't': 90 292 | }, 293 | scene_camera=camera) 294 | 295 | if params['web_graph'] is False: 296 | fig.update_layout( 297 | autosize=False, 298 | width=800, 299 | height=800 300 | ) 301 | 302 | # If running in an iPython notebook the chart will display 303 | # in line 304 | if params['notebook']: 305 | # If output is sent to Dash 306 | if params['web_graph']: 307 | return fig 308 | 309 | fig.show() 310 | return None 311 | 312 | # Otherwise create an HTML file that opens in a new window 313 | plot(fig, auto_open=True) 314 | return None 315 | 316 | 317 | @staticmethod 318 | def _mpl_axis_format(graph_params: dict) -> dict: 319 | """ 320 | Rescale Matplotlib axis values 321 | 322 | Returns 323 | ------- 324 | x, y axis values 325 | 326 | """ 327 | graph_params['x'] = graph_params['x'] * graph_params['graph_scale'] 328 | graph_params['y'] = graph_params['y'] * 365 329 | 330 | return graph_params 331 | 332 | 333 | @staticmethod 334 | def _mpl_3D( 335 | params: dict, 336 | graph_params: dict) -> tuple[mplfig.Figure, axes.Axes, str, int] | None: 337 | """ 338 | Display 3D greeks graph. 339 | 340 | Returns 341 | ------- 342 | Matplotlib static graph. 343 | 344 | """ 345 | 346 | # Update chart parameters 347 | plt.style.use('seaborn-v0_8-darkgrid') 348 | plt.rcParams.update(params['mpl_3d_params']) 349 | 350 | # create figure with specified size tuple 351 | fig = plt.figure(figsize=params['size3d']) 352 | ax = fig.add_subplot(111, 353 | projection='3d', 354 | azim=params['azim'], 355 | elev=params['elev']) 356 | 357 | # Set background color to white 358 | ax.set_facecolor('w') 359 | 360 | # Create values that scale fonts with fig_size 361 | ax_font_scale = int(round(params['size3d'][0] * 1.1)) 362 | title_font_scale = int(round(params['size3d'][0] * 1.8)) 363 | 364 | # Tint the axis panes, RGB values from 0-1 and alpha denoting 365 | # color intensity 366 | ax.xaxis.set_pane_color((0.9, 0.8, 0.9, 0.8)) 367 | ax.yaxis.set_pane_color((0.8, 0.8, 0.9, 0.8)) 368 | ax.zaxis.set_pane_color((0.9, 0.9, 0.8, 0.8)) 369 | 370 | # Set z-axis to left hand side 371 | ax.zaxis._axinfo['juggled'] = (1, 2, 0) # pylint: disable=protected-access 372 | 373 | # Set fontsize of axis ticks 374 | ax.tick_params(axis='both', 375 | which='major', 376 | labelsize=ax_font_scale*0.8, 377 | pad=2) 378 | 379 | # Label axes 380 | ax.set_xlabel(graph_params['axis_label1'], 381 | fontsize=ax_font_scale*0.9, 382 | labelpad=ax_font_scale*0.6) 383 | ax.set_ylabel(graph_params['axis_label2'], 384 | fontsize=ax_font_scale*0.9, 385 | labelpad=ax_font_scale*0.6) 386 | ax.set_zlabel(graph_params['axis_label3'], 387 | fontsize=ax_font_scale*0.9, 388 | labelpad=ax_font_scale*0.2, 389 | rotation='vertical') 390 | 391 | # Auto scale the z-axis 392 | ax.set_zlim(auto=True) 393 | 394 | # Set x-axis to decrease from left to right 395 | #ax.invert_xaxis() 396 | 397 | # Set y-axis to increase from left to right 398 | #ax.invert_yaxis() 399 | 400 | # apply graph_scale so that if volatility is the x-axis it 401 | # will be * 100 402 | ax.plot_surface(graph_params['x'], 403 | graph_params['y'], 404 | graph_params['z'], 405 | rstride=2, 406 | cstride=2, 407 | 408 | # set the colormap to the chosen colorscheme 409 | cmap=plt.get_cmap(params['colorscheme']), 410 | 411 | # set the alpha value to the chosen 412 | # colorintensity 413 | alpha=params['colorintensity'], 414 | linewidth=0.25) 415 | 416 | # Specify title 417 | st = fig.suptitle(graph_params['titlename'], 418 | fontsize=title_font_scale, 419 | fontweight=0, 420 | color='black', 421 | style='italic', 422 | y=1.02) 423 | 424 | st.set_y(0.98) 425 | fig.subplots_adjust(top=1) 426 | 427 | if params['gif']: 428 | return fig, ax, graph_params['titlename'], title_font_scale 429 | 430 | # Display graph 431 | return plt.show() 432 | -------------------------------------------------------------------------------- /optionvisualizer/option_formulas.py: -------------------------------------------------------------------------------- 1 | """ 2 | Option Pricing and Greeks formulas 3 | 4 | """ 5 | import numpy as np 6 | from optionvisualizer.utilities import Utils 7 | # pylint: disable=invalid-name 8 | 9 | class Option(): 10 | """ 11 | Calculate Black Scholes Option Price and Greeks 12 | 13 | """ 14 | 15 | @staticmethod 16 | def price( 17 | opt_params: dict, 18 | params: dict) -> float: 19 | """ 20 | Black Scholes Option Price 21 | 22 | Parameters 23 | ---------- 24 | opt_params : Dict 25 | S : Float 26 | Underlying Stock Price. The default is 100. 27 | K : Float 28 | Strike Price. The default is 100. 29 | T : Float 30 | Time to Maturity. The default is 0.25 (3 months). 31 | r : Float 32 | Interest Rate. The default is 0.05 (5%). 33 | q : Float 34 | Dividend Yield. The default is 0. 35 | sigma : Float 36 | Implied Volatility. The default is 0.2 (20%). 37 | option : Str 38 | Option type, Put or Call. The default is 'call' 39 | params : Dict 40 | Dictionary of key parameters; used for refreshing distribution. 41 | 42 | Returns 43 | ------- 44 | Float 45 | Black Scholes Option Price. 46 | 47 | """ 48 | 49 | # Update distribution parameters 50 | params = Utils.refresh_dist_params( 51 | opt_params=opt_params, params=params) 52 | 53 | if opt_params['option'] == "call": 54 | opt_price = ( 55 | (opt_params['S'] * params['carry'] * params['Nd1']) 56 | - (opt_params['K'] * np.exp(-opt_params['r'] * opt_params['T']) 57 | * params['Nd2'])) 58 | elif opt_params['option'] == "put": 59 | opt_price = ( 60 | (opt_params['K'] * np.exp(-opt_params['r'] * opt_params['T']) 61 | * params['minusNd2']) 62 | - (opt_params['S'] * params['carry'] * params['minusNd1'])) 63 | 64 | else: 65 | print("Please supply an option type, 'put' or 'call'") 66 | 67 | opt_price = np.nan_to_num(opt_price) 68 | 69 | return opt_price 70 | 71 | 72 | @staticmethod 73 | def delta( 74 | opt_params: dict, 75 | params: dict) -> float: 76 | """ 77 | Sensitivity of the option price to changes in asset price 78 | 79 | Parameters 80 | ---------- 81 | opt_params : Dict 82 | S : Float 83 | Underlying Stock Price. The default is 100. 84 | K : Float 85 | Strike Price. The default is 100. 86 | T : Float 87 | Time to Maturity. The default is 0.25 (3 months). 88 | r : Float 89 | Interest Rate. The default is 0.05 (5%). 90 | q : Float 91 | Dividend Yield. The default is 0. 92 | sigma : Float 93 | Implied Volatility. The default is 0.2 (20%). 94 | option : Str 95 | Option type, Put or Call. The default is 'call' 96 | params : Dict 97 | Dictionary of key parameters; used for refreshing distribution. 98 | 99 | Returns 100 | ------- 101 | Float 102 | Option Delta. 103 | 104 | """ 105 | 106 | # Update distribution parameters 107 | params = Utils.refresh_dist_params( 108 | opt_params=opt_params, params=params) 109 | 110 | if opt_params['option'] == 'call': 111 | opt_delta = params['carry'] * params['Nd1'] 112 | if opt_params['option'] == 'put': 113 | opt_delta = params['carry'] * (params['Nd1'] - 1) 114 | 115 | return opt_delta 116 | 117 | 118 | @staticmethod 119 | def theta( 120 | opt_params: dict, 121 | params: dict) -> float: 122 | """ 123 | Sensitivity of the option price to changes in time to maturity 124 | 125 | Parameters 126 | ---------- 127 | opt_params : Dict 128 | S : Float 129 | Underlying Stock Price. The default is 100. 130 | K : Float 131 | Strike Price. The default is 100. 132 | T : Float 133 | Time to Maturity. The default is 0.25 (3 months). 134 | r : Float 135 | Interest Rate. The default is 0.05 (5%). 136 | q : Float 137 | Dividend Yield. The default is 0. 138 | sigma : Float 139 | Implied Volatility. The default is 0.2 (20%). 140 | option : Str 141 | Option type, Put or Call. The default is 'call' 142 | params : Dict 143 | Dictionary of key parameters; used for refreshing distribution. 144 | 145 | Returns 146 | ------- 147 | Float 148 | Option Theta. 149 | 150 | """ 151 | 152 | # Update distribution parameters 153 | params = Utils.refresh_dist_params( 154 | opt_params=opt_params, params=params) 155 | 156 | if opt_params['option'] == 'call': 157 | opt_theta = ( 158 | ((-opt_params['S'] 159 | * params['carry'] 160 | * params['nd1'] 161 | * opt_params['sigma']) 162 | / (2 * np.sqrt(opt_params['T'])) 163 | - (params['b'] - opt_params['r']) 164 | * (opt_params['S'] 165 | * params['carry'] 166 | * params['Nd1']) 167 | - (opt_params['r'] * opt_params['K']) 168 | * np.exp(-opt_params['r'] * opt_params['T']) 169 | * params['Nd2']) 170 | / 100) 171 | 172 | if opt_params['option'] == 'put': 173 | opt_theta = ( 174 | ((-opt_params['S'] 175 | * params['carry'] 176 | * params['nd1'] 177 | * opt_params['sigma'] ) 178 | / (2 * np.sqrt(opt_params['T'])) 179 | + (params['b'] - opt_params['r']) 180 | * (opt_params['S'] 181 | * params['carry'] 182 | * params['minusNd1']) 183 | + (opt_params['r'] * opt_params['K']) 184 | * np.exp(-opt_params['r'] * opt_params['T']) 185 | * params['minusNd2']) 186 | / 100) 187 | 188 | return opt_theta 189 | 190 | 191 | @staticmethod 192 | def gamma( 193 | opt_params: dict, 194 | params: dict) -> float: 195 | """ 196 | Sensitivity of delta to changes in the underlying asset price 197 | 198 | Parameters 199 | ---------- 200 | opt_params : Dict 201 | S : Float 202 | Underlying Stock Price. The default is 100. 203 | K : Float 204 | Strike Price. The default is 100. 205 | T : Float 206 | Time to Maturity. The default is 0.25 (3 months). 207 | r : Float 208 | Interest Rate. The default is 0.05 (5%). 209 | q : Float 210 | Dividend Yield. The default is 0. 211 | sigma : Float 212 | Implied Volatility. The default is 0.2 (20%). 213 | option : Str 214 | Option type, Put or Call. The default is 'call' 215 | params : Dict 216 | Dictionary of key parameters; used for refreshing distribution. 217 | 218 | Returns 219 | ------- 220 | Float 221 | Option Gamma. 222 | 223 | """ 224 | 225 | # Update distribution parameters 226 | params = Utils.refresh_dist_params( 227 | opt_params=opt_params, params=params) 228 | 229 | opt_gamma = ((params['nd1'] * params['carry']) 230 | / (opt_params['S'] * opt_params['sigma'] 231 | * np.sqrt(opt_params['T']))) 232 | 233 | return opt_gamma 234 | 235 | 236 | @staticmethod 237 | def vega( 238 | opt_params: dict, 239 | params: dict) -> float: 240 | """ 241 | Sensitivity of the option price to changes in volatility 242 | 243 | Parameters 244 | ---------- 245 | opt_params : Dict 246 | S : Float 247 | Underlying Stock Price. The default is 100. 248 | K : Float 249 | Strike Price. The default is 100. 250 | T : Float 251 | Time to Maturity. The default is 0.25 (3 months). 252 | r : Float 253 | Interest Rate. The default is 0.05 (5%). 254 | q : Float 255 | Dividend Yield. The default is 0. 256 | sigma : Float 257 | Implied Volatility. The default is 0.2 (20%). 258 | option : Str 259 | Option type, Put or Call. The default is 'call' 260 | params : Dict 261 | Dictionary of key parameters; used for refreshing distribution. 262 | 263 | Returns 264 | ------- 265 | Float 266 | Option Vega. 267 | 268 | """ 269 | 270 | # Update distribution parameters 271 | params = Utils.refresh_dist_params( 272 | opt_params=opt_params, params=params) 273 | 274 | opt_vega = ((opt_params['S'] * params['carry'] 275 | * params['nd1'] * np.sqrt(opt_params['T'])) / 100) 276 | 277 | return opt_vega 278 | 279 | 280 | @staticmethod 281 | def rho( 282 | opt_params: dict, 283 | params: dict) -> float: 284 | """ 285 | Sensitivity of the option price to changes in the risk free rate 286 | 287 | Parameters 288 | ---------- 289 | opt_params : Dict 290 | S : Float 291 | Underlying Stock Price. The default is 100. 292 | K : Float 293 | Strike Price. The default is 100. 294 | T : Float 295 | Time to Maturity. The default is 0.25 (3 months). 296 | r : Float 297 | Interest Rate. The default is 0.05 (5%). 298 | q : Float 299 | Dividend Yield. The default is 0. 300 | sigma : Float 301 | Implied Volatility. The default is 0.2 (20%). 302 | option : Str 303 | Option type, Put or Call. The default is 'call' 304 | params : Dict 305 | Dictionary of key parameters; used for refreshing distribution. 306 | 307 | Returns 308 | ------- 309 | Float 310 | Option Rho. 311 | 312 | """ 313 | 314 | # Update distribution parameters 315 | params = Utils.refresh_dist_params( 316 | opt_params=opt_params, params=params) 317 | 318 | if opt_params['option'] == 'call': 319 | opt_rho = ( 320 | (opt_params['T'] * opt_params['K'] 321 | * np.exp(-opt_params['r'] * opt_params['T']) * params['Nd2']) 322 | / 10000) 323 | if opt_params['option'] == 'put': 324 | opt_rho = ( 325 | (-opt_params['T'] * opt_params['K'] 326 | * np.exp(-opt_params['r'] * opt_params['T']) 327 | * params['minusNd2']) 328 | / 10000) 329 | 330 | return opt_rho 331 | 332 | 333 | @staticmethod 334 | def vanna( 335 | opt_params: dict, 336 | params: dict) -> float: 337 | """ 338 | DdeltaDvol, DvegaDspot 339 | Sensitivity of delta to changes in volatility 340 | Sensitivity of vega to changes in the asset price 341 | 342 | Parameters 343 | ---------- 344 | opt_params : Dict 345 | S : Float 346 | Underlying Stock Price. The default is 100. 347 | K : Float 348 | Strike Price. The default is 100. 349 | T : Float 350 | Time to Maturity. The default is 0.25 (3 months). 351 | r : Float 352 | Interest Rate. The default is 0.05 (5%). 353 | q : Float 354 | Dividend Yield. The default is 0. 355 | sigma : Float 356 | Implied Volatility. The default is 0.2 (20%). 357 | option : Str 358 | Option type, Put or Call. The default is 'call' 359 | params : Dict 360 | Dictionary of key parameters; used for refreshing distribution. 361 | 362 | Returns 363 | ------- 364 | Float 365 | Option Vanna. 366 | 367 | """ 368 | 369 | # Update distribution parameters 370 | params = Utils.refresh_dist_params( 371 | opt_params=opt_params, params=params) 372 | 373 | opt_vanna = ( 374 | (((-params['carry'] * params['d2']) 375 | / opt_params['sigma']) * params['nd1']) / 100) 376 | 377 | return opt_vanna 378 | 379 | 380 | @classmethod 381 | def vomma( 382 | cls, 383 | opt_params: dict, 384 | params: dict) -> float: 385 | """ 386 | DvegaDvol, Vega Convexity, Volga, Vol Gamma 387 | Sensitivity of vega to changes in volatility 388 | 389 | Parameters 390 | ---------- 391 | opt_params : Dict 392 | S : Float 393 | Underlying Stock Price. The default is 100. 394 | K : Float 395 | Strike Price. The default is 100. 396 | T : Float 397 | Time to Maturity. The default is 0.25 (3 months). 398 | r : Float 399 | Interest Rate. The default is 0.05 (5%). 400 | q : Float 401 | Dividend Yield. The default is 0. 402 | sigma : Float 403 | Implied Volatility. The default is 0.2 (20%). 404 | option : Str 405 | Option type, Put or Call. The default is 'call' 406 | params : Dict 407 | Dictionary of key parameters; used for refreshing distribution. 408 | 409 | Returns 410 | ------- 411 | Float 412 | Option Vomma. 413 | 414 | """ 415 | 416 | # Update distribution parameters 417 | params = Utils.refresh_dist_params( 418 | opt_params=opt_params, params=params) 419 | 420 | opt_vomma = ( 421 | (cls.vega(opt_params, params) * ( 422 | (params['d1'] * params['d2']) / (opt_params['sigma']))) / 100) 423 | 424 | return opt_vomma 425 | 426 | 427 | @staticmethod 428 | def charm( 429 | opt_params: dict, 430 | params: dict) -> float: 431 | """ 432 | DdeltaDtime, Delta Bleed 433 | Sensitivity of delta to changes in time to maturity 434 | 435 | Parameters 436 | ---------- 437 | opt_params : Dict 438 | S : Float 439 | Underlying Stock Price. The default is 100. 440 | K : Float 441 | Strike Price. The default is 100. 442 | T : Float 443 | Time to Maturity. The default is 0.25 (3 months). 444 | r : Float 445 | Interest Rate. The default is 0.05 (5%). 446 | q : Float 447 | Dividend Yield. The default is 0. 448 | sigma : Float 449 | Implied Volatility. The default is 0.2 (20%). 450 | option : Str 451 | Option type, Put or Call. The default is 'call' 452 | params : Dict 453 | Dictionary of key parameters; used for refreshing distribution. 454 | 455 | Returns 456 | ------- 457 | Float 458 | Option Charm. 459 | 460 | """ 461 | 462 | # Update distribution parameters 463 | params = Utils.refresh_dist_params( 464 | opt_params=opt_params, params=params) 465 | 466 | if opt_params['option'] == 'call': 467 | opt_charm = ( 468 | (-params['carry'] * ((params['nd1'] * ( 469 | (params['b'] / (opt_params['sigma'] 470 | * np.sqrt(opt_params['T']))) 471 | - (params['d2'] / (2 * opt_params['T'])))) 472 | + ((params['b'] - opt_params['r']) * params['Nd1']))) 473 | / 100) 474 | if opt_params['option'] == 'put': 475 | opt_charm = ( 476 | (-params['carry'] * ( 477 | (params['nd1'] * ( 478 | (params['b'] 479 | / (opt_params['sigma'] * np.sqrt(opt_params['T']))) 480 | - (params['d2'] / (2 * opt_params['T'])))) 481 | - ((params['b'] - opt_params['r']) * params['minusNd1']))) 482 | / 100) 483 | 484 | return opt_charm 485 | 486 | 487 | @classmethod 488 | def zomma( 489 | cls, 490 | opt_params: dict, 491 | params: dict) -> float: 492 | """ 493 | DgammaDvol 494 | Sensitivity of gamma to changes in volatility 495 | 496 | Parameters 497 | ---------- 498 | opt_params : Dict 499 | S : Float 500 | Underlying Stock Price. The default is 100. 501 | K : Float 502 | Strike Price. The default is 100. 503 | T : Float 504 | Time to Maturity. The default is 0.25 (3 months). 505 | r : Float 506 | Interest Rate. The default is 0.05 (5%). 507 | q : Float 508 | Dividend Yield. The default is 0. 509 | sigma : Float 510 | Implied Volatility. The default is 0.2 (20%). 511 | option : Str 512 | Option type, Put or Call. The default is 'call' 513 | params : Dict 514 | Dictionary of key parameters; used for refreshing distribution. 515 | 516 | Returns 517 | ------- 518 | Float 519 | Option Zomma. 520 | 521 | """ 522 | 523 | # Update distribution parameters 524 | params = Utils.refresh_dist_params( 525 | opt_params=opt_params, params=params) 526 | 527 | opt_zomma = ( 528 | (cls.gamma(opt_params, params) * ( 529 | (params['d1'] * params['d2'] - 1) 530 | / opt_params['sigma'])) / 100) 531 | 532 | return opt_zomma 533 | 534 | 535 | @classmethod 536 | def speed( 537 | cls, 538 | opt_params: dict, 539 | params: dict) -> float: 540 | """ 541 | DgammaDspot 542 | Sensitivity of gamma to changes in asset price 543 | 3rd derivative of option price with respect to spot 544 | 545 | Parameters 546 | ---------- 547 | opt_params : Dict 548 | S : Float 549 | Underlying Stock Price. The default is 100. 550 | K : Float 551 | Strike Price. The default is 100. 552 | T : Float 553 | Time to Maturity. The default is 0.25 (3 months). 554 | r : Float 555 | Interest Rate. The default is 0.05 (5%). 556 | q : Float 557 | Dividend Yield. The default is 0. 558 | sigma : Float 559 | Implied Volatility. The default is 0.2 (20%). 560 | option : Str 561 | Option type, Put or Call. The default is 'call' 562 | params : Dict 563 | Dictionary of key parameters; used for refreshing distribution. 564 | 565 | Returns 566 | ------- 567 | Float 568 | Option Speed. 569 | 570 | """ 571 | 572 | # Update distribution parameters 573 | params = Utils.refresh_dist_params( 574 | opt_params=opt_params, params=params) 575 | 576 | opt_speed = -( 577 | cls.gamma(opt_params, params) * (1 + ( 578 | params['d1'] / (opt_params['sigma'] 579 | * np.sqrt(opt_params['T'])))) 580 | / opt_params['S']) 581 | 582 | return opt_speed 583 | 584 | 585 | @classmethod 586 | def color( 587 | cls, 588 | opt_params: dict, 589 | params: dict) -> float: 590 | """ 591 | DgammaDtime, Gamma Bleed, Gamma Theta 592 | Sensitivity of gamma to changes in time to maturity 593 | 594 | Parameters 595 | ---------- 596 | opt_params : Dict 597 | S : Float 598 | Underlying Stock Price. The default is 100. 599 | K : Float 600 | Strike Price. The default is 100. 601 | T : Float 602 | Time to Maturity. The default is 0.25 (3 months). 603 | r : Float 604 | Interest Rate. The default is 0.05 (5%). 605 | q : Float 606 | Dividend Yield. The default is 0. 607 | sigma : Float 608 | Implied Volatility. The default is 0.2 (20%). 609 | option : Str 610 | Option type, Put or Call. The default is 'call' 611 | params : Dict 612 | Dictionary of key parameters; used for refreshing distribution. 613 | 614 | Returns 615 | ------- 616 | Float 617 | Option Color. 618 | 619 | """ 620 | 621 | # Update distribution parameters 622 | params = Utils.refresh_dist_params( 623 | opt_params=opt_params, params=params) 624 | 625 | opt_color = ( 626 | (cls.gamma(opt_params, params) * ( 627 | (opt_params['r'] - params['b']) + ( 628 | (params['b'] * params['d1']) 629 | / (opt_params['sigma'] * np.sqrt(opt_params['T']))) 630 | + ((1 - params['d1'] * params['d2']) / (2 * opt_params['T'])))) 631 | / 100) 632 | 633 | return opt_color 634 | 635 | 636 | @classmethod 637 | def ultima( 638 | cls, 639 | opt_params: dict, 640 | params: dict) -> float: 641 | """ 642 | DvommaDvol 643 | Sensitivity of vomma to changes in volatility 644 | 3rd derivative of option price wrt volatility 645 | 646 | Parameters 647 | ---------- 648 | opt_params : Dict 649 | S : Float 650 | Underlying Stock Price. The default is 100. 651 | K : Float 652 | Strike Price. The default is 100. 653 | T : Float 654 | Time to Maturity. The default is 0.25 (3 months). 655 | r : Float 656 | Interest Rate. The default is 0.05 (5%). 657 | q : Float 658 | Dividend Yield. The default is 0. 659 | sigma : Float 660 | Implied Volatility. The default is 0.2 (20%). 661 | option : Str 662 | Option type, Put or Call. The default is 'call' 663 | params : Dict 664 | Dictionary of key parameters; used for refreshing distribution. 665 | 666 | Returns 667 | ------- 668 | Float 669 | Option Ultima. 670 | 671 | """ 672 | 673 | # Update distribution parameters 674 | params = Utils.refresh_dist_params( 675 | opt_params=opt_params, params=params) 676 | 677 | opt_ultima = ( 678 | (cls.vomma(opt_params, params) * ( 679 | (1 / opt_params['sigma']) * (params['d1'] * params['d2'] 680 | - (params['d1'] / params['d2']) 681 | - (params['d2'] / params['d1']) - 1))) 682 | / 100) 683 | 684 | return opt_ultima 685 | 686 | 687 | @classmethod 688 | def vega_bleed( 689 | cls, 690 | opt_params: dict, 691 | params: dict) -> float: 692 | """ 693 | DvegaDtime 694 | Sensitivity of vega to changes in time to maturity. 695 | 696 | Parameters 697 | ---------- 698 | opt_params : Dict 699 | S : Float 700 | Underlying Stock Price. The default is 100. 701 | K : Float 702 | Strike Price. The default is 100. 703 | T : Float 704 | Time to Maturity. The default is 0.25 (3 months). 705 | r : Float 706 | Interest Rate. The default is 0.05 (5%). 707 | q : Float 708 | Dividend Yield. The default is 0. 709 | sigma : Float 710 | Implied Volatility. The default is 0.2 (20%). 711 | option : Str 712 | Option type, Put or Call. The default is 'call' 713 | params : Dict 714 | Dictionary of key parameters; used for refreshing distribution. 715 | 716 | Returns 717 | ------- 718 | Float 719 | Option Vega Bleed. 720 | 721 | """ 722 | 723 | # Update distribution parameters 724 | params = Utils.refresh_dist_params( 725 | opt_params=opt_params, params=params) 726 | 727 | opt_vega_bleed = ( 728 | (cls.vega(opt_params, params) 729 | * (opt_params['r'] 730 | - params['b'] 731 | + ((params['b'] * params['d1']) 732 | / (opt_params['sigma'] * np.sqrt(opt_params['T']))) 733 | - ((1 + (params['d1'] * params['d2']) ) 734 | / (2 * opt_params['T'])))) 735 | / 100) 736 | 737 | return opt_vega_bleed 738 | 739 | 740 | @classmethod 741 | def return_options( 742 | cls, 743 | opt_dict: dict, 744 | params: dict) -> dict: 745 | 746 | """ 747 | Calculate option prices to be used in payoff diagrams. 748 | 749 | Parameters 750 | ---------- 751 | opt_dict : Dict 752 | Dictionary of option pricing parameters 753 | params : Dict 754 | Dictionary of key parameters 755 | 756 | Returns 757 | ------- 758 | From 1 to 4 sets of option values: 759 | Cx_0: Current option price; Float. 760 | Cx: Terminal Option payoff, varying by strike; Array 761 | Cx_G: Current option value, varying by strike; Array 762 | 763 | """ 764 | 765 | # Dictionary to store option legs 766 | option_legs = {} 767 | 768 | # create array of 1000 equally spaced points between 75% of 769 | # initial underlying price and 125% 770 | option_legs['SA'] = np.linspace( 771 | 0.75 * opt_dict['S'], 1.25 * opt_dict['S'], 1000) 772 | 773 | opt_params = { 774 | 'S':opt_dict['S'], 775 | 'K':opt_dict['K1'], 776 | 'T':opt_dict['T1'], 777 | 'r':opt_dict['r'], 778 | 'q':opt_dict['q'], 779 | 'sigma':opt_dict['sigma'], 780 | 'option':opt_dict['option1'], 781 | } 782 | 783 | # Calculate the current price of option 1 784 | option_legs['C1_0'] = cls.price(opt_params=opt_params, params=params) 785 | 786 | # Calculate the prices at maturity for the range of strikes 787 | # in SA of option 1 788 | change_params = {'S':option_legs['SA'], 'T':0} 789 | opt_params.update(change_params) 790 | 791 | option_legs['C1'] = cls.price(opt_params=opt_params, params=params) 792 | 793 | # Calculate the current prices for the range of strikes 794 | # in SA of option 1 795 | change_params = {'T':opt_dict['T1']} 796 | opt_params.update(change_params) 797 | 798 | option_legs['C1_G'] = cls.price(opt_params=opt_params, params=params) 799 | 800 | if opt_dict['legs'] > 1: 801 | # Calculate the current price of option 2 802 | change_params = {'S':opt_dict['S'], 803 | 'K':opt_dict['K2'], 804 | 'T':opt_dict['T2'], 805 | 'option':opt_dict['option2']} 806 | opt_params.update(change_params) 807 | 808 | option_legs['C2_0'] = cls.price( 809 | opt_params=opt_params, params=params) 810 | 811 | # Calculate the prices at maturity for the range of strikes 812 | # in SA of option 2 813 | change_params = {'S':option_legs['SA'], 'T':0} 814 | opt_params.update(change_params) 815 | 816 | option_legs['C2'] = cls.price(opt_params=opt_params, params=params) 817 | 818 | # Calculate the current prices for the range of strikes 819 | # in SA of option 2 820 | change_params = {'T':opt_dict['T2']} 821 | opt_params.update(change_params) 822 | 823 | option_legs['C2_G'] = cls.price( 824 | opt_params=opt_params, params=params) 825 | 826 | if opt_dict['legs'] > 2: 827 | # Calculate the current price of option 3 828 | change_params = {'S':opt_dict['S'], 829 | 'K':opt_dict['K3'], 830 | 'T':opt_dict['T3'], 831 | 'option':opt_dict['option3']} 832 | opt_params.update(change_params) 833 | 834 | option_legs['C3_0'] = cls.price( 835 | opt_params=opt_params, params=params) 836 | 837 | # Calculate the prices at maturity for the range of strikes 838 | # in SA of option 3 839 | change_params = {'S':option_legs['SA'], 'T':0} 840 | opt_params.update(change_params) 841 | 842 | option_legs['C3'] = cls.price(opt_params=opt_params, params=params) 843 | 844 | # Calculate the current prices for the range of strikes 845 | # in SA of option 3 846 | change_params = {'T':opt_dict['T3']} 847 | opt_params.update(change_params) 848 | 849 | option_legs['C3_G'] = cls.price( 850 | opt_params=opt_params, params=params) 851 | 852 | if opt_dict['legs'] > 3: 853 | # Calculate the current price of option 4 854 | change_params = {'S':opt_dict['S'], 855 | 'K':opt_dict['K4'], 856 | 'T':opt_dict['T4'], 857 | 'option':opt_dict['option4']} 858 | opt_params.update(change_params) 859 | 860 | option_legs['C4_0'] = cls.price( 861 | opt_params=opt_params, params=params) 862 | 863 | # Calculate the prices at maturity for the range of strikes 864 | # in SA of option 4 865 | change_params = {'S':option_legs['SA'], 'T':0} 866 | opt_params.update(change_params) 867 | 868 | option_legs['C4'] = cls.price( 869 | opt_params=opt_params, params=params) 870 | 871 | # Calculate the current prices for the range of strikes 872 | # in SA of option 4 873 | change_params = {'T':opt_dict['T4']} 874 | opt_params.update(change_params) 875 | 876 | option_legs['C4_G'] = cls.price( 877 | opt_params=opt_params, params=params) 878 | 879 | return option_legs 880 | -------------------------------------------------------------------------------- /optionvisualizer/sensitivities.py: -------------------------------------------------------------------------------- 1 | """ 2 | Calculate Analytical or Numerical sensitivities 3 | 4 | """ 5 | 6 | from optionvisualizer.option_formulas import Option 7 | # pylint: disable=invalid-name 8 | 9 | class Sens(): 10 | """ 11 | Summary functions for calculating sensitivities 12 | 13 | """ 14 | @classmethod 15 | def sensitivities_static( 16 | cls, 17 | params: dict, 18 | **kwargs) -> float | None: 19 | """ 20 | Sensitivities of the option. 21 | 22 | Parameters 23 | ---------- 24 | S : Float 25 | Underlying Stock Price. The default is 100. 26 | K : Float 27 | Strike Price. The default is 100. 28 | T : Float 29 | Time to Maturity. The default is 0.25 (3 months). 30 | r : Float 31 | Interest Rate. The default is 0.05 (5%). 32 | q : Float 33 | Dividend Yield. The default is 0. 34 | sigma : Float 35 | Implied Volatility. The default is 0.2 (20%). 36 | option : Str 37 | Option type, Put or Call. The default is 'call' 38 | greek : Str 39 | Sensitivity to return. Select from 'delta', 'gamma', 'vega', 40 | 'theta', 'rho', 'vomma', 'vanna', 'zomma', 'speed', 'color', 41 | 'ultima', 'vega bleed', 'charm'. The default is 'delta' 42 | price_shift : Float 43 | The size of the price shift in decimal terms. The default 44 | is 0.25. 45 | vol_shift : Float 46 | The size of the volatility shift in decimal terms. The 47 | default is 0.001. 48 | ttm_shift : Float 49 | The size of the time to maturity shift in decimal terms. The 50 | default is 1/365. 51 | rate_shift : Float 52 | The size of the interest rate shift in decimal terms. The 53 | default is 0.0001. 54 | num_sens : Bool 55 | Whether to calculate numerical or analytical sensitivity. 56 | The default is False. 57 | 58 | Returns 59 | ------- 60 | Float 61 | Option Sensitivity. 62 | 63 | """ 64 | 65 | opt_params = {} 66 | op_keys = ['S','K', 'T', 'r', 'q', 'sigma', 'option'] 67 | 68 | # Update params with the specified parameters 69 | for key, value in kwargs.items(): 70 | 71 | if key not in ['params']: 72 | if key in op_keys: 73 | # Add to the option parameter dictionary 74 | opt_params[key] = value 75 | # Replace the default parameter with that provided 76 | else: 77 | params[key] = value 78 | 79 | for parameter in op_keys: 80 | if parameter not in opt_params: 81 | opt_params[parameter] = params[parameter] 82 | 83 | if params['num_sens']: 84 | return cls.numerical_sensitivities( 85 | opt_params=opt_params, params=params) 86 | 87 | return cls.analytical_sensitivities( 88 | opt_params=opt_params, params=params) 89 | 90 | 91 | @staticmethod 92 | def analytical_sensitivities( 93 | opt_params: dict, 94 | params: dict) -> float | None: 95 | """ 96 | Sensitivities of the option calculated analytically from closed 97 | form solutions. 98 | 99 | Parameters 100 | ---------- 101 | S : Float 102 | Underlying Stock Price. The default is 100. 103 | K : Float 104 | Strike Price. The default is 100. 105 | T : Float 106 | Time to Maturity. The default is 0.25 (3 months). 107 | r : Float 108 | Interest Rate. The default is 0.05 (5%). 109 | q : Float 110 | Dividend Yield. The default is 0. 111 | sigma : Float 112 | Implied Volatility. The default is 0.2 (20%). 113 | option : Str 114 | Option type, Put or Call. The default is 'call' 115 | greek : Str 116 | Sensitivity to return. Select from 'delta', 'gamma', 'vega', 117 | 'theta', 'rho', 'vomma', 'vanna', 'zomma', 'speed', 'color', 118 | 'ultima', 'vega bleed', 'charm'. The default is 'delta' 119 | 120 | Returns 121 | ------- 122 | Float 123 | Option Sensitivity. 124 | 125 | """ 126 | 127 | for key, value in params['greek_dict'].items(): 128 | if str(params['greek']) == key: 129 | return getattr(Option, value)( 130 | opt_params=opt_params, params=params) 131 | 132 | return print("Please enter a valid Greek") 133 | 134 | 135 | @classmethod 136 | def numerical_sensitivities( 137 | cls, 138 | opt_params: dict, 139 | params: dict) -> float | None: 140 | """ 141 | Sensitivities of the option calculated numerically using shifts 142 | in parameters. 143 | 144 | Parameters 145 | ---------- 146 | S : Float 147 | Underlying Stock Price. The default is 100. 148 | K : Float 149 | Strike Price. The default is 100. 150 | T : Float 151 | Time to Maturity. The default is 0.25 (3 months). 152 | r : Float 153 | Interest Rate. The default is 0.05 (5%). 154 | q : Float 155 | Dividend Yield. The default is 0. 156 | sigma : Float 157 | Implied Volatility. The default is 0.2 (20%). 158 | option : Str 159 | Option type, Put or Call. The default is 'call' 160 | greek : Str 161 | Sensitivity to return. Select from 'delta', 'gamma', 'vega', 162 | 'theta', 'rho', 'vomma', 'vanna', 'zomma', 'speed', 'color', 163 | 'ultima', 'vega bleed', 'charm'. The default is 'delta' 164 | price_shift : Float 165 | The size of the price shift in decimal terms. The default 166 | is 0.25. 167 | vol_shift : Float 168 | The size of the volatility shift in decimal terms. The 169 | default is 0.001. 170 | ttm_shift : Float 171 | The size of the time to maturity shift in decimal terms. The 172 | default is 1/365. 173 | rate_shift : Float 174 | The size of the interest rate shift in decimal terms. The 175 | default is 0.0001. 176 | 177 | Returns 178 | ------- 179 | Float 180 | Option Sensitivity. 181 | 182 | """ 183 | 184 | for key, value in params['greek_dict'].items(): 185 | if key == params['greek']: 186 | return getattr(cls, '_num_'+value)( 187 | opt_params=opt_params, params=params) 188 | 189 | return print("Please enter a valid Greek") 190 | 191 | 192 | @classmethod 193 | def _num_price( 194 | cls, 195 | opt_params: dict, 196 | params: dict) -> float: 197 | option_names = [] 198 | opt_dict = cls._option_prices( 199 | opt_params=opt_params, params=params, option_names=option_names) 200 | return opt_dict['opt'] 201 | 202 | 203 | @classmethod 204 | def _num_delta( 205 | cls, 206 | opt_params: dict, 207 | params: dict) -> float: 208 | option_names = ['price_up', 'price_down'] 209 | opt_dict = cls._option_prices( 210 | opt_params=opt_params, params=params, option_names=option_names) 211 | return ((opt_dict['opt_price_up'] - opt_dict['opt_price_down']) 212 | / (2 * params['price_shift'])) 213 | 214 | 215 | @classmethod 216 | def _num_gamma( 217 | cls, 218 | opt_params: dict, 219 | params: dict) -> float: 220 | option_names = ['price_up', 'price_down'] 221 | opt_dict = cls._option_prices( 222 | opt_params=opt_params, params=params, option_names=option_names) 223 | return ((opt_dict['opt_price_up'] 224 | - (2 * opt_dict['opt']) 225 | + opt_dict['opt_price_down']) 226 | / (params['price_shift'] ** 2)) 227 | 228 | 229 | @classmethod 230 | def _num_vega( 231 | cls, 232 | opt_params: dict, 233 | params: dict) -> float: 234 | option_names = ['vol_up', 'vol_down'] 235 | opt_dict = cls._option_prices( 236 | opt_params=opt_params, params=params, option_names=option_names) 237 | return (((opt_dict['opt_vol_up'] - opt_dict['opt_vol_down']) 238 | / (2 * params['vol_shift'])) / 100) 239 | 240 | 241 | @classmethod 242 | def _num_theta( 243 | cls, 244 | opt_params: dict, 245 | params: dict) -> float: 246 | option_names = ['ttm_down'] 247 | opt_dict = cls._option_prices( 248 | opt_params=opt_params, params=params, option_names=option_names) 249 | return ((opt_dict['opt_ttm_down'] - opt_dict['opt']) 250 | / (params['ttm_shift'] * 100)) 251 | 252 | 253 | @classmethod 254 | def _num_rho( 255 | cls, 256 | opt_params: dict, 257 | params: dict) -> float: 258 | option_names = ['rate_up', 'rate_down'] 259 | opt_dict = cls._option_prices( 260 | opt_params=opt_params, params=params, option_names=option_names) 261 | return ((opt_dict['opt_rate_up'] - opt_dict['opt_rate_down']) 262 | / (2 * params['rate_shift'] * 10000)) 263 | 264 | 265 | @classmethod 266 | def _num_vomma( 267 | cls, 268 | opt_params: dict, 269 | params: dict) -> float: 270 | option_names = ['vol_up', 'vol_down'] 271 | opt_dict = cls._option_prices( 272 | opt_params=opt_params, params=params, option_names=option_names) 273 | return (((opt_dict['opt_vol_up'] 274 | - (2 * opt_dict['opt']) 275 | + opt_dict['opt_vol_down']) 276 | / (params['vol_shift'] ** 2)) / 10000) 277 | 278 | 279 | @classmethod 280 | def _num_vanna( 281 | cls, 282 | opt_params: dict, 283 | params: dict) -> float: 284 | option_names = ['price_up_vol_up', 'price_up_vol_down', 285 | 'price_down_vol_up', 'price_down_vol_down'] 286 | opt_dict = cls._option_prices( 287 | opt_params=opt_params, params=params, option_names=option_names) 288 | return (((1 / (4 * params['price_shift'] * params['vol_shift'])) 289 | * (opt_dict['opt_price_up_vol_up'] 290 | - opt_dict['opt_price_up_vol_down'] 291 | - opt_dict['opt_price_down_vol_up'] 292 | + opt_dict['opt_price_down_vol_down'])) / 100) 293 | 294 | 295 | @classmethod 296 | def _num_charm( 297 | cls, 298 | opt_params: dict, 299 | params: dict) -> float: 300 | option_names = ['price_up', 'price_down', 301 | 'price_up_ttm_down', 'price_down_ttm_down'] 302 | opt_dict = cls._option_prices( 303 | opt_params=opt_params, params=params, option_names=option_names) 304 | return ((((opt_dict['opt_price_up_ttm_down'] 305 | - opt_dict['opt_price_down_ttm_down']) 306 | / (2 * params['price_shift'])) 307 | - ((opt_dict['opt_price_up'] 308 | - opt_dict['opt_price_down']) 309 | / (2 * params['price_shift']))) 310 | / (params['ttm_shift'] * 100)) 311 | 312 | 313 | @classmethod 314 | def _num_zomma( 315 | cls, 316 | opt_params: dict, 317 | params: dict) -> float: 318 | option_names = ['vol_up', 'vol_down', 'price_up_vol_up', 319 | 'price_up_vol_down', 'price_down_vol_up', 320 | 'price_down_vol_down'] 321 | opt_dict = cls._option_prices( 322 | opt_params=opt_params, params=params, option_names=option_names) 323 | return (((opt_dict['opt_price_up_vol_up'] 324 | - (2 * opt_dict['opt_vol_up']) 325 | + opt_dict['opt_price_down_vol_up']) 326 | - opt_dict['opt_price_up_vol_down'] 327 | + (2 * opt_dict['opt_vol_down']) 328 | - opt_dict['opt_price_down_vol_down']) 329 | / (2 * params['vol_shift'] * (params['price_shift'] ** 2)) 330 | / 100) 331 | 332 | 333 | @classmethod 334 | def _num_speed( 335 | cls, 336 | opt_params: dict, 337 | params: dict) -> float: 338 | option_names = ['price_up', 'price_down', 'double_price_up'] 339 | opt_dict = cls._option_prices( 340 | opt_params=opt_params, params=params, option_names=option_names) 341 | return (1 / (params['price_shift'] ** 3) 342 | * (opt_dict['opt_double_price_up'] 343 | - (3 * opt_dict['opt_price_up']) 344 | + 3 * opt_dict['opt'] 345 | - opt_dict['opt_price_down'])) 346 | 347 | 348 | @classmethod 349 | def _num_color( 350 | cls, 351 | opt_params: dict, 352 | params: dict) -> float: 353 | option_names = ['price_up', 'price_down', 'price_up_ttm_down', 354 | 'price_down_ttm_down', 'ttm_down'] 355 | opt_dict = cls._option_prices( 356 | opt_params=opt_params, params=params, option_names=option_names) 357 | return ((((opt_dict['opt_price_up_ttm_down'] 358 | - (2 * opt_dict['opt_ttm_down']) 359 | + opt_dict['opt_price_down_ttm_down']) 360 | / (params['price_shift'] ** 2)) 361 | - ((opt_dict['opt_price_up'] 362 | - (2 * opt_dict['opt']) 363 | + opt_dict['opt_price_down']) 364 | / (params['price_shift'] ** 2) )) 365 | / (params['ttm_shift'] * 100)) 366 | 367 | 368 | @classmethod 369 | def _num_ultima( 370 | cls, 371 | opt_params: dict, 372 | params: dict) -> float: 373 | option_names = ['vol_up', 'vol_down', 'double_vol_up'] 374 | opt_dict = cls._option_prices( 375 | opt_params=opt_params, params=params, option_names=option_names) 376 | return ((1 / (params['vol_shift'] ** 3) 377 | * (opt_dict['opt_double_vol_up'] 378 | - (3 * opt_dict['opt_vol_up']) 379 | + 3 * opt_dict['opt'] 380 | - opt_dict['opt_vol_down'])) 381 | * (params['vol_shift'] ** 2)) 382 | 383 | 384 | @classmethod 385 | def _num_vega_bleed( 386 | cls, 387 | opt_params: dict, 388 | params: dict) -> float: 389 | option_names = ['vol_up', 'vol_down', 'vol_up_ttm_down', 390 | 'vol_down_ttm_down'] 391 | opt_dict = cls._option_prices( 392 | opt_params=opt_params, params=params, option_names=option_names) 393 | return ((((opt_dict['opt_vol_up_ttm_down'] 394 | - opt_dict['opt_vol_down_ttm_down']) 395 | / (2 * params['vol_shift'])) 396 | - ((opt_dict['opt_vol_up'] 397 | - opt_dict['opt_vol_down']) 398 | / (2 * params['vol_shift']))) 399 | / (params['ttm_shift'] * 10000)) 400 | 401 | 402 | @classmethod 403 | def _option_prices( 404 | cls, 405 | opt_params: dict, 406 | params: dict, 407 | option_names: list) -> dict: 408 | 409 | if params['greek'] in params['equal_greeks']: 410 | opt_params['option'] = 'call' 411 | 412 | shift_dict = cls._parameter_shifts( 413 | opt_params=opt_params, params=params) 414 | 415 | opt_dict = {} 416 | opt_dict['opt'] = Option.price(opt_params=opt_params, params=params) 417 | 418 | for option_name, option_params in shift_dict.items(): 419 | if option_name in option_names: 420 | opt_dict['opt_'+option_name] = Option.price( 421 | opt_params=option_params, params=params) 422 | 423 | return opt_dict 424 | 425 | 426 | @staticmethod 427 | def _parameter_shifts( 428 | opt_params: dict, 429 | params: dict) -> dict: 430 | 431 | shift_dict = {} 432 | shift_inputs = {'price':'S', 433 | 'vol':'sigma', 434 | 'ttm':'T', 435 | 'rate':'r'} 436 | 437 | for shift, shift_param in shift_inputs.items(): 438 | shift_dict[shift+'_up'] = { 439 | **opt_params, shift_param:( 440 | opt_params[shift_param] + params[shift+'_shift'])} 441 | shift_dict[shift+'_down'] = { 442 | **opt_params, shift_param:( 443 | opt_params[shift_param] - params[shift+'_shift'])} 444 | 445 | shift_dict['price_up_vol_up'] ={ 446 | **opt_params, 447 | 'S':(opt_params['S'] + params['price_shift']), 448 | 'sigma':(opt_params['sigma'] + params['vol_shift'])} 449 | 450 | shift_dict['price_down_vol_up'] ={ 451 | **opt_params, 452 | 'S':(opt_params['S'] - params['price_shift']), 453 | 'sigma':(opt_params['sigma'] + params['vol_shift'])} 454 | 455 | shift_dict['price_up_vol_down'] ={ 456 | **opt_params, 457 | 'S':(opt_params['S'] + params['price_shift']), 458 | 'sigma':(opt_params['sigma'] - params['vol_shift'])} 459 | 460 | shift_dict['price_down_vol_down'] ={ 461 | **opt_params, 462 | 'S':(opt_params['S'] - params['price_shift']), 463 | 'sigma':(opt_params['sigma'] - params['vol_shift'])} 464 | 465 | shift_dict['price_up_ttm_down'] ={ 466 | **opt_params, 467 | 'S':(opt_params['S'] + params['price_shift']), 468 | 'T':(opt_params['T'] - params['ttm_shift'])} 469 | 470 | shift_dict['price_down_ttm_down'] ={ 471 | **opt_params, 472 | 'S':(opt_params['S'] - params['price_shift']), 473 | 'T':(opt_params['T'] - params['ttm_shift'])} 474 | 475 | shift_dict['vol_up_ttm_down'] ={ 476 | **opt_params, 477 | 'sigma':(opt_params['sigma'] + params['vol_shift']), 478 | 'T':(opt_params['T'] - params['ttm_shift'])} 479 | 480 | shift_dict['vol_down_ttm_down'] ={ 481 | **opt_params, 482 | 'sigma':(opt_params['sigma'] - params['vol_shift']), 483 | 'T':(opt_params['T'] - params['ttm_shift'])} 484 | 485 | shift_dict['double_price_up'] = { 486 | **opt_params, 487 | 'S':(opt_params['S'] + 2 * params['price_shift'])} 488 | 489 | shift_dict['double_vol_up'] = { 490 | **opt_params, 491 | 'sigma':(opt_params['sigma'] + 2 * params['vol_shift'])} 492 | 493 | return shift_dict 494 | 495 | 496 | @staticmethod 497 | def option_data_static( 498 | params: dict, 499 | option_value: str, 500 | **kwargs) -> float | None: 501 | """ 502 | Calculate Option prices or Greeks 503 | 504 | Parameters 505 | ---------- 506 | option_value : str 507 | The value to return; price or specified greek 508 | params : Dict 509 | S : Float 510 | Underlying Stock Price. The default is 100. 511 | K : Float 512 | Strike Price. The default is 100. 513 | T : Float 514 | Time to Maturity. The default is 0.25 (3 months). 515 | r : Float 516 | Interest Rate. The default is 0.05 (5%). 517 | q : Float 518 | Dividend Yield. The default is 0. 519 | sigma : Float 520 | Implied Volatility. The default is 0.2 (20%). 521 | option : Str 522 | Option type, Put or Call. The default is 'call' 523 | 524 | 525 | Returns 526 | ------- 527 | Price / Greek. 528 | 529 | """ 530 | opt_params ={} 531 | 532 | # Update params with the specified parameters 533 | for key, value in kwargs.items(): 534 | if key not in ['params']: 535 | if key in ['S','K', 'T', 'r', 'q', 'sigma', 'option']: 536 | # Add to the option parameter dictionary 537 | opt_params[key] = value 538 | # Replace the default parameter with that provided 539 | else: 540 | params[key] = value 541 | 542 | try: 543 | # Select the chosen option value from the available functions 544 | function = params['greek_dict'][option_value] 545 | output = getattr(Option, function)( 546 | opt_params=opt_params, params=params) 547 | return output 548 | 549 | except KeyError: 550 | return print("Please enter a valid function from 'price', "\ 551 | "'delta', 'gamma', 'vega', 'theta', 'rho', 'vomma', "\ 552 | "'vanna', 'zomma', 'speed', 'color', 'ultima', "\ 553 | "'vega bleed', 'charm'") 554 | -------------------------------------------------------------------------------- /optionvisualizer/utilities.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for refreshing parameters 3 | 4 | """ 5 | 6 | import copy 7 | import numpy as np 8 | import scipy.stats as si 9 | from optionvisualizer.visualizer_params import vis_params_dict 10 | # pylint: disable=invalid-name 11 | 12 | class Utils(): 13 | """ 14 | Utility functions for refreshing parameters 15 | 16 | """ 17 | @staticmethod 18 | def init_params(inputs: dict) -> dict: 19 | """ 20 | Initialise parameter dictionary 21 | Parameters 22 | ---------- 23 | inputs : Dict 24 | Dictionary of parameters supplied to the function. 25 | Returns 26 | ------- 27 | params : Dict 28 | Dictionary of parameters. 29 | """ 30 | # Copy the default parameters 31 | params = copy.deepcopy(vis_params_dict) 32 | 33 | # For all the supplied arguments 34 | for key, value in inputs.items(): 35 | 36 | # Replace the default parameter with that provided 37 | params[key] = value 38 | 39 | return params 40 | 41 | 42 | @staticmethod 43 | def refresh_dist_params( 44 | opt_params: dict, 45 | params: dict) -> dict: 46 | """ 47 | Calculate various parameters and distributions 48 | 49 | Returns 50 | ------- 51 | Various 52 | Assigns parameters to the object 53 | 54 | """ 55 | 56 | # Cost of carry as risk free rate less dividend yield 57 | params['b'] = opt_params['r'] - opt_params['q'] 58 | 59 | params['carry'] = np.exp( 60 | (params['b'] - opt_params['r']) * opt_params['T']) 61 | params['discount'] = np.exp(-opt_params['r'] * opt_params['T']) 62 | 63 | with np.errstate(divide='ignore'): 64 | params['d1'] = ( 65 | (np.log(opt_params['S'] / opt_params['K']) 66 | + (params['b'] + (0.5 * opt_params['sigma'] ** 2)) 67 | * opt_params['T']) 68 | / (opt_params['sigma'] * np.sqrt(opt_params['T']))) 69 | 70 | params['d2'] = ( 71 | (np.log(opt_params['S'] / opt_params['K']) 72 | + (params['b'] - (0.5 * opt_params['sigma'] ** 2)) 73 | * opt_params['T']) 74 | / (opt_params['sigma'] * np.sqrt(opt_params['T']))) 75 | 76 | # standardised normal density function 77 | params['nd1'] = ( 78 | (1 / np.sqrt(2 * np.pi)) * (np.exp(-params['d1'] ** 2 * 0.5))) 79 | 80 | # Cumulative normal distribution function 81 | params['Nd1'] = si.norm.cdf(params['d1'], 0.0, 1.0) 82 | params['minusNd1'] = si.norm.cdf(-params['d1'], 0.0, 1.0) 83 | params['Nd2'] = si.norm.cdf(params['d2'], 0.0, 1.0) 84 | params['minusNd2'] = si.norm.cdf(-params['d2'], 0.0, 1.0) 85 | 86 | return params 87 | 88 | 89 | @staticmethod 90 | def refresh_combo_params( 91 | params: dict, 92 | inputs: dict) -> dict: 93 | """ 94 | Set parameters for use in various pricing functions 95 | 96 | Parameters 97 | ---------- 98 | **kwargs : Various 99 | Takes any of the arguments of the various methods 100 | that use it to refresh data. 101 | 102 | Returns 103 | ------- 104 | Various 105 | Runs methods to fix input parameters and reset defaults 106 | if no data provided 107 | 108 | """ 109 | default_values = copy.deepcopy(vis_params_dict) 110 | 111 | # Certain combo payoffs (found in the mod_payoffs list) require 112 | # specific default parameters 113 | if params['combo_payoff'] in params['mod_payoffs']: 114 | 115 | # For each parameter in the combo parameters dictionary 116 | # corresponding to this combo payoff 117 | for parameter in params[ 118 | 'combo_parameters'][params['combo_payoff']]: 119 | 120 | # If the parameter has been supplied as an input 121 | if parameter in inputs.keys(): 122 | 123 | # Set this value in the parameter dictionary 124 | params[parameter] = inputs[parameter] 125 | 126 | # Otherwise if the parameter is in the mod_params list 127 | elif parameter in params['mod_params']: 128 | 129 | # Try to extract this from the combo dict default 130 | try: 131 | params[parameter] = params['combo_dict'][str( 132 | params['combo_payoff'])][str(parameter)] 133 | 134 | # Otherwise 135 | except KeyError: 136 | # Set to the standard default value 137 | params[parameter] = default_values[str(parameter)] 138 | # Otherwise 139 | else: 140 | # Set to the standard default value 141 | params[parameter] = default_values[str(parameter)] 142 | 143 | # For all the other combo_payoffs 144 | else: 145 | # For each parameter in the combo parameters dictionary 146 | # corresponding to this combo payoff 147 | for parameter in params[ 148 | 'combo_parameters'][params['combo_payoff']]: 149 | 150 | # If the parameter has been supplied as an input 151 | if parameter in inputs.keys(): 152 | 153 | # Set this value in the parameter dictionary 154 | params[parameter] = inputs[parameter] 155 | 156 | # Otherwise 157 | else: 158 | # Set to the standard default value 159 | params[parameter] = default_values[str(parameter)] 160 | 161 | return params 162 | 163 | 164 | @staticmethod 165 | def barrier_factors(params: dict) -> tuple[dict, dict]: 166 | """ 167 | Calculate the barrier option specific parameters 168 | 169 | Returns 170 | ------- 171 | Various 172 | Assigns parameters to the object 173 | 174 | """ 175 | 176 | # Cost of carry as risk free rate less dividend yield 177 | params['b'] = params['r'] - params['q'] 178 | 179 | barrier_factors = {} 180 | 181 | barrier_factors['mu'] = (( 182 | params['b'] - ((params['sigma'] ** 2) / 2)) 183 | / (params['sigma'] ** 2)) 184 | 185 | barrier_factors['lambda'] = ( 186 | np.sqrt(barrier_factors['mu'] ** 2 + ( 187 | (2 * params['r']) / params['sigma'] ** 2))) 188 | 189 | barrier_factors['z'] = ( 190 | (np.log(params['H'] / params['S']) 191 | / (params['sigma'] * np.sqrt(params['T']))) 192 | + (barrier_factors['lambda'] 193 | * params['sigma'] 194 | * np.sqrt(params['T']))) 195 | 196 | barrier_factors['x1'] = ( 197 | np.log(params['S'] / params['K']) 198 | / (params['sigma'] * np.sqrt(params['T'])) 199 | + ((1 + barrier_factors['mu']) 200 | * params['sigma'] 201 | * np.sqrt(params['T']))) 202 | 203 | barrier_factors['x2'] = ( 204 | np.log(params['S'] / params['H']) 205 | / (params['sigma'] 206 | * np.sqrt(params['T'])) 207 | + ((1 + barrier_factors['mu']) 208 | * params['sigma'] 209 | * np.sqrt(params['T']))) 210 | 211 | barrier_factors['y1'] = ( 212 | np.log((params['H'] ** 2) 213 | / (params['S'] 214 | * params['K'])) 215 | / (params['sigma'] 216 | * np.sqrt(params['T'])) 217 | + ((1 + barrier_factors['mu']) 218 | * params['sigma'] 219 | * np.sqrt(params['T']))) 220 | 221 | barrier_factors['y2'] = ( 222 | np.log(params['H'] / params['S']) 223 | / (params['sigma'] 224 | * np.sqrt(params['T'])) 225 | + ((1 + barrier_factors['mu']) 226 | * params['sigma'] 227 | * np.sqrt(params['T']))) 228 | 229 | params['carry'] = np.exp((params['b'] - params['r']) * params['T']) 230 | 231 | barrier_factors['A'] = ( 232 | (params['phi'] 233 | * params['S'] 234 | * params['carry'] 235 | * si.norm.cdf((params['phi'] 236 | * barrier_factors['x1']), 0.0, 1.0)) 237 | - (params['phi'] 238 | * params['K'] 239 | * np.exp(-params['r'] 240 | * params['T']) 241 | * si.norm.cdf( 242 | ((params['phi'] * barrier_factors['x1']) 243 | - (params['phi'] * params['sigma'] 244 | * np.sqrt(params['T']))), 0.0, 1.0))) 245 | 246 | 247 | barrier_factors['B'] = ( 248 | (params['phi'] 249 | * params['S'] 250 | * params['carry'] 251 | * si.norm.cdf((params['phi'] 252 | * barrier_factors['x2']), 0.0, 1.0)) 253 | - (params['phi'] 254 | * params['K'] 255 | * np.exp(-params['r'] 256 | * params['T']) 257 | * si.norm.cdf( 258 | ((params['phi'] * barrier_factors['x2']) 259 | - (params['phi'] * params['sigma'] 260 | * np.sqrt(params['T']))), 0.0, 1.0))) 261 | 262 | barrier_factors['C'] = ( 263 | (params['phi'] 264 | * params['S'] 265 | * params['carry'] 266 | * ((params['H'] / params['S']) 267 | ** (2 * (barrier_factors['mu'] + 1))) 268 | * si.norm.cdf((params['eta'] 269 | * barrier_factors['y1']), 0.0, 1.0)) 270 | - (params['phi'] 271 | * params['K'] 272 | * np.exp(-params['r'] 273 | * params['T']) 274 | * ((params['H'] / params['S']) 275 | ** (2 * barrier_factors['mu'])) 276 | * si.norm.cdf( 277 | ((params['eta'] * barrier_factors['y1']) 278 | - (params['eta'] * params['sigma'] 279 | * np.sqrt(params['T']))), 0.0, 1.0))) 280 | 281 | barrier_factors['D'] = ( 282 | (params['phi'] * params['S'] * params['carry'] 283 | * ((params['H'] / params['S']) 284 | ** (2 * (barrier_factors['mu'] + 1))) 285 | * si.norm.cdf((params['eta'] 286 | * barrier_factors['y2']), 0.0, 1.0)) 287 | - (params['phi'] 288 | * params['K'] 289 | * np.exp(-params['r'] 290 | * params['T']) 291 | * ((params['H'] / params['S']) 292 | ** (2 * barrier_factors['mu'])) 293 | * si.norm.cdf( 294 | ((params['eta'] * barrier_factors['y2']) 295 | - (params['eta'] * params['sigma'] 296 | * np.sqrt(params['T']))), 0.0, 1.0))) 297 | 298 | barrier_factors['E'] = ( 299 | (params['R'] * np.exp(-params['r'] * params['T'])) 300 | * (si.norm.cdf( 301 | ((params['eta'] * barrier_factors['x2']) 302 | - (params['eta'] 303 | * params['sigma'] 304 | * np.sqrt(params['T']))), 0.0, 1.0) 305 | - (((params['H'] / params['S']) 306 | ** (2 * barrier_factors['mu'])) 307 | * si.norm.cdf( 308 | ((params['eta'] * barrier_factors['y2']) 309 | - (params['eta'] * params['sigma'] 310 | * np.sqrt(params['T']))), 0.0, 1.0)))) 311 | 312 | barrier_factors['F'] = ( 313 | params['R'] * (((params['H'] / params['S']) 314 | ** (barrier_factors['mu'] 315 | + barrier_factors['lambda'])) 316 | * (si.norm.cdf((params['eta'] 317 | * barrier_factors['z']), 0.0, 1.0)) 318 | + (((params['H'] / params['S']) 319 | ** (barrier_factors['mu'] 320 | - barrier_factors['lambda'])) 321 | * si.norm.cdf( 322 | ((params['eta'] * barrier_factors['z']) 323 | - (2 * params['eta'] 324 | * barrier_factors['lambda'] 325 | * params['sigma'] 326 | * np.sqrt(params['T']))), 0.0, 1.0)))) 327 | 328 | return barrier_factors, params 329 | -------------------------------------------------------------------------------- /optionvisualizer/visualizer_params.py: -------------------------------------------------------------------------------- 1 | """ 2 | Key parameters for visualizer 3 | 4 | """ 5 | 6 | # pylint: disable=invalid-name 7 | 8 | # Dictionary of default parameters 9 | vis_params_dict = { 10 | 'S':100, 11 | 'K':100, 12 | 'K1':95, 13 | 'K2':105, 14 | 'K3':105, 15 | 'K4':105, 16 | 'G1':90, 17 | 'G2':100, 18 | 'G3':110, 19 | 'H':105, 20 | 'R':0, 21 | 'T':0.25, 22 | 'T1':0.25, 23 | 'T2':0.25, 24 | 'T3':0.25, 25 | 'T4':0.25, 26 | 'r':0.005, 27 | 'b':0.005, 28 | 'q':0, 29 | 'sigma':0.2, 30 | 'eta':1, 31 | 'phi':1, 32 | 'C4':None, 33 | 'label4':None, 34 | 'barrier_direction':'down', 35 | 'knock':'in', 36 | 'option':'call', 37 | 'option1':'call', 38 | 'option2':'call', 39 | 'option3':'call', 40 | 'option4':'call', 41 | 'option1_color':'blue', 42 | 'option2_color':'red', 43 | 'option3_color':'green', 44 | 'option4_color':'orange', 45 | 'strike_min': 0.8, 46 | 'strike_max': 1.2, 47 | 'vol_min': 0.05, 48 | 'vol_max': 0.5, 49 | 'time_min': 0.01, 50 | 'time_max': 1, 51 | 'linspace_granularity': 1000, 52 | 'direction':'long', 53 | 'value':False, 54 | 'ratio':2, 55 | 'refresh':'Std', 56 | 'combo_payoff':'straddle', 57 | 'payoff_type':'straddle', 58 | 'price_shift':0.25, 59 | 'price_shift_type':'avg', 60 | 'vol_shift':0.001, 61 | 'ttm_shift':1/365, 62 | 'rate_shift':0.0001, 63 | 'greek':'delta', 64 | 'num_sens':False, 65 | 'interactive':False, 66 | 'notebook':True, 67 | 'data_output':False, 68 | 'web_graph':False, 69 | 'colorscheme':'jet', 70 | 'colorintensity':1, 71 | 'size3d':(15, 12), 72 | 'size2d':(8, 6), 73 | 'graphtype':'2D', 74 | 'y_plot':'delta', 75 | 'x_plot':'price', 76 | 'time_shift':0.25, 77 | 'cash':False, 78 | 'axis':'price', 79 | 'spacegrain':100, 80 | 'azim':-60, 81 | 'elev':30, 82 | 'risk':True, 83 | 'graph_figure': False, 84 | 'gif':False, 85 | 'gif_folder':None, 86 | 'gif_filename':None, 87 | 'gif_folder_2D':'images/greeks2D', 88 | 'gif_filename_2D':'greek2D', 89 | 'gif_folder_3D':'images/greeks3D', 90 | 'gif_filename_3D':'greek3D', 91 | 'gif_frame_update':2, 92 | 'gif_min_dist':9.0, 93 | 'gif_max_dist':10.0, 94 | 'gif_min_elev':10.0, 95 | 'gif_max_elev':60.0, 96 | 'gif_dpi':50, 97 | 'gif_ms':100, 98 | 'gif_start_azim':0, 99 | 'gif_end_azim':360, 100 | 'mpl_style':'seaborn-v0_8-darkgrid', 101 | 'steps':40, 102 | 'titlename':None, 103 | 'title_font_scale':None, 104 | 105 | # List of default parameters used when refreshing 106 | 'params_list':[ 107 | 'S', 'K', 'K1', 'K2', 'K3', 'K4', 'G1', 'G2', 'G3', 'H', 'R', 108 | 'T', 'T1', 'T2', 'T3', 'T4', 'r', 'b', 'q', 'sigma', 'eta', 109 | 'phi', 'barrier_direction', 'knock', 'option', 'option1', 110 | 'option2', 'option3', 'option4', 'direction', 'value', 111 | 'ratio', 'refresh', 'price_shift', 'price_shift_type', 112 | 'vol_shift','ttm_shift', 'rate_shift', 'greek', 'num_sens', 113 | 'interactive', 'notebook', 'colorscheme', 'colorintensity', 114 | 'size3d', 'size2d', 'graphtype', 'cash', 'axis', 'spacegrain', 115 | 'azim', 'elev', 'risk', 'mpl_style' 116 | ], 117 | 118 | # List of Greeks where call and put values are the same 119 | 'equal_greeks':[ 120 | 'gamma', 121 | 'vega', 122 | 'vomma', 123 | 'vanna', 124 | 'zomma', 125 | 'speed', 126 | 'color', 127 | 'ultima', 128 | 'vega bleed' 129 | ], 130 | 131 | # Payoffs requiring changes to default parameters 132 | 'mod_payoffs':[ 133 | 'call', 134 | 'put', 135 | 'collar', 136 | 'straddle', 137 | 'butterfly', 138 | 'christmas tree', 139 | 'condor', 140 | 'iron butterfly', 141 | 'iron condor' 142 | ], 143 | 144 | # Those parameters that need changing 145 | 'mod_params':[ 146 | 'S', 147 | 'K', 148 | 'K1', 149 | 'K2', 150 | 'K3', 151 | 'K4', 152 | 'T', 153 | 'T1', 154 | 'T2', 155 | 'T3', 156 | 'T4' 157 | ], 158 | 159 | # Combo parameter values differing from standard defaults 160 | 'combo_dict':{ 161 | 'call':{ 162 | 'S':100, 163 | 'K':100, 164 | 'K1':100, 165 | 'T1':0.25 166 | }, 167 | 'put':{ 168 | 'S':100, 169 | 'K':100, 170 | 'K1':100, 171 | 'T1':0.25 172 | }, 173 | 'collar':{ 174 | 'S':100, 175 | 'K':100, 176 | 'K1':98, 177 | 'K2':102, 178 | 'T1':0.25, 179 | 'T2':0.25 180 | }, 181 | 'straddle':{ 182 | 'S':100, 183 | 'K':100, 184 | 'K1':100, 185 | 'K2':100, 186 | 'T1':0.25, 187 | 'T2':0.25 188 | }, 189 | 'butterfly':{ 190 | 'S':100, 191 | 'K':100, 192 | 'K1':95, 193 | 'K2':100, 194 | 'K3':105, 195 | 'T1':0.25, 196 | 'T2':0.25, 197 | 'T3':0.25 198 | }, 199 | 'christmas tree':{ 200 | 'S':100, 201 | 'K':100, 202 | 'K1':95, 203 | 'K2':100, 204 | 'K3':105, 205 | 'T1':0.25, 206 | 'T2':0.25, 207 | 'T3':0.25 208 | }, 209 | 'condor':{ 210 | 'S':100, 211 | 'K':100, 212 | 'K1':90, 213 | 'K2':95, 214 | 'K3':100, 215 | 'K4':105, 216 | 'T1':0.25, 217 | 'T2':0.25, 218 | 'T3':0.25, 219 | 'T4':0.25 220 | }, 221 | 'iron butterfly':{ 222 | 'S':100, 223 | 'K':100, 224 | 'K1':95, 225 | 'K2':100, 226 | 'K3':100, 227 | 'K4':105, 228 | 'T1':0.25, 229 | 'T2':0.25, 230 | 'T3':0.25, 231 | 'T4':0.25 232 | }, 233 | 'iron condor':{ 234 | 'S':100, 235 | 'K':100, 236 | 'K1':90, 237 | 'K2':95, 238 | 'K3':100, 239 | 'K4':105, 240 | 'T1':0.25, 241 | 'T2':0.25, 242 | 'T3':0.25, 243 | 'T4':0.25 244 | } 245 | }, 246 | 247 | 'combo_parameters':{ 248 | 'call':[ 249 | 'S', 250 | 'K', 251 | 'T', 252 | 'r', 253 | 'q', 254 | 'sigma', 255 | 'direction', 256 | 'value', 257 | 'mpl_style', 258 | 'size2d' 259 | ], 260 | 'put':[ 261 | 'S', 262 | 'K', 263 | 'T', 264 | 'r', 265 | 'q', 266 | 'sigma', 267 | 'direction', 268 | 'value', 269 | 'mpl_style', 270 | 'size2d' 271 | ], 272 | 'stock':[ 273 | 'S', 274 | 'direction', 275 | 'mpl_style', 276 | 'size2d' 277 | ], 278 | 'forward':[ 279 | 'S', 280 | 'T', 281 | 'r', 282 | 'q', 283 | 'sigma', 284 | 'direction', 285 | 'cash', 286 | 'mpl_style', 287 | 'size2d' 288 | ], 289 | 'collar':[ 290 | 'S', 291 | 'K1', 292 | 'K2', 293 | 'T', 294 | 'r', 295 | 'q', 296 | 'sigma', 297 | 'direction', 298 | 'value', 299 | 'mpl_style', 300 | 'size2d' 301 | ], 302 | 'spread':[ 303 | 'S', 304 | 'K1', 305 | 'K2', 306 | 'T', 307 | 'r', 308 | 'q', 309 | 'sigma', 310 | 'option', 311 | 'direction', 312 | 'value', 313 | 'mpl_style', 314 | 'size2d' 315 | ], 316 | 'backspread':[ 317 | 'S', 318 | 'K1', 319 | 'K2', 320 | 'T', 321 | 'r', 322 | 'q', 323 | 'sigma', 324 | 'option', 325 | 'ratio', 326 | 'value', 327 | 'mpl_style', 328 | 'size2d' 329 | ], 330 | 'ratio vertical spread':[ 331 | 'S', 332 | 'K1', 333 | 'K2', 334 | 'T', 335 | 'r', 336 | 'q', 337 | 'sigma', 338 | 'option', 339 | 'ratio', 340 | 'value', 341 | 'mpl_style', 342 | 'size2d' 343 | ], 344 | 'straddle':[ 345 | 'S', 346 | 'K', 347 | 'T', 348 | 'r', 349 | 'q', 350 | 'sigma', 351 | 'direction', 352 | 'value', 353 | 'mpl_style', 354 | 'size2d' 355 | ], 356 | 'strangle':[ 357 | 'S', 358 | 'K1', 359 | 'K2', 360 | 'T', 361 | 'r', 362 | 'q', 363 | 'sigma', 364 | 'direction', 365 | 'value', 366 | 'mpl_style', 367 | 'size2d' 368 | ], 369 | 'butterfly':[ 370 | 'S', 371 | 'K1', 372 | 'K2', 373 | 'K3', 374 | 'T', 375 | 'r', 376 | 'q', 377 | 'sigma', 378 | 'option', 379 | 'direction', 380 | 'value', 381 | 'mpl_style', 382 | 'size2d' 383 | ], 384 | 'christmas tree':[ 385 | 'S', 386 | 'K1', 387 | 'K2', 388 | 'K3', 389 | 'T', 390 | 'r', 391 | 'q', 392 | 'sigma', 393 | 'option', 394 | 'direction', 395 | 'value', 396 | 'mpl_style', 397 | 'size2d' 398 | ], 399 | 'condor':[ 400 | 'S', 401 | 'K1', 402 | 'K2', 403 | 'K3', 404 | 'K4', 405 | 'T', 406 | 'r', 407 | 'q', 408 | 'sigma', 409 | 'option', 410 | 'direction', 411 | 'value', 412 | 'mpl_style', 413 | 'size2d' 414 | ], 415 | 'iron butterfly':[ 416 | 'S', 417 | 'K1', 418 | 'K2', 419 | 'K3', 420 | 'K4', 421 | 'T', 422 | 'r', 423 | 'q', 424 | 'sigma', 425 | 'direction', 426 | 'value', 427 | 'mpl_style', 428 | 'size2d' 429 | ], 430 | 'iron condor':[ 431 | 'S', 432 | 'K1', 433 | 'K2', 434 | 'K3', 435 | 'K4', 436 | 'T', 437 | 'r', 438 | 'q', 439 | 'sigma', 440 | 'direction', 441 | 'value', 442 | 'mpl_style', 443 | 'size2d' 444 | ], 445 | }, 446 | 447 | 'combo_name_dict':{ 448 | 'call':'call', 449 | 'put':'put', 450 | 'stock':'stock', 451 | 'forward':'forward', 452 | 'collar':'collar', 453 | 'spread':'spread', 454 | 'backspread':'backspread', 455 | 'ratio vertical spread':'ratio_vertical_spread', 456 | 'straddle':'straddle', 457 | 'strangle':'strangle', 458 | 'butterfly':'butterfly', 459 | 'christmas tree':'christmas_tree', 460 | 'condor':'condor', 461 | 'iron butterfly':'iron_butterfly', 462 | 'iron condor':'iron_condor', 463 | }, 464 | 465 | 'combo_simple_dict':{ 466 | 'call':'call', 467 | 'put':'put', 468 | 'stock':'stock', 469 | 'forward':'forward', 470 | 'collar':'collar', 471 | 'spread':'spread', 472 | 'backspread':'backspread', 473 | 'ratio vertical spread':'ratio_vertical_spread', 474 | 'straddle':'straddle', 475 | 'strangle':'strangle', 476 | }, 477 | 478 | 'combo_multi_dict':{ 479 | 'butterfly':'butterfly', 480 | 'christmas tree':'christmas_tree', 481 | 'condor':'condor', 482 | 'iron butterfly':'iron_butterfly', 483 | 'iron condor':'iron_condor', 484 | }, 485 | 486 | # Dictionary mapping function parameters to x axis labels 487 | # for 2D graphs 488 | 'x_name_dict':{ 489 | 'price':'SA', 490 | 'strike':'SA', 491 | 'vol':'sigmaA', 492 | 'time':'TA' 493 | }, 494 | 495 | # Dictionary mapping scaling parameters to x axis labels 496 | # for 2D graphs 497 | 'x_scale_dict':{ 498 | 'price':1, 499 | 'strike':1, 500 | 'vol':100, 501 | 'time':365 502 | }, 503 | 504 | # Dictionary mapping function parameters to y axis labels 505 | # for 2D graphs 506 | 'y_name_dict':{ 507 | 'value':'price', 508 | 'delta':'delta', 509 | 'gamma':'gamma', 510 | 'vega':'vega', 511 | 'theta':'theta' 512 | }, 513 | 514 | # Dictionary mapping function parameters to axis labels 515 | # for 3D graphs 516 | 'label_dict':{ 517 | 'price':'Underlying Price', 518 | 'value':'Theoretical Value', 519 | 'vol':'Volatility %', 520 | 'time':'Time to Expiration (Days)', 521 | 'delta':'Delta', 522 | 'gamma':'Gamma', 523 | 'vega':'Vega', 524 | 'theta':'Theta', 525 | 'rho':'Rho', 526 | 'strike':'Strike Price' 527 | }, 528 | 529 | # Ranges of Underlying price and Time to Expiry for 3D 530 | # greeks graphs 531 | '3D_chart_ranges':{ 532 | 'price':{ 533 | 'SA_lower':0.8, 534 | 'SA_upper':1.2, 535 | 'TA_lower':0.01, 536 | 'TA_upper':1 537 | }, 538 | 'delta':{ 539 | 'SA_lower':0.25, 540 | 'SA_upper':1.75, 541 | 'TA_lower':0.01, 542 | 'TA_upper':2 543 | }, 544 | 'gamma':{ 545 | 'SA_lower':0.8, 546 | 'SA_upper':1.2, 547 | 'TA_lower':0.01, 548 | 'TA_upper':5 549 | }, 550 | 'vega':{ 551 | 'SA_lower':0.5, 552 | 'SA_upper':1.5, 553 | 'TA_lower':0.01, 554 | 'TA_upper':1 555 | }, 556 | 'theta':{ 557 | 'SA_lower':0.8, 558 | 'SA_upper':1.2, 559 | 'TA_lower':0.01, 560 | 'TA_upper':1 561 | }, 562 | 'rho':{ 563 | 'SA_lower':0.8, 564 | 'SA_upper':1.2, 565 | 'TA_lower':0.01, 566 | 'TA_upper':0.5 567 | }, 568 | 'vomma':{ 569 | 'SA_lower':0.5, 570 | 'SA_upper':1.5, 571 | 'TA_lower':0.01, 572 | 'TA_upper':1 573 | }, 574 | 'vanna':{ 575 | 'SA_lower':0.5, 576 | 'SA_upper':1.5, 577 | 'TA_lower':0.01, 578 | 'TA_upper':1 579 | }, 580 | 'zomma':{ 581 | 'SA_lower':0.8, 582 | 'SA_upper':1.2, 583 | 'TA_lower':0.01, 584 | 'TA_upper':0.5 585 | }, 586 | 'speed':{ 587 | 'SA_lower':0.8, 588 | 'SA_upper':1.2, 589 | 'TA_lower':0.01, 590 | 'TA_upper':0.5 591 | }, 592 | 'color':{ 593 | 'SA_lower':0.8, 594 | 'SA_upper':1.2, 595 | 'TA_lower':0.01, 596 | 'TA_upper':0.5 597 | }, 598 | 'ultima':{ 599 | 'SA_lower':0.5, 600 | 'SA_upper':1.5, 601 | 'TA_lower':0.01, 602 | 'TA_upper':1 603 | }, 604 | 'vega bleed':{ 605 | 'SA_lower':0.5, 606 | 'SA_upper':1.5, 607 | 'TA_lower':0.01, 608 | 'TA_upper':1 609 | }, 610 | 'charm':{ 611 | 'SA_lower':0.8, 612 | 'SA_upper':1.2, 613 | 'TA_lower':0.01, 614 | 'TA_upper':0.25 615 | } 616 | }, 617 | 618 | # Greek names as function input and individual function 619 | # names 620 | 'greek_dict':{ 621 | 'price':'price', 622 | 'delta':'delta', 623 | 'gamma':'gamma', 624 | 'vega':'vega', 625 | 'theta':'theta', 626 | 'rho':'rho', 627 | 'vomma':'vomma', 628 | 'vanna':'vanna', 629 | 'zomma':'zomma', 630 | 'speed':'speed', 631 | 'color':'color', 632 | 'ultima':'ultima', 633 | 'vega bleed':'vega_bleed', 634 | 'charm':'charm' 635 | }, 636 | 637 | # Parameters to overwrite mpl_style defaults 638 | 'mpl_params':{ 639 | 'legend.fontsize': 'x-large', 640 | 'legend.fancybox':False, 641 | 'figure.dpi':72, 642 | 'axes.labelsize': 'medium', 643 | 'axes.titlesize':'large', 644 | 'axes.spines.bottom':True, 645 | 'axes.spines.left':True, 646 | 'axes.spines.right':True, 647 | 'axes.spines.top':True, 648 | 'axes.edgecolor':'black', 649 | 'axes.titlepad':20, 650 | 'axes.autolimit_mode':'data',#'round_numbers', 651 | 'axes.xmargin':0.05, 652 | 'axes.ymargin':0.05, 653 | 'axes.linewidth':2, 654 | 'xtick.labelsize':'medium', 655 | 'ytick.labelsize':'medium', 656 | 'xtick.major.pad':10, 657 | 'ytick.major.pad':10, 658 | 'lines.linewidth':3.0, 659 | 'lines.color':'black', 660 | 'grid.color':'black', 661 | 'grid.linestyle':':', 662 | 'font.size':14 663 | }, 664 | 665 | 'mpl_3d_params':{ 666 | 'axes.facecolor':'w', 667 | 'axes.labelcolor':'k', 668 | 'axes.edgecolor':'w', 669 | 'axes.titlepad':5, 670 | 'lines.linewidth':0.5, 671 | 'xtick.labelbottom':True, 672 | 'ytick.labelleft':True 673 | } 674 | } 675 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = optionvisualizer 3 | 4 | url = https://github.com/GBERESEARCH/optionvisualizer 5 | 6 | author = GBERESEARCH 7 | author_email = gberesearch@gmail.com 8 | version = attr: optionvisualizer.__version__ 9 | 10 | description = Visualize option prices and sensitivities. 11 | long_description = file: README.md 12 | long_description_content_type = text/markdown 13 | license = MIT 14 | 15 | [options] 16 | package_dir= 17 | =src 18 | packages=find: 19 | 20 | install_requires = 21 | numpy >= 1.16.2 22 | scipy >= 1.4.1 23 | matplotlib >= 3.6.* 24 | plotly >= 4.3.0 25 | pillow >= 5.4.1 26 | 27 | [options.packages.find] 28 | where=src -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | if __name__ == "__main__": 6 | setup() 7 | -------------------------------------------------------------------------------- /tests/vis_unittest_analytical_sens.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for numerical output functions 3 | 4 | """ 5 | 6 | import unittest 7 | import optionvisualizer.visualizer as vis 8 | 9 | class OptionNumericalTestCase(unittest.TestCase): 10 | """ 11 | Unit tests for numerical output functions 12 | 13 | """ 14 | 15 | def test_price(self): 16 | """ 17 | Unit test for option pricing function. 18 | 19 | Returns 20 | ------- 21 | Pass / Fail. 22 | 23 | """ 24 | 25 | self.assertGreater(vis.Visualizer().option_data( 26 | option_value='price'), 0) 27 | print("Default price: ", vis.Visualizer().option_data( 28 | option_value='price')) 29 | 30 | self.assertIsInstance(vis.Visualizer().option_data( 31 | option_value='price'), float) 32 | 33 | self.assertGreater(vis.Visualizer().option_data( 34 | option_value='price', S=50, K=55, T=1, r=0.05, q=0.01, 35 | sigma=0.3, option='put'), 0) 36 | print("Revalued price: ", vis.Visualizer().option_data( 37 | option_value='price', S=50, K=55, T=1, r=0.05, q=0.01, 38 | sigma=0.3, option='put')) 39 | 40 | self.assertIsInstance(vis.Visualizer().option_data( 41 | option_value='price', S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, 42 | option='put'), float) 43 | 44 | 45 | def test_delta(self): 46 | """ 47 | Unit test for delta function. 48 | 49 | Returns 50 | ------- 51 | Pass / Fail. 52 | 53 | """ 54 | self.assertGreater(abs(vis.Visualizer().option_data( 55 | option_value='delta')), 0) 56 | print("Default delta: ", vis.Visualizer().option_data( 57 | option_value='delta')) 58 | 59 | self.assertIsInstance(vis.Visualizer().option_data( 60 | option_value='delta'), float) 61 | 62 | self.assertGreater(abs(vis.Visualizer().option_data( 63 | option_value='delta', 64 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 65 | print("Revalued delta: ", vis.Visualizer().option_data( 66 | option_value='delta', 67 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 68 | 69 | self.assertIsInstance(vis.Visualizer().option_data( 70 | option_value='delta', 71 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 72 | 73 | 74 | def test_theta(self): 75 | """ 76 | Unit test for theta function. 77 | 78 | Returns 79 | ------- 80 | Pass / Fail. 81 | 82 | """ 83 | self.assertGreater(abs(vis.Visualizer().option_data( 84 | option_value='theta')), 0) 85 | print("Default theta: ", vis.Visualizer().option_data( 86 | option_value='theta')) 87 | 88 | self.assertIsInstance(vis.Visualizer().option_data( 89 | option_value='theta'), float) 90 | 91 | self.assertGreater(abs(vis.Visualizer().option_data( 92 | option_value='theta', 93 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 94 | print("Revalued theta: ", vis.Visualizer().option_data( 95 | option_value='theta', 96 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 97 | 98 | self.assertIsInstance(vis.Visualizer().option_data( 99 | option_value='theta', 100 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 101 | 102 | 103 | def test_gamma(self): 104 | """ 105 | Unit test for gamma function. 106 | 107 | Returns 108 | ------- 109 | Pass / Fail. 110 | 111 | """ 112 | self.assertGreater(abs(vis.Visualizer().option_data( 113 | option_value='gamma')), 0) 114 | print("Default gamma: ", vis.Visualizer().option_data( 115 | option_value='gamma')) 116 | 117 | self.assertIsInstance(vis.Visualizer().option_data( 118 | option_value='gamma'), float) 119 | 120 | self.assertGreater(abs(vis.Visualizer().option_data( 121 | option_value='gamma', 122 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 123 | print("Revalued gamma: ", vis.Visualizer().option_data( 124 | option_value='gamma', 125 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 126 | 127 | self.assertIsInstance(vis.Visualizer().option_data( 128 | option_value='gamma', 129 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 130 | 131 | 132 | def test_vega(self): 133 | """ 134 | Unit test for vega function. 135 | 136 | Returns 137 | ------- 138 | Pass / Fail. 139 | 140 | """ 141 | self.assertGreater(abs(vis.Visualizer().option_data( 142 | option_value='vega')), 0) 143 | print("Default vega: ", vis.Visualizer().option_data( 144 | option_value='vega')) 145 | 146 | self.assertIsInstance(vis.Visualizer().option_data( 147 | option_value='vega'), float) 148 | 149 | self.assertGreater(abs(vis.Visualizer().option_data( 150 | option_value='vega', 151 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 152 | print("Revalued vega: ", vis.Visualizer().option_data( 153 | option_value='vega', 154 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 155 | 156 | self.assertIsInstance(vis.Visualizer().option_data( 157 | option_value='vega', 158 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 159 | 160 | 161 | def test_rho(self): 162 | """ 163 | Unit test for rho function. 164 | 165 | Returns 166 | ------- 167 | Pass / Fail. 168 | 169 | """ 170 | self.assertGreater(abs(vis.Visualizer().option_data( 171 | option_value='rho')), 0) 172 | print("Default rho: ", vis.Visualizer().option_data( 173 | option_value='rho')) 174 | 175 | self.assertIsInstance(vis.Visualizer().option_data( 176 | option_value='rho'), float) 177 | 178 | self.assertGreater(abs(vis.Visualizer().option_data( 179 | option_value='rho', 180 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 181 | print("Revalued rho: ", vis.Visualizer().option_data( 182 | option_value='rho', 183 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 184 | 185 | self.assertIsInstance(vis.Visualizer().option_data( 186 | option_value='rho', 187 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 188 | 189 | 190 | def test_vanna(self): 191 | """ 192 | Unit test for vanna function. 193 | 194 | Returns 195 | ------- 196 | Pass / Fail. 197 | 198 | """ 199 | self.assertGreater(abs(vis.Visualizer().option_data( 200 | option_value='vanna')), 0) 201 | print("Default vanna: ", vis.Visualizer().option_data( 202 | option_value='vanna')) 203 | 204 | self.assertIsInstance(vis.Visualizer().option_data( 205 | option_value='vanna'), float) 206 | 207 | self.assertGreater(abs(vis.Visualizer().option_data( 208 | option_value='vanna', 209 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 210 | print("Revalued vanna: ", vis.Visualizer().option_data( 211 | option_value='vanna', 212 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 213 | 214 | self.assertIsInstance(vis.Visualizer().option_data( 215 | option_value='vanna', 216 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 217 | 218 | 219 | def test_vomma(self): 220 | """ 221 | Unit test for vomma function. 222 | 223 | Returns 224 | ------- 225 | Pass / Fail. 226 | 227 | """ 228 | self.assertGreater(abs(vis.Visualizer().option_data( 229 | option_value='vomma')), 0) 230 | print("Default vomma: ", vis.Visualizer().option_data( 231 | option_value='vomma')) 232 | 233 | self.assertIsInstance(vis.Visualizer().option_data( 234 | option_value='vomma'), float) 235 | 236 | self.assertGreater(abs(vis.Visualizer().option_data( 237 | option_value='vomma', 238 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 239 | print("Revalued vomma: ", vis.Visualizer().option_data( 240 | option_value='vomma', 241 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 242 | 243 | self.assertIsInstance(vis.Visualizer().option_data( 244 | option_value='vomma', 245 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 246 | 247 | 248 | def test_charm(self): 249 | """ 250 | Unit test for charm function. 251 | 252 | Returns 253 | ------- 254 | Pass / Fail. 255 | 256 | """ 257 | self.assertGreater(abs(vis.Visualizer().option_data( 258 | option_value='charm')), 0) 259 | print("Default charm: ", vis.Visualizer().option_data( 260 | option_value='charm')) 261 | 262 | self.assertIsInstance(vis.Visualizer().option_data( 263 | option_value='charm'), float) 264 | 265 | self.assertGreater(abs(vis.Visualizer().option_data( 266 | option_value='charm', 267 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 268 | print("Revalued charm: ", vis.Visualizer().option_data( 269 | option_value='charm', 270 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 271 | 272 | self.assertIsInstance(vis.Visualizer().option_data( 273 | option_value='charm', 274 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 275 | 276 | 277 | def test_zomma(self): 278 | """ 279 | Unit test for zomma function. 280 | 281 | Returns 282 | ------- 283 | Pass / Fail. 284 | 285 | """ 286 | self.assertGreater(abs(vis.Visualizer().option_data( 287 | option_value='zomma')), 0) 288 | print("Default zomma: ", vis.Visualizer().option_data( 289 | option_value='zomma')) 290 | 291 | self.assertIsInstance(vis.Visualizer().option_data( 292 | option_value='zomma'), float) 293 | 294 | self.assertGreater(abs(vis.Visualizer().option_data( 295 | option_value='zomma', 296 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 297 | print("Revalued zomma: ", vis.Visualizer().option_data( 298 | option_value='zomma', 299 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 300 | 301 | self.assertIsInstance(vis.Visualizer().option_data( 302 | option_value='zomma', 303 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 304 | 305 | 306 | def test_speed(self): 307 | """ 308 | Unit test for speed function. 309 | 310 | Returns 311 | ------- 312 | Pass / Fail. 313 | 314 | """ 315 | self.assertGreater(abs(vis.Visualizer().option_data( 316 | option_value='speed')), 0) 317 | print("Default speed: ", vis.Visualizer().option_data( 318 | option_value='speed')) 319 | 320 | self.assertIsInstance(vis.Visualizer().option_data( 321 | option_value='speed'), float) 322 | 323 | self.assertGreater(abs(vis.Visualizer().option_data( 324 | option_value='speed', 325 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 326 | print("Revalued speed: ", vis.Visualizer().option_data( 327 | option_value='speed', 328 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 329 | 330 | self.assertIsInstance(vis.Visualizer().option_data( 331 | option_value='speed', 332 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 333 | 334 | 335 | def test_color(self): 336 | """ 337 | Unit test for color function. 338 | 339 | Returns 340 | ------- 341 | Pass / Fail. 342 | 343 | """ 344 | self.assertGreater(abs(vis.Visualizer().option_data( 345 | option_value='color')), 0) 346 | print("Default color: ", vis.Visualizer().option_data( 347 | option_value='color')) 348 | 349 | self.assertIsInstance(vis.Visualizer().option_data( 350 | option_value='color'), float) 351 | 352 | self.assertGreater(abs(vis.Visualizer().option_data( 353 | option_value='color', 354 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 355 | print("Revalued color: ", vis.Visualizer().option_data( 356 | option_value='color', 357 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 358 | 359 | self.assertIsInstance(vis.Visualizer().option_data( 360 | option_value='color', 361 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 362 | 363 | 364 | def test_ultima(self): 365 | """ 366 | Unit test for ultima function. 367 | 368 | Returns 369 | ------- 370 | Pass / Fail. 371 | 372 | """ 373 | self.assertGreater(abs(vis.Visualizer().option_data( 374 | option_value='ultima')), 0) 375 | print("Default ultima: ", vis.Visualizer().option_data( 376 | option_value='ultima')) 377 | 378 | self.assertIsInstance(vis.Visualizer().option_data( 379 | option_value='ultima'), float) 380 | 381 | self.assertGreater(abs(vis.Visualizer().option_data( 382 | option_value='ultima', 383 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 384 | print("Revalued ultima: ", vis.Visualizer().option_data( 385 | option_value='ultima', 386 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 387 | 388 | self.assertIsInstance(vis.Visualizer().option_data( 389 | option_value='ultima', 390 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 391 | 392 | 393 | def test_vega_bleed(self): 394 | """ 395 | Unit test for vega bleed function. 396 | 397 | Returns 398 | ------- 399 | Pass / Fail. 400 | 401 | """ 402 | self.assertGreater(abs(vis.Visualizer().option_data( 403 | option_value='vega bleed')), 0) 404 | print("Default vega_bleed: ", vis.Visualizer().option_data( 405 | option_value='vega bleed')) 406 | 407 | self.assertIsInstance(vis.Visualizer().option_data( 408 | option_value='vega bleed'), float) 409 | 410 | self.assertGreater(abs(vis.Visualizer().option_data( 411 | option_value='vega bleed', 412 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 413 | print("Revalued vega_bleed: ", vis.Visualizer().option_data( 414 | option_value='vega bleed', 415 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 416 | 417 | self.assertIsInstance(vis.Visualizer().option_data( 418 | option_value='vega bleed', 419 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 420 | 421 | 422 | def test_analytical_sensitivities(self): 423 | """ 424 | Unit test for analytical sensitivities function. 425 | 426 | Returns 427 | ------- 428 | Pass / Fail. 429 | 430 | """ 431 | self.assertGreater( 432 | abs(vis.Visualizer().sensitivities(num_sens=False)), 0) 433 | print("Default analytical_sensitivities: ", 434 | vis.Visualizer().sensitivities(num_sens=False)) 435 | 436 | self.assertIsInstance( 437 | vis.Visualizer().sensitivities(num_sens=False), float) 438 | 439 | self.assertGreater(abs(vis.Visualizer().sensitivities(num_sens=False, 440 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 441 | print("Revalued analytical_sensitivities: ", 442 | vis.Visualizer().sensitivities(num_sens=False, 443 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 444 | 445 | self.assertIsInstance(vis.Visualizer().sensitivities(num_sens=False, 446 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 447 | 448 | 449 | def test_numerical_sensitivities(self): 450 | """ 451 | Unit test for numerical sensitivities function. 452 | 453 | Returns 454 | ------- 455 | Pass / Fail. 456 | 457 | """ 458 | self.assertGreater( 459 | abs(vis.Visualizer().sensitivities(num_sens=True)), 0) 460 | print("Default numerical_sensitivities: ", 461 | vis.Visualizer().sensitivities(num_sens=True)) 462 | 463 | self.assertIsInstance( 464 | vis.Visualizer().sensitivities(num_sens=True), float) 465 | 466 | self.assertGreater(abs(vis.Visualizer().sensitivities(num_sens=True, 467 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 468 | print("Revalued numerical_sensitivities: ", 469 | vis.Visualizer().sensitivities(num_sens=True, 470 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 471 | 472 | self.assertIsInstance(vis.Visualizer().sensitivities(num_sens=True, 473 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 474 | 475 | 476 | def test_barrier(self): 477 | """ 478 | Unit test for barrier pricing function. 479 | 480 | Returns 481 | ------- 482 | Pass / Fail. 483 | 484 | """ 485 | self.assertGreater(abs(vis.Visualizer().barrier()), 0) 486 | print("Default barrier_price: ", vis.Visualizer().barrier()) 487 | 488 | self.assertIsInstance(vis.Visualizer().barrier(), float) 489 | 490 | self.assertGreater(abs(vis.Visualizer().barrier( 491 | S=50, K=55, H=60, R=0.1, T=1, r=0.05, q=0.01, sigma=0.3, 492 | option='put', barrier_direction='up', knock='out')), 0) 493 | print("Revalued barrier_price: ", vis.Visualizer().barrier( 494 | S=50, K=55, H=60, R=0.1, T=1, r=0.05, q=0.01, sigma=0.3, 495 | option='put', barrier_direction='up', knock='out')) 496 | 497 | self.assertIsInstance(vis.Visualizer().barrier( 498 | S=50, K=55, H=60, R=0.1, T=1, r=0.05, q=0.01, sigma=0.3, 499 | option='put', barrier_direction='up', knock='out'), float) 500 | 501 | 502 | if __name__ == '__main__': 503 | unittest.main() 504 | -------------------------------------------------------------------------------- /tests/vis_unittest_graphical.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for graphical output functions 3 | 4 | """ 5 | 6 | import unittest 7 | import optionvisualizer.visualizer as vis 8 | 9 | class OptionGraphicalTestCase(unittest.TestCase): 10 | """ 11 | Unit tests for graphical output functions 12 | 13 | """ 14 | 15 | @staticmethod 16 | def test_visualize(): 17 | """ 18 | Unit test for visualize function. 19 | 20 | Returns 21 | ------- 22 | Pass / Fail. 23 | 24 | """ 25 | vis.Visualizer().visualize() 26 | 27 | vis.Visualizer().visualize( 28 | risk=False, combo_payoff='backspread', S=50, K1=48, K2=52, T=1, 29 | r=0.05, q=0.01, sigma=0.3, option='put', ratio=3, value=True, 30 | mpl_style='fivethirtyeight', size2d=(6, 4)) 31 | 32 | vis.Visualizer().visualize( 33 | risk=True, graphtype='2D', x_plot='vol', y_plot='vega', S=50, 34 | G1=45, G2=50, G3=55, T=0.5, T1=0.5, T2=0.5, T3=0.5, time_shift=0.5, 35 | r=0.05, q=0.01, sigma=0.3, option='put', direction='short', 36 | size2d=(6, 4), mpl_style='fivethirtyeight', num_sens=True) 37 | 38 | vis.Visualizer().visualize( 39 | risk=True, graphtype='3D', interactive=False, S=50, r=0.05, q=0.01, 40 | sigma=0.3, option='put', notebook=False, colorscheme='plasma', 41 | colorintensity=0.8, size3d=(12, 8), direction='short', axis='vol', 42 | spacegrain=150, azim=-50, elev=20, greek='vega', num_sens=True) 43 | 44 | vis.Visualizer().visualize( 45 | risk=True, graphtype='3D', interactive=True, S=50, r=0.05, q=0.01, 46 | sigma=0.3, option='put', notebook=False, colorscheme='plasma', 47 | colorintensity=0.8, size3d=(12, 8), direction='short', axis='vol', 48 | spacegrain=150, azim=-50, elev=20, greek='vega', num_sens=True) 49 | 50 | 51 | @staticmethod 52 | def test_greeks(): 53 | """ 54 | Unit test for greeks function. 55 | 56 | Returns 57 | ------- 58 | Pass / Fail. 59 | 60 | """ 61 | vis.Visualizer().greeks() 62 | 63 | vis.Visualizer().greeks( 64 | graphtype='2D', x_plot='vol', y_plot='vega', S=50, G1=45, G2=50, 65 | G3=55, T=0.5, T1=0.5, T2=0.5, T3=0.5, time_shift=0.5, r=0.05, 66 | q=0.01, sigma=0.3, option='put', direction='short', size2d=(6, 4), 67 | mpl_style='fivethirtyeight', num_sens=True) 68 | 69 | vis.Visualizer().greeks( 70 | graphtype='3D', interactive=False, S=50, r=0.05, q=0.01, sigma=0.3, 71 | option='put', notebook=False, colorscheme='plasma', 72 | colorintensity=0.8, size3d=(12, 8), direction='short', axis='vol', 73 | spacegrain=150, azim=-50, elev=20, greek='vega', num_sens=True) 74 | 75 | vis.Visualizer().greeks( 76 | graphtype='3D', interactive=True, S=50, r=0.05, q=0.01, sigma=0.3, 77 | option='put', notebook=False, colorscheme='plasma', 78 | colorintensity=0.8, size3d=(12, 8), direction='short', axis='vol', 79 | spacegrain=150, azim=-50, elev=20, greek='vega', num_sens=True) 80 | 81 | 82 | @staticmethod 83 | def test_greeks_graphs_2d(): 84 | """ 85 | Unit test for 2d greeks function. 86 | 87 | Returns 88 | ------- 89 | Pass / Fail. 90 | 91 | """ 92 | vis.Visualizer().greeks(graphtype='2D') 93 | 94 | vis.Visualizer().greeks(graphtype='2D', 95 | x_plot='vol', y_plot='vega', S=50, G1=45, G2=50, G3=55, T=0.5, 96 | T1=0.5, T2=0.5, T3=0.5, time_shift=0.5, r=0.05, q=0.01, sigma=0.3, 97 | option='put', direction='short', size2d=(6, 4), 98 | mpl_style='fivethirtyeight', num_sens=True) 99 | 100 | vis.Visualizer().greeks(graphtype='2D', 101 | x_plot='vol', y_plot='rho', S=50, G1=45, G2=50, G3=55, T=0.5, 102 | time_shift=0.5, r=0.05, q=0.01, sigma=0.3, option='put', 103 | direction='short', size2d=(6, 4), mpl_style='fivethirtyeight', 104 | num_sens=True) 105 | 106 | 107 | @staticmethod 108 | def test_greeks_graphs_3d(): 109 | """ 110 | Unit test for 3d greeks function. 111 | 112 | Returns 113 | ------- 114 | Pass / Fail. 115 | 116 | """ 117 | vis.Visualizer().greeks(graphtype='3D') 118 | 119 | vis.Visualizer().greeks(graphtype='3D', 120 | interactive=False, S=50, r=0.05, q=0.01, sigma=0.3, option='put', 121 | notebook=False, colorscheme='plasma', colorintensity=0.8, 122 | size3d=(12, 8), direction='short', axis='vol', spacegrain=150, 123 | azim=-50, elev=20, greek='vega', num_sens=True) 124 | 125 | vis.Visualizer().greeks(graphtype='3D', 126 | interactive=True, S=50, r=0.05, q=0.01, sigma=0.3, option='put', 127 | notebook=False, colorscheme='plasma', colorintensity=0.8, 128 | size3d=(12, 8), direction='short', axis='vol', spacegrain=150, 129 | azim=-50, elev=20, greek='vega', num_sens=True) 130 | 131 | 132 | @staticmethod 133 | def test_animated_gif_2d(): 134 | """ 135 | Unit test for animated 2d gif function. 136 | 137 | Returns 138 | ------- 139 | Pass / Fail. 140 | 141 | """ 142 | vis.Visualizer().animated_gif(graphtype='2D') 143 | 144 | vis.Visualizer().animated_gif(graphtype='2D', 145 | gif_folder='images/test2d', gif_filename='test2d', T=1, steps=60, 146 | x_plot='vol', y_plot='vega', S=50, G1=45, G2=50, G3=55, 147 | r=0.05, q=0.01, sigma=0.3, option='put', direction='short', 148 | size2d=(6, 4), mpl_style='fivethirtyeight', num_sens=True) 149 | 150 | 151 | @staticmethod 152 | def test_animated_gif_3d(): 153 | """ 154 | Unit test for animated 3d gif function. 155 | 156 | Returns 157 | ------- 158 | Pass / Fail. 159 | 160 | """ 161 | vis.Visualizer().animated_gif(graphtype='3D') 162 | 163 | vis.Visualizer().animated_gif(graphtype='3D', 164 | S=50, r=0.05, q=0.01, sigma=0.3, option='put', direction='short', 165 | notebook=False, colorscheme='plasma', colorintensity=0.8, 166 | size3d=(12, 8), axis='vol', spacegrain=150, azim=-50, elev=20, 167 | greek='vega', num_sens=True, gif_folder='images/test3d', 168 | gif_filename='test3d', gif_frame_update=2, gif_min_dist=9.2, 169 | gif_max_dist=9.8, gif_min_elev=20.0, gif_max_elev=50.0, 170 | gif_start_azim=90, gif_end_azim=270, gif_dpi=40, gif_ms=150) 171 | 172 | 173 | @staticmethod 174 | def test_payoffs(): 175 | """ 176 | Unit test for payoffs function. 177 | 178 | Returns 179 | ------- 180 | Pass / Fail. 181 | 182 | """ 183 | vis.Visualizer().payoffs(payoff_type='straddle') 184 | 185 | vis.Visualizer().payoffs( 186 | payoff_type='backspread', S=50, K1=48, K2=52, T=1, r=0.05, q=0.01, 187 | sigma=0.3, option='put', ratio=3, value=True, 188 | mpl_style='fivethirtyeight', size2d=(6, 4)) 189 | 190 | 191 | @staticmethod 192 | def test_call(): 193 | """ 194 | Unit test for call function. 195 | 196 | Returns 197 | ------- 198 | Pass / Fail. 199 | 200 | """ 201 | vis.Visualizer().payoffs(payoff_type='call') 202 | 203 | vis.Visualizer().payoffs(payoff_type='call', 204 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, direction='short', 205 | value=True, mpl_style='fivethirtyeight', size2d=(6, 4)) 206 | 207 | 208 | @staticmethod 209 | def test_put(): 210 | """ 211 | Unit test for put function. 212 | 213 | Returns 214 | ------- 215 | Pass / Fail. 216 | 217 | """ 218 | vis.Visualizer().payoffs(payoff_type='put') 219 | 220 | vis.Visualizer().payoffs(payoff_type='put', 221 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, direction='short', 222 | value=True, mpl_style='fivethirtyeight', size2d=(6, 4)) 223 | 224 | 225 | @staticmethod 226 | def test_stock(): 227 | """ 228 | Unit test for stock function. 229 | 230 | Returns 231 | ------- 232 | Pass / Fail. 233 | 234 | """ 235 | vis.Visualizer().payoffs(payoff_type='stock') 236 | 237 | vis.Visualizer().payoffs(payoff_type='stock', S=50, direction='short', 238 | mpl_style='fivethirtyeight', size2d=(6, 4)) 239 | 240 | @staticmethod 241 | def test_forward(): 242 | """ 243 | Unit test for forward function. 244 | 245 | Returns 246 | ------- 247 | Pass / Fail. 248 | 249 | """ 250 | vis.Visualizer().payoffs(payoff_type='forward') 251 | 252 | vis.Visualizer().payoffs(payoff_type='forward', 253 | S=50, T=1, r=0.05, q=0.01, sigma=0.3, direction='short', cash=True, 254 | mpl_style='fivethirtyeight', size2d=(6, 4)) 255 | 256 | 257 | @staticmethod 258 | def test_collar(): 259 | """ 260 | Unit test for collar function. 261 | 262 | Returns 263 | ------- 264 | Pass / Fail. 265 | 266 | """ 267 | vis.Visualizer().payoffs(payoff_type='collar') 268 | 269 | vis.Visualizer().payoffs( 270 | payoff_type='collar', S=50, K1=48, K2=52, T=1, r=0.05, q=0.01, 271 | sigma=0.3, direction='short', value=True, 272 | mpl_style='fivethirtyeight', size2d=(6, 4)) 273 | 274 | 275 | @staticmethod 276 | def test_spread(): 277 | """ 278 | Unit test for spread function. 279 | 280 | Returns 281 | ------- 282 | Pass / Fail. 283 | 284 | """ 285 | vis.Visualizer().payoffs(payoff_type='spread') 286 | 287 | vis.Visualizer().payoffs(payoff_type='spread', 288 | S=50, K1=48, K2=52, T=1, r=0.05, q=0.01, sigma=0.3, option='put', 289 | direction='short', value=True, mpl_style='fivethirtyeight', 290 | size2d=(6, 4)) 291 | 292 | 293 | @staticmethod 294 | def test_backspread(): 295 | """ 296 | Unit test for backspread function. 297 | 298 | Returns 299 | ------- 300 | Pass / Fail. 301 | 302 | """ 303 | vis.Visualizer().payoffs(payoff_type='backspread') 304 | 305 | vis.Visualizer().payoffs(payoff_type='backspread', 306 | S=50, K1=48, K2=52, T=1, r=0.05, q=0.01, sigma=0.3, option='put', 307 | ratio=3, value=True, mpl_style='fivethirtyeight', size2d=(6, 4)) 308 | 309 | 310 | @staticmethod 311 | def test_ratio_vertical_spread(): 312 | """ 313 | Unit test for ratio vertical spread function. 314 | 315 | Returns 316 | ------- 317 | Pass / Fail. 318 | 319 | """ 320 | vis.Visualizer().payoffs(payoff_type='ratio vertical spread') 321 | 322 | vis.Visualizer().payoffs(payoff_type='ratio vertical spread', 323 | S=50, K1=48, K2=52, T=1, r=0.05, q=0.01, sigma=0.3, option='put', 324 | ratio=3, value=True, mpl_style='fivethirtyeight', size2d=(6, 4)) 325 | 326 | 327 | @staticmethod 328 | def test_straddle(): 329 | """ 330 | Unit test for straddle function. 331 | 332 | Returns 333 | ------- 334 | Pass / Fail. 335 | 336 | """ 337 | vis.Visualizer().payoffs(payoff_type='straddle') 338 | 339 | vis.Visualizer().payoffs(payoff_type='straddle', 340 | S=50, K=50, T=1, r=0.05, q=0.01, sigma=0.3, direction='short', 341 | value=True, mpl_style='fivethirtyeight', size2d=(6, 4)) 342 | 343 | 344 | @staticmethod 345 | def test_strangle(): 346 | """ 347 | Unit test for strangle function. 348 | 349 | Returns 350 | ------- 351 | Pass / Fail. 352 | 353 | """ 354 | vis.Visualizer().payoffs(payoff_type='strangle') 355 | 356 | vis.Visualizer().payoffs(payoff_type='strangle', 357 | S=50, K1=45, K2=55, T=1, r=0.05, q=0.01, sigma=0.3, 358 | direction='short', value=True, mpl_style='fivethirtyeight', 359 | size2d=(6, 4)) 360 | 361 | 362 | @staticmethod 363 | def test_butterfly(): 364 | """ 365 | Unit test for butterfly function. 366 | 367 | Returns 368 | ------- 369 | Pass / Fail. 370 | 371 | """ 372 | vis.Visualizer().payoffs(payoff_type='butterfly') 373 | 374 | vis.Visualizer().payoffs(payoff_type='butterfly', 375 | S=50, K1=45, K2=50, K3=55, T=1, r=0.05, q=0.01, sigma=0.3, 376 | option='put', direction='short', value=True, 377 | mpl_style='fivethirtyeight', size2d=(6, 4)) 378 | 379 | 380 | @staticmethod 381 | def test_christmas_tree(): 382 | """ 383 | Unit test for christmas tree function. 384 | 385 | Returns 386 | ------- 387 | Pass / Fail. 388 | 389 | """ 390 | vis.Visualizer().payoffs(payoff_type='christmas tree') 391 | 392 | vis.Visualizer().payoffs(payoff_type='christmas tree', 393 | S=50, K1=45, K2=50, K3=55, T=1, r=0.05, q=0.01, sigma=0.3, 394 | option='put', direction='short', value=True, 395 | mpl_style='fivethirtyeight', size2d=(6, 4)) 396 | 397 | 398 | @staticmethod 399 | def test_condor(): 400 | """ 401 | Unit test for condor function. 402 | 403 | Returns 404 | ------- 405 | Pass / Fail. 406 | 407 | """ 408 | vis.Visualizer().payoffs(payoff_type='condor') 409 | 410 | vis.Visualizer().payoffs(payoff_type='condor', 411 | S=50, K1=45, K2=50, K3=55, K4=60, T=1, r=0.05, q=0.01, sigma=0.3, 412 | option='put', direction='short', value=True, 413 | mpl_style='fivethirtyeight', size2d=(6, 4)) 414 | 415 | 416 | @staticmethod 417 | def test_iron_butterfly(): 418 | """ 419 | Unit test for iron butterfly function. 420 | 421 | Returns 422 | ------- 423 | Pass / Fail. 424 | 425 | """ 426 | vis.Visualizer().payoffs(payoff_type='iron butterfly') 427 | 428 | vis.Visualizer().payoffs(payoff_type='iron butterfly', 429 | S=50, K1=45, K2=50, K3=55, K4=60, T=1, r=0.05, q=0.01, sigma=0.3, 430 | direction='short', value=True, 431 | mpl_style='fivethirtyeight', size2d=(6, 4)) 432 | 433 | 434 | @staticmethod 435 | def test_iron_condor(): 436 | """ 437 | Unit test for condor function. 438 | 439 | Returns 440 | ------- 441 | Pass / Fail. 442 | 443 | """ 444 | vis.Visualizer().payoffs(payoff_type='iron condor') 445 | 446 | vis.Visualizer().payoffs(payoff_type='iron condor', 447 | S=50, K1=45, K2=50, K3=55, K4=60, T=1, r=0.05, q=0.01, sigma=0.3, 448 | direction='short', value=True, 449 | mpl_style='fivethirtyeight', size2d=(6, 4)) 450 | 451 | if __name__ == '__main__': 452 | unittest.main() 453 | -------------------------------------------------------------------------------- /tests/vis_unittest_numerical_sens.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for numerical output functions 3 | 4 | """ 5 | 6 | import unittest 7 | import optionvisualizer.visualizer as vis 8 | 9 | class OptionNumericalTestCase(unittest.TestCase): 10 | """ 11 | Unit tests for numerical output functions 12 | 13 | """ 14 | 15 | def test_price(self): 16 | """ 17 | Unit test for option pricing function. 18 | 19 | Returns 20 | ------- 21 | Pass / Fail. 22 | 23 | """ 24 | 25 | self.assertGreater(vis.Visualizer().sensitivities( 26 | greek='price', num_sens=True), 0) 27 | print("Default price: ", vis.Visualizer().sensitivities( 28 | greek='price', num_sens=True)) 29 | 30 | self.assertIsInstance(vis.Visualizer().sensitivities( 31 | greek='price', num_sens=True), float) 32 | 33 | self.assertGreater(vis.Visualizer().sensitivities( 34 | greek='price', S=50, K=55, T=1, r=0.05, q=0.01, 35 | sigma=0.3, option='put', num_sens=True), 0) 36 | print("Revalued price: ", vis.Visualizer().sensitivities( 37 | greek='price', S=50, K=55, T=1, r=0.05, q=0.01, 38 | sigma=0.3, option='put', num_sens=True)) 39 | 40 | self.assertIsInstance(vis.Visualizer().sensitivities( 41 | greek='price', S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, 42 | option='put', num_sens=True), float) 43 | 44 | 45 | def test_delta(self): 46 | """ 47 | Unit test for delta function. 48 | 49 | Returns 50 | ------- 51 | Pass / Fail. 52 | 53 | """ 54 | self.assertGreater(abs(vis.Visualizer().sensitivities( 55 | greek='delta', num_sens=True)), 0) 56 | print("Default delta: ", vis.Visualizer().sensitivities( 57 | greek='delta', num_sens=True)) 58 | 59 | self.assertIsInstance(vis.Visualizer().sensitivities( 60 | greek='delta', num_sens=True), float) 61 | 62 | self.assertGreater(abs(vis.Visualizer().sensitivities( 63 | greek='delta', num_sens=True, 64 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 65 | print("Revalued delta: ", vis.Visualizer().sensitivities( 66 | greek='delta', num_sens=True, 67 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 68 | 69 | self.assertIsInstance(vis.Visualizer().sensitivities( 70 | greek='delta', num_sens=True, 71 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 72 | 73 | 74 | def test_theta(self): 75 | """ 76 | Unit test for theta function. 77 | 78 | Returns 79 | ------- 80 | Pass / Fail. 81 | 82 | """ 83 | self.assertGreater(abs(vis.Visualizer().sensitivities( 84 | greek='theta', num_sens=True)), 0) 85 | print("Default theta: ", vis.Visualizer().sensitivities( 86 | greek='theta', num_sens=True)) 87 | 88 | self.assertIsInstance(vis.Visualizer().sensitivities( 89 | greek='theta', num_sens=True), float) 90 | 91 | self.assertGreater(abs(vis.Visualizer().sensitivities( 92 | greek='theta', num_sens=True, 93 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 94 | print("Revalued theta: ", vis.Visualizer().sensitivities( 95 | greek='theta', num_sens=True, 96 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 97 | 98 | self.assertIsInstance(vis.Visualizer().sensitivities( 99 | greek='theta', num_sens=True, 100 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 101 | 102 | 103 | def test_gamma(self): 104 | """ 105 | Unit test for gamma function. 106 | 107 | Returns 108 | ------- 109 | Pass / Fail. 110 | 111 | """ 112 | self.assertGreater(abs(vis.Visualizer().sensitivities( 113 | greek='gamma', num_sens=True)), 0) 114 | print("Default gamma: ", vis.Visualizer().sensitivities( 115 | greek='gamma', num_sens=True)) 116 | 117 | self.assertIsInstance(vis.Visualizer().sensitivities( 118 | greek='gamma', num_sens=True), float) 119 | 120 | self.assertGreater(abs(vis.Visualizer().sensitivities( 121 | greek='gamma', num_sens=True, 122 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 123 | print("Revalued gamma: ", vis.Visualizer().sensitivities( 124 | greek='gamma', num_sens=True, 125 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 126 | 127 | self.assertIsInstance(vis.Visualizer().sensitivities( 128 | greek='gamma', num_sens=True, 129 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 130 | 131 | 132 | def test_vega(self): 133 | """ 134 | Unit test for vega function. 135 | 136 | Returns 137 | ------- 138 | Pass / Fail. 139 | 140 | """ 141 | self.assertGreater(abs(vis.Visualizer().sensitivities( 142 | greek='vega', num_sens=True)), 0) 143 | print("Default vega: ", vis.Visualizer().sensitivities( 144 | greek='vega', num_sens=True)) 145 | 146 | self.assertIsInstance(vis.Visualizer().sensitivities( 147 | greek='vega', num_sens=True), float) 148 | 149 | self.assertGreater(abs(vis.Visualizer().sensitivities( 150 | greek='vega', num_sens=True, 151 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 152 | print("Revalued vega: ", vis.Visualizer().sensitivities( 153 | greek='vega', num_sens=True, 154 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 155 | 156 | self.assertIsInstance(vis.Visualizer().sensitivities( 157 | greek='vega', num_sens=True, 158 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 159 | 160 | 161 | def test_rho(self): 162 | """ 163 | Unit test for rho function. 164 | 165 | Returns 166 | ------- 167 | Pass / Fail. 168 | 169 | """ 170 | self.assertGreater(abs(vis.Visualizer().sensitivities( 171 | greek='rho', num_sens=True)), 0) 172 | print("Default rho: ", vis.Visualizer().sensitivities( 173 | greek='rho', num_sens=True)) 174 | 175 | self.assertIsInstance(vis.Visualizer().sensitivities( 176 | greek='rho', num_sens=True), float) 177 | 178 | self.assertGreater(abs(vis.Visualizer().sensitivities( 179 | greek='rho', num_sens=True, 180 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 181 | print("Revalued rho: ", vis.Visualizer().sensitivities( 182 | greek='rho', num_sens=True, 183 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 184 | 185 | self.assertIsInstance(vis.Visualizer().sensitivities( 186 | greek='rho', num_sens=True, 187 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 188 | 189 | 190 | def test_vanna(self): 191 | """ 192 | Unit test for vanna function. 193 | 194 | Returns 195 | ------- 196 | Pass / Fail. 197 | 198 | """ 199 | self.assertGreater(abs(vis.Visualizer().sensitivities( 200 | greek='vanna', num_sens=True)), 0) 201 | print("Default vanna: ", vis.Visualizer().sensitivities( 202 | greek='vanna', num_sens=True)) 203 | 204 | self.assertIsInstance(vis.Visualizer().sensitivities( 205 | greek='vanna', num_sens=True), float) 206 | 207 | self.assertGreater(abs(vis.Visualizer().sensitivities( 208 | greek='vanna', num_sens=True, 209 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 210 | print("Revalued vanna: ", vis.Visualizer().sensitivities( 211 | greek='vanna', num_sens=True, 212 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 213 | 214 | self.assertIsInstance(vis.Visualizer().sensitivities( 215 | greek='vanna', num_sens=True, 216 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 217 | 218 | 219 | def test_vomma(self): 220 | """ 221 | Unit test for vomma function. 222 | 223 | Returns 224 | ------- 225 | Pass / Fail. 226 | 227 | """ 228 | self.assertGreater(abs(vis.Visualizer().sensitivities( 229 | greek='vomma', num_sens=True)), 0) 230 | print("Default vomma: ", vis.Visualizer().sensitivities( 231 | greek='vomma', num_sens=True)) 232 | 233 | self.assertIsInstance(vis.Visualizer().sensitivities( 234 | greek='vomma', num_sens=True), float) 235 | 236 | self.assertGreater(abs(vis.Visualizer().sensitivities( 237 | greek='vomma', num_sens=True, 238 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 239 | print("Revalued vomma: ", vis.Visualizer().sensitivities( 240 | greek='vomma', num_sens=True, 241 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 242 | 243 | self.assertIsInstance(vis.Visualizer().sensitivities( 244 | greek='vomma', num_sens=True, 245 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 246 | 247 | 248 | def test_charm(self): 249 | """ 250 | Unit test for charm function. 251 | 252 | Returns 253 | ------- 254 | Pass / Fail. 255 | 256 | """ 257 | self.assertGreater(abs(vis.Visualizer().sensitivities( 258 | greek='charm', num_sens=True)), 0) 259 | print("Default charm: ", vis.Visualizer().sensitivities( 260 | greek='charm', num_sens=True)) 261 | 262 | self.assertIsInstance(vis.Visualizer().sensitivities( 263 | greek='charm', num_sens=True), float) 264 | 265 | self.assertGreater(abs(vis.Visualizer().sensitivities( 266 | greek='charm', num_sens=True, 267 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 268 | print("Revalued charm: ", vis.Visualizer().sensitivities( 269 | greek='charm', num_sens=True, 270 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 271 | 272 | self.assertIsInstance(vis.Visualizer().sensitivities( 273 | greek='charm', num_sens=True, 274 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 275 | 276 | 277 | def test_zomma(self): 278 | """ 279 | Unit test for zomma function. 280 | 281 | Returns 282 | ------- 283 | Pass / Fail. 284 | 285 | """ 286 | self.assertGreater(abs(vis.Visualizer().sensitivities( 287 | greek='zomma', num_sens=True)), 0) 288 | print("Default zomma: ", vis.Visualizer().sensitivities( 289 | greek='zomma', num_sens=True)) 290 | 291 | self.assertIsInstance(vis.Visualizer().sensitivities( 292 | greek='zomma', num_sens=True), float) 293 | 294 | self.assertGreater(abs(vis.Visualizer().sensitivities( 295 | greek='zomma', num_sens=True, 296 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 297 | print("Revalued zomma: ", vis.Visualizer().sensitivities( 298 | greek='zomma', num_sens=True, 299 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 300 | 301 | self.assertIsInstance(vis.Visualizer().sensitivities( 302 | greek='zomma', num_sens=True, 303 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 304 | 305 | 306 | def test_speed(self): 307 | """ 308 | Unit test for speed function. 309 | 310 | Returns 311 | ------- 312 | Pass / Fail. 313 | 314 | """ 315 | self.assertGreater(abs(vis.Visualizer().sensitivities( 316 | greek='speed', num_sens=True)), 0) 317 | print("Default speed: ", vis.Visualizer().sensitivities( 318 | greek='speed', num_sens=True)) 319 | 320 | self.assertIsInstance(vis.Visualizer().sensitivities( 321 | greek='speed', num_sens=True), float) 322 | 323 | self.assertGreater(abs(vis.Visualizer().sensitivities( 324 | greek='speed', num_sens=True, 325 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 326 | print("Revalued speed: ", vis.Visualizer().sensitivities( 327 | greek='speed', num_sens=True, 328 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 329 | 330 | self.assertIsInstance(vis.Visualizer().sensitivities( 331 | greek='speed', num_sens=True, 332 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 333 | 334 | 335 | def test_color(self): 336 | """ 337 | Unit test for color function. 338 | 339 | Returns 340 | ------- 341 | Pass / Fail. 342 | 343 | """ 344 | self.assertGreater(abs(vis.Visualizer().sensitivities( 345 | greek='color', num_sens=True)), 0) 346 | print("Default color: ", vis.Visualizer().sensitivities( 347 | greek='color', num_sens=True)) 348 | 349 | self.assertIsInstance(vis.Visualizer().sensitivities( 350 | greek='color', num_sens=True), float) 351 | 352 | self.assertGreater(abs(vis.Visualizer().sensitivities( 353 | greek='color', num_sens=True, 354 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 355 | print("Revalued color: ", vis.Visualizer().sensitivities( 356 | greek='color', num_sens=True, 357 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 358 | 359 | self.assertIsInstance(vis.Visualizer().sensitivities( 360 | greek='color', num_sens=True, 361 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 362 | 363 | 364 | def test_ultima(self): 365 | """ 366 | Unit test for ultima function. 367 | 368 | Returns 369 | ------- 370 | Pass / Fail. 371 | 372 | """ 373 | self.assertGreater(abs(vis.Visualizer().sensitivities( 374 | greek='ultima', num_sens=True)), 0) 375 | print("Default ultima: ", vis.Visualizer().sensitivities( 376 | greek='ultima', num_sens=True)) 377 | 378 | self.assertIsInstance(vis.Visualizer().sensitivities( 379 | greek='ultima', num_sens=True), float) 380 | 381 | self.assertGreater(abs(vis.Visualizer().sensitivities( 382 | greek='ultima', num_sens=True, 383 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 384 | print("Revalued ultima: ", vis.Visualizer().sensitivities( 385 | greek='ultima', num_sens=True, 386 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 387 | 388 | self.assertIsInstance(vis.Visualizer().sensitivities( 389 | greek='ultima', num_sens=True, 390 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 391 | 392 | 393 | def test_vega_bleed(self): 394 | """ 395 | Unit test for vega bleed function. 396 | 397 | Returns 398 | ------- 399 | Pass / Fail. 400 | 401 | """ 402 | self.assertGreater(abs(vis.Visualizer().sensitivities( 403 | greek='vega bleed', num_sens=True)), 0) 404 | print("Default vega_bleed: ", vis.Visualizer().sensitivities( 405 | greek='vega bleed', num_sens=True)) 406 | 407 | self.assertIsInstance(vis.Visualizer().sensitivities( 408 | greek='vega bleed', num_sens=True), float) 409 | 410 | self.assertGreater(abs(vis.Visualizer().sensitivities( 411 | greek='vega bleed', num_sens=True, 412 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 413 | print("Revalued vega_bleed: ", vis.Visualizer().sensitivities( 414 | greek='vega bleed', num_sens=True, 415 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 416 | 417 | self.assertIsInstance(vis.Visualizer().sensitivities( 418 | greek='vega bleed', num_sens=True, 419 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 420 | 421 | 422 | def test_numerical_sensitivities(self): 423 | """ 424 | Unit test for numerical sensitivities function. 425 | 426 | Returns 427 | ------- 428 | Pass / Fail. 429 | 430 | """ 431 | self.assertGreater( 432 | abs(vis.Visualizer().sensitivities(num_sens=True)), 0) 433 | print("Default numerical_sensitivities: ", 434 | vis.Visualizer().sensitivities(num_sens=True)) 435 | 436 | self.assertIsInstance( 437 | vis.Visualizer().sensitivities(num_sens=True), float) 438 | 439 | self.assertGreater(abs(vis.Visualizer().sensitivities(num_sens=True, 440 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')), 0) 441 | print("Revalued numerical_sensitivities: ", 442 | vis.Visualizer().sensitivities(num_sens=True, 443 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put')) 444 | 445 | self.assertIsInstance(vis.Visualizer().sensitivities(num_sens=True, 446 | S=50, K=55, T=1, r=0.05, q=0.01, sigma=0.3, option='put'), float) 447 | 448 | 449 | def test_barrier(self): 450 | """ 451 | Unit test for barrier pricing function. 452 | 453 | Returns 454 | ------- 455 | Pass / Fail. 456 | 457 | """ 458 | self.assertGreater(abs(vis.Visualizer().barrier()), 0) 459 | print("Default barrier_price: ", vis.Visualizer().barrier()) 460 | 461 | self.assertIsInstance(vis.Visualizer().barrier(), float) 462 | 463 | self.assertGreater(abs(vis.Visualizer().barrier( 464 | S=50, K=55, H=60, R=0.1, T=1, r=0.05, q=0.01, sigma=0.3, 465 | option='put', barrier_direction='up', knock='out')), 0) 466 | print("Revalued barrier_price: ", vis.Visualizer().barrier( 467 | S=50, K=55, H=60, R=0.1, T=1, r=0.05, q=0.01, sigma=0.3, 468 | option='put', barrier_direction='up', knock='out')) 469 | 470 | self.assertIsInstance(vis.Visualizer().barrier( 471 | S=50, K=55, H=60, R=0.1, T=1, r=0.05, q=0.01, sigma=0.3, 472 | option='put', barrier_direction='up', knock='out'), float) 473 | 474 | 475 | if __name__ == '__main__': 476 | unittest.main() 477 | --------------------------------------------------------------------------------