Metadata-Version: 2.4
Name: volsurface
Version: 0.2.0
Summary: Typed, extensible toolkit for implied volatility surface calibration
Project-URL: Homepage, https://github.com/cjpvanderwouden/volsurface
Project-URL: Documentation, https://cjpvanderwouden.github.io/volsurface/
Project-URL: Repository, https://github.com/cjpvanderwouden/volsurface
Project-URL: Changelog, https://github.com/cjpvanderwouden/volsurface/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/cjpvanderwouden/volsurface/issues
Author: Casper van der Wouden
License: MIT
License-File: LICENSE
Keywords: SSVI,SVI,calibration,derivatives,finance,options,volatility
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scipy>=1.10
Provides-Extra: all
Requires-Dist: matplotlib>=3.7; extra == 'all'
Requires-Dist: yfinance>=0.2.30; extra == 'all'
Provides-Extra: dev
Requires-Dist: matplotlib>=3.7; extra == 'dev'
Requires-Dist: mypy>=1.5; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Requires-Dist: yfinance>=0.2.30; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.0; extra == 'docs'
Requires-Dist: mkdocs>=1.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Provides-Extra: plot
Requires-Dist: matplotlib>=3.7; extra == 'plot'
Provides-Extra: yahoo
Requires-Dist: yfinance>=0.2.30; extra == 'yahoo'
Description-Content-Type: text/markdown

# volsurface

[![CI](https://github.com/cjpvanderwouden/volsurface/actions/workflows/ci.yml/badge.svg)](https://github.com/cjpvanderwouden/volsurface/actions/workflows/ci.yml)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Typed](https://img.shields.io/badge/typing-mypy%20strict-brightgreen.svg)](https://mypy-lang.org/)

A typed, extensible Python toolkit for implied volatility surface calibration. Fits Raw SVI per expiry or SSVI across the full surface, checks for arbitrage, estimates forwards from put-call parity, and renders publication-quality plots — all with `mypy --strict` compliance throughout.

## Features

- **Raw SVI parameterisation** — per-expiry L-BFGS-B calibration with data-scaled multi-start initial guesses
- **SSVI surface model** — globally consistent surface with shared (ρ, η, γ) parameters; free of calendar arbitrage by construction
- **Put-call parity forward estimation** — estimates the implied forward at each expiry from mid-prices, with robust median aggregation and sanity bounds
- **Arbitrage checks** — butterfly (convexity), calendar spread, and SVI parameter-level no-arbitrage conditions
- **Fit diagnostics** — per-slice and aggregate RMSE, max/mean absolute error, and side-by-side surface comparison tables
- **Yahoo Finance integration** — fetch, filter by liquidity, and clean live option chains in one call
- **Surface interpolation** — linear interpolation in total-variance space between fitted expiries
- **Visualisation** — 3D surface plots, heatmaps, and per-expiry smile overlays
- **Fully typed** — `mypy --strict` compliant with `py.typed` marker
- **Extensible** — implement `VolModel` to add any new parameterisation

## Installation

```bash
pip install volsurface            # core only
pip install volsurface[yahoo]     # + Yahoo Finance data
pip install volsurface[plot]      # + matplotlib plotting
pip install volsurface[all]       # everything
```

## Quick Start

### Fetch, fit, query

```python
from volsurface.market_data import fetch_chain
from volsurface.calibration import calibrate_surface
from volsurface.models import RawSVI
from volsurface.plotting import plot_surface

# Fetch SPY option chains and estimate forwards from put-call parity
slices = fetch_chain("SPY", min_volume=30, moneyness_range=(0.8, 1.2))

# Fit Raw SVI independently to each expiry
result = calibrate_surface(slices, RawSVI, ticker="SPY")
print(f"Fitted {result.surface.n_expiries} expiries")
print(f"Arbitrage clean: {result.arbitrage_report.is_clean}")

# Query any (strike, expiry) point — interpolates between fitted expiries
point = result.surface.iv(strike=570.0, expiry_years=0.5)
print(f"IV at K=570, T=0.5y: {point.iv:.4f}")

plot_surface(result.surface, kind="3d")
```

### Fit SSVI and compare

```python
from volsurface.models import SSVI
from volsurface.diagnostics import compare_surfaces
from volsurface.core import VolSurface

# Fit SSVI: global (rho, eta, gamma) calibrated across all expiries at once
ssvi = SSVI()
ssvi_slices, fit_result = ssvi.fit_surface(slices)
print(f"rho={ssvi.global_params.rho:.4f}  eta={ssvi.global_params.eta:.4f}")

# Assemble an SSVI VolSurface for querying and plotting
ssvi_surface = VolSurface(ticker="SPY (SSVI)")
for ms in slices:
    ssvi_surface.slices[ms.expiry_years] = ssvi_slices[ms.expiry_years]
    ssvi_surface.market_data[ms.expiry_years] = ms

comparison = compare_surfaces(result.surface, ssvi_surface, "Raw SVI", "SSVI")
print(comparison.summary_table())
```

## Working with Custom Data

```python
import numpy as np
from volsurface.market_data import clean_chain
from volsurface.models import RawSVI

strikes = np.array([90, 95, 100, 105, 110], dtype=float)
ivs = np.array([0.25, 0.22, 0.20, 0.21, 0.24])

slice_ = clean_chain(strikes, ivs, expiry_years=0.25, forward=100.0, spot=100.0)

model = RawSVI()
result = model.fit(slice_)
print(f"RMSE: {result.rmse:.6f}")
print(f"Params: {result.params}")
```

## Adding a New Model

Implement the `VolModel` abstract base class — three methods required:

```python
from volsurface.models.base import VolModel
from volsurface.core import FitResult, MarketSlice
import numpy as np

class MyModel(VolModel):
    @property
    def n_params(self) -> int:
        return 4

    def fit(self, market_slice: MarketSlice) -> FitResult:
        # calibrate to market_slice.log_moneyness, market_slice.total_variance
        ...

    def total_variance(self, log_moneyness):
        # return model total variance for given log-moneyness values
        ...
```

The fitted model plugs directly into `calibrate_surface`, `plot_smile`, and `diagnose_surface`.

## Documentation

Full documentation including the Theory & Models reference:
[https://cjpvanderwouden.github.io/volsurface/](https://cjpvanderwouden.github.io/volsurface/)

## Development

```bash
git clone https://github.com/cjpvanderwouden/volsurface.git
cd volsurface
pip install -e ".[dev]"

pytest                  # tests + coverage
ruff check src/ tests/  # linting
mypy --strict src/      # type checking
```

## License

MIT
