SURF Volatility Surfaces
The 3D map of market fear — and why Black-Scholes is visibly wrong here
Learning Objectives
- •Understand the volatility smile and skew
- •Learn how to construct and interpret volatility surfaces
- •Know why the surface shape contains tradeable information
Explain Like I'm 5
If Black-Scholes were actually correct, all options on the same stock would have the same implied vol. They don't. Not even close. Plotting implied vol against strike gives you a curve called the "volatility smile" — OTM puts and calls have HIGHER implied vol than ATM options. The smile is the market saying "we know returns have fat tails." It IS the market's correction to Black-Scholes' flawed assumptions.
Think of It This Way
The vol surface is like a topographic map of fear. High points (high IV) represent areas where the market is pricing in danger. Low points represent calm zones. The map changes shape constantly as sentiment shifts. After a crash the terrain becomes jagged and tall. During calm markets it flattens out.
1The Volatility Smile
2The Surface in 3D
3Trading the Vol Surface
4Vol Surface as an Information Signal
Equity Implied Volatility Smile (30-Day Options)
Key Formulas
Moneyness
Normalized measure of how far the strike is from spot. m=0 is ATM. |m|>2 is deep OTM. The smile is plotted against moneyness for consistency across assets.
Hands-On Code
Volatility Surface Construction
import numpy as np
def build_vol_surface(strikes, maturities, market_prices, S, r):
"""Build implied volatility surface from market prices."""
from scipy.stats import norm
def bs_price(S, K, T, r, sigma):
d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
return S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
def find_iv(price, S, K, T, r):
sigma = 0.20
for _ in range(50):
bs = bs_price(S, K, T, r, sigma)
d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))
vega = S * norm.pdf(d1) * np.sqrt(T)
if vega < 1e-10: break
sigma -= (bs - price) / vega
return sigma
surface = np.zeros((len(maturities), len(strikes)))
for i, T in enumerate(maturities):
for j, K in enumerate(strikes):
if market_prices[i][j] > 0:
surface[i][j] = find_iv(market_prices[i][j], S, K, T, r)
print(f"=== VOLATILITY SURFACE ===")
print(f"Strikes: {strikes}")
print(f"Maturities: {maturities}")
print(f"ATM vol (shortest): {surface[0][len(strikes)//2]:.2%}")
print(f"ATM vol (longest): {surface[-1][len(strikes)//2]:.2%}")
return surfaceBuilds an implied volatility surface from market option prices by computing IV for every strike-maturity combination via Newton's method.
Knowledge Check
Q1.Why do OTM puts on the S&P 500 have higher implied vol than ATM options?
Assignment
Build a volatility surface from option market data. Plot the smile at different maturities. Identify the skew direction and compare across asset types.