CodeForFinance
← Back to Quant Concepts

Volatility Smiles and Surfaces

Why Black-Scholes assumes constant volatility, why reality disagrees, and how quants model the difference.

The Constant Volatility Assumption

Black-Scholes assumes one single volatility number applies to all options on a given stock, regardless of strike price or expiry. If this were true, you could plug in any option's market price, solve for the implied volatility, and get the same number every time. Try this with real market data and you will quickly discover it does not work.

Different strikes and different expiries produce different implied volatilities. This is not a failure of the extraction method — it is a failure of the Black-Scholes assumption. The market is telling you that it does not believe returns are log-normally distributed with constant volatility.

Implied Volatility: Reverse-Engineering

Implied volatility (IV) is the volatility you must plug into Black-Scholes to match the observed market price. It is found numerically: there is no closed-form inverse of Black-Scholes. We use Brent's root-finding method to solve BS(σ) = Market Price for σ.

from scipy.optimize import brentq
from scipy.stats import norm
import numpy as np

def black_scholes_call(S, K, T, r, sigma):
    d1 = (np.log(S / K) + (r + 0.5 * sigma**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 implied_vol(market_price, S, K, T, r):
    """
    Extract implied volatility from market price.
    Uses Brent's method to solve: BS(sigma) = market_price
    """
    def objective(sigma):
        return black_scholes_call(S, K, T, r, sigma) - market_price

    try:
        iv = brentq(objective, 0.001, 5.0)
        return iv
    except ValueError:
        return None

# Example: extract IV from a market price
S = 100
K = 100
T = 0.25
r = 0.05
market_price = 5.50  # Observed market price

iv = implied_vol(market_price, S, K, T, r)
print(f"Implied Volatility: {iv:.2%}")

The Volatility Smile

Plot IV against strike price for a fixed expiry and you see a characteristic pattern. For equity indices, out-of-the-money puts have much higher IV than at-the-money options. This is called the volatility skew. For FX options, both OTM puts and OTM calls have elevated IV, forming a symmetric smile.

def build_smile(S, T, r, strikes, market_prices):
    """Build a volatility smile from market option prices."""
    ivs = []
    for K, price in zip(strikes, market_prices):
        iv = implied_vol(price, S, K, T, r)
        ivs.append(iv)
        moneyness = K / S
        print(f"  K={K:>6.0f}  K/S={moneyness:.2f}  Price={price:>6.2f}  IV={iv:.2%}")
    return ivs

# Simulated market data showing a smile/skew
S = 100
strikes =       [80,    85,    90,    95,    100,   105,   110,   115,   120]
market_prices = [21.50, 17.20, 13.10, 9.50,  6.40,  4.00,  2.30,  1.20,  0.55]

print("Volatility Smile:")
ivs = build_smile(S, 0.25, 0.05, strikes, market_prices)

Why the Skew Exists

Crash protection. After the 1987 Black Monday crash (when the S&P 500 dropped 22% in one day), market participants permanently repriced downside risk. OTM puts are essentially crash insurance, and like any insurance, they cost more when the risk feels real. The market prices in fat tails that Black-Scholes does not model.

Supply and demand. Portfolio managers constantly buy puts to hedge their equity portfolios. This persistent demand pushes up put prices and their implied volatilities. There is no equivalent systematic demand for OTM calls.

The Volatility Surface

The full picture is a 3D surface: implied volatility as a function of both strike price and time to expiry. Short-dated options typically show a steeper skew than long-dated options (the smile flattens with time). This surface encodes the market's complete view of risk.

def build_surface(S, r, strikes, expiries, price_matrix):
    """
    Build a volatility surface: IV across strikes AND expiries.
    price_matrix[i][j] = market price for expiry i, strike j
    """
    surface = []
    for i, T in enumerate(expiries):
        row = []
        for j, K in enumerate(strikes):
            iv = implied_vol(price_matrix[i][j], S, K, T, r)
            row.append(iv)
        surface.append(row)

    # Print as table
    header = "Expiry  | " + " | ".join(f"K={k}" for k in strikes)
    print(header)
    print("-" * len(header))
    for i, T in enumerate(expiries):
        vals = " | ".join(f"{v:.1%}" if v else "  N/A" for v in surface[i])
        print(f"{T:>5.2f}y  | {vals}")

    return surface

# Example surface data
strikes = [90, 95, 100, 105, 110]
expiries = [0.08, 0.25, 0.50, 1.00]  # 1M, 3M, 6M, 1Y

prices = [
    [11.00, 7.10, 3.90, 1.60, 0.45],   # 1 month
    [13.10, 9.50, 6.40, 4.00, 2.30],   # 3 months
    [15.80, 12.30, 9.20, 6.70, 4.60],  # 6 months
    [19.50, 16.20, 13.20, 10.60, 8.30], # 1 year
]

print("
Volatility Surface:")
surface = build_surface(100, 0.05, strikes, expiries, prices)

Key Surface Features

Skew: IV(OTM put) > IV(ATM) > IV(OTM call) for equities

Term structure: IV can be upward or downward sloping with time

Smile: symmetric U-shape common in FX markets

Smirk: asymmetric skew typical of equity index options

Why This Matters for Trading

Exotic options desks at banks like Barclays, HSBC, and Goldman Sachs in London need to price options that are not liquid in the market. They calibrate models (local vol, stochastic vol, SABR) to match the observed volatility surface, then use those models to price bespoke derivatives. If your model does not match the surface, your prices will be wrong and you will either overpay or underprice risk.

For systematic traders, changes in the volatility surface signal shifts in market sentiment. A steepening skew means increased demand for downside protection. A collapsing term structure means the market expects near-term turmoil to resolve quickly.

Interview Warning

Never say "the volatility smile is a failure of Black-Scholes." It is a feature, not a bug. Black-Scholes is a quoting convention — a common language for expressing option prices in terms of volatility. Everyone knows the assumptions are violated. The model is used precisely because it allows traders to communicate by quoting a single number (IV) rather than a raw price.

Developer Essentials

As an Amazon Associate we may earn from qualifying purchases.