CodeForFinance
← Back to Python Tutorials

Black-Scholes Option Pricing in Python

The foundational model every quant must know. Build it from scratch in Python using scipy.

What Black-Scholes Calculates

The Black-Scholes model calculates the fair theoretical price of a European-style option. Published by Fischer Black and Myron Scholes in 1973, it remains the most important formula in quantitative finance. Every options desk on every trading floor in the City of London uses it as a starting point.

European options can only be exercised at expiry (unlike American options which can be exercised anytime). The model tells you what a call or put option should cost given five inputs, assuming markets behave according to geometric Brownian motion with constant volatility.

The genius of Black-Scholes is that the option price depends on volatility but not on the expected return of the stock. This is because the model uses risk-neutral pricing — it constructs a hedge portfolio that eliminates risk, making the expected return irrelevant.

The Five Inputs

SStock Price

Current market price of the underlying asset

KStrike Price

The price at which the option can be exercised

TTime to Expiry

Years remaining until the option expires

rRisk-Free Rate

Annual risk-free interest rate (e.g. UK gilt yield)

σVolatility

Annualised standard deviation of stock returns

The Black-Scholes Formula

C = S·N(d1) - K·e^(-rT)·N(d2)

P = K·e^(-rT)·N(-d2) - S·N(-d1)

d1 = [ln(S/K) + (r + σ²/2)T] / (σ√T)

d2 = d1 - σ√T

N(x) = cumulative standard normal distribution

Python Implementation

The implementation is surprisingly concise. We use scipy.stats.norm for the cumulative normal distribution function N(x). The entire pricer fits in under 30 lines.

import numpy as np
from scipy.stats import norm

def black_scholes(S, K, T, r, sigma, option_type="call"):
    """
    Calculate Black-Scholes option price.
    S: Current stock price
    K: Strike price
    T: Time to expiration (years)
    r: Risk-free rate (annual)
    sigma: Volatility (annual)
    """
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)

    if option_type == "call":
        price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
    else:
        price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)

    return price

# Example: FTSE 100 stock
S = 100    # Current price
K = 105    # Strike price
T = 0.5    # 6 months to expiry
r = 0.05   # 5% risk-free rate
sigma = 0.2  # 20% annual volatility

call_price = black_scholes(S, K, T, r, sigma, "call")
put_price = black_scholes(S, K, T, r, sigma, "put")

print(f"Call Price: {call_price:.4f}")
print(f"Put Price:  {put_price:.4f}")

With a stock at 100, strike 105, 6 months to expiry, 5% rate and 20% vol, the call is worth about 3.77 and the put about 6.17. The put is more expensive because the strike is above the current price — the call is out-of-the-money while the put is in-the-money.

Calculating the Greeks

The Greeks measure how sensitive the option price is to changes in each input. They are partial derivatives of the Black-Scholes formula. Traders use them daily to manage risk. Understanding Greeks is essential for any quant role in London.

def greeks(S, K, T, r, sigma):
    """Calculate all option Greeks."""
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)

    # Delta: dPrice/dS
    delta_call = norm.cdf(d1)
    delta_put = delta_call - 1

    # Gamma: d2Price/dS2 (same for calls and puts)
    gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))

    # Vega: dPrice/dSigma (per 1% move)
    vega = S * norm.pdf(d1) * np.sqrt(T) / 100

    # Theta: dPrice/dT (per day)
    theta_call = (-(S * norm.pdf(d1) * sigma) / (2 * np.sqrt(T))
                  - r * K * np.exp(-r * T) * norm.cdf(d2)) / 365
    theta_put = (-(S * norm.pdf(d1) * sigma) / (2 * np.sqrt(T))
                 + r * K * np.exp(-r * T) * norm.cdf(-d2)) / 365

    # Rho: dPrice/dr (per 1% move)
    rho_call = K * T * np.exp(-r * T) * norm.cdf(d2) / 100
    rho_put = -K * T * np.exp(-r * T) * norm.cdf(-d2) / 100

    return {
        "delta_call": delta_call, "delta_put": delta_put,
        "gamma": gamma, "vega": vega,
        "theta_call": theta_call, "theta_put": theta_put,
        "rho_call": rho_call, "rho_put": rho_put,
    }

g = greeks(100, 105, 0.5, 0.05, 0.2)
for k, v in g.items():
    print(f"{k:>12}: {v:>8.4f}")

Sensitivity Analysis

Understanding how each input affects the price is critical. This analysis shows you the non-linear relationship between inputs and option value — the kind of intuition interviewers test for at firms like Citadel, GSA Capital, and Man Group.

import numpy as np

# How call price changes with stock price
for s in range(80, 121, 5):
    price = black_scholes(s, 100, 0.5, 0.05, 0.2, "call")
    print(f"  S={s:>3}  Call={price:>7.2f}")

# How call price changes with volatility
for vol in [0.10, 0.15, 0.20, 0.25, 0.30, 0.40]:
    price = black_scholes(100, 100, 0.5, 0.05, vol, "call")
    print(f"  sigma={vol:.2f}  Call={price:>7.2f}")

Key Insight

Black-Scholes assumes constant volatility, but in reality volatility changes constantly and differs across strikes (the volatility smile). This is why the model is a starting point, not the final word. Every desk adjusts for this. The model gives you the language to describe option prices; the market tells you where the model is wrong.

Common Interview Trap

"What assumptions does Black-Scholes make?" — Constant volatility, no dividends (basic version), continuous trading, no transaction costs, log-normal stock prices, constant risk-free rate. Know all of these and which matter most in practice. Volatility not being constant is by far the biggest issue.

Developer Essentials

As an Amazon Associate we may earn from qualifying purchases.