DEV Community

Amit Kumar Jha
Amit Kumar Jha

Posted on • Originally published at desk2quant.vercel.app

Black-Scholes Explained Simply — With Python Code

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:

CBS=S0N(d1)KerTN(d2) C_{BS} = S_0 \, N(d_1) - K e^{-rT} N(d_2)

where:

d1=ln(S0/K)+(r+σ2/2)TσT,d2=d1σT d_1 = \frac{\ln(S_0/K) + (r + \sigma^2/2)T}{\sigma\sqrt{T}}, \quad d_2 = d_1 - \sigma\sqrt{T}
  • 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:

P=CS0+KerT P = C - S_0 + K e^{-rT}

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}")
Enter fullscreen mode Exit fullscreen mode

Output:

Call Price: $10.4506
Put Price:  $5.5735
Enter fullscreen mode Exit fullscreen mode

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

Δcall=N(d1),Δput=N(d1)1 \Delta_{call} = N(d_1), \quad \Delta_{put} = N(d_1) - 1

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

Γ=N(d1)S0σT \Gamma = \frac{N'(d_1)}{S_0 \sigma \sqrt{T}}

Gamma is the rate of change of delta. High gamma = the option's delta changes rapidly = more convexity = more valuable.

Vega

ν=S0TN(d1) \nu = S_0 \sqrt{T} \, N'(d_1)

Vega is the sensitivity to volatility. This is the most important Greek for vol traders.

Theta

Θcall=S0N(d1)σ2TrKerTN(d2) \Theta_{call} = -\frac{S_0 N'(d_1) \sigma}{2\sqrt{T}} - r K e^{-rT} N(d_2)

Theta is time decay. Options lose value as expiration approaches.

Rho

ρcall=KTerTN(d2) \rho_{call} = K T e^{-rT} N(d_2)

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}")
Enter fullscreen mode Exit fullscreen mode

Output:

 delta: 0.6368
 gamma: 0.0188
  vega: 0.3752
 theta: -0.0176
   rho: 0.5323
Enter fullscreen mode Exit fullscreen mode

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}%")
Enter fullscreen mode Exit fullscreen mode

Output:

Implied Volatility: 20.15%
Enter fullscreen mode Exit fullscreen mode

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)