Skip to main content

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:

  1. Model-free implied variance (log contract):
  2. Simple model-free implied variance
  3. Corridor VIX
  4. Tail Loss Measure
  5. 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
  6. Tail jump measure
  7. Ad hoc tail measures (smile steepness for the tails)
    • Slopeup (right tail) and Slopedn (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
  8. Generalized Lower Bounds on expected returns
    • GLB using 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.20 for 20%), same units as Black–Scholes sigma.

Example data and loaders

Example data ship with the package (three CSV files). Column mappings are in qmoms_params.py:

  1. surface.csv — IV surface by delta for several dates and 30/60/91-day maturities
    (id, date, days, mnes, impl_volatility, delta)
  2. zerocd.csv — zero CD rates by date and maturity, stored as % p.a. in the file
  3. returns.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. rate is an annual decimal; vol is annualized IV in decimals. Default calculation switches live in default_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:

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)

Source distribution for qmoms 0.1.3
File Size Uploaded Provenance
qmoms-0.1.3.tar.gz 1.6 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for qmoms 0.1.3
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page