Skip to content

Repository files navigation

Local Volatility — Dupire Construction & Exotic Pricing

Building the unique local volatility surface implied by SPY's option market, and measuring what flat-vol pricing costs on a barrier option: 32 basis points of spot.

the interface

Live demo: https://codeebytee.github.io/04-local-vol-dupire/ (enable Pages: Settings → Pages → main /docs)

Run the interface locally: clone the repo and double-click docs/index.html. No install, no server, no internet connection needed — the page ships its own copy of Plotly and computes everything in the browser. requirements.txt is only for re-running the research.

What this does

  • Builds a local volatility surface from a real SPY option chain via Dupire's equation, in both the implied-vol form (used) and the call-price form (implemented in order to measure why nobody uses it).
  • Prices a barrier option under that surface with a Rannacher-started Crank–Nicolson PDE and an independent Brownian-bridge Monte Carlo, and against three flat-volatility conventions a desk might actually use.
  • Quantifies the smile effect on exotics in basis points — the number a trader would act on, rather than a picture of two surfaces side by side.

The headline

One-year down-and-out call on SPY, struck at spot, barrier 10% below (chain as of 2026-08-05, spot 771.33):

Method Price vs local vol
Local volatility (Crank–Nicolson PDE) 53.897
Local volatility (Monte Carlo, 200k paths) 53.943 ± 0.130 +0.35 se
Flat vol @ ATM — the usual shortcut 56.351 −31.8 bp of spot (−4.36%)
Flat vol @ strike 57.570 −47.6 bp (−6.38%)
Flat vol @ barrier 61.049 −92.7 bp (−11.72%)

Every flat-vol convention overprices the contract, by between 32 and 93bp of spot against a bid-offer of perhaps 20–40bp. Under a negative skew, volatility rises as spot falls, so paths heading toward the barrier are more volatile than a flat pricer assumes, they knock out more often, and the option is worth less.

Two other measured results:

  • Local vol is 2.0–2.1× as steep in log-moneyness as the implied vol it came from, at 3, 9 and 18 months. The textbook rule of thumb, reproduced from a real chain.
  • Smoothing cannot rescue a raw surface. Across five orders of magnitude of smoothing parameter, the fraction of grid points where local vol is undefined bottoms out at 2.1% and never reaches zero — the smoother just trades butterfly violations for calendar ones.

Install and run

pip install -r requirements.txt
python scripts/make_results.py      # the whole study -> results/  (~2.5 min)
python scripts/build_frontend.py    # results/ -> docs/data.js

pytest runs the 27-test suite in about a minute. python scripts/check_page.py drives the finished page in headless Chrome from file:// and cross-checks its JavaScript against the Python library.

Repo map

docs/index.html          the interface — single file, opens offline, no build step
docs/data.js             generated by scripts/build_frontend.py, never hand-edited
src/models/dupire.py     Dupire in both forms; start here
src/models/ssvi.py       the arbitrage-free implied surface it differentiates
src/models/pde.py        Crank–Nicolson, Rannacher start, barrier on a grid node
src/models/mc.py         Brownian-bridge Monte Carlo
src/models/smoothing.py  the lambda scan — the case against regularisation
src/models/validation.py fixed point, round trip, convergence, closed forms
scripts/make_results.py  runs everything, writes results/local_vol_results.json
scripts/refresh_chain.py refetches data/chain_snapshot.json (needs yfinance)
notebooks/               the story in four figures
tests/                   27 tests: the surface, then the engines
config.yaml              every tunable number in the project

New to options?PREREQUISITES.md, written for a software engineer with no finance background.

Want the equations and the validation tables?DEEP_DIVE.md.

Design decisions

  • Fit worse on purpose. Per-slice SVI fits the quotes to 1.25 vol points with 45 parameters; SSVI manages only 3.86 with 12. SSVI wins anyway, because the raw chain contains 649 butterfly arbitrage violations and a closer fit inherits them. Dupire's denominator is the no-butterfly condition, so an arbitraged input surface produces a local vol that does not exist. Zero of 6,305 grid points are invalid under SSVI.
  • The browser runs the model, not a slideshow. Because SSVI is closed form, so are its derivatives, so the entire local vol surface is analytic apart from one 1-D spline. Every slider move rebuilds the surface from scratch and re-solves a 241 × 200 barrier PDE in JavaScript. Only the things with an optimiser in them — the calibration, the λ-scan, the validation tables — are precomputed, and each such panel says so on the page.
  • Broken points stay visibly broken. Where the local vol is undefined the code returns NaN and counts it, rather than clipping it into a plausible number. A floored local vol reaches a PDE solver silently; a hole in the surface is a diagnosis. The interface has a "break it" preset that pushes the input surface until the local vol stops existing, and shows you where.

Validation

Check Result
Flat surface in ⟹ flat surface out exact to machine precision (7,381 points)
Forward-PDE round trip: IV → local vol → prices → IV 6.98 bp of vol RMSE
PDE and MC vs Reiner–Rubinstein closed form PDE within 0.05 bp of spot; MC within 1.2 se
PDE vs MC on the real surface (no closed form) 0.35 se apart
PDE convergence order 1.96 (theory: 2)
Browser JS vs Python library local variance 2.8e-15; barrier PDE 6.0e-6 relative

Data and honesty notes

Exchange-delayed SPY quotes from yfinance, snapshotted 2026-08-05 and committed to data/ so the repo reproduces without a network call. The as-of date is displayed on the page. Quotes are filtered for two-sided markets, relative spread, and moneyness before anything is fitted; the page shows the funnel. If no snapshot and no network are available, the code falls back to a fixed-seed synthetic chain that is labelled as synthetic wherever it appears. All barrier prices assume continuous monitoring unless the monitoring control says otherwise; the discrete-monitoring correction is worth +15.0 bp of spot on the base contract and is reported rather than buried.

License

MIT — see LICENSE.

About

Dupire local volatility from a real SPY surface, and what flat-vol pricing costs on a barrier option: 32bp of spot. Live in-browser PDE.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages