Back to Writing Finite Differences and PSOR: Solving the American Put PDE

Finite Differences and PSOR: Solving the American Put PDE

How do you turn “exercise whenever it is optimal” into equations a computer can solve? The previous post answered with backward induction on a binomial tree. Here we take the continuous-time route: start from the Black-Scholes PDE, convert early exercise into an inequality constraint, and solve the resulting system on a grid.

Finite difference methods take a different approach. Instead of discretising the stochastic process, they discretise the partial differential equation directly. The result is a system of equations on a spatial grid — and for American options, that system has a twist: it must simultaneously satisfy the PDE and the exercise constraint.

That twist is the linear complementarity problem. This post develops it one layer at a time: first the economic intuition, then the finite-difference stencil, then the Projected SOR (PSOR) iteration. By the end, you should be able to explain every term in the implementation, diagnose convergence, and recover the early-exercise boundary from the solution.

This is Part 3 of the American Options sub-series.

Contents


From Tree to Grid

Assume a non-dividend-paying stock follows the Black-Scholes model with constant non-negative interest rate and volatility . The PDE for a European option is:

For a European option, this PDE holds on the entire domain with a fixed terminal condition .

For an American put, the holder can stop the contract at any time and receive the intrinsic value

This creates two non-negotiable conditions. First, the option can never be worth less than immediate exercise, so . Second, in any region where waiting is optimal, the usual Black-Scholes PDE must hold: otherwise a locally riskless arbitrage would remain.

Where exercise is optimal, the PDE need not hold with equality. The option has been pinned to the obstacle , and waiting must not be more valuable than stopping. It is useful to define the calendar-time PDE residual

Then , , and at least one of them must be zero at every point. That gives the linear complementarity problem (LCP):

The minimum is taken pointwise. Equivalently,

The last equation is the complementarity condition. It says that the PDE residual and the value-above-exercise gap cannot both be strictly positive:

Region Value gap PDE residual Interpretation
Continuation Waiting has value, so the PDE holds exactly.
Exercise The value is pinned to immediate exercise.

For a quick sign check, consider the in-the-money exercise payoff . It has , , and , so . Economically, waiting to receive sacrifices interest on the strike; that is precisely why early exercise can be valuable for a put when rates are positive.

The boundary between the two regions is the unknown free boundary . Under the usual regularity assumptions it satisfies value matching and smooth pasting:

We will not impose on the grid. The LCP will decide which nodes exercise, and the boundary will emerge from those decisions.

Why Switch to Time to Maturity?

Pricing runs backward in calendar time: the payoff is known at , but the desired price is at . Define time to maturity and . Since , the continuation PDE becomes

$$ U_\tau=\mathcal L U,\qquad \mathcal LU=\frac{1}{2}\sigma^2S^2U_{SS}+rSU_S-rU.$$

Now the computation moves forward in from the known initial condition to . The obstacle form becomes


Crank-Nicolson Discretisation

Finite difference methods replace the continuous domain with a finite set of nodes. Choose a large but finite and define:

  • for with
  • for , where is maturity and is today
  • , with

The stock-price domain has been truncated from to . This introduces a domain error that is separate from the grid-spacing error; later we will vary and independently.

At an interior node, use the three-point central differences

Substituting them into gives a three-node stencil:

where

This formula is the bridge from the differential operator to a matrix: , , and become the lower, main, and upper diagonals.

Why Crank-Nicolson?

Forward Euler would evaluate only at the known layer . Backward Euler would evaluate it only at the unknown layer . Crank-Nicolson averages the two:

It is implicit, so every new node depends on its neighbours at the new layer. For smooth solutions it is second-order accurate in both and , while avoiding the severe time-step restriction of an explicit scheme. Rearranging the interior-node equation gives

with:

On the uniform grid , these simplify to

That simplified form is useful for checking an implementation, but the form generalises more naturally to non-uniform coordinates and other models.

From the Stencil to a Matrix

For a European option, this is a tridiagonal linear system, solvable with the Thomas algorithm in work per time step.

For the interior unknown vector , the matrix has the structure

The vector contains the right-hand side built from the known layer plus any boundary contributions. For an American put we use

The old left boundary already appears in on the explicit side. Moving the new left-boundary term off the matrix adds another to the first entry of . The upper boundary contributes zero under the far-field approximation.

For an American option, let be the tridiagonal matrix on the left and the right-hand side after boundary terms are included. Each new layer must satisfy the discrete LCP

A plain tridiagonal solve enforces everywhere, including nodes where exercise should bind. Clipping that solution to the payoff only once is also insufficient: changing one node changes the neighbouring matrix equations. The linear equations and the obstacle constraint must be solved together.


Projected SOR (PSOR)

PSOR is easiest to understand as “solve locally, relax, then enforce the contract.” During sweep , the algorithm visits the interior nodes from left to right.

First compute the Gauss-Seidel value that would satisfy row of the linear system. It uses the already-updated left neighbour and the not-yet-updated right neighbour:

Then blend that candidate with the current iterate:

Finally project onto the exercise constraint:

The word projected refers exactly to this last maximum. The update can never finish below the payoff. Repeating the sweep lets the projected nodes and their neighbours adjust to one another until both the matrix inequality and the obstacle are satisfied.

The relaxation parameter controls how far the iteration moves:

  • : under-relaxation; more cautious, usually slower.
  • : projected Gauss-Seidel.
  • : over-relaxation; often faster when tuned well.

One Complete Time Step

At each new time-to-maturity layer, the solver performs the same sequence:

  1. Treat as known and build .
  2. Start the unknown layer from $U^m$; this warm start is usually close because adjacent time levels differ only slightly.
  3. Sweep through the stock-price nodes with the SOR update and payoff projection.
  4. Repeat until the iterate change and the complementarity residual are both small.
  5. Store the converged layer as and advance in .

For a discrete candidate , define

The LCP requires , , and at every node. A compact numerical check is therefore

where the minimum is componentwise. Unlike iterate change alone, this tests the equations we actually want to solve. The implementation below requires both checks and raises an error if PSOR reaches its iteration cap.

import numpy as np


def cn_psor_american_put(S0, K, r, sigma, T,
                         N_S=100, N_t=100, S_max_factor=3.0,
                         omega=1.2, psor_iters=500, tol=1e-8):
    if S0 <= 0.0 or K <= 0.0 or sigma <= 0.0 or T <= 0.0 or r < 0.0:
        raise ValueError("Use S0, K, sigma, T > 0 and r >= 0")
    if N_S < 3 or N_t < 1 or S_max_factor <= 1.0:
        raise ValueError("Use N_S >= 3, N_t >= 1, S_max_factor > 1")
    if not (0.0 < omega < 2.0):
        raise ValueError("omega must be between 0 and 2")

    S_max = S_max_factor * max(S0, K)
    dS    = S_max / N_S
    dt    = T / N_t
    S     = np.linspace(0, S_max, N_S + 1)
    n_int = N_S - 1

    def payoff(S_):
        return np.maximum(K - S_, 0.0)

    V = payoff(S).copy()  # tau=0: payoff at maturity
    S_int = np.arange(1, N_S, dtype=float) * dS

    # Crank-Nicolson coefficients from the derivation above
    a = 0.25*dt*(sigma**2*S_int**2/dS**2 - r*S_int/dS)
    b = 1.0 + 0.5*dt*(sigma**2*S_int**2/dS**2 + r)
    c = 0.25*dt*(sigma**2*S_int**2/dS**2 + r*S_int/dS)

    for _ in range(N_t):
        V_old = V.copy()
        rhs = a*V_old[:-2] + (2.0-b)*V_old[1:-1] + c*V_old[2:]
        rhs[0] += a[0]*K   # implicit contribution from V(0, tau)=K

        obstacle = payoff(S_int)
        V_int = V_old[1:-1].copy()  # warm start from previous layer

        def lcp_residual(x):
            # Apply the interior tridiagonal matrix A. Boundary terms
            # have already been moved into rhs.
            Ax = b*x.copy()
            Ax[1:] -= a[1:]*x[:-1]
            Ax[:-1] -= c[:-1]*x[1:]
            return float(np.max(np.abs(np.minimum(Ax-rhs, x-obstacle))))

        converged = False
        for _ in range(psor_iters):
            max_chg = 0.0
            for k in range(n_int):
                left_  = V_int[k-1] if k > 0 else 0.0  # a[0]*K is already in rhs[0]
                right_ = V_int[k+1] if k < n_int-1 else 0.0
                V_sor  = (rhs[k] + a[k]*left_ + c[k]*right_) / b[k]
                V_upd  = V_int[k] + omega*(V_sor - V_int[k])
                V_upd  = max(V_upd, obstacle[k])   # projection step
                max_chg = max(max_chg, abs(V_upd - V_int[k]))
                V_int[k] = V_upd
            if max_chg < tol and lcp_residual(V_int) < tol:
                converged = True
                break

        if not converged:
            raise RuntimeError("PSOR failed to converge")

        V[0] = K
        V[-1] = 0.0
        V[1:-1] = V_int

    return float(np.interp(S0, S, V))

For , , , and , the grid returns about . The corresponding European Black-Scholes put is about , so the roughly difference is the early-exercise premium at this grid resolution. This gives a useful sanity chain:

and, independently, because the American holder can exercise immediately.

The boundary conditions are:

  • : an American put worth immediately (exercise now, stock can never recover from zero) →
  • : an approximation to the far-field condition as

The upper boundary is a truncation, not an exact condition at arbitrary finite . Increase and refine the grid to confirm that truncation error is negligible for the contract being priced.

Central differencing also needs care when drift dominates diffusion near the left edge: the coefficient can become negative, so the discrete operator need not be monotone. Log-price coordinates, non-uniform grids, or an upwind/fitted treatment of the first derivative are common remedies when this affects the contract or parameter regime.

What the Code Is Really Solving

The outer loop evolves the option from maturity to today. The inner PSOR loop resolves the exercise decision at one time layer. Those are different convergence problems:

  • Increasing reduces time-discretisation error.
  • Increasing reduces spatial-discretisation error.
  • Increasing checks domain-truncation error.
  • Tightening tol reduces algebraic error from the iterative LCP solve.

Changing all four at once can hide the source of an error. A disciplined convergence study varies one control at a time.


The Option Value Surface

If each time layer is retained instead of overwritten, CN-PSOR gives the option value surface as well as today’s price.

Contour plot of American put value V(S,t). Value rises as the stock price falls. Colour encodes option value; identifying the exercise boundary requires comparing V with intrinsic value.
American put value surface V(S,t) from CN-PSOR (N_S=150, N_t=100). K=100, r=5%, σ=20%, T=1y. Colour shows value, not the exercise decision directly.

The two regimes are identified by the gap :

  • Continuation region (to the right of the boundary before maturity): , so the option has positive continuation value.
  • Exercise region (to the left of the boundary): , so the surface is linear in .

At maturity, everywhere; “exercise” versus “continue” is no longer a meaningful choice.


The Free Boundary from PSOR

The free boundary is extracted from the PSOR solution by identifying, at each time step, the largest in-the-money node where the exercise constraint binds. Numerically, use a small gap tolerance and require :

The in-the-money test matters: far out-of-the-money nodes can have both payoff and computed value close to zero, but they are not part of the put’s exercise region.

Why take the largest exercise node? For a vanilla put, exercise occurs at low stock prices. At a fixed time, the grid typically looks like

The rightmost exercise node is therefore the node-level estimate of . Because it can only land on grid points, the extracted curve is stair-stepped. A finer grid reduces those jumps; interpolation using the obstacle gap or the smooth-pasting condition can produce a sub-grid estimate, but it does not replace a convergence study.

Free exercise boundary from CN-PSOR compared with a CRR(500) estimate. The node-based curves are closely aligned across most of the time interval.
Free-boundary estimates from CN-PSOR (dashed cyan) and CRR(500) (solid amber), plotted against calendar time. S₀=K=100, r=5%, σ=20%, T=1y.

The CN-PSOR boundary closely tracks the CRR(500) comparator. Small differences arise because both methods discretise the boundary on different node sets. Agreement between two discretisations is a useful cross-check, not proof that either boundary is exact.

The shape also has an economic interpretation. With little time remaining, the critical price rises toward the strike because there is less optionality left to preserve. Farther from maturity, the holder is willing to tolerate a lower stock price before exercising because the remaining time value is larger.


Grid Convergence

For a sufficiently smooth solution, central spatial differences and Crank-Nicolson have formal truncation error . An American put is less tidy: the payoff has a kink, the free boundary reduces regularity, the domain is truncated, and the PSOR tolerance adds algebraic error. Raw grid errors can therefore be non-monotone even when the asymptotic error envelope is close to second order.

Log-log plot of CN-PSOR absolute differences from a CRR(2000) comparator. Errors decrease overall but oscillate with grid alignment around an O(1/N²) guide line.
Absolute difference from CRR(2000) as N_S=N_t increases. The dashed O(1/N²) line is a visual guide, not a fitted convergence rate; grid-alignment oscillations are visible.

The computed values behind the chart reproduce as follows. “Error” means absolute difference from CRR(2000), not error against an exact American-option value:

The comparison table for the standard at-the-money American put:

Grid CN-PSOR price Difference vs CRR(2000)
20 6.3707 0.2808
50 6.1367 0.0467
100 6.0997 0.0097
200 6.0925 0.0025
CRR(2000) comparator 6.0900

The aligned 50→100→200 refinement sequence is consistent with roughly second-order decay, but the omitted 30, 75, and 150 grids oscillate rather than decrease monotonically. For more reliable convergence studies, use a better-converged independent benchmark, verify separately, and consider Rannacher startup steps (a few backward-Euler substeps before Crank-Nicolson) to damp payoff-kink oscillations.

Separate the Four Error Sources

A reported “finite-difference error” is usually a mixture:

Error source Controlled by Practical test
Domain truncation Increase while keeping approximately fixed.
Spatial discretisation Increase at fixed .
Time discretisation Increase at fixed spatial grid.
Algebraic LCP solve PSOR tolerance and iteration cap Tighten tol until the price no longer changes at the desired precision.

This separation matters. For example, increasing while holding S_max_factor fixed reduces , but increasing S_max_factor while holding fixed makes larger. The second change improves the far boundary and worsens local resolution at the same time.

Numerical Sanity Checks

A solver can converge numerically and still solve the wrong discretised problem. For a vanilla American put, check these invariants:

  • Obstacle: at every node.
  • Complementarity: is below tolerance at every time layer.
  • Bounds: for .
  • Shape in stock price: the put value should be non-increasing and normally convex in .
  • European comparison: the American put should not be worth less than the otherwise identical European put.
  • Boundary policy: in-the-money nodes should form one contiguous exercise region at the low-$S$ end for this vanilla one-factor case.
  • Refinement: the price should stabilise as the domain, spatial grid, time grid, and PSOR tolerance are refined separately.

These checks catch common bugs such as a reversed time direction, a missing implicit boundary contribution, the wrong sign on or , and false exercise detection at zero-payoff nodes.


Choosing the Over-Relaxation Parameter

The parameter controls the SOR convergence rate. For , the algorithm reduces to Gauss-Seidel (standard sequential updates). For , updates are "over-relaxed" — each iterate moves further in the direction of the correction.

The optimal depends on the iteration matrix and therefore on the grid, time step, and model coefficients. Values around are reasonable experiments for this example, not universal defaults. The usual SOR convergence theory does not extend to , and such choices typically diverge.

In practice, test several values against the same complementarity-residual tolerance and compare total solve time—not just the iteration count.


When to Use Finite Differences

FDM with CN-PSOR is a useful baseline and a practical approach for one-factor American option pricing:

  • Structure: each PSOR sweep is because the matrix is tridiagonal
  • Accuracy controls: spatial, temporal, truncation, and iterative errors can be tested separately
  • Flexibility: local-volatility coefficients, dividends, and many early-exercise payoff variants fit the same PDE/LCP framework
  • Free boundary: can be extracted from the converged grid for diagnostics and exercise-policy analysis

PSOR is not automatically the fastest production LCP solver. Direct projected methods, penalty or policy-iteration schemes, and operator splitting can be materially faster for some models and grids.

FDM becomes impractical for:

  • More than two underlying assets: the curse of dimensionality makes the grid exponentially large
  • Path-dependent payoffs: the PDE framework requires augmenting the state space, which quickly becomes intractable
  • High-dimensional models: interest rate models with many factors, credit models with multiple obligors

For those cases, Longstaff-Schwartz Monte Carlo (Part 4) is a common alternative, especially when simulation is already natural for the model.


Companion Notebook

The Python notebook for this post is available for download: american-options-finite-difference.ipynb. It includes:

  • Full CN-PSOR implementation with boundary extraction
  • Option value surface visualisation
  • Free boundary comparison against CRR reference
  • Grid convergence study
  • Exercises for the penalty method and omega tuning

Previous Posts in This Thread


References


Get in Touch

Working on PDE methods for derivatives, or comparing FDM and Monte Carlo for American options?

Connect with me:

Whether you're implementing production pricing engines or exploring numerical PDE methods, I'd love to hear from you!