Move the sliders to see how each input shapes the option price. The call price is the theoretical cost of the right to buy the stock at the strike price on the expiry date.
<!-- Black-Scholes European call option pricer -->
<div class="bs-wrap">
<div class="sliders">
<label>Stock price S <span class="val" id="vS">100</span>
<input type="range" id="sS" min="50" max="200" step="1" value="100">
</label>
<label>Strike price K <span class="val" id="vK">100</span>
<input type="range" id="sK" min="50" max="200" step="1" value="100">
</label>
<label>Time to expiry T (yr) <span class="val" id="vT">1.0</span>
<input type="range" id="sT" min="0.1" max="3.0" step="0.1" value="1.0">
</label>
<label>Volatility <span class="val" id="vV">20</span>%
<input type="range" id="sV" min="5" max="80" step="1" value="20">
</label>
<label>Risk-free rate <span class="val" id="vR">5</span>%
<input type="range" id="sR" min="0" max="15" step="1" value="5">
</label>
</div>
<div class="result-box">
<div class="result-label">European Call Price</div>
<div class="result-price" id="callPrice">—</div>
<div class="greeks" id="greeks"></div>
</div>
<div class="chart-wrap">
<div class="chart-title">Call price vs. stock price (red dot = current S)</div>
<canvas id="chart" width="320" height="140"></canvas>
<div class="chart-axis">
<span>0.5K</span><span>K</span><span>1.5K</span>
</div>
</div>
</div>
/* layout and slider styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.bs-wrap { display: flex; flex-direction: column; gap: .7rem; }
.sliders { display: flex; flex-direction: column; gap: .4rem; }
label { display: grid; grid-template-columns: 10rem 2.8rem 1fr; align-items: center; gap: .3rem;
font-size: .85rem; }
input[type=range] { width: 100%; accent-color: #1d3557; }
.val { font-weight: 700; color: #1d3557; text-align: right; }
/* result display box */
.result-box { background: #e8eef3; border-radius: 10px; padding: .6rem 1rem;
display: flex; flex-direction: column; align-items: center; }
.result-label { font-size: .8rem; color: #555; text-transform: uppercase; letter-spacing: .05em; }
.result-price { font-size: 2.2rem; font-weight: 700; color: #1d3557; line-height: 1.1; }
.greeks { font-size: .78rem; color: #444; margin-top: .3rem; display: flex; gap: .8rem;
flex-wrap: wrap; justify-content: center; }
.greeks span { white-space: nowrap; }
/* chart area styles */
.chart-wrap { background: #f5f7fa; border-radius: 10px; padding: .5rem; }
.chart-title { font-size: .78rem; color: #555; text-align: center; margin-bottom: .2rem; }
canvas { display: block; margin: 0 auto; border-radius: 6px; }
.chart-axis { display: flex; justify-content: space-between; font-size: .7rem;
color: #888; padding: 0 .2rem; margin-top: .1rem; }
/* Black-Scholes closed-form pricer with Greeks */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Abramowitz & Stegun approximation for the standard normal CDF, max error < 1.5e-7 */
function normCdf(x) {
const a1=0.254829592,a2=-0.284496736,a3=1.421413741;
const a4=-1.453152027,a5=1.061405429,p=0.3275911;
const sign = x < 0 ? -1 : 1;
const ax = Math.abs(x) / Math.sqrt(2);
const t = 1 / (1 + p * ax);
const erfc = ((((a5*t+a4)*t+a3)*t+a2)*t+a1)*t*Math.exp(-ax*ax);
return sign === 1 ? 1 - 0.5*erfc : 0.5*erfc;
}
/* Standard normal probability density function */
function normPdf(x) { return Math.exp(-0.5*x*x) / Math.sqrt(2*Math.PI); }
/* Black-Scholes call price and Greeks (delta, gamma, theta, vega) */
function blackScholes(S, K, T, sigma, r) {
if (T <= 0) return { call: Math.max(S - K, 0), delta: S > K ? 1 : 0, gamma: 0, theta: 0, vega: 0 };
const d1 = (Math.log(S/K) + (r + 0.5*sigma*sigma)*T) / (sigma*Math.sqrt(T));
const d2 = d1 - sigma*Math.sqrt(T);
const call = S*normCdf(d1) - K*Math.exp(-r*T)*normCdf(d2);
const delta = normCdf(d1);
const gamma = normPdf(d1) / (S*sigma*Math.sqrt(T));
const theta = (-S*normPdf(d1)*sigma/(2*Math.sqrt(T)) - r*K*Math.exp(-r*T)*normCdf(d2)) / 365;
const vega = S*normPdf(d1)*Math.sqrt(T) / 100;
return { call, delta, gamma, theta, vega };
}
function getInputs() {
return {
S: +document.getElementById('sS').value,
K: +document.getElementById('sK').value,
T: +document.getElementById('sT').value,
sigma: document.getElementById('sV').value / 100,
r: document.getElementById('sR').value / 100
};
}
/* Draw call price curve across a range of stock prices from 0.5K to 1.5K */
function drawChart(K, T, sigma, r) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const sMin = K * 0.5, sMax = K * 1.5;
const prices = [];
const steps = W;
for (let i = 0; i <= steps; i++) {
const S = sMin + (sMax - sMin) * (i / steps);
prices.push(blackScholes(S, K, T, sigma, r).call);
}
const maxP = Math.max(...prices, K * 0.01);
ctx.beginPath();
ctx.strokeStyle = '#1d3557';
ctx.lineWidth = 2;
for (let i = 0; i <= steps; i++) {
const x = i;
const y = H - (prices[i] / maxP) * (H - 10) - 5;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.stroke();
/* Mark current stock price as a red dot on the curve */
const inp = getInputs();
const cx = ((inp.S - sMin) / (sMax - sMin)) * W;
const cy = H - (blackScholes(inp.S, K, T, sigma, r).call / maxP) * (H - 10) - 5;
ctx.beginPath();
ctx.arc(cx, cy, 5, 0, 2*Math.PI);
ctx.fillStyle = '#e63946';
ctx.fill();
}
function update() {
const { S, K, T, sigma, r } = getInputs();
document.getElementById('vS').textContent = S;
document.getElementById('vK').textContent = K;
document.getElementById('vT').textContent = T.toFixed(1);
document.getElementById('vV').textContent = Math.round(sigma*100);
document.getElementById('vR').textContent = Math.round(r*100);
const bs = blackScholes(S, K, T, sigma, r);
document.getElementById('callPrice').textContent = '
Notice how volatility (σ) has the biggest visual effect: higher uncertainty makes the option more valuable because upside is unlimited while downside is capped at zero. The time to expiry (T) works similarly — more time means more chance the stock moves in your favor. In contrast, a higher risk-free rate (r) modestly increases call prices because it makes the alternative of holding cash less attractive. This is what Monte Carlo methods exploit: simulate thousands of price paths and average the payoffs.
+ bs.call.toFixed(2);
document.getElementById('greeks').innerHTML =
`<span>Δ ${bs.delta.toFixed(3)}</span>` +
`<span>Γ ${bs.gamma.toFixed(4)}</span>` +
`<span>Θ ${bs.theta.toFixed(3)}</span>` +
`<span>ν ${bs.vega.toFixed(3)}</span>`;
drawChart(K, T, sigma, r);
}
['sS','sK','sT','sV','sR'].forEach(id => {
document.getElementById(id).addEventListener('input', update);
});
update();
<!-- Black-Scholes European call option pricer -->
<div class="bs-wrap">
<div class="sliders">
<label>Stock price S <span class="val" id="vS">100</span>
<input type="range" id="sS" min="50" max="200" step="1" value="100">
</label>
<label>Strike price K <span class="val" id="vK">100</span>
<input type="range" id="sK" min="50" max="200" step="1" value="100">
</label>
<label>Time to expiry T (yr) <span class="val" id="vT">1.0</span>
<input type="range" id="sT" min="0.1" max="3.0" step="0.1" value="1.0">
</label>
<label>Volatility <span class="val" id="vV">20</span>%
<input type="range" id="sV" min="5" max="80" step="1" value="20">
</label>
<label>Risk-free rate <span class="val" id="vR">5</span>%
<input type="range" id="sR" min="0" max="15" step="1" value="5">
</label>
</div>
<div class="result-box">
<div class="result-label">European Call Price</div>
<div class="result-price" id="callPrice">—</div>
<div class="greeks" id="greeks"></div>
</div>
<div class="chart-wrap">
<div class="chart-title">Call price vs. stock price (red dot = current S)</div>
<canvas id="chart" width="320" height="140"></canvas>
<div class="chart-axis">
<span>0.5K</span><span>K</span><span>1.5K</span>
</div>
</div>
</div>
/* layout and slider styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.bs-wrap { display: flex; flex-direction: column; gap: .7rem; }
.sliders { display: flex; flex-direction: column; gap: .4rem; }
label { display: grid; grid-template-columns: 10rem 2.8rem 1fr; align-items: center; gap: .3rem;
font-size: .85rem; }
input[type=range] { width: 100%; accent-color: #1d3557; }
.val { font-weight: 700; color: #1d3557; text-align: right; }
/* result display box */
.result-box { background: #e8eef3; border-radius: 10px; padding: .6rem 1rem;
display: flex; flex-direction: column; align-items: center; }
.result-label { font-size: .8rem; color: #555; text-transform: uppercase; letter-spacing: .05em; }
.result-price { font-size: 2.2rem; font-weight: 700; color: #1d3557; line-height: 1.1; }
.greeks { font-size: .78rem; color: #444; margin-top: .3rem; display: flex; gap: .8rem;
flex-wrap: wrap; justify-content: center; }
.greeks span { white-space: nowrap; }
/* chart area styles */
.chart-wrap { background: #f5f7fa; border-radius: 10px; padding: .5rem; }
.chart-title { font-size: .78rem; color: #555; text-align: center; margin-bottom: .2rem; }
canvas { display: block; margin: 0 auto; border-radius: 6px; }
.chart-axis { display: flex; justify-content: space-between; font-size: .7rem;
color: #888; padding: 0 .2rem; margin-top: .1rem; }
/* Black-Scholes closed-form pricer with Greeks */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Abramowitz & Stegun approximation for the standard normal CDF, max error < 1.5e-7 */
function normCdf(x) {
const a1=0.254829592,a2=-0.284496736,a3=1.421413741;
const a4=-1.453152027,a5=1.061405429,p=0.3275911;
const sign = x < 0 ? -1 : 1;
const ax = Math.abs(x) / Math.sqrt(2);
const t = 1 / (1 + p * ax);
const erfc = ((((a5*t+a4)*t+a3)*t+a2)*t+a1)*t*Math.exp(-ax*ax);
return sign === 1 ? 1 - 0.5*erfc : 0.5*erfc;
}
/* Standard normal probability density function */
function normPdf(x) { return Math.exp(-0.5*x*x) / Math.sqrt(2*Math.PI); }
/* Black-Scholes call price and Greeks (delta, gamma, theta, vega) */
function blackScholes(S, K, T, sigma, r) {
if (T <= 0) return { call: Math.max(S - K, 0), delta: S > K ? 1 : 0, gamma: 0, theta: 0, vega: 0 };
const d1 = (Math.log(S/K) + (r + 0.5*sigma*sigma)*T) / (sigma*Math.sqrt(T));
const d2 = d1 - sigma*Math.sqrt(T);
const call = S*normCdf(d1) - K*Math.exp(-r*T)*normCdf(d2);
const delta = normCdf(d1);
const gamma = normPdf(d1) / (S*sigma*Math.sqrt(T));
const theta = (-S*normPdf(d1)*sigma/(2*Math.sqrt(T)) - r*K*Math.exp(-r*T)*normCdf(d2)) / 365;
const vega = S*normPdf(d1)*Math.sqrt(T) / 100;
return { call, delta, gamma, theta, vega };
}
function getInputs() {
return {
S: +document.getElementById('sS').value,
K: +document.getElementById('sK').value,
T: +document.getElementById('sT').value,
sigma: document.getElementById('sV').value / 100,
r: document.getElementById('sR').value / 100
};
}
/* Draw call price curve across a range of stock prices from 0.5K to 1.5K */
function drawChart(K, T, sigma, r) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const sMin = K * 0.5, sMax = K * 1.5;
const prices = [];
const steps = W;
for (let i = 0; i <= steps; i++) {
const S = sMin + (sMax - sMin) * (i / steps);
prices.push(blackScholes(S, K, T, sigma, r).call);
}
const maxP = Math.max(...prices, K * 0.01);
ctx.beginPath();
ctx.strokeStyle = '#1d3557';
ctx.lineWidth = 2;
for (let i = 0; i <= steps; i++) {
const x = i;
const y = H - (prices[i] / maxP) * (H - 10) - 5;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.stroke();
/* Mark current stock price as a red dot on the curve */
const inp = getInputs();
const cx = ((inp.S - sMin) / (sMax - sMin)) * W;
const cy = H - (blackScholes(inp.S, K, T, sigma, r).call / maxP) * (H - 10) - 5;
ctx.beginPath();
ctx.arc(cx, cy, 5, 0, 2*Math.PI);
ctx.fillStyle = '#e63946';
ctx.fill();
}
function update() {
const { S, K, T, sigma, r } = getInputs();
document.getElementById('vS').textContent = S;
document.getElementById('vK').textContent = K;
document.getElementById('vT').textContent = T.toFixed(1);
document.getElementById('vV').textContent = Math.round(sigma*100);
document.getElementById('vR').textContent = Math.round(r*100);
const bs = blackScholes(S, K, T, sigma, r);
document.getElementById('callPrice').textContent = '
Notice how volatility (σ) has the biggest visual effect: higher uncertainty makes the option more valuable because upside is unlimited while downside is capped at zero. The time to expiry (T) works similarly — more time means more chance the stock moves in your favor. In contrast, a higher risk-free rate (r) modestly increases call prices because it makes the alternative of holding cash less attractive. This is what Monte Carlo methods exploit: simulate thousands of price paths and average the payoffs.
+ bs.call.toFixed(2);
document.getElementById('greeks').innerHTML =
`<span>Δ ${bs.delta.toFixed(3)}</span>` +
`<span>Γ ${bs.gamma.toFixed(4)}</span>` +
`<span>Θ ${bs.theta.toFixed(3)}</span>` +
`<span>ν ${bs.vega.toFixed(3)}</span>`;
drawChart(K, T, sigma, r);
}
['sS','sK','sT','sV','sR'].forEach(id => {
document.getElementById(id).addEventListener('input', update);
});
update();