Black-Scholes Formula and European Option Pricing › Implementing the Black-Scholes Formula
Quantitative FinanceAll chapters Black-Scholes Formula and European Option Pricing › Implementing the Black-Scholes FormulaOne section · Sections in this chapter5 / 11 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Let's implement the Black-Scholes formula in Python and explore its behavior. These educational functions use scalar inputs with and positive remaining time for the pre-expiry formulas, with a separate expiry-payoff branch. They assume finite, numerically usable inputs and are not general-range pricing routines. The validator explicitly rejects zero volatility, which would require a separate deterministic-limit formula. Negative remaining maturity is rejected; zero remaining maturity returns the expiry payoff.
import numpy as np
from scipy.stats import norm
def validate_bs_inputs(S, K, T, r, sigma, option_type="call", q=0.0):
"""Validate this chapter's finite scalar educational pricing domain.
S, K and sigma must be positive; remaining maturity T may be zero.
Rates r and continuous yield q may have either sign.
Zero volatility is excluded, rather than evaluated through a singular formula.
Numeric inputs must be representable as finite Python floats.
"""
from numbers import Real
values = (S, K, T, r, sigma, q)
if not all(isinstance(value, Real) for value in values):
raise ValueError("Pricing inputs must be finite real scalars")
try:
S, K, T, r, sigma, q = map(float, values)
except (OverflowError, TypeError, ValueError) as error:
raise ValueError("Pricing inputs must be finite real scalars") from error
if not all(np.isfinite(value) for value in (S, K, T, r, sigma, q)):
raise ValueError("Pricing inputs must be finite real scalars")
if S <= 0 or K <= 0 or sigma <= 0 or T < 0:
raise ValueError("S, K and sigma must be positive; T must be nonnegative")
if not isinstance(option_type, str) or option_type not in ("call", "put"):
raise ValueError("option_type must be 'call' or 'put'")
return S, K, T, r, sigma, q
def black_scholes_call(S, K, T, r, sigma):
"""
Calculate Black-Scholes price for a European call option.
Parameters:
S: Current stock price
K: Strike price
T: Time to expiration (in years)
r: Risk-free interest rate (annualized)
sigma: Volatility (annualized)
Returns:
Call option price
Inputs are finite real scalars with S,K,sigma>0 and T>=0; T=0
returns the expiry payoff. The implementation assumes numerically usable
intermediates and does not support arbitrary numerical ranges.
"""
S, K, T, r, sigma, _ = validate_bs_inputs(S, K, T, r, sigma)
if T == 0:
return max(S - K, 0) # At expiration, return intrinsic value
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
call_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
return call_price
def black_scholes_put(S, K, T, r, sigma):
"""
Calculate a scalar European put price for finite inputs with S,K,sigma>0.
T is nonnegative remaining maturity; T=0 returns the expiry payoff.
This educational implementation assumes numerically usable intermediates.
"""
S, K, T, r, sigma, _ = validate_bs_inputs(S, K, T, r, sigma, "put")
if T == 0:
return max(K - S, 0)
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
put_price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
return put_priceNow let's calculate option prices for a specific example:
# Example parameters
S = 100 # Current stock price: $100
K = 105 # Strike price: $105
T = 0.5 # Time to expiration: 6 months
r = 0.05 # Risk-free rate: 5%
sigma = 0.20 # Volatility: 20%
call_price = black_scholes_call(S, K, T, r, sigma)
put_price = black_scholes_put(S, K, T, r, sigma)Black-Scholes Option Pricing Example ======================================== Stock Price (S): $100.00 Strike Price (K): $105.00 Time to Expiry (T): 0.50 years (6 months) Risk-free Rate (r): 5.0% Volatility (σ): 20.0% ---------------------------------------- Call Option Price: $4.5817 Put Option Price: $6.9892
The call option on this slightly out-of-the-money stock has a lower value than the put, which holds intrinsic value. Let's verify put-call parity holds:
# Verify put-call parity: C - P = S - K*exp(-rT)
lhs = call_price - put_price
rhs = S - K * np.exp(-r * T)
parity_difference = abs(lhs - rhs)Put-Call Parity Verification ======================================== C - P = -2.4075 S - K*exp(-rT) = -2.4075 Difference: 0.00e+00 Put-call parity holds
At these example inputs, the implementation agrees with put-call parity to floating-point precision.
Examining the Intermediate Calculations
Understanding the intermediate values and helps build intuition for how the formula works:
def compute_d1_d2(S, K, T, r, sigma):
"""Compute d1/d2 for finite scalar S,K,T,sigma>0 and finite r."""
S, K, T, r, sigma, _ = validate_bs_inputs(S, K, T, r, sigma)
if T == 0:
raise ValueError("d1 and d2 require positive remaining maturity")
log_moneyness = np.log(S / K)
d1_shift = (r + 0.5 * sigma**2) * T
diffusion = sigma * np.sqrt(T)
d1 = (log_moneyness + d1_shift) / diffusion
d2 = d1 - diffusion
return d1, d2, log_moneyness, d1_shift, diffusion
d1, d2, log_moneyness, d1_shift, diffusion = compute_d1_d2(S, K, T, r, sigma)Breakdown of d1 and d2 Calculations ============================================= ln(S/K) = ln(100/105) = -0.0488 d1 numerator shift (r + σ²/2)T = 0.0350 Diffusion σ√T = 0.1414 --------------------------------------------- d1 = (ln(S/K) + shift) / diffusion = -0.0975 d2 = d1 - σ√T = -0.2389 --------------------------------------------- N(d1) = 0.4612 (probability under share measure) N(d2) = 0.4056 (risk-neutral probability ITM)
The negative value tells us there's less than a 50% risk-neutral probability that this option finishes in-the-money. The value of calculated above represents the precise probability of the stock price exceeding the strike at expiration under the risk-neutral measure.
Key Parameters
The key parameters for the Black-Scholes implementation are:
- : Current stock price. Higher prices increase call values and decrease put values.
- : Strike price. The reference price for the option's payoff.
- : Time to expiration in years. Longer times generally increase option values.
- : Risk-free interest rate (annualized). Higher rates favor calls over puts.
- : Volatility (annualized). Controls the dispersion of terminal stock prices; with the other inputs fixed, higher volatility increases both call and put values.