Finite Difference Methods for Option Pricing: PDE Solutions

Michael BrenndoerferDecember 4, 202553 min read

Learn finite difference methods for option pricing. Master explicit, implicit, and Crank-Nicolson schemes to solve the Black-Scholes PDE numerically.

Reading Level

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.

Finite Difference Methods for Option Pricing

Derivative pricing uses two major numerical approaches: binomial trees, which discretize the underlying asset's price movements into up and down jumps, and Monte Carlo simulation, which generates random paths of the underlying asset to estimate option values. Both methods are powerful, but they approach the pricing problem from a probabilistic perspective, simulating possible futures and working backward or averaging outcomes.

Finite difference methods take an entirely different approach. Rather than simulating the stochastic process that drives asset prices, they directly attack the partial differential equation (PDE) that governs option values. As shown in the chapter on the Black-Scholes-Merton PDE, the option price V(S,t)V(S,t) satisfies a deterministic equation that can be solved numerically by discretizing both the asset price and time dimensions onto a grid.

This chapter develops three finite difference schemes, each representing a different trade-off between computational simplicity and numerical stability: the explicit scheme, the implicit scheme, and the Crank-Nicolson scheme. Understanding these methods provides essential tools for pricing options where analytical solutions don't exist, including American options with early exercise features and exotic derivatives with complex payoff structures. We'll examine how to set up the computational grid, handle boundary conditions, ensure numerical stability, and implement each scheme in practice.

The Black-Scholes PDE on a Grid

The foundation of finite difference methods is the Black-Scholes PDE, which is derived using Itô's lemma and the principle of no-arbitrage pricing. Recall that this partial differential equation emerges from the requirement that a perfectly hedged portfolio of an option and its underlying asset must earn the risk-free rate. The equation captures how the option value V(S,t)V(S,t) evolves as time passes and as the underlying asset price SS fluctuates. For an option with value V(S,t)V(S,t) on an underlying asset with price SS, the PDE is:

Vt+12σ2S22VS2+rSVSrV=0\frac{\partial V}{\partial t} + \frac{1}{2}\sigma^2 S^2 \frac{\partial^2 V}{\partial S^2} + rS\frac{\partial V}{\partial S} - rV = 0

where:

  • V(S,t)V(S,t): option price as a function of asset price SS and time tt
  • rr: risk-free interest rate
  • σ\sigma: volatility of the underlying asset
  • 12σ2S22VS2\frac{1}{2}\sigma^2 S^2 \frac{\partial^2 V}{\partial S^2}: diffusion term capturing the effect of volatility
  • rSVSrS\frac{\partial V}{\partial S}: drift term reflecting the risk-neutral expected return
  • rV-rV: discounting term reflecting the time value of money

Each term in this equation has a clear financial interpretation that helps us understand why the option value must satisfy this relationship. The time derivative Vt\frac{\partial V}{\partial t} captures how the option value changes purely due to the passage of time, with all else held constant. The second derivative term, sometimes called the gamma term, reflects the convexity of the option's payoff and how volatility affects value. When an option has positive gamma, increased volatility raises its expected payoff under risk-neutral pricing. The first derivative term represents the delta exposure, capturing how the option value responds to small changes in the underlying price. Finally, the discounting term ensures that the option value is expressed in present-value terms, consistent with no-arbitrage pricing. This equation, combined with appropriate boundary conditions and a terminal condition at expiration, uniquely determines the option value.

Transforming to a Standard Form

Working directly with the Black-Scholes PDE can be cumbersome because the coefficient 12σ2S2\frac{1}{2}\sigma^2 S^2 in front of the second derivative varies with SS. This spatial dependence of the coefficients means that the diffusion rate changes across the price grid, which can complicate numerical analysis and introduce additional discretization challenges. A common transformation simplifies the problem by converting to log-price coordinates. The key insight is that if the stock price follows geometric Brownian motion, then the log of the stock price follows arithmetic Brownian motion with constant volatility. Let x=ln(S)x = \ln(S), so S=exS = e^x. After applying the chain rule to transform the derivatives:

VS=VxxS(chain rule)=Vx1S(derivative of x=lnS)=1SVx(rearrange)\begin{aligned} \frac{\partial V}{\partial S} &= \frac{\partial V}{\partial x}\frac{\partial x}{\partial S} && \text{(chain rule)} \\ &= \frac{\partial V}{\partial x}\frac{1}{S} && \text{(derivative of } x = \ln S \text{)} \\ &= \frac{1}{S}\frac{\partial V}{\partial x} && \text{(rearrange)} \end{aligned}

where:

  • VV: option value
  • SS: asset price
  • xx: log-price coordinate (x=lnSx = \ln S)

The transformation of the first derivative follows directly from the chain rule. For the second derivative, we must apply the product rule and chain rule together, recognizing that 1S\frac{1}{S} itself depends on xx. The calculation yields:

2VS2=1S2(2Vx2Vx)\frac{\partial^2 V}{\partial S^2} = \frac{1}{S^2}\left(\frac{\partial^2 V}{\partial x^2} - \frac{\partial V}{\partial x}\right)

where:

  • 2VS2\frac{\partial^2 V}{\partial S^2}: second derivative (gamma) in price coordinates
  • 2Vx2,Vx\frac{\partial^2 V}{\partial x^2}, \frac{\partial V}{\partial x}: derivatives in log-price coordinates

Notice how the transformation introduces a correction term: the second derivative in log-coordinates does not simply equal S2S^2 times the second derivative in price coordinates. This correction arises because the log transformation is nonlinear, stretching the coordinate system differently at different price levels. Substituting these transformed derivatives into the Black-Scholes PDE and simplifying yields a much simpler equation:

Vt+12σ22Vx2+(r12σ2)VxrV=0\frac{\partial V}{\partial t} + \frac{1}{2}\sigma^2 \frac{\partial^2 V}{\partial x^2} + \left(r - \frac{1}{2}\sigma^2\right)\frac{\partial V}{\partial x} - rV = 0

where:

  • VV: option value
  • tt: time
  • σ\sigma: volatility
  • rr: risk-free rate
  • xx: log-price coordinate

This transformed equation has constant coefficients, making it more amenable to finite difference approximations because the same discretization scheme applies uniformly across the entire spatial domain. The coefficient r12σ2r - \frac{1}{2}\sigma^2 in front of the first derivative is the risk-neutral drift of the log-price process, which should be familiar from the study of the Black-Scholes-Merton model. To keep things clear and practical, we'll focus on the original SS-coordinate formulation. This makes setting boundary conditions and interpreting results more intuitive.

Setting Up the Computational Grid

To solve the PDE numerically, we must replace the continuous price and time domains with discrete grid points. This process, called discretization, transforms the continuous partial differential equation into a system of algebraic equations that a computer can solve. For the asset price SS, we create a grid from Smin=0S_{\min} = 0 to SmaxS_{\max}, where SmaxS_{\max} is chosen large enough that the option value is negligible for out-of-the-money options. For time, we work backward from expiration TT to the current time t=0t = 0, which aligns with the natural direction for solving parabolic PDEs with terminal conditions.

The grid points are defined as:

  • Price grid: Sj=jΔSS_j = j \cdot \Delta S for j=0,1,,Mj = 0, 1, \ldots, M, where ΔS=Smax/M\Delta S = S_{\max}/M
  • Time grid: tn=nΔtt_n = n \cdot \Delta t for n=0,1,,Nn = 0, 1, \ldots, N, where Δt=T/N\Delta t = T/N

Let VjnV_j^n denote the option value at grid point (Sj,tn)(S_j, t_n). This notation uses subscripts for spatial indices and superscripts for temporal indices, a convention that helps distinguish the two dimensions of the problem. Our goal is to compute the option values at n=0n = 0 (current time) given the terminal condition at n=Nn = N (expiration). The backward-in-time solution process mirrors the logic of risk-neutral valuation: we know the option's payoff at expiration and work backward to determine its present value.

In[2]:
Code
import numpy as np

# Set up the discretization grid
S_max = 200.0  # Maximum stock price on grid
M = 100  # Number of price steps
N = 1000  # Number of time steps
T = 1.0  # Time to expiration (years)
K = 100.0  # Strike price

dS = S_max / M
dt = T / N

# Create the grid
S = np.linspace(0, S_max, M + 1)
t = np.linspace(0, T, N + 1)
Out[3]:
Console
Price step size (dS): $2.00
Time step size (dt): 0.001000 years (0.252 trading days)
Grid dimensions: 101 price points × 1001 time points
Total grid points: 101,101
Out[4]:
Visualization
Schematic showing a 2D grid with stock price on the vertical axis and time on the horizontal axis, with grid points marked at intersections.
The finite difference grid discretizes the continuous price-time domain into discrete points. Option values are computed at each grid intersection, with the solution propagating backward from the terminal condition at expiration (right edge) to the present value (left edge).

The choice of grid parameters involves trade-offs that you must carefully consider. Finer grids (larger MM and NN) provide more accurate solutions but require more computation and memory. The grid must be fine enough to capture the option's behavior near the strike price, where values change most rapidly, but extending the grid too far into deep in-the-money or out-of-the-money regions wastes computational resources. The ratio between Δt\Delta t and (ΔS)2(\Delta S)^2 is particularly important for stability, as we'll see when analyzing each scheme.

The Explicit Finite Difference Scheme

The explicit scheme is the most straightforward approach to solving the Black-Scholes PDE numerically. Its simplicity makes it an excellent starting point for understanding finite difference methods, even though its stability limitations often favor other schemes in practice. The explicit scheme approximates the PDE derivatives using values at the current time step to calculate values at the previous time step (remembering that we work backward from expiration).

Derivative Approximations

The central idea of finite difference methods is to replace derivatives with algebraic approximations involving function values at nearby grid points. These approximations come from Taylor series expansions. We approximate the partial derivatives using finite differences. At grid point (Sj,tn+1)(S_j, t_{n+1}), the approximations are:

Time derivative (backward difference):

VtVjn+1VjnΔt\frac{\partial V}{\partial t} \approx \frac{V_j^{n+1} - V_j^n}{\Delta t}

where:

  • VjnV_j^n: option value at grid point jj and current time step nn
  • Vjn+1V_j^{n+1}: option value at grid point jj and next time step n+1n+1
  • Δt\Delta t: time step size

This approximation follows from the definition of a derivative as a limit. When we cannot take an infinitesimal step, we use a small but finite step Δt\Delta t instead. The error in this approximation is proportional to Δt\Delta t, meaning we achieve first-order accuracy in time.

First spatial derivative (central difference):

VSVj+1n+1Vj1n+12ΔS\frac{\partial V}{\partial S} \approx \frac{V_{j+1}^{n+1} - V_{j-1}^{n+1}}{2\Delta S}

where:

  • Vj±1n+1V_{j\pm 1}^{n+1}: option values at adjacent spatial points at time tn+1t_{n+1}
  • ΔS\Delta S: price step size

The central difference uses function values on both sides of the point of interest, which leads to a symmetric approximation with second-order accuracy. Compared to one-sided differences, the central difference cancels the leading error terms, giving a more accurate estimate of the derivative.

Second spatial derivative (central difference):

2VS2Vj+1n+12Vjn+1+Vj1n+1(ΔS)2\frac{\partial^2 V}{\partial S^2} \approx \frac{V_{j+1}^{n+1} - 2V_j^{n+1} + V_{j-1}^{n+1}}{(\Delta S)^2}

where:

  • Vj±1n+1V_{j\pm 1}^{n+1}: option values at adjacent spatial points
  • ΔS\Delta S: price step size

This formula for the second derivative can be derived by applying the first derivative approximation twice, or equivalently by using Taylor expansions and solving for the second derivative. The resulting expression has an intuitive interpretation: it measures the curvature of the option value function by comparing the value at a point to the average of its neighbors. Notice that all spatial derivatives use values at time tn+1t_{n+1}. This makes the scheme "explicit" because we can directly calculate VjnV_j^n from known values at tn+1t_{n+1} without needing to solve a system of equations.

Deriving the Update Formula

With our finite difference approximations in hand, we can now derive the explicit update formula by substituting into the Black-Scholes PDE. Substituting these approximations into the Black-Scholes PDE and rearranging for VjnV_j^n:

Vjn+1VjnΔt+12σ2Sj2Vj+1n+12Vjn+1+Vj1n+1(ΔS)2+rSjVj+1n+1Vj1n+12ΔSrVjn+1=0\begin{aligned} \frac{V_j^{n+1} - V_j^n}{\Delta t} &+ \frac{1}{2}\sigma^2 S_j^2 \frac{V_{j+1}^{n+1} - 2V_j^{n+1} + V_{j-1}^{n+1}}{(\Delta S)^2} \\ &+ rS_j\frac{V_{j+1}^{n+1} - V_{j-1}^{n+1}}{2\Delta S} - rV_j^{n+1} = 0 \end{aligned}

where:

  • VjnV_j^n: unknown option value at current time step nn
  • Vn+1V_{\cdot}^{n+1}: known option values at next time step n+1n+1
  • SjS_j: asset price at grid point jj
  • Δt,ΔS\Delta t, \Delta S: time and price step sizes
  • r,σr, \sigma: risk-free rate and volatility

Our objective is to isolate VjnV_j^n on one side of the equation, expressing it in terms of known quantities. Multiplying by Δt\Delta t and rearranging to isolate VjnV_j^n:

Vjn=Vjn+1+Δt(12σ2Sj2Vj+1n+12Vjn+1+Vj1n+1(ΔS)2+rSjVj+1n+1Vj1n+12ΔSrVjn+1)\begin{aligned} V_j^n = V_j^{n+1} + \Delta t \bigg( &\frac{1}{2}\sigma^2 S_j^2 \frac{V_{j+1}^{n+1} - 2V_j^{n+1} + V_{j-1}^{n+1}}{(\Delta S)^2} \\ &+ rS_j\frac{V_{j+1}^{n+1} - V_{j-1}^{n+1}}{2\Delta S} - rV_j^{n+1} \bigg) \end{aligned}

where:

  • VjnV_j^n: option value at the current time step
  • Δt\Delta t: time step size
  • Terms in parenthesis: discrete approximation of the Black-Scholes operator

The expression in parentheses represents the spatial part of the Black-Scholes operator, evaluated at the known time step. To make the formula more useful for computation, we collect terms by which grid point they involve. Grouping terms by the grid points Vj1n+1V_{j-1}^{n+1}, Vjn+1V_j^{n+1}, and Vj+1n+1V_{j+1}^{n+1}:

Vjn=[Δt2(ΔS)2σ2Sj2Δt2ΔSrSj]Vj1n+1+[1Δt(ΔS)2σ2Sj2Δtr]Vjn+1+[Δt2(ΔS)2σ2Sj2+Δt2ΔSrSj]Vj+1n+1\begin{aligned} V_j^n &= \left[ \frac{\Delta t}{2(\Delta S)^2}\sigma^2 S_j^2 - \frac{\Delta t}{2\Delta S}rS_j \right] V_{j-1}^{n+1} \\ &\quad + \left[ 1 - \frac{\Delta t}{(\Delta S)^2}\sigma^2 S_j^2 - \Delta t r \right] V_j^{n+1} \\ &\quad + \left[ \frac{\Delta t}{2(\Delta S)^2}\sigma^2 S_j^2 + \frac{\Delta t}{2\Delta S}rS_j \right] V_{j+1}^{n+1} \end{aligned}

where:

  • VjnV_j^n: option value at the current time step
  • Δt\Delta t: time step size
  • ΔS\Delta S: price step size
  • σ\sigma: volatility
  • rr: risk-free rate
  • SjS_j: asset price at grid point jj
  • Vn+1V_{\cdot}^{n+1}: option values at the next time step

The structure of this equation reveals that the option value at any interior grid point is a weighted combination of three values at the next time step: the value at the same price level and the values at adjacent price levels. Using Sj=jΔSS_j = j\Delta S, these bracketed terms simplify to a more compact form:

Vjn=ajVj1n+1+bjVjn+1+cjVj+1n+1V_j^n = a_j V_{j-1}^{n+1} + b_j V_j^{n+1} + c_j V_{j+1}^{n+1}

where:

  • VjnV_j^n: option value at the current time step nn
  • Vj1n+1,Vjn+1,Vj+1n+1V_{j-1}^{n+1}, V_j^{n+1}, V_{j+1}^{n+1}: known option values at the next time step n+1n+1
  • aj,bj,cja_j, b_j, c_j: weighting coefficients for the grid points

The coefficients are:

aj=Δt2(σ2j2rj)bj=1Δt(σ2j2+r)cj=Δt2(σ2j2+rj)\begin{aligned} a_j &= \frac{\Delta t}{2}\left(\sigma^2 j^2 - rj\right) \\ b_j &= 1 - \Delta t\left(\sigma^2 j^2 + r\right) \\ c_j &= \frac{\Delta t}{2}\left(\sigma^2 j^2 + rj\right) \end{aligned}

where:

  • Δt\Delta t: time step size
  • σ\sigma: volatility
  • rr: risk-free rate
  • jj: grid index (j=Sj/ΔSj = S_j/\Delta S)

The option value at each interior point depends only on three values at the next time step, which we already know from either the terminal condition or previous calculations. Intuitively, this weighted sum resembles an expected value calculation: aja_j and cjc_j represent the weights (pseudo-probabilities) of the asset price moving down or up, while bjb_j is the weight of staying effectively in the same region, all adjusted for the risk-free discounting. This probabilistic interpretation connects finite difference methods to the binomial tree approach, where we also computed option values as discounted expected values under risk-neutral probabilities. The explicit scheme essentially implements the same logic on a finer grid.

Implementation

The explicit scheme proceeds as follows:

  1. Initialize the grid with the terminal payoff at expiration
  2. Apply boundary conditions at S=0S = 0 and S=SmaxS = S_{\max}
  3. Sweep backward through time, calculating each VjnV_j^n from values at tn+1t_{n+1}
In[5]:
Code
def explicit_european_call(S_max, K, T, r, sigma, M, N):
    """
    Price a European call option using the explicit finite difference method.

    Parameters:
        S_max: Maximum stock price on grid
        K: Strike price
        T: Time to expiration
        r: Risk-free rate
        sigma: Volatility
        M: Number of price steps
        N: Number of time steps

    Returns:
        S: Array of stock prices
        V: Array of option values at t=0
    """
    dS = S_max / M
    dt = T / N

    # Grid of stock prices
    S = np.linspace(0, S_max, M + 1)

    # Initialize option values at expiration (terminal condition)
    V = np.maximum(S - K, 0)  # Call payoff

    # Precompute coefficients for interior points
    j = np.arange(1, M)  # Interior indices
    a = 0.5 * dt * (sigma**2 * j**2 - r * j)
    b = 1 - dt * (sigma**2 * j**2 + r)
    c = 0.5 * dt * (sigma**2 * j**2 + r * j)

    # Time stepping (backward from T to 0)
    for n in range(N - 1, -1, -1):
        V_new = np.zeros(M + 1)

        # Interior points: explicit update
        V_new[1:M] = a * V[0 : M - 1] + b * V[1:M] + c * V[2 : M + 1]

        # Boundary conditions
        V_new[0] = 0  # Call worth 0 when S = 0
        V_new[M] = S_max - K * np.exp(-r * (N - n) * dt)  # Linear approximation

        V = V_new

    return S, V
In[6]:
Code
# Parameters
r = 0.05  # Risk-free rate
sigma = 0.2  # Volatility
S_0 = 100  # Current stock price

# Price the option with explicit method
S_grid, V_explicit = explicit_european_call(S_max, K, T, r, sigma, M, N=5000)

# Find the option value at S_0 = 100
idx = np.argmin(np.abs(S_grid - S_0))
price_explicit = V_explicit[idx]
Out[7]:
Console
Explicit FD price at S = 100: $10.4409

The calculated option price represents the fair value derived from the explicit finite difference grid. Since the grid is relatively fine (5000 time steps), this value closely approximates the theoretical Black-Scholes price, serving as a baseline for verifying the numerical method's accuracy.

Stability Constraint

The explicit scheme has a critical limitation. It becomes unstable if the time step is too large. This instability arises because the coefficients aj,bj,cja_j, b_j, c_j must behave like probabilities to ensure the solution remains bounded. When we interpret these coefficients as weights in an expected value calculation, they should all be non-negative for the scheme to behave sensibly. If Δt\Delta t is too large, bjb_j becomes negative, causing errors to oscillate and grow exponentially with each time step. Small numerical errors introduced by rounding or discretization get amplified rather than damped, eventually overwhelming the true solution. The stability condition requires:

Δt1σ2jmax2+r\Delta t \leq \frac{1}{\sigma^2 j_{\max}^2 + r}

where:

  • Δt\Delta t: maximum allowable time step
  • σ\sigma: volatility
  • rr: risk-free rate
  • jmaxj_{\max}: maximum grid index (MM)

In practice, this means:

Δt(ΔS)2σ2Smax2\Delta t \leq \frac{(\Delta S)^2}{\sigma^2 S_{\max}^2}

where:

  • Δt\Delta t: maximum stable time step
  • ΔS\Delta S: price grid spacing
  • σ\sigma: volatility
  • SmaxS_{\max}: maximum price on the grid

This constraint can be severe because the bound depends on the square of the price grid spacing. Halving ΔS\Delta S to improve spatial resolution requires dividing Δt\Delta t by four to maintain stability. For typical parameters, the explicit scheme may require thousands of time steps to remain stable, making it computationally expensive despite its conceptual simplicity. This stability restriction motivates the development of implicit methods that remove or relax this constraint.

In[8]:
Code
# Check stability condition
def check_explicit_stability(S_max, sigma, r, M, dt):
    """Check if explicit scheme parameters satisfy stability condition."""
    j_max = M
    dt_max = 1.0 / (sigma**2 * j_max**2 + r)
    is_stable = dt <= dt_max
    return is_stable, dt_max
In[9]:
Code
## Check stability for our parameters
dt_check = T / 5000

is_stable, dt_max = check_explicit_stability(S_max, sigma, r, M, dt_check)
min_steps_required = int(np.ceil(T / dt_max))
Out[10]:
Console
Time step used: 0.000200
Maximum stable time step: 0.002500
Stability satisfied: True
Minimum time steps required: 401

The stability requirement explains why we used 5,000 time steps in our implementation. Using fewer steps would cause the solution to oscillate wildly and diverge from the true option value.

Out[11]:
Visualization
Line plot showing smooth option value curve for stable explicit scheme.
Stable explicit finite difference solution. With sufficient time steps ($N=5000$), the solution (blue line) is smooth and matches the payoff and theoretical values.
Line plot showing jagged, oscillating curve for unstable explicit scheme.
Unstable explicit finite difference solution. When the stability condition is violated ($N=200$), the solution (red line) oscillates wildly and diverges from the true option values.

The Implicit Finite Difference Scheme

The implicit scheme overcomes the stability limitation by evaluating the spatial derivatives at the current time step tnt_n rather than the future time step tn+1t_{n+1}. This small change significantly improves numerical stability. Rather than computing values at time tnt_n directly from known values at tn+1t_{n+1}, the implicit scheme requires solving for all values at tnt_n simultaneously, since the update formula for each point involves unknown values at neighboring points at the same time level.

Derivative Approximations

The implicit scheme uses the same time derivative approximation as the explicit scheme but evaluates the spatial derivatives at a different time level. The implicit scheme uses:

Time derivative (backward difference):

VtVjn+1VjnΔt\frac{\partial V}{\partial t} \approx \frac{V_j^{n+1} - V_j^n}{\Delta t}

where:

  • VjnV_j^n: option value at grid point jj and current time step nn
  • Vjn+1V_j^{n+1}: option value at grid point jj and next time step n+1n+1
  • Δt\Delta t: time step size

Spatial derivatives at time tnt_n:

VSVj+1nVj1n2ΔS2VS2Vj+1n2Vjn+Vj1n(ΔS)2\begin{aligned} \frac{\partial V}{\partial S} &\approx \frac{V_{j+1}^n - V_{j-1}^n}{2\Delta S} \\[1em] \frac{\partial^2 V}{\partial S^2} &\approx \frac{V_{j+1}^n - 2V_j^n + V_{j-1}^n}{(\Delta S)^2} \end{aligned}

where:

  • VjnV_{j\cdot}^n: option values at current time tnt_n (unknowns)
  • ΔS\Delta S: price step size

The crucial distinction from the explicit scheme is that these spatial derivatives use values at time tnt_n, which are the unknowns we seek to determine. This creates a coupling between neighboring grid points at the same time level: to find VjnV_j^n, we need Vj1nV_{j-1}^n and Vj+1nV_{j+1}^n, but these values are also unknown.

The Tridiagonal System

Because all values at time tnt_n are coupled together, we cannot compute them one at a time. Instead, we must solve a system of linear equations simultaneously. Substituting the approximations into the Black-Scholes PDE:

Vjn+1VjnΔt+12σ2Sj2Vj+1n2Vjn+Vj1n(ΔS)2+rSjVj+1nVj1n2ΔSrVjn=0\begin{aligned} \frac{V_j^{n+1} - V_j^n}{\Delta t} &+ \frac{1}{2}\sigma^2 S_j^2 \frac{V_{j+1}^n - 2V_j^n + V_{j-1}^n}{(\Delta S)^2} \\ &+ rS_j\frac{V_{j+1}^n - V_{j-1}^n}{2\Delta S} - rV_j^n = 0 \end{aligned}

where:

  • VnV_{\cdot}^n: unknown option values at current time tnt_n
  • Vjn+1V_j^{n+1}: known option value at next time tn+1t_{n+1}

We multiply by Δt\Delta t and isolate Vjn+1V_j^{n+1}:

Vjn+1=VjnΔt[12σ2Sj2Vj+1n2Vjn+Vj1n(ΔS)2+rSjVj+1nVj1n2ΔSrVjn]V_j^{n+1} = V_j^n - \Delta t \left[ \frac{1}{2}\sigma^2 S_j^2 \frac{V_{j+1}^n - 2V_j^n + V_{j-1}^n}{(\Delta S)^2} + rS_j\frac{V_{j+1}^n - V_{j-1}^n}{2\Delta S} - rV_j^n \right]

Grouping the unknown terms at tnt_n by spatial index:

Vjn+1=[Δt2(ΔS)2σ2Sj2+Δt2ΔSrSj]Vj1n+[1+Δt(ΔS)2σ2Sj2+Δtr]Vjn+[Δt2(ΔS)2σ2Sj2Δt2ΔSrSj]Vj+1n\begin{aligned} V_j^{n+1} &= \left[ -\frac{\Delta t}{2(\Delta S)^2}\sigma^2 S_j^2 + \frac{\Delta t}{2\Delta S}rS_j \right] V_{j-1}^n \\ &\quad + \left[ 1 + \frac{\Delta t}{(\Delta S)^2}\sigma^2 S_j^2 + \Delta t r \right] V_j^n \\ &\quad + \left[ -\frac{\Delta t}{2(\Delta S)^2}\sigma^2 S_j^2 - \frac{\Delta t}{2\Delta S}rS_j \right] V_{j+1}^n \end{aligned}

Defining coefficients αj,βj,γj\alpha_j, \beta_j, \gamma_j to simplify notation, we obtain the linear system:

αjVj1n+(1+βj)VjnγjVj+1n=Vjn+1-\alpha_j V_{j-1}^n + (1 + \beta_j) V_j^n - \gamma_j V_{j+1}^n = V_j^{n+1}

where:

  • VnV_{\cdot}^n: unknown values at current time tnt_n
  • Vjn+1V_j^{n+1}: known value at next time tn+1t_{n+1}
  • αj,βj,γj\alpha_j, \beta_j, \gamma_j: coefficients for the implicit system

The coefficients are:

αj=Δt2(σ2j2rj)βj=Δt(σ2j2+r)γj=Δt2(σ2j2+rj)\begin{aligned} \alpha_j &= \frac{\Delta t}{2}\left(\sigma^2 j^2 - rj\right) \\ \beta_j &= \Delta t\left(\sigma^2 j^2 + r\right) \\ \gamma_j &= \frac{\Delta t}{2}\left(\sigma^2 j^2 + rj\right) \end{aligned}

where:

  • Δt\Delta t: time step
  • σ\sigma: volatility
  • rr: risk-free rate
  • jj: spatial index corresponding to SjS_j

Unlike the explicit scheme, we cannot directly calculate VjnV_j^n from Vjn+1V_j^{n+1}. Instead, we must solve a system of linear equations at each time step. For the M1M-1 interior points, this forms a tridiagonal matrix equation:

AVn=Vn+1+b\mathbf{A}\mathbf{V}^n = \mathbf{V}^{n+1} + \mathbf{b}

where:

  • A\mathbf{A}: tridiagonal coefficient matrix
  • Vn\mathbf{V}^n: vector of option values at time tnt_n (unknowns)
  • Vn+1\mathbf{V}^{n+1}: vector of known option values at time tn+1t_{n+1}
  • b\mathbf{b}: vector incorporating boundary conditions

The Tridiagonal Matrix Structure

The matrix A\mathbf{A} has the form:

A=(1+β1γ100α21+β2γ200α31+β3000αM11+βM1)\mathbf{A} = \begin{pmatrix} 1 + \beta_1 & -\gamma_1 & 0 & \cdots & 0 \\ -\alpha_2 & 1 + \beta_2 & -\gamma_2 & \cdots & 0 \\ 0 & -\alpha_3 & 1 + \beta_3 & \cdots & 0 \\ \vdots & \ddots & \ddots & \ddots & \vdots \\ 0 & 0 & \cdots & -\alpha_{M-1} & 1 + \beta_{M-1} \end{pmatrix}

where:

  • αj,βj,γj\alpha_j, \beta_j, \gamma_j: coefficients defined previously
  • MM: number of spatial grid intervals

The tridiagonal structure arises because each interior equation involves only three unknowns: the value at the current grid point and its two immediate neighbors. This sparse structure is extremely important computationally because it allows efficient solution algorithms. By coupling all VjnV_j^n values in a single system, the implicit method allows information to propagate across the entire grid instantaneously, mirroring the infinite propagation speed of the diffusion equation. In contrast, the explicit scheme propagates information only one grid point per time step, which is why it requires the stability constraint. This global dependency ensures errors are damped rather than amplified, providing unconditional stability. Tridiagonal systems can be solved very efficiently using the Thomas algorithm (a specialized form of Gaussian elimination), requiring only O(M)O(M) operations per time step, making the implicit scheme practical even though it requires solving a linear system.

Out[12]:
Visualization
Heatmap showing the sparsity pattern of a tridiagonal matrix with non-zero values only on three diagonals.
Structure of the tridiagonal matrix in the implicit finite difference scheme. Non-zero entries appear only on the main diagonal and the two adjacent diagonals, enabling efficient O(M) solution using the Thomas algorithm.

Implementation

In[13]:
Code
def thomas_algorithm(a, b, c, d):
    """
    Solve tridiagonal system using the Thomas algorithm.

    Solves Ax = d where A has:
        - a: sub-diagonal (length n-1)
        - b: main diagonal (length n)
        - c: super-diagonal (length n-1)
    """
    n = len(b)
    c_prime = np.zeros(n - 1)
    d_prime = np.zeros(n)

    # Forward sweep
    c_prime[0] = c[0] / b[0]
    d_prime[0] = d[0] / b[0]

    for i in range(1, n - 1):
        denom = b[i] - a[i - 1] * c_prime[i - 1]
        c_prime[i] = c[i] / denom
        d_prime[i] = (d[i] - a[i - 1] * d_prime[i - 1]) / denom

    d_prime[n - 1] = (d[n - 1] - a[n - 2] * d_prime[n - 2]) / (
        b[n - 1] - a[n - 2] * c_prime[n - 2]
    )

    # Back substitution
    x = np.zeros(n)
    x[n - 1] = d_prime[n - 1]
    for i in range(n - 2, -1, -1):
        x[i] = d_prime[i] - c_prime[i] * x[i + 1]

    return x


def implicit_european_call(S_max, K, T, r, sigma, M, N):
    """
    Price a European call option using the implicit finite difference method.
    """
    dS = S_max / M
    dt = T / N

    # Grid of stock prices
    S = np.linspace(0, S_max, M + 1)

    # Initialize option values at expiration
    V = np.maximum(S - K, 0)

    # Precompute coefficients for interior points
    j = np.arange(1, M)
    alpha = 0.5 * dt * (sigma**2 * j**2 - r * j)
    beta = dt * (sigma**2 * j**2 + r)
    gamma = 0.5 * dt * (sigma**2 * j**2 + r * j)

    # Build tridiagonal matrix coefficients
    a = -alpha[1:]  # Sub-diagonal (length M-2)
    b = 1 + beta  # Main diagonal (length M-1)
    c = -gamma[:-1]  # Super-diagonal (length M-2)

    # Time stepping
    for n in range(N - 1, -1, -1):
        # Right-hand side
        d = V[1:M].copy()

        # Boundary condition adjustments
        d[0] += alpha[0] * 0  # V[0] = 0 for call
        boundary_M = S_max - K * np.exp(-r * (N - n) * dt)
        d[-1] += gamma[-1] * boundary_M

        # Solve tridiagonal system
        V[1:M] = thomas_algorithm(a, b, c, d)

        # Update boundaries
        V[0] = 0
        V[M] = boundary_M

    return S, V
In[14]:
Code
# Price with implicit method (can use fewer time steps due to unconditional stability)
S_grid, V_implicit = implicit_european_call(S_max, K, T, r, sigma, M, N=500)

idx = np.argmin(np.abs(S_grid - S_0))
price_implicit = V_implicit[idx]
Out[15]:
Console
Implicit FD price at S = 100: $10.4386
Time steps used: 500 (vs 5000 for explicit)

The implicit method yields a price very close to the explicit method but achieves this using only 500 time steps, one-tenth of the effort required for the explicit scheme. This efficiency demonstrates the practical benefit of unconditional stability.

Unconditional Stability

The implicit scheme is unconditionally stable, meaning it produces bounded solutions for any choice of Δt\Delta t and ΔS\Delta S. This property allows you to use much larger time steps, dramatically reducing computation. Although each time step requires solving a linear system, the overall efficiency often surpasses the explicit scheme because far fewer time steps are needed.

The stability comes from the implicit treatment of the spatial derivatives. Mathematically, when we analyze the amplification factor of the numerical scheme, the implicit treatment ensures that errors decay rather than grow over time. Any errors introduced in one time step are damped rather than amplified in subsequent steps. The price we pay for this stability is the need to solve a linear system at each time step, but for tridiagonal systems this cost is minimal.

The Crank-Nicolson Scheme

The Crank-Nicolson scheme represents a middle ground between explicit and implicit methods. It averages the spatial derivatives at times tnt_n and tn+1t_{n+1}, achieving second-order accuracy in time while maintaining unconditional stability. This combination of accuracy and stability makes Crank-Nicolson the method of choice for most practical applications.

The Averaging Approach

Instead of evaluating spatial derivatives entirely at tnt_n (implicit) or tn+1t_{n+1} (explicit), Crank-Nicolson uses the average of both evaluations. This averaging can be motivated by the trapezoidal rule for numerical integration: when integrating the time derivative, we approximate the integral of the spatial operator by averaging its values at the beginning and end of the time interval. The scheme uses:

2VS212[Vj+1n2Vjn+Vj1n(ΔS)2+Vj+1n+12Vjn+1+Vj1n+1(ΔS)2]\frac{\partial^2 V}{\partial S^2} \approx \frac{1}{2}\left[\frac{V_{j+1}^n - 2V_j^n + V_{j-1}^n}{(\Delta S)^2} + \frac{V_{j+1}^{n+1} - 2V_j^{n+1} + V_{j-1}^{n+1}}{(\Delta S)^2}\right]

where:

  • VnV_{\cdot}^n: values at current time step
  • Vn+1V_{\cdot}^{n+1}: values at next time step
  • ΔS\Delta S: price step size

and similarly for the first derivative. This averaging can be viewed as taking a half-step with the explicit scheme and a half-step with the implicit scheme. The explicit part uses known information from the future time step, while the implicit part ensures stability by coupling values at the current time step.

The Discretized Equation

Substituting the approximations into the Black-Scholes PDE:

Vjn+1VjnΔt+12(LjVn+LjVn+1)=0\frac{V_j^{n+1} - V_j^n}{\Delta t} + \frac{1}{2}\left(\mathcal{L}_j V^n + \mathcal{L}_j V^{n+1}\right) = 0

where:

  • LjV\mathcal{L}_j V: spatial operator at node jj, equal to 12σ2Sj22VS2+rSjVSrV\frac{1}{2}\sigma^2 S_j^2 \frac{\partial^2 V}{\partial S^2} + rS_j\frac{\partial V}{\partial S} - rV
  • Vn,Vn+1V^n, V^{n+1}: option values at current and next time steps

The spatial operator Lj\mathcal{L}_j encapsulates all the terms involving spatial derivatives and the discounting term. Applying this operator at both time levels and averaging creates the characteristic Crank-Nicolson structure. Rearranging terms by time step:

αjVj1n+(1+βj)VjnγjVj+1n=αjVj1n+1+(1βj)Vjn+1+γjVj+1n+1-\alpha_j V_{j-1}^n + (1 + \beta_j) V_j^n - \gamma_j V_{j+1}^n = \alpha_j V_{j-1}^{n+1} + (1 - \beta_j) V_j^{n+1} + \gamma_j V_{j+1}^{n+1}

where:

  • LHS: implicit terms at current time tnt_n
  • RHS: explicit terms at next time tn+1t_{n+1}
  • αj,βj,γj\alpha_j, \beta_j, \gamma_j: Crank-Nicolson coefficients

The left side involves unknown values at tnt_n, forming a tridiagonal system just as in the implicit scheme. The right side uses known values at tn+1t_{n+1}, which we can compute explicitly before solving the system. The coefficients are:

αj=Δt4(σ2j2rj)βj=Δt2(σ2j2+r)γj=Δt4(σ2j2+rj)\begin{aligned} \alpha_j &= \frac{\Delta t}{4}\left(\sigma^2 j^2 - rj\right) \\ \beta_j &= \frac{\Delta t}{2}\left(\sigma^2 j^2 + r\right) \\ \gamma_j &= \frac{\Delta t}{4}\left(\sigma^2 j^2 + rj\right) \end{aligned}

where:

  • Δt\Delta t: time step
  • σ\sigma: volatility
  • rr: risk-free rate
  • jj: spatial index

Notice that these coefficients are exactly half of the corresponding implicit scheme coefficients, reflecting the averaging of the spatial operator between two time levels. The left side involves unknown values at tnt_n, while the right side uses known values at tn+1t_{n+1}. This again leads to a tridiagonal system at each time step.

Implementation

In[16]:
Code
def crank_nicolson_european_call(S_max, K, T, r, sigma, M, N):
    """
    Price a European call option using the Crank-Nicolson finite difference method.
    """
    dS = S_max / M
    dt = T / N

    # Grid of stock prices
    S = np.linspace(0, S_max, M + 1)

    # Initialize option values at expiration
    V = np.maximum(S - K, 0)

    # Precompute coefficients for interior points
    j = np.arange(1, M)
    alpha = 0.25 * dt * (sigma**2 * j**2 - r * j)
    beta = 0.5 * dt * (sigma**2 * j**2 + r)
    gamma = 0.25 * dt * (sigma**2 * j**2 + r * j)

    # Tridiagonal matrix coefficients (left side)
    a_left = -alpha[1:]  # Sub-diagonal
    b_left = 1 + beta  # Main diagonal
    c_left = -gamma[:-1]  # Super-diagonal

    # Time stepping
    for n in range(N - 1, -1, -1):
        # Build right-hand side (explicit part)
        d = np.zeros(M - 1)
        d = alpha * V[0 : M - 1] + (1 - beta) * V[1:M] + gamma * V[2 : M + 1]

        # Boundary condition adjustments
        boundary_0 = 0
        boundary_M = S_max - K * np.exp(-r * (N - n) * dt)

        d[0] += alpha[0] * boundary_0
        d[-1] += gamma[-1] * boundary_M

        # Solve tridiagonal system
        V[1:M] = thomas_algorithm(a_left, b_left, c_left, d)

        # Update boundaries
        V[0] = boundary_0
        V[M] = boundary_M

    return S, V
In[17]:
Code
# Price with Crank-Nicolson method
S_grid, V_cn = crank_nicolson_european_call(S_max, K, T, r, sigma, M, N=500)

idx = np.argmin(np.abs(S_grid - S_0))
price_cn = V_cn[idx]
Out[18]:
Console
Crank-Nicolson FD price at S = 100: $10.4407

The Crank-Nicolson price is consistent with the other methods. By averaging the explicit and implicit approaches, it maintains stability while improving accuracy, effectively reducing the bias introduced by time discretization.

Accuracy Analysis

The Crank-Nicolson scheme achieves O((Δt)2)O((\Delta t)^2) accuracy in time, compared to O(Δt)O(\Delta t) for both the explicit and implicit schemes. This means that halving the time step reduces the time discretization error by a factor of four rather than two. The improved accuracy comes from the averaging, which cancels the leading-order error terms in the Taylor expansion. For most applications, Crank-Nicolson provides the best balance of accuracy, stability, and computational efficiency.

Out[19]:
Visualization
Line plot showing European call option values versus stock price for explicit, implicit, and Crank-Nicolson schemes.
Comparison of the three finite difference schemes for pricing a European call option. All three methods produce similar option value curves, but they differ in computational requirements and accuracy. The explicit scheme requires many more time steps to maintain stability.

Boundary Conditions

Proper boundary conditions are essential for finite difference methods. They determine the option value at the edges of the computational grid, where the standard update formulas cannot be applied directly because they would require values outside the grid.

Terminal Condition

At expiration (t=Tt = T), the option value equals its payoff. This is the fundamental condition that initializes our backward-in-time computation:

V(S,T)=max(SK,0)(call)V(S,T)=max(KS,0)(put)\begin{aligned} V(S, T) &= \max(S - K, 0) && \text{(call)} \\ V(S, T) &= \max(K - S, 0) && \text{(put)} \end{aligned}

where:

  • V(S,T)V(S,T): option value at expiration
  • SS: stock price at expiration
  • KK: strike price

This condition initializes the grid before we begin the backward time-stepping process. The non-smooth nature of the payoff function at S=KS = K can introduce complications, as the second derivative is undefined at that point. You might sometimes smooth the payoff near the strike to improve numerical behavior.

Boundary at S=0S = 0

When the underlying asset price is zero, it remains at zero forever (assuming geometric Brownian motion). This follows because geometric Brownian motion has the property that zero is an absorbing state: if dS=μSdt+σSdWdS = \mu S dt + \sigma S dW, then S=0S = 0 implies dS=0dS = 0. Therefore:

  • Call option:

    V(0,t)=0V(0, t) = 0

    where:

    • V(0,t)V(0, t): option value at asset price zero

    (a call is worthless if the stock is worthless)

  • Put option:

    V(0,t)=Ker(Tt)V(0, t) = Ke^{-r(T-t)}

    where:

    • V(0,t)V(0, t): option value at asset price zero
    • KK: strike price
    • rr: risk-free interest rate
    • TtT-t: time to expiration

    (a put pays KK at expiration with certainty)

Boundary at S=SmaxS = S_{\max}

At very high stock prices, we need to specify the option value at the upper edge of the grid. The exact value depends on the type of option:

  • Call option:

    V(Smax,t)SmaxKer(Tt)V(S_{\max}, t) \approx S_{\max} - Ke^{-r(T-t)}

    where:

    • V(Smax,t)V(S_{\max}, t): option value at maximum grid price
    • SmaxS_{\max}: maximum asset price on the grid
    • KK: strike price
    • rr: risk-free interest rate
    • TtT-t: time to expiration

    (deep in-the-money, behaves like a forward)

  • Put option:

    V(Smax,t)0V(S_{\max}, t) \approx 0

    where:

    • V(Smax,t)V(S_{\max}, t): option value at maximum grid price

    (deep out-of-the-money)

The boundary at SmaxS_{\max} requires some care. If SmaxS_{\max} is chosen too small, the boundary condition may distort the solution in the region of interest. A rule of thumb is to set Smax3KS_{\max} \geq 3K to 5K5K for typical volatilities.

Implementing Put Option Boundaries

In[20]:
Code
def crank_nicolson_european_put(S_max, K, T, r, sigma, M, N):
    """
    Price a European put option using Crank-Nicolson.
    """
    dS = S_max / M
    dt = T / N

    S = np.linspace(0, S_max, M + 1)

    # Terminal condition: put payoff
    V = np.maximum(K - S, 0)

    # Coefficients
    j = np.arange(1, M)
    alpha = 0.25 * dt * (sigma**2 * j**2 - r * j)
    beta = 0.5 * dt * (sigma**2 * j**2 + r)
    gamma = 0.25 * dt * (sigma**2 * j**2 + r * j)

    a_left = -alpha[1:]
    b_left = 1 + beta
    c_left = -gamma[:-1]

    for n in range(N - 1, -1, -1):
        d = alpha * V[0 : M - 1] + (1 - beta) * V[1:M] + gamma * V[2 : M + 1]

        # Put boundary conditions
        boundary_0 = K * np.exp(-r * (N - n) * dt)  # PV of strike
        boundary_M = 0

        d[0] += alpha[0] * boundary_0
        d[-1] += gamma[-1] * boundary_M

        V[1:M] = thomas_algorithm(a_left, b_left, c_left, d)

        V[0] = boundary_0
        V[M] = boundary_M

    return S, V

Convergence Analysis and Method Comparison

Now let's compare all three finite difference methods against the analytical Black-Scholes formula to assess their accuracy and convergence properties.

In[21]:
Code
from scipy.stats import norm


def black_scholes_call(S, K, T, r, sigma):
    """Analytical Black-Scholes call price."""
    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)


# Analytical price
bs_price = black_scholes_call(S_0, K, T, r, sigma)
Out[22]:
Console
Black-Scholes analytical price: $10.450584

Finite difference prices:
  Explicit (N=5000):        $10.440896  Error: $0.009688
  Implicit (N=500):         $10.438580  Error: $0.012004
  Crank-Nicolson (N=500):   $10.440686  Error: $0.009897

All three methods converge to the analytical Black-Scholes price. The Crank-Nicolson scheme achieves an error comparable to the implicit scheme in this snapshot, but with better theoretical convergence properties that become apparent on finer grids.

Convergence Study

Let's examine how the error decreases as we refine the grid. This reveals the order of convergence for each method.

In[23]:
Code
# Convergence study
M_values = [25, 50, 100, 200]
errors_cn = []
errors_implicit = []

for m_sim in M_values:
    n_sim = 10 * m_sim  # Keep ratio fixed

    # Crank-Nicolson
    S_grid, V = crank_nicolson_european_call(
        S_max, K, T, r, sigma, m_sim, n_sim
    )
    idx = np.argmin(np.abs(S_grid - S_0))
    errors_cn.append(abs(V[idx] - bs_price))

    # Implicit
    S_grid, V = implicit_european_call(S_max, K, T, r, sigma, m_sim, n_sim)
    idx = np.argmin(np.abs(S_grid - S_0))
    errors_implicit.append(abs(V[idx] - bs_price))
Out[24]:
Visualization
Log-log plot showing error versus number of price steps for implicit and Crank-Nicolson schemes.
Convergence of finite difference methods as the grid is refined. The Implicit scheme exhibits first-order convergence ($O(1/M)$), while the Crank-Nicolson scheme demonstrates second-order convergence ($O(1/M^2)$) due to its superior time accuracy, resulting in significantly smaller errors.

The convergence plot reveals that both methods achieve approximately first-order convergence in the spatial discretization. Crank-Nicolson typically produces smaller absolute errors due to its superior time accuracy. The implicit scheme's errors are slightly larger but within the same order of magnitude.

Pricing American Options

American options are a major application for finite difference methods because they can be exercised at any time. The analytical Black-Scholes formula doesn't apply to American options, making numerical methods essential.

The Early Exercise Constraint

For an American option, at each point in time we must check whether early exercise is optimal. The key insight is that no rational holder would keep an option whose continuation value (the value if held) falls below its intrinsic value (the value if exercised immediately). The option value cannot fall below its intrinsic value:

V(S,t)max(SK,0)(American call)V(S,t)max(KS,0)(American put)\begin{aligned} V(S, t) &\geq \max(S - K, 0) && \text{(American call)} \\ V(S, t) &\geq \max(K - S, 0) && \text{(American put)} \end{aligned}

where:

  • V(S,t)V(S,t): American option value
  • SS: current stock price
  • KK: strike price

In finite difference terms, after each backward time step, we apply:

Vjn=max(Vjn,payoff(Sj))V_j^n = \max(V_j^n, \text{payoff}(S_j))

where:

  • VjnV_j^n: option continuation value computed by the FD scheme
  • payoff(Sj)\text{payoff}(S_j): immediate exercise value (e.g., KSjK-S_j for a put)
  • max\max: operator selecting the higher of holding or exercising

This simple modification converts our European option pricer into an American option pricer. This step implements the dynamic programming principle: at each state, we compare the value of waiting (continuation value) against the value of acting now (exercise value) and rationally choose the maximum. The finite difference framework handles this naturally because we solve backward in time, determining the optimal decision at each grid point given knowledge of future values.

Implementation

In[25]:
Code
def crank_nicolson_american_put(S_max, K, T, r, sigma, M, N):
    """
    Price an American put option using Crank-Nicolson with early exercise.
    """
    dS = S_max / M
    dt = T / N

    S = np.linspace(0, S_max, M + 1)

    # Terminal condition
    V = np.maximum(K - S, 0)
    payoff = V.copy()  # Store payoff for early exercise comparison

    # Coefficients
    j = np.arange(1, M)
    alpha = 0.25 * dt * (sigma**2 * j**2 - r * j)
    beta = 0.5 * dt * (sigma**2 * j**2 + r)
    gamma = 0.25 * dt * (sigma**2 * j**2 + r * j)

    a_left = -alpha[1:]
    b_left = 1 + beta
    c_left = -gamma[:-1]

    # Track early exercise boundary
    exercise_boundary = []

    for n in range(N - 1, -1, -1):
        d = alpha * V[0 : M - 1] + (1 - beta) * V[1:M] + gamma * V[2 : M + 1]

        boundary_0 = K * np.exp(-r * (N - n) * dt)
        boundary_M = 0

        d[0] += alpha[0] * boundary_0
        d[-1] += gamma[-1] * boundary_M

        V[1:M] = thomas_algorithm(a_left, b_left, c_left, d)
        V[0] = boundary_0
        V[M] = boundary_M

        # Apply early exercise constraint
        V = np.maximum(V, payoff)

        # Find exercise boundary (highest S where early exercise is optimal)
        exercise_idx = np.where(V == payoff)[0]
        if len(exercise_idx) > 0:
            exercise_boundary.append((n * dt, S[exercise_idx[-1]]))

    return S, V, exercise_boundary
In[26]:
Code
# Price American put
S_grid, V_american, boundary = crank_nicolson_american_put(
    S_max, K, T, r, sigma, M, 500
)

# Also price European put for comparison
S_grid, V_european = crank_nicolson_european_put(S_max, K, T, r, sigma, M, 500)

idx = np.argmin(np.abs(S_grid - S_0))
price_american_put = V_american[idx]
price_european_put = V_european[idx]
early_exercise_premium = price_american_put - price_european_put
Out[27]:
Console
American put price at S = 100: $6.0780
European put price at S = 100: $5.5636
Early exercise premium: $0.5144

The early exercise premium represents the additional value an American put holder gains from the flexibility to exercise early. This premium is positive for puts on non-dividend-paying stocks because the put holder can capture the strike price immediately rather than waiting and earning interest on the proceeds.

Out[28]:
Visualization
Line plot comparing American and European put values across stock prices with the early exercise premium shown as shaded region.
Comparison of American and European put option values. The American put is always worth at least as much as the European put due to the early exercise feature. The difference (shaded region) represents the early exercise premium, which is most significant for in-the-money options.

Visualizing the Early Exercise Boundary

Out[29]:
Visualization
Plot showing the early exercise boundary as a curve separating the exercise and continuation regions.
Early exercise boundary for an American put option. The shaded region shows where immediate exercise is optimal. Below this boundary, the option holder should exercise; above it, they should continue holding the option.

The early exercise boundary shows the critical stock price below which immediate exercise is optimal. As time to expiration decreases, this boundary rises toward the strike price because the time value of waiting diminishes.

Comparing Methods: A Comprehensive View

Let's bring together all the numerical methods we've covered for pricing options: binomial trees, Monte Carlo simulation, and finite difference methods.

In[30]:
Code
def binomial_european_call(S, K, T, r, sigma, N):
    """Price European call using binomial tree."""
    dt = T / N
    u = np.exp(sigma * np.sqrt(dt))
    d = 1 / u
    p = (np.exp(r * dt) - d) / (u - d)

    # Terminal payoffs
    payoffs = np.array(
        [max(S * u**j * d ** (N - j) - K, 0) for j in range(N + 1)]
    )

    # Backward induction
    for i in range(N - 1, -1, -1):
        payoffs = np.exp(-r * dt) * (
            p * payoffs[1 : i + 2] + (1 - p) * payoffs[0 : i + 1]
        )

    return payoffs[0]


def monte_carlo_european_call(S, K, T, r, sigma, n_paths=100000, seed=42):
    """Price European call using Monte Carlo simulation."""
    np.random.seed(seed)
    Z = np.random.standard_normal(n_paths)
    S_T = S * np.exp((r - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * Z)
    payoffs = np.maximum(S_T - K, 0)
    return np.exp(-r * T) * payoffs.mean()
In[31]:
Code
# Compute prices using all methods
S = S_0  # Use scalar spot price for comparison

price_bs = black_scholes_call(S, K, T, r, sigma)
price_binomial = binomial_european_call(S, K, T, r, sigma, N=500)
price_mc = monte_carlo_european_call(S, K, T, r, sigma)

# Finite difference prices already computed
S_grid, V_cn_final = crank_nicolson_european_call(
    S_max, K, T, r, sigma, M, N=500
)
idx = np.argmin(np.abs(S_grid - S))
price_fd = V_cn_final[idx]
Out[32]:
Console
European Call Option Price Comparison
==================================================
Parameters: S=100, K=100.0, T=1.0, r=0.05, σ=0.2
==================================================
Method                         Price        Error
--------------------------------------------------
Black-Scholes (exact)     $  10.4506            -
Binomial Tree (N=500)     $  10.4466 $  0.003998
Monte Carlo (100K paths)  $  10.4739 $  0.023308
Crank-Nicolson FD         $  10.4407 $  0.009897

The Finite Difference (Crank-Nicolson) method matches the exact Black-Scholes price with high precision, comparable to the Binomial Tree. Unlike Monte Carlo, which shows stochastic variation, the finite difference result is deterministic and stable for this 1D problem.

Out[33]:
Visualization
Bar chart comparing pricing errors for binomial tree, Monte Carlo, and finite difference methods.
Comparison of pricing errors for a European call option across three numerical methods. The deterministic methods (Binomial Tree and Crank-Nicolson) achieve high precision relative to the Black-Scholes benchmark, while the Monte Carlo method (100,000 paths) exhibits a larger residual error due to its stochastic nature.

Method Characteristics

Each numerical method has distinct strengths and weaknesses:

Binomial Trees:

  • Intuitive, based on discrete price movements
  • Natural for American options with early exercise
  • Accuracy limited by number of time steps
  • Convergence can be non-monotonic

Monte Carlo Simulation:

  • Excellent for path-dependent options
  • Easily handles multiple underlying assets (high dimensions)
  • Statistical error decreases slowly (O(1/n)O(1/\sqrt{n}))
  • Less natural for American options (though methods like Longstaff-Schwartz exist)

Finite Difference Methods:

  • Directly solves the pricing PDE
  • Very efficient for one or two underlying assets
  • Natural for American options and barrier options
  • Difficulty increases rapidly with dimension

When to Use Each Method

The choice of numerical method depends on the specific option being priced:

  • European options on single assets: All methods work well; analytical formulas (when available) are fastest
  • American options: Finite difference methods and binomial trees are natural choices
  • Barrier options: Finite difference methods handle absorbing boundaries elegantly
  • Path-dependent options (Asian, lookback): Monte Carlo is typically preferred
  • Multi-asset options: Monte Carlo scales better to high dimensions

We'll explore exotic options and their pricing in the next chapter, where we'll see how these numerical methods adapt to more complex payoff structures.

Key Parameters

The key parameters for finite difference option pricing are:

  • S: Current stock price. Represents the underlying asset value at any grid point.
  • K: Strike price. Determines the option payoff at expiration and intrinsic value.
  • T: Time to expiration. The domain over which the PDE is solved.
  • r: Risk-free interest rate. Used for drift in the PDE and discounting.
  • σ\sigma: Volatility. Determines the diffusion coefficient in the Black-Scholes PDE.
  • M: Number of price steps. Determines spatial resolution (ΔSΔS).
  • N: Number of time steps. Determines temporal resolution (ΔtΔt) and stability.

Practical Considerations

Implementing finite difference methods in practice requires attention to several technical details that affect both accuracy and computational performance.

Grid Design

The accuracy of finite difference solutions depends heavily on grid design. Key considerations include:

Price grid spacing: Finer grids near the strike price improve accuracy where option values change most rapidly. Non-uniform grids can concentrate points where they matter most.

Grid boundaries: SmaxS_{\max} should be large enough that the boundary condition is accurate. For deep out-of-the-money regions, the option value is near zero regardless of the exact boundary.

Time steps: Even unconditionally stable methods benefit from reasonable time step sizes. Too few steps introduce significant discretization error.

Computational Efficiency

For production systems, several optimizations are common:

  • Sparse matrix libraries: Tridiagonal solvers are highly optimized in numerical libraries
  • Adaptive grids: Concentrate grid points near regions of interest
  • Coordinate transformations: Log-price coordinates equalize effective spacing
  • Parallel implementation: Multiple option valuations can proceed simultaneously

Limitations and Impact

Finite difference methods transformed quantitative finance by providing a general-purpose framework for solving the pricing PDEs that arise from no-arbitrage arguments. Before these methods became practical, traders relied heavily on analytical formulas that exist only for simple payoffs, or on binomial trees that converge slowly. The development of stable, accurate finite difference schemes enabled rapid and reliable pricing of American options, barrier options, and other derivatives where analytical solutions don't exist.

However, finite difference methods face fundamental limitations. The most significant is the "curse of dimensionality": computational cost grows exponentially with the number of underlying assets. A one-dimensional grid with 100 points becomes a two-dimensional grid with 10,000 points, a three-dimensional grid with 1,000,000 points, and so on. Beyond two or three dimensions, Monte Carlo methods typically dominate despite their slower convergence rate.

Boundary conditions also require care. The assumption that V(Smax,t)SmaxKer(Tt)V(S_{\max}, t) \approx S_{\max} - Ke^{-r(T-t)} for deep in-the-money calls is an approximation that can introduce errors if SmaxS_{\max} is not chosen sufficiently large. Similarly, for options with complex features like barriers or early exercise, ensuring that boundary conditions accurately reflect the true option behavior requires domain expertise.

Numerical stability, while guaranteed for implicit and Crank-Nicolson schemes, doesn't guarantee accuracy. Coarse grids can produce stable but inaccurate solutions. You must balance computational cost against required precision, often through convergence studies like those presented above.

Despite these limitations, finite difference methods remain essential tools in quantitative finance. They provide deterministic, reproducible prices without the statistical noise of Monte Carlo simulation. For low-dimensional problems, they are typically faster and more accurate than alternatives. And they offer a direct numerical solution to the fundamental pricing PDE, providing intuition about how option values evolve across price and time.

Summary

This chapter showed how to use finite difference methods to solve the Black-Scholes PDE. These methods are a powerful alternative to binomial trees and Monte Carlo simulation.

The key concepts and takeaways are:

  • Grid discretization transforms the continuous PDE into a system of algebraic equations by approximating derivatives with finite differences on a mesh of price and time points

  • The explicit scheme is conceptually simple, directly calculating option values at the current time step from values at the next step, but suffers from severe stability constraints requiring very small time steps

  • The implicit scheme evaluates spatial derivatives at the current time step, leading to a tridiagonal linear system that must be solved at each step, but gains unconditional stability allowing much larger time steps

  • The Crank-Nicolson scheme averages explicit and implicit approaches, achieving second-order accuracy in time while maintaining unconditional stability, making it the preferred choice for most applications

  • Boundary conditions at S=0S = 0, S=SmaxS = S_{\max}, and at expiration must be specified carefully to ensure accurate solutions; for calls, V(0,t)=0V(0,t) = 0 and V(Smax,t)SmaxKer(Tt)V(S_{\max}, t) \approx S_{\max} - Ke^{-r(T-t)}

  • American options require checking the early exercise constraint after each time step, setting Vjn=max(Vjn,payoff(Sj))V_j^n = \max(V_j^n, \text{payoff}(S_j)) to ensure the option value never falls below intrinsic value

  • Convergence properties differ across methods: explicit and implicit schemes have first-order time accuracy while Crank-Nicolson achieves second-order accuracy

  • Method selection depends on the problem: finite difference methods excel for low-dimensional problems with early exercise or barriers, while Monte Carlo dominates for high-dimensional or path-dependent options

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about finite difference methods for option pricing.

Loading component...

Reference

BIBTEXAcademic
@misc{finitedifferencemethodsforoptionpricingpdesolutions, author = {Michael Brenndoerfer}, title = {Finite Difference Methods for Option Pricing: PDE Solutions}, year = {2025}, url = {https://mbrenndoerfer.com/writing/finite-difference-methods-option-pricing-numerical-pde}, organization = {mbrenndoerfer.com}, note = {Accessed: 2025-01-01} }
APAAcademic
Michael Brenndoerfer (2025). Finite Difference Methods for Option Pricing: PDE Solutions. Retrieved from https://mbrenndoerfer.com/writing/finite-difference-methods-option-pricing-numerical-pde
MLAAcademic
Michael Brenndoerfer. "Finite Difference Methods for Option Pricing: PDE Solutions." 2026. Web. today. <https://mbrenndoerfer.com/writing/finite-difference-methods-option-pricing-numerical-pde>.
CHICAGOAcademic
Michael Brenndoerfer. "Finite Difference Methods for Option Pricing: PDE Solutions." Accessed today. https://mbrenndoerfer.com/writing/finite-difference-methods-option-pricing-numerical-pde.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Finite Difference Methods for Option Pricing: PDE Solutions'. Available at: https://mbrenndoerfer.com/writing/finite-difference-methods-option-pricing-numerical-pde (Accessed: today).
SimpleBasic
Michael Brenndoerfer (2025). Finite Difference Methods for Option Pricing: PDE Solutions. https://mbrenndoerfer.com/writing/finite-difference-methods-option-pricing-numerical-pde