voltorch
Differentiable option pricing and volatility surfaces in PyTorch.
Every model is an nn.Module. Every price is differentiable with respect to the
spot, the strike, the maturity and the model parameters. So:
- Greeks come from autograd, not from bumping inputs and subtracting.
- Calibration is gradient descent, not a derivative-free optimiser crawling a five-dimensional surface.
- A pricer can sit inside a network and receive gradients through it.
pip install voltorch
0.2: arbitrage-free surfaces from a live chain
from voltorch import fit_chain
from voltorch.deribit import fetch_chain # no key; Deribit public API
report = fit_chain(fetch_chain("BTC"), currency="BTC")
report.refined["rmse_vol_pts"], report.refined["inside_bid_ask_share"]
report.venue_violations["executable"] # butterflies/verticals/calendars you could trade
report.our_violations # must be zero; Durrleman g and calendar Δw
report.greeks_max_abs_err # autograd vs closed-form Black-76
Live, refit every 30 minutes: https://savabs.github.io/voltorch/
ESSVI— extended SSVI (Hendriks–Martini 2019): per-expiry (θ, ρ, ψ) with the butterfly and calendar conditions enforced by the parameterisation, so the surface is arbitrage-free by construction at every optimiser step.SVISlice— per-expiry raw SVI refinement, arbitrage-checked on a dense grid (Durrleman g ≥ 0, calendar against neighbours) with fallback to the backbone.arbitrage—durrleman_g,butterfly_violations,vertical_violations,calendar_violations, andexecutable_violations(against bids and asks).implied_volatility_bisect— bracketed bisection for market quotes; it cannot stall where Newton does (a documented Deribit case is in the tests).deribit.fetch_chain— coin prices convert to USD on the forward (price × F = Black-76), verified against the venue's marks to 1e-4.
Install: pip install "voltorch[page]" for the loader and the page renderer.
Greeks, without finite differences
import torch
from voltorch import BlackScholes
S = torch.tensor(100.0, requires_grad=True)
K, T, r, sigma = (torch.tensor(x) for x in (100.0, 1.0, 0.05, 0.20))
price = BlackScholes()(S, K, T, r, sigma)
delta, = torch.autograd.grad(price, S, create_graph=True)
gamma, = torch.autograd.grad(delta, S)
print(f"price {price.item():.4f} delta {delta.item():.4f} gamma {gamma.item():.4f}")
Second-order greeks are a second grad call. No bump size to choose, and no
cancellation error from choosing it badly.
Calibrating Heston by gradient descent
learnable=True registers the model parameters as nn.Parameter, so the whole
of torch.optim applies:
import torch
from voltorch import HestonCOS
model = HestonCOS(kappa=1.0, theta=0.10, xi=0.20, rho=-0.20, v0=0.10,
learnable=True)
opt = torch.optim.Adam(model.parameters(), lr=2e-2)
S = torch.full((16,), 100.0)
K = torch.linspace(75.0, 125.0, 16)
T = torch.full((16,), 1.0)
r = torch.full((16,), 0.02)
for _ in range(600):
opt.zero_grad()
loss = torch.nn.functional.mse_loss(model(S, K, T, r), market_prices)
loss.backward()
opt.step()
The gradient flows from the loss, back through the Fourier-COS expansion,
through a complex-valued characteristic function with a branch cut in it, and
into kappa, theta, xi, rho and v0.
examples/calibrate_heston.py runs this against prices from a known Heston and
shows something a fit-quality number hides. On one maturity the recovered prices
are near-exact (rmse 0.0036) and the parameters are wrong — kappa comes back
1.04 against a true 2.5, because on a single smile kappa and xi trade off
against each other. Across five maturities the rmse is worse (0.013) and the
parameters are right (kappa 2.07, rho −0.649, v0 0.0493), because the
speed of mean reversion is a statement about the term structure. Fit quality and
identification are different things.
What is in it
| European | BlackScholes — closed form, vectorised, with greeks by autograd |
| American | BaroneAdesiWhaley — quadratic approximation for early exercise |
| Stochastic vol | HestonCOS |
| Jumps | BatesCOS (Heston + jumps), MertonCOS (lognormal jumps) |
| Pure jump | VarianceGammaCOS |
| Surfaces | SVIParameterization, SABRModel, ImpliedVolatilitySurface |
| Rough vol | RoughBergomiModel, estimate_hurst_exponent |
| Inversion | implied_volatility |
| Paths (extra) | voltorch.sde — GBM, HestonSDE via torchsde |
All Fourier models share the FourierCOS base (Fang & Oosterlee), so a new
model is a characteristic function and its cumulants — the expansion, the
truncation interval and the payoff coefficients are inherited.
The part that is actually hard
The formulae are in the papers. What is not in the papers is the short list of numerical failures that silently poison a direct implementation. Each is guarded here, at its site, with the symptom it prevents:
- The complex logarithm's branch cut in Heston and Bates. The principal branch flips sign partway along the integration, and the price is wrong in a way that looks like a modest calibration error. Uses Albrecher's Little Trap formulation, which keeps the integrand continuous — without it, gradients are NaN rather than merely wrong.
- The
z / χ(z)singularity in Hagan's SABR asK → F. It is0/0, finite in the limit and NaN in floating point, and it lands exactly at-the-money — the strike you care about most. Guarded by a Taylor expansion below|z| < 1e-4. - Martingale drift correction. The
-½σ²Tdiffusion term and the-λκTjump compensator must both appear in the risk-neutral characteristic function. Omit either and prices drift away from the forward as maturity grows, which reads as a term-structure effect and is a bug. - Interpolating total variance, not volatility.
w(k,T) = σ²Tis what is linear in time; interpolating raw implied vol admits calendar arbitrage between the pillars you interpolated from. - Durrleman's condition on the SVI slice, so a fitted smile is butterfly arbitrage-free rather than merely close to the quotes.
Each of these was found by having the gradients go NaN and working backwards.
Tested against limits, not against itself
39 tests. Heston, Bates, Merton and Variance Gamma each converge to the Black-Scholes price as their extra parameters go to zero — a check that fails loudly if a characteristic function or a cumulant is wrong, unlike a regression test against a number the same code produced yesterday.
The rough Bergomi model reproduces the empirical power-law explosion of the
at-the-money skew, ψ(T) ∝ T^(H-1/2): at H = 0.07 the skew at T = 0.1 is
~11× the skew at T = 0.5. That is the signature rough volatility exists to
explain, and it is a real check rather than a smoke test.
pip install voltorch[dev] && pytest
Honest limitations
- Single-asset, European exercise for the Fourier models. No basket, no
American under stochastic volatility (
BaroneAdesiWhaleyis Black-Scholes dynamics). float32throughout, matching PyTorch's default. Deep out-of-the-money prices over long maturities will show it; cast tofloat64if that matters to you.- The COS method assumes the characteristic function is known in closed form. It is not a general PDE or Monte Carlo engine.
RoughBergomiModelsimulates; it does not admit a closed-form characteristic function, so it does not price throughFourierCOS.- Not a risk system. There is no calendar, no day count convention, no settlement, no market data. It prices and it differentiates.
Licence
Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file voltorch-0.2.0.tar.gz.
File metadata
- Download URL: voltorch-0.2.0.tar.gz
- Upload date:
- Size: 34.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92d11e56560a049e1cfb9751608111428a0c52f67337caa98c4065e198e0fbe0
|
|
| MD5 |
fb014a6e3a421eed39e71e2895e1cff4
|
|
| BLAKE2b-256 |
12a0d43ca2bc5fdc1dcd178c06eed9c69ea65aa54b7b3b0154c5345947730a4f
|
File details
Details for the file voltorch-0.2.0-py3-none-any.whl.
File metadata
- Download URL: voltorch-0.2.0-py3-none-any.whl
- Upload date:
- Size: 38.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
caaa9b00b45a9909188e9f0613c4c59d17d9dce90f7c5619aaad30e9465236d1
|
|
| MD5 |
f1ba86b7d38462a730419fb606c4b9d9
|
|
| BLAKE2b-256 |
60ccf082da6848952be8355bf990515f9af4b30f30f4a4de9b50222c0b9e1fc4
|