├── .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 | 
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 | 
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 | 
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 | 
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 | 
134 |
135 |
136 |
137 | #### Short Gamma
138 | ```
139 | opt.visualize(risk=True, graphtype='3D', greek='gamma', direction='short')
140 | ```
141 |
142 | 
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 | 
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 | 
161 |
162 |
163 |
164 | #### Long Vanna
165 | ```
166 | opt.visualize(risk=True, graphtype='3D', greek='vanna', sigma=0.4, interactive=True)
167 | ```
168 | 
169 |
170 |
171 |
172 | #### Short Zomma
173 | ```
174 | opt.visualize(risk=True, graphtype='3D', greek='zomma', direction='short', interactive=True)
175 | ```
176 | 
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 | 
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 | 
210 |
211 |
212 |
213 | #### Long Straddle:
214 | ```
215 | opt.visualize(risk=False, payoff_type='straddle', mpl_style='ggplot')
216 | ```
217 | 
218 |
219 |
220 |
221 | #### Short Christmas Tree:
222 | ```
223 | opt.visualize(risk=False, payoff_type='christmas tree', value=True, direction='short')
224 | ```
225 | 
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 |
--------------------------------------------------------------------------------