Binomial Tree Option Pricing: American Options & CRR Model

Michael BrenndoerferDecember 1, 202554 min read

Learn binomial tree option pricing with the Cox-Ross-Rubinstein model. Price American and European options using backward induction and risk-neutral valuation.

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.

Binomial Tree Option Pricing

The Black-Scholes formula, which we derived in previous chapters, provides elegant closed-form solutions for European options. But what happens when you need to price an American put option, where early exercise might be optimal? Or an option with path-dependent features? The continuous-time framework becomes analytically intractable, and we need alternative approaches.

The binomial tree model, introduced by Cox, Ross, and Rubinstein in 1979, offers a powerful discrete-time alternative. Rather than assuming stock prices follow continuous geometric Brownian motion, the binomial model discretizes time into steps and assumes the stock price can only move up or down by fixed amounts at each step. This simplification makes the model remarkably tractable: you can price options by working backward through a tree of possible price paths, checking at each node whether early exercise is beneficial.

What makes binomial trees particularly valuable is that they're not just a pedagogical simplification. As you increase the number of time steps, the binomial model converges to Black-Scholes, providing both intuition for continuous-time results and a practical computational method for options that lack closed-form solutions. This chapter builds the binomial framework from first principles, shows how risk-neutral valuation applies at each node, and demonstrates pricing for both European and American options.

The One-Step Binomial Model

We begin with the simplest possible case: a single time step. This minimal setup strips away all complexity and reveals the core logic of option pricing in its purest form. By understanding how to price a derivative when there are only two possible future states of the world, we establish a foundation that extends seamlessly to models with thousands of time steps.

Consider a stock with current price S0S_0 that, after one period, will either move up to Su=uS0S_u = u \cdot S_0 or down to Sd=dS0S_d = d \cdot S_0, where u>1u > 1 and d<1d < 1 are the up and down factors. The up factor exceeds one because an "up" move represents an increase in price, while the down factor falls below one because a "down" move represents a decrease. We want to price a derivative with payoff fuf_u if the stock goes up and fdf_d if it goes down. Notice that we have not specified the probability of an up move. This omission is intentional and, as we shall see, reflects a key insight in derivative pricing: the option price does not depend on the probability of the stock rising.

Out[2]:
Visualization
Tree diagram with one root node branching to two terminal nodes representing up and down price movements.
One-step binomial tree showing stock price evolution from $S_0$ to either $S_u$ (up state) or $S_d$ (down state), with corresponding option payoffs $f_u$ and $f_d$. The tree illustrates the two possible future states used to construct a replicating portfolio.

Replicating Portfolio Approach

The key insight that unlocks option pricing in this framework is replication. Rather than trying to guess the probability of the stock going up or down, we construct a portfolio of basic assets whose payoff exactly matches the derivative's payoff in every possible future state. This replicating portfolio consists of two components: the underlying stock and a risk-free bond. If we can perfectly replicate the derivative using these tradeable instruments, then the derivative must have the same price as the portfolio, otherwise arbitrage opportunities would exist.

Suppose we hold Δ\Delta shares of stock and invest BB dollars in a risk-free bond earning rate rr over the period. The parameter Δ\Delta represents the number of shares we hold, which can be fractional, and BB represents our cash position in the risk-free bond, which can be positive (lending) or negative (borrowing). At expiration, after one time period TT, this portfolio is worth:

  • Up state: ΔSu+BerT=ΔuS0+BerT\Delta \cdot S_u + B \cdot e^{rT} = \Delta \cdot u S_0 + B \cdot e^{rT}
  • Down state: ΔSd+BerT=ΔdS0+BerT\Delta \cdot S_d + B \cdot e^{rT} = \Delta \cdot d S_0 + B \cdot e^{rT}

For this portfolio to replicate the derivative, we need:

ΔuS0+BerT=fuΔdS0+BerT=fd\begin{aligned} \Delta \cdot u S_0 + B \cdot e^{rT} &= f_u \\ \Delta \cdot d S_0 + B \cdot e^{rT} &= f_d \end{aligned}

where:

  • Δ\Delta: number of shares of stock in the portfolio
  • S0S_0: current stock price
  • u,du, d: up and down movement factors
  • BB: amount invested in the risk-free bond
  • rr: risk-free interest rate
  • TT: time to expiration
  • fu,fdf_u, f_d: option payoffs in the up and down states

This is a system of two linear equations in two unknowns (Δ\Delta and BB). We can solve this system using straightforward algebraic manipulation. The strategy is to first eliminate one variable to find the other, then substitute back to complete the solution.

Subtracting the second equation from the first eliminates BB, since the bond value BerTB \cdot e^{rT} appears identically in both equations:

(ΔuS0+BerT)(ΔdS0+BerT)=fufd(subtract down state from up state)ΔS0(ud)=fufd(simplify to isolate Δ)\begin{aligned} (\Delta \cdot u S_0 + B \cdot e^{rT}) - (\Delta \cdot d S_0 + B \cdot e^{rT}) &= f_u - f_d && \text{(subtract down state from up state)} \\ \Delta \cdot S_0 (u - d) &= f_u - f_d && \text{(simplify to isolate } \Delta \text{)} \end{aligned}

where:

  • Δ\Delta: number of shares in the portfolio
  • S0S_0: current stock price
  • u,du, d: up and down movement factors
  • BB: bond investment
  • rr: risk-free rate
  • TT: time to expiration
  • fu,fdf_u, f_d: option payoffs

This gives us the hedge ratio, which represents the number of shares needed to replicate the derivative:

Δ=fufdS0(ud)=fufdSuSd\Delta = \frac{f_u - f_d}{S_0(u - d)} = \frac{f_u - f_d}{S_u - S_d}

where:

  • Δ\Delta: the hedge ratio (number of shares to hold)
  • fufdf_u - f_d: the range of option payoffs
  • Su,SdS_u, S_d: stock prices in up and down states
  • SuSdS_u - S_d: the range of stock prices
  • S0S_0: current stock price
  • u,du, d: up and down movement factors

This formula has a beautiful interpretation: the hedge ratio equals the change in option value divided by the change in stock price. This is exactly the discrete-time analog of the delta we encountered in continuous-time option pricing. It tells us how many shares to hold to hedge the derivative, and it measures the sensitivity of the option price to movements in the underlying stock. For a call option, Δ\Delta is positive because the option gains value when the stock rises. For a put option, Δ\Delta is negative because the option loses value when the stock rises.

Once we know Δ\Delta, we can solve for BB by substituting back into either of our original equations. Using the up-state equation:

BerT=fuΔuS0(from up state equation)B=erT(fuΔuS0)(solve for B)\begin{aligned} B \cdot e^{rT} &= f_u - \Delta \cdot u S_0 && \text{(from up state equation)} \\ B &= e^{-rT}(f_u - \Delta \cdot u S_0) && \text{(solve for } B \text{)} \end{aligned}

where:

  • BB: present value of the bond investment
  • erTe^{-rT}: discount factor
  • fuΔuS0f_u - \Delta \cdot u S_0: portfolio cash needed to match the payoff in the up state
  • fu,fdf_u, f_d: option payoffs
  • Δ\Delta: hedge ratio
  • u,du, d: up and down factors
  • S0S_0: current stock price
  • rr: risk-free interest rate
  • TT: time to expiration

The bond position BB is typically negative for call options, meaning we borrow money to fund the stock purchase. For put options, BB is typically positive, meaning we lend money while shorting stock.

No-Arbitrage Pricing

Since the replicating portfolio has the same payoff as the derivative in both states, by the no-arbitrage principle from Part III Chapter 4, the derivative must have the same price as the portfolio:

f0=ΔS0+Bf_0 = \Delta \cdot S_0 + B

where:

  • f0f_0: current option price
  • Δ\Delta: number of shares held in the replicating portfolio
  • S0S_0: current stock price
  • BB: value of the risk-free bond investment

If the derivative traded at any other price, an arbitrage opportunity would exist. If the derivative were cheaper than the replicating portfolio, we could buy the derivative, sell the portfolio, and pocket the difference while having perfectly offsetting positions. Conversely, if the derivative were more expensive, we could sell the derivative, buy the portfolio, and again lock in a risk-free profit. Market forces ensure that such opportunities are quickly eliminated, establishing the pricing relationship above.

Now we can derive an explicit formula for the option price by substituting our expressions for Δ\Delta and BB into the portfolio value equation f0=ΔS0+Bf_0 = \Delta S_0 + B:

f0=ΔS0+erT(fuΔuS0)(substitute B)=erTfu+ΔS0(1uerT)(group terms)=erTfu+fufdud(1uerT)(substitute Δ)=erT[fu+(fufd)(erTu)ud](factor out erT)=erT[erTdudfu+uerTudfd](simplify)\begin{aligned} f_0 &= \Delta S_0 + e^{-rT}(f_u - \Delta u S_0) && \text{(substitute } B \text{)} \\ &= e^{-rT}f_u + \Delta S_0 (1 - u e^{-rT}) && \text{(group terms)} \\ &= e^{-rT}f_u + \frac{f_u - f_d}{u - d} (1 - u e^{-rT}) && \text{(substitute } \Delta \text{)} \\ &= e^{-rT}\left[f_u + \frac{(f_u - f_d)(e^{rT} - u)}{u - d}\right] && \text{(factor out } e^{-rT} \text{)} \\ &= e^{-rT}\left[\frac{e^{rT} - d}{u - d} f_u + \frac{u - e^{rT}}{u - d} f_d\right] && \text{(simplify)} \end{aligned}

where:

  • f0f_0: current option price
  • Δ\Delta: number of shares held in the replicating portfolio
  • S0S_0: current stock price
  • erTdud\frac{e^{rT} - d}{u - d}: weight assigned to the up payoff
  • uerTud\frac{u - e^{rT}}{u - d}: weight assigned to the down payoff
  • rr: risk-free interest rate
  • TT: time to expiration
  • u,du, d: up and down factors
  • fu,fdf_u, f_d: option payoffs

This final expression is remarkable: the option price is a discounted weighted average of the two possible payoffs. The weights depend only on the up and down factors and the risk-free rate, not on any probability of the stock going up or down. This observation leads us to one of the most important concepts in derivative pricing.

The Risk-Neutral Probability

The formula above has a beautiful interpretation. Define the risk-neutral probability pp:

p=erTdudp = \frac{e^{rT} - d}{u - d}

where:

  • pp: risk-neutral probability of an up move
  • u,du, d: up and down factors
  • rr: risk-free rate
  • TT: time step

Then the pricing formula becomes:

f0=erT[pfu+(1p)fd]f_0 = e^{-rT}[p \cdot f_u + (1-p) \cdot f_d]

where:

  • f0f_0: current option price
  • pp: risk-neutral probability
  • 1p1-p: probability of a down move
  • erTe^{-rT}: risk-free discount factor
  • fu,fdf_u, f_d: option payoffs in up and down states
  • rr: risk-free interest rate
  • TT: time step duration

This elegant formula states that the derivative price equals the discounted expected payoff, where the expectation uses the probability pp rather than the real-world probability of an up move. The expression pfu+(1p)fdp \cdot f_u + (1-p) \cdot f_d computes the expected payoff under the risk-neutral probability measure, and the factor erTe^{-rT} discounts this expected value back to the present. We never needed to know or estimate the actual probability of the stock rising; the risk-neutral probability emerges naturally from the no-arbitrage requirement.

Risk-Neutral Probability

The risk-neutral probability p=erTdudp = \frac{e^{rT} - d}{u - d} is not the actual probability of the stock going up. It's a mathematical construct that makes the expected return on the stock equal to the risk-free rate. Under this probability measure, the expected stock price is EQ[ST]=puS0+(1p)dS0=S0erTE^{\mathbb{Q}}[S_T] = p \cdot uS_0 + (1-p) \cdot dS_0 = S_0 e^{rT}.

For pp to be a valid probability (between 0 and 1), we need:

d<erT<ud < e^{rT} < u

where:

  • dd: down movement factor
  • uu: up movement factor
  • rr: risk-free interest rate
  • TT: time step duration

This condition ensures no arbitrage and has an intuitive economic interpretation. The term erTe^{rT} represents the growth factor of a risk-free investment over the period. The condition states that this risk-free growth must fall strictly between the down move dd and the up move uu. If erTue^{rT} \geq u, the risk-free bond would always outperform the stock, making the stock unattractive at any positive price. Conversely, if erTde^{rT} \leq d, the stock would always outperform the risk-free bond, meaning investors would borrow at the risk-free rate and invest in the stock for guaranteed profit. Either case would create an arbitrage opportunity, which cannot persist in equilibrium.

Out[3]:
Visualization
No-arbitrage bounds for the risk-neutral probability $p$ as a function of the risk-free rate $r$. The green shaded region indicates where $0 < p < 1$, requiring the forward growth factor $e^{rT}$ to lie between the down factor $d$ and up factor $u$. Boundaries at $p=0$ and $p=1$ mark the limits where the risk-free rate makes the stock risklessly dominate or be dominated by the bond, creating arbitrage opportunities.
No-arbitrage bounds for the risk-neutral probability $p$ as a function of the risk-free rate $r$. The green shaded region indicates where $0 < p < 1$, requiring the forward growth factor $e^{rT}$ to lie between the down factor $d$ and up factor $u$. Boundaries at $p=0$ and $p=1$ mark the limits where the risk-free rate makes the stock risklessly dominate or be dominated by the bond, creating arbitrage opportunities.
In[4]:
Code
import numpy as np


def one_step_binomial_price(S0, K, T, r, u, d, option_type="call"):
    """
    Price an option using a one-step binomial model.

    Parameters:
    -----------
    S0 : float - Current stock price
    K : float - Strike price
    T : float - Time to expiration (years)
    r : float - Risk-free rate (annualized)
    u : float - Up factor (u > 1)
    d : float - Down factor (d < 1)
    option_type : str - 'call' or 'put'

    Returns:
    --------
    float : Option price
    """
    # Stock prices at expiration
    Su = u * S0
    Sd = d * S0

    # Option payoffs at expiration
    if option_type == "call":
        fu = max(Su - K, 0)
        fd = max(Sd - K, 0)
    else:
        fu = max(K - Su, 0)
        fd = max(K - Sd, 0)

    # Risk-neutral probability
    p = (np.exp(r * T) - d) / (u - d)

    # Option price (discounted expected payoff)
    f0 = np.exp(-r * T) * (p * fu + (1 - p) * fd)

    return f0, p, fu, fd
In[5]:
Code
# Example: Price a call option
S0 = 100  # Current stock price
K = 100  # Strike price (at-the-money)
T = 1  # One year to expiration
r = 0.05  # 5% risk-free rate
u = 1.2  # Stock can go up 20%
d = 0.9  # Stock can go down 10%

price, p, fu, fd = one_step_binomial_price(S0, K, T, r, u, d, "call")
Su = u * S0
Sd = d * S0
Out[6]:
Console
Stock prices at expiration: Su = $120.00, Sd = $90.00
Call payoffs: fu = $20.00, fd = $0.00
Risk-neutral probability of up move: p = 0.5042
Call option price: $9.59

The calculated risk-neutral probability is close to 0.5 because with u=1.2u = 1.2 and d=0.9d = 0.9, the up and down moves are roughly symmetric around the forward price S0erTS_0 e^{rT}. The forward price represents where the stock "should" be in the risk-neutral world, and when up and down moves are symmetric around this level, the risk-neutral probabilities are approximately equal.

Multi-Step Binomial Trees

The one-step model captures the essential logic, but real options require finer granularity. A single period cannot adequately represent the continuous evolution of stock prices over months or years. Moreover, for American options where early exercise is possible, we need intermediate time points to evaluate exercise decisions. We extend to multiple steps by dividing time to expiration into NN equal intervals, each of length Δt=T/N\Delta t = T/N. At each step, the stock price moves up by factor uu or down by factor dd, creating a tree of possible price paths.

Tree Structure and Recombination

A crucial property of binomial trees is recombination: an up move followed by a down move leads to the same price as a down move followed by an up move (udS0=duS0udS_0 = duS_0). This property arises because multiplication is commutative, so the order of multiplying by uu and dd does not matter. The recombination property dramatically reduces the computational burden of the tree. Without recombination, each node would branch into two new nodes, creating 2N2^N terminal nodes after NN steps. With recombination, the tree "collapses" as paths converge to common nodes, leaving only N+1N+1 distinct prices at the final step.

Out[7]:
Visualization
Tree diagram showing price evolution over three time steps with recombining nodes.
Three-step recombining binomial tree showing the evolution of stock prices from $t=0$ to $t=3$. The lattice structure demonstrates how an up-move followed by a down-move leads to the same node as a down-move followed by an up-move, resulting in $N+1$ terminal nodes rather than $2^N$.

At the final time step NN, the possible stock prices are:

SN,j=S0ujdNj,j=0,1,,NS_{N,j} = S_0 \cdot u^j \cdot d^{N-j}, \quad j = 0, 1, \ldots, N

where:

  • SN,jS_{N,j}: stock price at maturity node jj
  • S0S_0: initial stock price
  • u,du, d: up and down factors
  • NN: total number of steps
  • jj: number of up moves

The price SjS_j corresponds to a node that can be reached by any path with exactly jj up moves and NjN-j down moves. The number of paths reaching this node is given by the binomial coefficient (Nj)\binom{N}{j}, which counts the number of ways to arrange jj up moves among NN total moves. This combinatorial structure gives the model its name: the binomial tree.

Backward Induction

We price options by working backward through the tree, a technique known as backward induction. This approach exploits the fact that at maturity, option values are known with certainty since they equal the payoff function evaluated at each terminal stock price. Starting from the known payoffs at expiration and moving step-by-step toward time zero, we apply the one-step pricing formula at each node. At each step, we treat each node as if it were the root of a one-step tree and calculate the option value using the risk-neutral pricing formula derived earlier.

At the final step NN, the option value at node jj is simply the payoff:

fN,j=max(SN,jK,0)for a callfN,j=max(KSN,j,0)for a put\begin{aligned} f_{N,j} &= \max(S_{N,j} - K, 0) && \text{for a call} \\ f_{N,j} &= \max(K - S_{N,j}, 0) && \text{for a put} \end{aligned}

where:

  • fN,jf_{N,j}: option value at maturity node jj
  • SN,jS_{N,j}: stock price at maturity node jj
  • KK: strike price

These terminal values serve as the boundary conditions for our backward recursion. From these known values, we can calculate the option value at any node one step earlier.

For any earlier node at step i<Ni < N with jj up moves, we compute the option value as:

fi,j=erΔt[pfi+1,j+1+(1p)fi+1,j]f_{i,j} = e^{-r\Delta t}\left[p \cdot f_{i+1,j+1} + (1-p) \cdot f_{i+1,j}\right]

where:

  • fi,jf_{i,j}: option value at step ii, node jj
  • Δt\Delta t: time step size
  • pp: risk-neutral probability
  • fi+1,j+1f_{i+1,j+1}: option value in the up state (next step)
  • fi+1,jf_{i+1,j}: option value in the down state (next step)
  • rr: risk-free interest rate

This formula says that the option value at node (i,j)(i,j) equals the discounted risk-neutral expected value of the option at the next time step. If the stock moves up from node (i,j)(i,j), it arrives at node (i+1,j+1)(i+1,j+1); if it moves down, it arrives at node (i+1,j)(i+1,j). The recursion propagates backward until we reach node (0,0)(0, 0), which gives us the current option price.

In[8]:
Code
import numpy as np


def binomial_tree_european(S0, K, T, r, sigma, N, option_type="call"):
    """
    Price a European option using a multi-step binomial tree.

    Parameters:
    -----------
    S0 : float - Current stock price
    K : float - Strike price
    T : float - Time to expiration (years)
    r : float - Risk-free rate (annualized)
    sigma : float - Volatility (annualized)
    N : int - Number of time steps
    option_type : str - 'call' or 'put'

    Returns:
    --------
    float : Option price
    """
    dt = T / N  # Time step size

    # CRR parameters (we'll derive these shortly)
    u = np.exp(sigma * np.sqrt(dt))
    d = 1 / u

    # Risk-neutral probability
    p = (np.exp(r * dt) - d) / (u - d)

    # Initialize stock prices at maturity (step N)
    # S[j] = S0 * u^j * d^(N-j) for j = 0, 1, ..., N
    S = np.zeros(N + 1)
    for j in range(N + 1):
        S[j] = S0 * (u**j) * (d ** (N - j))

    # Initialize option values at maturity
    if option_type == "call":
        f = np.maximum(S - K, 0)
    else:
        f = np.maximum(K - S, 0)

    # Backward induction through the tree
    for i in range(N - 1, -1, -1):
        for j in range(i + 1):
            f[j] = np.exp(-r * dt) * (p * f[j + 1] + (1 - p) * f[j])

    return f[0]
In[9]:
Code
# Example: Price a European call with different numbers of steps
S0 = 100
K = 100
T = 1
r = 0.05
sigma = 0.2

step_counts = [5, 10, 25, 50, 100]
prices = {}
for N in step_counts:
    prices[N] = binomial_tree_european(S0, K, T, r, sigma, N, "call")
Out[10]:
Console
N =   5 steps: Call price = $10.8059
N =  10 steps: Call price = $10.2534
N =  25 steps: Call price = $10.5210
N =  50 steps: Call price = $10.4107
N = 100 steps: Call price = $10.4306

Notice how the price oscillates and gradually converges as we increase the number of steps. This oscillatory behavior is characteristic of the binomial model and arises from the discrete nature of the tree structure. We'll explore this convergence behavior in detail later.

The Cox-Ross-Rubinstein Model

The choice of up and down factors uu and dd determines how well the discrete model approximates continuous-time dynamics. These parameters are not arbitrary; they must be chosen carefully to ensure that the discrete binomial process converges to the continuous geometric Brownian motion as the number of steps increases. The Cox-Ross-Rubinstein (CRR) parameterization, introduced in their seminal 1979 paper, sets:

u=eσΔtd=eσΔt=1u\begin{aligned} u &= e^{\sigma\sqrt{\Delta t}} \\ d &= e^{-\sigma\sqrt{\Delta t}} = \frac{1}{u} \end{aligned}

where:

  • u,du, d: up and down factors
  • σ\sigma: annualized volatility
  • Δt\Delta t: time step size (T/NT/N)

The relationship d=1/ud = 1/u ensures that the tree recombines: an up move followed by a down move returns the stock to its original price (ud=1ud = 1). The exponential form reflects the multiplicative nature of stock returns, and the dependence on σΔt\sigma\sqrt{\Delta t} ensures that the volatility of the discrete model matches the volatility of the continuous model.

Derivation of CRR Parameters

The CRR parameters are chosen to match the first two moments of the log-return distribution under the continuous-time model. This moment-matching approach ensures that the discrete model faithfully reproduces the statistical properties of the continuous model it approximates.

In continuous time, over a small interval Δt\Delta t, the log-return has:

  • Mean: (rσ22)Δt(r - \frac{\sigma^2}{2})\Delta t
  • Variance: σ2Δt\sigma^2 \Delta t

The mean reflects the drift of the log-price process, which under the risk-neutral measure equals the risk-free rate minus a convexity adjustment. The variance reflects the accumulated randomness over the time interval and grows proportionally with time.

In the binomial model, the log-return is either ln(u)=σΔt\ln(u) = \sigma\sqrt{\Delta t} (with probability pp) or ln(d)=σΔt\ln(d) = -\sigma\sqrt{\Delta t} (with probability 1p1-p). These are the only two possible outcomes in the discrete model. The variance of this discrete log-return is:

Var[ln(St+Δt/St)]=p(1p)[ln(u)ln(d)]2=p(1p)[σΔt(σΔt)]2(substitute CRR parameters)=p(1p)(2σΔt)2(simplify)\begin{aligned} \text{Var}[\ln(S_{t+\Delta t}/S_t)] &= p(1-p)[\ln(u) - \ln(d)]^2 \\ &= p(1-p)[\sigma\sqrt{\Delta t} - (-\sigma\sqrt{\Delta t})]^2 && \text{(substitute CRR parameters)} \\ &= p(1-p)(2\sigma\sqrt{\Delta t})^2 && \text{(simplify)} \end{aligned}

where:

  • St,St+ΔtS_t, S_{t+\Delta t}: stock prices at time tt and t+Δtt+\Delta t
  • p(1p)p(1-p): variance of a Bernoulli variable
  • ln(u)ln(d)\ln(u) - \ln(d): difference in log prices between up and down states
  • 2σΔt2\sigma\sqrt{\Delta t}: magnitude of the log price jump
  • pp: risk-neutral probability
  • σ\sigma: volatility
  • Δt\Delta t: time step
  • u,du, d: up and down factors

With the CRR choice and as Δt0\Delta t \to 0, we can approximate the risk-neutral probability using Taylor series expansions. This analysis reveals how the probability approaches a specific limiting value:

p=erΔtdud(1+rΔt)(1σΔt+12σ2Δt)2σΔt(Taylor expansion)=σΔt+(r12σ2)Δt2σΔt(simplify numerator)=12+rσ2/22σΔt(divide by denominator)\begin{aligned} p &= \frac{e^{r\Delta t} - d}{u - d} \\ &\approx \frac{(1 + r\Delta t) - (1 - \sigma\sqrt{\Delta t} + \frac{1}{2}\sigma^2\Delta t)}{2\sigma\sqrt{\Delta t}} && \text{(Taylor expansion)} \\ &= \frac{\sigma\sqrt{\Delta t} + (r - \frac{1}{2}\sigma^2)\Delta t}{2\sigma\sqrt{\Delta t}} && \text{(simplify numerator)} \\ &= \frac{1}{2} + \frac{r - \sigma^2/2}{2\sigma}\sqrt{\Delta t} && \text{(divide by denominator)} \end{aligned}

where:

  • pp: risk-neutral probability
  • u,du, d: up and down factors
  • rσ2/2r - \sigma^2/2: drift adjustment term
  • Δt\Delta t: time step size
  • Δt\sqrt{\Delta t}: scaling factor for the drift
  • rr: risk-free interest rate
  • σ\sigma: volatility

This derivation reveals that the risk-neutral probability approaches 1/2 as the time step shrinks, with a small correction proportional to Δt\sqrt{\Delta t}. The correction term involves the drift rate rσ2/2r - \sigma^2/2, which is the expected growth rate of the log-price under the risk-neutral measure. This means p(1p)1/4p(1-p) \to 1/4 as Δt0\Delta t \to 0, giving variance 4×1/4×σ2Δt=σ2Δt4 \times 1/4 \times \sigma^2 \Delta t = \sigma^2 \Delta t, which matches the continuous-time variance.

Alternative Parameterizations

Other parameterizations exist with different properties:

  • Jarrow-Rudd (JR): Sets p=0.5p = 0.5 exactly and adjusts uu and dd to match moments. This gives equal probabilities but asymmetric up/down moves.

  • Tian: Matches the first three moments of the price distribution, potentially providing faster convergence.

For most practical purposes, the CRR parameterization works well and has become the standard choice. Its mathematical elegance, intuitive interpretation, and proven convergence properties make it the preferred approach in both academic and industry applications.

In[11]:
Code
import numpy as np

# Compare CRR parameters for different numbers of steps
T = 1.0
sigma = 0.2
r = 0.05

step_counts = [10, 50, 100, 500]
crr_params = []

for N in step_counts:
    dt = T / N
    u = np.exp(sigma * np.sqrt(dt))
    d = 1 / u
    p = (np.exp(r * dt) - d) / (u - d)
    crr_params.append((N, dt, u, d, p))
Out[12]:
Console
CRR Parameters for different N:
-------------------------------------------------------
    N         dt          u          d          p
-------------------------------------------------------
   10     0.1000   1.065288   0.938713   0.523795
   50     0.0200   1.028688   0.972112   0.510614
  100     0.0100   1.020201   0.980199   0.507502
  500     0.0020   1.008984   0.991096   0.503354

As NN increases (and Δt\Delta t decreases), the up and down factors approach 1, meaning each step represents a smaller price change. The risk-neutral probability approaches 0.5 plus a small drift adjustment, consistent with our theoretical analysis above.

Out[13]:
Visualization
Convergence of Cox-Ross-Rubinstein (CRR) parameters as the number of time steps $N$ increases. The first panel shows the up factor $u$ and down factor $d$ approaching 1 as $\Delta t \to 0$. The second panel demonstrates that the risk-neutral probability $p$ converges to 0.5 (dashed line), reflecting the symmetry of the random walk in the limit.
Convergence of Cox-Ross-Rubinstein (CRR) parameters as the number of time steps $N$ increases. The first panel shows the up factor $u$ and down factor $d$ approaching 1 as $\Delta t \to 0$. The second panel demonstrates that the risk-neutral probability $p$ converges to 0.5 (dashed line), reflecting the symmetry of the random walk in the limit.
Notebook output

Pricing American Options

The real power of binomial trees emerges when pricing American options, which can be exercised at any time before expiration. Unlike European options, American options have no known closed-form solution except in special cases (American calls on non-dividend-paying stocks are never optimally exercised early and thus equal European calls). The ability to exercise early adds a layer of complexity that continuous-time methods handle poorly, but binomial trees address naturally through backward induction.

Early Exercise Decision

At each node in the tree, you face a choice: exercise now or continue holding. The optimal strategy is to exercise if and only if the immediate exercise value exceeds the continuation value:

fi,jAmerican=max(Exercise Value,Continuation Value)f_{i,j}^{\text{American}} = \max\left(\text{Exercise Value}, \text{Continuation Value}\right)

where:

  • fi,jAmericanf_{i,j}^{\text{American}}: American option value at node (i,j)(i,j)
  • Exercise Value\text{Exercise Value}: immediate payoff from exercising the option
  • Continuation Value\text{Continuation Value}: discounted expected value of holding the option

This simple modification to the backward induction algorithm transforms the European pricing method into an American pricing method. At each node, rather than automatically using the continuation value as we would for a European option, we compare it to the immediate exercise value and take the larger of the two.

For an American put at node (i,j)(i, j):

fi,j=max(KSi,j,erΔt[pfi+1,j+1+(1p)fi+1,j])f_{i,j} = \max\left(K - S_{i,j}, \, e^{-r\Delta t}[p \cdot f_{i+1,j+1} + (1-p) \cdot f_{i+1,j}]\right)

where:

  • KSi,jK - S_{i,j}: immediate exercise value (intrinsic value)
  • erΔt[]e^{-r\Delta t}[\dots]: continuation value (discounted expected future value)
  • pp: risk-neutral probability
  • fi,jf_{i,j}: option value at node (i,j)(i,j)
  • KK: strike price
  • Si,jS_{i,j}: stock price at node (i,j)(i,j)
  • rr: risk-free interest rate
  • Δt\Delta t: time step

The first term is the immediate payoff from exercising; the second is the discounted expected value from waiting. By evaluating this comparison at every node, the algorithm finds the optimal exercise strategy and the corresponding option value.

Why American Puts May Be Exercised Early

For American puts, early exercise can be optimal when the stock price is sufficiently low. Consider a deep in-the-money put with strike $100 and stock price $10. Exercising immediately gives $90. Waiting means:

  1. You tie up $90 of potential proceeds
  2. The maximum additional gain is $10 (if the stock goes to zero)
  3. You risk the stock rising, reducing your payoff

When the time value of money on the exercise proceeds exceeds the expected gain from waiting, early exercise is optimal. The $90 in immediate cash can be invested at the risk-free rate, earning interest. Meanwhile, the probability that the stock falls further to provide additional gains may be low, and the risk that it rises is significant. This trade-off creates a critical stock price below which early exercise is optimal, known as the early exercise boundary.

In[14]:
Code
import numpy as np


def binomial_tree_american(S0, K, T, r, sigma, N, option_type="call"):
    """
    Price an American option using a binomial tree.

    Returns both the option price and a matrix showing where early
    exercise is optimal.
    """
    dt = T / N
    u = np.exp(sigma * np.sqrt(dt))
    d = 1 / u
    p = (np.exp(r * dt) - d) / (u - d)
    disc = np.exp(-r * dt)

    # Build the stock price tree
    S = np.zeros((N + 1, N + 1))
    for i in range(N + 1):
        for j in range(i + 1):
            S[i, j] = S0 * (u**j) * (d ** (i - j))

    # Initialize option values at maturity
    f = np.zeros((N + 1, N + 1))
    if option_type == "call":
        f[N, :] = np.maximum(S[N, :] - K, 0)
    else:
        f[N, :] = np.maximum(K - S[N, :], 0)

    # Track early exercise
    early_exercise = np.zeros((N + 1, N + 1), dtype=bool)

    # Backward induction with early exercise check
    for i in range(N - 1, -1, -1):
        for j in range(i + 1):
            # Continuation value
            cont_value = disc * (p * f[i + 1, j + 1] + (1 - p) * f[i + 1, j])

            # Exercise value
            if option_type == "call":
                exercise_value = max(S[i, j] - K, 0)
            else:
                exercise_value = max(K - S[i, j], 0)

            # American option: take the maximum
            if exercise_value > cont_value:
                f[i, j] = exercise_value
                early_exercise[i, j] = True
            else:
                f[i, j] = cont_value

    return f[0, 0], S, f, early_exercise
In[15]:
Code
# Compare European and American put prices
S0 = 100
K = 100
T = 1
r = 0.05
sigma = 0.2
N = 100

european_put = binomial_tree_european(S0, K, T, r, sigma, N, "put")
american_put, _, _, _ = binomial_tree_american(S0, K, T, r, sigma, N, "put")
premium = american_put - european_put
Out[16]:
Console
European Put Price: $5.5536
American Put Price: $6.0824
Early Exercise Premium: $0.5288

The American put is worth more than the European put because you have the valuable option to exercise early. This difference is called the early exercise premium and it represents the economic value of the flexibility to choose when to exercise.

Out[17]:
Visualization
Comparison of European and American put option prices and the resulting early exercise premium across stock prices. The first panel shows American puts (dashed red) trading at a premium to European puts (solid blue) for in-the-money options ($S < K$). The second panel isolates this early exercise premium, which peaks for deep in-the-money options where the benefit of immediate exercise is greatest.
Comparison of European and American put option prices and the resulting early exercise premium across stock prices. The first panel shows American puts (dashed red) trading at a premium to European puts (solid blue) for in-the-money options ($S < K$). The second panel isolates this early exercise premium, which peaks for deep in-the-money options where the benefit of immediate exercise is greatest.
Notebook output

Visualizing Early Exercise Regions

We can examine where in the tree early exercise is optimal by using a small number of steps and visualizing the exercise boundary.

Out[18]:
Visualization
Tree diagram with nodes colored to show early exercise versus continuation regions.
Early exercise region for an American put option with $N=8$ steps. Red nodes mark states where the immediate exercise value $K-S$ exceeds the continuation value, making early exercise optimal. The boundary between red and green nodes visualizes the early exercise boundary, below which the put option should be exercised immediately.

The early exercise boundary shows that for low stock prices (bottom of the tree), immediate exercise becomes optimal. This creates an "exercise region" below the boundary where you should exercise your puts. The boundary separates the state space into two regions: where it is optimal to continue holding the option and where it is optimal to exercise immediately.

Convergence to Black-Scholes

One of the most elegant properties of the binomial model is its convergence to the Black-Scholes price as the number of steps increases. This convergence is not merely a computational convenience; it provides theoretical validation that the binomial approach captures the same economic principles as the continuous-time framework. The convergence also offers practical assurance that binomial trees produce accurate prices when enough steps are used.

Theoretical Convergence

As we discussed in Part III Chapter 2, the binomial random walk converges in distribution to geometric Brownian motion as NN \to \infty (a consequence of the central limit theorem applied to the sum of log-returns). Each step in the binomial tree contributes a small random increment to the log-price, and the sum of many such increments becomes normally distributed. Since the Black-Scholes model assumes geometric Brownian motion, and since both models use risk-neutral valuation, the binomial price converges to the Black-Scholes price.

The rate of convergence for European options is O(1/N)O(1/N), meaning doubling the number of steps roughly halves the error. However, convergence is not monotonic: prices oscillate around the true value, particularly for at-the-money options. This oscillation arises from the discrete nature of the tree and how the strike price falls relative to the tree's nodes at each step.

In[19]:
Code
import numpy as np
from scipy.stats import norm


def black_scholes_price(S0, K, T, r, sigma, option_type="call"):
    """Calculate Black-Scholes price for European options."""
    d1 = (np.log(S0 / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)

    if option_type == "call":
        price = S0 * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
    else:
        price = K * np.exp(-r * T) * norm.cdf(-d2) - S0 * norm.cdf(-d1)

    return price
In[20]:
Code
# Compare binomial convergence to Black-Scholes
S0 = 100
K = 100
T = 1
r = 0.05
sigma = 0.2

bs_price = black_scholes_price(S0, K, T, r, sigma, "call")

# Calculate convergence data
step_counts = [10, 25, 50, 100, 200, 500, 1000]
convergence_data = []

for N in step_counts:
    binomial_price = binomial_tree_european(S0, K, T, r, sigma, N, "call")
    error = binomial_price - bs_price
    pct_error = 100 * error / bs_price
    convergence_data.append((N, binomial_price, error, pct_error))
Out[21]:
Console
Black-Scholes Call Price: $10.450584

Binomial Tree Convergence:
---------------------------------------------
     N     Binomial        Error    % Error
---------------------------------------------
    10 $  10.253409    -0.197175   -1.8867%
    25 $  10.520966     0.070382    0.6735%
    50 $  10.410692    -0.039892   -0.3817%
   100 $  10.430612    -0.019972   -0.1911%
   200 $  10.440591    -0.009992   -0.0956%
   500 $  10.446585    -0.003998   -0.0383%
  1000 $  10.448584    -0.001999   -0.0191%
Out[22]:
Visualization
Line chart showing binomial option prices converging to Black-Scholes price as steps increase.
Convergence of binomial tree prices to the Black-Scholes analytical solution as the number of steps $N$ increases. The left panel shows the oscillating price convergence around the Black-Scholes value (dashed red line). The right panel displays the pricing error, which decreases in magnitude but alternates in sign, illustrating the $O(1/N)$ convergence rate with oscillation characteristic of the CRR parameterization.
Notebook output

The characteristic oscillation occurs because of how the strike price falls relative to the tree's nodes. For even N, the strike may fall exactly on a node, while for odd N it falls between nodes. This creates alternating over- and under-estimation that damps out as N increases.

Convergence Rate Analysis

The error decreases roughly proportionally to 1/N1/N:

In[23]:
Code
# Analyze convergence rate
N_values = [25, 50, 100, 200, 400, 800]
errors = []

products = []

for N in N_values:
    binomial_price = binomial_tree_european(S0, K, T, r, sigma, N, "call")
    err = abs(binomial_price - bs_price)
    errors.append(err)
    products.append(N * err)
Out[24]:
Console
Convergence Rate Analysis:
--------------------------------------------------
     N      |Error|    N × |Error|
--------------------------------------------------
    25     0.070382         1.7596
    50     0.039892         1.9946
   100     0.019972         1.9972
   200     0.009992         1.9985
   400     0.004998         1.9991
   800     0.002499         1.9994

If convergence is O(1/N), then N × |Error| should be roughly constant.

The product N×ErrorN \times |\text{Error}| being approximately constant confirms the O(1/N)O(1/N) convergence rate. This means that to reduce the error by a factor of 10, we need to increase the number of steps by a factor of 10, which has important implications for computational cost.

Complete Implementation

Let's build a more comprehensive implementation that includes visualization of the full tree and supports both European and American options.

In[25]:
Code
import numpy as np


class BinomialTree:
    """
    A comprehensive binomial tree implementation for option pricing.
    """

    def __init__(
        self, S0, K, T, r, sigma, N, option_type="call", american=False
    ):
        self.S0 = S0
        self.K = K
        self.T = T
        self.r = r
        self.sigma = sigma
        self.N = N
        self.option_type = option_type
        self.american = american

        # CRR parameters
        self.dt = T / N
        self.u = np.exp(sigma * np.sqrt(self.dt))
        self.d = 1 / self.u
        self.p = (np.exp(r * self.dt) - self.d) / (self.u - self.d)
        self.disc = np.exp(-r * self.dt)

        # Build tree
        self._build_tree()

    def _build_tree(self):
        """Construct stock price and option value trees."""
        N = self.N

        # Stock prices
        self.stock_tree = np.zeros((N + 1, N + 1))
        for i in range(N + 1):
            for j in range(i + 1):
                self.stock_tree[i, j] = (
                    self.S0 * (self.u**j) * (self.d ** (i - j))
                )

        # Option values
        self.option_tree = np.zeros((N + 1, N + 1))
        self.exercise_tree = np.zeros((N + 1, N + 1), dtype=bool)

        # Terminal payoffs
        for j in range(N + 1):
            S = self.stock_tree[N, j]
            if self.option_type == "call":
                self.option_tree[N, j] = max(S - self.K, 0)
            else:
                self.option_tree[N, j] = max(self.K - S, 0)

        # Backward induction
        for i in range(N - 1, -1, -1):
            for j in range(i + 1):
                # Continuation value
                cont = self.disc * (
                    self.p * self.option_tree[i + 1, j + 1]
                    + (1 - self.p) * self.option_tree[i + 1, j]
                )

                if self.american:
                    # Exercise value
                    S = self.stock_tree[i, j]
                    if self.option_type == "call":
                        exercise = max(S - self.K, 0)
                    else:
                        exercise = max(self.K - S, 0)

                    if exercise > cont:
                        self.option_tree[i, j] = exercise
                        self.exercise_tree[i, j] = True
                    else:
                        self.option_tree[i, j] = cont
                else:
                    self.option_tree[i, j] = cont

    @property
    def price(self):
        """Return the option price."""
        return self.option_tree[0, 0]

    @property
    def delta(self):
        """Calculate delta using the first step of the tree."""
        if self.N < 1:
            return None
        return (self.option_tree[1, 1] - self.option_tree[1, 0]) / (
            self.stock_tree[1, 1] - self.stock_tree[1, 0]
        )

    @property
    def gamma(self):
        """Calculate gamma using the second step of the tree."""
        if self.N < 2:
            return None
        # Delta at upper node after one step
        delta_up = (self.option_tree[2, 2] - self.option_tree[2, 1]) / (
            self.stock_tree[2, 2] - self.stock_tree[2, 1]
        )
        # Delta at lower node after one step
        delta_down = (self.option_tree[2, 1] - self.option_tree[2, 0]) / (
            self.stock_tree[2, 1] - self.stock_tree[2, 0]
        )
        # Average stock price change
        h = 0.5 * (self.stock_tree[2, 2] - self.stock_tree[2, 0])
        return (delta_up - delta_down) / h

    @property
    def theta(self):
        """Calculate theta using the second step of the tree."""
        if self.N < 2:
            return None
        # Option value at t=2dt for middle node
        f_2dt = self.option_tree[2, 1]
        # Per-year theta
        return (f_2dt - self.price) / (2 * self.dt)
In[26]:
Code
# Demonstrate the BinomialTree class
S0, K, T, r, sigma = 100, 100, 1, 0.05, 0.2
N = 100

# Price European and American puts
euro_put = BinomialTree(S0, K, T, r, sigma, N, "put", american=False)
amer_put = BinomialTree(S0, K, T, r, sigma, N, "put", american=True)
premium = amer_put.price - euro_put.price
Out[27]:
Console
Put Option Comparison (N = 100 steps)
=============================================
                    European     American
---------------------------------------------
Price           $     5.5536 $     6.0824
Delta                -0.3635      -0.4116
Gamma               0.018922     0.023139
Theta                -1.6868      -2.2626
---------------------------------------------
Early Exercise Premium : $0.5288

The Greeks computed from the tree match the intuition from Part III Chapter 7: puts have negative delta (value decreases as stock rises), positive gamma (delta becomes less negative as stock rises), and negative theta (value decays over time).

Key Parameters

The key parameters for the Binomial Tree model are:

  • S0: Current stock price. The starting node of the tree.
  • K: Strike price. Used to calculate option payoffs at terminal nodes and exercise values for American options.
  • T: Time to expiration in years. Determines the total duration covered by the tree.
  • r: Risk-free interest rate (annualized). Used for discounting and calculating the risk-neutral probability.
  • σ: Volatility (annualized). Determines the magnitude of up and down moves (uu and dd).
  • N: Number of time steps. Controls the granularity of the tree; higher NN improves accuracy but increases computational cost.
  • option_type: 'call' or 'put'. Determines the payoff structure.
  • american: Boolean flag. If True, allows for early exercise checks at each node.

Dividends and Extensions

Real stocks often pay dividends, which affect option prices and early exercise decisions. The binomial framework easily accommodates dividends.

Continuous Dividend Yield

For stocks with a continuous dividend yield qq (common for index options), we modify the risk-neutral probability:

p=e(rq)Δtdudp = \frac{e^{(r-q)\Delta t} - d}{u - d}

where:

  • pp: risk-neutral probability adjusted for dividends
  • rr: risk-free rate
  • qq: continuous dividend yield
  • Δt\Delta t: time step
  • u,du, d: up and down factors

The forward price grows at rate rqr - q rather than rr, reflecting the dividend leakage. This modification accounts for the fact that holding the stock entitles you to dividends, which reduces the expected capital appreciation under the risk-neutral measure.

Discrete Dividends

For individual stocks paying discrete dividends, we adjust the stock price at dividend dates. If a dividend DD is paid at time tdt_d, the stock price drops by DD immediately after payment. This creates a tree that is not strictly recombining around dividend dates but remains computationally tractable.

In[28]:
Code
import numpy as np


def binomial_tree_dividend_yield(
    S0, K, T, r, q, sigma, N, option_type="call", american=False
):
    """
    Price an option on an asset with continuous dividend yield q.
    """
    dt = T / N
    u = np.exp(sigma * np.sqrt(dt))
    d = 1 / u

    # Modified risk-neutral probability for dividend yield
    p = (np.exp((r - q) * dt) - d) / (u - d)
    disc = np.exp(-r * dt)

    # Build stock tree
    S = np.zeros((N + 1, N + 1))
    for i in range(N + 1):
        for j in range(i + 1):
            S[i, j] = S0 * (u**j) * (d ** (i - j))

    # Initialize at maturity
    f = np.zeros((N + 1, N + 1))
    for j in range(N + 1):
        if option_type == "call":
            f[N, j] = max(S[N, j] - K, 0)
        else:
            f[N, j] = max(K - S[N, j], 0)

    # Backward induction
    for i in range(N - 1, -1, -1):
        for j in range(i + 1):
            cont = disc * (p * f[i + 1, j + 1] + (1 - p) * f[i + 1, j])

            if american:
                if option_type == "call":
                    exercise = max(S[i, j] - K, 0)
                else:
                    exercise = max(K - S[i, j], 0)
                f[i, j] = max(cont, exercise)
            else:
                f[i, j] = cont

    return f[0, 0]
In[29]:
Code
# Impact of dividend yield on American call options
S0, K, T, r, sigma = 100, 100, 1, 0.05, 0.2
N = 100

dividend_yields = [0.0, 0.02, 0.04, 0.06, 0.08]
results = []

for q in dividend_yields:
    euro = binomial_tree_dividend_yield(S0, K, T, r, q, sigma, N, "call", False)
    amer = binomial_tree_dividend_yield(S0, K, T, r, q, sigma, N, "call", True)
    results.append((q, euro, amer, amer - euro))
Out[30]:
Console
 Div Yield    Euro Call    Amer Call      Premium
------------------------------------------------------------
      0.0% $    10.4306 $    10.4306 $     0.0000
      2.0% $     9.2076 $     9.2076 $     0.0000
      4.0% $     8.0836 $     8.0991 $     0.0155
      6.0% $     7.0567 $     7.2440 $     0.1873
      8.0% $     6.1242 $     6.5327 $     0.4085

With no dividends, American calls equal European calls because early exercise is never optimal. As the dividend yield increases, early exercise becomes attractive: you want to own the stock to capture the dividend, making the American call more valuable than the European.

Out[31]:
Visualization
Impact of continuous dividend yield $q$ on American call option prices. The left panel compares European (solid blue) and American (dashed red) call prices; as dividend yield increases, the American call maintains value better than the European call. The right panel shows the early exercise premium rising with dividend yield, as the incentive to exercise early to capture dividends before they leak from the stock price increases.
Impact of continuous dividend yield $q$ on American call option prices. The left panel compares European (solid blue) and American (dashed red) call prices; as dividend yield increases, the American call maintains value better than the European call. The right panel shows the early exercise premium rising with dividend yield, as the incentive to exercise early to capture dividends before they leak from the stock price increases.
Notebook output

Practical Considerations and Limitations

While binomial trees are versatile and intuitive, several practical considerations arise when implementing them for production use.

Computational Complexity

The standard backward induction algorithm has time complexity O(N2)O(N^2) because we process approximately N2/2N^2/2 nodes. For N=1000N = 1000, this means roughly 500,000 node evaluations, which is fast on modern computers. However, for path-dependent options or trees with many underlying assets, complexity grows quickly.

Memory can be optimized by recognizing that we only need two adjacent columns at any time during backward induction. This reduces memory from O(N2)O(N^2) to O(N)O(N):

In[32]:
Code
import numpy as np


def binomial_tree_memory_efficient(S0, K, T, r, sigma, N, option_type="call"):
    """
    Memory-efficient binomial tree using only O(N) storage.
    """
    dt = T / N
    u = np.exp(sigma * np.sqrt(dt))
    d = 1 / u
    p = (np.exp(r * dt) - d) / (u - d)
    disc = np.exp(-r * dt)

    # Only store one column of option values
    f = np.zeros(N + 1)

    # Terminal payoffs
    for j in range(N + 1):
        S = S0 * (u**j) * (d ** (N - j))
        if option_type == "call":
            f[j] = max(S - K, 0)
        else:
            f[j] = max(K - S, 0)

    # Backward induction (in-place update)
    for i in range(N - 1, -1, -1):
        for j in range(i + 1):
            f[j] = disc * (p * f[j + 1] + (1 - p) * f[j])

    return f[0]
In[33]:
Code
# Verify memory-efficient version matches standard implementation
S0, K, T, r, sigma = 100, 100, 1, 0.05, 0.2
N = 500

standard = binomial_tree_european(S0, K, T, r, sigma, N, "call")
efficient = binomial_tree_memory_efficient(S0, K, T, r, sigma, N, "call")
diff = abs(standard - efficient)
Out[34]:
Console
Standard implementation:        $10.446585
Memory-efficient implementation: $10.446585
Difference: $0.0000000000

The negligible difference confirms that the memory-efficient implementation produces identical results to the standard approach, validating the optimization.

Oscillation and Convergence Enhancement

The oscillation in binomial prices can be problematic when high precision is needed. Several techniques improve convergence:

  • Richardson extrapolation: Combine prices from N and 2N steps to cancel leading-order errors
  • Smoothing: Average prices from N and N+1 steps
  • Adaptive mesh: Place nodes exactly at the strike price
In[35]:
Code
# Richardson extrapolation for improved convergence
def richardson_extrapolation(S0, K, T, r, sigma, N):
    """Use Richardson extrapolation to improve convergence."""
    price_N = binomial_tree_european(S0, K, T, r, sigma, N, "call")
    price_2N = binomial_tree_european(S0, K, T, r, sigma, 2 * N, "call")
    # Richardson extrapolation assuming O(1/N) convergence
    return 2 * price_2N - price_N


S0, K, T, r, sigma = 100, 100, 1, 0.05, 0.2
bs = black_scholes_price(S0, K, T, r, sigma, "call")

step_counts = [25, 50, 100, 200]
richardson_results = []

for N in step_counts:
    standard = binomial_tree_european(S0, K, T, r, sigma, N, "call")
    richardson = richardson_extrapolation(S0, K, T, r, sigma, N)
    richardson_results.append((N, standard, richardson, richardson - bs))
Out[36]:
Console
Richardson Extrapolation Improvement
=======================================================
Black-Scholes price: $10.450584

     N     Standard   Richardson     BS Error
-------------------------------------------------------
    25 $  10.520966 $  10.300417 $  -0.150166
    50 $  10.410692 $  10.450532 $  -0.000052
   100 $  10.430612 $  10.450571 $  -0.000013
   200 $  10.440591 $  10.450580 $  -0.000003

Richardson extrapolation significantly reduces the error for a given number of steps, making high-precision pricing computationally cheaper.

Out[37]:
Visualization
Convergence error comparison between standard binomial pricing (blue) and Richardson extrapolation (red) on a logarithmic scale. Richardson extrapolation achieves significantly lower absolute errors for the same number of steps $N$, demonstrating a faster effective convergence rate than the standard $O(1/N)$ behavior.
Convergence error comparison between standard binomial pricing (blue) and Richardson extrapolation (red) on a logarithmic scale. Richardson extrapolation achieves significantly lower absolute errors for the same number of steps $N$, demonstrating a faster effective convergence rate than the standard $O(1/N)$ behavior.

Limitations of Binomial Trees

Despite their flexibility, binomial trees have important limitations:

  • Single underlying asset: Standard trees handle one underlying. Multi-asset options require multi-dimensional trees where nodes grow exponentially with the number of assets.

  • Constant volatility: The basic model assumes constant volatility. For options with significant volatility smile effects, as we explored in Part III Chapter 8, extensions like implied trees are needed.

  • Path independence assumption: The recombining tree structure assumes the derivative's value depends only on the current stock price, not the path taken. Path-dependent options like Asian or lookback options require different approaches, which we'll explore in Part III Chapter 13 on exotic options.

  • Slow convergence for some payoffs: Options with discontinuous payoffs (like digital options) converge slowly because the discrete tree poorly approximates the discontinuity.

For options where binomial trees become cumbersome, Monte Carlo simulation (covered in the next chapter) and finite difference methods (Part III Chapter 12) offer powerful alternatives.

Summary

Binomial trees provide an intuitive and flexible framework for option pricing that bridges the gap between theoretical understanding and practical computation.

The key concepts from this chapter include:

  • Discrete-time modeling: Stock prices evolve through discrete up and down moves, with the Cox-Ross-Rubinstein parameterization u=eσΔtu = e^{\sigma\sqrt{\Delta t}} and d=1/ud = 1/u ensuring convergence to geometric Brownian motion.

  • Risk-neutral valuation: The risk-neutral probability p=erΔtdudp = \frac{e^{r\Delta t} - d}{u - d} allows pricing as a discounted expected payoff without knowing the true probability of up moves.

  • Backward induction: Option prices are computed by working backward from known terminal payoffs, applying the one-step pricing formula at each node.

  • American options: Early exercise is handled by comparing exercise value to continuation value at each node, making binomial trees the method of choice for American-style derivatives.

  • Convergence to Black-Scholes: As the number of steps increases, binomial prices converge to Black-Scholes at rate O(1/N)O(1/N), with Richardson extrapolation improving accuracy.

The binomial framework extends naturally to dividend-paying stocks, exotic features, and serves as a foundation for understanding more advanced numerical methods. In the next chapter, we'll explore Monte Carlo simulation, which handles high-dimensional problems and path-dependent options that are challenging for tree-based methods.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about binomial tree option pricing.

Loading component...

Reference

BIBTEXAcademic
@misc{binomialtreeoptionpricingamericanoptionscrrmodel, author = {Michael Brenndoerfer}, title = {Binomial Tree Option Pricing: American Options & CRR Model}, year = {2025}, url = {https://mbrenndoerfer.com/writing/binomial-tree-option-pricing-cox-ross-rubinstein}, organization = {mbrenndoerfer.com}, note = {Accessed: 2025-01-01} }
APAAcademic
Michael Brenndoerfer (2025). Binomial Tree Option Pricing: American Options & CRR Model. Retrieved from https://mbrenndoerfer.com/writing/binomial-tree-option-pricing-cox-ross-rubinstein
MLAAcademic
Michael Brenndoerfer. "Binomial Tree Option Pricing: American Options & CRR Model." 2026. Web. today. <https://mbrenndoerfer.com/writing/binomial-tree-option-pricing-cox-ross-rubinstein>.
CHICAGOAcademic
Michael Brenndoerfer. "Binomial Tree Option Pricing: American Options & CRR Model." Accessed today. https://mbrenndoerfer.com/writing/binomial-tree-option-pricing-cox-ross-rubinstein.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Binomial Tree Option Pricing: American Options & CRR Model'. Available at: https://mbrenndoerfer.com/writing/binomial-tree-option-pricing-cox-ross-rubinstein (Accessed: today).
SimpleBasic
Michael Brenndoerfer (2025). Binomial Tree Option Pricing: American Options & CRR Model. https://mbrenndoerfer.com/writing/binomial-tree-option-pricing-cox-ross-rubinstein