Black-Scholes Model

Finance

The Black-Scholes model is a mathematical model for pricing European-style options. It assumes that the underlying asset price follows a geometric Brownian motion with constant drift and volatility.

Geometric Brownian Motion

The Black-Scholes model assumes the stock price follows a geometric Brownian motion:

where:

Using Itô's lemma, the solution is:

In the risk-neutral world, where is the risk-free rate.

Black-Scholes Formula

The Black-Scholes formula for a European call option is:

and for a European put option:

where:

and is the cumulative distribution function of the standard normal distribution.

Assumptions

The Black-Scholes model makes several assumptions:

Python Implementation

The following code implements the Black-Scholes formula:

import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
import matplotlib as mpl

def set_publication_style():
    """Set publication-quality matplotlib style."""
    mpl.rcParams.update({
        'font.family': 'serif',
        'font.size': 12,
        'axes.labelsize': 14,
        'axes.titlesize': 16,
        'axes.linewidth': 1.2,
        'axes.labelpad': 8,
        'axes.titlepad': 10,
        'xtick.labelsize': 12,
        'ytick.labelsize': 12,
        'xtick.direction': 'in',
        'ytick.direction': 'in',
        'xtick.top': True,
        'ytick.right': True,
        'xtick.major.size': 6,
        'ytick.major.size': 6,
        'xtick.major.width': 1.2,
        'ytick.major.width': 1.2,
        'legend.fontsize': 12,
        'legend.frameon': False,
        'lines.linewidth': 2,
        'lines.markersize': 6,
        'figure.dpi': 100,
        'savefig.dpi': 300,
        'savefig.bbox': 'tight'
    })

set_publication_style()

def black_scholes(S, K, T, r, sigma, option_type='call'):
    """
    Calculate Black-Scholes option price.
    
    Parameters
    ----------
    S : float
        Current stock price
    K : float
        Strike price
    T : float
        Time to expiration (in years)
    r : float
        Risk-free interest rate
    sigma : float
        Volatility
    option_type : str
        'call' or 'put'
    
    Returns
    -------
    float
        Option price
    """
    # Calculate d1 and d2
    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)
    elif option_type == 'put':
        price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
    else:
        raise ValueError("option_type must be 'call' or 'put'")
    
    return price

# Example parameters
S = 100  # Current stock price
K = 100  # Strike price
T = 1.0  # Time to expiration (1 year)
r = 0.05  # Risk-free rate (5%)
sigma = 0.2  # Volatility (20%)

# Calculate option prices
call_price = black_scholes(S, K, T, r, sigma, 'call')
put_price = black_scholes(S, K, T, r, sigma, 'put')

print("Call Option Price: {:.2f}".format(call_price))
print("Put Option Price: {:.2f}".format(put_price))

# Verify put-call parity: C - P = S - K*e^(-rT)
lhs = call_price - put_price
rhs = S - K * np.exp(-r * T)
print("\nPut-Call Parity Check:")
print("  C - P = {:.6f}".format(lhs))
print("  S - K*e^(-rT) = {:.6f}".format(rhs))
print("  Difference: {:.2e}".format(abs(lhs - rhs)))

# Plot option prices as a function of stock price
S_range = np.linspace(50, 150, 100)
call_prices = [black_scholes(S_val, K, T, r, sigma, 'call') for S_val in S_range]
put_prices = [black_scholes(S_val, K, T, r, sigma, 'put') for S_val in S_range]

plt.figure(figsize=(12, 5))

# Left plot: Option prices vs stock price
plt.subplot(1, 2, 1)
plt.plot(S_range, call_prices, 'b-', linewidth=2, label='Call Option')
plt.plot(S_range, put_prices, 'r-', linewidth=2, label='Put Option')
plt.axvline(K, color='k', linestyle='--', alpha=0.5, label='Strike Price')
plt.xlabel('Stock Price')
plt.ylabel('Option Price')
plt.title('Black-Scholes Option Prices')
plt.legend()
plt.grid(True, alpha=0.3)

# Right plot: Option prices vs volatility
sigma_range = np.linspace(0.05, 0.5, 100)
call_prices_vol = [black_scholes(S, K, T, r, sig, 'call') for sig in sigma_range]
put_prices_vol = [black_scholes(S, K, T, r, sig, 'put') for sig in sigma_range]

plt.subplot(1, 2, 2)
plt.plot(sigma_range, call_prices_vol, 'b-', linewidth=2, label='Call Option')
plt.plot(sigma_range, put_prices_vol, 'r-', linewidth=2, label='Put Option')
plt.xlabel('Volatility (σ)')
plt.ylabel('Option Price')
plt.title('Option Prices vs Volatility')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('figures/black_scholes_plot.png', dpi=300, bbox_inches='tight')
plt.show()

Visualization

The following plots show how option prices vary with stock price and volatility:

Black-Scholes Option Prices

Option Greeks

The Greeks measure the sensitivity of option prices to various parameters:

Put-Call Parity

Put-call parity is a fundamental relationship:

This relationship must hold for European options to prevent arbitrage opportunities.

Limitations

The Black-Scholes model has several limitations:

Applications

The Black-Scholes model is widely used for: