Black-Scholes: an options pricing primer

The Black-Scholes model prices European options under a few idealising assumptions: lognormal asset prices, no arbitrage, constant volatility, and continuous trading. It’s the baseline every quant reaches for first — not because it’s perfect, but because it’s the cleanest place to build intuition.

The formula

For a non-dividend-paying stock, the price of a European call is:

$$ C = S_0 N(d_1) - K e^{-rT} N(d_2) $$

where

$$ d_1 = \frac{\ln(S_0 / K) + (r + \sigma^2 / 2)T}{\sigma \sqrt{T}}, \qquad d_2 = d_1 - \sigma \sqrt{T} $$

Here $N(\cdot)$ is the standard normal CDF, $S_0$ the spot price, $K$ the strike, $r$ the risk-free rate, $T$ time to expiry (in years), and $\sigma$ the volatility.

The Greeks

The Greeks fall out of differentiating the price. The most important for hedging:

  • Delta $\Delta = N(d_1)$ — sensitivity to the underlying
  • Gamma $\Gamma = \frac{N’(d_1)}{S_0 \sigma \sqrt{T}}$ — rate of change of delta
  • Vega $\mathcal{V} = S_0 N’(d_1) \sqrt{T}$ — sensitivity to volatility

Black-Scholes assumes constant volatility. Real implied vol surfaces are anything but flat — the model is a first approximation, not ground truth.

Implementation

A vectorised NumPy implementation:

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)

Where to go next

The model’s real value is as a coordinate system for the options market: implied volatility, the vol surface, and the Greeks are all defined relative to it. Once that’s internalised, the interesting work — stochastic vol, local vol, jump models — builds naturally on top.