Implied Volatilities and Model Calibration ========================================== This setion of the documentation illustrates how to calculate **implied volatilities** and how to calibrate a model to **VSTOXX volatility index call option quotes**. The example implements the calibration for a total of **one month worth of data**. .. code:: python from dx import * import numpy as np import pandas as pd import seaborn as sns; sns.set() VSTOXX Futures & Options Data ----------------------------- We start by loading **VSTOXX data** from a pandas ``HDFStore`` into ``DataFrame`` objects (source: Eurex, cf. http://www.eurexchange.com/advanced-services/). .. code:: python h5 = pd.HDFStore('./data/vstoxx_march_2014.h5', 'r') vstoxx_index = h5['vstoxx_index'] vstoxx_futures = h5['vstoxx_futures'] vstoxx_options = h5['vstoxx_options'] h5.close() **VSTOXX index** for the first quarter of 2014. .. code:: python %matplotlib inline vstoxx_index['V2TX'].plot(figsize=(10, 6)) .. parsed-literal:: .. image:: 09_dx_calibration_files/09_dx_calibration_8_1.png The **VSTOXX futures** data (8 futures maturities/quotes per day). .. code:: python vstoxx_futures.info() .. parsed-literal:: Int64Index: 504 entries, 0 to 503 Data columns (total 5 columns): DATE 504 non-null datetime64[ns] EXP_YEAR 504 non-null int64 EXP_MONTH 504 non-null int64 PRICE 504 non-null float64 MATURITY 504 non-null datetime64[ns] dtypes: datetime64[ns](2), float64(1), int64(2) memory usage: 23.6 KB .. code:: python vstoxx_futures.tail() .. raw:: html
DATE EXP_YEAR EXP_MONTH PRICE MATURITY
499 2014-03-31 2014 7 20.40 2014-07-18
500 2014-03-31 2014 8 20.70 2014-08-15
501 2014-03-31 2014 9 20.95 2014-09-19
502 2014-03-31 2014 10 21.05 2014-10-17
503 2014-03-31 2014 11 21.25 2014-11-21
The **VSTOXX options** data. This data set is quite large due to the large number of European put and call options on the VSTOXX. .. code:: python vstoxx_options.info() .. parsed-literal:: Int64Index: 46960 entries, 0 to 46959 Data columns (total 7 columns): DATE 46960 non-null datetime64[ns] EXP_YEAR 46960 non-null int64 EXP_MONTH 46960 non-null int64 TYPE 46960 non-null object STRIKE 46960 non-null float64 PRICE 46960 non-null float64 MATURITY 46960 non-null datetime64[ns] dtypes: datetime64[ns](2), float64(2), int64(2), object(1) memory usage: 2.9+ MB .. code:: python vstoxx_options.tail() .. raw:: html
DATE EXP_YEAR EXP_MONTH TYPE STRIKE PRICE MATURITY
46955 2014-03-31 2014 11 P 85 63.65 2014-11-21
46956 2014-03-31 2014 11 P 90 68.65 2014-11-21
46957 2014-03-31 2014 11 P 95 73.65 2014-11-21
46958 2014-03-31 2014 11 P 100 78.65 2014-11-21
46959 2014-03-31 2014 11 P 105 83.65 2014-11-21
As a helper function we need a function to calculate all relevant **third Fridays** for all relevant maturity months of the data sets. .. code:: python import datetime as dt import calendar def third_friday(date): day = 21 - (calendar.weekday(date.year, date.month, 1) + 2) % 7 return dt.datetime(date.year, date.month, day) .. code:: python third_fridays = {} for month in set(vstoxx_futures['EXP_MONTH']): third_fridays[month] = third_friday(dt.datetime(2014, month, 1)) .. code:: python third_fridays .. parsed-literal:: {1: datetime.datetime(2014, 1, 17, 0, 0), 2: datetime.datetime(2014, 2, 21, 0, 0), 3: datetime.datetime(2014, 3, 21, 0, 0), 4: datetime.datetime(2014, 4, 18, 0, 0), 5: datetime.datetime(2014, 5, 16, 0, 0), 6: datetime.datetime(2014, 6, 20, 0, 0), 7: datetime.datetime(2014, 7, 18, 0, 0), 8: datetime.datetime(2014, 8, 15, 0, 0), 9: datetime.datetime(2014, 9, 19, 0, 0), 10: datetime.datetime(2014, 10, 17, 0, 0), 11: datetime.datetime(2014, 11, 21, 0, 0)} Implied Volatilities from Market Quotes --------------------------------------- Often calibration efforts are undertaken to replicate the **market implied volatilities** or the so-called **volatility surface** as good as possible. With DX Analytics and the ``BSM_european_option`` class, you can efficiently calculate (i.e. numerically estimate) implied volatilities. For the example, we use the **VSTOXX futures and call options data** from 31. March 2014. Some definitions, the pre-selection of option data and the pre-definition of the market environment needed. .. code:: python V0 = 17.6639 # VSTOXX level on 31.03.2014 futures_data = vstoxx_futures[vstoxx_futures.DATE == '2014/3/31'].copy() options_data = vstoxx_options[(vstoxx_options.DATE == '2014/3/31') & (vstoxx_options.TYPE == 'C')].copy() me = market_environment('me', dt.datetime(2014, 3, 31)) me.add_constant('initial_value', 17.6639) # index on 31.03.2014 me.add_constant('volatility', 2.0) # for initialization me.add_curve('discount_curve', constant_short_rate('r', 0.01)) # assumption options_data['IMP_VOL'] = 0.0 # initialization new iv column The following loop now **calculates the implied volatilities** for all those options whose strike lies within the defined tolerance level. .. code:: python %%time tol = 0.3 # tolerance level for moneyness for option in options_data.index: # iterating over all option quotes forward = futures_data[futures_data['MATURITY'] == \ options_data.loc[option]['MATURITY']]['PRICE'].values # picking the right futures value if (forward * (1 - tol) < options_data.loc[option]['STRIKE'] < forward * (1 + tol)): # only for options with moneyness within tolerance call = options_data.loc[option] me.add_constant('strike', call['STRIKE']) me.add_constant('maturity', call['MATURITY']) call_option = BSM_european_option('call', me) options_data.loc[option, 'IMP_VOL'] = \ call_option.imp_vol(call['PRICE'], 'call', volatility_est=0.6) .. parsed-literal:: CPU times: user 514 ms, sys: 0 ns, total: 514 ms Wall time: 512 ms A selection of the **results**. .. code:: python options_data[60:70] .. raw:: html
DATE EXP_YEAR EXP_MONTH TYPE STRIKE PRICE MATURITY IMP_VOL
46230 2014-03-31 2014 5 C 12 7.55 2014-05-16 0.000000
46231 2014-03-31 2014 5 C 13 6.55 2014-05-16 0.000000
46232 2014-03-31 2014 5 C 14 5.55 2014-05-16 1.541568
46233 2014-03-31 2014 5 C 15 4.55 2014-05-16 1.321803
46234 2014-03-31 2014 5 C 16 3.65 2014-05-16 1.153001
46235 2014-03-31 2014 5 C 17 2.90 2014-05-16 1.042549
46236 2014-03-31 2014 5 C 18 2.35 2014-05-16 0.997178
46237 2014-03-31 2014 5 C 19 1.90 2014-05-16 0.969301
46238 2014-03-31 2014 5 C 20 1.55 2014-05-16 0.958777
46239 2014-03-31 2014 5 C 21 1.30 2014-05-16 0.968430
And the **complete results visualized**. .. code:: python import matplotlib.pyplot as plt %matplotlib inline plot_data = options_data[options_data.IMP_VOL > 0] plt.figure(figsize=(10, 6)) for maturity in sorted(set(options_data['MATURITY'])): data = plot_data.isin({'MATURITY': [maturity,]}) data = plot_data[plot_data.MATURITY == maturity] # select data for this maturity plt.plot(data['STRIKE'], data['IMP_VOL'], label=maturity.date(), lw=1.5) plt.plot(data['STRIKE'], data['IMP_VOL'], 'r.') plt.xlabel('strike') plt.ylabel('implied volatility of volatility') plt.legend() plt.show() .. image:: 09_dx_calibration_files/09_dx_calibration_28_0.png Market Modeling --------------- This sub-section now implements the model calibration based on **selected options data**. In particular, we choose, for a given pricing date, the following options data: - for a single maturity only - call options only - for a certain moneyness of the options Relevant Market Data ~~~~~~~~~~~~~~~~~~~~ The following following returns the **relevant market data per calibration date**: .. code:: python tol = 0.2 def get_option_selection(pricing_date, maturity, tol=tol): ''' Function selects relevant options data. ''' forward = vstoxx_futures[(vstoxx_futures.DATE == pricing_date) & (vstoxx_futures.MATURITY == maturity)]['PRICE'].values[0] option_selection = \ vstoxx_options[(vstoxx_options.DATE == pricing_date) & (vstoxx_options.MATURITY == maturity) & (vstoxx_options.TYPE == 'C') & (vstoxx_options.STRIKE > (1 - tol) * forward) & (vstoxx_options.STRIKE < (1 + tol) * forward)] return option_selection, forward Options Modeling ~~~~~~~~~~~~~~~~ Given the options and their respective quotes to which to calibrate the model, the function ``get_option_models`` returns the **DX Analytics option models for all relevant options**. As **risk factor model** we use the ``square_root_diffusion`` class. .. code:: python def get_option_models(pricing_date, maturity, option_selection): ''' Models and returns traded options for given option_selection object. ''' me_vstoxx = market_environment('me_vstoxx', pricing_date) initial_value = vstoxx_index['V2TX'][pricing_date] me_vstoxx.add_constant('initial_value', initial_value) me_vstoxx.add_constant('final_date', maturity) me_vstoxx.add_constant('currency', 'EUR') me_vstoxx.add_constant('frequency', 'W') me_vstoxx.add_constant('paths', 10000) csr = constant_short_rate('csr', 0.01) # somewhat arbitrarily chosen here me_vstoxx.add_curve('discount_curve', csr) # parameters to be calibrated later me_vstoxx.add_constant('kappa', 1.0) me_vstoxx.add_constant('theta', 1.2 * initial_value) me_vstoxx.add_constant('volatility', 1.0) vstoxx_model = square_root_diffusion('vstoxx_model', me_vstoxx) # square-root diffusion for volatility modeling # mean-reverting, positive process # option parameters and payoff me_vstoxx.add_constant('maturity', maturity) payoff_func = 'np.maximum(maturity_value - strike, 0)' option_models = {} for option in option_selection.index: strike = option_selection['STRIKE'].ix[option] me_vstoxx.add_constant('strike', strike) option_models[option] = \ valuation_mcs_european_single( 'eur_call_%d' % strike, vstoxx_model, me_vstoxx, payoff_func) return vstoxx_model, option_models The function ``calculate_model_values`` estimates and returns **model value estimates for all relevant options** given a parameter set for the ``square_root_diffusion`` risk factor model. .. code:: python def calculate_model_values(p0): ''' Returns all relevant option values. Parameters =========== p0 : tuple/list tuple of kappa, theta, volatility Returns ======= model_values : dict dictionary with model values ''' kappa, theta, volatility = p0 vstoxx_model.update(kappa=kappa, theta=theta, volatility=volatility) model_values = {} for option in option_models: model_values[option] = \ option_models[option].present_value(fixed_seed=True) return model_values Calibration Functions --------------------- Mean-Squared Error Calculation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The calibration of the pricing model is based on the **minimization of the mean-squared error (MSE)** of the model values vs. the market quotes. The MSE calculation is implemented by the function ``mean_squared_error`` which also **penalizes** economically implausible parameter values. .. code:: python i = 0 def mean_squared_error(p0): ''' Returns the mean-squared error given the model and market values. Parameters =========== p0 : tuple/list tuple of kappa, theta, volatility Returns ======= MSE : float mean-squared error ''' if p0[0] < 0 or p0[1] < 5. or p0[2] < 0 or p0[2] > 10.: return 100 global i, option_selection, vstoxx_model, option_models, first, last pd = dt.datetime.strftime( option_selection['DATE'].iloc[0].to_pydatetime(), '%d-%b-%Y') mat = dt.datetime.strftime( option_selection['MATURITY'].iloc[0].to_pydatetime(), '%d-%b-%Y') model_values = calculate_model_values(p0) option_diffs = {} for option in model_values: option_diffs[option] = (model_values[option] - option_selection['PRICE'].loc[option]) MSE = np.sum(np.array(option_diffs.values()) ** 2) / len(option_diffs) if i % 150 == 0: # output every 0th and 100th iteration if i == 0: print '%12s %13s %4s %6s %6s %6s --> %6s' % \ ('pricing_date', 'maturity_date', 'i', 'kappa', 'theta', 'vola', 'MSE') print '%12s %13s %4d %6.3f %6.3f %6.3f --> %6.3f' % \ (pd, mat, i, p0[0], p0[1], p0[2], MSE) i += 1 return MSE Implementing the Calibration Procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The function ``get_parameter_series`` calibrates the model to the market data for **every date** contained in the ``pricing_date_list`` object for **all maturities** contained in the ``maturity_list`` object. .. code:: python import scipy.optimize as spo def get_parameter_series(pricing_date_list, maturity_list): global i, option_selection, vstoxx_model, option_models, first, last # collects optimization results for later use (eg. visualization) parameters = pd.DataFrame() for maturity in maturity_list: first = True for pricing_date in pricing_date_list: option_selection, forward = \ get_option_selection(pricing_date, maturity) vstoxx_model, option_models = \ get_option_models(pricing_date, maturity, option_selection) if first is True: # use brute force for the first run i = 0 opt = spo.brute(mean_squared_error, ((0.5, 2.51, 1.), # range for kappa (10., 20.1, 5.), # range for theta (0.5, 10.51, 5.0)), # range for volatility finish=None) i = 0 opt = spo.fmin(mean_squared_error, opt, maxiter=200, maxfun=350, xtol=0.0000001, ftol=0.0000001) parameters = parameters.append( pd.DataFrame( {'pricing_date' : pricing_date, 'maturity' : maturity, 'initial_value' : vstoxx_model.initial_value, 'kappa' : opt[0], 'theta' : opt[1], 'sigma' : opt[2], 'MSE' : mean_squared_error(opt)}, index=[0,]), ignore_index=True) first = False last = opt return parameters The Calibration Itself ~~~~~~~~~~~~~~~~~~~~~~ This completes the set of necessary function to implement such a **larger calibration effort**. The following code defines the **dates** for which a calibration shall be conducted and for which **maturities** the calibration is required. .. code:: python %%time pricing_date_list = pd.date_range('2014/3/1', '2014/3/31', freq='B') maturity_list = [third_fridays[7]] parameters = get_parameter_series(pricing_date_list, maturity_list) .. parsed-literal:: pricing_date maturity_date i kappa theta vola --> MSE 03-Mar-2014 18-Jul-2014 0 0.500 10.000 0.500 --> 4.507 pricing_date maturity_date i kappa theta vola --> MSE 03-Mar-2014 18-Jul-2014 0 2.500 15.000 5.500 --> 0.022 03-Mar-2014 18-Jul-2014 150 2.490 17.012 4.665 --> 0.005 Optimization terminated successfully. Current function value: 0.004840 Iterations: 146 Function evaluations: 296 pricing_date maturity_date i kappa theta vola --> MSE 04-Mar-2014 18-Jul-2014 0 2.497 17.010 4.674 --> 0.048 04-Mar-2014 18-Jul-2014 150 2.474 17.633 4.738 --> 0.003 Optimization terminated successfully. Current function value: 0.002750 Iterations: 70 Function evaluations: 164 pricing_date maturity_date i kappa theta vola --> MSE 05-Mar-2014 18-Jul-2014 0 2.474 17.633 4.738 --> 0.008 05-Mar-2014 18-Jul-2014 150 3.042 17.856 4.919 --> 0.003 05-Mar-2014 18-Jul-2014 300 4.407 17.995 5.667 --> 0.003 Warning: Maximum number of function evaluations has been exceeded. pricing_date maturity_date i kappa theta vola --> MSE 06-Mar-2014 18-Jul-2014 0 4.407 17.995 5.668 --> 0.004 06-Mar-2014 18-Jul-2014 150 4.543 18.209 5.661 --> 0.003 Optimization terminated successfully. Current function value: 0.003179 Iterations: 76 Function evaluations: 175 pricing_date maturity_date i kappa theta vola --> MSE 07-Mar-2014 18-Jul-2014 0 4.543 18.209 5.661 --> 0.030 07-Mar-2014 18-Jul-2014 150 4.958 18.332 5.553 --> 0.005 Optimization terminated successfully. Current function value: 0.004775 Iterations: 84 Function evaluations: 183 pricing_date maturity_date i kappa theta vola --> MSE 10-Mar-2014 18-Jul-2014 0 4.958 18.332 5.553 --> 0.086 10-Mar-2014 18-Jul-2014 150 4.816 18.733 5.722 --> 0.003 Optimization terminated successfully. Current function value: 0.002975 Iterations: 75 Function evaluations: 173 pricing_date maturity_date i kappa theta vola --> MSE 11-Mar-2014 18-Jul-2014 0 4.816 18.733 5.722 --> 0.006 11-Mar-2014 18-Jul-2014 150 4.281 19.060 5.162 --> 0.004 Optimization terminated successfully. Current function value: 0.004069 Iterations: 100 Function evaluations: 210 pricing_date maturity_date i kappa theta vola --> MSE 12-Mar-2014 18-Jul-2014 0 4.281 19.060 5.162 --> 0.008 12-Mar-2014 18-Jul-2014 150 4.461 18.959 5.231 --> 0.005 Optimization terminated successfully. Current function value: 0.004915 Iterations: 67 Function evaluations: 164 pricing_date maturity_date i kappa theta vola --> MSE 13-Mar-2014 18-Jul-2014 0 4.461 18.959 5.231 --> 0.007 13-Mar-2014 18-Jul-2014 150 4.515 18.920 5.333 --> 0.006 Optimization terminated successfully. Current function value: 0.005971 Iterations: 84 Function evaluations: 189 pricing_date maturity_date i kappa theta vola --> MSE 14-Mar-2014 18-Jul-2014 0 4.515 18.920 5.333 --> 0.017 14-Mar-2014 18-Jul-2014 150 5.124 18.963 5.952 --> 0.003 Optimization terminated successfully. Current function value: 0.002936 Iterations: 131 Function evaluations: 280 pricing_date maturity_date i kappa theta vola --> MSE 17-Mar-2014 18-Jul-2014 0 5.223 19.002 5.997 --> 0.025 17-Mar-2014 18-Jul-2014 150 5.330 18.581 6.097 --> 0.004 Optimization terminated successfully. Current function value: 0.003809 Iterations: 81 Function evaluations: 185 pricing_date maturity_date i kappa theta vola --> MSE 18-Mar-2014 18-Jul-2014 0 5.330 18.581 6.097 --> 0.006 18-Mar-2014 18-Jul-2014 150 3.838 18.503 5.161 --> 0.003 Optimization terminated successfully. Current function value: 0.002652 Iterations: 144 Function evaluations: 300 18-Mar-2014 18-Jul-2014 300 3.191 18.288 4.852 --> 0.003 pricing_date maturity_date i kappa theta vola --> MSE 19-Mar-2014 18-Jul-2014 0 3.191 18.288 4.852 --> 0.005 19-Mar-2014 18-Jul-2014 150 3.136 18.084 4.968 --> 0.003 Optimization terminated successfully. Current function value: 0.003397 Iterations: 67 Function evaluations: 169 pricing_date maturity_date i kappa theta vola --> MSE 20-Mar-2014 18-Jul-2014 0 3.136 18.084 4.968 --> 0.010 20-Mar-2014 18-Jul-2014 150 2.936 18.441 4.849 --> 0.002 Optimization terminated successfully. Current function value: 0.002263 Iterations: 128 Function evaluations: 267 pricing_date maturity_date i kappa theta vola --> MSE 21-Mar-2014 18-Jul-2014 0 2.928 18.450 4.842 --> 0.044 21-Mar-2014 18-Jul-2014 150 2.935 19.134 4.876 --> 0.004 Optimization terminated successfully. Current function value: 0.003655 Iterations: 64 Function evaluations: 158 pricing_date maturity_date i kappa theta vola --> MSE 24-Mar-2014 18-Jul-2014 0 2.935 19.134 4.876 --> 0.021 24-Mar-2014 18-Jul-2014 150 5.169 18.555 6.217 --> 0.004 Optimization terminated successfully. Current function value: 0.004381 Iterations: 138 Function evaluations: 297 pricing_date maturity_date i kappa theta vola --> MSE 25-Mar-2014 18-Jul-2014 0 5.653 18.592 6.449 --> 0.014 25-Mar-2014 18-Jul-2014 150 6.252 18.525 6.554 --> 0.003 Optimization terminated successfully. Current function value: 0.002918 Iterations: 107 Function evaluations: 231 pricing_date maturity_date i kappa theta vola --> MSE 26-Mar-2014 18-Jul-2014 0 6.251 18.525 6.553 --> 0.014 26-Mar-2014 18-Jul-2014 150 5.189 18.301 6.063 --> 0.003 Optimization terminated successfully. Current function value: 0.002839 Iterations: 107 Function evaluations: 243 pricing_date maturity_date i kappa theta vola --> MSE 27-Mar-2014 18-Jul-2014 0 5.189 18.301 6.063 --> 0.006 27-Mar-2014 18-Jul-2014 150 5.789 18.693 6.112 --> 0.003 Optimization terminated successfully. Current function value: 0.002992 Iterations: 112 Function evaluations: 248 pricing_date maturity_date i kappa theta vola --> MSE 28-Mar-2014 18-Jul-2014 0 5.788 18.693 6.111 --> 0.003 28-Mar-2014 18-Jul-2014 150 5.684 18.828 5.974 --> 0.003 Optimization terminated successfully. Current function value: 0.002811 Iterations: 97 Function evaluations: 216 pricing_date maturity_date i kappa theta vola --> MSE 31-Mar-2014 18-Jul-2014 0 5.683 18.828 5.974 --> 0.009 31-Mar-2014 18-Jul-2014 150 12.121 18.656 8.053 --> 0.004 31-Mar-2014 18-Jul-2014 300 15.247 18.578 8.978 --> 0.004 Warning: Maximum number of function evaluations has been exceeded. CPU times: user 1min 9s, sys: 6.7 s, total: 1min 15s Wall time: 1min 15s Calibration Results ------------------- The results are now stored in the pandas ``DataFrame`` called ``parameters``. We set a new index and inspect the last results. Throughout the MSE is pretty low indicated a good fit of the model to the market quotes. .. code:: python paramet = parameters.set_index('pricing_date') paramet.tail() .. raw:: html
MSE initial_value kappa maturity sigma theta
pricing_date
2014-03-25 0.002918 18.2637 6.250875 2014-07-18 6.553352 18.525022
2014-03-26 0.002839 17.5869 5.189260 2014-07-18 6.062754 18.301087
2014-03-27 0.002992 17.6397 5.787693 2014-07-18 6.111093 18.693053
2014-03-28 0.002811 17.0324 5.683422 2014-07-18 5.974289 18.827773
2014-03-31 0.003657 17.6639 15.246458 2014-07-18 8.978325 18.578233
This is also illustrated by the visualization of the time series data for the **calibrated/optimal parameter values**. The **MSE** is below 0.01 throughout. .. code:: python %matplotlib inline paramet[['kappa', 'theta', 'sigma', 'MSE']].plot(subplots=True, color='b', figsize=(10, 12)) plt.tight_layout() .. image:: 09_dx_calibration_files/09_dx_calibration_53_0.png The following generates a plot of the calibration results for the **last calibration day**. The absolute price differences are below **0.10 EUR** for all options. .. code:: python index = paramet.index[-1] opt = np.array(paramet[['kappa', 'theta', 'sigma']].loc[index]) option_selection = get_option_selection(index, maturity_list[0], tol=tol)[0] model_values = np.sort(np.array(calculate_model_values(opt).values()))[::-1] import matplotlib.pyplot as plt %matplotlib inline fix, (ax1, ax2) = plt.subplots(2, sharex=True, figsize=(10, 8)) strikes = option_selection['STRIKE'].values ax1.plot(strikes, option_selection['PRICE'], label='market quotes') ax1.plot(strikes, model_values, 'ro', label='model values') ax1.set_ylabel('option values') ax1.grid(True) ax1.legend(loc=0) wi = 0.25 ax2.bar(strikes - wi / 2., model_values - option_selection['PRICE'], label='market quotes', width=wi) ax2.grid(True) ax2.set_ylabel('differences') ax2.set_xlabel('strikes') .. parsed-literal:: .. image:: 09_dx_calibration_files/09_dx_calibration_55_1.png **Copyright, License & Disclaimer** © Dr. Yves J. Hilpisch \| The Python Quants GmbH DX Analytics (the "dx library") is licensed under the GNU Affero General Public License version 3 or later (see http://www.gnu.org/licenses/). DX Analytics comes with no representations or warranties, to the extent permitted by applicable law. http://tpq.io \| team@tpq.io \| http://twitter.com/dyjh **Quant Platform** \| http://quant-platform.com **Derivatives Analytics with Python (Wiley Finance)** \| http://derivatives-analytics-with-python.com **Python for Finance (O'Reilly)** \| http://python-for-finance.com