Multi-Step Binomial Trees
The
one-step model priced an option over a single jump — realistic for nothing, but the whole
point was that it's a Lego brick. Chain many of these one-step bricks together, back to back, and the
crude "up or down" market becomes a rich lattice of possible price paths that can approximate any
continuous-time model as closely as you like. This is how a desk actually implements pricing for
instruments that Black–Scholes can't touch — and, reassuringly, if you chain enough bricks together for a
plain European option, you get the Black–Scholes
price back out, to as many decimal places as you like.
Chaining one-step nodes into a tree
Split the option's life T into N equal steps of
length \Delta t = T/N. At every node of the resulting lattice, the
stock still only moves up by a factor u or down by a factor
d — the one-step model, over and over. Because
u and d are the same at every node, an up-move
followed by a down-move lands on exactly the same price as a down-move followed by an up-move
(S_0 u d = S_0 d u). The tree recombines: after
N steps there are only N+1 distinct terminal prices,
not 2^N — the difference between a lattice you can compute on a laptop and one
you can't.
Pricing is backward induction, exactly the one-step formula applied again and again.
First fill in the terminal payoffs at every leaf — that needs no probability at all, just the payoff
function. Then walk backward one layer at a time: at every node, the value is the one-step discounted
q-expectation of the two nodes directly ahead of it,
V_{i,j} = e^{-r\Delta t}\Bigl(q\,V_{i+1,j+1} + (1-q)\,V_{i+1,j}\Bigr),
where i indexes the time step and j the number of
up-moves so far. Repeat until you reach the root, and the number sitting there is
V_0. Every node uses the same q, because
u, d and the per-step rate never change across the
tree — the one-step recipe just repeats itself N times.
Choosing u and d: the Cox–Ross–Rubinstein tree
The one-step model left u and d as free inputs. To
make an N-step tree approximate a particular continuous-time stock — one with
volatility \sigma — Cox, Ross and Rubinstein (1979) proposed
matching the tree's variance per step to the stock's:
- Step length: \Delta t = T/N, shrinking as N
grows.
- Up and down factors: u = e^{\sigma\sqrt{\Delta t}},
d = 1/u — reciprocal factors, which is exactly what makes the tree
recombine cleanly.
- Risk-neutral probability (continuous compounding at rate r per step):
q = \dfrac{e^{r\Delta t} - d}{u - d}.
- As N \to \infty, the CRR tree's terminal distribution converges to the
lognormal
distribution of geometric Brownian motion, and the tree's price converges to the Black–Scholes
price.
Notice the shape of the trick: u and d no longer
come from a market view — they're engineered from \sigma alone, purely
so the discrete tree mimics the continuous model as N grows.
Watching it converge
Price a European call with S_0 = K = 100, r = 5\%,
\sigma = 20\%, T = 1 year, on CRR trees of growing
N, and compare against the closed-form Black–Scholes price
(10.4506):
// Cox-Ross-Rubinstein binomial price vs. the closed-form Black-Scholes price,
// for a European call, as the number of tree steps N grows.
function erf(x: number): number {
const sign = x < 0 ? -1 : 1;
x = Math.abs(x);
const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741;
const a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911;
const t = 1 / (1 + p * x);
const y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return sign * y;
}
function normCdf(x: number): number {
return 0.5 * (1 + erf(x / Math.sqrt(2)));
}
function bsCall(S0: number, K: number, r: number, sig: number, T: number): number {
const d1 = (Math.log(S0 / K) + (r + 0.5 * sig * sig) * T) / (sig * Math.sqrt(T));
const d2 = d1 - sig * Math.sqrt(T);
return S0 * normCdf(d1) - K * Math.exp(-r * T) * normCdf(d2);
}
function crrCall(S0: number, K: number, r: number, sig: number, T: number, N: number): number {
const dt = T / N;
const u = Math.exp(sig * Math.sqrt(dt));
const d = 1 / u;
const disc = Math.exp(-r * dt);
const q = (Math.exp(r * dt) - d) / (u - d);
const payoffs: number[] = [];
for (let i = 0; i <= N; i++) {
const ST = S0 * Math.pow(u, N - i) * Math.pow(d, i);
payoffs.push(Math.max(ST - K, 0));
}
for (let step = N; step > 0; step--) {
for (let i = 0; i < step; i++) {
payoffs[i] = disc * (q * payoffs[i] + (1 - q) * payoffs[i + 1]);
}
}
return payoffs[0];
}
const S0 = 100, K = 100, r = 0.05, sig = 0.2, T = 1;
const bs = bsCall(S0, K, r, sig, T);
console.log(`Black-Scholes price = ${bs.toFixed(4)}`);
for (const N of [1, 2, 5, 10, 20, 50, 100, 200]) {
const price = crrCall(S0, K, r, sig, T, N);
console.log(`N=${N.toString().padStart(3)} CRR price=${price.toFixed(4)} gap=${(price - bs).toFixed(4)}`);
}
The gap column shrinks towards zero, but not smoothly — it flips sign and shrinks in a zig-zag. That
wobble, and why it happens, is worth a closer look below. The chart shows the same convergence over a
wider range of N, against the Black–Scholes price as a flat reference line.
The wobble is a discretization artifact, not noise — CRR trees are entirely
deterministic, so the same N always gives the same price. The culprit is where
the strike K lands relative to the tree's terminal nodes. For some
N a terminal node sits almost exactly at K, which
the discrete tree handles awkwardly (that node's payoff is either counted fully as in-the-money or fully
as out-of-the-money, with nothing in between); for other N the nodes straddle
K more evenly. As N increases by one, which case
you're in flips back and forth, so the error oscillates in sign even as its size shrinks roughly
like 1/N. Practitioners route around it in two ways: average the price at
N and N+1 steps (the oscillations largely cancel),
or use a variant like the Trigeorgis or Jarrow–Rudd tree that centres the nodes around
K deliberately.
-
\Delta t shrinks with N — recompute
u and d every time N
changes. A one-step u and d calibrated
to a whole year is not the right u, d for
a single day-long step in a 252-step tree. Because
u = e^{\sigma\sqrt{\Delta t}}, halving the step length does not halve
u-1 — it shrinks it by a factor of \sqrt{2}.
-
More steps is not automatically "more accurate" for a fixed budget of arithmetic. Cost
grows like N^2 (there are roughly N^2/2 nodes to
fill in), so doubling N quadruples the work for what — thanks to the
oscillation above — might be a worse answer than a slightly smaller, better-chosen
N.
-
The recombining property depends on u and d
being constant across the whole tree. If volatility (and so u,d)
were allowed to change from step to step, up-then-down would no longer equal down-then-up, the tree
would stop recombining, and the node count would explode back to 2^N.