An interactive 3D implied‑volatility surface visualizer for any US‑listed equity, built as a standalone page for vikalpthukral.com/volatility-surface.
The frontend is a single self‑contained index.html (Tailwind + Plotly via CDN). The backend is a tiny FastAPI service that fetches options chains from Yahoo Finance via yfinance, cleans them, and caches them in memory.
- 3D surface plot of implied volatility across strike × days‑to‑expiration
- Bilinear resampling + Gaussian blur for adjustable surface smoothing (0–5)
- 4 Z‑axis metrics on the same mesh: Implied Vol, Total Variance (σ²·T), Volume, Open Interest
- 4 camera presets: ISO, TOP (heatmap), SKEW (front view of smile), TERM (side view of term structure)
- Side‑by‑side mini‑charts:
- ATM Term Structure (IV vs DTE for the strike closest to spot)
- Volatility Skew slice for any selected expiry, with spot‑price marker
- Dark / Light theme with automatic OS preference detection (
prefers-color-scheme) andlocalStoragepersistence - 10 color palettes: Cyan, Blue, Pink, Orange, Green, Amber (custom neon ramps) + Plasma, Viridis, Inferno, Turbo (Plotly built‑ins). Selected palette re‑themes the UI accent and both mini‑charts.
- Glassmorphism control panels with backdrop blur
- Quick‑select chips for common DTE ranges (
0‑30d,30‑60d,60‑90d,90‑180d,180d+,ALL) and moneyness windows (±5%,±10%,±20%,±35%,±50%) - Live spot price, contract count, and freshness indicator in the header
- Two‑tier caching: 10 minutes for options chains, 1 hour for the risk‑free rate, all in memory
- Yahoo rate‑limit bypass via
curl_cffiChrome browser impersonation - Cleaned chain: NaN IVs dropped, zero‑volume / zero‑OI contracts removed, IVs clamped to (0.01, 5.0) to kill illiquid noise
- Pre‑computed bid/ask mid in every contract row (ready for v3 Greeks)
- Live risk‑free rate from
^IRX(13‑week US T‑bill), with manual override - Dividend yield included in the payload
- PNG export at 1920×1080 of the current view
- CSV export of the cleaned options chain with bid/ask/mid
volatility-surface/
├── main.py # FastAPI backend (yfinance + caching)
├── requirements.txt # Python dependencies
├── Dockerfile # Production container image
├── fly.toml # Fly.io app configuration
├── .dockerignore # Files excluded from the Docker build context
├── .gitignore # Files excluded from git
├── index.html # Standalone frontend (single file, drop into GH Pages)
├── README.md # This file
└── DEPLOY.md # Step‑by‑step deployment guide
| Layer | Choice | Why |
|---|---|---|
| Frontend | Vanilla HTML + Tailwind (CDN) + Plotly.js (CDN) | Zero build step, single file, ships to any static host. |
| 3D rendering | Plotly.js surface trace |
Best‑in‑class 3D surface support out of the box. |
| Backend | FastAPI + Uvicorn | Async, tiny, fast, automatic OpenAPI. |
| Data source | yfinance + curl_cffi | Free Yahoo Finance options chains. curl_cffi impersonates Chrome to bypass anti‑bot rate limiting. |
| Caching | In‑memory dict with TTL | Simple, no external dependency, perfect for a single long‑lived process. |
| Production runtime | Docker on Fly.io | Always‑on, scale‑to‑zero, free SSL, custom domains, ~$0–3/month for this workload. |
| Method | Path | Description | TTL |
|---|---|---|---|
GET |
/ |
Health check | — |
GET |
/api/options/{ticker} |
Cleaned options chain (calls + puts) for the given ticker | 10 min |
GET |
/api/risk-free-rate |
Latest 13‑week T‑bill yield from ^IRX |
1 hour |
{
"cached": false,
"ticker": "SPY",
"spot": 658.93,
"dividendYield": 0.0106,
"fetched_at": "2026-04-07T00:10:06.923157+00:00",
"calls": [
{
"strike": 650.0,
"expiration": "2026-04-17",
"dte": 11,
"iv": 0.1542,
"volume": 12834,
"openInterest": 23410,
"bid": 12.30,
"ask": 12.45,
"mid": 12.375
}
],
"puts": [...]
}- Python 3.10+ (tested with 3.12)
pip- (Optional) Node-style live server, or any HTTP server, for the frontend
cd volatility-surface
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -r requirements.txt
python main.pyThe API is now live at http://127.0.0.1:8000. Test it:
http://127.0.0.1:8000/
http://127.0.0.1:8000/api/options/SPY
http://127.0.0.1:8000/api/risk-free-rate
The frontend cannot be opened directly via file:// because it makes fetch() calls. Serve it over HTTP from a second terminal:
cd volatility-surface
python -m http.server 5500Then open http://localhost:5500/.
The frontend auto‑detects localhost and points at http://127.0.0.1:8000 automatically — no config needed for local dev.
| Variable | Default | Purpose |
|---|---|---|
HOST |
127.0.0.1 (local), 0.0.0.0 (Docker) |
Bind address for uvicorn |
PORT |
8000 (local), 8080 (Docker) |
Bind port for uvicorn |
ALLOWED_ORIGINS |
http://localhost:5500,http://127.0.0.1:5500 |
Comma‑separated CORS allowlist. Set to * for "allow all" (not recommended in production). |
The frontend resolves its API base in this order:
<meta name="api-base" content="...">in the HTML<head>. If you set this to a non‑empty value, it wins.- If running on
localhost/127.0.0.1, defaults tohttp://127.0.0.1:8000. - Otherwise, defaults to
https://api.vikalpthukral.com.
For your own deployment, change the meta tag (recommended) or the fallback URL inside the <script> block:
<meta name="api-base" content="https://api.yourdomain.com" />See DEPLOY.md for the full step‑by‑step guide covering:
- Splitting the project into two repos (frontend on GitHub Pages, backend on Fly.io)
- Building and deploying the Docker image
- Configuring a custom subdomain (
api.vikalpthukral.com) with auto‑provisioned SSL - Locking down CORS to your production origin
- Optional: GitHub Actions workflow for auto‑deploy on push
- Black‑Scholes Greeks surfaces (Δ, Γ, ν, Θ) — backend already returns mid + dividend + risk‑free rate
- SVI / SSVI parametric surface fit
- Earnings & dividend markers on the DTE axis
- Compare mode (two tickers side‑by‑side)
- Shareable URL with state encoded in query params
- 30‑day constant‑maturity IV (VIX‑style interpolation)
- Implied move calculator (front‑month straddle → expected move)
MIT. Data via Yahoo Finance — for educational and research use only. This project is not affiliated with, endorsed by, or sponsored by Yahoo, Verizon Media, or any exchange.
Built by Vikalp Thukral — vikalpthukral.com