├── .gitignore ├── .idea ├── Quantsbin.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── MANIFEST ├── README.md ├── REQUIREMENTS.txt ├── development_testing.py ├── quantsbin ├── __init__.py ├── derivativepricing │ ├── __init__.py │ ├── engineconfig.py │ ├── helperfn.py │ ├── instruments.py │ ├── namesnmapper.py │ ├── numericalgreeks.py │ ├── optionstrategies.py │ ├── plotting.py │ └── pricingmodels.py └── montecarlo │ ├── __init__.py │ ├── namesnmapper.py │ └── stimulations.py ├── setup.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | #pycharm 2 | __pycache__/ 3 | .idea/ 4 | 5 | #vscode 6 | .vscode/ 7 | 8 | #jupyter notebooks 9 | .ipynb_checkpoints 10 | 11 | #internal test file 12 | development_testing.py 13 | 14 | -------------------------------------------------------------------------------- /.idea/Quantsbin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 quantsbin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.py 3 | quantsbin/__init__.py 4 | quantsbin/derivativepricing/__init__.py 5 | quantsbin/derivativepricing/engineconfig.py 6 | quantsbin/derivativepricing/helperfn.py 7 | quantsbin/derivativepricing/instruments.py 8 | quantsbin/derivativepricing/namesnmapper.py 9 | quantsbin/derivativepricing/numericalgreeks.py 10 | quantsbin/derivativepricing/optionstrategies.py 11 | quantsbin/derivativepricing/plotting.py 12 | quantsbin/derivativepricing/pricingmodels.py 13 | quantsbin/montecarlo/__init__.py 14 | quantsbin/montecarlo/namesnmapper.py 15 | quantsbin/montecarlo/stimulations.py 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPi version](https://img.shields.io/pypi/v/quantsbin.svg?maxAge=60)](https://pypi.python.org/pypi/pandas-montecarlo) [![PyPi Status](https://img.shields.io/pypi/status/quantsbin.svg?maxAge=60)](https://pypi.python.org/pypi/quantsbin) [![Github Stars](https://img.shields.io/github/stars/quantsbin/Quantsbin.svg?style=social&label=Star&maxAge=60)](https://github.com/quantsbin/Quantsbin) [![Twitter follows](https://img.shields.io/twitter/follow/quantsbin.svg?style=social&label=Follow%20Me&maxAge=60)](https://twitter.com/quantsbin) 2 | 3 | # Quantsbin 4 | 5 | Open source library for finance. 6 | 7 | Quantsbin 1.0.3, which started as a weekend project is currently in its initial phase and 8 | incorporates tools for pricing and plotting of vanilla option prices, greeks and various other analysis around them. 9 | We are working on optimising calculations and expanding the scope of library in multiple directions for future releases. 10 | 11 | ## Quantsbin 1.0.3 includes 12 | 1. Option payoff, premium and greeks calculation for vanilla options on Equity, FX, Commodity and Futures. 13 | 2. Capability to calculate greeks numerically for all models and also analytically for Black Scholes Model. 14 | 3. Price vanilla options with European expiry using BSM, Binomial tree and MonteCarlo with option to 15 | incorporate continuous compounded dividend yield for Equity options, 16 | cost and convenience yield for Commodity options and 17 | local and foreign risk-free rate in case of FX options. 18 | It also allows option to give discrete dividends in cased of Equity options. 19 | 4. Price vanilla options with American expiry using Binomial tree and MonteCarlo(Longstaff Schwartz) method. 20 | There is option to provide discrete dividends for Equity options for both the models. 21 | 5. Implied volatility calculation under BSM framework model. 22 | 6. Option to create user defined or standard strategies using multiple single underlying options and 23 | directly generate and plot valuation and greeks for these strategies. 24 | 25 | ## License 26 | [MIT LICENCE](https://github.com/quantsbin/Quantsbin/blob/master/LICENSE/) 27 | 28 | ## Dependencies and Installation details 29 | scipy==1.6.3 30 | pandas==1.2.4 31 | matplotlib==3.4.2 32 | numpy==1.18.0 33 | 34 | Install using setup.py: 35 | ``` 36 | >>> python setup.py install 37 | ``` 38 | Install using pip: 39 | ``` 40 | >>> pip install quantsbin 41 | ``` 42 | 43 | ## Detailed documentation 44 | Refer to our [Documentation](http://www.quantsbin.com/introduction-to-option-pricing-using-python-library-quantsbin/) page 45 | 46 | ## Our Website 47 | For collaboration and suggestion reach us at [Quantsbin](http://www.quantsbin.com/) 48 | 49 | ## Tutorial 50 | Refer to our [Tutorial](http://www.quantsbin.com/introduction-to-option-pricing-using-python-library-quantsbin/) page 51 | 52 | ## Note 53 | For Quantsbin 1.0.3 documentation are still WIP. 54 | -------------------------------------------------------------------------------- /REQUIREMENTS.txt: -------------------------------------------------------------------------------- 1 | scipy==1.6.3 2 | pandas==1.2.4 3 | matplotlib==3.4.2 4 | numpy==1.18.0 5 | -------------------------------------------------------------------------------- /development_testing.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | import quantsbin.derivativepricing as qbdp 7 | 8 | import quantsbin.derivativepricing.plotting as pt 9 | 10 | 11 | """Defining options""" 12 | eqOption1 = qbdp.EqOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='European') 13 | eqOption2 = qbdp.EqOption(option_type='Call', strike=110, expiry_date='20180630', expiry_type='European') 14 | 15 | fxOption1 = qbdp.FXOption(option_type='Call', strike=98, expiry_date='20190630', expiry_type='European') 16 | fxOption2 = qbdp.FXOption(option_type='Put', strike=95, expiry_date='20180630', expiry_type='American') 17 | 18 | futOption1 = qbdp.FutOption(option_type='Put', strike=102, expiry_date='20190630', expiry_type='American') 19 | futOption2 = qbdp.FutOption(option_type='Call', strike=95, expiry_date='20180630', expiry_type='European') 20 | 21 | comOption1 = qbdp.ComOption(option_type='Put', strike=95, expiry_date='20190630', expiry_type='European') 22 | comOption2 = qbdp.ComOption(option_type='Call', strike=105, expiry_date='20190630', expiry_type='American') 23 | 24 | """Creating list of all defined option for consolidate testing""" 25 | 26 | # option_list = [eqOption1, eqOption2, fxOption1, fxOption2, futOption1, futOption2, comOption1, comOption2] 27 | # 28 | # # print(eqOption2.payoff(0)) 29 | # 30 | # """Payoff plot testing""" 31 | # for option in option_list: 32 | # payoff_plt = qbdp.Plotting(option, "payoff", x_axis_range=[0, 200]).line_plot() 33 | # payoff_plt.show() 34 | 35 | 36 | """Testing payoff""" 37 | # for option in option_list: 38 | # print("Payoff for option {} with strike {} at {} is {}".format(option.option_type, 39 | # option.strike, 110, option.payoff(110))) 40 | # print("Payoff for option {} with strike {} at {} is {}".format(option.option_type, 41 | # option.strike, 95, option.payoff(95))) 42 | # """Testing model list generation""" 43 | # for option in option_list: 44 | # print("Models available for pricing {} option are {}".format(option.undl, option.list_models())) 45 | 46 | print(eqOption1.engine.__doc__) 47 | 48 | 49 | eqOption1_pricer = eqOption1.engine(model="BSM", spot0=100, pricing_date='20180531', volatility=.25, 50 | rf_rate=.05, pv_div=0.0, 51 | yield_div=0.0, seed=12) 52 | 53 | print(eqOption1_pricer.risk_parameters_num()) 54 | 55 | # payoff_plt = qbdp.Plotting(eqOption1, "payoff", x_axis_range=[0, 200]).line_plot() 56 | 57 | # delta_plt = qbdp.Plotting(eqOption1_pricer, "Gamma", x_axis_range=[0, 200]).line_plot() 58 | # 59 | # delta_plt.show() 60 | 61 | OPTION1 = qbdp.OptionStr1Udl([(eqOption1, 1), (eqOption2, -1)]) 62 | OPTION1_Pricer = OPTION1.engine(model="BSM", spot0=100, pricing_date='20180531', volatility=.25, 63 | rf_rate=.05, pv_div=0.0, yield_div=0.0, seed=12) 64 | 65 | test_plot = qbdp.Plotting(eqOption1_pricer,'pnl', x_axis_range=[50, 150]).line_plot() 66 | test_plot.show() 67 | # option_payoff = qbdp.Plotting(OPTION1, "payoff", x_axis_range=[0, 200]).line_plot() 68 | # 69 | # option_payoff.show() 70 | 71 | # option_val = qbdp.Plotting(OPTION1_Pricer, "valuation", x_axis_range=[0, 200]).line_plot() 72 | # option_val.show() 73 | 74 | abc = qbdp.StdStrategies(name="bull_call", expiry_date="20180630", expiry_type=None, low_strike=100, spread=10) 75 | abc_engine = abc.engine(model="BSM", spot0=100, pricing_date='20180531', volatility=.25, 76 | rf_rate=.05, pv_div=0.0, yield_div=0.0, seed=12) 77 | 78 | 79 | # stg_payoff = qbdp.Plotting(abc, "payoff", x_axis_range=[75, 125]).line_plot() 80 | 81 | stg_gamma = qbdp.Plotting(OPTION1_Pricer, "valuation", x_axis_range=[75, 125]).line_plot() 82 | stg_gamma.show() 83 | 84 | 85 | """Testing for incorrect model - should raise assertion error""" 86 | # eqOption2_pricer = eqOption2.engine(model="BSM", spot0=100, pricing_date='20180531', volatility=.1, 87 | # rf_rate=.05, pv_div=0.0, 88 | # yield_div=0.0, seed=12) 89 | 90 | """BS Framework Testing""" 91 | 92 | # div_list = [("20180610", 2), ("20180624", 4)] 93 | # 94 | # """Equity Options BSM Testing""" 95 | # eqOption_BSM_test = qbdp.EqOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='European') 96 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="BSM", spot0=110, pricing_date='20180531', volatility=.25, 97 | # rf_rate=.05, div_list=None, yield_div=0.01, seed=12) 98 | # 99 | # print(eqOption_BSM_test_pricer.valuation()) 100 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 101 | # print(eqOption_BSM_test_pricer.risk_parameters()) 102 | 103 | # """Futures Option B76 Testing""" 104 | # eqOption_BSM_test = qbdp.FutOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='European') 105 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="B76", fwd0=110, pricing_date='20180531', volatility=.25, 106 | # rf_rate=.05, seed=12) 107 | # 108 | # print(eqOption_BSM_test_pricer.valuation()) 109 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 110 | # print(eqOption_BSM_test_pricer.risk_parameters()) 111 | 112 | # """COM Option GK Testing""" 113 | # eqOption_BSM_test = qbdp.ComOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='European') 114 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="GK", spot0=110, pricing_date='20180531', volatility=.25, 115 | # rf_rate=.05, cost_yield=0.03, cnv_yield=0.01, seed=12) 116 | # 117 | # print(eqOption_BSM_test_pricer.valuation()) 118 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 119 | # print(eqOption_BSM_test_pricer.risk_parameters()) 120 | 121 | # # 122 | # """EqOption using Binomial""" 123 | # eqOption_BSM_test = qbdp.EqOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='American') 124 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="Binomial", spot0=110, pricing_date='20180531', volatility=.25, 125 | # rf_rate=.05, div_list=div_list, yield_div=0.01, seed=12) 126 | # 127 | # print(eqOption_BSM_test_pricer.valuation()) 128 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 129 | # print(eqOption_BSM_test_pricer.risk_parameters()) 130 | 131 | # """FutOption using Binomial""" 132 | # eqOption_BSM_test = qbdp.FutOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='American') 133 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="Binomial", fwd0=110, pricing_date='20180531', volatility=.25, 134 | # rf_rate=.05, seed=12) 135 | # 136 | # print(eqOption_BSM_test_pricer.valuation()) 137 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 138 | 139 | # """Comm using Binomial""" 140 | # eqOption_BSM_test = qbdp.ComOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='European') 141 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="Binomial", spot0=110, pricing_date='20180531', volatility=.25, 142 | # rf_rate=.05, cost_yield=0.03, cnv_yield=0.01) 143 | # 144 | # print(eqOption_BSM_test_pricer.valuation()) 145 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 146 | 147 | 148 | # """Comm using MC""" 149 | # eqOption_BSM_test = qbdp.ComOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='European') 150 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="MC_GBM", spot0=110, pricing_date='20180531', volatility=.25, 151 | # rf_rate=.05, cost_yield=0.03, cnv_yield=0.01) 152 | # 153 | # print(eqOption_BSM_test_pricer.valuation()) 154 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 155 | 156 | # """Fut using MC""" 157 | # eqOption_BSM_test = qbdp.FutOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='American') 158 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="MC_GBM", fwd0=110, pricing_date='20180531', volatility=.25, 159 | # rf_rate=.05, seed=153) 160 | # 161 | # print(eqOption_BSM_test_pricer.valuation()) 162 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 163 | 164 | # 165 | # """Equity using MC""" 166 | # eqOption_BSM_test = qbdp.EqOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='American') 167 | # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="MC_GBM", spot0=110, pricing_date='20180531', volatility=.25, 168 | # rf_rate=.05, div_list=div_list, yield_div=0.01, seed=153) 169 | # 170 | # print(eqOption_BSM_test_pricer.valuation()) 171 | # print(eqOption_BSM_test_pricer.risk_parameters_num()) 172 | 173 | 174 | 175 | 176 | """strategy1_constituents = [(eqOption1, 1), (eqOption2, -1)] 177 | 178 | option_strategy1 = qbdp.OptionStr1Udl(strategy1_constituents) 179 | 180 | # print(option_strategy1.payoff(105)) 181 | 182 | # print(qbdp.StdStrategies(name="strip", asset="Stock", expiry_date='20180630', expiry_type="European", low_strike=100 183 | # , spread=10).payoff(0)) 184 | 185 | # payoff = eqOption1.payoff(110) 186 | # 187 | # # print(payoff) 188 | # 189 | eqOption1_pricer = eqOption1.engine(model="Binomial", spot0=100, pricing_date='20180531', volatility=.1, 190 | rf_rate=.05, pv_div=0.0, 191 | yield_div=0.0, seed=12) 192 | 193 | premium = eqOption1_pricer.valuation() 194 | 195 | print(premium) 196 | 197 | Plotting 198 | 199 | # eqOption1.plot("payoff", x_axis="spot", range=[50, 150]) 200 | # 201 | # eqOption1_pricer.plot("valuation", spot_range=[50, 150]) 202 | # 203 | # eqOption1_pricer.plot("delta", spot_range=[50, 150]) 204 | 205 | Plotting code structure 206 | 207 | # Construct spot range 208 | # range 50 to 150 209 | # 210 | # map (func, range ) 211 | # 212 | # func set attr x_axis, first variable 213 | 214 | # print(premium) 215 | 216 | # print(eqOption1_pricer.risk_parameters()) 217 | 218 | # payoff = pt.Plotting(eqOption1, "payoff", x_axis_range=[0, 150]).line_plot() 219 | # payoff.show() 220 | # 221 | # premium = pt.Plotting(eqOption1_pricer, "valuation", x_axis_range=[50, 150]).line_plot() 222 | # premium.show() 223 | 224 | delta = pt.Plotting(eqOption1_pricer, "delta", x_axis_range=[50, 200]).line_plot() 225 | print(delta) 226 | delta.show() 227 | 228 | """ -------------------------------------------------------------------------------- /quantsbin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code is still in its development phase. 3 | 4 | Developer: Quantsbin (Jasmeet) 5 | Last Modified Date: 4th March 2018 6 | """ 7 | 8 | -------------------------------------------------------------------------------- /quantsbin/derivativepricing/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from .instruments import EqOption, FutOption, FXOption, ComOption 7 | from .optionstrategies import OptionStr1Udl, StdStrategies 8 | from .plotting import Plotting -------------------------------------------------------------------------------- /quantsbin/derivativepricing/engineconfig.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from .namesnmapper import MODEL_MAPPER, IV_MODELS, ANALYTICAL_GREEKS, OBJECT_MODEL 7 | from .numericalgreeks import NumericalGreeks 8 | 9 | 10 | class PricingEngine: 11 | """ 12 | Maps engine from instrument to model class. 13 | """ 14 | 15 | def __init__(self, instrument, model, **kwargs): 16 | self.instrument = instrument 17 | self._model = model 18 | self._other_args = kwargs 19 | self.check_model = self.model_check() 20 | 21 | @property 22 | def _model_class(self): 23 | """ 24 | Maps pricing model class according to the type of Instrument 25 | Args required: 26 | model: pricing model given as argument or the default value(default value set to BSM for European expiry) 27 | **kwargs: Dictionary of parameters and their corresponding value required for valuation 28 | """ 29 | return MODEL_MAPPER[self._model](self.instrument, **self._other_args) 30 | 31 | def model_check(self): 32 | """ 33 | Asserts pricing model mapped to the Instrument in the "namesmapper" and raises assertion error if not 34 | Args required: 35 | self.model: property defined in engineconfig module under Pricing Engine class 36 | """ 37 | assert self._model in OBJECT_MODEL[self.instrument.undl][self.instrument.expiry_type], \ 38 | "Model not valid please check available models using option.list_models()" 39 | return True 40 | 41 | def valuation(self): 42 | """ 43 | Maps to the valuation method defined under required pricing model class 44 | Args required: 45 | **kwargs: Dictionary of parameters and their corresponding value required for valuation. 46 | For arguments required and method available for each model check\ 47 | help(.derivativepricing.pricingmodels.) 48 | """ 49 | return self._model_class.valuation() 50 | 51 | def risk_parameters(self, delta_spot=None, delta_vol=None, delta_rf_rate=None, 52 | delta_time=None, delta_rf_conv=None, delta_cost_yield=None, **kwargs): 53 | """ 54 | Maps to the riskparameters method defined under required pricing model class 55 | For American type of options only numerical greeks are calculated 56 | Args required: 57 | **kwargs: Dictionary of parameters and their corresponding value required for risk_parameters. 58 | For arguments required and method available for each model check\ 59 | help(.derivativepricing.pricingmodels.) 60 | """ 61 | if self._model in ANALYTICAL_GREEKS: 62 | return self._model_class.risk_parameters() 63 | else: 64 | return self.risk_parameters_num(delta_spot=delta_spot, delta_vol=delta_vol 65 | , delta_rf_rate=delta_rf_rate, delta_time=delta_time 66 | , delta_rf_conv=delta_rf_conv, delta_cost_yield=delta_cost_yield, **kwargs) 67 | 68 | def risk_parameters_num(self, delta_spot=None, delta_vol=None, delta_rf_rate=None, 69 | delta_time=None, delta_rf_conv=None, delta_cost_yield=None, **kwargs): 70 | """ 71 | Maps to the risk_parameters_num method defined in numericalgreeks module. 72 | Args required: 73 | **kwargs: Dictionary of parameters and their corresponding value required for risk_parameters_num. 74 | For arguments required and method available for each model check\ 75 | help(.numericalgreeks.NumericalGreeks) 76 | """ 77 | ng = NumericalGreeks(self._model_class, delta_spot, delta_vol, delta_rf_rate, 78 | delta_time, delta_rf_conv, delta_cost_yield, **kwargs) 79 | return ng.risk_parameters_num() 80 | 81 | def risk_parameters_num_func(self): 82 | ng = NumericalGreeks(self._model_class) 83 | return ng.risk_parameters_num_func() 84 | 85 | def pnl_attribution(self, delta_spot=None, delta_vol=None, delta_rf_rate=None, 86 | delta_time=None, delta_rf_conv=None, delta_rf_foreign=None,**kwargs): 87 | """ 88 | Maps to the pnl_attribution method defined in numericalgreeks module. 89 | Args required: 90 | **kwargs: Dictionary of parameters and their corresponding value required for pnl_attribution. 91 | For arguments required and method available for each model check\ 92 | help(.numericalgreeks.NumericalGreeks) 93 | """ 94 | ng = NumericalGreeks(self._model_class, delta_spot, delta_vol, delta_rf_rate, 95 | delta_time, delta_rf_conv, delta_rf_foreign,**kwargs) 96 | return ng.pnl_attribution() 97 | 98 | def risk_parameters_func(self): 99 | if self._model in ANALYTICAL_GREEKS: 100 | return self._model_class.risk_parameters_func() 101 | else: 102 | return self.risk_parameters_num_func() 103 | 104 | def imply_volatility(self, premium): 105 | """ 106 | Maps to the imply_volatility method defined in pricingmodels module under required model class. 107 | Args required: 108 | **kwargs: Dictionary of parameters and their corresponding value required for valuation. 109 | For the implied volatility calculation in addition to the **kwargs premium is also taken as input 110 | For arguments required and method available for each model check\ 111 | help(.derivativepricing.pricingmodels.) 112 | """ 113 | if self._model in IV_MODELS: 114 | return self._model_class.imply_volatility(premium) 115 | else: 116 | raise NameError("implied volatility method not defined for " + self._model + " model") 117 | -------------------------------------------------------------------------------- /quantsbin/derivativepricing/helperfn.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from datetime import datetime as dt 7 | from math import e 8 | 9 | 10 | def dividend_processor(div_list, pricing_date, expiry_date): 11 | div_processed = [] 12 | if div_list: 13 | for date, div_amount in div_list: 14 | ex_date = dt.strptime(date, '%Y%m%d') 15 | if (ex_date > pricing_date) and (ex_date <= expiry_date): 16 | div_time = (ex_date-pricing_date).days/365.0 17 | div_processed.append((div_time, div_amount)) 18 | return div_processed 19 | 20 | 21 | def pv_div(div_processed, time_point, disc_rate): 22 | pv_div_amount = 0 23 | if len(div_processed) == 0: return pv_div_amount 24 | for div_time, div_amount in div_processed: 25 | disc_time = div_time - time_point 26 | if disc_time >= 0: 27 | pv_div_amount += (div_amount * (e**(-1*disc_rate*disc_time))) 28 | return pv_div_amount 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /quantsbin/derivativepricing/instruments.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from abc import ABCMeta, abstractmethod 7 | from datetime import datetime 8 | 9 | from .engineconfig import PricingEngine 10 | from .namesnmapper import VanillaOptionType, ExpiryType, DEFAULT_MODEL, UdlType, OBJECT_MODEL, DerivativeType 11 | 12 | 13 | class Instrument(metaclass=ABCMeta): 14 | """ 15 | Instrument - Metaclass to define financial instrument 16 | 17 | @abstract functions: 18 | payoff => defines payoff on instrument. 19 | engine => attach the instrument with the pricing model and market data. 20 | """ 21 | 22 | @abstractmethod 23 | def payoff(self): 24 | pass 25 | 26 | def engine(self, **kwargs): 27 | """ 28 | Binds pricing model class and market data to the object 29 | Args required: 30 | model: pricing model (default value set to BSM for European expiry) 31 | **kwargs: Dictionary of parameters and their corresponding value required for valuation. 32 | For arguments required and method available for each model check\ 33 | help(.derivativepricing.pricingmodels.) 34 | """ 35 | if not kwargs['model']: 36 | kwargs['model'] = DEFAULT_MODEL[self.undl][self.derivative_type][self.expiry_type] 37 | return PricingEngine(self, **kwargs) 38 | 39 | def list_models(self): 40 | return ", ".join(OBJECT_MODEL[self.undl][self.expiry_type]) 41 | 42 | 43 | class VanillaOption(Instrument): 44 | """ 45 | Parent class for all Vanilla options on different underlying. 46 | 47 | Methods: 48 | payoff(spot0) -> 49 | Calculates the payoff of the function 50 | 51 | engine(model, **kwargs) 52 | Binds the inout parameter with pricing models. 53 | To check valid models for underlying use .models() 54 | """ 55 | 56 | def __init__(self, option_type, expiry_type, strike, expiry_date, derivative_type): 57 | self.option_type = option_type or VanillaOptionType.CALL.value 58 | self.expiry_type = expiry_type or ExpiryType.EUROPEAN.value 59 | self.strike = strike 60 | self.expiry_date = datetime.strptime(expiry_date, '%Y%m%d') 61 | self.derivative_type = derivative_type or DerivativeType.VANILLA_OPTION.value 62 | 63 | @property 64 | def _option_type_flag(self): 65 | if self.option_type == VanillaOptionType.CALL.value: 66 | return 1 67 | else: 68 | return -1 69 | 70 | def payoff(self, spot0=None): 71 | """ 72 | Calculates the payoff of option 73 | 74 | Defines payoff of the option 75 | Payoff(Call) = max(S-K,0) 76 | Payoff(Put) = max(K-S,0) 77 | Args required: 78 | spot0: Value of underlying e.g. 110 79 | """ 80 | return max(self._option_type_flag * (spot0 - self.strike), 0.0) 81 | 82 | 83 | class EqOption(VanillaOption): 84 | """ 85 | Defines object for vanilla options on equity with both European and American expiry type. 86 | 87 | Args required: 88 | option_type: 'Call' or 'Put' (default value is set to 'Call') 89 | expiry_type: 'European' or 'American' (default is set to 'European') 90 | strike: (Float in same unit as underlying price) e.g. 110.0 91 | expiry_date: (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210" 92 | derivative_type: Default value as "Vanilla Option". 93 | """ 94 | 95 | def __init__(self, option_type=VanillaOptionType.CALL.value, expiry_type=ExpiryType.EUROPEAN.value, 96 | strike=None, expiry_date=None, derivative_type=None 97 | ): 98 | super().__init__(option_type, expiry_type, strike, expiry_date, derivative_type) 99 | self.undl = UdlType.STOCK.value 100 | 101 | def engine(self, model=None, spot0=None, rf_rate=0, yield_div=0, div_list=None, volatility=None, 102 | pricing_date=None, **kwargs): 103 | """ 104 | Binds pricing model class and market data to the object 105 | Args required: 106 | Core Arguments: 107 | model: pricing model (default value set to BSM for European expiry) 108 | To check available list of models use print(option_object.list_models()) 109 | fwd0: (float) current underlying price/value e.g. 110.0 110 | rf_rate: (Float < 1) risk free continuously compounded discount rate e.g. 5% as 0.05 111 | volatility: (Float < 1) Underlying price/value return annualized volatility. 112 | Volatility in decimal e.g. Volatility of 10% => 0.10 113 | pricing_Date: Date on which option value need to be calculated. 114 | (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210". 115 | yield_div: (Float < 1) div yield continuously compounded (for index options) e.g. 5% as 0.05 116 | div_list: List of tuples for discrete dividends with dates. e.g. [("20180610", 2), ("20180624", 4)] 117 | [("Date", div amount),...] 118 | Model specific arguments: 119 | MonteCarlo 120 | no_of_path = (Integer). Number of paths to be generated for simulation e.g. 10000 121 | no_of_steps = (Integer). Number of steps(nodes) for the premium calculation e.g. 100 122 | seed = (Integer). Used for seeding 123 | antithetic = (Boolean). A variance reduction process in Montecarlo Simulation. 124 | Default False 125 | Binomial 126 | no_of_steps = (Integer). Number of steps (nodes) for the premium calculation. 127 | Maximum value accepted is 100. This limit will be increased 128 | in future release. 129 | """ 130 | return super().engine(model=model, spot0=spot0, rf_rate=rf_rate, cnv_yield=yield_div, pv_cnv=0, 131 | div_list=div_list, volatility=volatility, pricing_date=pricing_date, **kwargs) 132 | 133 | 134 | class FutOption(VanillaOption): 135 | """ 136 | Defines object for vanilla options on futures with both European and American expiry type. 137 | 138 | Args required: 139 | option_type: 'Call' or 'Put' (default value is set to 'Call') 140 | expiry_type: 'European' or 'American' (default is set to 'European') 141 | strike: (Float in same unit as underlying price) e.g. 110.0 142 | expiry_date: (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210". 143 | """ 144 | 145 | def __init__(self, option_type=VanillaOptionType.CALL.value, expiry_type=ExpiryType.EUROPEAN.value, 146 | strike=None, expiry_date=None, derivative_type=None 147 | ): 148 | super().__init__(option_type, expiry_type, strike, expiry_date, derivative_type) 149 | self.undl = UdlType.FUTURES.value 150 | 151 | def engine(self, model=None, fwd0=None, rf_rate=0, volatility=None, pricing_date=None, **kwargs): 152 | """ 153 | Binds pricing model class and market data to the object 154 | Args required: 155 | Core Arguments: 156 | model: pricing model (default value set to BSM for European expiry) 157 | To check available list of models use print(option_object.list_models()) 158 | fwd0: (float) current future price quote e.g. 110.0 159 | rf_rate: (Float < 1) risk free continuously compounded discount rate e.g. 5% as 0.05 160 | volatility: (Float < 1) Underlying price/value return annualized volatility. 161 | Volatility in decimal e.g. Volatility of 10% => 0.10 162 | pricing_Date: Date on which option value need to be calculated. 163 | (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210". 164 | Model specific arguments: 165 | MonteCarlo 166 | no_of_path = (Integer). Number of paths to be generated for simulation e.g. 10000 167 | no_of_steps = (Integer). Number of steps(nodes) for the premium calculation e.g. 100 168 | seed = (Integer). Used for seeding 169 | antithetic = (Boolean). A variance reduction process in Montecarlo Simulation. 170 | Default False 171 | Binomial 172 | no_of_steps = (Integer). Number of steps (nodes) for the premium calculation. 173 | Maximum value accepted is 100. This limit will be increased 174 | in future release. 175 | """ 176 | return super().engine(model=model, spot0=fwd0, rf_rate=rf_rate, cnv_yield=rf_rate, 177 | volatility=volatility, pricing_date=pricing_date, **kwargs) 178 | 179 | 180 | class FXOption(VanillaOption): 181 | """ 182 | Defines object for vanilla options on fx rates with both European and American expiry type. 183 | 184 | Args required: 185 | option_type: 'Call' or 'Put' (default value is set to 'Call') 186 | expiry_type: 'European' or 'American' (default is set to 'European') 187 | strike: (Float in same unit as underlying price) e.g. 110.0 188 | expiry_date: (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210". 189 | """ 190 | 191 | def __init__(self, option_type=VanillaOptionType.CALL.value, expiry_type=ExpiryType.EUROPEAN.value, 192 | strike=None, expiry_date=None, derivative_type=None 193 | ): 194 | super().__init__(option_type, expiry_type, strike, expiry_date, derivative_type) 195 | self.undl = UdlType.FX.value 196 | 197 | def engine(self, model=None, spot0=None, rf_rate_local=0, rf_rate_foreign=0, volatility=None, 198 | pricing_date=None, **kwargs): 199 | """ 200 | Binds pricing model class and market data to the object 201 | Args required: 202 | Core Arguments: 203 | model: pricing model (default value set to BSM for European expiry) 204 | To check available list of models use print(option_object.list_models()) 205 | spot0: (float) current underlying price/value e.g. 110.0 206 | rf_rate_local: (Float < 1) risk free continuously compounded discount rate of local currency 207 | e.g. 5% as 0.05 208 | rf_rate_foreign: (Float < 1) risk free continuously compounded discount rate of 209 | foreign currency e.g. 5% as 0.05 210 | volatility: (Float < 1) Underlying price/value return annualized volatility. 211 | Volatility in decimal e.g. Volatility of 10% => 0.10 212 | pricing_Date: Date on which option value need to be calculated. 213 | (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210". 214 | Model specific arguments: 215 | MonteCarlo 216 | no_of_path = (Integer). Number of paths to be generated for simulation e.g. 10000 217 | no_of_steps = (Integer). Number of steps(nodes) for the premium calculation e.g. 100 218 | seed = (Integer). Used for seeding 219 | antithetic = (Boolean). A variance reduction process in Montecarlo Simulation. 220 | Default False 221 | Binomial 222 | no_of_steps = (Integer). Number of steps (nodes) for the premium calculation. 223 | Maximum value accepted is 100. This limit will be increased 224 | in future release. 225 | """ 226 | return super().engine(model=model, spot0=spot0, rf_rate=rf_rate_local, cnv_yield=rf_rate_foreign, 227 | volatility=volatility, pricing_date=pricing_date, **kwargs) 228 | 229 | 230 | class ComOption(VanillaOption): 231 | """ 232 | Defines object for vanilla options on commodities with both European and American expiry type. 233 | 234 | Args required: 235 | option_type: 'Call' or 'Put' (default value is set to 'Call') 236 | expiry_type: 'European' or 'American' (default is set to 'European') 237 | strike: (Float in same unit as underlying price) e.g. 110.0 238 | expiry_date: (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210". 239 | """ 240 | def __init__(self, option_type=VanillaOptionType.CALL.value, expiry_type=ExpiryType.EUROPEAN.value, 241 | strike=None, expiry_date=None, derivative_type=None 242 | ): 243 | super().__init__(option_type, expiry_type, strike, expiry_date, derivative_type) 244 | self.undl = UdlType.COMMODITY.value 245 | 246 | def engine(self, model=None, spot0=None, rf_rate=0, cnv_yield=0, cost_yield=0, volatility=None, 247 | pricing_date=None, **kwargs): 248 | """ 249 | Binds pricing model class and market data to the object 250 | Args required: 251 | Core Arguments: 252 | model: pricing model (default value set to BSM for European expiry) 253 | To check available list of models use print(option_object.list_models()) 254 | spot0: (float) current underlying price/value e.g. 110.0 255 | rf_rate: (Float < 1) risk free continuously compounded discount rate e.g. 5% as 0.05 256 | cnv_yield: (Float < 1) Convenience yield continuously compounded e.g. 4% as 0.04 257 | cost_yield: (Float < 1) Cost yield continuously compounded e.g. 2% as 0.02 258 | volatility: (Float < 1) Underlying price/value return annualized volatility. 259 | Volatility in decimal e.g. Volatility of 10% => 0.10 260 | pricing_Date: Date on which option value need to be calculated. 261 | (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210". 262 | Model specific arguments: 263 | MonteCarlo 264 | no_of_path = (Integer). Number of paths to be generated for simulation e.g. 10000 265 | no_of_steps = (Integer). Number of steps(nodes) for the premium calculation e.g. 100 266 | seed = (Integer). Used for seeding 267 | antithetic = (Boolean). A variance reduction process in Montecarlo Simulation. 268 | Default False 269 | Binomial 270 | no_of_steps = (Integer). Number of steps (nodes) for the premium calculation. 271 | Maximum value accepted is 100. This limit will be increased 272 | in future release. 273 | """ 274 | return super().engine(model=model, spot0=spot0, rf_rate=rf_rate, cnv_yield=cnv_yield, cost_yield=cost_yield, 275 | volatility=volatility, pricing_date=pricing_date, **kwargs) 276 | -------------------------------------------------------------------------------- /quantsbin/derivativepricing/namesnmapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from enum import Enum 7 | 8 | 9 | class AssetClass(Enum): 10 | EQOPTION = 'EqOption' 11 | FXOPTION = 'FXOption' 12 | FUTOPTION = 'FutOption' 13 | COMOPTION = 'ComOption' 14 | 15 | 16 | class DerivativeType(Enum): 17 | VANILLA_OPTION = 'Vanilla Option' 18 | 19 | 20 | class PricingModel(Enum): 21 | BLACKSCHOLESMERTON = 'BSM' 22 | BLACK76 = 'B76' 23 | GK = 'GK' 24 | MC_GBM = "MC_GBM" 25 | MC_GBM_LSM = "MC_GBM_LSM" 26 | BINOMIAL = "Binomial" 27 | 28 | 29 | class UnderlyingParameters(Enum): 30 | SPOT = "spot0" 31 | VOLATILITY = "volatility" 32 | PRICEDATE = "_pricing_date" 33 | RF_RATE = "rf_rate" 34 | CNV_YIELD = "cnv_yield" 35 | COST_YIELD = "cost_yield" 36 | UNEXPLAINED = "unexplained" 37 | 38 | 39 | class RiskParameter(Enum): 40 | DELTA = 'delta' 41 | GAMMA = 'gamma' 42 | THETA = 'theta' 43 | VEGA = 'vega' 44 | RHO = 'rho' 45 | PHI = 'phi' 46 | RHO_FOREIGN = 'rho_foreign' 47 | RHO_CONV = 'rho_conv_yield' 48 | 49 | 50 | class VanillaOptionType(Enum): 51 | CALL = 'Call' 52 | PUT = 'Put' 53 | 54 | 55 | class ExpiryType(Enum): 56 | AMERICAN = 'American' 57 | EUROPEAN = 'European' 58 | 59 | 60 | class UdlType(Enum): 61 | INDEX = 'Index' 62 | STOCK = 'Stock' 63 | FX = 'Currency' 64 | COMMODITY = 'Commodity' 65 | FUTURES = 'Futures' 66 | 67 | 68 | class DivType(Enum): 69 | DISCRETE = 'Discrete' 70 | YIELD = 'Yield' 71 | 72 | 73 | OBJECT_MODEL = { 74 | UdlType.STOCK.value: {ExpiryType.EUROPEAN.value: [PricingModel.BLACKSCHOLESMERTON.value, PricingModel.MC_GBM.value 75 | , PricingModel.BINOMIAL.value], 76 | ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} 77 | , UdlType.FUTURES.value: {ExpiryType.EUROPEAN.value: [PricingModel.BLACK76.value, PricingModel.MC_GBM.value 78 | , PricingModel.BINOMIAL.value], 79 | ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} 80 | , UdlType.FX.value: {ExpiryType.EUROPEAN.value: [PricingModel.GK.value, PricingModel.MC_GBM.value 81 | , PricingModel.BINOMIAL.value], 82 | ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} 83 | , UdlType.COMMODITY.value: {ExpiryType.EUROPEAN.value: [PricingModel.GK.value, PricingModel.MC_GBM.value 84 | , PricingModel.BINOMIAL.value], 85 | ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} 86 | } 87 | 88 | DEFAULT_MODEL = { 89 | UdlType.STOCK.value: 90 | {DerivativeType.VANILLA_OPTION.value: {ExpiryType.EUROPEAN.value: PricingModel.BLACKSCHOLESMERTON.value, 91 | ExpiryType.AMERICAN.value: PricingModel.BINOMIAL.value}, 92 | }, 93 | UdlType.FUTURES.value: 94 | {DerivativeType.VANILLA_OPTION.value: {ExpiryType.EUROPEAN.value: PricingModel.BLACK76.value, 95 | ExpiryType.AMERICAN.value: PricingModel.BINOMIAL.value}, 96 | }, 97 | UdlType.FX.value: 98 | {DerivativeType.VANILLA_OPTION.value: {ExpiryType.EUROPEAN.value: PricingModel.GK.value, 99 | ExpiryType.AMERICAN.value: PricingModel.BINOMIAL.value}, 100 | }, 101 | UdlType.COMMODITY.value: 102 | {DerivativeType.VANILLA_OPTION.value: {ExpiryType.EUROPEAN.value: PricingModel.GK.value, 103 | ExpiryType.AMERICAN.value: PricingModel.BINOMIAL.value}, 104 | } 105 | } 106 | 107 | IV_MODELS = [PricingModel.BLACKSCHOLESMERTON.value, PricingModel.BLACK76.value, PricingModel.GK.value] 108 | 109 | ANALYTICAL_GREEKS = [PricingModel.BLACKSCHOLESMERTON.value, PricingModel.BLACK76.value, PricingModel.GK.value] 110 | 111 | from . import pricingmodels as pm 112 | 113 | MODEL_MAPPER = { 114 | PricingModel.BLACKSCHOLESMERTON.value: pm.BSM, 115 | PricingModel.BLACK76.value: pm.B76, 116 | PricingModel.GK.value: pm.GK, 117 | PricingModel.MC_GBM.value: pm.MonteCarloGBM, 118 | PricingModel.BINOMIAL.value: pm.BinomialModel 119 | } 120 | -------------------------------------------------------------------------------- /quantsbin/derivativepricing/numericalgreeks.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | import pandas as pd 7 | import copy 8 | from .namesnmapper import UnderlyingParameters, RiskParameter 9 | 10 | 11 | class NumericalGreeks: 12 | """ 13 | Numerical greeks are calculated. It is mapped with engineconfig module for calculation of valuation 14 | and riskparameters 15 | Args required: 16 | delta_spot = (Float < 1). Change in Spot price e.g. 0.2 17 | delta_vol = (Float < 1). Change in Volatility price e.g. 0.2 18 | delta_rf_rate = (Float < 1). Change in risk free rate e.g. 0.2 19 | delta_time = (Integer). Number of days to change in pricing date e.g. 2 20 | delta_conv_yield = (Float < 1). Change in conv_yield e.g. 0.2 21 | delta_cost_yield = (Float < 1). Change in cost yield e.g. 0.2 22 | """ 23 | 24 | def __init__(self, model_class, delta_spot=0.02, delta_vol=0.02, delta_rf_rate=0.02, 25 | delta_time=1, delta_conv_yield=0, delta_cost_yield=0, **kwargs): 26 | self.model = model_class 27 | self._model = copy.deepcopy(self.model) 28 | self.delta_spot = delta_spot or 0 29 | self.delta_vol = delta_vol or 0 30 | self.delta_rf_rate = delta_rf_rate or 0 31 | self.delta_time = delta_time or 0 32 | self.delta_conv_yield = delta_conv_yield or 0 33 | self.delta_cost_yield = delta_cost_yield or 0 34 | 35 | def degree_one(self, var, change): 36 | up_model = copy.deepcopy(self._model) 37 | if var == UnderlyingParameters.PRICEDATE.value: 38 | setattr(up_model, var, getattr(self._model, var) + pd.Timedelta(change, unit='D')) 39 | return (up_model.valuation() - self.model.valuation())/change 40 | else: 41 | setattr(up_model, var, getattr(self._model, var)*(1 + change)) 42 | down_model = copy.deepcopy(self._model) 43 | setattr(down_model, var, getattr(self._model, var) * (1 - change)) 44 | return (up_model.valuation() - down_model.valuation())/(2*(getattr(self._model, var)*change)) 45 | 46 | def degree_two(self, var, change): 47 | up_model = copy.deepcopy(self._model) 48 | setattr(up_model, var, getattr(self._model, var)*(1 + change)) 49 | down_model = copy.deepcopy(self._model) 50 | setattr(down_model, var, getattr(self._model, var) * (1 - change)) 51 | return (up_model.valuation() - 2*self.model.valuation() + down_model.valuation())/((getattr(self._model, var) 52 | * change)**2) 53 | 54 | def delta(self): 55 | return self.degree_one(UnderlyingParameters.SPOT.value, self.delta_spot) 56 | 57 | def gamma(self): 58 | return self.degree_two(UnderlyingParameters.SPOT.value, self.delta_spot) 59 | 60 | def theta(self): 61 | return self.degree_one(UnderlyingParameters.PRICEDATE.value, self.delta_time) 62 | 63 | def vega(self): 64 | return self.degree_one(UnderlyingParameters.VOLATILITY.value, self.delta_vol) 65 | 66 | def rho(self): 67 | return self.degree_one(UnderlyingParameters.RF_RATE.value, self.delta_rf_rate) 68 | 69 | def risk_parameters_num(self): 70 | return {RiskParameter.DELTA.value: self.delta() 71 | , RiskParameter.GAMMA.value: self.gamma() 72 | , RiskParameter.THETA.value: self.theta() 73 | , RiskParameter.VEGA.value: self.vega() 74 | , RiskParameter.RHO.value: self.rho() 75 | } 76 | 77 | def risk_parameters_num_func(self): 78 | return {RiskParameter.DELTA.value: self.delta 79 | , RiskParameter.GAMMA.value: self.gamma 80 | , RiskParameter.THETA.value: self.theta 81 | , RiskParameter.VEGA.value: self.vega 82 | , RiskParameter.RHO.value: self.rho 83 | } 84 | 85 | def pnl(self, var, change): 86 | if change == 0: 87 | return 0 88 | up_model = copy.copy(self.model) 89 | if var == UnderlyingParameters.PRICEDATE.value: 90 | setattr(up_model, var, getattr(self.model, var) + pd.Timedelta(change, unit='D')) 91 | else: 92 | setattr(up_model, var, getattr(self.model, var)*(1 + change)) 93 | return up_model.valuation() - self.model.valuation() 94 | 95 | def pnl_attribution(self): 96 | return {UnderlyingParameters.SPOT.value: self.pnl(UnderlyingParameters.SPOT.value, self.delta_spot) 97 | , UnderlyingParameters.PRICEDATE.value: self.pnl(UnderlyingParameters.PRICEDATE.value, self.delta_time) 98 | , UnderlyingParameters.VOLATILITY.value: self.pnl(UnderlyingParameters.VOLATILITY.value, self.delta_vol) 99 | , UnderlyingParameters.RF_RATE.value: self.pnl(UnderlyingParameters.RF_RATE.value, self.delta_rf_rate) 100 | , UnderlyingParameters.CNV_YIELD.value: self.pnl(UnderlyingParameters.CNV_YIELD.value, self.delta_conv_yield) 101 | , UnderlyingParameters.COST_YIELD.value: self.pnl(UnderlyingParameters.COST_YIELD.value, self.delta_cost_yield) 102 | } 103 | -------------------------------------------------------------------------------- /quantsbin/derivativepricing/optionstrategies.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from multiprocessing import Pool 7 | from collections import Counter 8 | from datetime import timedelta 9 | from functools import partial 10 | 11 | from .namesnmapper import VanillaOptionType, ExpiryType, UdlType, RiskParameter 12 | 13 | from . import instruments as inst 14 | import platform 15 | 16 | VANILLA_OBJECT_MAPPER = { 17 | UdlType.STOCK.value: inst.EqOption, 18 | UdlType.FUTURES.value: inst.FutOption, 19 | UdlType.FX.value: inst.FXOption, 20 | UdlType.COMMODITY.value: inst.ComOption 21 | } 22 | 23 | 24 | def p_map(func, parameter): 25 | if platform.system() == "Windows": 26 | return map(func, parameter) 27 | else: 28 | with Pool() as p: 29 | return p.map(func, parameter) 30 | 31 | 32 | class OptionStr1Udl: 33 | """ 34 | This module is written over the .instruments and .pricingmodels modules 35 | Here the combined payoff, valuation and riskparameters are calculated for the portfolio 36 | Args required: 37 | option_portfolio => (List of tuples). e.g. [(eqOption1, -1), (eqOption2, -2)] 38 | eqOption1 => qbdp.derivativepricing.EqOption(**Instrument parameters) 39 | -1 => position and units of the option (long/short) 40 | spot0 => (Float). e.g. 110 41 | **kwargs => market parameters for PricingEngine (calculation of combined valuation and riskparameters) 42 | """ 43 | 44 | def __init__(self, option_portfolio): 45 | self.option_portfolio = option_portfolio 46 | self.spot = None 47 | 48 | def weighted_payoff(self, option_detail): 49 | return option_detail[0].payoff(self.spot)*option_detail[1] 50 | 51 | def payoff(self, spot): 52 | self.spot = spot 53 | _payoffs = p_map(self.weighted_payoff, self.option_portfolio) 54 | return sum(_payoffs) 55 | 56 | def engine(self, **kwargs): 57 | return Engine(self, self.option_portfolio, **kwargs) 58 | 59 | 60 | class Engine: 61 | 62 | def __init__(self, instrument, option_portfolio, **kwargs): 63 | self._other_args = kwargs 64 | self.option_portfolio = option_portfolio 65 | self.instrument = instrument 66 | 67 | def weighted_valuation(self, option_detail): 68 | return option_detail[0].engine(**self._other_args).valuation() * option_detail[1] 69 | 70 | def valuation(self): 71 | _valuations = p_map(self.weighted_valuation, self.option_portfolio) 72 | return sum(_valuations) 73 | 74 | def weighted_risk_parameters(self, option_detail): 75 | risk_parameters = option_detail[0].engine(**self._other_args).risk_parameters() 76 | risk_parameters.update((x, y * option_detail[1]) for x, y in risk_parameters.items()) 77 | return risk_parameters 78 | 79 | def risk_parameter_ind(self, option_detail, risk_name): 80 | _risk_parameter_func = option_detail[0].engine(**self._other_args).risk_parameters_func() 81 | _risk_parameter = _risk_parameter_func[risk_name]() 82 | return _risk_parameter * option_detail[1] 83 | 84 | def risk_parameter(self, var): 85 | _risk_parameters = p_map(partial(self.risk_parameter_ind, risk_name=var), self.option_portfolio) 86 | return sum(_risk_parameters) 87 | 88 | def risk_parameters(self): 89 | str_risk_parameters = Counter() 90 | _risk_parameters = p_map(self.weighted_risk_parameters, self.option_portfolio) 91 | [str_risk_parameters.update(i) for i in _risk_parameters] 92 | return dict(str_risk_parameters) 93 | 94 | def risk_parameters_func(self): 95 | return { 96 | RiskParameter.DELTA.value: partial(self.risk_parameter, RiskParameter.DELTA.value), 97 | RiskParameter.GAMMA.value: partial(self.risk_parameter, RiskParameter.GAMMA.value), 98 | RiskParameter.THETA.value: partial(self.risk_parameter, RiskParameter.THETA.value), 99 | RiskParameter.VEGA.value: partial(self.risk_parameter, RiskParameter.VEGA.value), 100 | RiskParameter.RHO.value: partial(self.risk_parameter, RiskParameter.RHO.value), 101 | } 102 | 103 | 104 | class StdStrategies(OptionStr1Udl): 105 | """ 106 | The combined payoff, valuation and riskparameters are calculated for the Standard Strategies. 107 | This class inherits OptionStr1Udl class. so for valuation and riskparameters the market parameters are passed 108 | to the engine in similar fashion 109 | Args required: 110 | name = (String). Name of the std strategy e.g. "bull_call" 111 | asset = (String). Name of the asset class e.g. "Stock" 112 | expiry_date = (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210" 113 | expiry_type = 'European' or 'American' (default is set to 'European') 114 | low_strike = (Float) Strike for option with lowest strike e.g. 110 115 | strike_spread = (Float). This is added or subtracted to strike based on the strategy e.g. 10 116 | time_spread = time added to expiry in to find expiry of long term maturity option in calendar spread. 117 | 118 | List of Std. Strategies [bull_call, bear_call, bull_put, bear_put, box_spread, butterfly_call, butterfly_put, 119 | calendar_call, calendar_put, rev_calendar_call, rev_calendar_put, bottom_straddle, 120 | top_straddle, bottom_strangle, top_strangle, strip, strap] 121 | """ 122 | 123 | def __init__(self, name="bull_call", asset=None, expiry_date=None, expiry_type=None, low_strike=100, 124 | strike_spread=10, time_spread=30): 125 | self.name = name 126 | self.asset = asset or UdlType.STOCK.value 127 | self.expiry_date = expiry_date or None 128 | self.expiry_type = expiry_type or ExpiryType.EUROPEAN.value 129 | self.strike = low_strike or 100 130 | self.spread = strike_spread or (low_strike * 0.1) 131 | self.time_spread = time_spread or 30 132 | super().__init__(self.std_option_portfolio) 133 | 134 | @property 135 | def std_option_portfolio(self): 136 | _option_portfolio = getattr(self, self.name)() 137 | return _option_portfolio 138 | 139 | def low_option(self, option_type): 140 | return VANILLA_OBJECT_MAPPER[self.asset](option_type=option_type, strike=self.strike - self.spread, 141 | expiry_date=self.expiry_date, expiry_type=self.expiry_type) 142 | 143 | def mid_option(self, option_type): 144 | return VANILLA_OBJECT_MAPPER[self.asset](option_type=option_type, strike=self.strike, 145 | expiry_date=self.expiry_date, expiry_type=self.expiry_type) 146 | 147 | def high_option(self, option_type): 148 | return VANILLA_OBJECT_MAPPER[self.asset](option_type=option_type, strike=self.strike + self.spread, 149 | expiry_date=self.expiry_date, expiry_type=self.expiry_type) 150 | 151 | def before_option(self, option_type): 152 | return VANILLA_OBJECT_MAPPER[self.asset](option_type=option_type, strike=self.strike, expiry_date= 153 | self.expiry_date - timedelta(days=self.time_spread), 154 | expiry_type=self.expiry_type) 155 | 156 | def after_option(self, option_type): 157 | return VANILLA_OBJECT_MAPPER[self.asset](option_type=option_type, strike=self.strike, expiry_date= 158 | self.expiry_date + timedelta(days=self.time_spread), 159 | expiry_type=self.expiry_type) 160 | 161 | def bull_call(self): 162 | return [(self.mid_option(VanillaOptionType.CALL.value), 1), (self.high_option(VanillaOptionType.CALL.value), -1)] 163 | 164 | def bear_call(self): 165 | return [(self.mid_option(VanillaOptionType.CALL.value), -1), (self.high_option(VanillaOptionType.CALL.value), 1)] 166 | 167 | def bull_put(self): 168 | return [(self.mid_option(VanillaOptionType.PUT.value), 1), (self.high_option(VanillaOptionType.PUT.value), -1)] 169 | 170 | def bear_put(self): 171 | return [(self.mid_option(VanillaOptionType.PUT.value), -1), (self.high_option(VanillaOptionType.PUT.value), 1)] 172 | 173 | def box_spread(self): 174 | return [(self.mid_option(VanillaOptionType.CALL.value), 1), (self.high_option(VanillaOptionType.CALL.value), -1) 175 | , (self.mid_option(VanillaOptionType.PUT.value), -1), (self.high_option(VanillaOptionType.PUT.value), 1)] 176 | 177 | def butterfly_call(self): 178 | return [(self.low_option(VanillaOptionType.CALL.value), 1), (self.mid_option(VanillaOptionType.CALL.value), -1), 179 | (self.mid_option(VanillaOptionType.CALL.value), -1), (self.high_option(VanillaOptionType.CALL.value), 1)] 180 | 181 | def butterfly_put(self): 182 | return [(self.low_option(VanillaOptionType.PUT.value), -1), (self.mid_option(VanillaOptionType.PUT.value), 1), 183 | (self.mid_option(VanillaOptionType.PUT.value), 1), (self.high_option(VanillaOptionType.PUT.value), -1)] 184 | 185 | def calendar_call(self): 186 | return [(self.mid_option(VanillaOptionType.CALL.value), -1), self.after_option(VanillaOptionType.CALL.value), 1] 187 | 188 | def calendar_put(self): 189 | return [(self.mid_option(VanillaOptionType.PUT.value), -1), self.after_option(VanillaOptionType.PUT.value), 1] 190 | 191 | def rev_calendar_call(self): 192 | return [(self.mid_option(VanillaOptionType.CALL.value), 1), self.after_option(VanillaOptionType.CALL.value), -1] 193 | 194 | def rev_calendar_put(self): 195 | return [(self.mid_option(VanillaOptionType.PUT.value), 1), self.after_option(VanillaOptionType.PUT.value), -1] 196 | 197 | def bottom_straddle(self): 198 | return [(self.mid_option(VanillaOptionType.CALL.value), 1), (self.mid_option(VanillaOptionType.PUT.value), 1)] 199 | 200 | def top_straddle(self): 201 | return [(self.mid_option(VanillaOptionType.CALL.value), -1), (self.mid_option(VanillaOptionType.PUT.value), -1)] 202 | 203 | def bottom_strangle(self): 204 | return [(self.mid_option(VanillaOptionType.CALL.value), 1), (self.high_option(VanillaOptionType.PUT.value), 1)] 205 | 206 | def top_strangle(self): 207 | return [(self.mid_option(VanillaOptionType.CALL.value), -1), (self.high_option(VanillaOptionType.PUT.value), -1)] 208 | 209 | def strip(self): 210 | return [(self.mid_option(VanillaOptionType.CALL.value), 1), (self.mid_option(VanillaOptionType.PUT.value), 1), 211 | (self.mid_option(VanillaOptionType.PUT.value), 1)] 212 | 213 | def strap(self): 214 | return [(self.mid_option(VanillaOptionType.CALL.value), 1), (self.mid_option(VanillaOptionType.CALL.value), 1), 215 | (self.mid_option(VanillaOptionType.PUT.value), 1)] -------------------------------------------------------------------------------- /quantsbin/derivativepricing/plotting.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | import numpy as np 7 | import copy 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | plt.style.use('ggplot') 11 | from .namesnmapper import UnderlyingParameters 12 | from scipy.interpolate import interp1d 13 | 14 | 15 | class Plotting: 16 | """ 17 | Here the graphs for payoff, valuation and riskparameters are plotted 18 | Args required: 19 | object = (Object). e.g, EqOption for payoff graph/engine for valuation and riskparameters 20 | func = (String). e.g, "payoff" (graph of which needs to be plotted) 21 | x_axis = (String). e.g, "spot0" (the x-axis of the graph) 22 | x_axis_range = (List). List with start and end points of the x-axis. 23 | :return: Graph of the func 24 | """ 25 | 26 | def __init__(self, instrument_object, func, x_axis=UnderlyingParameters.SPOT.value, x_axis_range=None 27 | , no_of_points=50): 28 | self.object = instrument_object 29 | self.func = func 30 | self.x_axis = x_axis 31 | self.x_axis_range = x_axis_range 32 | self.no_of_points = no_of_points 33 | 34 | def _x(self): 35 | _temp_x = np.linspace(self.x_axis_range[0], self.x_axis_range[1], self.no_of_points) 36 | if not _temp_x[0]: 37 | _temp_x[0] += 0.001 38 | return np.linspace(self.x_axis_range[0], self.x_axis_range[1], self.no_of_points) 39 | 40 | def _get_set(self, _x_var): 41 | temp_object = copy.copy(self.object) 42 | temp_object._other_args[self.x_axis] = _x_var 43 | if self.func == "valuation": 44 | _fucntion_return = getattr(temp_object, self.func)() 45 | else: 46 | _fucntion_return = temp_object.risk_parameters_func()[self.func]() 47 | return _fucntion_return 48 | 49 | def _y(self): 50 | if self.func == "payoff": 51 | return list(map(getattr(self.object, self.func), list(self._x()))) 52 | elif self.func == "pnl": 53 | return np.array(list(map(getattr(self.object.instrument, "payoff"), list(self._x()))))\ 54 | - self.object.valuation() 55 | else: 56 | return list(map(self._get_set, list(self._x()))) 57 | 58 | def line_plot(self): 59 | """ 60 | The plot uses matplotlib library and for smoothing purposes interp1d is used 61 | (spline is retired since scipy 1.0.0) 62 | :return: matplotlib plot object 63 | """ 64 | _x_axis = self._x() 65 | _x_new = np.linspace(_x_axis[0], _x_axis[-1], self.no_of_points*10) 66 | # _y_smooth = spline(_x_axis, self._y(), _x_new) 67 | f = interp1d(_x_axis, self._y(), kind='cubic') 68 | _y_smooth = f(_x_new) 69 | plt.plot(_x_new, _y_smooth) 70 | plt.grid(True) 71 | plt.xlabel(str(self.x_axis).capitalize()) 72 | plt.ylabel(str(self.func).capitalize()) 73 | plt.title(str(self.func).capitalize() + " vs "+str(self.x_axis).capitalize()) 74 | return plt 75 | 76 | 77 | -------------------------------------------------------------------------------- /quantsbin/derivativepricing/pricingmodels.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from abc import ABCMeta, abstractmethod 7 | from datetime import datetime as dt 8 | from math import log, sqrt 9 | import sys 10 | 11 | import numpy as np 12 | from scipy import optimize 13 | from scipy.stats import norm 14 | 15 | from .namesnmapper import RiskParameter, VanillaOptionType, ExpiryType, UdlType 16 | from ..montecarlo.namesnmapper import StimulationType, mc_methd_mapper, ProcessNames 17 | from .helperfn import * 18 | 19 | 20 | class Model(metaclass=ABCMeta): 21 | """ 22 | Basic model class defined with properties required for all the pricing models for any type of Asset class 23 | 24 | """ 25 | @abstractmethod 26 | def valuation(self): 27 | pass 28 | 29 | @abstractmethod 30 | def risk_parameters(self): 31 | pass 32 | 33 | @property 34 | def _cnv_yield(self): 35 | if self.instrument.undl == UdlType.FUTURES.value: 36 | return self.rf_rate 37 | else: 38 | return self.cnv_yield 39 | 40 | @property 41 | def pricing_date(self): 42 | if self._pricing_date < self.instrument.expiry_date: 43 | return self._pricing_date 44 | else: 45 | raise Exception("Pricing date should be less than expiry of instrument") 46 | 47 | @property 48 | def maturity(self): 49 | return (self.instrument.expiry_date - self.pricing_date).days / 365.0 50 | 51 | @property 52 | def option_flag(self): 53 | if self.instrument.option_type == VanillaOptionType.CALL.value: 54 | return 1 55 | elif self.instrument.option_type == VanillaOptionType.PUT.value: 56 | return -1 57 | 58 | 59 | class BSMFramework(Model): 60 | """ 61 | This is the base class for the Black Scholes Merton, Black76 and GK models 62 | Args required: 63 | instrument = Instrument parameters mapped from instrument module 64 | spot0 = (Float) e.g. 110.0 65 | rf_rate = (Float < 1) e.g. 0.2 66 | _cnv_yield = (Float < 1) e.g. 0.3 67 | cost_yield = (Float < 1) e.g. 0.2 68 | pv_cnv = (Float) e.g. 1.2 69 | pv_cost = (Float) e.g. 3.2 70 | volatility = (Float < 1) e.g. 0.25 71 | pricing_date = (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210" 72 | """ 73 | 74 | def __init__(self, instrument, spot0=None, rf_rate=0, cnv_yield=0, cost_yield=0, 75 | pv_cnv=0, pv_cost=0, volatility=None, pricing_date=None, **kwargs): 76 | self.instrument = instrument 77 | self.spot0 = spot0 or .00001 78 | self.rf_rate = rf_rate or 0 79 | self.cnv_yield = cnv_yield or 0 80 | self.cost_yield = cost_yield or 0 81 | self.pv_cnv = pv_cnv or 0 82 | self.pv_cost = pv_cost or 0 83 | self.volatility = volatility or 0.10 84 | self._pricing_date = dt.strptime(pricing_date, '%Y%m%d') 85 | 86 | @property 87 | def adj_spot0(self): 88 | return self.spot0+self.pv_cost-self.pv_cnv 89 | 90 | @property 91 | def d1(self): 92 | return (log(self.adj_spot0 / self.instrument.strike) + ( 93 | self.rf_rate - self._cnv_yield + self.cost_yield + 0.5 * (self.volatility ** 2)) * self.maturity) \ 94 | / (self.volatility * sqrt(self.maturity)) 95 | 96 | @property 97 | def d2(self): 98 | return self.d1 - self.volatility * sqrt(self.maturity) 99 | 100 | @property 101 | def discount_factor(self): 102 | return e ** (-1 * self.rf_rate * self.maturity) 103 | 104 | @property 105 | def adj_discount_factor(self): 106 | return e ** (-1 * (self._cnv_yield - self.cost_yield) * self.maturity) 107 | 108 | @property 109 | def option_flag(self): 110 | if self.instrument.option_type == VanillaOptionType.CALL.value: 111 | return 1 112 | elif self.instrument.option_type == VanillaOptionType.PUT.value: 113 | return -1 114 | 115 | def valuation(self): 116 | return self.option_flag * ( 117 | self.adj_spot0 * self.adj_discount_factor * norm.cdf(self.option_flag * self.d1)) \ 118 | - self.option_flag * (self.instrument.strike * self.discount_factor 119 | * norm.cdf(self.option_flag * self.d2)) 120 | 121 | # greeks defined 122 | def delta(self): 123 | return norm.cdf(self.option_flag * self.d1) 124 | 125 | def gamma(self): 126 | return (norm.pdf(self.d1) * self.adj_discount_factor) / ( 127 | self.adj_spot0 * self.volatility * sqrt(self.maturity)) 128 | 129 | def vega(self): 130 | return (self.adj_spot0 * self.adj_discount_factor * sqrt(self.maturity)) * norm.pdf(self.d1) 131 | 132 | def rho(self): 133 | return self.option_flag * ( 134 | self.instrument.strike * self.maturity * self.discount_factor 135 | * norm.cdf(self.option_flag * self.d2)) 136 | 137 | def phi(self): 138 | return -1 * self.option_flag * self.adj_spot0 * self.maturity * norm.cdf(self.option_flag * self.d1) 139 | 140 | def rho_fut(self): 141 | return -1 * self.maturity * self.valuation() 142 | 143 | def theta(self): 144 | return ((-1 * norm.pdf(self.d1) * self.volatility * self.adj_discount_factor * self.adj_spot0 / ( 145 | 2 * sqrt(self.maturity))) + 146 | (self.option_flag * (self._cnv_yield - self.cost_yield) * self.adj_spot0 147 | * norm.cdf(self.option_flag * self.d1) * self.adj_discount_factor) - 148 | (self.option_flag * self.rf_rate * self.instrument.strike * self.discount_factor 149 | * norm.cdf(self.option_flag * self.d2))) / 365 150 | 151 | @abstractmethod 152 | def risk_parameters(self): 153 | pass 154 | 155 | def risk_parameters_func(self): 156 | return {RiskParameter.DELTA.value: self.delta, 157 | RiskParameter.GAMMA.value: self.gamma, 158 | RiskParameter.THETA.value: self.theta, 159 | RiskParameter.VEGA.value: self.vega, 160 | RiskParameter.RHO.value: self.rho, 161 | } 162 | 163 | def imply_volatility(self, premium): 164 | def objective_func(vol_guess): 165 | self.volatility = vol_guess 166 | val = self.valuation() 167 | return val - premium 168 | 169 | try: 170 | return optimize.bisect(objective_func, a=0.001, b=0.999, xtol=0.00005) 171 | except ValueError: 172 | raise ValueError("Unable to converge to implied volatility") 173 | 174 | 175 | class BSM(BSMFramework): 176 | """ 177 | This is the Black Scholes Merton model. This model is for EqOption(Stocks) 178 | Args required: 179 | instrument = Instrument parameters mapped from instrument module 180 | **market_kwargs = All the parameters from BSMFramework 181 | help(.derivativepricing.pricingmodels.BSMFramework) 182 | 183 | """ 184 | def __init__(self, instrument, **market_kwargs): 185 | self._rf_rate = market_kwargs['rf_rate'] 186 | self._div_list = market_kwargs['div_list'] 187 | self._pricing_date = dt.strptime(market_kwargs['pricing_date'], '%Y%m%d') 188 | self._instrument = instrument 189 | market_kwargs['pv_cnv'] = self.pv_div() 190 | super().__init__(instrument, **market_kwargs) 191 | 192 | def pv_div(self): 193 | if self._div_list: 194 | _div_processed = dividend_processor(self._div_list, self._pricing_date, self._instrument.expiry_date) 195 | return pv_div(_div_processed, 0, self._rf_rate) 196 | else: 197 | return 0 198 | 199 | def risk_parameters(self): 200 | return { 201 | RiskParameter.DELTA.value: self.delta(), 202 | RiskParameter.GAMMA.value: self.gamma(), 203 | RiskParameter.THETA.value: self.theta(), 204 | RiskParameter.VEGA.value: self.vega(), 205 | RiskParameter.RHO.value: self.rho(), 206 | RiskParameter.PHI.value: self.phi() 207 | } 208 | 209 | 210 | class B76(BSMFramework): 211 | """ 212 | This is the Black76. This model is for FutOption(Futures) 213 | Args required: 214 | instrument = Instrument parameters mapped from instrument module 215 | **market_kwargs = All the parameters from BSMFramework 216 | help(.derivativepricing.pricingmodels.BSMFramework) 217 | 218 | """ 219 | def __init__(self, instrument, **market_kwargs): 220 | super().__init__(instrument, **market_kwargs) 221 | 222 | def risk_parameters(self): 223 | risk_parameters = { 224 | RiskParameter.DELTA.value: self.delta(), 225 | RiskParameter.GAMMA.value: self.gamma(), 226 | RiskParameter.THETA.value: self.theta(), 227 | RiskParameter.VEGA.value: self.vega(), 228 | RiskParameter.RHO.value: self.rho_fut() 229 | } 230 | return risk_parameters 231 | 232 | 233 | class GK(BSMFramework): 234 | """ 235 | This is the Garman Kohlhagen model. This model is for FxOption and ComOption(Fx Rates and Commodities) 236 | Args required: 237 | instrument = Instrument parameters mapped from instrument module 238 | **market_kwargs = All the parameters from BSMFramework 239 | help(.derivativepricing.pricingmodels.BSMFramework) 240 | 241 | """ 242 | def __init__(self, instrument, **market_kwargs): 243 | self.instrument = instrument 244 | super().__init__(instrument, **market_kwargs) 245 | 246 | def risk_parameters(self): 247 | risk_parameters = { 248 | RiskParameter.DELTA.value: self.delta(), 249 | RiskParameter.GAMMA.value: self.gamma(), 250 | RiskParameter.THETA.value: self.theta(), 251 | RiskParameter.VEGA.value: self.vega(), 252 | RiskParameter.RHO.value: self.rho(), 253 | } 254 | if self.instrument.undl == UdlType.FX: 255 | risk_parameters.update({RiskParameter.RHO_FOREIGN.value: self.phi()}) 256 | elif self.instrument.undl == UdlType.COMMODITY: 257 | risk_parameters.update({RiskParameter.RHO_CONV.value: self.phi()}) 258 | return risk_parameters 259 | 260 | 261 | class MonteCarloGBM(Model): 262 | """ 263 | This is the Montecarlo Simulation(for Geometric Brownian motion) method for both European and 264 | American type of Options for all Asset classes. For American Type LSM method is used defined under this class 265 | Args required: 266 | instrument = Instrument parameters mapped from instrument module 267 | spot0 = (Float) e.g. 110.0 268 | rf_rate = (Float < 1) e.g. 0.2 269 | cnv_yield = (Float < 1) e.g. 0.3 270 | cost_yield = (Float < 1) e.g. 0.2 271 | volatility = (Float < 1) e.g. 0.25 272 | pricing_date = (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210" 273 | no_of_path = (Integer). Number of paths to be generated for simulation e.g. 10000 274 | no_of_steps = (Integer). Number of steps (nodes) for the premium calculation e.g. 100 275 | seed = (Integer). Used for seeding 276 | antithetic = (Boolean). A process in Montecarlo Simulation. Default False 277 | method = (String). Type of Simulation e.g. GBM 278 | div_list = (List). list of tuples with Ex-Dates and Dividend amounts. e.g. [('20180625',0.2),('20180727',0.6)] 279 | 280 | """ 281 | def __init__(self, instrument, spot0=None, rf_rate=0, cnv_yield=0, cost_yield=0, volatility=None, pricing_date=None, 282 | no_of_path=None, no_of_steps=None, mc_method=None, seed=None, antithetic=False, div_list=None, 283 | **kwargs): 284 | self.instrument = instrument 285 | self.spot0 = spot0 or .0001 286 | self.rf_rate = rf_rate or 0 287 | self.cnv_yield = cnv_yield or 0 288 | self.cost_yield = cost_yield or 0 289 | self.volatility = volatility or 0.10 290 | self._pricing_date = dt.strptime(pricing_date, '%Y%m%d') 291 | self._no_of_path = no_of_path 292 | self.no_of_steps = no_of_steps or 100 293 | self.seed = seed or 100 294 | self.antithetic = antithetic 295 | self.method = mc_method or ProcessNames.GEOMETRICBROWNIANMOTION.value 296 | self.div_list = div_list 297 | 298 | @property 299 | def div_processed(self): 300 | return dividend_processor(self.div_list, self._pricing_date, self.instrument.expiry_date) 301 | 302 | @property 303 | def drift(self): 304 | return self.rf_rate + self.cost_yield - self._cnv_yield 305 | 306 | @property 307 | def no_of_path(self): 308 | if self._no_of_path: 309 | return self._no_of_path 310 | else: 311 | if self.instrument.expiry_type == ExpiryType.EUROPEAN.value: 312 | if self.div_list: 313 | return 10000 314 | return 1000000 315 | elif self.instrument.expiry_type == ExpiryType.AMERICAN.value: 316 | return 10000 317 | 318 | @property 319 | def stimulation_type(self): 320 | if self.instrument.expiry_type == ExpiryType.EUROPEAN.value: 321 | if self.div_list: 322 | return StimulationType.FULLPATH.value 323 | else: 324 | return StimulationType.FINALVALUE.value 325 | elif self.instrument.expiry_type == ExpiryType.AMERICAN.value: 326 | return StimulationType.FULLPATH.value 327 | 328 | @property 329 | def delta_t(self): 330 | return self.maturity / self.no_of_steps 331 | 332 | @property 333 | def step_disc_fact(self): 334 | return e**(-self.rf_rate * self.maturity / self.no_of_steps) 335 | 336 | def stimulation_method(self): 337 | obj_stimulation = mc_methd_mapper[self.method](self.spot0, self.maturity, drift=self.drift, 338 | volatility=self.volatility, 339 | div_list_processed=self.div_processed, 340 | stimulation_type=self.stimulation_type, 341 | no_of_path=self.no_of_path, no_of_steps=self.no_of_steps, 342 | seed=self.seed, antithetic=self.antithetic) 343 | return obj_stimulation.stimulation() 344 | 345 | def option_payoff(self, stimulated_price): 346 | temp_zeros = np.zeros_like(stimulated_price) 347 | return np.maximum(self.option_flag * (stimulated_price - self.instrument.strike), temp_zeros) 348 | 349 | def LSM_model(self): 350 | _s_stimulated = self.stimulation_method() 351 | _intrinsic_val = self.option_payoff(_s_stimulated) 352 | _option_value = np.zeros_like(_intrinsic_val) 353 | _option_value[:, -1] = _intrinsic_val[:, -1] 354 | 355 | for t in range(self.no_of_steps - 1, 0, -1): 356 | if len(_s_stimulated[_intrinsic_val[:, t] > 0, t]) == 0: 357 | continue 358 | else: 359 | _ITM_check = _intrinsic_val[:, t] > 0 360 | _x_axis = _s_stimulated[_ITM_check, t] 361 | _y_axis = _option_value[_ITM_check, t + 1] * self.step_disc_fact 362 | _option_value[:, t] = _option_value[:, t + 1] * self.step_disc_fact 363 | laguerre_poly_degree = 4 364 | laguerre_poly_4 = np.polynomial.laguerre.lagfit(_x_axis, _y_axis, laguerre_poly_degree) 365 | _cond_pv = np.polynomial.laguerre.lagval(_x_axis, laguerre_poly_4) 366 | 367 | _option_value[_ITM_check, t] = np.where(_intrinsic_val[_ITM_check, t] > _cond_pv.transpose(), 368 | _intrinsic_val[_ITM_check, t], _y_axis) 369 | 370 | _option_value[:, 0] = _option_value[:, 1] * self.step_disc_fact 371 | return np.mean(_option_value[:, 0]) 372 | 373 | def valuation(self): 374 | if self.instrument.expiry_type == ExpiryType.EUROPEAN.value: 375 | if self.div_list: 376 | return np.average(self.option_payoff(self.stimulation_method()[:, -1]) 377 | * (e ** (-1 * self.rf_rate * self.maturity))) 378 | return np.average(self.option_payoff(self.stimulation_method()) * e ** (-1 * self.rf_rate * self.maturity)) 379 | elif self.instrument.expiry_type == ExpiryType.AMERICAN.value: 380 | return self.LSM_model() 381 | 382 | def risk_parameters(self): 383 | return None 384 | 385 | 386 | class BinomialModel(Model): 387 | """ 388 | This is the generalised Binomial model used for valuation calculation for both European and American type 389 | Args required: 390 | instrument = Instrument parameters mapped from instrument module 391 | spot0 = (Float) e.g. 110.0 392 | rf_rate = (Float < 1) e.g. 0.2 393 | cnv_yield = (Float < 1) e.g. 0.3 394 | cost_yield = (Float < 1) e.g. 0.2 395 | volatility = (Float < 1) e.g. 0.25 396 | pricing_date = (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210" 397 | no_of_steps = (Integer). Number of steps (nodes) for the premium calculation e.g. 100 398 | div_list = (List). list of tuples with Ex-Dates and Dividend amounts. e.g. [('20180625',0.2),('20180727',0.6)] 399 | 400 | """ 401 | 402 | def __init__(self, instrument, spot0=None, rf_rate=0, cnv_yield=0, cost_yield=0, 403 | volatility=None, pricing_date=None, no_of_steps=None, div_list=None, **kwargs): 404 | self.instrument = instrument 405 | self.spot0 = spot0 or 0.0001 406 | self.rf_rate = rf_rate or 0 407 | self.cnv_yield = cnv_yield or 0 408 | self.cost_yield = cost_yield or 0 409 | self.volatility = volatility or 0.10 410 | self._pricing_date = dt.strptime(pricing_date, '%Y%m%d') 411 | self.no_of_steps = no_of_steps or 100 412 | self.div_list = div_list 413 | self.cache_node = {} 414 | 415 | @property 416 | def drift(self): 417 | return self.rf_rate + self.cost_yield - self._cnv_yield 418 | 419 | @property 420 | def div_processed(self): 421 | return dividend_processor(self.div_list, self._pricing_date, self.instrument.expiry_date) 422 | 423 | @property 424 | def spot_update(self): 425 | return self.spot0 - pv_div(self.div_processed, 0, self.rf_rate) 426 | 427 | @property 428 | def t_delta(self): 429 | return self.maturity/self.no_of_steps 430 | 431 | @property 432 | def up_mult(self): 433 | return e**(self.volatility*(self.t_delta ** 0.5)) 434 | 435 | @property 436 | def up_prob(self): 437 | return (e**(self.drift * self.t_delta) - (1/self.up_mult))/(self.up_mult - (1/self.up_mult)) 438 | 439 | @property 440 | def step_discount_fact(self): 441 | return e**(-1*self.rf_rate * self.t_delta) 442 | 443 | def node_value_store(self, pv, intrinsic_value): 444 | if self.instrument.expiry_type == ExpiryType.AMERICAN.value: 445 | return max(pv, intrinsic_value) 446 | else: 447 | return pv 448 | 449 | def intrinsic_value(self, _spot): 450 | return max(self.option_flag * (_spot - self.instrument.strike), 0.0) 451 | 452 | def calc_spot(self, step_no, no_up): 453 | return self.spot_update * (self.up_mult**(2*no_up - step_no)) + \ 454 | pv_div(self.div_processed, self.t_delta * step_no, self.rf_rate) 455 | 456 | def node_value(self, step_no, no_up): 457 | cache_node_key = (step_no, no_up) 458 | if cache_node_key in self.cache_node: 459 | return self.cache_node[cache_node_key] 460 | else: 461 | _spot = self.calc_spot(step_no, no_up) 462 | _intrinsic_value = self.intrinsic_value(_spot) 463 | 464 | if step_no >= self.no_of_steps: 465 | return _intrinsic_value 466 | else: 467 | _pv = ((self.up_prob*self.node_value(step_no+1, no_up+1)) + 468 | ((1-self.up_prob)*self.node_value(step_no+1, no_up)))*self.step_discount_fact 469 | _node_value = self.node_value_store(_pv,_intrinsic_value) 470 | self.cache_node[cache_node_key] = _node_value 471 | return self.cache_node[cache_node_key] 472 | 473 | def valuation(self): 474 | return self.node_value(0, 0) 475 | 476 | def risk_parameters(self): 477 | pass 478 | 479 | 480 | -------------------------------------------------------------------------------- /quantsbin/montecarlo/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | -------------------------------------------------------------------------------- /quantsbin/montecarlo/namesnmapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | from enum import Enum 7 | 8 | 9 | class ProcessNames(Enum): 10 | GEOMETRICBROWNIANMOTION = 'GBM' 11 | 12 | 13 | class StimulationType(Enum): 14 | FULLPATH = 'Path' 15 | FINALVALUE = 'Final' 16 | 17 | 18 | from .stimulations import GeometricBrownianMotion 19 | 20 | mc_methd_mapper = { 21 | ProcessNames.GEOMETRICBROWNIANMOTION.value: GeometricBrownianMotion 22 | } 23 | -------------------------------------------------------------------------------- /quantsbin/montecarlo/stimulations.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | 6 | import numpy as np 7 | 8 | from .namesnmapper import StimulationType 9 | 10 | 11 | class GeometricBrownianMotion: 12 | def __init__(self, spot0, maturity, drift=0.0, volatility=0.1, stimulation_type=StimulationType.FINALVALUE, 13 | no_of_path=10000, no_of_steps=100, seed=None, antithetic=False, random_array=None, 14 | div_list_processed=None, **kwargs): 15 | self.spot0 = spot0 16 | self.maturity = maturity 17 | self.drift = drift 18 | self.volatility = volatility 19 | self.stimulation_type = stimulation_type 20 | self.no_of_path = no_of_path 21 | self.no_of_steps = no_of_steps 22 | self.seed = seed 23 | self.antithetic = antithetic 24 | self.random_array = random_array 25 | self.div_list_processed = div_list_processed 26 | 27 | @property 28 | def delta_maturity(self): 29 | return self.maturity / self.no_of_steps 30 | 31 | @property 32 | def norm_random(self): 33 | if self.random_array: 34 | if self.stimulation_type == StimulationType.FINALVALUE.value: 35 | assert (self.random_array.shape == (self.no_of_path, 1)), "Incorrect dimension of random array" 36 | if self.stimulation_type == StimulationType.FULLPATH.value: 37 | assert (self.random_array.shape == (self.no_of_path, self.no_of_steps)), "Incorrect dimension of array" 38 | __norm_random = self.random_array 39 | else: 40 | np.random.seed(self.seed) 41 | if self.stimulation_type == StimulationType.FINALVALUE.value: 42 | __norm_random = np.random.normal(size=(self.no_of_path, 1)) 43 | else: 44 | __norm_random = np.random.normal(size=(self.no_of_path, self.no_of_steps)) 45 | 46 | if self.antithetic: 47 | __norm_random = np.vstack((__norm_random, __norm_random * -1)) 48 | 49 | return __norm_random 50 | 51 | def _stimulate_final(self): 52 | return (self.spot0 * np.exp((self.drift - (self.volatility ** 2) / 2) * self.maturity 53 | + self.volatility * np.sqrt(self.maturity) * self.norm_random)) 54 | 55 | def _stimulate_path(self): 56 | __exp_term = ((self.drift - (self.volatility ** 2) / 2) * self.delta_maturity) + \ 57 | (self.volatility * np.sqrt(self.delta_maturity) * self.norm_random) 58 | __cum_exp_term = np.exp(np.cumsum(__exp_term, axis=1)) 59 | if self.antithetic: 60 | __paths = self.no_of_path * 2 61 | else: 62 | __paths = self.no_of_path 63 | __final_term = np.hstack((np.ones((__paths, 1)), __cum_exp_term)) 64 | _stimulated_spot = self.spot0 * __final_term 65 | for div in self.div_list_processed: 66 | _temp_n = int(div[0]/self.delta_maturity) 67 | _temp_cum_exp_term = div[1]*np.exp(np.cumsum(__exp_term[:, _temp_n:], axis=1)) 68 | _stimulated_spot[:, _temp_n+1:] = _stimulated_spot[:, _temp_n+1:] - _temp_cum_exp_term 69 | return _stimulated_spot 70 | 71 | def stimulation(self): 72 | if self.stimulation_type == StimulationType.FINALVALUE.value: 73 | return self._stimulate_final() 74 | elif self.stimulation_type == StimulationType.FULLPATH.value: 75 | return self._stimulate_path() 76 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | import setuptools 6 | from distutils.core import setup 7 | 8 | 9 | # try: 10 | # with open('README.md') as file: 11 | # long_description=file.read() 12 | # except: 13 | # long_description='Quantitative Finance Tools' 14 | 15 | try: 16 | import pypandoc 17 | long_description = pypandoc.convert('README.md', 'rst') 18 | except(IOError, ImportError): 19 | long_description = open('README.md').read() 20 | 21 | setup( 22 | name='Quantsbin', 23 | version='1.0.2', 24 | description='Quantitative Finance Tools', 25 | long_description=long_description, 26 | long_description_content_type='text/markdown', 27 | author='Quantsbin', 28 | author_email='contactus@quantsbin.com', 29 | url='https://github.com/quantsbin/Quantsbin', 30 | packages=['quantsbin', 'quantsbin.derivativepricing', 'quantsbin.montecarlo'], 31 | license='MIT', 32 | classifiers=[ 'Development Status :: 3 - Beta', 33 | 'Programming Language :: Python :: 3.4', 34 | 'Programming Language :: Python :: 3.5', 35 | 'Programming Language :: Python :: 3.6'] 36 | ) 37 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | """ 2 | developed by Quantsbin - Jun'18 3 | 4 | """ 5 | import unittest 6 | import quantsbin.derivativepricing as qbdp 7 | 8 | from quantsbin.derivativepricing.namesnmapper import VanillaOptionType, ExpiryType, UdlType, OBJECT_MODEL, DerivativeType 9 | 10 | 11 | Input = {'equityInst': {'option_type': 'Call', 12 | 'expiry_type': 'European', 13 | 'derivative_type': 'Vanilla Option', 14 | 'expiry_date': '20180630', 15 | 'strike': 100}, 16 | 'equityEng': {'model': 'BSM', 17 | 'spot0': 110, 18 | 'pricing_date':'20180531', 19 | 'volatility': 0.25, 20 | 'rf_rate': 0.05, 21 | 'pv_div': 0, 22 | 'yield_div':0.01}, 23 | 24 | 'futuresInst': {'option_type': 'Call', 25 | 'expiry_type': 'European', 26 | 'derivative_type': 'Vanilla Option', 27 | 'expiry_date': '20180630', 28 | 'strike': 100}, 29 | 'futuresEng': {'model': 'B76', 30 | 'fwd0': 110, 31 | 'pricing_date': '20180531', 32 | 'volatility': 0.25, 33 | 'rf_rate': 0.05}, 34 | 'fxInst': {'option_type': 'Call', 35 | 'expiry_type': 'European', 36 | 'derivative_type': 'Vanilla Option', 37 | 'expiry_date': '20180630', 38 | 'strike': 100}, 39 | 'fxEng': {'model': 'GK', 40 | 'spot0': 110, 41 | 'rf_rate_local': 0.05, 42 | 'pricing_date': '20180531', 43 | 'volatility': 0.25, 44 | 'rf_rate_foreign':0.03 }, 45 | 'comInst': {'option_type': 'Call', 46 | 'expiry_type': 'European', 47 | 'derivative_type': 'Vanilla Option', 48 | 'expiry_date': '20180630', 49 | 'strike': 100}, 50 | 'comEng': {'model': 'GK', 51 | 'spot0': 110, 52 | 'rf_rate': 0.05, 53 | 'cnv_yield': 0.03, 54 | 'cost_yield': 0.02, 55 | 'pricing_date': '20180531', 56 | 'volatility': 0.25}} 57 | 58 | Output = {'equity':{'payOff': 10, 59 | 'premium': 10.60964, 60 | 'riskParameters': {'Delta': 0.9209518466754392, 61 | 'Gamma': 0.01867131211008939, 62 | 'Phi': -8.326413956243696, 63 | 'Rho': 0.07447547801828713, 64 | 'Theta': -0.028982100654899527, 65 | 'Vega': 0.04642250887645513}}, 66 | 'futures': {'payOff': 10, 67 | 'premium': 10.278649928591477, 68 | 'riskParameters': {'Delta': 0.9139728421226333, 69 | 'Gamma': 0.019833947075722964, 70 | 'Rho': -0.8448205420760118, 71 | 'Theta': -0.0191391198399402, 72 | 'Vega': 0.04931316978416053}}, 73 | 'fx': {'payOff': 10, 74 | 'premium': 10.443695049596826, 75 | 'riskParameters': {'Delta': 0.9175179027112913, 76 | 'Gamma': 0.019248913449738413, 77 | 'Rho': 0.07416552311621331, 78 | 'Theta': -0.02402706559595148, 79 | 'Vega': 0.04785859987845919}}, 80 | 'commodity': {'payOff': 10, 81 | 'premium': 10.60964161458648, 82 | 'riskParameters': {'Delta': 0.5234330145822786, 83 | 'Gamma': 0.05542873261965293, 84 | 'Rho': 0.040506072164629855, 85 | 'Theta': -0.04991552463127051, 86 | 'Vega': 4.642250887645513}}} 87 | 88 | 89 | class Test_eqOption(unittest.TestCase): 90 | def setUp(self): 91 | self.eq_option = qbdp.EqOption(**Input['equityInst']) 92 | self.eq_option_engine = self.eq_option.engine(**Input['equityEng']) 93 | 94 | def test(self): 95 | if Input["equityEng"]["model"] in OBJECT_MODEL[UdlType.STOCK.value][Input['equityInst']['expiry_type']]: 96 | self.assertAlmostEqual(self.eq_option.payoff(Input['equityEng']['spot0']), Output['equity']['payOff'], places=5) 97 | self.assertAlmostEqual(self.eq_option_engine.valuation(), Output['equity']['premium'], places=5) 98 | self.assertAlmostEqual(self.eq_option_engine.risk_parameters()['delta'],Output['equity']['riskParameters']['Delta'], places=5) 99 | else: 100 | self.fail("Invalid Model") 101 | 102 | 103 | class Test_futOption(unittest.TestCase): 104 | def setUp(self): 105 | self.fut_option = qbdp.FutOption(**Input['futuresInst']) 106 | self.fut_option_engine = self.fut_option.engine(**Input['futuresEng']) 107 | 108 | def test(self): 109 | if Input["futuresEng"]["model"] in OBJECT_MODEL[UdlType.FUTURES.value][Input['futuresInst']['expiry_type']]: 110 | self.assertAlmostEqual(self.fut_option.payoff(Input['futuresEng']['fwd0']), Output['futures']['payOff'], places=5) 111 | self.assertAlmostEqual(self.fut_option_engine.valuation(), Output['futures']['premium'], places=5) 112 | self.assertAlmostEqual(self.fut_option_engine.risk_parameters()['gamma'], Output['futures']['riskParameters']['Gamma'], places=5) 113 | else: 114 | self.fail("Invalid Model") 115 | 116 | class Test_fxtOption(unittest.TestCase): 117 | def setUp(self): 118 | self.fx_option = qbdp.FXOption(**Input['fxInst']) 119 | self.fx_option_engine = self.fx_option.engine(**Input['fxEng']) 120 | 121 | def test(self): 122 | if Input["fxEng"]["model"] in OBJECT_MODEL[UdlType.FX.value][Input['fxInst']['expiry_type']]: 123 | self.assertAlmostEqual(self.fx_option.payoff(Input['fxEng']['spot0']), Output['fx']['payOff'],places=5) 124 | self.assertAlmostEqual(self.fx_option_engine.valuation(), Output['fx']['premium'], places=5) 125 | self.assertAlmostEqual(self.fx_option_engine.risk_parameters()['theta'], Output['fx']['riskParameters']['Theta'],places=5) 126 | else: 127 | self.fail("Invalid Model") 128 | 129 | class Test_comOption(unittest.TestCase): 130 | def setUp(self): 131 | self.com_option = qbdp.ComOption(**Input['comInst']) 132 | self.com_option_engine = self.com_option.engine(**Input['comEng']) 133 | 134 | def test(self): 135 | if Input["comEng"]["model"] in OBJECT_MODEL[UdlType.COMMODITY.value][Input['comInst']['expiry_type']]: 136 | self.assertAlmostEqual(self.com_option.payoff(Input['comEng']['spot0']), Output['commodity']['payOff'],places=5) 137 | self.assertAlmostEqual(self.com_option_engine.valuation(), Output['commodity']['premium'], places=5) 138 | self.assertAlmostEqual(self.com_option_engine.risk_parameters()['vega'], Output['commodity']['riskParameters']['Vega'],places=5) 139 | else: 140 | self.fail("Invalid Model") 141 | 142 | # eqOption1 = qbdp.EqOption(option_type='Call', strike=100, expiry_date='20180630') 143 | # models = eqOption1.list_models() 144 | # eqOption1_pricer = eqOption1.engine(model='BSM', spot0=100, pricing_date='20180531', volatility=.25, 145 | # rf_rate=.05, pv_div=0, 146 | # yield_div=.01) 147 | # premium = eqOption1_pricer.valuation() 148 | # risk_parameters = eqOption1_pricer.risk_parameters() 149 | 150 | 151 | if __name__ == "__main__": 152 | test_classes_to_run = [Test_eqOption, Test_futOption] 153 | 154 | loader = unittest.TestLoader() 155 | 156 | suites_list = [] 157 | for test_class in test_classes_to_run: 158 | suite = loader.loadTestsFromTestCase(test_class) 159 | suites_list.append(suite) 160 | 161 | big_suite = unittest.TestSuite(suites_list) 162 | 163 | runner = unittest.TextTestRunner() 164 | results = runner.run(big_suite) 165 | 166 | --------------------------------------------------------------------------------