Implied Moments from Volatility Surface Data
This is a Python implementation of a set of option-implied characteristics.
Scope
The package contains functions to compute option-implied moments and characteristics from implied volatility surface data. The computations are based on the out-of-the-money (OTM) implied volatilities, interpolated as a function of moneyness (Strike/Underlying Price or Strike/Forward Price) within the available moneyness range and set to the boundary values outside that range to fill the pre-specified moneyness grid. OTM is defined as moneyness >= 1 for calls and < 1 for puts.
The following moments and characteristics are computed:
- Model-free implied variance (log contract):
MFIV_BKMusing Bakshi, Kapadia, and Madan (RFS, 2003) / https://doi.org/10.1093/rfs/16.1.0101MFIV_BJNusing Britten-Jones and Neuberger (JF, 2002) / https://doi.org/10.1111/0022-1082.00228
- Simple model-free implied variance
SMFIVusing Martin (QJE, 2017) / https://doi.org/10.1093/qje/qjw034
- Corridor VIX
CVIXusing Andersen, Bondarenko, and Gonzalez-Perez (RFS, 2015) / https://doi.org/10.1093/rfs/hhv033
- Tail Loss Measure
TLMusing Vilkov, Xiao (2012) / https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1940117 and Hamidieh (Journal of Risk, 2017) / https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2209654
- Semivariances (only down) for log and simple contracts
- Following Feunou, Jahan-Parvar,and Okou, (Journal of Fin Econometrics, 2017) / https://doi.org/10.1093/jjfinec/nbx020
- Up semivariances can be computed as the respective total variance minus down semivariance
- Tail jump measure
RIXusing Gao, Gao and Song, (RFS, 2018) / https://doi.org/10.1093/rfs/hhy027
- Ad hoc tail measures (smile steepness for the tails)
Slopeup(right tail) andSlopedn(left tail) measure right and left tail slope, respectively. Larger values indicate more expensive tail relative to the at-the-money level.- Used in Carbon Tail Risk, Ilhan, Sautner, and Vilkov, (RFS, 2021) / https://doi.org/10.1093/rfs/hhaa071 and Pricing Climate Change Exposure, v.Lent, Sautner, Zhang, and Vilkov, (Management Science, 2023) / https://doi.org/10.1287/mnsc.2023.4686 (mind the sign of slopeup -- we take '-Slope' here for better interpretability). Originally, Slopedn is used in Kelly, Pastor, Veronesi (JF, 2016) / https://doi.org/10.1111/jofi.12406
- Generalized Lower Bounds on expected returns
GLBusing Chabi-Yo, Dim, and Vilkov (Management Science, 2023) / https://ssrn.com/abstract=3565130- Requires historical return data in addition to the implied volatility surface
Usage
Interest rates (units)
All computation functions expect the risk-free rate as an annual continuously compounded rate in decimals
(e.g. 0.05 means 5% per annum). Do not pass percent values such as 5.0.
| Stage | Typical unit | Example |
|---|---|---|
Raw OptionMetrics-style zerocd.csv |
percent p.a. | 5.12 |
After load_data() / load_rate_csv() (default rates_in_percent=True) |
annual decimal | 0.0512 |
Argument to qmoms_compute(..., rate=...) |
annual decimal | 0.0512 |
Related conventions used throughout:
- Discounting / forward:
mat = days / 365,er = exp(rate * mat)(365-day year). - Implied volatility: annualized, in decimals (e.g.
0.20for 20%), same units as Black–Scholessigma.
Example data and loaders
Example data ship with the package (three CSV files). Column mappings are in qmoms_params.py:
surface.csv— IV surface by delta for several dates and 30/60/91-day maturities
(id,date,days,mnes,impl_volatility,delta)zerocd.csv— zero CD rates by date and maturity, stored as % p.a. in the filereturns.csv— daily returns, market factor (mktrf), and risk-free (rf) for GLB
Default date format: default_date_format = '%Y-%m-%d'.
Bundled examples:
from qmoms import load_data
df_surf, df_rate, df_return = load_data() # rates already in annual decimals
Your own CSV files:
from qmoms import load_surface_csv, load_rate_csv, load_returns_csv
df_surf = load_surface_csv('path/to/surface.csv')
# percent p.a. in file -> annual decimals (default)
df_rate = load_rate_csv('path/to/zerocd.csv')
# if your rates are already decimals:
# df_rate = load_rate_csv('path/to/rates.csv', rates_in_percent=False)
df_return = load_returns_csv('path/to/returns.csv')
Runnable scripts: qmoms/examples/qmoms_test.py and qmoms/examples/qmoms_glb_test.py.
Main functions
qmoms_compute(mnes, vol, days, rate, params, output='pandas')— moments for one id/date/days surface.rateis an annual decimal;volis annualized IV in decimals. Default calculation switches live indefault_params(qmoms_params.py). Override with e.g.params = default_params | {'tlm': {'compute': False}}.qmoms_compute_bygroup([group_df, params])— same for one group DataFrame that already contains moneyness, IV, and (usually)rate. Optional keyword overrides:id,date,days,rate.
Full example script: qmoms/examples/qmoms_test.py. Minimal usage:
import pandas as pd
from qmoms import (
load_data, default_params, default_surf_dtype_mapping,
filter_options, get_rate_for_maturity, qmoms_compute, qmoms_compute_bygroup,
)
df_surf, df_rate, df_return = load_data() # rates already in annual decimals
df_surf = df_surf[list(default_surf_dtype_mapping.keys())]
df_surf = filter_options(df_surf, default_params['filter'])
# one group
ids, group_now = next(iter(df_surf.groupby(['id', 'date', 'days'], group_keys=False)))
id_, date, days = ids
rate = get_rate_for_maturity(df_rate, date=date, days=days)
qout = qmoms_compute(
mnes=group_now.mnes, vol=group_now.impl_volatility,
days=days, rate=rate, params=default_params,
)
print(qout)
# all groups (serial)
df_surf = get_rate_for_maturity(df_rate, df_surf=df_surf)
grouped = df_surf.groupby(['id', 'date', 'days'], group_keys=False)
out_s = pd.DataFrame.from_records(
[qmoms_compute_bygroup([g, default_params]) for _, g in grouped]
)
print(out_s)
Sample output for one group (id=12490, date=2023-01-03, days=30):
nopt 17.000000
smfiv 0.093976
mfiv_bkm 0.097201
mfiv_bjn 0.095837
smfivd 0.046491
mfivd_bkm 0.055252
mfivd_bjn 0.052134
mfis -0.531184
mfik 3.565793
cvix_sigma2 0.095381
cvix_mnes20 0.096203
rix 0.003118
rixnorm 0.056425
tlm_sigma2 35.497808
tlm_delta20 34.314357
slopedn 0.171740
slopeup -0.052627
Summary statistics over all 3,750 groups (5 assets, daily, 30/60/91-day maturities):
smfiv mfiv_bkm mfiv_bjn mfis mfik rix slopedn slopeup
count 3750.000 3750.0000 3750.0000 3750.00 3750.00 3750.00 3750.00 3750.000
mean 0.120 0.1245 0.1212 -0.64 4.23 0.008 0.260 0.026
std 0.120 0.1236 0.1189 0.25 0.69 0.013 0.069 0.080
min 0.020 0.0201 0.0201 -2.20 3.11 0.000 0.077 -0.121
25% 0.053 0.0548 0.0539 -0.81 3.78 0.002 0.219 -0.029
50% 0.072 0.0754 0.0739 -0.67 4.14 0.003 0.255 0.010
75% 0.106 0.1138 0.1106 -0.48 4.51 0.006 0.291 0.063
max 0.799 0.8678 0.8237 0.38 11.07 0.112 0.985 0.636
Generalized Lower Bounds (GLB)
The GLB procedure computes lower bounds on expected stock returns using option-implied moments and historical return data, following Chabi-Yo, Dim, and Vilkov (Management Science, 2023).
from qmoms import compute_glb_betas, prepare_integration_inputs_surf, compute_glb
# compute rolling betas from return data
beta_df = compute_glb_betas(df_return, parallel=False)
# prepare integration inputs from the IV surface (for one group)
inputs = prepare_integration_inputs_surf(mnes, vol, days, rate, default_params)
# assemble betas coefficient vector: [Bmj,...,Bmj2j, gamma, rho, kappa, er]
betas_sel = ['Bmj', 'Bm2j', 'Bj2j', 'Bmjj', 'Bm3j', 'Bj3j', 'Bm2jj', 'Bmj2j']
gm, rho, kp = 2, 1, 4
betas_coef = list(beta_row[betas_sel].values) + [gm, rho, kp, inputs['er']]
# compute GLB
result = compute_glb(inputs['er'], inputs['ki'], inputs['dKi'],
inputs['Q'], inputs['mat'], betas_coef)
Sample GLB output (id=12490, gamma=2, rho=1, kappa=4):
theta_GLB_TS3 = 0.980207
GLB_TS3 = 0.091838
theta_GLB_TS2 = 0.977650
GLB_TS2 = 0.091603
The GLB values are annualized lower bounds on expected excess returns.
TS2 and TS3 refer to 2nd and 3rd order Taylor series expansions used to determine the feasible theta range.
See qmoms/examples/qmoms_glb_test.py for a complete working example.
Installation
The package can be installed from PyPI (recommended) or from GitHub. Example data (surface.csv, zerocd.csv, returns.csv) is included in the package with either installation method.
From PyPI:
pip install qmoms
From GitHub (latest development version):
pip install git+https://github.com/vilkovgr/qmoms.git
Requirements
- Python 3.10+
- numpy>=1.24
- pandas>=2.0
- scipy>=1.10
- sympy>=1.12
- tqdm>=4.0
Testing
Run the test suite with:
python -m pytest tests/ -v
Other Channels and Additional Data
Precomputed moments and other data are available through the OSF repository at https://osf.io/8awyu/
Feel free to load and use in your projects:
- Generalized lower bounds as in Chabi-Yo, Dim, Vilkov (MS, 2023) / https://ssrn.com/abstract=3565130
- Option-implied betas as in Buss and Vilkov (RFS, 2012) / https://ssrn.com/abstract=1301437
- Options-implied correlations as in Driessen, Maenhout, Vilkov (JF, 2009) / https://ssrn.com/abstract=2166829
- Climate change exposure measures as in Lent, Sautner, Zhang, and Vilkov (JF, MS, 2023) / https://ssrn.com/abstract=3792366 and https://ssrn.com/abstract=3642508
Acknowledgements
The implementation is inspired by the Python and MATLAB code for implied moments made available on Grigory Vilkov's website.
The package is still in the development phase, hence please share your comments and suggestions with us.
Contributions welcome!
Grigory Vilkov
Metadata
Release files for qmoms 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| qmoms-0.1.3.tar.gz | 1.6 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qmoms-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.2 MB
Release files / qmoms-0.1.3.tar.gz
| Download URL | qmoms-0.1.3.tar.gz |
|---|---|
| Size | 1.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9bf141e94c065abd397748f767b5fb9961bf14ed697d9d1e0beb520956fdae10
|
|
BLAKE2b-256 checksum How to use checksums |
5cee72bd74358e2679ac99ab8221e3ba007df80fa9d5e162becfa8c466c6890a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / qmoms-0.1.3-py3-none-any.whl
| Download URL | qmoms-0.1.3-py3-none-any.whl |
|---|---|
| Size | 1.6 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
67c1bff8bb5f3d11936efb25c182f51d257e3d29a19a0d64aa8de6c6db923de6
|
|
BLAKE2b-256 checksum How to use checksums |
a5f442205677de0a63495b2f3f4df675af415b3700bbe2785fc9182fa967fcb6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Security for qmoms 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.