Adjust the sliders to build a binomial lattice. Each node shows the stock price; the highlighted bottom row shows the option payoff at expiry. The model folds those payoffs back to give the fair price today (top-left node). Increase the number of steps and watch the price converge toward the Black-Scholes value.
<!-- Controls for the binomial lattice demo -->
<div class="controls">
<label>Steps: <span id="stepsVal">8</span>
<input type="range" id="steps" min="2" max="20" value="8">
</label>
<label>Volatility: <span id="volVal">20</span>%
<input type="range" id="vol" min="5" max="80" value="20">
</label>
<label>Strike: lt;span id="strikeVal">100</span>
<input type="range" id="strike" min="80" max="120" value="100">
</label>
</div>
<div class="results">
<div class="result-box">
<div class="result-label">Binomial price</div>
<div class="result-value" id="binomialPrice">—</div>
</div>
<div class="result-box">
<div class="result-label">Black-Scholes</div>
<div class="result-value" id="bsPrice">—</div>
</div>
<div class="result-box">
<div class="result-label">Difference</div>
<div class="result-value" id="diffVal">—</div>
</div>
</div>
<canvas id="chart" width="480" height="200"></canvas>
<div class="hint-bar" id="hintBar">Adjust sliders to see how the binomial price converges to Black-Scholes.</div>
/* Styles for the binomial option pricing demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; background: transparent; }
.controls { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .8rem; }
label { font-size: .85rem; color: #444; display: flex; align-items: center; gap: .5rem; }
input[type=range] { flex: 1; cursor: pointer; }
.results { display: flex; gap: .6rem; margin-bottom: .8rem; }
.result-box { flex: 1; background: #e8eef3; border-radius: 8px; padding: .5rem .7rem; text-align: center; }
.result-label { font-size: .72rem; color: #666; margin-bottom: .2rem; }
.result-value { font: 700 1.1rem ui-monospace, monospace; color: #1d3557; }
canvas { display: block; width: 100%; max-width: 480px; background: #f7f9fb; border-radius: 8px; border: 1px solid #dde3ea; }
.hint-bar { font-size: .8rem; color: #555; margin-top: .5rem; min-height: 1.2em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Fixed model parameters: spot price, risk-free rate, time to maturity
const S0 = 100; // spot price (current stock price)
const r = 0.05; // annual risk-free rate (5%)
const T = 1.0; // time to maturity in years
// Black-Scholes closed-form for a European call (Abramowitz & Stegun normal CDF approximation)
function normCDF(x) {
const a1=0.254829592,a2=-0.284496736,a3=1.421413741,a4=-1.453152027,a5=1.061405429,p=0.3275911;
const sign = x < 0 ? -1 : 1;
x = Math.abs(x) / Math.sqrt(2);
const t = 1.0 / (1.0 + p * x);
const y = 1.0 - (((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-x*x);
return 0.5 * (1.0 + sign * y);
}
function blackScholes(S, K, T, r, sigma) {
// d1 and d2 from the Black-Scholes formula
const d1 = (Math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * Math.sqrt(T));
const d2 = d1 - sigma * Math.sqrt(T);
return S * normCDF(d1) - K * Math.exp(-r * T) * normCDF(d2);
}
// Cox-Ross-Rubinstein binomial model: compute option price for n steps
function binomialPrice(S, K, T, r, sigma, n) {
const dt = T / n;
const u = Math.exp(sigma * Math.sqrt(dt)); // up factor per step
const d = 1 / u; // down factor (recombining tree)
const p = (Math.exp(r * dt) - d) / (u - d); // risk-neutral up probability
const disc = Math.exp(-r * dt); // one-step discount factor
// terminal payoffs at expiry
const vals = [];
for (let j = 0; j <= n; j++) {
const ST = S * Math.pow(u, n - j) * Math.pow(d, j);
vals.push(Math.max(ST - K, 0));
}
// backward induction through the tree
for (let i = n - 1; i >= 0; i--) {
for (let j = 0; j <= i; j++) {
vals[j] = disc * (p * vals[j] + (1 - p) * vals[j + 1]);
}
}
return vals[0];
}
// compute binomial prices for a range of step counts
function computeSeries(K, sigma) {
const ns = [2,3,4,5,6,7,8,9,10,12,14,16,18,20];
return ns.map(n => ({ n, price: binomialPrice(S0, K, T, r, sigma, n) }));
}
// draw convergence chart on canvas
function drawChart(series, bsVal, K, sigma) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const prices = series.map(p => p.price);
const allPrices = [...prices, bsVal];
const minP = Math.max(0, Math.min(...allPrices) - 0.5);
const maxP = Math.max(...allPrices) + 0.5;
const pad = { l: 40, r: 20, t: 16, b: 30 };
const toX = (i) => pad.l + (i / (series.length - 1)) * (W - pad.l - pad.r);
const toY = (v) => pad.t + (1 - (v - minP) / (maxP - minP)) * (H - pad.t - pad.b);
// draw horizontal Black-Scholes reference line
ctx.setLineDash([4, 4]);
ctx.strokeStyle = '#e63946';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(pad.l, toY(bsVal));
ctx.lineTo(W - pad.r, toY(bsVal));
ctx.stroke();
ctx.setLineDash([]);
// draw binomial price curve
ctx.strokeStyle = '#1d3557';
ctx.lineWidth = 2;
ctx.beginPath();
series.forEach((pt, i) => {
const x = toX(i), y = toY(pt.price);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
// draw data-point dots
series.forEach((pt, i) => {
ctx.fillStyle = '#1d3557';
ctx.beginPath();
ctx.arc(toX(i), toY(pt.price), 3, 0, 2 * Math.PI);
ctx.fill();
});
// draw axis labels
ctx.fillStyle = '#666';
ctx.font = '10px system-ui';
ctx.textAlign = 'center';
series.forEach((pt, i) => {
if (i === 0 || pt.n % 4 === 0) ctx.fillText(pt.n, toX(i), H - 6);
});
ctx.textAlign = 'right';
ctx.fillText('
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ minP.toFixed(1), pad.l - 4, toY(minP));
ctx.fillText('
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ maxP.toFixed(1), pad.l - 4, toY(maxP));
}
function update() {
const n = +document.getElementById('steps').value;
const sigma = +document.getElementById('vol').value / 100;
const K = +document.getElementById('strike').value;
document.getElementById('stepsVal').textContent = n;
document.getElementById('volVal').textContent = Math.round(sigma * 100);
document.getElementById('strikeVal').textContent = K;
const bp = binomialPrice(S0, K, T, r, sigma, n);
const bsp = blackScholes(S0, K, T, r, sigma);
const diff = Math.abs(bp - bsp);
document.getElementById('binomialPrice').textContent = '
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ bp.toFixed(4);
document.getElementById('bsPrice').textContent = '
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ bsp.toFixed(4);
document.getElementById('diffVal').textContent = '
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ diff.toFixed(4);
const series = computeSeries(K, sigma);
drawChart(series, bsp, K, sigma);
const pct = (diff / bsp * 100).toFixed(2);
document.getElementById('hintBar').textContent = i18nFmt('With %n% steps the binomial price differs from Black-Scholes by $%diff% (%pct%).', { n: n, diff: diff.toFixed(4), pct: pct });
}
['steps', 'vol', 'strike'].forEach(id => document.getElementById(id).addEventListener('input', update));
update();
<!-- Controls for the binomial lattice demo -->
<div class="controls">
<label>Steps: <span id="stepsVal">8</span>
<input type="range" id="steps" min="2" max="20" value="8">
</label>
<label>Volatility: <span id="volVal">20</span>%
<input type="range" id="vol" min="5" max="80" value="20">
</label>
<label>Strike: lt;span id="strikeVal">100</span>
<input type="range" id="strike" min="80" max="120" value="100">
</label>
</div>
<div class="results">
<div class="result-box">
<div class="result-label">Binomial price</div>
<div class="result-value" id="binomialPrice">—</div>
</div>
<div class="result-box">
<div class="result-label">Black-Scholes</div>
<div class="result-value" id="bsPrice">—</div>
</div>
<div class="result-box">
<div class="result-label">Difference</div>
<div class="result-value" id="diffVal">—</div>
</div>
</div>
<canvas id="chart" width="480" height="200"></canvas>
<div class="hint-bar" id="hintBar">Adjust sliders to see how the binomial price converges to Black-Scholes.</div>
/* Styles for the binomial option pricing demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; background: transparent; }
.controls { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .8rem; }
label { font-size: .85rem; color: #444; display: flex; align-items: center; gap: .5rem; }
input[type=range] { flex: 1; cursor: pointer; }
.results { display: flex; gap: .6rem; margin-bottom: .8rem; }
.result-box { flex: 1; background: #e8eef3; border-radius: 8px; padding: .5rem .7rem; text-align: center; }
.result-label { font-size: .72rem; color: #666; margin-bottom: .2rem; }
.result-value { font: 700 1.1rem ui-monospace, monospace; color: #1d3557; }
canvas { display: block; width: 100%; max-width: 480px; background: #f7f9fb; border-radius: 8px; border: 1px solid #dde3ea; }
.hint-bar { font-size: .8rem; color: #555; margin-top: .5rem; min-height: 1.2em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Fixed model parameters: spot price, risk-free rate, time to maturity
const S0 = 100; // spot price (current stock price)
const r = 0.05; // annual risk-free rate (5%)
const T = 1.0; // time to maturity in years
// Black-Scholes closed-form for a European call (Abramowitz & Stegun normal CDF approximation)
function normCDF(x) {
const a1=0.254829592,a2=-0.284496736,a3=1.421413741,a4=-1.453152027,a5=1.061405429,p=0.3275911;
const sign = x < 0 ? -1 : 1;
x = Math.abs(x) / Math.sqrt(2);
const t = 1.0 / (1.0 + p * x);
const y = 1.0 - (((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-x*x);
return 0.5 * (1.0 + sign * y);
}
function blackScholes(S, K, T, r, sigma) {
// d1 and d2 from the Black-Scholes formula
const d1 = (Math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * Math.sqrt(T));
const d2 = d1 - sigma * Math.sqrt(T);
return S * normCDF(d1) - K * Math.exp(-r * T) * normCDF(d2);
}
// Cox-Ross-Rubinstein binomial model: compute option price for n steps
function binomialPrice(S, K, T, r, sigma, n) {
const dt = T / n;
const u = Math.exp(sigma * Math.sqrt(dt)); // up factor per step
const d = 1 / u; // down factor (recombining tree)
const p = (Math.exp(r * dt) - d) / (u - d); // risk-neutral up probability
const disc = Math.exp(-r * dt); // one-step discount factor
// terminal payoffs at expiry
const vals = [];
for (let j = 0; j <= n; j++) {
const ST = S * Math.pow(u, n - j) * Math.pow(d, j);
vals.push(Math.max(ST - K, 0));
}
// backward induction through the tree
for (let i = n - 1; i >= 0; i--) {
for (let j = 0; j <= i; j++) {
vals[j] = disc * (p * vals[j] + (1 - p) * vals[j + 1]);
}
}
return vals[0];
}
// compute binomial prices for a range of step counts
function computeSeries(K, sigma) {
const ns = [2,3,4,5,6,7,8,9,10,12,14,16,18,20];
return ns.map(n => ({ n, price: binomialPrice(S0, K, T, r, sigma, n) }));
}
// draw convergence chart on canvas
function drawChart(series, bsVal, K, sigma) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const prices = series.map(p => p.price);
const allPrices = [...prices, bsVal];
const minP = Math.max(0, Math.min(...allPrices) - 0.5);
const maxP = Math.max(...allPrices) + 0.5;
const pad = { l: 40, r: 20, t: 16, b: 30 };
const toX = (i) => pad.l + (i / (series.length - 1)) * (W - pad.l - pad.r);
const toY = (v) => pad.t + (1 - (v - minP) / (maxP - minP)) * (H - pad.t - pad.b);
// draw horizontal Black-Scholes reference line
ctx.setLineDash([4, 4]);
ctx.strokeStyle = '#e63946';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(pad.l, toY(bsVal));
ctx.lineTo(W - pad.r, toY(bsVal));
ctx.stroke();
ctx.setLineDash([]);
// draw binomial price curve
ctx.strokeStyle = '#1d3557';
ctx.lineWidth = 2;
ctx.beginPath();
series.forEach((pt, i) => {
const x = toX(i), y = toY(pt.price);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
// draw data-point dots
series.forEach((pt, i) => {
ctx.fillStyle = '#1d3557';
ctx.beginPath();
ctx.arc(toX(i), toY(pt.price), 3, 0, 2 * Math.PI);
ctx.fill();
});
// draw axis labels
ctx.fillStyle = '#666';
ctx.font = '10px system-ui';
ctx.textAlign = 'center';
series.forEach((pt, i) => {
if (i === 0 || pt.n % 4 === 0) ctx.fillText(pt.n, toX(i), H - 6);
});
ctx.textAlign = 'right';
ctx.fillText('
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ minP.toFixed(1), pad.l - 4, toY(minP));
ctx.fillText('
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ maxP.toFixed(1), pad.l - 4, toY(maxP));
}
function update() {
const n = +document.getElementById('steps').value;
const sigma = +document.getElementById('vol').value / 100;
const K = +document.getElementById('strike').value;
document.getElementById('stepsVal').textContent = n;
document.getElementById('volVal').textContent = Math.round(sigma * 100);
document.getElementById('strikeVal').textContent = K;
const bp = binomialPrice(S0, K, T, r, sigma, n);
const bsp = blackScholes(S0, K, T, r, sigma);
const diff = Math.abs(bp - bsp);
document.getElementById('binomialPrice').textContent = '
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ bp.toFixed(4);
document.getElementById('bsPrice').textContent = '
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ bsp.toFixed(4);
document.getElementById('diffVal').textContent = '
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
+ diff.toFixed(4);
const series = computeSeries(K, sigma);
drawChart(series, bsp, K, sigma);
const pct = (diff / bsp * 100).toFixed(2);
document.getElementById('hintBar').textContent = i18nFmt('With %n% steps the binomial price differs from Black-Scholes by $%diff% (%pct%).', { n: n, diff: diff.toFixed(4), pct: pct });
}
['steps', 'vol', 'strike'].forEach(id => document.getElementById(id).addEventListener('input', update));
update();
Notice how even 4–5 steps give a good approximation, and by 20 steps the binomial price is within a fraction of a cent of the Black-Scholes closed form. The tree trades continuous mathematics for a simple loop — and the two answers agree in the limit.
Comments
Loading comments...