├── .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 |
10 |
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 | [](https://pypi.python.org/pypi/pandas-montecarlo) [](https://pypi.python.org/pypi/quantsbin) [](https://github.com/quantsbin/Quantsbin) [](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 |
--------------------------------------------------------------------------------