Metadata-Version: 2.4
Name: qmoms
Version: 0.1.3
Summary: Option-implied moments from implied volatility surface data
Author-email: Grigory Vilkov <vilkov@vilkov.net>
License: MIT License
        
        Copyright (c) 2008-2024 Grigory Vilkov
        
        Contact: vilkov@vilkov.net www.vilkov.net 
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/vilkovgr/qmoms
Keywords: implied variance,variance swap,VIX,MFIV,CVIX,skewness
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scipy>=1.10
Requires-Dist: sympy>=1.12
Requires-Dist: tqdm>=4.0
Dynamic: license-file

# 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):
   * `MFIV_BKM` using Bakshi, Kapadia, and Madan (RFS, 2003) / https://doi.org/10.1093/rfs/16.1.0101
   * `MFIV_BJN` using Britten-Jones and Neuberger (JF, 2002) / https://doi.org/10.1111/0022-1082.00228
2. Simple model-free implied variance 
   * `SMFIV` using Martin (QJE, 2017) / https://doi.org/10.1093/qje/qjw034
3. Corridor VIX 
   * `CVIX` using Andersen, Bondarenko, and Gonzalez-Perez (RFS, 2015) / https://doi.org/10.1093/rfs/hhv033
4. Tail Loss Measure 
   * `TLM` using 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
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 
   * `RIX` using Gao, Gao and Song, (RFS, 2018) / https://doi.org/10.1093/rfs/hhy027
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:**
```python
from qmoms import load_data
df_surf, df_rate, df_return = load_data()  # rates already in annual decimals
```

**Your own CSV files:**
```python
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:

```python
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).

```python
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:**
```bash
pip install qmoms
```

**From GitHub** (latest development version):
```bash
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:
```bash
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](https://vilkov.net).

***
The package is still in the development phase, hence please share your comments and suggestions with us.

Contributions welcome!

Grigory Vilkov
