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:
- is the drift (expected return)
- is the volatility
- is a Wiener process (Brownian motion)
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:
- The stock price follows a geometric Brownian motion
- No dividends are paid during the option's life
- Risk-free rate and volatility are constant
- No transaction costs or taxes
- Options can be exercised only at expiration (European style)
- Markets are efficient and frictionless
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:
Option Greeks
The Greeks measure the sensitivity of option prices to various parameters:
- Delta (Δ): Sensitivity to stock price changes
- Gamma (Γ): Rate of change of delta
- Theta (Θ): Sensitivity to time decay
- Vega (ν): Sensitivity to volatility changes
- Rho (ρ): Sensitivity to interest rate changes
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:
- Assumes constant volatility (volatility smile/skew observed in practice)
- Assumes constant risk-free rate
- Does not account for early exercise (American options)
- Assumes log-normal distribution (fat tails in reality)
- No transaction costs or market frictions
Applications
The Black-Scholes model is widely used for:
- Pricing European options
- Risk management and hedging
- Implied volatility calculations
- Academic research and teaching
- As a benchmark for more complex models