├── qmoms ├── data │ ├── __init__.py │ ├── zerocd.csv │ └── surface.csv ├── examples │ ├── __init__.py │ └── qmoms_test.py ├── __init__.py ├── qmoms_params.py ├── qmoms_utils.py └── qmoms.py ├── MANIFEST.in ├── requirements.txt ├── setup.py.bak ├── LICENSE.md ├── pyproject.toml ├── .gitignore └── README.md /qmoms/data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include qmoms/data/*.csv 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.19 2 | pandas>=2.0 3 | scipy>=1.10 4 | tqdm>=4.0 -------------------------------------------------------------------------------- /setup.py.bak: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import io 3 | from os import path 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | with io.open(path.join(here, 'requirements.txt'), encoding='utf-8') as f: 7 | requirements = [line.rstrip() for line in f] 8 | 9 | setup( 10 | name='qmoms', 11 | version='0.1', 12 | packages=find_packages(), 13 | description='Option-implied moments from implied volatility surface data', 14 | author='Grigory Vilkov', 15 | author_email='vilkov@vilkov.net', 16 | license='MIT', 17 | platforms=['any'], 18 | keywords=['implied variance', 'variance swap', 'VIX', 'MFIV', 'CVIX', 'skewness'], 19 | install_requires=requirements, 20 | package_data={ 21 | 'qmoms': ['data/*.csv'], 22 | }, 23 | include_package_data=True, 24 | classifiers=[ 25 | 'License :: OSI Approved :: MIT License', 26 | 'Programming Language :: Python :: 3.9', 27 | 'Programming Language :: Python :: 3.10', 28 | 'Programming Language :: Python :: 3.11', 29 | 'Operating System :: OS Independent', 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2008-2024 Grigory Vilkov 4 | 5 | Contact: vilkov@vilkov.net www.vilkov.net 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "qmoms" 7 | version = "0.1" 8 | description = "Option-implied moments from implied volatility surface data" 9 | authors = [{ name = "Grigory Vilkov", email = "vilkov@vilkov.net" }] 10 | license = { file = "LICENSE.md" } 11 | readme = "README.md" 12 | keywords = ["implied variance", "variance swap", "VIX", "MFIV", "CVIX", "skewness"] 13 | classifiers = [ 14 | "License :: OSI Approved :: MIT License", 15 | "Programming Language :: Python :: 3.9", 16 | "Programming Language :: Python :: 3.10", 17 | "Programming Language :: Python :: 3.11", 18 | "Operating System :: OS Independent", 19 | ] 20 | requires-python = ">=3.9" 21 | dependencies = [ 22 | "numpy >=1.19", 23 | "pandas >=2.0", 24 | "scipy >=1.10", 25 | "tqdm >=4.0", 26 | ] 27 | 28 | [tool.setuptools] 29 | include-package-data = true 30 | package-dir = { "" = "." } 31 | package-data = { "qmoms" = ["data/*.csv"] } 32 | 33 | [tool.setuptools.packages.find] 34 | where = ["."] # This finds packages in the specified directory 35 | 36 | 37 | [project.urls] 38 | Homepage = "https://github.com/vilkovgr/qmoms" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | setup.py.bak 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # celery beat schedule file 87 | celerybeat-schedule 88 | 89 | # SageMath parsed files 90 | *.sage.py 91 | 92 | # PyCharm 93 | .idea/ 94 | 95 | # Environments 96 | .env 97 | .venv 98 | env/ 99 | venv/ 100 | ENV/ 101 | env.bak/ 102 | venv.bak/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | .dmypy.json 117 | dmypy.json 118 | 119 | # Pyre type checker 120 | .pyre/ 121 | 122 | -------------------------------------------------------------------------------- /qmoms/examples/__init__.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from qmoms import default_surf_dtype_mapping, default_rate_dtype_mapping, default_date_format 3 | from importlib.resources import files 4 | 5 | 6 | def load_data(surf_dtype_mapping=default_surf_dtype_mapping, 7 | rate_dtype_mapping=default_rate_dtype_mapping, 8 | surf_date_format=default_date_format, 9 | rate_date_format=default_date_format, 10 | rate_factor=0.01): 11 | """ 12 | Load and process example datasets for surface and zero coupon rate data from CSV files. 13 | 14 | Parameters: 15 | - surf_dtype_mapping (dict, optional): Data types for surface data columns. Defaults to: 16 | {'id': 'int64', 'date': 'str', 'days': 'int64', 'mnes': 'float64', 17 | 'prem': 'float64', 'impl_volatility': 'float64', 'delta': 'float64'}. 18 | - rate_dtype_mapping (dict, optional): Data types for zero coupon rate data columns. 19 | Defaults to: {'date': 'str', 'days': 'int64', 'rate': 'float64'} 20 | - surf_date_format (str, optional): Date format for surface data, used to parse string dates into datetime objects. 21 | - rate_date_format (str, optional): Date format for zero coupon rate data, similar to surf_date_format. 22 | - rate_factor (float, optional): Factor by which the 'rate' column values are adjusted. Defaults to 0.01. 23 | 24 | Returns: 25 | - tuple(pandas.DataFrame, pandas.DataFrame): Returns two DataFrames: 26 | - df_surf: Contains surface data, where 'delta' is adjusted and 'date' is converted. 27 | - df_rate: Contains zero coupon rate data, where 'rate' is adjusted by 'rate_factor' and 'date' is converted. 28 | 29 | Examples: 30 | ```python 31 | df_surf, df_rate = load_data() 32 | ``` 33 | """ 34 | data_files = files('qmoms').joinpath('data') 35 | df_surf = pd.read_csv(data_files / 'surface.csv', sep=',', dtype=surf_dtype_mapping) 36 | df_surf['date'] = pd.to_datetime(df_surf['date'], format=surf_date_format, errors='coerce') 37 | df_surf['delta'] = df_surf['delta'] / 100 38 | 39 | df_rate = pd.read_csv(data_files / 'zerocd.csv', sep=',', dtype=rate_dtype_mapping) 40 | df_rate['date'] = pd.to_datetime(df_rate['date'], format=rate_date_format, errors='coerce') 41 | df_rate['rate'] = df_rate['rate'] * rate_factor 42 | 43 | return df_surf, df_rate 44 | 45 | -------------------------------------------------------------------------------- /qmoms/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | MIT License 4 | 5 | Copyright (c) 2008-2024 Grigory Vilkov 6 | 7 | Contact: vilkov@vilkov.net www.vilkov.net 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | Original math is from 28 | 1. Bakshi, Kapadia, and Madan (RFS, 2003) for MFIV_BKM + Britten-Jones and Neuberger (JF, 2002) for MFIV_BJN 29 | https://doi.org/10.1093/rfs/16.1.0101 and https://doi.org/10.1111/0022-1082.00228 30 | 2. Martin (QJE, 2017) for SMFIV 31 | https://doi.org/10.1093/qje/qjw034 32 | 3. Andersen, Bondarenko, and Gonzalez-Perez (RFS, 2015) for CVIX 33 | https://doi.org/10.1093/rfs/hhv033 34 | 4. Vilkov, Xiao (2012) and Hamidieh (Journal of Risk, 2017) for TLM 35 | https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1940117 and 36 | https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2209654 37 | 5. Semivariances are based on CVIX derivation from #3/ 38 | following Feunou, Jahan-Parvar,and Okou, (Journal of Fin Econometrics, 2017) 39 | https://doi.org/10.1093/jjfinec/nbx020 40 | 6. RIX following Gao, Gao and Song, (RFS, 2018) 41 | https://doi.org/10.1093/rfs/hhy027 42 | 7. Slopeup and Slopedn measures used in Carbon Tail Risk, Ilhan, Sautner, and Vilkov, (RFS, 2021) and 43 | Pricing Climate Change Exposure, v.Lent, Sautner, Zhang, and Vilkov, (Management Science, 2023) 44 | https://doi.org/10.1093/rfs/hhaa071 and https://doi.org/10.1287/mnsc.2023.4686 45 | (mind the sign of slopeup -- we take '-Slope' here for better interpretability) 46 | Originally Slopedn is used in https://doi.org/10.1111/jofi.12406 Kelly, Pastor, Veronesi (JF, 2016) 47 | """ 48 | 49 | __version__ = 0.1 50 | name = 'qmoms' 51 | 52 | from .qmoms_params import * 53 | from .qmoms import qmoms_compute_bygroup, qmoms_compute 54 | from .qmoms_utils import * 55 | 56 | -------------------------------------------------------------------------------- /qmoms/qmoms_params.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2008-2024 Grigory Vilkov 5 | 6 | Contact: vilkov@vilkov.net www.vilkov.net 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | ''' 26 | 27 | # MAIN PARAMETERS FOR THE PROCEDURE 28 | default_params = {'atmfwd': False, # if True, use FWD as ATM level and Black model for option valuation 29 | 'grid': {'number_points': 500, # points*2 + 1 30 | 'grid_limit': 2}, # 1/(1+k) to 1+k; typically, k = 2 31 | 'filter': {'mnes_lim': [0, 1000], # [0.7, 1.3], 32 | 'delta_put_lim': [-0.5 + 1e-3, 0], # typically, only OTM 33 | 'delta_call_lim': [0, 0.5], # typically, only OTM 34 | 'best_bid_zero': -1, # if negative, no restriction on zero bid; set to 0 for limit 35 | 'open_int_zero': -1, # if negative, no restriction on zero open interest; set to 0 for limit 36 | 'min_price': 0}, # a limit on the min mid-price 37 | 'semivars': {'compute': True}, # save semivariances or not, they are computed anyway 38 | 'mfismfik': {'compute': True}, # compute skewness/ kurtosis 39 | 'cvix': {'compute': True, # compute corridor VIX 40 | 'abs_dev': [0.2], # absolute deviation from ATM level of 1 41 | 'vol_dev': [2]}, # deviation in terms of own sigmas from ATM level of 1 42 | 'rix': {'compute': True}, # compute RIX (Gao,Gao,Song RFS) or not, 43 | 'tlm': {'compute': True, # compute Tail Loss Measure (Xiao/ Vilkov) or not 44 | 'delta_lim': [20], # OTM put delta for the threshold (pos->neg or neg) 45 | 'vol_lim': [2]}, # deviation to the left in terms of sigmas from ATM level of 1 46 | 'slope': {'compute': True, 47 | 'deltaP_lim': [-0.5, -0.05], 48 | # limits to compute the slopedn as Slope from IV = const + Slope * Delta 49 | 'deltaC_lim': [0.05, 0.5] 50 | # limits to compute the slopeup as -Slope from IV = const + Slope * Delta 51 | } 52 | } 53 | 54 | # specify mapping for the file with surface data 55 | default_surf_dtype_mapping = { 56 | 'id': 'int64', 57 | 'date': 'str', # Treat date as a string initially 58 | 'days': 'int64', 59 | 'mnes': 'float64', 60 | 'prem': 'float64', 61 | 'impl_volatility': 'float64', 62 | 'delta': 'float64' 63 | } 64 | 65 | default_rate_dtype_mapping = { 66 | 'date': 'str', # Treat date as a string initially 67 | 'days': 'int64', 68 | 'rate': 'float64' 69 | } 70 | 71 | default_date_format = '%d%b%Y' -------------------------------------------------------------------------------- /qmoms/qmoms_utils.py: -------------------------------------------------------------------------------- 1 | # import packages and variables 2 | import pandas as pd 3 | import numpy as np 4 | from multiprocessing import Pool, cpu_count 5 | import warnings 6 | 7 | # Ignore RuntimeWarning 8 | warnings.filterwarnings("ignore", category=RuntimeWarning) 9 | 10 | 11 | # ***************************************************************************** 12 | # execution over grouped data -- parallel and serial 13 | # ***************************************************************************** 14 | def applyParallel(dfGrouped, func_main, params, CPUUsed=20): 15 | with Pool(CPUUsed) as p: 16 | ret_list = p.map(func_main, [[group_indv, params] for name, group_indv in dfGrouped]) 17 | out = list(filter(None.__ne__, ret_list)) 18 | out = pd.DataFrame.from_records(out) 19 | return out 20 | 21 | 22 | def applySerial(dfGrouped, func_main, params): 23 | ret_list = [func_main([group_indv, params]) for name, group_indv in dfGrouped] 24 | out = list(filter(None.__ne__, ret_list)) 25 | out = pd.DataFrame.from_records(out) 26 | return out 27 | 28 | 29 | # ***************************************************************************** 30 | # % Filter Dataframes, apply this for a whole dataset and a specific grid 31 | # ***************************************************************************** 32 | def filter_options(optdata, filter): 33 | g1 = (optdata['mnes'] >= filter['mnes_lim'][0]) & (optdata['mnes'] <= filter['mnes_lim'][1]) 34 | g2 = (optdata['delta'] >= filter['delta_call_lim'][0]) & (optdata['delta'] <= filter['delta_call_lim'][1]) 35 | g3 = (optdata['delta'] >= filter['delta_put_lim'][0]) & (optdata['delta'] <= filter['delta_put_lim'][1]) 36 | g1 = g1 & (g2 | g3) 37 | 38 | if 'open_interest' in optdata.columns: 39 | g1 = g1 & (optdata['open_interest'] >= filter['open_int_zero']) 40 | if ('best_bid' in optdata.columns): 41 | g1 = g1 & (optdata['best_bid'] >= filter['best_bid_zero']) 42 | if ('best_offer' in optdata.columns): 43 | g1 = g1 & ((optdata['best_bid'] + optdata['best_offer']) / 2 >= filter['min_price']) 44 | 45 | # moneyness is sorted now from low to high 46 | optdata4interp = optdata.loc[g1, :].sort_values(['id', 'date', 'mnes'], ascending=[True, True, True]).copy() 47 | return optdata4interp 48 | 49 | 50 | # ***************************************************************************** 51 | # get rate with linear interpolation / pass ratedata as input 52 | # ***************************************************************************** 53 | def get_rate_for_maturity(df_rate, df_surf=None, date=None, days=None): 54 | """ 55 | Retrieves or interpolates the interest rate for a given date and maturity or across a surface of dates and maturities. 56 | 57 | Parameters: 58 | - df_rate (pd.DataFrame): DataFrame containing interest rates with columns 'date', 'days', and 'rate'. 59 | - df_surf (pd.DataFrame, optional): DataFrame representing the surface data with 'date' and 'days' columns. 60 | If provided, rates are interpolated for the entire surface. 61 | - date (str, optional): The specific date for which the rate needs to be retrieved or interpolated. 62 | Required if df_surf is not provided. 63 | - days (int, optional): The specific number of days until maturity for which the rate is required. 64 | Required if df_surf is not provided. 65 | 66 | Returns: 67 | - float or pd.DataFrame: Returns a single interpolated rate if 'date' and 'days' are provided. 68 | Returns a DataFrame with interpolated rates across the surface if 'df_surf' is provided. 69 | 70 | The function first checks if the input is sufficient to perform the operation. It then either: 71 | 1. Retrieves and interpolates the rate for a single date and maturity when 'date' and 'days' are provided. 72 | 2. Interpolates rates for an entire surface provided by 'df_surf' DataFrame, updating the 'rate' column based 73 | on available data in 'df_rate'. 74 | 75 | Errors: 76 | - Prints an error and returns NaN if neither sufficient single date and days nor a DataFrame surface is provided. 77 | 78 | Example: 79 | ```python 80 | # Single rate interpolation 81 | rate_on_date = get_rate_for_maturity(df_rate, date='2023-01-01', days=360) 82 | 83 | # Surface rate interpolation 84 | df_interpolated_rates = get_rate_for_maturity(df_rate, df_surf=df_surface) 85 | ``` 86 | """ 87 | 88 | if (df_surf is None) and ((date is None) or (days is None)): 89 | print('Error: Not enough inputs, provide date and days') 90 | return np.nan 91 | 92 | # return just a number for that particular date 93 | if ((date is not None) and (days is not None)): 94 | # get rate for first_date 95 | ZeroCoupon = df_rate[df_rate['date'] == date].copy() 96 | # if date is not available, get last available date 97 | if ZeroCoupon.empty: 98 | ZeroCoupon = df_rate[df_rate['date'] <= date].copy() 99 | last_available_date = ZeroCoupon['date'].iloc[-1] 100 | ZeroCoupon = df_rate[df_rate['date'] == last_available_date].copy() 101 | pass 102 | # get the rate with the maturity 103 | ZeroCoupon.sort_values(by=['date', 'days'], inplace=True) 104 | x = days 105 | xp = ZeroCoupon['days'] 106 | fp = ZeroCoupon['rate'] 107 | y = np.interp(x, xp, fp, left=None, right=None) 108 | return y 109 | 110 | # interpolate for the whole surface and return a frame 111 | if isinstance(df_surf, pd.DataFrame): 112 | if 'rate' in df_surf.columns: 113 | df_surf = df_surf.drop(columns=['rate']) 114 | df_surf_rate = df_surf[['date', 'days']].drop_duplicates() 115 | df_surf_rate['rate'] = np.nan 116 | 117 | # Merge df_rate and df_surf_rate on 'date' 118 | merged_df = pd.concat([df_rate, df_surf_rate], axis=0) 119 | 120 | # Sort by 'date' and 'days_surf' because we want to interpolate based on df_surf_rate's 'days' 121 | merged_df.sort_values(by=['date', 'days'], inplace=True) 122 | 123 | # Now group by 'date' and apply the interpolation for each group in the 'days' dimension 124 | def interpolate_group(group): 125 | # Ensure the days are sorted for interpolation 126 | group = group.sort_values(by='days') 127 | bads = group['rate'].isna() 128 | x = group.loc[bads, 'days'] 129 | xp = group.loc[~bads, 'days'] 130 | fp = group.loc[~bads, 'rate'] 131 | y = np.interp(x, xp, fp, left=None, right=None) 132 | # Interpolate the missing rates 133 | group.loc[bads, 'rate'] = y 134 | return group 135 | 136 | # Apply interpolation to each group 137 | interpolated_df = merged_df.groupby(['date'])[['days', 'rate']]. \ 138 | apply(interpolate_group). \ 139 | reset_index()[['date', 'days', 'rate']] 140 | 141 | df_surf = df_surf.merge(interpolated_df, how='left', on=['date', 'days']) 142 | return df_surf 143 | -------------------------------------------------------------------------------- /qmoms/examples/qmoms_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | MIT License 4 | 5 | Copyright (c) 2008-2024 Grigory Vilkov 6 | 7 | Contact: vilkov@vilkov.net www.vilkov.net 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | _________________________________________________________________________________ 27 | Partially based on Matlab code for implied moments by G.Vilkov (www.vilkov.net) 28 | Original math is from 29 | 1. Bakshi, Kapadia, and Madan (RFS, 2003) for MFIV_BKM + Britten-Jones and Neuberger (JF, 2002) for MFIV_BJN 30 | https://doi.org/10.1093/rfs/16.1.0101 and https://doi.org/10.1111/0022-1082.00228 31 | 2. Martin (QJE, 2017) for SMFIV 32 | https://doi.org/10.1093/qje/qjw034 33 | 3. Andersen, Bondarenko, and Gonzalez-Perez (RFS, 2015) for CVIX 34 | https://doi.org/10.1093/rfs/hhv033 35 | 4. Vilkov, Xiao (2012) and Hamidieh (Journal of Risk, 2017) for TLM 36 | https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1940117 and https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2209654 37 | 5. Semivariances are based on CVIX derivation from #3/ 38 | following Feunou, Jahan-Parvar,and Okou, (Journal of Fin Econometrics, 2017) 39 | https://doi.org/10.1093/jjfinec/nbx020 40 | 6. RIX following Gao, Gao and Song, (RFS, 2018) 41 | https://doi.org/10.1093/rfs/hhy027 42 | 7. Slopeup and Slopedn measures as used in Carbon Tail Risk, Ilhan, Sautner, and Vilkov, (RFS, 2021) and 43 | Pricing Climate Change Exposure, v.Lent, Sautner, Zhang, and Vilkov, (Management Science, 2023) 44 | https://doi.org/10.1093/rfs/hhaa071 and https://doi.org/10.1287/mnsc.2023.4686 45 | (mind the sign of slopeup -- we take '-Slope' here for better interpretability) 46 | Originally Slopedn is used in https://doi.org/10.1111/jofi.12406 Kelly, Pastor, Veronesi (JF, 2016) 47 | 48 | Data required: 49 | 1. Zero-cd rates 50 | -- one can use zero cd rates from OptionMetrics, or any other provider, e.g., short-term t-bill rates from FRED 51 | 52 | 2. Surface data from OptionMetrics or any other provider 53 | -- see attached files for 1996 for all SP500 components and SP itself (ID = 99991) 54 | for formats and names of the columns 55 | -- note that the ID gives the PERMNO of the company and is based on CRSP data 56 | 57 | Output: csv file with all the required option-based measures (the file with specified measures for 1996 is attached) 58 | 59 | Notes: 60 | -- the code is written in a very simple form for a purpose -- this way it is easier to debug and adopt; 61 | Please write me with any comments on vilkov@vilkov.net 62 | 63 | """ 64 | # %% Load Variables and Dataframes 65 | # import packages and variables 66 | import pandas as pd 67 | from tqdm import * 68 | from multiprocessing import cpu_count, Pool 69 | from qmoms.examples import load_data 70 | from qmoms import default_params, default_surf_dtype_mapping, filter_options, get_rate_for_maturity 71 | from qmoms import qmoms_compute, qmoms_compute_bygroup 72 | 73 | # DEFAULT PARAMETERS FOR THE PROCEDURE 74 | ''' 75 | default_params = {'atmfwd': False, # if True, use FWD as ATM level and Black model for option valuation 76 | 'grid': {'number_points': 500, # points*2 + 1 77 | 'grid_limit': 2}, # 1/(1+k) to 1+k; typically, k = 2 78 | 'filter': {'mnes_lim': [0, 1000], # [0.7, 1.3], 79 | 'delta_put_lim': [-0.5 + 1e-3, 0], # typically, only OTM 80 | 'delta_call_lim': [0, 0.5], # typically, only OTM 81 | 'best_bid_zero': -1, # if negative, no restriction on zero bid; 82 | 'open_int_zero': -1, # if negative, no restriction on zero open interest; 83 | 'min_price': 0}, # a limit on the min mid-price 84 | 'semivars': {'compute': True}, # save semivariances or not, they are computed anyway 85 | 'mfismfik': {'compute': True}, # compute skewness/ kurtosis 86 | 'cvix': {'compute': True, # compute corridor VIX 87 | 'abs_dev': [0.2], # absolute deviation from ATM level of 1 88 | 'vol_dev': [2]}, # deviation in terms of own sigmas from ATM level of 1 89 | 'rix': {'compute': True}, # compute RIX (Gao,Gao,Song RFS) or not, 90 | 'tlm': {'compute': True, # compute Tail Loss Measure (Xiao/ Vilkov) or not 91 | 'delta_lim': [20], # OTM put delta for the threshold (pos->neg or neg) 92 | 'vol_lim': [2]}, # deviation to the left in terms of sigmas from ATM level of 1 93 | 'slope': {'compute': True, 94 | 'deltaP_lim': [-0.5, -0.05], 95 | # limits to compute the slopedn as Slope from IV = const + Slope * Delta 96 | 'deltaC_lim': [0.05, 0.5] 97 | # limits to compute the slopeup as -Slope from IV = const + Slope * Delta 98 | } 99 | } 100 | ''' 101 | ########## 102 | ''' 103 | to modify parameters, update the default_params dictionary with the dictionary with desired parameters 104 | for example, in case you do not want to compute the tlm, and want to change slope parameters, use 105 | new_params0 = {'tlm': {'compute': False}} 106 | new_params1 = {'slope': {'compute': True, 107 | 'deltaP_lim': [-0.4, -0.1], 108 | # limits to compute the slopedn as Slope from IV = const + Slope * Delta 109 | 'deltaC_lim': [0.05, 0.5] 110 | # limits to compute the slopeup as -Slope from IV = const + Slope * Delta 111 | }} 112 | params = default_params | new_params0 | new_params1 113 | ''' 114 | 115 | # set number of used cores for parallel computation 116 | if cpu_count() > 30: 117 | CPUUsed = 20 118 | else: 119 | CPUUsed = cpu_count() 120 | 121 | # %% ALL FUNCTIONS THAT ARE USED IN COMPUTATIONS ARE IMPORTED FROM fn_defs_qmoments 122 | # ***************************************************************************** 123 | # ***************************************************************************** 124 | if __name__ == '__main__': 125 | 126 | df_surf, df_rate = load_data() 127 | df_rate['rate'] = df_rate['rate'] # rates in decimals p.a. 128 | 129 | ####################################################################### 130 | # FILTER THE DATA USING DEFAULT PARAMS 131 | # note that you can easily define your own filter -- make sure you select 132 | # only OTM options with non-repeating moneyness levels 133 | # we use OTM calls identified by mnes >=1 134 | 135 | # select only relevant columns and order them 136 | df_surf = df_surf[default_surf_dtype_mapping.keys()] 137 | # Filter the whole dataset 138 | df_surf = filter_options(df_surf, default_params['filter']) 139 | print("loading and filter done") 140 | ####################################################################### 141 | 142 | ####################################################################### 143 | # GROUP THE DATA FOR COMPUTATIONS, AND TEST THE OUTPUT ON ONE GROUP 144 | df_surf_grouped = df_surf.groupby(['id', 'date', 'days'], group_keys=False) 145 | # create an iterator object 146 | group = iter(df_surf_grouped) 147 | # select the next group 148 | group_next = next(group) 149 | ids, group_now = group_next 150 | # extract group identifiers 151 | id, date, days = ids 152 | # extract data on moneyness and implied volatility 153 | mnes = group_now.mnes 154 | iv = group_now.impl_volatility 155 | # interpolate the rate to the exact maturity 156 | rate = get_rate_for_maturity(df_rate, date=date, days=days) 157 | # feed to the function computing implied moments 158 | qout = qmoms_compute(mnes=mnes, vol=iv, days=days, rate=rate, params=default_params) 159 | print(ids, qout) 160 | 161 | ####################################################################### 162 | # GROUP THE DATA, AND RUN COMPUTATIONS IN A LOOP SEQUENTIALLY 163 | qmoms_all = {} 164 | df_surf_grouped = df_surf.groupby(['id', 'date', 'days'], group_keys=False) 165 | for ids, group_now in tqdm(df_surf_grouped): 166 | id, date, days = ids 167 | # extract data on moneyness and implied volatility 168 | mnes = group_now.mnes 169 | iv = group_now.impl_volatility 170 | # interpolate the rate to the exact maturity 171 | rate = get_rate_for_maturity(df_rate, date=date, days=days) 172 | # feed to the function computing implied moments 173 | qout = qmoms_compute(mnes=mnes, vol=iv, days=days, rate=rate, params=default_params) 174 | qmoms_all[ids] = qout 175 | pass 176 | 177 | qmoms_all = pd.DataFrame(qmoms_all).T 178 | qmoms_all.index.names = ['id', 'date', 'days'] 179 | print(qmoms_all) 180 | 181 | ####################################################################### 182 | # GROUP THE DATA, AND RUN COMPUTATIONS FOR THE WHOLE DATAFRAME 183 | # first, add the interest rate for each maturity to the surface dataframe 184 | df_surf = get_rate_for_maturity(df_rate, df_surf=df_surf) 185 | # group dataset by id,date,days 186 | grouped = df_surf.groupby(['id', 'date', 'days'], group_keys=False) 187 | 188 | # PARALLEL 189 | with Pool(CPUUsed) as p: 190 | ret_list = p.map(qmoms_compute_bygroup, [[group_indv, default_params] for name, group_indv in grouped]) 191 | out_p = pd.DataFrame.from_records(ret_list) 192 | pass 193 | print(out_p) 194 | 195 | # SERIAL 196 | ret_list = [qmoms_compute_bygroup([group_indv, default_params]) for name, group_indv in grouped] 197 | out_s = pd.DataFrame.from_records(ret_list) 198 | print(out_s) 199 | 200 | -------------------------------------------------------------------------------- /qmoms/data/zerocd.csv: -------------------------------------------------------------------------------- 1 | date,days,rate 2 | 02JAN1996,9,5.763067 3 | 02JAN1996,15,5.745902 4 | 02JAN1996,50,5.673317 5 | 02JAN1996,78,5.608884 6 | 02JAN1996,169,5.473762 7 | 02JAN1996,260,5.352667 8 | 02JAN1996,351,5.276194 9 | 02JAN1996,442,5.249759 10 | 02JAN1996,533,5.235769 11 | 02JAN1996,624,5.243372 12 | 02JAN1996,715,5.26432 13 | 02JAN1996,806,5.297475 14 | 02JAN1996,897,5.327961 15 | 02JAN1996,988,5.361118 16 | 02JAN1996,1079,5.394583 17 | 02JAN1996,1170,5.432169 18 | 02JAN1996,1261,5.465051 19 | 02JAN1996,1352,5.497541 20 | 02JAN1996,1443,5.529714 21 | 02JAN1996,1534,5.563993 22 | 02JAN1996,1632,5.596421 23 | 02JAN1996,1723,5.626581 24 | 02JAN1996,1814,5.655719 25 | 02JAN1996,1905,5.686364 26 | 02JAN1996,1996,5.714215 27 | 02JAN1996,2087,5.741378 28 | 02JAN1996,2178,5.767939 29 | 02JAN1996,2269,5.795171 30 | 02JAN1996,2360,5.819918 31 | 02JAN1996,2451,5.844309 32 | 02JAN1996,2542,5.868739 33 | 02JAN1996,2633,5.893894 34 | 02JAN1996,2724,5.917035 35 | 02JAN1996,2815,5.939969 36 | 02JAN1996,2906,5.962716 37 | 02JAN1996,2997,5.986201 38 | 02JAN1996,3088,6.008008 39 | 02JAN1996,3179,6.029708 40 | 02JAN1996,3270,6.051033 41 | 02JAN1996,3361,6.073362 42 | 02JAN1996,3452,6.094251 43 | 02JAN1996,3550,6.116799 44 | 02JAN1996,3641,6.138008 45 | 03JAN1996,9,5.763067 46 | 03JAN1996,14,5.747397 47 | 03JAN1996,49,5.672263 48 | 03JAN1996,77,5.603705 49 | 03JAN1996,168,5.470584 50 | 03JAN1996,259,5.346621 51 | 03JAN1996,350,5.266295 52 | 03JAN1996,441,5.235646 53 | 03JAN1996,532,5.218907 54 | 03JAN1996,623,5.221677 55 | 03JAN1996,714,5.23777 56 | 03JAN1996,805,5.268315 57 | 03JAN1996,896,5.297734 58 | 03JAN1996,987,5.330025 59 | 03JAN1996,1078,5.363615 60 | 03JAN1996,1169,5.40131 61 | 03JAN1996,1260,5.434282 62 | 03JAN1996,1351,5.466851 63 | 03JAN1996,1442,5.499091 64 | 03JAN1996,1533,5.533432 65 | 03JAN1996,1631,5.566517 66 | 03JAN1996,1722,5.59722 67 | 03JAN1996,1813,5.626845 68 | 03JAN1996,1904,5.657933 69 | 03JAN1996,1995,5.686184 70 | 03JAN1996,2086,5.714148 71 | 03JAN1996,2177,5.741442 72 | 03JAN1996,2268,5.769348 73 | 03JAN1996,2359,5.794717 74 | 03JAN1996,2450,5.820054 75 | 03JAN1996,2541,5.845363 76 | 03JAN1996,2632,5.871335 77 | 03JAN1996,2723,5.895572 78 | 03JAN1996,2814,5.919531 79 | 03JAN1996,2905,5.943551 80 | 03JAN1996,2996,5.967929 81 | 03JAN1996,3087,5.990575 82 | 03JAN1996,3178,6.013067 83 | 03JAN1996,3269,6.035139 84 | 03JAN1996,3360,6.058175 85 | 03JAN1996,3451,6.079733 86 | 03JAN1996,3549,6.102963 87 | 03JAN1996,3640,6.124773 88 | 04JAN1996,11,5.72946 89 | 04JAN1996,13,5.726104 90 | 04JAN1996,48,5.664931 91 | 04JAN1996,76,5.601891 92 | 04JAN1996,167,5.468961 93 | 04JAN1996,258,5.34509 94 | 04JAN1996,349,5.264934 95 | 04JAN1996,440,5.234497 96 | 04JAN1996,531,5.219638 97 | 04JAN1996,622,5.22377 98 | 04JAN1996,713,5.239618 99 | 04JAN1996,804,5.269992 100 | 04JAN1996,895,5.299274 101 | 04JAN1996,986,5.331455 102 | 04JAN1996,1077,5.364955 103 | 04JAN1996,1168,5.402578 104 | 04JAN1996,1259,5.436207 105 | 04JAN1996,1350,5.470017 106 | 04JAN1996,1441,5.503341 107 | 04JAN1996,1532,5.538639 108 | 04JAN1996,1630,5.57263 109 | 04JAN1996,1721,5.604084 110 | 04JAN1996,1812,5.634383 111 | 04JAN1996,1903,5.666081 112 | 04JAN1996,1994,5.694886 113 | 04JAN1996,2085,5.723354 114 | 04JAN1996,2176,5.751111 115 | 04JAN1996,2267,5.779442 116 | 04JAN1996,2358,5.805587 117 | 04JAN1996,2449,5.831643 118 | 04JAN1996,2540,5.857618 119 | 04JAN1996,2631,5.884212 120 | 04JAN1996,2722,5.909027 121 | 04JAN1996,2813,5.933527 122 | 04JAN1996,2904,5.958054 123 | 04JAN1996,2995,5.982908 124 | 04JAN1996,3086,6.006002 125 | 04JAN1996,3177,6.028916 126 | 04JAN1996,3268,6.051386 127 | 04JAN1996,3359,6.074798 128 | 04JAN1996,3450,6.096713 129 | 04JAN1996,3548,6.120305 130 | 04JAN1996,3639,6.142435 131 | 05JAN1996,11,5.72946 132 | 05JAN1996,12,5.727855 133 | 05JAN1996,47,5.664076 134 | 05JAN1996,75,5.608054 135 | 05JAN1996,166,5.481913 136 | 05JAN1996,257,5.352974 137 | 05JAN1996,348,5.273144 138 | 05JAN1996,439,5.243011 139 | 05JAN1996,530,5.231818 140 | 05JAN1996,621,5.240037 141 | 05JAN1996,712,5.258942 142 | 05JAN1996,803,5.291698 143 | 05JAN1996,894,5.323891 144 | 05JAN1996,985,5.359372 145 | 05JAN1996,1076,5.395614 146 | 05JAN1996,1167,5.435553 147 | 05JAN1996,1258,5.47116 148 | 05JAN1996,1349,5.50668 149 | 05JAN1996,1440,5.542128 150 | 05JAN1996,1531,5.579298 151 | 05JAN1996,1629,5.615664 152 | 05JAN1996,1720,5.649085 153 | 05JAN1996,1811,5.681654 154 | 05JAN1996,1902,5.715404 155 | 05JAN1996,1993,5.746072 156 | 05JAN1996,2084,5.776241 157 | 05JAN1996,2175,5.805554 158 | 05JAN1996,2266,5.835318 159 | 05JAN1996,2357,5.862783 160 | 05JAN1996,2448,5.890431 161 | 05JAN1996,2539,5.917527 162 | 05JAN1996,2630,5.945162 163 | 05JAN1996,2721,5.971283 164 | 05JAN1996,2812,5.997327 165 | 05JAN1996,2903,6.0233 166 | 05JAN1996,2994,6.049814 167 | 05JAN1996,3085,6.074471 168 | 05JAN1996,3176,6.098856 169 | 05JAN1996,3267,6.122716 170 | 05JAN1996,3358,6.147443 171 | 05JAN1996,3449,6.170602 172 | 05JAN1996,3547,6.195458 173 | 05JAN1996,3638,6.218705 174 | 08JAN1996,9,5.698918 175 | 08JAN1996,35,5.687564 176 | 08JAN1996,44,5.683618 177 | 08JAN1996,72,5.617662 178 | 08JAN1996,163,5.48942 179 | 08JAN1996,254,5.359855 180 | 08JAN1996,345,5.277515 181 | 08JAN1996,436,5.248352 182 | 08JAN1996,527,5.236173 183 | 08JAN1996,618,5.243791 184 | 08JAN1996,709,5.262294 185 | 08JAN1996,800,5.294791 186 | 08JAN1996,891,5.326777 187 | 08JAN1996,982,5.362099 188 | 08JAN1996,1073,5.398211 189 | 08JAN1996,1164,5.43805 190 | 08JAN1996,1255,5.473561 191 | 08JAN1996,1346,5.508998 192 | 08JAN1996,1437,5.544373 193 | 08JAN1996,1528,5.581482 194 | 08JAN1996,1626,5.617784 195 | 08JAN1996,1717,5.65115 196 | 08JAN1996,1808,5.68367 197 | 08JAN1996,1899,5.717377 198 | 08JAN1996,1990,5.748001 199 | 08JAN1996,2081,5.778128 200 | 08JAN1996,2172,5.807403 201 | 08JAN1996,2263,5.837132 202 | 08JAN1996,2354,5.864562 203 | 08JAN1996,2445,5.892178 204 | 08JAN1996,2536,5.919243 205 | 08JAN1996,2627,5.946851 206 | 08JAN1996,2718,5.972944 207 | 08JAN1996,2809,5.998961 208 | 08JAN1996,2900,6.02491 209 | 08JAN1996,2991,6.051402 210 | 08JAN1996,3082,6.076036 211 | 08JAN1996,3173,6.1004 212 | 08JAN1996,3264,6.124238 213 | 08JAN1996,3355,6.148946 214 | 08JAN1996,3446,6.172086 215 | 08JAN1996,3544,6.196922 216 | 08JAN1996,3635,6.220151 217 | 09JAN1996,9,5.730387 218 | 09JAN1996,34,5.687967 219 | 09JAN1996,43,5.684059 220 | 09JAN1996,71,5.613018 221 | 09JAN1996,162,5.486594 222 | 09JAN1996,253,5.350332 223 | 09JAN1996,344,5.267624 224 | 09JAN1996,435,5.238368 225 | 09JAN1996,526,5.226162 226 | 09JAN1996,617,5.233793 227 | 09JAN1996,708,5.253608 228 | 09JAN1996,799,5.287134 229 | 09JAN1996,890,5.319939 230 | 09JAN1996,981,5.355931 231 | 09JAN1996,1072,5.3926 232 | 09JAN1996,1163,5.432913 233 | 09JAN1996,1254,5.46955 234 | 09JAN1996,1345,5.50596 235 | 09JAN1996,1436,5.542186 236 | 09JAN1996,1527,5.580044 237 | 09JAN1996,1625,5.617057 238 | 09JAN1996,1716,5.65101 239 | 09JAN1996,1807,5.684057 240 | 09JAN1996,1898,5.718242 241 | 09JAN1996,1989,5.749298 242 | 09JAN1996,2080,5.77982 243 | 09JAN1996,2171,5.809456 244 | 09JAN1996,2262,5.839918 245 | 09JAN1996,2353,5.868023 246 | 09JAN1996,2444,5.896264 247 | 09JAN1996,2535,5.923909 248 | 09JAN1996,2626,5.952057 249 | 09JAN1996,2717,5.978653 250 | 09JAN1996,2808,6.005141 251 | 09JAN1996,2899,6.03153 252 | 09JAN1996,2990,6.058436 253 | 09JAN1996,3081,6.083459 254 | 09JAN1996,3172,6.108189 255 | 09JAN1996,3263,6.132374 256 | 09JAN1996,3354,6.157409 257 | 09JAN1996,3445,6.180858 258 | 09JAN1996,3543,6.206009 259 | 09JAN1996,3634,6.229516 260 | 10JAN1996,9,5.698918 261 | 10JAN1996,33,5.688488 262 | 10JAN1996,42,5.684501 263 | 10JAN1996,70,5.624384 264 | 10JAN1996,161,5.507712 265 | 10JAN1996,252,5.381357 266 | 10JAN1996,343,5.303457 267 | 10JAN1996,434,5.279213 268 | 10JAN1996,525,5.272048 269 | 10JAN1996,616,5.284737 270 | 10JAN1996,707,5.309609 271 | 10JAN1996,798,5.348194 272 | 10JAN1996,889,5.385018 273 | 10JAN1996,980,5.425213 274 | 10JAN1996,1071,5.466217 275 | 10JAN1996,1162,5.510969 276 | 10JAN1996,1253,5.551396 277 | 10JAN1996,1344,5.591082 278 | 10JAN1996,1435,5.630166 279 | 10JAN1996,1526,5.670542 280 | 10JAN1996,1624,5.710539 281 | 10JAN1996,1715,5.746966 282 | 10JAN1996,1806,5.782236 283 | 10JAN1996,1897,5.818907 284 | 10JAN1996,1988,5.852678 285 | 10JAN1996,2079,5.885676 286 | 10JAN1996,2170,5.917996 287 | 10JAN1996,2261,5.950926 288 | 10JAN1996,2352,5.981693 289 | 10JAN1996,2443,6.012397 290 | 10JAN1996,2534,6.042327 291 | 10JAN1996,2625,6.0726 292 | 10JAN1996,2716,6.101178 293 | 10JAN1996,2807,6.129519 294 | 10JAN1996,2898,6.157644 295 | 10JAN1996,2989,6.18618 296 | 10JAN1996,3080,6.212735 297 | 10JAN1996,3171,6.23891 298 | 10JAN1996,3262,6.264458 299 | 10JAN1996,3353,6.290781 300 | 10JAN1996,3444,6.31545 301 | 10JAN1996,3542,6.341837 302 | 10JAN1996,3633,6.366438 303 | 11JAN1996,11,5.72946 304 | 11JAN1996,35,5.687564 305 | 11JAN1996,41,5.684941 306 | 11JAN1996,69,5.619677 307 | 11JAN1996,160,5.499264 308 | 11JAN1996,251,5.36821 309 | 11JAN1996,342,5.288253 310 | 11JAN1996,433,5.258735 311 | 11JAN1996,524,5.24816 312 | 11JAN1996,615,5.257002 313 | 11JAN1996,706,5.277749 314 | 11JAN1996,797,5.31317 315 | 11JAN1996,888,5.347479 316 | 11JAN1996,979,5.38563 317 | 11JAN1996,1070,5.424941 318 | 11JAN1996,1161,5.468269 319 | 11JAN1996,1252,5.507477 320 | 11JAN1996,1343,5.546109 321 | 11JAN1996,1434,5.584273 322 | 11JAN1996,1525,5.62384 323 | 11JAN1996,1623,5.663069 324 | 11JAN1996,1714,5.698859 325 | 11JAN1996,1805,5.733556 326 | 11JAN1996,1896,5.76971 327 | 11JAN1996,1987,5.803467 328 | 11JAN1996,2078,5.836452 329 | 11JAN1996,2169,5.868761 330 | 11JAN1996,2260,5.902081 331 | 11JAN1996,2351,5.933209 332 | 11JAN1996,2442,5.964617 333 | 11JAN1996,2533,5.995201 334 | 11JAN1996,2624,6.026429 335 | 11JAN1996,2715,6.055897 336 | 11JAN1996,2806,6.08507 337 | 11JAN1996,2897,6.113976 338 | 11JAN1996,2988,6.143547 339 | 11JAN1996,3079,6.171077 340 | 11JAN1996,3170,6.19817 341 | 11JAN1996,3261,6.224862 342 | 11JAN1996,3352,6.252268 343 | 11JAN1996,3443,6.277962 344 | 11JAN1996,3541,6.305394 345 | 11JAN1996,3632,6.330915 346 | 12JAN1996,11,5.72946 347 | 12JAN1996,35,5.687564 348 | 12JAN1996,40,5.685383 349 | 12JAN1996,68,5.61482 350 | 12JAN1996,159,5.490704 351 | 12JAN1996,250,5.340379 352 | 12JAN1996,341,5.248911 353 | 12JAN1996,432,5.214959 354 | 12JAN1996,523,5.201532 355 | 12JAN1996,614,5.208401 356 | 12JAN1996,705,5.227703 357 | 12JAN1996,796,5.262032 358 | 12JAN1996,887,5.296497 359 | 12JAN1996,978,5.334781 360 | 12JAN1996,1069,5.374204 361 | 12JAN1996,1160,5.417631 362 | 12JAN1996,1251,5.456921 363 | 12JAN1996,1342,5.496301 364 | 12JAN1996,1433,5.535118 365 | 12JAN1996,1524,5.575858 366 | 12JAN1996,1622,5.616204 367 | 12JAN1996,1713,5.653443 368 | 12JAN1996,1804,5.689947 369 | 12JAN1996,1895,5.728215 370 | 12JAN1996,1986,5.76389 371 | 12JAN1996,2077,5.798624 372 | 12JAN1996,2168,5.832536 373 | 12JAN1996,2259,5.867331 374 | 12JAN1996,2350,5.899817 375 | 12JAN1996,2441,5.932483 376 | 12JAN1996,2532,5.964234 377 | 12JAN1996,2623,5.996548 378 | 12JAN1996,2714,6.027029 379 | 12JAN1996,2805,6.057149 380 | 12JAN1996,2896,6.086942 381 | 12JAN1996,2987,6.117347 382 | 12JAN1996,3078,6.14566 383 | 12JAN1996,3169,6.173491 384 | 12JAN1996,3260,6.200881 385 | 12JAN1996,3351,6.228946 386 | 12JAN1996,3442,6.255264 387 | 12JAN1996,3540,6.283332 388 | 12JAN1996,3631,6.309413 389 | 15JAN1996,8,5.731329 390 | 15JAN1996,32,5.688933 391 | 15JAN1996,37,5.686709 392 | 15JAN1996,65,5.607969 393 | 15JAN1996,156,5.479627 394 | 15JAN1996,247,5.320492 395 | 15JAN1996,338,5.220086 396 | 15JAN1996,429,5.179269 397 | 15JAN1996,520,5.161498 398 | 15JAN1996,611,5.16542 399 | 15JAN1996,702,5.182595 400 | 15JAN1996,793,5.216492 401 | 15JAN1996,884,5.250615 402 | 15JAN1996,975,5.289567 403 | 15JAN1996,1066,5.329549 404 | 15JAN1996,1157,5.373458 405 | 15JAN1996,1248,5.413878 406 | 15JAN1996,1339,5.454234 407 | 15JAN1996,1430,5.493904 408 | 15JAN1996,1521,5.535995 409 | 15JAN1996,1619,5.577622 410 | 15JAN1996,1710,5.615918 411 | 15JAN1996,1801,5.653369 412 | 15JAN1996,1892,5.692498 413 | 15JAN1996,1983,5.729408 414 | 15JAN1996,2074,5.765268 415 | 15JAN1996,2165,5.800209 416 | 15JAN1996,2256,5.835952 417 | 15JAN1996,2347,5.869697 418 | 15JAN1996,2438,5.903527 419 | 15JAN1996,2529,5.936357 420 | 15JAN1996,2620,5.969677 421 | 15JAN1996,2711,6.001094 422 | 15JAN1996,2802,6.032088 423 | 15JAN1996,2893,6.0627 424 | 15JAN1996,2984,6.093875 425 | 15JAN1996,3075,6.122911 426 | 15JAN1996,3166,6.151422 427 | 15JAN1996,3257,6.179453 428 | 15JAN1996,3348,6.208126 429 | 15JAN1996,3439,6.235018 430 | 15JAN1996,3537,6.263671 431 | 15JAN1996,3628,6.290266 432 | 16JAN1996,9,5.698918 433 | 16JAN1996,34,5.687967 434 | 16JAN1996,36,5.684924 435 | 16JAN1996,64,5.5969 436 | 16JAN1996,92,5.537873 437 | 16JAN1996,155,5.424291 438 | 16JAN1996,246,5.262755 439 | 16JAN1996,337,5.16412 440 | 16JAN1996,428,5.122333 441 | 16JAN1996,519,5.103978 442 | 16JAN1996,610,5.109022 443 | 16JAN1996,701,5.128346 444 | 16JAN1996,792,5.163922 445 | 16JAN1996,883,5.20041 446 | 16JAN1996,974,5.241291 447 | 16JAN1996,1065,5.283727 448 | 16JAN1996,1156,5.329708 449 | 16JAN1996,1247,5.371895 450 | 16JAN1996,1338,5.413778 451 | 16JAN1996,1429,5.45478 452 | 16JAN1996,1520,5.498046 453 | 16JAN1996,1618,5.540789 454 | 16JAN1996,1709,5.580005 455 | 16JAN1996,1800,5.618284 456 | 16JAN1996,1891,5.658161 457 | 16JAN1996,1982,5.695293 458 | 16JAN1996,2073,5.731354 459 | 16JAN1996,2164,5.766479 460 | 16JAN1996,2255,5.802392 461 | 16JAN1996,2346,5.836292 462 | 16JAN1996,2437,5.870267 463 | 16JAN1996,2528,5.903231 464 | 16JAN1996,2619,5.936676 465 | 16JAN1996,2710,5.967874 466 | 16JAN1996,2801,5.998664 467 | 16JAN1996,2892,6.029084 468 | 16JAN1996,2983,6.060079 469 | 16JAN1996,3074,6.088945 470 | 16JAN1996,3165,6.117296 471 | 16JAN1996,3256,6.145176 472 | 16JAN1996,3347,6.173707 473 | 16JAN1996,3438,6.2002 474 | 16JAN1996,3536,6.228448 475 | 16JAN1996,3627,6.254685 -------------------------------------------------------------------------------- /qmoms/data/surface.csv: -------------------------------------------------------------------------------- 1 | id,date,days,mnes,prem,impl_volatility,delta 2 | 10078,04JAN1996,30,1.2375417284,0.2567898765,0.783005,-80 3 | 10078,04JAN1996,30,1.1912945679,0.2175403951,0.774917,-75 4 | 10078,04JAN1996,30,1.1503323457,0.1839782222,0.762773,-70 5 | 10078,04JAN1996,30,1.1140375309,0.1555153086,0.749439,-65 6 | 10078,04JAN1996,30,1.0825560494,0.1328225432,0.743469,-60 7 | 10078,04JAN1996,30,1.0544545679,0.1150896049,0.749295,-55 8 | 10078,04JAN1996,30,1.027902716,0.1011814321,0.768885,-50 9 | 10078,04JAN1996,30,1.0004009877,0.0872326914,0.781802,-45 10 | 10078,04JAN1996,30,0.9723269136,0.0731472593,0.784399,-40 11 | 10078,04JAN1996,30,0.9440883951,0.0599900247,0.78298,-35 12 | 10078,04JAN1996,30,0.9154844444,0.0479520494,0.779121,-30 13 | 10078,04JAN1996,30,0.8856444444,0.0372411852,0.777003,-25 14 | 10078,04JAN1996,30,0.8532318519,0.0277416543,0.777861,-20 15 | 10078,04JAN1996,30,1.2563698765,0.0234005185,0.81349,20 16 | 10078,04JAN1996,30,1.2088254321,0.0311424691,0.814982,25 17 | 10078,04JAN1996,30,1.1683207407,0.0397342469,0.819612,30 18 | 10078,04JAN1996,30,1.1326046914,0.049389284,0.828843,35 19 | 10078,04JAN1996,30,1.0988049383,0.0599174815,0.83638,40 20 | 10078,04JAN1996,30,1.0656234568,0.0709111605,0.83616,45 21 | 10078,04JAN1996,30,1.0333212346,0.082108963,0.826801,50 22 | 10078,04JAN1996,30,1.0026516049,0.094085284,0.816329,55 23 | 10078,04JAN1996,30,0.9731308642,0.1080268642,0.814352,60 24 | 10078,04JAN1996,30,0.9432918519,0.1246650123,0.821579,65 25 | 10078,04JAN1996,30,0.911977284,0.1441671111,0.833504,70 26 | 10078,04JAN1996,30,0.879125679,0.1657270617,0.840091,75 27 | 10078,04JAN1996,30,0.8434703704,0.1910273086,0.846721,80 28 | 10078,05JAN1996,30,1.2537261728,0.2740219753,0.825442,-80 29 | 10078,05JAN1996,30,1.2038195062,0.2314122716,0.81545,-75 30 | 10078,05JAN1996,30,1.1598735802,0.1951606667,0.801342,-70 31 | 10078,05JAN1996,30,1.120937037,0.1642925926,0.785076,-65 32 | 10078,05JAN1996,30,1.08724,0.1395418025,0.775479,-60 33 | 10078,05JAN1996,30,1.0571730864,0.1197703951,0.775427,-55 34 | 10078,05JAN1996,30,1.0288925926,0.103489358,0.784004,-50 35 | 10078,05JAN1996,30,1.0006538272,0.0883779012,0.790699,-45 36 | 10078,05JAN1996,30,0.9722674074,0.073960963,0.792008,-40 37 | 10078,05JAN1996,30,0.9437740741,0.0606215802,0.790213,-35 38 | 10078,05JAN1996,30,0.9149276543,0.0484435309,0.786177,-30 39 | 10078,05JAN1996,30,0.8848503704,0.0376145926,0.78393,-25 40 | 10078,05JAN1996,30,0.8523488889,0.0279743457,0.783699,-20 41 | 10078,05JAN1996,30,1.2355903704,0.0219827654,0.758991,20 42 | 10078,05JAN1996,30,1.1915130864,0.0292220247,0.759108,25 43 | 10078,05JAN1996,30,1.1527234568,0.037025358,0.757167,30 44 | 10078,05JAN1996,30,1.1178007407,0.0454128148,0.753913,35 45 | 10078,05JAN1996,30,1.0861646914,0.0545943704,0.752537,40 46 | 10078,05JAN1996,30,1.0568928395,0.0648535309,0.75508,45 47 | 10078,05JAN1996,30,1.0290217284,0.0765663951,0.763013,50 48 | 10078,05JAN1996,30,1.0012716049,0.0893498025,0.769042,55 49 | 10078,05JAN1996,30,0.9734412346,0.1031303704,0.771573,60 50 | 10078,05JAN1996,30,0.945214321,0.1187663704,0.776167,65 51 | 10078,05JAN1996,30,0.9157548148,0.1369441481,0.784248,70 52 | 10078,05JAN1996,30,0.8848624691,0.157213679,0.788667,75 53 | 10078,05JAN1996,30,0.8518419753,0.180406716,0.789988,80 54 | 10078,08JAN1996,30,1.2580461059,0.2786173209,0.83659,-80 55 | 10078,08JAN1996,30,1.2072478505,0.2352038629,0.82638,-75 56 | 10078,08JAN1996,30,1.161694704,0.1972920623,0.808608,-70 57 | 10078,08JAN1996,30,1.1219563863,0.1655877134,0.790281,-65 58 | 10078,08JAN1996,30,1.0880558255,0.1407092586,0.780992,-60 59 | 10078,08JAN1996,30,1.0577627414,0.1207818318,0.781034,-55 60 | 10078,08JAN1996,30,1.0290479751,0.1038549782,0.786387,-50 61 | 10078,08JAN1996,30,1.0006038629,0.0881677009,0.789065,-45 62 | 10078,08JAN1996,30,0.9722826168,0.0736997632,0.789565,-40 63 | 10078,08JAN1996,30,0.9438048598,0.0605530966,0.789428,-35 64 | 10078,08JAN1996,30,0.9146938318,0.0486486729,0.789116,-30 65 | 10078,08JAN1996,30,0.8842701558,0.0378880748,0.788993,-25 66 | 10078,08JAN1996,30,0.8515511526,0.0281850467,0.788976,-20 67 | 10078,08JAN1996,30,1.2412229283,0.0223738318,0.773948,20 68 | 10078,08JAN1996,30,1.1963643614,0.0297699938,0.774966,25 69 | 10078,08JAN1996,30,1.1574280374,0.0378591153,0.77627,30 70 | 10078,08JAN1996,30,1.1223476636,0.0466655701,0.777342,35 71 | 10078,08JAN1996,30,1.0898270405,0.0561870903,0.777399,40 72 | 10078,08JAN1996,30,1.0590409969,0.0664046355,0.775642,45 73 | 10078,08JAN1996,30,1.0296226791,0.0773837259,0.772335,50 74 | 10078,08JAN1996,30,1.0013208723,0.089548162,0.771007,55 75 | 10078,08JAN1996,30,0.9734265421,0.1032661184,0.77275,60 76 | 10078,08JAN1996,30,0.944985919,0.1194337695,0.781266,65 77 | 10078,08JAN1996,30,0.9150223053,0.1383185047,0.793546,70 78 | 10078,08JAN1996,30,0.8837550156,0.1588391277,0.7984,75 79 | 10078,08JAN1996,30,0.8504366355,0.1821762741,0.799342,80 80 | 10078,09JAN1996,30,1.2532644816,0.2735307023,0.824245,-80 81 | 10078,09JAN1996,30,1.2006322408,0.2278868495,0.805223,-75 82 | 10078,09JAN1996,30,1.1587149164,0.193805806,0.796704,-70 83 | 10078,09JAN1996,30,1.1241345819,0.1683397993,0.801323,-65 84 | 10078,09JAN1996,30,1.0927111706,0.1473064615,0.811941,-60 85 | 10078,09JAN1996,30,1.0615537124,0.127182903,0.816257,-55 86 | 10078,09JAN1996,30,1.0311138462,0.1085490836,0.816846,-50 87 | 10078,09JAN1996,30,1.0014017391,0.0916181405,0.815716,-45 88 | 10078,09JAN1996,30,0.9721228094,0.0763411371,0.814148,-40 89 | 10078,09JAN1996,30,0.942786087,0.0626791973,0.81366,-35 90 | 10078,09JAN1996,30,0.9128058863,0.0503629699,0.813582,-30 91 | 10078,09JAN1996,30,0.881504214,0.0392212977,0.813571,-25 92 | 10078,09JAN1996,30,0.8478860201,0.0291711037,0.813569,-20 93 | 10078,09JAN1996,30,1.2668446823,0.0240917191,0.840334,20 94 | 10078,09JAN1996,30,1.2172896321,0.0320495518,0.841666,25 95 | 10078,09JAN1996,30,1.174441204,0.0407581806,0.843504,30 96 | 10078,09JAN1996,30,1.1365634783,0.0504073579,0.848294,35 97 | 10078,09JAN1996,30,1.1020476254,0.0612121472,0.857101,40 98 | 10078,09JAN1996,30,1.0686504348,0.0728794916,0.862962,45 99 | 10078,09JAN1996,30,1.0362054849,0.0855301672,0.866859,50 100 | 10078,09JAN1996,30,1.004678796,0.1000108094,0.876612,55 101 | 10078,09JAN1996,30,0.9729372575,0.1164586488,0.889604,60 102 | 10078,09JAN1996,30,0.9405407358,0.133937097,0.894576,65 103 | 10078,09JAN1996,30,0.9074972575,0.1530650435,0.895537,70 104 | 10078,09JAN1996,30,0.8731756522,0.1747288294,0.895693,75 105 | 10078,09JAN1996,30,0.8364826756,0.1999872375,0.895716,80 106 | 10078,10JAN1996,30,1.2760957962,0.2977811465,0.882449,-80 107 | 10078,10JAN1996,30,1.2187166879,0.2478555924,0.862471,-75 108 | 10078,10JAN1996,30,1.1691811465,0.206020586,0.838144,-70 109 | 10078,10JAN1996,30,1.1280366879,0.1732642803,0.820928,-65 110 | 10078,10JAN1996,30,1.0933472611,0.1482076688,0.816131,-60 111 | 10078,10JAN1996,30,1.0622303185,0.1283189045,0.822451,-55 112 | 10078,10JAN1996,30,1.0319156688,0.1103461401,0.828407,-50 113 | 10078,10JAN1996,30,1.0018473885,0.0934740637,0.829939,-45 114 | 10078,10JAN1996,30,0.9720407643,0.0780496306,0.829932,-40 115 | 10078,10JAN1996,30,0.9421472611,0.0640640255,0.829333,-35 116 | 10078,10JAN1996,30,0.9116382166,0.0514484586,0.828972,-30 117 | 10078,10JAN1996,30,0.8798012739,0.040057707,0.828893,-25 118 | 10078,10JAN1996,30,0.845628535,0.0297887134,0.82888,-20 119 | 10078,10JAN1996,30,1.2527757962,0.0231602803,0.804202,20 120 | 10078,10JAN1996,30,1.2067388535,0.0309164586,0.808363,25 121 | 10078,10JAN1996,30,1.167955414,0.0396735287,0.818201,30 122 | 10078,10JAN1996,30,1.1317559236,0.049170242,0.824672,35 123 | 10078,10JAN1996,30,1.0969979618,0.0591867261,0.824742,40 124 | 10078,10JAN1996,30,1.0637587261,0.0696707516,0.819386,45 125 | 10078,10JAN1996,30,1.0324496815,0.0810383694,0.814374,50 126 | 10078,10JAN1996,30,1.0025011465,0.0936187261,0.811635,55 127 | 10078,10JAN1996,30,0.9731882803,0.1068387261,0.803911,60 128 | 10078,10JAN1996,30,0.9448858599,0.1197341146,0.783564,65 129 | 10078,10JAN1996,30,0.9168504459,0.1348850191,0.770381,70 130 | 10078,10JAN1996,30,0.8872751592,0.1536734013,0.767605,75 131 | 10078,10JAN1996,30,0.8552886624,0.1760642803,0.767198,80 132 | 10078,11JAN1996,30,1.2463579822,0.2661811276,0.806251,-80 133 | 10078,11JAN1996,30,1.199348368,0.2264650208,0.801087,-75 134 | 10078,11JAN1996,30,1.1585265282,0.1935846409,0.795949,-70 135 | 10078,11JAN1996,30,1.1217630861,0.165338635,0.789288,-65 136 | 10078,11JAN1996,30,1.0882594659,0.1409942315,0.782343,-60 137 | 10078,11JAN1996,30,1.0576932938,0.1206574718,0.78035,-55 138 | 10078,11JAN1996,30,1.0290789318,0.1039181246,0.786804,-50 139 | 10078,11JAN1996,30,1.0011653412,0.0906107537,0.807964,-45 140 | 10078,11JAN1996,30,0.972067181,0.077509911,0.824958,-40 141 | 10078,11JAN1996,30,0.9421946588,0.0639674065,0.828245,-35 142 | 10078,11JAN1996,30,0.9120334718,0.0510809258,0.823771,-30 143 | 10078,11JAN1996,30,0.8815947774,0.0391775668,0.812768,-25 144 | 10078,11JAN1996,30,0.849074184,0.0288494243,0.805566,-20 145 | 10078,11JAN1996,30,1.2496721662,0.0229504807,0.796108,20 146 | 10078,11JAN1996,30,1.2034905638,0.0305602374,0.797955,25 147 | 10078,11JAN1996,30,1.1632859347,0.0388759169,0.799707,30 148 | 10078,11JAN1996,30,1.1271843323,0.0479654362,0.801823,35 149 | 10078,11JAN1996,30,1.093898635,0.0579058516,0.804442,40 150 | 10078,11JAN1996,30,1.0623268843,0.0686940059,0.80624,45 151 | 10078,11JAN1996,30,1.0320823739,0.0805705401,0.80896,50 152 | 10078,11JAN1996,30,1.0026468843,0.094069911,0.816174,55 153 | 10078,11JAN1996,30,0.9730862908,0.1089931632,0.822874,60 154 | 10078,11JAN1996,30,0.9431679525,0.1250591098,0.824641,65 155 | 10078,11JAN1996,30,0.9127038576,0.1427592166,0.823827,70 156 | 10078,11JAN1996,30,0.8811791098,0.1626621958,0.82145,75 157 | 10078,11JAN1996,30,0.8473956083,0.186032997,0.819865,80 158 | 10078,12JAN1996,30,1.2662816199,0.2873634891,0.857672,-80 159 | 10078,12JAN1996,30,1.2116695327,0.2400823427,0.840385,-75 160 | 10078,12JAN1996,30,1.1640127103,0.1999940685,0.817805,-70 161 | 10078,12JAN1996,30,1.1238434891,0.167970891,0.799848,-65 162 | 10078,12JAN1996,30,1.0898454829,0.1432491464,0.792959,-60 163 | 10078,12JAN1996,30,1.0593071651,0.1233988536,0.795499,-55 164 | 10078,12JAN1996,30,1.0301305919,0.1063262305,0.80247,-50 165 | 10078,12JAN1996,30,1.0010883489,0.0902804486,0.805417,-45 166 | 10078,12JAN1996,30,0.9721652336,0.0755867414,0.807151,-40 167 | 10078,12JAN1996,30,0.9428368847,0.0625727103,0.812452,-35 168 | 10078,12JAN1996,30,0.912328972,0.0508055327,0.819867,-30 169 | 10078,12JAN1996,30,0.8804989408,0.0397146168,0.822618,-25 170 | 10078,12JAN1996,30,0.8464785047,0.0295558879,0.823117,-20 171 | 10078,12JAN1996,30,1.2538031153,0.02322881,0.806849,20 172 | 10078,12JAN1996,30,1.2052152025,0.0307493832,0.803478,25 173 | 10078,12JAN1996,30,1.1633724611,0.0388907664,0.800051,30 174 | 10078,12JAN1996,30,1.1263543925,0.0477441745,0.797644,35 175 | 10078,12JAN1996,30,1.0926758879,0.0573940685,0.796366,40 176 | 10078,12JAN1996,30,1.0610385047,0.067805109,0.794324,45 177 | 10078,12JAN1996,30,1.0308894704,0.0790459315,0.791383,50 178 | 10078,12JAN1996,30,1.0018721495,0.0914972461,0.790389,55 179 | 10078,12JAN1996,30,0.9732765109,0.1054609097,0.791852,60 180 | 10078,12JAN1996,30,0.9442148287,0.1217841745,0.799303,65 181 | 10078,12JAN1996,30,0.9137156386,0.1408146542,0.810522,70 182 | 10078,12JAN1996,30,0.8819122741,0.16157281,0.814859,75 183 | 10078,12JAN1996,30,0.8480174455,0.1852442866,0.815653,80 184 | 10078,15JAN1996,30,1.2599551155,0.2806450165,0.841501,-80 185 | 10078,15JAN1996,30,1.2026426403,0.2301120528,0.811679,-75 186 | 10078,15JAN1996,30,1.1585436304,0.1936069967,0.79602,-70 187 | 10078,15JAN1996,30,1.1238006601,0.1679190759,0.799636,-65 188 | 10078,15JAN1996,30,1.0930014521,0.1477178614,0.813855,-60 189 | 10078,15JAN1996,30,1.0619012541,0.1277664158,0.81944,-55 190 | 10078,15JAN1996,30,1.0315266007,0.1094764884,0.822819,-50 191 | 10078,15JAN1996,30,1.0020868647,0.0944353795,0.83728,-45 192 | 10078,15JAN1996,30,0.9719361056,0.08190833,0.865262,-40 193 | 10078,15JAN1996,30,0.9403477228,0.0682850957,0.876602,-35 194 | 10078,15JAN1996,30,0.9080279868,0.0549719868,0.878408,-30 195 | 10078,15JAN1996,30,0.8744227063,0.0428005281,0.878638,-25 196 | 10078,15JAN1996,30,0.8384528053,0.0318158944,0.878664,-20 197 | 10078,15JAN1996,30,1.2847817822,0.0252405017,0.88536,20 198 | 10078,15JAN1996,30,1.2336042244,0.0337421782,0.891969,25 199 | 10078,15JAN1996,30,1.1883194719,0.0430031155,0.896448,30 200 | 10078,15JAN1996,30,1.147580462,0.0531465611,0.901182,35 201 | 10078,15JAN1996,30,1.1105610561,0.0644851749,0.910073,40 202 | 10078,15JAN1996,30,1.0750632343,0.0768510363,0.917745,45 203 | 10078,15JAN1996,30,1.0405652805,0.0903265215,0.923924,50 204 | 10078,15JAN1996,30,1.0072744554,0.1064207525,0.943265,55 205 | 10078,15JAN1996,30,0.9733108911,0.1263973333,0.980993,60 206 | 10078,15JAN1996,30,0.9372691749,0.1470568449,1.001404,65 207 | 10078,15JAN1996,30,0.9002838284,0.168370429,1.00592,70 208 | 10078,15JAN1996,30,0.8620749835,0.1920881584,1.006675,75 209 | 10078,15JAN1996,30,0.8214558416,0.2195999208,1.00679,80 210 | 10078,16JAN1996,30,1.2379209552,0.2571971343,0.784003,-80 211 | 10078,16JAN1996,30,1.1928250746,0.2192413851,0.779918,-75 212 | 10078,16JAN1996,30,1.1546777313,0.1890813612,0.780449,-70 213 | 10078,16JAN1996,30,1.1211801791,0.1646045373,0.786326,-65 214 | 10078,16JAN1996,30,1.0894949254,0.1427563701,0.790637,-60 215 | 10078,16JAN1996,30,1.0588582687,0.1226448955,0.791336,-55 216 | 10078,16JAN1996,30,1.0292031045,0.1042125373,0.78872,-50 217 | 10078,16JAN1996,30,1.0004071642,0.0872779463,0.78215,-45 218 | 10078,16JAN1996,30,0.9723570149,0.072704406,0.780246,-40 219 | 10078,16JAN1996,30,0.9438607761,0.0604394985,0.788127,-35 220 | 10078,16JAN1996,30,0.9124047761,0.0507314627,0.818814,-30 221 | 10078,16JAN1996,30,0.8754899104,0.042243606,0.868599,-25 222 | 10078,16JAN1996,30,0.8360637612,0.0325118328,0.895588,-20 223 | 10078,16JAN1996,30,1.2535288358,0.0232108657,0.806156,20 224 | 10078,16JAN1996,30,1.2070791642,0.0309535284,0.809448,25 225 | 10078,16JAN1996,30,1.1654665075,0.0392505791,0.808382,30 226 | 10078,16JAN1996,30,1.1270617313,0.0479344,0.801237,35 227 | 10078,16JAN1996,30,1.0923971343,0.0572792836,0.794557,40 228 | 10078,16JAN1996,30,1.0610810746,0.067838591,0.794772,45 229 | 10078,16JAN1996,30,1.0321571343,0.0806718567,0.810132,50 230 | 10078,16JAN1996,30,1.0036663881,0.0971917851,0.847776,55 231 | 10078,16JAN1996,30,0.9729399403,0.1142747701,0.869917,60 232 | 10078,16JAN1996,30,0.9410536119,0.1321015403,0.879965,65 233 | 10078,16JAN1996,30,0.9071916418,0.1536794269,0.899877,70 234 | 10078,16JAN1996,30,0.8698593433,0.1798277731,0.927767,75 235 | 10078,16JAN1996,30,0.8300950448,0.2082600836,0.941911,80 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Implied Moments from Volatility Surface Data 2 | 3 | This is a Python implementation of a set of option-implied characteristics. 4 | 5 | ## Scope 6 | The package contains functions to compute option-implied moments and characteristics from implied volatility surface data. 7 | The computations are based on the out-the-money (OTM) implied volatilities, interpolated as function of moneyness 8 | (Strike/Underlying Price or Strike/ Forward Price) within the avaialable moneyness range and set to the boundary values 9 | outside to fill in the pre-specified moneyness range. OTM is defined as moenyess>=1 for calls and <1 for puts. 10 | 11 | The following moments and characteristics are computed: 12 | 1. Model-free implied variance (log contract): 13 | * `MFIV_BKM` using Bakshi, Kapadia, and Madan (RFS, 2003) / https://doi.org/10.1093/rfs/16.1.0101 14 | * `MFIV_BJN` using Britten-Jones and Neuberger (JF, 2002) / https://doi.org/10.1111/0022-1082.00228 15 | 2. Simple model-free implied variance 16 | * `SMFIV` using Martin (QJE, 2017) / https://doi.org/10.1093/qje/qjw034 17 | 3. Corridor VIX 18 | * `CVIX` using Andersen, Bondarenko, and Gonzalez-Perez (RFS, 2015) / https://doi.org/10.1093/rfs/hhv033 19 | 4. Tail Loss Measure 20 | * `TLM` using Vilkov, Xiao (2012) / https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1940117 and Hamidieh (Journal of Risk, 2017) / 21 | https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2209654 22 | 5. Semivariances (only down) for log and simple contracts 23 | * Following Feunou, Jahan-Parvar,and Okou, (Journal of Fin Econometrics, 2017) / https://doi.org/10.1093/jjfinec/nbx020 24 | * Up semivariances can be computed as the respective total variance minus down semivariance 25 | 6. Tail jump measure 26 | * `RIX` using Gao, Gao and Song, (RFS, 2018) / https://doi.org/10.1093/rfs/hhy027 27 | 7. Ad hoc tail measures (smile steepness for the tails) 28 | * `Slopeup` (right tail) and `Slopedn` (left tail) measure right and left tail slope, respectively. Larger values indicate 29 | more expensive tail relative to the at-the-money level. 30 | * Used in Carbon Tail Risk, Ilhan, Sautner, and Vilkov, (RFS, 2021) / https://doi.org/10.1093/rfs/hhaa071 and 31 | Pricing Climate Change Exposure, v.Lent, Sautner, Zhang, and Vilkov, (Management Science, 2023) / https://doi.org/10.1287/mnsc.2023.4686 32 | (mind the sign of slopeup -- we take '-Slope' here for better interpretability). 33 | Originally, Slopedn is used in Kelly, Pastor, Veronesi (JF, 2016) / https://doi.org/10.1111/jofi.12406 34 | 35 | ## Usage 36 | 37 | Exemplary use of the `qmoms` package. The data is the provided with the package, and are composed of two files: 38 | 39 | 1. `surface.csv`: the implied volatility surface as a function of option delta for several dates in 1996 and 30-day maturity. 40 | The columns mapping is specified in `qmoms_params.py` and can be imported from the main package: 41 | ```python 42 | from qmoms import default_surf_dtype_mapping 43 | default_surf_dtype_mapping = {'id': 'int64', 44 | 'date': 'str', 45 | 'days': 'int64', 46 | 'mnes': 'float64', 47 | 'prem': 'float64', 48 | 'impl_volatility': 'float64', 49 | 'delta': 'float64'} 50 | ``` 51 | 2. `zerocd.csv`: the zero CD rates recorded on each date for several maturities, in % p.a. 52 | ```python 53 | from qmoms import default_rate_dtype_mapping 54 | default_rate_dtype_mapping = {'date': 'str', 55 | 'days': 'int64', 56 | 'rate': 'float64'} 57 | ``` 58 | The default date format for these files is provided in `qmoms_params.py`: `default_date_format = '%d%b%Y'` 59 | 60 | To load the data, use the provided function `load_data()` 61 | ```python 62 | from qmoms.examples import load_data 63 | df_surf, df_rate = load_data() 64 | ``` 65 | 66 | 67 | There are two main functions provided: `qmoms_compute()` and `qmoms_compute_bygroup()`: 68 | 69 | The first function computes a set of variables as specified in `params` dictionary for one instance of the surface 70 | (i.e., one id/date/days combination). 71 | ```python 72 | def qmoms_compute(mnes, vol, days, rate, params, output='pandas'): 73 | """ 74 | Computes implied moments for option pricing using interpolated volatility and various moment formulas. 75 | 76 | Parameters: 77 | - mnes (array-like): moneyness (K/S) of the options. 78 | - vol (array-like): implied volatilities corresponding to the moneyness. 79 | - days (int): Days until the options' expiration. 80 | - rate (float): Risk-free interest rate. 81 | - params (dict): Configuration parameters containing grid settings and other options. 82 | - output (str): Specifies the output format ('pandas' for a pandas.Series, else returns a dictionary). 83 | 84 | Returns: 85 | - pandas.Series or dict: Calculated moments and optionally other metrics based on the provided parameters. 86 | 87 | The function performs the following: 88 | - Sorts inputs by moneyness. 89 | - Computes forward prices and interpolation of implied volatilities. 90 | - Uses Black or Black-Scholes formulas to compute option prices. 91 | - Calculates implied variances and semi-variances using specified methods. 92 | - Optionally computes advanced metrics like MFIS/MFIK, Corridor VIX, RIX, Tail Loss Measure, 93 | and Slopes based on user configurations in `params`. 94 | 95 | ####################################################################################################### 96 | default_params = {'atmfwd': False, # if True, use FWD as ATM level and Black model for option valuation 97 | 'grid': {'number_points': 500, # points*2 + 1 98 | 'grid_limit': 2}, # 1/(1+k) to 1+k; typically, k = 2 99 | 'filter': {'mnes_lim': [0, 1000], # [0.7, 1.3], 100 | 'delta_put_lim': [-0.5 + 1e-3, 0], # typically, only OTM 101 | 'delta_call_lim': [0, 0.5], # typically, only OTM 102 | 'best_bid_zero': -1, # if negative, no restriction on zero bid; 103 | 'open_int_zero': -1, # if negative, no restriction on zero open interest; 104 | 'min_price': 0}, # a limit on the min mid-price 105 | 'semivars': {'compute': True}, # save semivariances or not, they are computed anyway 106 | 'mfismfik': {'compute': True}, # compute skewness/ kurtosis 107 | 'cvix': {'compute': True, # compute corridor VIX 108 | 'abs_dev': [0.2], # absolute deviation from ATM level of 1 109 | 'vol_dev': [2]}, # deviation in terms of own sigmas from ATM level of 1 110 | 'rix': {'compute': True}, # compute RIX (Gao,Gao,Song RFS) or not, 111 | 'tlm': {'compute': True, # compute Tail Loss Measure (Xiao/ Vilkov) or not 112 | 'delta_lim': [20], # OTM put delta for the threshold (pos->neg or neg) 113 | 'vol_lim': [2]}, # deviation to the left in terms of sigmas from ATM level of 1 114 | 'slope': {'compute': True, 115 | 'deltaP_lim': [-0.5, -0.05], 116 | # limits to compute the slopedn as Slope from IV = const + Slope * Delta 117 | 'deltaC_lim': [0.05, 0.5] 118 | # limits to compute the slopeup as -Slope from IV = const + Slope * Delta 119 | } 120 | } 121 | ####################################################################################################### 122 | To modify parameters, update the default_params dictionary with the dictionary with desired parameters 123 | for example, in case you do not want to compute the tlm, and want to change slope parameters, use 124 | new_params0 = {'tlm': {'compute': False}} 125 | new_params1 = {'slope': {'compute': True, 126 | 'deltaP_lim': [-0.4, -0.1], 127 | # limits to compute the slopedn as Slope from IV = const + Slope * Delta 128 | 'deltaC_lim': [0.05, 0.5] 129 | # limits to compute the slopeup as -Slope from IV = const + Slope * Delta 130 | }} 131 | params = default_params | new_params0 | new_params1 132 | ####################################################################################################### 133 | """ 134 | ``` 135 | 136 | The second function `qmoms_compute_bygroup` computes the variables for a set of instances collected in a DataFrame. 137 | Note that the input variable `groupparams` shall be a tuple or a list with the 0th element containing the 138 | group (dataframe) and the 1st element being a dictionary with parameters. Asset `id`, current `date`, 139 | maturity in `days`, and current `rate` in decimals p.a. can be provided as individual variables, or in the 140 | group DataFrame. If these variables are provided directly to the function, they will take precedence over any values 141 | provided within the DataFrame. 142 | The DataFrame column names shall conform to the `cols_map` mapping. 143 | 144 | ```python 145 | def qmoms_compute_bygroup(groupparams, 146 | id=None, rate=None, days=None, date=None, 147 | cols_map={'id': 'id', 148 | 'date': 'date', 149 | 'days': 'days', 150 | 'rate': 'rate', 151 | 'mnes': 'mnes', 152 | 'impl_volatility': 'impl_volatility'}): 153 | """ 154 | Computes implied moments for grouped option data using specified parameters and column mappings. 155 | 156 | Parameters: 157 | - groupparams (tuple, list, or dict): If a tuple or list, this should contain the group data and parameters as the 158 | first and second elements, respectively. If a dict, it is used directly as parameters. 159 | - id (int, optional): Identifier for the data group, defaults to the first 'id' in the group data as specified in 160 | cols_map if not provided. 161 | - rate (float, optional): Risk-free interest rate, defaults to the first 'rate' in the group data as specified in 162 | cols_map if not provided. 163 | - days (int, optional): Days until expiration, defaults to the first 'days' in the group data as specified in cols_ 164 | map if not provided. 165 | - date (any, optional): Date of the data, defaults to the first 'date' in the group data as specified in cols_map 166 | if not provided. 167 | - cols_map (dict, optional): A dictionary mapping the expected columns in the group data to the actual column names. 168 | Default is {'id': 'id', 'date': 'date', 'days': 'days', 'rate': 'rate', 169 | 'mnes': 'mnes', 'impl_volatility': 'impl_volatility'}. 170 | 171 | Returns: 172 | - pandas.Series: Contains the computed moments along with initial group identifiers such as id, date, and days. 173 | 174 | The function sorts the group data by moneyness, removes duplicates, and computes moments using interpolation. 175 | It then merges the computed moments with initial data identifiers and returns a pandas Series, using the column 176 | mappings specified in cols_map for accessing data fields. 177 | """ 178 | ``` 179 | 180 | The usage of both functions is illustrated below: 181 | 182 | ```python 183 | '''the script with examples is provided in qmoms/examples/qmoms_test.py''' 184 | # import packages and variables 185 | import pandas as pd 186 | from tqdm import * 187 | from multiprocessing import cpu_count, Pool 188 | from qmoms.examples import load_data 189 | from qmoms import default_params, default_surf_dtype_mapping, filter_options, get_rate_for_maturity 190 | from qmoms import qmoms_compute, qmoms_compute_bygroup 191 | 192 | # set number of used cores for parallel computation 193 | if cpu_count() > 30: 194 | CPUUsed = 20 195 | else: 196 | CPUUsed = cpu_count() 197 | 198 | if __name__ == '__main__': 199 | # load the provided data 200 | df_surf, df_rate = load_data() 201 | 202 | ####################################################################### 203 | # FILTER THE DATA USING DEFAULT PARAMS 204 | # note that you can easily define your own filter -- make sure you select 205 | # only OTM options with non-repeating moneyness levels 206 | # we use OTM calls identified by mnes >=1 207 | 208 | # select only relevant columns and order them 209 | df_surf = df_surf[default_surf_dtype_mapping.keys()] 210 | # Filter the whole dataset 211 | df_surf = filter_options(df_surf, default_params['filter']) 212 | print("loading and filter done") 213 | ####################################################################### 214 | 215 | ####################################################################### 216 | # GROUP THE DATA FOR COMPUTATIONS, AND TEST THE OUTPUT ON ONE GROUP 217 | df_surf_grouped = df_surf.groupby(['id', 'date', 'days'], group_keys=False) 218 | # create an iterator object 219 | group = iter(df_surf_grouped) 220 | # select the next group 221 | group_next = next(group) 222 | ids, group_now = group_next 223 | # extract group identifiers 224 | id, date, days = ids 225 | # extract data on moneyness and implied volatility 226 | mnes = group_now.mnes 227 | iv = group_now.impl_volatility 228 | # interpolate the rate to the exact maturity 229 | rate = get_rate_for_maturity(df_rate, date=date, days=days) 230 | # feed to the function computing implied moments 231 | qout = qmoms_compute(mnes=mnes, vol=iv, days=days, rate=rate, params=default_params) 232 | print(ids, qout) 233 | 234 | ####################################################################### 235 | # GROUP THE DATA, AND RUN COMPUTATIONS IN A LOOP SEQUENTIALLY 236 | qmoms_all = {} 237 | df_surf_grouped = df_surf.groupby(['id', 'date', 'days'], group_keys=False) 238 | for ids, group_now in tqdm(df_surf_grouped): 239 | id, date, days = ids 240 | # extract data on moneyness and implied volatility 241 | mnes = group_now.mnes 242 | iv = group_now.impl_volatility 243 | # interpolate the rate to the exact maturity 244 | rate = get_rate_for_maturity(df_rate, date=date, days=days) 245 | # feed to the function computing implied moments 246 | qout = qmoms_compute(mnes=mnes, vol=iv, days=days, rate=rate, params=default_params) 247 | qmoms_all[ids] = qout 248 | pass 249 | 250 | qmoms_all = pd.DataFrame(qmoms_all).T 251 | qmoms_all.index.names = ['id', 'date', 'days'] 252 | print(qmoms_all) 253 | 254 | ####################################################################### 255 | # GROUP THE DATA, AND RUN COMPUTATIONS FOR THE WHOLE DATAFRAME 256 | # first, add the interest rate for each maturity to the surface dataframe 257 | df_surf = get_rate_for_maturity(df_rate, df_surf=df_surf) 258 | # group dataset by id,date,days 259 | grouped = df_surf.groupby(['id', 'date', 'days'], group_keys=False) 260 | 261 | # PARALLEL 262 | with Pool(CPUUsed) as p: 263 | ret_list = p.map(qmoms_compute_bygroup, [[group_indv, default_params] for name, group_indv in grouped]) 264 | out_p = pd.DataFrame.from_records(ret_list) 265 | pass 266 | print(out_p) 267 | 268 | # SERIAL 269 | ret_list = [qmoms_compute_bygroup([group_indv, default_params]) for name, group_indv in grouped] 270 | out_s = pd.DataFrame.from_records(ret_list) 271 | print(out_s) 272 | ``` 273 | 274 | 275 | ## Installation 276 | 277 | You can install the package master branch directly from GitHub with: 278 | 279 | ```bash 280 | pip install git+https://github.com/vilkovgr/qmoms.git 281 | ``` 282 | 283 | 284 | ## Requirements 285 | 286 | * Python 3.9+ 287 | * numpy>=1.19 288 | * pandas>=2.0 289 | * scipy>=1.10 290 | * tqdm>=4.0 291 | 292 | Older versions might work, but were not tested. 293 | 294 | ## Other Channels and Additional Data 295 | Precomputed moments and other data are available through the OSF repository at https://osf.io/8awyu/ 296 | 297 | Feel free to load and use in your projects: 298 | * Generalized lower bounds as in Chabi-Yo, Dim, Vilkov (MS, 2023) / https://ssrn.com/abstract=3565130 299 | * Option-implied betas as in Buss and Vilkov (RFS, 2012) / https://ssrn.com/abstract=1301437 300 | * Options-implied correlations as in Driessen, Maenhout, Vilkov (JF, 2009) / https://ssrn.com/abstract=2166829 301 | * Climate change exposure measures as in Lent, Sautner, Zhang, and Vilkov (JF, MS, 2023) / https://ssrn.com/abstract=3792366 302 | and https://ssrn.com/abstract=3642508 303 | 304 | 305 | ## Acknowledgements 306 | 307 | The implementation is inspired by the Python and MATLAB code for implied moments made available on [Grigory Vilkov's website](https://vilkov.net). 308 | 309 | *** 310 | The package is still in the development phase, hence please share your comments and suggestions with us. 311 | 312 | Contributions welcome! 313 | 314 | Grigory Vilkov -------------------------------------------------------------------------------- /qmoms/qmoms.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2008-2024 Grigory Vilkov 5 | 6 | Contact: vilkov@vilkov.net www.vilkov.net 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | ''' 26 | 27 | # import packages and variables 28 | import pandas as pd 29 | import numpy as np 30 | import scipy.stats as ss 31 | from scipy.interpolate import PchipInterpolator 32 | from scipy.optimize import minimize 33 | import warnings 34 | 35 | # Ignore RuntimeWarning 36 | warnings.filterwarnings("ignore", category=RuntimeWarning) 37 | 38 | 39 | # ***************************************************************************** 40 | # compute the moments from a dataframe-based group 41 | # ***************************************************************************** 42 | def qmoms_compute_bygroup(groupparams, 43 | id=None, rate=None, days=None, date=None, 44 | cols_map={'id': 'id', 45 | 'date': 'date', 46 | 'days': 'days', 47 | 'rate': 'rate', 48 | 'mnes': 'mnes', 49 | 'impl_volatility': 'impl_volatility'}): 50 | """ 51 | Computes implied moments for grouped option data using specified parameters and column mappings. 52 | 53 | Parameters: 54 | - groupparams (tuple, list, or dict): If a tuple or list, this should contain the group data and parameters as the 55 | first and second elements, respectively. If a dict, it is used directly as parameters. 56 | - id (int, optional): Identifier for the data group, defaults to the first 'id' in the group data as specified in 57 | cols_map if not provided. 58 | - rate (float, optional): Risk-free interest rate, defaults to the first 'rate' in the group data as specified in 59 | cols_map if not provided. 60 | - days (int, optional): Days until expiration, defaults to the first 'days' in the group data as specified in cols_ 61 | map if not provided. 62 | - date (any, optional): Date of the data, defaults to the first 'date' in the group data as specified in cols_map 63 | if not provided. 64 | - cols_map (dict, optional): A dictionary mapping the expected columns in the group data to the actual column names. 65 | Default is {'id': 'id', 'date': 'date', 'days': 'days', 'rate': 'rate', 66 | 'mnes': 'mnes', 'impl_volatility': 'impl_volatility'}. 67 | 68 | Returns: 69 | - pandas.Series: Contains the computed moments along with initial group identifiers such as id, date, and days. 70 | 71 | The function sorts the group data by moneyness, removes duplicates, and computes moments using interpolation. 72 | It then merges the computed moments with initial data identifiers and returns a pandas Series, using the column 73 | mappings specified in cols_map for accessing data fields. 74 | """ 75 | cols_map = {'id': 'id', 76 | 'date': 'date', 77 | 'days': 'days', 78 | 'rate': 'rate', 79 | 'mnes': 'mnes', 80 | 'impl_volatility': 'impl_volatility'} | cols_map 81 | 82 | if (isinstance(groupparams, tuple) or isinstance(groupparams, list)) and len(groupparams) == 2: 83 | group = groupparams[0] 84 | params = groupparams[1] 85 | else: 86 | params = groupparams 87 | 88 | # scalars 89 | id = id or group[cols_map['id']].iloc[0] 90 | date = date or group[cols_map['date']].iloc[0] 91 | days = days or group[cols_map['days']].iloc[0] 92 | rate = rate or group[cols_map['rate']].iloc[0] 93 | 94 | # the surface is given in two columns 95 | group = group.sort_values(by=[cols_map['mnes']]) 96 | mnes = group[cols_map['mnes']] 97 | vol = group[cols_map['impl_volatility']] 98 | 99 | # remove duplicated moneyness points 100 | goods = ~mnes.duplicated() 101 | mnes = mnes[goods] 102 | vol = vol[goods] 103 | 104 | # pre-define the output dict 105 | res = {'id': id, 'date': date, 'days': days} 106 | 107 | # compute the moments with interpolation 108 | res_computed = qmoms_compute(mnes, vol, days, rate, params, output='dict') 109 | 110 | # update the output 111 | res = res | res_computed 112 | 113 | return pd.Series(res) 114 | 115 | 116 | # ***************************************************************************** 117 | # % compute the moments for one id/date/days combo 118 | # ***************************************************************************** 119 | def qmoms_compute(mnes, vol, days, rate, params, output='pandas'): 120 | """ 121 | Computes implied moments for option pricing using interpolated volatility and various moment formulas. 122 | 123 | Parameters: 124 | - mnes (array-like): moneyness (K/S) of the options. 125 | - vol (array-like): implied volatilities corresponding to the moneyness. 126 | - days (int): Days until the options' expiration. 127 | - rate (float): Risk-free interest rate. 128 | - params (dict): Configuration parameters containing grid settings and other options. 129 | - output (str): Specifies the output format ('pandas' for a pandas.Series, else returns a dictionary). 130 | 131 | Returns: 132 | - pandas.Series or dict: Calculated moments and optionally other metrics based on the provided parameters. 133 | 134 | The function performs the following: 135 | - Sorts inputs by moneyness. 136 | - Computes forward prices and interpolation of implied volatilities. 137 | - Uses Black or Black-Scholes formulas to compute option prices. 138 | - Calculates implied variances and semi-variances using specified methods. 139 | - Optionally computes advanced metrics like MFIS/MFIK, Corridor VIX, RIX, Tail Loss Measure, 140 | and Slopes based on user configurations in `params`. 141 | 142 | ####################################################################################################### 143 | default_params = {'atmfwd': False, # if True, use FWD as ATM level and Black model for option valuation 144 | 'grid': {'number_points': 500, # points*2 + 1 145 | 'grid_limit': 2}, # 1/(1+k) to 1+k; typically, k = 2 146 | 'filter': {'mnes_lim': [0, 1000], # [0.7, 1.3], 147 | 'delta_put_lim': [-0.5 + 1e-3, 0], # typically, only OTM 148 | 'delta_call_lim': [0, 0.5], # typically, only OTM 149 | 'best_bid_zero': -1, # if negative, no restriction on zero bid; 150 | 'open_int_zero': -1, # if negative, no restriction on zero open interest; 151 | 'min_price': 0}, # a limit on the min mid-price 152 | 'semivars': {'compute': True}, # save semivariances or not, they are computed anyway 153 | 'mfismfik': {'compute': True}, # compute skewness/ kurtosis 154 | 'cvix': {'compute': True, # compute corridor VIX 155 | 'abs_dev': [0.2], # absolute deviation from ATM level of 1 156 | 'vol_dev': [2]}, # deviation in terms of own sigmas from ATM level of 1 157 | 'rix': {'compute': True}, # compute RIX (Gao,Gao,Song RFS) or not, 158 | 'tlm': {'compute': True, # compute Tail Loss Measure (Xiao/ Vilkov) or not 159 | 'delta_lim': [20], # OTM put delta for the threshold (pos->neg or neg) 160 | 'vol_lim': [2]}, # deviation to the left in terms of sigmas from ATM level of 1 161 | 'slope': {'compute': True, 162 | 'deltaP_lim': [-0.5, -0.05], 163 | # limits to compute the slopedn as Slope from IV = const + Slope * Delta 164 | 'deltaC_lim': [0.05, 0.5] 165 | # limits to compute the slopeup as -Slope from IV = const + Slope * Delta 166 | } 167 | } 168 | ####################################################################################################### 169 | To modify parameters, update the default_params dictionary with the dictionary with desired parameters 170 | for example, in case you do not want to compute the tlm, and want to change slope parameters, use 171 | new_params0 = {'tlm': {'compute': False}} 172 | new_params1 = {'slope': {'compute': True, 173 | 'deltaP_lim': [-0.4, -0.1], 174 | # limits to compute the slopedn as Slope from IV = const + Slope * Delta 175 | 'deltaC_lim': [0.05, 0.5] 176 | # limits to compute the slopeup as -Slope from IV = const + Slope * Delta 177 | }} 178 | params = default_params | new_params0 | new_params1 179 | ####################################################################################################### 180 | """ 181 | mnes = np.array(mnes) 182 | vol = np.array(vol) 183 | ii = np.argsort(mnes) 184 | mnes = mnes[ii] 185 | vol = vol[ii] 186 | 187 | # assign grid parameters to variables 188 | gridparams = params.get('grid', {}) 189 | gridparams_m = gridparams.get('number_points', 500) 190 | gridparams_k = gridparams.get('grid_limit', 2) 191 | grid = np.array([gridparams_m, gridparams_k]) 192 | ########################################## 193 | # define maturity and forward price (for S0=1) 194 | ########################################## 195 | mat = days / 365 196 | er = np.exp(mat * rate) 197 | ########################################## 198 | # compute the moments WITH INTERPOLATION 199 | ########################################## 200 | nopt = len(mnes) 201 | dicct = {'nopt': nopt} 202 | 203 | if (nopt >= 4) & (len(np.unique(mnes)) == len(mnes)): 204 | ####################################################################### 205 | # interpolate 206 | iv, ki = interpolate_iv_by_moneyness(mnes, vol, grid) 207 | # calculate BS prices - OTM calls - mnes >=1 208 | otmcalls = ki >= 1 209 | otmputs = ~otmcalls 210 | kicalls = ki[otmcalls] 211 | 212 | if params.get('atmfwd', False): # use Black formula 213 | # OTM calls 214 | currcalls, itmputs = Black(1, kicalls, rate, iv[otmcalls], mat) 215 | # OTM puts 216 | kiputs = ki[otmputs] 217 | itmcalls, currputs = Black(1, kiputs, rate, iv[otmputs], mat) 218 | else: 219 | # OTM calls 220 | currcalls, itmputs = BlackScholes(1, kicalls, rate, iv[otmcalls], mat) 221 | # OTM puts 222 | kiputs = ki[otmputs] 223 | itmcalls, currputs = BlackScholes(1, kiputs, rate, iv[otmputs], mat) 224 | 225 | ####################################################################### 226 | # use the gridparams to define some variables 227 | m = gridparams_m # use points -500:500, i.e. 1001 points for integral approximation 228 | k = gridparams_k # use moneyness 1/(1+2) to 1+2 - should be enough as claimed by JT 229 | u = np.power((1 + k), (1 / m)) 230 | 231 | mi = np.arange(-m, m + 1) # exponent for the grid 232 | mi.shape = otmcalls.shape 233 | ic = mi[otmcalls] # exponent for OTM calls 234 | ip = mi[otmputs] 235 | 236 | ### compute some general input to implied variances 237 | Q = np.append(currputs, currcalls) # all option prices 238 | Q.shape = (len(Q), 1) 239 | dKi = np.empty((len(Q), 1)) # dK 240 | dKi[:] = np.NAN 241 | x = (ki[2:, ] - ki[0:-2]) / 2 242 | x = np.reshape(x, [ki.shape[0] - 2, 1]) 243 | dKi[1:-1] = x 244 | dKi[0] = ki[1] - ki[0] 245 | dKi[-1] = ki[-1] - ki[-2] 246 | dKi = abs(dKi) 247 | 248 | ####################################################################### 249 | ### compute SMFIV -- Ian Martin 250 | K0sq = 1 251 | # inputs for semivars: 252 | svar_ingredients = np.divide(np.multiply(dKi, Q), K0sq) 253 | svar_multiplier = 2 * er 254 | # semivariance 255 | moments_smfivu = svar_multiplier * np.sum(svar_ingredients[otmcalls]) / mat 256 | moments_smfivd = svar_multiplier * np.sum(svar_ingredients[otmputs]) / mat 257 | # total variance 258 | moments_smfiv = moments_smfivu + moments_smfivd 259 | 260 | ####################################################################### 261 | ### compute MFIV_BJN - Britten-Jones and Neuberger (2002) 262 | Ksq = ki ** 2 # denominator 263 | # inputs for semivars: 264 | bjn_nom = np.multiply(dKi, Q) 265 | bjn_ingredients = np.divide(bjn_nom, Ksq) 266 | bjn_multiplier = 2 * er 267 | # semivariance 268 | moments_mfivu_bjn = bjn_multiplier * np.sum(bjn_ingredients[otmcalls]) / mat 269 | moments_mfivd_bjn = bjn_multiplier * np.sum(bjn_ingredients[otmputs]) / mat 270 | # total variance 271 | moments_mfiv_bjn = moments_mfivu_bjn + moments_mfivd_bjn 272 | 273 | ####################################################################### 274 | ### compute MFIV_BKM - Bakshi, Kapadia and Madan (2003) 275 | Ksq = ki ** 2 276 | # inputs for semivars: 277 | bkm_nom = np.multiply(dKi, np.multiply(1 - np.log(ki), Q)) 278 | bkm_ingredients = np.divide(bkm_nom, Ksq) 279 | bkm_multiplier = 2 * er 280 | # semivariance 281 | moments_mfivu_bkm = bkm_multiplier * np.sum(bkm_ingredients[otmcalls]) / mat 282 | moments_mfivd_bkm = bkm_multiplier * np.sum(bkm_ingredients[otmputs]) / mat 283 | # total variance 284 | moments_mfiv_bkm = moments_mfivu_bkm + moments_mfivd_bkm 285 | 286 | # OUTPUT 1 287 | dicct.update({'smfiv': moments_smfiv, 288 | 'mfiv_bkm': moments_mfiv_bkm, 289 | 'mfiv_bjn': moments_mfiv_bjn}) 290 | 291 | if params.get('semivars', {}).get('compute', False): 292 | dicct.update({'smfivd': moments_smfivd, 293 | 'mfivd_bkm': moments_mfivd_bkm, 294 | 'mfivd_bjn': moments_mfivd_bjn}) 295 | 296 | ####################################################################### 297 | ### compute some inputs for MFIS/MFIK based on BKM 298 | a = 2 * (u - 1) 299 | b1 = 1 - (np.log(1 + k) / m) * ic 300 | b1.shape = currcalls.shape 301 | b1 = np.multiply(b1, currcalls) 302 | h = np.power(u, ic) 303 | h.shape = currcalls.shape 304 | b1 = np.divide(b1, h) 305 | b2 = 1 - (np.log(1 + k) / m) * ip 306 | b2.shape = currputs.shape 307 | b2 = np.multiply(b2, currputs) 308 | g = np.power(u, ip) 309 | g.shape = currputs.shape 310 | b2 = np.divide(b2, g) 311 | b_all = np.concatenate((b2, b1)) # note: puts first because ki has moneyness range from puts to calls 312 | 313 | # if not called just to compute market variance -- proceed with other computations 314 | # MFIS/ MFIK 315 | if params.get('mfismfik', {}).get('compute', False): 316 | mfismfik_dic = compute_skew_kurtosis(m, k, u, ic, ip, currcalls, currputs, er, 317 | moments_mfiv_bkm * mat / er) # adjustment /er on 2023-03-30 318 | # OUTPUT MFIS/ MFIK 319 | dicct.update(mfismfik_dic) 320 | 321 | ####################################################################### 322 | # CORRIDOR VIX / Bondarenko et al 323 | if params.get('cvix', {}).get('compute', False): 324 | cvix_dic = cvix_func(b_all, params['cvix'], ki, moments_mfiv_bkm, mat, a) 325 | dicct.update(cvix_dic) 326 | pass 327 | 328 | ####################################################################### 329 | # RIX - Gao Gao Song RFS 330 | if params.get('rix', {}).get('compute', False): 331 | rix = moments_mfivd_bkm - moments_mfivd_bjn 332 | rixn = rix / moments_mfivd_bkm 333 | dicct.update({'rix': rix, 'rixnorm': rixn}) 334 | pass 335 | 336 | ####################################################################### 337 | # TAIL LOSS MEASURE Hamidieh / Vilkov and Xiao 338 | if params.get('tlm', {}).get('compute', False): 339 | if params.get('atmfwd', False): 340 | x, currputd = Black_delta(1, kiputs, rate, iv[otmcalls == False], mat) 341 | else: 342 | x, currputd = BlackScholes_delta(1, kiputs, rate, iv[otmcalls == False], mat) 343 | tlm_dic = tlm_func(currputs, kiputs, currputd, params['tlm'], moments_mfiv_bkm, mat) 344 | dicct.update(tlm_dic) 345 | pass 346 | 347 | ####################################################################### 348 | # SLOPE Kelly et al + applications in Carbon Tail Risk and Pricing Climate Change Exposure 349 | if params.get('slope', {}).get('compute', False): 350 | delta_lim = params['slope'].get('deltaP_lim', [-0.5, -0.05]) 351 | ivputs = iv[otmputs] 352 | if params.get('atmfwd', False): 353 | x, currputd = Black_delta(1, kiputs, rate, ivputs, mat) 354 | else: 355 | x, currputd = BlackScholes_delta(1, kiputs, rate, ivputs, mat) 356 | selputs = (currputd >= min(delta_lim)) & (currputd <= max(delta_lim)) 357 | dnow = currputd[selputs] 358 | ivnow = ivputs[selputs] 359 | 360 | if sum(selputs) > 3: 361 | slope, intercept, r_value, p_value, std_err = ss.linregress(dnow, ivnow) # x, y ;) 362 | else: 363 | slope = np.nan 364 | pass 365 | dicct.update({'slopedn': slope}) 366 | 367 | # same for calls 368 | delta_lim = params['slope'].get('deltaC_lim', [0.05, 0.5]) 369 | ivcalls = iv[otmcalls] 370 | if params.get('atmfwd', False): 371 | currcalld, x = Black_delta(1, kicalls, rate, ivcalls, mat) 372 | else: 373 | currcalld, x = BlackScholes_delta(1, kicalls, rate, ivcalls, mat) 374 | selcalls = (currcalld >= min(delta_lim)) & (currcalld <= max(delta_lim)) 375 | dnow = currcalld[selcalls] 376 | ivnow = ivcalls[selcalls] 377 | 378 | if sum(selcalls) > 3: 379 | slope, intercept, r_value, p_value, std_err = ss.linregress(dnow, ivnow) # x, y ;) 380 | else: 381 | slope = np.nan 382 | pass 383 | dicct.update({'slopeup': -slope}) 384 | pass 385 | 386 | ####################################################################### 387 | out = dicct 388 | if output == 'pandas': 389 | out = pd.Series(dicct) 390 | pass 391 | return out 392 | 393 | 394 | ### compute MFIS/MFIK 395 | def compute_skew_kurtosis(m, k, u, ic, ip, currcalls, currputs, er, V): 396 | u = u.ravel() 397 | ic = ic.ravel() 398 | ip = ip.ravel() 399 | currcalls = currcalls.ravel() 400 | currputs = currputs.ravel() 401 | 402 | result_dict = {} 403 | 404 | a = 3 * (u - 1) * np.log(1 + k) / m 405 | b1 = np.sum(ic * (2 - (np.log(1 + k) / m) * ic) * currcalls / u ** ic) 406 | b2 = np.sum(ip * (2 - (np.log(1 + k) / m) * ip) * currputs / u ** ip) 407 | W = a * (b1 + b2) 408 | 409 | a = 4 * (u - 1) * (np.log(1 + k) / m) ** 2 410 | b1 = np.sum(ic ** 2 * (3 - (np.log(1 + k) / m) * ic) * currcalls / u ** ic) 411 | b2 = np.sum(ip ** 2 * (3 - (np.log(1 + k) / m) * ip) * currputs / u ** ip) 412 | X = a * (b1 + b2) 413 | 414 | mu = er - 1 - er / 2 * V - er / 6 * W - er / 24 * X 415 | c = (er * V - mu ** 2) 416 | 417 | mfis = (er * W - 3 * mu * er * V + 2 * mu ** 3) / (c ** (3 / 2)) 418 | mfik = (er * X - 4 * mu * er * W + 6 * er * mu ** 2 * V - 3 * mu ** 4) / c ** 2 419 | 420 | result_dict['mfis'] = mfis.item() 421 | result_dict['mfik'] = mfik.item() 422 | 423 | return result_dict 424 | 425 | 426 | ### compute TAIL LOSS MEASURE 427 | def tlm_func(currputs, kiputs, currputd, tlm_params, iv, mat): 428 | currputs = currputs.ravel() 429 | kiputs = kiputs.ravel() 430 | currputd = currputd.ravel() 431 | result_dict = {} 432 | 433 | sd_ls = tlm_params.get('vol_lim', []) 434 | absD_ls = tlm_params.get('delta_lim', []) 435 | 436 | # define time varying threshold 437 | iv_m = np.sqrt(iv * mat) # convert to horizon = days 438 | for r in sd_ls: 439 | tlm = np.NAN 440 | k0 = 1.0 - r * iv_m 441 | goods = kiputs <= k0 442 | if np.sum(goods) > 1: 443 | Pt = currputs[goods] 444 | K = kiputs[goods] # strikes 445 | P0 = np.max(Pt) 446 | ind = np.argmax(Pt) 447 | K0 = K[ind] 448 | try: 449 | res = minimize(f_tlm, np.array([2, 0.01]), args=(P0, K0, Pt, K,), 450 | method='SLSQP', 451 | options={'ftol': 1e-32, 'disp': False}) 452 | tlm = res.x[1] / (1 - res.x[0]) 453 | except: 454 | pass 455 | pass 456 | result_dict['tlm_sigma' + str(r)] = tlm 457 | pass 458 | 459 | for r in absD_ls: 460 | tlm = np.NAN 461 | if abs(r) > 1: r = r / 100 462 | goods = abs(currputd) <= abs(r) 463 | if np.sum(goods) > 1: 464 | Pt = currputs[goods] # prices 465 | K = kiputs[goods] # strikes 466 | P0 = np.max(Pt) 467 | ind = np.argmax(Pt) 468 | K0 = K[ind] 469 | try: 470 | res = minimize(f_tlm, np.array([2, 0.01]), args=(P0, K0, Pt, K,), 471 | method='SLSQP', options={'ftol': 1e-32, 'disp': False}) 472 | tlm = res.x[1] / (1 - res.x[0]) 473 | except: 474 | pass 475 | pass 476 | result_dict['tlm_delta' + str(int(abs(r * 100)))] = tlm 477 | pass 478 | return result_dict 479 | 480 | 481 | ############################################################################### 482 | def f_tlm(X, P0, K0, Pt, K): 483 | # prices = Pt 484 | # strikes = K 485 | xi = X[0] 486 | beta = X[1] 487 | checkterms = 1 + (xi / beta) * (K0 - K) 488 | term2 = np.multiply(P0, np.divide(np.power(checkterms, (1 - 1 / xi)), Pt)) 489 | return np.sum((1 - term2) ** 2) 490 | 491 | 492 | ### compute CORRIDOR VIX 493 | def cvix_func(b_all, cvix_params, ki, iv, mat, a): 494 | ''' 495 | Computes corridor variance based on r relative (sigma) deviations 496 | or specified absolute deviations from ATM moneyness of 1 497 | ''' 498 | # change a to adjust for maturity 499 | a = a / mat 500 | 501 | sd_ls = cvix_params.get('vol_dev', []) 502 | absD_ls = cvix_params.get('abs_dev', []) 503 | 504 | result_dict = {} 505 | 506 | # define time varying threshold for semi-variances 507 | iv_m = np.sqrt(iv * mat) # convert to horizon = days 508 | 509 | b_all.shape = ki.shape 510 | 511 | # compute CVIX based on r standard deviations (mfiv**0.5) 512 | for r in sd_ls: 513 | kl = 1 - r * iv_m 514 | ku = 1 + r * iv_m 515 | sel_ki = (ki >= kl) & (ki <= ku) # range for cvix 516 | result_dict['cvix_sigma' + str(r)] = a * np.sum(b_all[sel_ki]) 517 | 518 | # compute CVIX based on absolute deviation 519 | for r in absD_ls: 520 | kl = 1 - r 521 | ku = 1 + r 522 | sel_ki = (ki >= kl) & (ki <= ku) # range for CVIX 523 | result_dict['cvix_mnes' + str(int(r * 100))] = a * np.sum(b_all[sel_ki]) 524 | 525 | return result_dict 526 | 527 | 528 | # ***************************************************************************** 529 | # COMPUTE BS AND BS DELTA 530 | # ***************************************************************************** 531 | def BlackScholes(S0, K, r, sigma, T): 532 | d1 = (np.log(S0 / K) + (r + np.square(sigma) / 2) * T) / (sigma * np.sqrt(T)) 533 | d2 = (np.log(S0 / K) + (r - np.square(sigma) / 2) * T) / (sigma * np.sqrt(T)) 534 | c = S0 * ss.norm.cdf(d1) - K * np.exp(-r * T) * ss.norm.cdf(d2) 535 | p = K * np.exp(-r * T) * ss.norm.cdf(-d2) - S0 * ss.norm.cdf(-d1) 536 | return c, p 537 | 538 | 539 | def Black(F, K, r, sigma, T): 540 | d1 = (np.log(F / K) + (np.square(sigma) / 2) * T) / (sigma * np.sqrt(T)) 541 | d2 = (np.log(F / K) + (-np.square(sigma) / 2) * T) / (sigma * np.sqrt(T)) 542 | c = (F * ss.norm.cdf(d1) - K * ss.norm.cdf(d2)) * np.exp(-r * T) 543 | p = (K * ss.norm.cdf(-d2) - F * ss.norm.cdf(-d1)) * np.exp(-r * T) 544 | return c, p 545 | 546 | 547 | def BlackScholes_delta(S0, K, r, sigma, T): 548 | d1 = (np.log(S0 / K) + (r + np.square(sigma) / 2) * T) / (sigma * np.sqrt(T)) 549 | delta_c = ss.norm.cdf(d1) 550 | delta_p = delta_c - 1 551 | return delta_c, delta_p 552 | 553 | 554 | def Black_delta(F, K, r, sigma, T): 555 | d1 = (np.log(F / K) + (np.square(sigma) / 2) * T) / (sigma * np.sqrt(T)) 556 | delta_c = ss.norm.cdf(d1) * np.exp(-r * T) 557 | delta_p = -ss.norm.cdf(-d1) * np.exp(-r * T) 558 | return delta_c, delta_p 559 | 560 | 561 | # ***************************************************************************** 562 | # % Function OM_Interpolate_IV 563 | # ***************************************************************************** 564 | def interpolate_iv_by_moneyness(mnes, vol, grid): 565 | # set the grid in terms of moneyness that we want to use to compute the 566 | # MFIV/ MFIS and other integrals as needed 567 | m = grid[0] # use points -500:500, i.e. 1001 points for integral approximation 568 | k = grid[1] # use moneyness 1/(1+2) to 1+2 - should be enough as claimed by JT 569 | u = np.power((1 + k), (1 / m)) 570 | 571 | # create strikes using s=1, i.e. get k/s = moneyness 572 | mi = np.arange(-m, m + 1) 573 | ki = np.power(u, mi) 574 | iv = np.empty((len(ki), 1)) 575 | iv[:] = np.NAN 576 | ki.shape = iv.shape 577 | 578 | currspline = PchipInterpolator(mnes, vol, axis=0) 579 | 580 | k_s_max = max(mnes) # OTM calls 581 | k_s_min = min(mnes) # OTM puts 582 | iv_max = vol[0] # for more OTM puts i.e we have iv_max for min k/s, i.e. for OTM put option 583 | iv_min = vol[-1] # for more OTM calls i.e. we have iv_min for OTM call option 584 | 585 | # calculate the interpolated/ extrapolated IV for these k/s 586 | ks_larger_ind = ki > k_s_max # more OTM call 587 | ks_smaller_ind = ki < k_s_min # more OTM put 588 | ks_between_ind = (ki >= k_s_min) & (ki <= k_s_max) 589 | 590 | if sum(ks_larger_ind) > 0: 591 | iv[ks_larger_ind] = iv_min 592 | 593 | if sum(ks_smaller_ind) > 0: 594 | iv[ks_smaller_ind] = iv_max 595 | 596 | # evaluate the spline at ki[ks_between_ind] 597 | if sum(ks_between_ind) > 0: 598 | s = currspline(ki[ks_between_ind], nu=0, extrapolate=None) 599 | s.shape = iv[ks_between_ind].shape 600 | iv[ks_between_ind] = s 601 | return iv, ki 602 | --------------------------------------------------------------------------------