Pick a quoted option price and let the solver find the implied volatility. The demo uses the Black-Scholes call formula and locates σ with a simple bisection search: it brackets the answer between a low and high volatility, then halves the interval until the computed price matches the target.
<!-- Main layout — controls on top, result text, then canvas chart -->
<div class="controls">
<div class="row">
<label>Stock price S<input type="number" id="S" value="100" min="1" max="500" step="1"></label>
<label>Strike K<input type="number" id="K" value="100" min="1" max="500" step="1"></label>
<label>Rate r<input type="number" id="r" value="0.05" min="0" max="0.3" step="0.01"></label>
<label>Time T (yr)<input type="number" id="T" value="0.5" min="0.01" max="3" step="0.01"></label>
</div>
<div class="row">
<label>Option price C<input type="number" id="optPrice" value="8.00" min="0.01" max="400" step="0.01"></label>
<button id="solveBtn" type="button">Solve for IV</button>
<button id="smileBtn" type="button">Show smile</button>
</div>
</div>
<div id="result" class="result"></div>
<canvas id="chart" width="480" height="220" aria-label="Black-Scholes price curve or implied volatility smile chart"></canvas>
<div class="legend" id="legend"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { background: #f0f4f8; border-radius: 10px; padding: .7rem .8rem .5rem; margin-bottom: .6rem; }
.row { display: flex; flex-wrap: wrap; gap: .5rem .8rem; align-items: flex-end; margin-bottom: .4rem; }
label { font-size: .78rem; font-weight: 600; color: #445; display: flex; flex-direction: column; gap: 2px; }
input[type=number] { font-size: .88rem; padding: .25rem .4rem; border: 1px solid #bcc; border-radius: 6px;
width: 80px; background: #fff; }
button { font: 600 .82rem system-ui; padding: .35rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button:last-child { background: #2a7f62; border-color: #2a7f62; }
.result { font-size: .95rem; font-weight: 600; min-height: 1.5em; margin-bottom: .4rem; }
.result.ok { color: #1a7d40; }
.result.bad { color: #c0392b; }
canvas { display: block; max-width: 100%; border-radius: 8px; background: #f7fafc; }
.legend { font-size: .78rem; color: #556; margin-top: .3rem; display: flex; gap: 1rem; flex-wrap: wrap; }
.legend span { display: flex; align-items: center; gap: 4px; }
.legend .dot { width: 12px; height: 4px; border-radius: 2px; display: inline-block; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Black-Scholes implied volatility solver
// C = S·N(d1) - K·exp(-rT)·N(d2), invert numerically for sigma
// Normal CDF via Abramowitz & Stegun approximation (error < 1.5e-7)
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 / (1 + p * x);
const y = 1 - (((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-x*x);
return 0.5 * (1 + sign * y);
}
// Black-Scholes European call price
function bsCall(S, K, r, T, sigma) {
if (sigma <= 0 || T <= 0) return Math.max(S - K * Math.exp(-r * T), 0);
const d1 = (Math.log(S / K) + (r + sigma * sigma / 2) * T) / (sigma * Math.sqrt(T));
const d2 = d1 - sigma * Math.sqrt(T);
return S * normCDF(d1) - K * Math.exp(-r * T) * normCDF(d2);
}
// Bisection root-finder — returns { iv, iter } or null if price out of range
function bisectIV(S, K, r, T, targetPrice, tol, maxIter) {
const intrinsic = Math.max(S - K * Math.exp(-r * T), 0);
const maxPrice = S;
if (targetPrice <= intrinsic || targetPrice >= maxPrice) return null;
let lo = 1e-6, hi = 10, iter = 0;
while (iter < maxIter) {
const mid = (lo + hi) / 2;
const price = bsCall(S, K, r, T, mid);
if (Math.abs(price - targetPrice) < tol) return { iv: mid, iter: iter + 1 };
if (price < targetPrice) lo = mid; else hi = mid;
iter++;
}
return { iv: (lo + hi) / 2, iter: maxIter };
}
function getInputs() {
return {
S: parseFloat(document.getElementById('S').value),
K: parseFloat(document.getElementById('K').value),
r: parseFloat(document.getElementById('r').value),
T: parseFloat(document.getElementById('T').value),
price: parseFloat(document.getElementById('optPrice').value)
};
}
const resultEl = document.getElementById('result');
function setResult(txt, cls) { resultEl.textContent = txt; resultEl.className = 'result ' + cls; }
// Read inputs, run bisection, show result and draw the B-S curve
function solve() {
const { S, K, r, T, price } = getInputs();
if ([S, K, r, T, price].some(isNaN) || S <= 0 || K <= 0 || T <= 0 || price <= 0) {
setResult('Invalid inputs — check that all fields are positive numbers.', 'bad'); return;
}
const res = bisectIV(S, K, r, T, price, 1e-6, 100);
if (!res) { setResult('No solution — price is outside the valid range for this option.', 'bad'); return; }
const ivPct = (res.iv * 100).toFixed(2);
setResult(i18nFmt('Implied volatility: %iv%% (converged in %iter% iterations)', { iv: ivPct, iter: res.iter }), 'ok');
drawSingle(S, K, r, T, res.iv);
}
// Build implied-volatility smile across a range of strikes
function smile() {
const { S, r, T } = getInputs();
const strikes = [];
const ivs = [];
// Moneyness ratios K/S from 0.70 to 1.30
const moneyness = [0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00, 1.05, 1.10, 1.15, 1.20, 1.25, 1.30];
// Simulated 'market' IVs — higher at extremes, smile shape
const trueIVs = [0.38, 0.33, 0.28, 0.24, 0.21, 0.19, 0.18, 0.19, 0.20, 0.22, 0.24, 0.26, 0.28];
moneyness.forEach((m, i) => {
const K = Math.round(S * m);
const sigma = trueIVs[i];
const price = bsCall(S, K, r, T, sigma);
const res = bisectIV(S, K, r, T, price, 1e-6, 100);
if (res) { strikes.push(K); ivs.push(res.iv * 100); }
});
setResult('Volatility smile plotted across strikes.', 'ok');
drawSmile(S, strikes, ivs);
}
// Draw Black-Scholes price vs sigma, mark the solved IV
function drawSingle(S, K, r, T, iv) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const sigmas = [], prices = [];
for (let i = 1; i <= 80; i++) {
const s = i * 0.01;
sigmas.push(s);
prices.push(bsCall(S, K, r, T, s));
}
const maxP = Math.max(...prices);
const pad = { l: 48, r: 16, t: 18, b: 36 };
const toX = s => pad.l + (s - 0.01) / (0.80) * (W - pad.l - pad.r);
const toY = p => pad.t + (1 - p / (maxP * 1.05)) * (H - pad.t - pad.b);
// Draw axes
ctx.strokeStyle = '#aab'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, H - pad.b); ctx.lineTo(W - pad.r, H - pad.b); ctx.stroke();
ctx.fillStyle = '#778'; ctx.font = '11px system-ui'; ctx.textAlign = 'center';
ctx.fillText('σ (volatility)', pad.l + (W - pad.l - pad.r) / 2, H - 4);
ctx.save(); ctx.rotate(-Math.PI / 2); ctx.fillText('C (price)', -(pad.t + (H - pad.t - pad.b) / 2), 13); ctx.restore();
// Draw B-S call price curve
ctx.strokeStyle = '#2a5f8f'; ctx.lineWidth = 2; ctx.beginPath();
sigmas.forEach((s, i) => { const x = toX(s), y = toY(prices[i]); i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); });
ctx.stroke();
// Mark the solved IV with crosshairs
const x0 = toX(iv), y0 = toY(bsCall(S, K, r, T, iv));
ctx.strokeStyle = '#e63946'; ctx.lineWidth = 1.5; ctx.setLineDash([4, 3]);
ctx.beginPath(); ctx.moveTo(x0, H - pad.b); ctx.lineTo(x0, y0); ctx.stroke();
ctx.beginPath(); ctx.moveTo(pad.l, y0); ctx.lineTo(x0, y0); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#e63946'; ctx.beginPath(); ctx.arc(x0, y0, 4, 0, 2 * Math.PI); ctx.fill();
ctx.fillStyle = '#333'; ctx.textAlign = 'left'; ctx.font = 'bold 11px system-ui';
ctx.fillText(i18nFmt('IV = %iv%%', { iv: (iv * 100).toFixed(1) }), x0 + 6, y0 - 4);
document.getElementById('legend').innerHTML =
`<span><span class="dot" style="background:#2a5f8f"></span>B-S call price vs σ</span>` +
`<span><span class="dot" style="background:#e63946"></span>Implied volatility</span>`;
}
// Draw volatility smile — IV vs strike
function drawSmile(S, strikes, ivs) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const minK = strikes[0], maxK = strikes[strikes.length - 1];
const minIV = Math.min(...ivs) * 0.9, maxIV = Math.max(...ivs) * 1.05;
const pad = { l: 48, r: 16, t: 18, b: 36 };
const toX = k => pad.l + (k - minK) / (maxK - minK) * (W - pad.l - pad.r);
const toY = iv => pad.t + (1 - (iv - minIV) / (maxIV - minIV)) * (H - pad.t - pad.b);
ctx.strokeStyle = '#aab'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, H - pad.b); ctx.lineTo(W - pad.r, H - pad.b); ctx.stroke();
ctx.fillStyle = '#778'; ctx.font = '11px system-ui'; ctx.textAlign = 'center';
ctx.fillText('Strike K', pad.l + (W - pad.l - pad.r) / 2, H - 4);
ctx.save(); ctx.rotate(-Math.PI / 2); ctx.fillText('IV (%)', -(pad.t + (H - pad.t - pad.b) / 2), 13); ctx.restore();
ctx.strokeStyle = '#2a7f62'; ctx.lineWidth = 2.5; ctx.beginPath();
strikes.forEach((k, i) => { const x = toX(k), y = toY(ivs[i]); i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); });
ctx.stroke();
strikes.forEach((k, i) => {
ctx.fillStyle = '#2a7f62'; ctx.beginPath(); ctx.arc(toX(k), toY(ivs[i]), 3.5, 0, 2 * Math.PI); ctx.fill();
ctx.fillStyle = '#445'; ctx.textAlign = 'center'; ctx.font = '10px system-ui';
ctx.fillText(k, toX(k), H - pad.b + 14);
});
const atmX = toX(S);
ctx.strokeStyle = '#b0b'; ctx.lineWidth = 1; ctx.setLineDash([3, 3]);
ctx.beginPath(); ctx.moveTo(atmX, pad.t); ctx.lineTo(atmX, H - pad.b); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#b0b'; ctx.textAlign = 'center'; ctx.font = 'bold 10px system-ui';
ctx.fillText('ATM', atmX, pad.t + 10);
document.getElementById('legend').innerHTML =
`<span><span class="dot" style="background:#2a7f62"></span>Volatility smile</span>` +
`<span><span class="dot" style="background:#b0b"></span>At-the-money</span>`;
}
document.getElementById('solveBtn').addEventListener('click', solve);
document.getElementById('smileBtn').addEventListener('click', smile);
// Auto-solve on load
solve();
<!-- Main layout — controls on top, result text, then canvas chart -->
<div class="controls">
<div class="row">
<label>Stock price S<input type="number" id="S" value="100" min="1" max="500" step="1"></label>
<label>Strike K<input type="number" id="K" value="100" min="1" max="500" step="1"></label>
<label>Rate r<input type="number" id="r" value="0.05" min="0" max="0.3" step="0.01"></label>
<label>Time T (yr)<input type="number" id="T" value="0.5" min="0.01" max="3" step="0.01"></label>
</div>
<div class="row">
<label>Option price C<input type="number" id="optPrice" value="8.00" min="0.01" max="400" step="0.01"></label>
<button id="solveBtn" type="button">Solve for IV</button>
<button id="smileBtn" type="button">Show smile</button>
</div>
</div>
<div id="result" class="result"></div>
<canvas id="chart" width="480" height="220" aria-label="Black-Scholes price curve or implied volatility smile chart"></canvas>
<div class="legend" id="legend"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { background: #f0f4f8; border-radius: 10px; padding: .7rem .8rem .5rem; margin-bottom: .6rem; }
.row { display: flex; flex-wrap: wrap; gap: .5rem .8rem; align-items: flex-end; margin-bottom: .4rem; }
label { font-size: .78rem; font-weight: 600; color: #445; display: flex; flex-direction: column; gap: 2px; }
input[type=number] { font-size: .88rem; padding: .25rem .4rem; border: 1px solid #bcc; border-radius: 6px;
width: 80px; background: #fff; }
button { font: 600 .82rem system-ui; padding: .35rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button:last-child { background: #2a7f62; border-color: #2a7f62; }
.result { font-size: .95rem; font-weight: 600; min-height: 1.5em; margin-bottom: .4rem; }
.result.ok { color: #1a7d40; }
.result.bad { color: #c0392b; }
canvas { display: block; max-width: 100%; border-radius: 8px; background: #f7fafc; }
.legend { font-size: .78rem; color: #556; margin-top: .3rem; display: flex; gap: 1rem; flex-wrap: wrap; }
.legend span { display: flex; align-items: center; gap: 4px; }
.legend .dot { width: 12px; height: 4px; border-radius: 2px; display: inline-block; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Black-Scholes implied volatility solver
// C = S·N(d1) - K·exp(-rT)·N(d2), invert numerically for sigma
// Normal CDF via Abramowitz & Stegun approximation (error < 1.5e-7)
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 / (1 + p * x);
const y = 1 - (((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-x*x);
return 0.5 * (1 + sign * y);
}
// Black-Scholes European call price
function bsCall(S, K, r, T, sigma) {
if (sigma <= 0 || T <= 0) return Math.max(S - K * Math.exp(-r * T), 0);
const d1 = (Math.log(S / K) + (r + sigma * sigma / 2) * T) / (sigma * Math.sqrt(T));
const d2 = d1 - sigma * Math.sqrt(T);
return S * normCDF(d1) - K * Math.exp(-r * T) * normCDF(d2);
}
// Bisection root-finder — returns { iv, iter } or null if price out of range
function bisectIV(S, K, r, T, targetPrice, tol, maxIter) {
const intrinsic = Math.max(S - K * Math.exp(-r * T), 0);
const maxPrice = S;
if (targetPrice <= intrinsic || targetPrice >= maxPrice) return null;
let lo = 1e-6, hi = 10, iter = 0;
while (iter < maxIter) {
const mid = (lo + hi) / 2;
const price = bsCall(S, K, r, T, mid);
if (Math.abs(price - targetPrice) < tol) return { iv: mid, iter: iter + 1 };
if (price < targetPrice) lo = mid; else hi = mid;
iter++;
}
return { iv: (lo + hi) / 2, iter: maxIter };
}
function getInputs() {
return {
S: parseFloat(document.getElementById('S').value),
K: parseFloat(document.getElementById('K').value),
r: parseFloat(document.getElementById('r').value),
T: parseFloat(document.getElementById('T').value),
price: parseFloat(document.getElementById('optPrice').value)
};
}
const resultEl = document.getElementById('result');
function setResult(txt, cls) { resultEl.textContent = txt; resultEl.className = 'result ' + cls; }
// Read inputs, run bisection, show result and draw the B-S curve
function solve() {
const { S, K, r, T, price } = getInputs();
if ([S, K, r, T, price].some(isNaN) || S <= 0 || K <= 0 || T <= 0 || price <= 0) {
setResult('Invalid inputs — check that all fields are positive numbers.', 'bad'); return;
}
const res = bisectIV(S, K, r, T, price, 1e-6, 100);
if (!res) { setResult('No solution — price is outside the valid range for this option.', 'bad'); return; }
const ivPct = (res.iv * 100).toFixed(2);
setResult(i18nFmt('Implied volatility: %iv%% (converged in %iter% iterations)', { iv: ivPct, iter: res.iter }), 'ok');
drawSingle(S, K, r, T, res.iv);
}
// Build implied-volatility smile across a range of strikes
function smile() {
const { S, r, T } = getInputs();
const strikes = [];
const ivs = [];
// Moneyness ratios K/S from 0.70 to 1.30
const moneyness = [0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00, 1.05, 1.10, 1.15, 1.20, 1.25, 1.30];
// Simulated 'market' IVs — higher at extremes, smile shape
const trueIVs = [0.38, 0.33, 0.28, 0.24, 0.21, 0.19, 0.18, 0.19, 0.20, 0.22, 0.24, 0.26, 0.28];
moneyness.forEach((m, i) => {
const K = Math.round(S * m);
const sigma = trueIVs[i];
const price = bsCall(S, K, r, T, sigma);
const res = bisectIV(S, K, r, T, price, 1e-6, 100);
if (res) { strikes.push(K); ivs.push(res.iv * 100); }
});
setResult('Volatility smile plotted across strikes.', 'ok');
drawSmile(S, strikes, ivs);
}
// Draw Black-Scholes price vs sigma, mark the solved IV
function drawSingle(S, K, r, T, iv) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const sigmas = [], prices = [];
for (let i = 1; i <= 80; i++) {
const s = i * 0.01;
sigmas.push(s);
prices.push(bsCall(S, K, r, T, s));
}
const maxP = Math.max(...prices);
const pad = { l: 48, r: 16, t: 18, b: 36 };
const toX = s => pad.l + (s - 0.01) / (0.80) * (W - pad.l - pad.r);
const toY = p => pad.t + (1 - p / (maxP * 1.05)) * (H - pad.t - pad.b);
// Draw axes
ctx.strokeStyle = '#aab'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, H - pad.b); ctx.lineTo(W - pad.r, H - pad.b); ctx.stroke();
ctx.fillStyle = '#778'; ctx.font = '11px system-ui'; ctx.textAlign = 'center';
ctx.fillText('σ (volatility)', pad.l + (W - pad.l - pad.r) / 2, H - 4);
ctx.save(); ctx.rotate(-Math.PI / 2); ctx.fillText('C (price)', -(pad.t + (H - pad.t - pad.b) / 2), 13); ctx.restore();
// Draw B-S call price curve
ctx.strokeStyle = '#2a5f8f'; ctx.lineWidth = 2; ctx.beginPath();
sigmas.forEach((s, i) => { const x = toX(s), y = toY(prices[i]); i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); });
ctx.stroke();
// Mark the solved IV with crosshairs
const x0 = toX(iv), y0 = toY(bsCall(S, K, r, T, iv));
ctx.strokeStyle = '#e63946'; ctx.lineWidth = 1.5; ctx.setLineDash([4, 3]);
ctx.beginPath(); ctx.moveTo(x0, H - pad.b); ctx.lineTo(x0, y0); ctx.stroke();
ctx.beginPath(); ctx.moveTo(pad.l, y0); ctx.lineTo(x0, y0); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#e63946'; ctx.beginPath(); ctx.arc(x0, y0, 4, 0, 2 * Math.PI); ctx.fill();
ctx.fillStyle = '#333'; ctx.textAlign = 'left'; ctx.font = 'bold 11px system-ui';
ctx.fillText(i18nFmt('IV = %iv%%', { iv: (iv * 100).toFixed(1) }), x0 + 6, y0 - 4);
document.getElementById('legend').innerHTML =
`<span><span class="dot" style="background:#2a5f8f"></span>B-S call price vs σ</span>` +
`<span><span class="dot" style="background:#e63946"></span>Implied volatility</span>`;
}
// Draw volatility smile — IV vs strike
function drawSmile(S, strikes, ivs) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const minK = strikes[0], maxK = strikes[strikes.length - 1];
const minIV = Math.min(...ivs) * 0.9, maxIV = Math.max(...ivs) * 1.05;
const pad = { l: 48, r: 16, t: 18, b: 36 };
const toX = k => pad.l + (k - minK) / (maxK - minK) * (W - pad.l - pad.r);
const toY = iv => pad.t + (1 - (iv - minIV) / (maxIV - minIV)) * (H - pad.t - pad.b);
ctx.strokeStyle = '#aab'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, H - pad.b); ctx.lineTo(W - pad.r, H - pad.b); ctx.stroke();
ctx.fillStyle = '#778'; ctx.font = '11px system-ui'; ctx.textAlign = 'center';
ctx.fillText('Strike K', pad.l + (W - pad.l - pad.r) / 2, H - 4);
ctx.save(); ctx.rotate(-Math.PI / 2); ctx.fillText('IV (%)', -(pad.t + (H - pad.t - pad.b) / 2), 13); ctx.restore();
ctx.strokeStyle = '#2a7f62'; ctx.lineWidth = 2.5; ctx.beginPath();
strikes.forEach((k, i) => { const x = toX(k), y = toY(ivs[i]); i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); });
ctx.stroke();
strikes.forEach((k, i) => {
ctx.fillStyle = '#2a7f62'; ctx.beginPath(); ctx.arc(toX(k), toY(ivs[i]), 3.5, 0, 2 * Math.PI); ctx.fill();
ctx.fillStyle = '#445'; ctx.textAlign = 'center'; ctx.font = '10px system-ui';
ctx.fillText(k, toX(k), H - pad.b + 14);
});
const atmX = toX(S);
ctx.strokeStyle = '#b0b'; ctx.lineWidth = 1; ctx.setLineDash([3, 3]);
ctx.beginPath(); ctx.moveTo(atmX, pad.t); ctx.lineTo(atmX, H - pad.b); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#b0b'; ctx.textAlign = 'center'; ctx.font = 'bold 10px system-ui';
ctx.fillText('ATM', atmX, pad.t + 10);
document.getElementById('legend').innerHTML =
`<span><span class="dot" style="background:#2a7f62"></span>Volatility smile</span>` +
`<span><span class="dot" style="background:#b0b"></span>At-the-money</span>`;
}
document.getElementById('solveBtn').addEventListener('click', solve);
document.getElementById('smileBtn').addEventListener('click', smile);
// Auto-solve on load
solve();
Notice how the bisection homes in quickly — within 20 iterations the error is below 0.0001. That speed reflects a key property of the Black-Scholes price: it is strictly increasing in σ, so there is always exactly one solution (when the price is between its intrinsic value and the stock price). The search is guaranteed to converge.
Comments
Loading comments...