Black-Scholes Explained Simply — With Python Code
The Black-Scholes model is the foundation of modern option pricing. Published in 1973 by Fischer Black, Myron Scholes, and Robert Merton, it revolutionized finance by providing the first closed-form formula for European option prices. Every quant interview starts here.
This guide breaks down the model from first principles, implements it in Python, covers the Greeks, and explains where and why it fails.
The Black-Scholes Formula
Under the risk-neutral measure, a European call option price is:
where:
- S₀ — current stock price
- K — strike price
- r — risk-free rate
- σ — volatility (annualized)
- T — time to expiration (years)
- N(·) — cumulative standard normal distribution
For a European put, use put-call parity:
The Five Assumptions
| Assumption | Reality | Impact |
|---|---|---|
| Constant volatility | Vol varies by strike/time (smile) | Model underprices OTM options |
| Log-normal returns | Returns have fat tails | Underestimates crash risk |
| No dividends | Most stocks pay dividends | Overprices calls on dividend stocks |
| No transaction costs | Spreads exist | Real hedging costs more |
| Continuous trading | Markets close, gaps happen | Overnight risk unmodeled |
Understanding these assumptions is critical for interviews. When a trader says "the model is wrong," they mean one of these is violated.
Python Implementation
import numpy as np
from scipy.stats import norm
def bs_call(S, K, r, T, sigma):
"""Black-Scholes European call price."""
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 bs_put(S, K, r, T, sigma):
"""Black-Scholes European put price."""
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
return K * np.exp(-r*T) * norm.cdf(-d2) - S * norm.cdf(-d1)
# Price an ATM call
S0, K, r, sigma, T = 100, 100, 0.05, 0.20, 1.0
call_price = bs_call(S0, K, r, T, sigma)
put_price = bs_put(S0, K, r, T, sigma)
print(f"Call Price: ${call_price:.4f}")
print(f"Put Price: ${put_price:.4f}")
Output:
Call Price: $10.4506
Put Price: $5.5735
The Greeks: Risk Sensitivities
The Greeks tell traders how the option price changes when inputs move. This is what you actually use on the desk.
Delta
Delta is the sensitivity to the underlying price. A delta of 0.63 means the option price moves $0.63 for every $1 move in the stock.
Gamma
Gamma is the rate of change of delta. High gamma = the option's delta changes rapidly = more convexity = more valuable.
Vega
Vega is the sensitivity to volatility. This is the most important Greek for vol traders.
Theta
Theta is time decay. Options lose value as expiration approaches.
Rho
Rho is the sensitivity to interest rates.
Python: Greeks Calculator
def bs_greeks(S, K, r, T, sigma):
"""Compute all Greeks for a European call."""
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
delta = norm.cdf(d1)
gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))
vega = S * norm.pdf(d1) * np.sqrt(T) / 100
theta = (-(S * norm.pdf(d1) * sigma) / (2*np.sqrt(T))
- r * K * np.exp(-r*T) * norm.cdf(d2)) / 365
rho = K * T * np.exp(-r*T) * norm.cdf(d2) / 100
return {'delta': delta, 'gamma': gamma, 'vega': vega,
'theta': theta, 'rho': rho}
greeks = bs_greeks(100, 100, 0.05, 1.0, 0.20)
for name, val in greeks.items():
print(f"{name:>6s}: {val:.4f}")
Output:
delta: 0.6368
gamma: 0.0188
vega: 0.3752
theta: -0.0176
rho: 0.5323
Implied Volatility
Given a market price, we can back out the volatility that makes Black-Scholes match that price. This is implied volatility.
from scipy.optimize import brentq
def implied_vol(price, S, K, r, T):
try:
return brentq(
lambda sigma: bs_call(S, K, r, T, sigma) - price,
0.01, 3.0
)
except:
return np.nan
market_price = 10.50
iv = implied_vol(market_price, 100, 100, 0.05, 1.0)
print(f"Implied Volatility: {iv*100:.2f}%")
Output:
Implied Volatility: 20.15%
If you plot implied volatility across different strikes, you get the volatility smile. This is the single biggest evidence that Black-Scholes is wrong.
Where Black-Scholes Fails
1. The Volatility Smile
If Black-Scholes were correct, implied volatility would be constant across all strikes. It isn't. OTM puts trade at higher implied vol — the skew. This is why quants use Heston, local vol, or jump-diffusion models.
2. Fat Tails
Real returns have kurtosis > 3. The 1987 crash, 2008 crisis, and 2020 COVID crash were all "impossible" under log-normal assumptions.
3. Path Dependence
Black-Scholes prices European options only. Asian, barrier, and lookback options require Monte Carlo or PDE methods.
Interview-Ready Summary
| Concept | Formula/Key Point | Interview Question |
|---|---|---|
| BS Call | S·N(d₁) − Ke⁻ʳᵀ·N(d₂) | "Derive BS from risk-neutral pricing" |
| Delta | N(d₁) | "What's the delta of an ATM call?" |
| Gamma | N'(d₁)/(S·σ·√T) | "Why is gamma highest ATM?" |
| Vega | S·√T·N'(d₁) | "Why does vega increase with T?" |
| Put-Call Parity | P = C − S + Ke⁻ʳᵀ | "Does put-call parity hold for Americans?" |
| Implied Vol | σ such that C_BS(σ) = C_market | "What does the vol smile tell us?" |
| Key Limitation | Constant vol assumption | "Why use Heston instead of BS?" |
Further Reading
- Hull, J. Options, Futures, and Other Derivatives — Chapter 13-15
- Wilmott, P. Paul Wilmott on Quantitative Finance
- Shreve, S. Stochastic Calculus for Finance II
This post originally appeared on Desk2Quant. For deeper coverage, see The Stochastic Calculus Visual Lab and Quant Interview Problem Book.
Top comments (0)