Trading strategies with options
This interactive tool demonstrates various option trading strategies using European-style options. All strategies are evaluated at maturity using the Black-Scholes model for option pricing.
Common Parameters
Adjust these parameters to see how they affect all strategies:
erf = x => {
const sign = Math.sign(x);
const ax = Math.abs(x);
const t = 1 / (1 + 0.3275911 * ax);
const y = 1 - (((((1.061405429*t - 1.453152027)*t) + 1.421413741)*t - 0.284496736)*t + 0.254829592)*t*Math.exp(-ax*ax);
return sign * y;
}
// Standard normal CDF
N = z => 0.5 * (1 + erf(z / Math.SQRT2));
// Black-Scholes pricing function
blackScholes = (S, K, T, r, sigma, q) => {
// S: Current stock price
// K: Strike price
// T: Time to expiration (in years)
// r: Risk-free rate (annualized)
// sigma: Volatility (annualized)
// q: Dividend yield (annualized)
if (T <= 0) return {call: Math.max(0, S - K), put: Math.max(0, K - S)};
const d1 = (Math.log(S / K) + (r - q + 0.5 * sigma * sigma) * T) / (sigma * Math.sqrt(T));
const d2 = d1 - sigma * Math.sqrt(T);
const call = S * Math.exp(-q * T) * N(d1) - K * Math.exp(-r * T) * N(d2);
const put = K * Math.exp(-r * T) * N(-d2) - S * Math.exp(-q * T) * N(-d1);
return {call, put, d1, d2};
}html`<style>
.strategy-section {
margin-top: 2rem;
padding-top: 0;
}
.plot-title-small {
font-size: 12px;
font-weight: 600;
}
.info-box {
background: #f5f5f5;
border-left: 4px solid #2196F3;
padding: 1rem;
margin: 1rem 0;
}
.info-box h4 {
margin-top: 0;
color: #1565C0;
}
</style>`viewof S0_common = Inputs.range([50, 200], {step: 1, label: "Stock Price (S₀)", value: 100})
viewof T_common = Inputs.range([0.1, 2], {step: 0.1, label: "Time to Expiration (T, years)", value: 1})
viewof sigma_common = Inputs.range([0.05, 0.80], {step: 0.05, label: "Volatility (σ)", value: 0.20})
viewof r_common = Inputs.range([0, 0.15], {step: 0.01, label: "Risk-Free Rate (r)", value: 0.04})
viewof q_common = Inputs.range([0, 0.10], {step: 0.01, label: "Dividend Yield (q)", value: 0.00})Principal-Protected Notes (PPNs)
Principal-Protected Notes are structured products that combine a zero-coupon bond with a European option. The bond guarantees the return of the initial principal at maturity, while the option provides potential upside.
Investor Expectation
A PPN is for risk-averse investors who want capital protection while speculating on market direction.- A Call-based PPN is for investors who are bullish and expect the market to rise. It provides participation in market upside.
- A Put-based PPN is for investors who are bearish and expect the market to fall. It provides participation in market downside.
The principal protection is subject to the credit risk of the issuing institution.
viewof ppn_type = Inputs.radio(["Call-based", "Put-based"], {label: "PPN Type", value: "Call-based"})
viewof K_ppn = Inputs.range([50, 200], {step: 5, label: "Strike Price (K)", value: 100})
viewof ST_ppn = Inputs.range([0, 200], {step: 1, label: "S_T (Asset Price at Maturity)", value: 100})
viewof initial_investment = Inputs.range([100, 1000], {step: 100, label: "Initial Investment", value: 1000})bond_value = initial_investment * Math.exp(-r_common * T_common)
available_for_options = initial_investment - bond_value
// Get option price
ppn_option_price = ppn_type === "Call-based"
? blackScholes(S0_common, K_ppn, T_common, r_common, sigma_common, q_common).call
: blackScholes(S0_common, K_ppn, T_common, r_common, sigma_common, q_common).put
// Number of options we can buy
num_options = available_for_options / ppn_option_price
// Generate data for plotting
x_max_ppn = Math.max(200, K_ppn + 50);
data_ppn = d3.range(0, x_max_ppn + 1, 2).map(st => {
const option_payoff = ppn_type === "Call-based"
? num_options * Math.max(0, st - K_ppn)
: num_options * Math.max(0, K_ppn - st);
const total_payoff = initial_investment + option_payoff;
const profit = total_payoff - initial_investment;
return {st, payoff: total_payoff, profit};
});
// Current values
current_ppn_option_payoff = ppn_type === "Call-based"
? num_options * Math.max(0, ST_ppn - K_ppn)
: num_options * Math.max(0, K_ppn - ST_ppn);
current_ppn_payoff = initial_investment + current_ppn_option_payoff;
current_ppn_profit = current_ppn_payoff - initial_investment;Plot.plot({
title: plotTitle("PPN Total Value at Maturity"),
height: 300,
x: {label: "S_T", domain: [0, x_max_ppn]},
y: {label: "Value", domain: [0, d3.max(data_ppn, d => d.payoff) + 20]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([initial_investment], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K_ppn], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.line(data_ppn, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.dot([[ST_ppn, current_ppn_payoff]], {
fill: "red",
r: 6
}),
Plot.tip(data_ppn, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nValue: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_ppn, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})Plot.plot({
title: plotTitle("PPN Profit/Loss at Maturity"),
height: 300,
x: {label: "S_T", domain: [0, x_max_ppn]},
y: {label: "Profit/Loss", domain: [d3.min(data_ppn, d => d.profit) - 20, d3.max(data_ppn, d => d.profit) + 20]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K_ppn], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.line(data_ppn, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_ppn, current_ppn_profit]], {
fill: "red",
r: 6
}),
Plot.tip(data_ppn, Plot.pointerX({x: "st", y: "profit", title: (d) => `S_T: ${d.st}\nValue: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_ppn, {x: "st", y: "profit", stroke: "#444", opacity: 0.6})
]
})html`<div class="info-box">
<h4>PPN Structure</h4>
<p><strong>Initial Investment:</strong> $${initial_investment.toFixed(2)}</p>
<p><strong>Zero-Coupon Bond:</strong> $${bond_value.toFixed(2)} (grows to $${initial_investment.toFixed(2)} at maturity)</p>
<p><strong>Available for Options:</strong> $${available_for_options.toFixed(2)}</p>
<p><strong>${ppn_type} Option Price:</strong> $${ppn_option_price.toFixed(4)}</p>
<p><strong>Number of Options:</strong> ${num_options.toFixed(4)}</p>
<p><strong>Maximum Loss:</strong> $0 (principal protected)</p>
<p><strong>Maximum Profit:</strong> Unlimited (for call-based) / Limited to $${(num_options * K_ppn).toFixed(2)} (when S_T = 0 for put-based)</p>
</div>`Covered Call
A covered call involves holding a long position in the underlying stock and selling (shorting) a call option.
Investor Expectation
Neutral to slightly bullish. The investor expects the stock price to remain relatively stable or increase modestly. This strategy generates income from the option premium but caps the upside potential. By put-call parity, a covered call (long stock + short call) is equivalent to a short put position (short put + cash equal to strike price discounted at risk-free rate).
call_premium_cc = blackScholes(S0_common, K_cc, T_common, r_common, sigma_common, q_common).call
// Generate data
x_max_cc = Math.max(200, K_cc + 50);
data_cc = d3.range(0, x_max_cc + 1, 2).map(st => {
const stock_payoff = st - S0_common;
const call_payoff = -Math.max(0, st - K_cc);
const total_payoff = stock_payoff + call_payoff;
const profit = total_payoff + call_premium_cc;
return {st, payoff: total_payoff, profit};
});
current_cc_stock_payoff = ST_cc - S0_common;
current_cc_call_payoff = -Math.max(0, ST_cc - K_cc);
current_cc_payoff = current_cc_stock_payoff + current_cc_call_payoff;
current_cc_profit = current_cc_payoff + call_premium_cc;
// Breakeven
breakeven_cc = S0_common - call_premium_cc;
max_profit_cc = K_cc - S0_common + call_premium_cc;Plot.plot({
title: plotTitle("Covered Call at Maturity"),
height: 400,
x: {label: "S_T", domain: [0, x_max_cc]},
y: {label: "Value", domain: [d3.min(data_cc, d => Math.min(d.payoff, d.profit)) - 10, d3.max(data_cc, d => Math.max(d.payoff, d.profit)) + 10]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K_cc], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_cc], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_cc, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_cc, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_cc, current_cc_profit]], {fill: "red", r: 6}),
Plot.tip(data_cc, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_cc, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>Strike K <span class="badge">${K_cc}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_cc.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>Covered Call Analysis</h4>
<p><strong>Stock Position:</strong> Long at $${S0_common.toFixed(2)}</p>
<p><strong>Call Premium Received:</strong> $${call_premium_cc.toFixed(4)}</p>
<p><strong>Breakeven:</strong> S_T = $${breakeven_cc.toFixed(2)}</p>
<p><strong>Maximum Profit:</strong> $${max_profit_cc.toFixed(2)} (when S_T ≥ ${K_cc})</p>
<p><strong>Maximum Loss:</strong> $${(S0_common - call_premium_cc).toFixed(2)} (when S_T = 0)</p>
</div>`Protective Put
A protective put involves holding a long position in the underlying stock and buying a put option.
Investor Expectation
Bullish with downside protection. The investor expects the stock price to rise but wants insurance against a significant decline. By put-call parity, a protective put (long stock + long put) is equivalent to a long call position (long call + cash equal to strike price discounted at risk-free rate).
put_premium_pp = blackScholes(S0_common, K_pp, T_common, r_common, sigma_common, q_common).put
// Generate data
x_max_pp = Math.max(200, K_pp + 50);
data_pp = d3.range(0, x_max_pp + 1, 2).map(st => {
const stock_payoff = st - S0_common;
const put_payoff = Math.max(0, K_pp - st);
const total_payoff = stock_payoff + put_payoff;
const profit = total_payoff - put_premium_pp;
return {st, payoff: total_payoff, profit};
});
current_pp_stock_payoff = ST_pp - S0_common;
current_pp_put_payoff = Math.max(0, K_pp - ST_pp);
current_pp_payoff = current_pp_stock_payoff + current_pp_put_payoff;
current_pp_profit = current_pp_payoff - put_premium_pp;
// Breakeven
breakeven_pp = S0_common + put_premium_pp;
max_loss_pp = S0_common - K_pp + put_premium_pp;Plot.plot({
title: plotTitle("Protective Put at Maturity"),
height: 400,
x: {label: "S_T", domain: [0, x_max_pp]},
y: {label: "Value", domain: [d3.min(data_pp, d => Math.min(d.payoff, d.profit)) - 10, d3.max(data_pp, d => Math.max(d.payoff, d.profit)) + 10]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K_pp], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_pp], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_pp, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_pp, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_pp, current_pp_profit]], {fill: "red", r: 6}),
Plot.tip(data_pp, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_pp, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>Strike K <span class="badge">${K_pp}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_pp.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>Protective Put Analysis</h4>
<p><strong>Stock Position:</strong> Long at $${S0_common.toFixed(2)}</p>
<p><strong>Put Premium Paid:</strong> $${put_premium_pp.toFixed(4)}</p>
<p><strong>Breakeven:</strong> S_T = $${breakeven_pp.toFixed(2)}</p>
<p><strong>Maximum Loss:</strong> $${max_loss_pp.toFixed(2)} (when S_T ≤ ${K_pp})</p>
<p><strong>Maximum Profit:</strong> Unlimited (when S_T → ∞)</p>
</div>`Bull Spread
A bull spread can be created using calls or puts. With calls: buy a call at lower strike K₁, sell a call at higher strike K₂.
Investor Expectation
Moderately bullish. The investor expects the stock price to rise but wants to reduce the cost by capping the maximum profit.
viewof option_type_bull = Inputs.radio(["Calls", "Puts"], {label: "Option Type", value: "Calls"})
viewof K1_bull = Inputs.range([50, 180], {step: 5, label: "K₁ (Lower Strike)", value: 90})
viewof spread_bull = Inputs.range([5, 50], {step: 5, label: "Spread (K₂ - K₁)", value: 20})
viewof ST_bull = Inputs.range([0, 200], {step: 1, label: "S_T", value: 100})K2_bull = K1_bull + spread_bull
// Calculate premiums
prices_bull_k1 = blackScholes(S0_common, K1_bull, T_common, r_common, sigma_common, q_common)
prices_bull_k2 = blackScholes(S0_common, K2_bull, T_common, r_common, sigma_common, q_common)
net_premium_bull = option_type_bull === "Calls"
? prices_bull_k1.call - prices_bull_k2.call // Debit: Long K1 Call, Short K2 Call
: prices_bull_k2.put - prices_bull_k1.put; // Credit: Short K2 Put, Long K1 Put
// Generate data
x_max_bull = Math.max(200, K2_bull + 20);
data_bull = d3.range(0, x_max_bull + 1, 2).map(st => {
let payoff;
if (option_type_bull === "Calls") {
// Bull Call Spread: Long K1 Call, Short K2 Call
payoff = Math.max(0, st - K1_bull) - Math.max(0, st - K2_bull);
} else {
// Bull Put Spread: Short K2 Put, Long K1 Put
payoff = -Math.max(0, K2_bull - st) + Math.max(0, K1_bull - st);
}
// Profit calculation depends on whether it's a debit or credit spread
const profit = option_type_bull === "Calls"
? payoff - net_premium_bull // Debit spread
: payoff + net_premium_bull; // Credit spread
return {st, payoff, profit};
});
// Current values
current_bull_payoff = option_type_bull === "Calls"
? Math.max(0, ST_bull - K1_bull) - Math.max(0, ST_bull - K2_bull)
: -Math.max(0, K2_bull - ST_bull) + Math.max(0, K1_bull - ST_bull);
current_bull_profit = option_type_bull === "Calls"
? current_bull_payoff - net_premium_bull
: current_bull_payoff + net_premium_bull;
// Key metrics
max_profit_bull = option_type_bull === "Calls"
? (K2_bull - K1_bull) - net_premium_bull // Max payoff (K2-K1) - debit
: net_premium_bull; // Max profit is the credit received
max_loss_bull = option_type_bull === "Calls"
? net_premium_bull // Max loss is the debit paid
: (K2_bull - K1_bull) - net_premium_bull; // Max loss is (K2-K1) - credit (this will be negative, so take absolute value for display)
// Breakeven
breakeven_bull = option_type_bull === "Calls"
? K1_bull + net_premium_bull
: K2_bull - net_premium_bull;Plot.plot({
title: plotTitle(`Bull Spread (${option_type_bull}) at Maturity`),
height: 400,
x: {label: "S_T", domain: [0, x_max_bull]},
y: {label: "Value", domain: [d3.min(data_bull, d => Math.min(d.payoff, d.profit)) - 10, d3.max(data_bull, d => Math.max(d.payoff, d.profit)) + 10]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K1_bull], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K2_bull], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_bull], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_bull, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_bull, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_bull, current_bull_profit]], {fill: "red", r: 6}),
Plot.tip(data_bull, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_bull, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₁ <span class="badge">${K1_bull}</span></span>
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₂ <span class="badge">${K2_bull}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_bull.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>Bull Spread Analysis (${option_type_bull})</h4>
<p><strong>Position:</strong> ${option_type_bull === "Calls" ? `Long call at K₁=${K1_bull}, Short call at K₂=${K2_bull}` : `Long put at K₁=${K1_bull}, Short put at K₂=${K2_bull}`}</p>
<p><strong>Net Premium:</strong> $${Math.abs(net_premium_bull).toFixed(4)} ${net_premium_bull < 0 ? "(paid)" : "(received)"}</p>
<p><strong>Breakeven:</strong> S_T = $${breakeven_bull.toFixed(2)}</p>
<p><strong>Maximum Profit:</strong> $${max_profit_bull.toFixed(2)} (when S_T ≥ ${K2_bull})</p>
<p><strong>Maximum Loss:</strong> $${Math.abs(max_loss_bull).toFixed(2)} (which is K₂ - K₁ minus the credit received, when S_T ≤ ${K1_bull})</p>
</div>`Bear Spread
A bear spread can be created using puts or calls. With puts: buy a put at higher strike K₂, sell a put at lower strike K₁.
Investor Expectation
Moderately bearish. The investor expects the stock price to decline but wants to reduce the cost by capping the maximum profit.
viewof option_type_bear = Inputs.radio(["Puts", "Calls"], {label: "Option Type", value: "Puts"})
viewof K1_bear = Inputs.range([50, 180], {step: 5, label: "K₁ (Lower Strike)", value: 90})
viewof spread_bear = Inputs.range([5, 50], {step: 5, label: "Spread (K₂ - K₁)", value: 20})
viewof ST_bear = Inputs.range([0, 200], {step: 1, label: "S_T", value: 100})K2_bear = K1_bear + spread_bear
// Calculate premiums
prices_bear_k1 = blackScholes(S0_common, K1_bear, T_common, r_common, sigma_common, q_common)
prices_bear_k2 = blackScholes(S0_common, K2_bear, T_common, r_common, sigma_common, q_common)
net_premium_bear = option_type_bear === "Puts"
? prices_bear_k2.put - prices_bear_k1.put // Debit: Long K2 Put, Short K1 Put
: prices_bear_k1.call - prices_bear_k2.call; // Credit: Short K1 Call, Long K2 Call
// Generate data
x_max_bear = Math.max(200, K2_bear + 20);
data_bear = d3.range(0, x_max_bear + 1, 2).map(st => {
let payoff;
if (option_type_bear === "Puts") {
// Bear Put Spread: Long K2 Put, Short K1 Put
payoff = Math.max(0, K2_bear - st) - Math.max(0, K1_bear - st);
} else {
// Bear Call Spread: Short K1 Call, Long K2 Call
payoff = -Math.max(0, st - K1_bear) + Math.max(0, st - K2_bear);
}
const profit = option_type_bear === "Puts"
? payoff - net_premium_bear // Debit spread
: payoff + net_premium_bear; // Credit spread
return {st, payoff, profit};
});
// Current values
current_bear_payoff = option_type_bear === "Puts"
? Math.max(0, K2_bear - ST_bear) - Math.max(0, K1_bear - ST_bear)
: -Math.max(0, ST_bear - K1_bear) + Math.max(0, ST_bear - K2_bear);
current_bear_profit = option_type_bear === "Puts"
? current_bear_payoff - net_premium_bear
: current_bear_payoff + net_premium_bear;
// Key metrics
max_profit_bear = option_type_bear === "Puts"
? (K2_bear - K1_bear) - net_premium_bear // Max payoff (K2-K1) - debit
: net_premium_bear; // Max profit is the credit received
max_loss_bear = option_type_bear === "Puts"
? net_premium_bear // Max loss is the debit paid
: (K2_bear - K1_bear) - net_premium_bear; // Max loss is (K2-K1) - credit (this will be negative, so take absolute value for display)
breakeven_bear = option_type_bear === "Puts"
? K2_bear - net_premium_bear
: K1_bear + net_premium_bear;Plot.plot({
title: plotTitle(`Bear Spread (${option_type_bear}) at Maturity`),
height: 400,
x: {label: "S_T", domain: [0, x_max_bear]},
y: {label: "Value", domain: [d3.min(data_bear, d => Math.min(d.payoff, d.profit)) - 10, d3.max(data_bear, d => Math.max(d.payoff, d.profit)) + 10]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K1_bear], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K2_bear], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_bear], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_bear, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_bear, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_bear, current_bear_profit]], {fill: "red", r: 6}),
Plot.tip(data_bear, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_bear, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₁ <span class="badge">${K1_bear}</span></span>
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₂ <span class="badge">${K2_bear}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_bear.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>Bear Spread Analysis (${option_type_bear})</h4>
<p><strong>Position:</strong> ${option_type_bear === "Puts" ? `Long put at K₂=${K2_bear}, Short put at K₁=${K1_bear}` : `Long call at K₂=${K2_bear}, Short call at K₁=${K1_bear}`}</p>
<p><strong>Net Premium:</strong> $${(-net_premium_bear).toFixed(4)} (paid)</p>
<p><strong>Breakeven:</strong> S_T = $${breakeven_bear.toFixed(2)}</p>
<p><strong>Maximum Profit:</strong> $${max_profit_bear.toFixed(2)} (when S_T ≤ ${K1_bear})</p>
<p><strong>Maximum Loss:</strong> $${Math.abs(max_loss_bear).toFixed(2)} (which is K₂ - K₁ minus the credit received, when S_T ≥ ${K2_bear})</p>
</div>`Butterfly Spread
A butterfly spread involves three strike prices: buy one option at K₁, sell two options at K₂, and buy one option at K₃.
Investor Expectation
Neutral or low volatility. The investor expects the stock price to remain near the middle strike price K₂. Profits from stability, loses from large price movements.
viewof option_type_butterfly = Inputs.radio(["Calls", "Puts"], {label: "Option Type", value: "Calls"})
viewof K2_butterfly = Inputs.range([70, 130], {step: 5, label: "K₂ (Middle Strike)", value: 100})
viewof wing_butterfly = Inputs.range([5, 40], {step: 5, label: "Wing Size (K₂ - K₁)", value: 20})
viewof ST_butterfly = Inputs.range([0, 200], {step: 1, label: "S_T", value: 100})K1_butterfly = K2_butterfly - wing_butterfly
K3_butterfly = K2_butterfly + wing_butterfly
// Calculate premiums
prices_bf_k1 = blackScholes(S0_common, K1_butterfly, T_common, r_common, sigma_common, q_common)
prices_bf_k2 = blackScholes(S0_common, K2_butterfly, T_common, r_common, sigma_common, q_common)
prices_bf_k3 = blackScholes(S0_common, K3_butterfly, T_common, r_common, sigma_common, q_common)
net_premium_butterfly = option_type_butterfly === "Calls"
? prices_bf_k1.call - 2 * prices_bf_k2.call + prices_bf_k3.call
: prices_bf_k1.put - 2 * prices_bf_k2.put + prices_bf_k3.put;
// Generate data
data_butterfly = d3.range(0, 201, 2).map(st => {
let payoff;
if (option_type_butterfly === "Calls") {
const long_call_k1 = Math.max(0, st - K1_butterfly);
const short_calls_k2 = -2 * Math.max(0, st - K2_butterfly);
const long_call_k3 = Math.max(0, st - K3_butterfly);
payoff = long_call_k1 + short_calls_k2 + long_call_k3;
} else {
const long_put_k1 = Math.max(0, K1_butterfly - st);
const short_puts_k2 = -2 * Math.max(0, K2_butterfly - st);
const long_put_k3 = Math.max(0, K3_butterfly - st);
payoff = long_put_k1 + short_puts_k2 + long_put_k3;
}
const profit = payoff - net_premium_butterfly; // Long butterfly is a debit spread
return {st, payoff, profit};
});
// Current values
calculateButterflyPayoff = (st, k1, k2, k3, type) => {
if (type === "Calls") {
return Math.max(0, st - k1) - 2 * Math.max(0, st - k2) + Math.max(0, st - k3);
} else {
return Math.max(0, k1 - st) - 2 * Math.max(0, k2 - st) + Math.max(0, k3 - st);
}
}
current_butterfly_payoff = calculateButterflyPayoff(ST_butterfly, K1_butterfly, K2_butterfly, K3_butterfly, option_type_butterfly);
current_butterfly_profit = current_butterfly_payoff - net_premium_butterfly;
// Key metrics
max_profit_butterfly = (K2_butterfly - K1_butterfly) - net_premium_butterfly; // Max payoff (K2-K1) - debit
max_loss_butterfly = net_premium_butterfly; // Max loss is the debit paid
breakeven_lower_butterfly = K1_butterfly + net_premium_butterfly;
breakeven_upper_butterfly = K3_butterfly - net_premium_butterfly;Plot.plot({
title: plotTitle(`Butterfly Spread (${option_type_butterfly}) at Maturity`),
height: 400,
x: {label: "S_T", domain: [Math.max(0, K1_butterfly - 30), K3_butterfly + 30]},
y: {label: "Value", domain: [d3.min(data_butterfly, d => Math.min(d.payoff, d.profit)) - 10, d3.max(data_butterfly, d => Math.max(d.payoff, d.profit)) + 10]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K1_butterfly], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K2_butterfly], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K3_butterfly], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_lower_butterfly], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_upper_butterfly], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_butterfly, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_butterfly, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_butterfly, current_butterfly_profit]], {fill: "red", r: 6}),
Plot.tip(data_butterfly, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_butterfly, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₁ <span class="badge">${K1_butterfly}</span></span>
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₂ <span class="badge">${K2_butterfly}</span></span>
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₃ <span class="badge">${K3_butterfly}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_lower_butterfly.toFixed(2)}, ${breakeven_upper_butterfly.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>Butterfly Spread Analysis (${option_type_butterfly})</h4>
<p><strong>Position:</strong> Long ${option_type_butterfly === "Calls" ? "call" : "put"} at K₁=${K1_butterfly}, Short 2 ${option_type_butterfly === "Calls" ? "calls" : "puts"} at K₂=${K2_butterfly}, Long ${option_type_butterfly === "Calls" ? "call" : "put"} at K₃=${K3_butterfly}</p>
<p><strong>Net Premium:</strong> $${(-net_premium_butterfly).toFixed(4)} (paid)</p>
<p><strong>Breakeven Points:</strong> S_T = $${breakeven_lower_butterfly.toFixed(2)} and $${breakeven_upper_butterfly.toFixed(2)}</p>
<p><strong>Maximum Profit:</strong> $${max_profit_butterfly.toFixed(2)} (when S_T = ${K2_butterfly})</p>
<p><strong>Maximum Loss:</strong> $${max_loss_butterfly.toFixed(2)} (when S_T ≤ ${K1_butterfly} or S_T ≥ ${K3_butterfly})</p>
</div>`Box Spread
A box spread combines a bull call spread and a bear put spread with the same strikes. It creates a risk-free position whose payoff should equal the present value of K₂ - K₁.
Investor Expectation
Risk-free arbitrage. No directional view needed. The value should equal the present value of the payoff (K₂ - K₁) discounted at the risk-free rate. Deviations from this value represent arbitrage opportunities.
K2_box = K1_box + spread_box
// Calculate premiums for all four options
prices_box_k1 = blackScholes(S0_common, K1_box, T_common, r_common, sigma_common, q_common)
prices_box_k2 = blackScholes(S0_common, K2_box, T_common, r_common, sigma_common, q_common)
// Bull call spread: long call K1, short call K2
bull_call_cost = prices_box_k1.call - prices_box_k2.call
// Bear put spread: long put K2, short put K1
bear_put_cost = prices_box_k2.put - prices_box_k1.put
// Total cost of box spread
box_spread_cost = bull_call_cost + bear_put_cost
// Theoretical value
theoretical_box_value = (K2_box - K1_box) * Math.exp(-r_common * T_common)
// Arbitrage opportunity
arbitrage_profit = theoretical_box_value - box_spread_cost
// Generate data
data_box = d3.range(0, Math.max(200, K2_box + 20) + 1, 2).map(st => {
// Bull call spread payoff
const bull_call_payoff = Math.max(0, st - K1_box) - Math.max(0, st - K2_box);
// Bear put spread payoff
const bear_put_payoff = Math.max(0, K2_box - st) - Math.max(0, K1_box - st);
// Total payoff
const payoff = bull_call_payoff + bear_put_payoff;
const profit = payoff - box_spread_cost;
return {st, payoff, profit};
});
// Current values
current_box_payoff = K2_box - K1_box; // Always constant
current_box_profit = current_box_payoff - box_spread_cost;Plot.plot({
title: plotTitle("Box Spread at Maturity"),
height: 400,
y: {label: "Value", domain: [Math.min(0, Math.min(K2_box - K1_box, current_box_profit)) - 10, Math.max(K2_box - K1_box, current_box_profit) + 10]},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K1_box], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K2_box], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleY([K2_box - K1_box], {stroke: "#2196F3", strokeWidth: 2.5}),
Plot.ruleY([current_box_profit], {stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_box, current_box_profit]], {fill: "red", r: 6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₁ <span class="badge">${K1_box}</span></span>
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₂ <span class="badge">${K2_box}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff <span class="badge">${(K2_box - K1_box).toFixed(2)}</span></span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss <span class="badge">${current_box_profit.toFixed(2)}</span></span>
</div>`html`<div class="info-box">
<h4>Box Spread Analysis</h4>
<p><strong>Position:</strong> Bull call spread (long call K₁=${K1_box}, short call K₂=${K2_box}) + Bear put spread (long put K₂=${K2_box}, short put K₁=${K1_box})</p>
<p><strong>Total Cost:</strong> $${box_spread_cost.toFixed(4)}</p>
<p><strong>Payoff at Maturity:</strong> $${(K2_box - K1_box).toFixed(2)} (constant for all S_T)</p>
<p><strong>Theoretical Value (PV):</strong> $${theoretical_box_value.toFixed(4)}</p>
<p><strong>Arbitrage Profit:</strong> $${arbitrage_profit.toFixed(4)} ${arbitrage_profit > 0.01 ? "(Buy box)" : arbitrage_profit < -0.01 ? "(Sell box)" : "(Fair value)"}</p>
<p><strong>Profit at Maturity:</strong> $${current_box_profit.toFixed(2)}</p>
</div>`Calendar Spread
A calendar (or time) spread involves buying and selling options with the same strike but different expiration dates. Typically, sell near-term and buy longer-term.
Investor Expectation
Neutral with expectations of increasing volatility. Profits from time decay differences between short-term and long-term options, especially when the stock price remains near the strike.
viewof option_type_calendar = Inputs.radio(["Calls", "Puts"], {label: "Option Type", value: "Calls"})
viewof K_calendar = Inputs.range([50, 200], {step: 5, label: "Strike Price (K)", value: 100})
viewof T2_calendar = Inputs.range([T_common + 0.1, T_common + 2], {step: 0.1, label: "T₂ (Long Option Expiration, years)", value: T_common + 0.5})
viewof ST_calendar = Inputs.range([0, 200], {step: 1, label: "S_T (at T₁)", value: 100})prices_calendar_t1 = blackScholes(S0_common, K_calendar, T_common, r_common, sigma_common, q_common)
prices_calendar_t2 = blackScholes(S0_common, K_calendar, T2_calendar, r_common, sigma_common, q_common)
net_premium_calendar = option_type_calendar === "Calls"
? prices_calendar_t1.call - prices_calendar_t2.call // Receive from selling T1, pay for buying T2
: prices_calendar_t1.put - prices_calendar_t2.put
// At maturity T1, the short option expires
// The long option still has (T2 - T1) time remaining
time_remaining_t2 = T2_calendar - T_common
// Generate data for payoff at T1
x_max_calendar = Math.max(200, K_calendar + 50);
data_calendar = d3.range(0, x_max_calendar + 1, 2).map(st => {
// Short option payoff at T1
const short_payoff = option_type_calendar === "Calls"
? -Math.max(0, st - K_calendar)
: -Math.max(0, K_calendar - st);
// Long option value at T1 (still has time remaining)
const long_option_value_at_t1 = option_type_calendar === "Calls"
? blackScholes(st, K_calendar, time_remaining_t2, r_common, sigma_common, q_common).call
: blackScholes(st, K_calendar, time_remaining_t2, r_common, sigma_common, q_common).put;
const payoff = short_payoff + long_option_value_at_t1;
const profit = payoff - net_premium_calendar; // Calendar spread is typically a debit spread
return {st, payoff, profit};
});
// Current values at ST_calendar
current_calendar_short_payoff = option_type_calendar === "Calls"
? -Math.max(0, ST_calendar - K_calendar)
: -Math.max(0, K_calendar - ST_calendar);
current_calendar_long_value = option_type_calendar === "Calls"
? blackScholes(ST_calendar, K_calendar, time_remaining_t2, r_common, sigma_common, q_common).call
: blackScholes(ST_calendar, K_calendar, time_remaining_t2, r_common, sigma_common, q_common).put;
current_calendar_payoff = current_calendar_short_payoff + current_calendar_long_value;
current_calendar_profit = current_calendar_payoff - net_premium_calendar;Plot.plot({
title: plotTitle(`Calendar Spread (${option_type_calendar}) at T₁`),
height: 400,
x: {label: "S_T (at T₁)", domain: [0, x_max_calendar]},
y: {label: "Value"},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K_calendar], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.line(data_calendar, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_calendar, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_calendar, current_calendar_profit]], {fill: "red", r: 6}),
Plot.tip(data_calendar, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPosition Value: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_calendar, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>Strike K <span class="badge">${K_calendar}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Position Value</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>Calendar Spread Analysis (${option_type_calendar})</h4>
<p><strong>Position:</strong> Short ${option_type_calendar === "Calls" ? "call" : "put"} expiring at T₁=${T_common.toFixed(1)}, Long ${option_type_calendar === "Calls" ? "call" : "put"} expiring at T₂=${T2_calendar.toFixed(1)}</p>
<p><strong>Net Premium at t=0:</strong> $${(-net_premium_calendar).toFixed(4)} ${net_premium_calendar < 0 ? "(paid)" : "(received)"}</p>
<p><strong>Strike Price:</strong> K = ${K_calendar}</p>
<p><strong>Time Remaining at T₁:</strong> ${time_remaining_t2.toFixed(2)} years for long option</p>
<p><strong>Current Profit at S_T=${ST_calendar}:</strong> $${current_calendar_profit.toFixed(4)}</p>
<p><em>Note: This shows position value at T₁ expiration. The long option still has time value remaining.</em></p>
</div>`Diagonal Spread
A diagonal spread combines different strikes and different expiration dates. Typically involves selling a near-term option at one strike and buying a longer-term option at a different strike.
Investor Expectation
Directional bias with time decay advantage. Combines elements of vertical spreads and calendar spreads. The specific view depends on which strikes are chosen.
viewof option_type_diagonal = Inputs.radio(["Calls", "Puts"], {label: "Option Type", value: "Calls"})
viewof K1_diagonal = Inputs.range([50, 180], {step: 5, label: "K₁ (Short Option Strike)", value: 100})
viewof K2_diagonal = Inputs.range([50, 180], {step: 5, label: "K₂ (Long Option Strike)", value: 110})
viewof T2_diagonal = Inputs.range([T_common + 0.1, T_common + 2], {step: 0.1, label: "T₂ (Long Option Expiration, years)", value: T_common + 0.5})
viewof ST_diagonal = Inputs.range([0, 200], {step: 1, label: "S_T (at T₁)", value: 100})prices_diagonal_short = blackScholes(S0_common, K1_diagonal, T_common, r_common, sigma_common, q_common)
prices_diagonal_long = blackScholes(S0_common, K2_diagonal, T2_diagonal, r_common, sigma_common, q_common)
net_premium_diagonal = option_type_diagonal === "Calls"
? prices_diagonal_short.call - prices_diagonal_long.call
: prices_diagonal_short.put - prices_diagonal_long.put
// At maturity T1
time_remaining_diagonal = T2_diagonal - T_common
// Generate data for payoff at T1
x_max_diagonal = Math.max(200, K1_diagonal + 50, K2_diagonal + 50);
data_diagonal = d3.range(0, x_max_diagonal + 1, 2).map(st => {
// Short option payoff at T1
const short_payoff = option_type_diagonal === "Calls"
? -Math.max(0, st - K1_diagonal)
: -Math.max(0, K1_diagonal - st);
// Long option value at T1
const long_option_value = option_type_diagonal === "Calls"
? blackScholes(st, K2_diagonal, time_remaining_diagonal, r_common, sigma_common, q_common).call
: blackScholes(st, K2_diagonal, time_remaining_diagonal, r_common, sigma_common, q_common).put;
const payoff = short_payoff + long_option_value;
const profit = payoff + net_premium_diagonal;
return {st, payoff, profit};
});
// Current values
current_diagonal_short_payoff = option_type_diagonal === "Calls"
? -Math.max(0, ST_diagonal - K1_diagonal)
: -Math.max(0, K1_diagonal - ST_diagonal);
current_diagonal_long_value = option_type_diagonal === "Calls"
? blackScholes(ST_diagonal, K2_diagonal, time_remaining_diagonal, r_common, sigma_common, q_common).call
: blackScholes(ST_diagonal, K2_diagonal, time_remaining_diagonal, r_common, sigma_common, q_common).put;
current_diagonal_payoff = current_diagonal_short_payoff + current_diagonal_long_value;
current_diagonal_profit = current_diagonal_payoff + net_premium_diagonal;Plot.plot({
title: plotTitle(`Diagonal Spread (${option_type_diagonal}) at T₁`),
height: 400,
x: {label: "S_T (at T₁)", domain: [0, x_max_diagonal]},
y: {label: "Value"},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K1_diagonal], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K2_diagonal], {stroke: "blue", strokeDasharray: "4,4"}),
Plot.line(data_diagonal, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_diagonal, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_diagonal, current_diagonal_profit]], {fill: "red", r: 6}),
Plot.tip(data_diagonal, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPosition Value: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_diagonal, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₁ (Short) <span class="badge">${K1_diagonal}</span></span>
<span><span class="swatch-line" style="border-top-color: blue; border-top-style: dashed;"></span>K₂ (Long) <span class="badge">${K2_diagonal}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Position Value</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>Diagonal Spread Analysis (${option_type_diagonal})</h4>
<p><strong>Position:</strong> Short ${option_type_diagonal === "Calls" ? "call" : "put"} at K₁=${K1_diagonal} expiring at T₁=${T_common.toFixed(1)}, Long ${option_type_diagonal === "Calls" ? "call" : "put"} at K₂=${K2_diagonal} expiring at T₂=${T2_diagonal.toFixed(1)}</p>
<p><strong>Net Premium at t=0:</strong> $${(-net_premium_diagonal).toFixed(4)} ${net_premium_diagonal < 0 ? "(paid)" : "(received)"}</p>
<p><strong>Direction:</strong> ${option_type_diagonal === "Calls" && K2_diagonal > K1_diagonal ? "Bullish" : option_type_diagonal === "Puts" && K2_diagonal < K1_diagonal ? "Bearish" : "Neutral/Complex"}</p>
<p><strong>Time Remaining at T₁:</strong> ${time_remaining_diagonal.toFixed(2)} years for long option</p>
<p><strong>Current Profit at S_T=${ST_diagonal}:</strong> $${current_diagonal_profit.toFixed(4)}</p>
<p><em>Note: This shows position value at T₁ expiration. The long option still has time value remaining.</em></p>
</div>`Straddle
A straddle involves buying (or selling) both a call and a put with the same strike price and expiration date.
Investor Expectation
Long Straddle: Expects high volatility and a large price movement in either direction.
Short Straddle: Expects low volatility with the price remaining near the strike.
prices_straddle = blackScholes(S0_common, K_straddle, T_common, r_common, sigma_common, q_common)
total_premium_straddle = prices_straddle.call + prices_straddle.put
// Generate data
x_max_straddle = Math.max(200, K_straddle + 50);
data_straddle = d3.range(0, x_max_straddle + 1, 2).map(st => {
const call_payoff = Math.max(0, st - K_straddle);
const put_payoff = Math.max(0, K_straddle - st);
const payoff = straddle_position === "Long"
? call_payoff + put_payoff
: -(call_payoff + put_payoff);
const profit = straddle_position === "Long"
? payoff - total_premium_straddle
: payoff + total_premium_straddle;
return {st, payoff, profit};
});
// Current values
current_straddle_call = Math.max(0, ST_straddle - K_straddle);
current_straddle_put = Math.max(0, K_straddle - ST_straddle);
current_straddle_payoff = straddle_position === "Long"
? current_straddle_call + current_straddle_put
: -(current_straddle_call + current_straddle_put);
current_straddle_profit = straddle_position === "Long"
? current_straddle_payoff - total_premium_straddle
: current_straddle_payoff + total_premium_straddle;
// Breakeven points
breakeven_lower_straddle = K_straddle - total_premium_straddle;
breakeven_upper_straddle = K_straddle + total_premium_straddle;Plot.plot({
title: plotTitle(`${straddle_position} Straddle at Maturity`),
height: 400,
x: {label: "S_T", domain: [0, x_max_straddle]},
y: {label: "Value"},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K_straddle], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_lower_straddle], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_upper_straddle], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_straddle, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_straddle, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_straddle, current_straddle_profit]], {fill: "red", r: 6}),
Plot.tip(data_straddle, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_straddle, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>Strike K <span class="badge">${K_straddle}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_lower_straddle.toFixed(2)}, ${breakeven_upper_straddle.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>${straddle_position} Straddle Analysis</h4>
<p><strong>Position:</strong> ${straddle_position} call and ${straddle_position.toLowerCase()} put at K=${K_straddle}</p>
<p><strong>Total Premium:</strong> $${total_premium_straddle.toFixed(4)} ${straddle_position === "Long" ? "(paid)" : "(received)"}</p>
<p><strong>Breakeven Points:</strong> S_T = $${breakeven_lower_straddle.toFixed(2)} and $${breakeven_upper_straddle.toFixed(2)}</p>
${straddle_position === "Long"
? `<p><strong>Maximum Loss:</strong> $${total_premium_straddle.toFixed(2)} (when S_T = ${K_straddle})</p>
<p><strong>Maximum Profit:</strong> Unlimited (as S_T moves away from K)</p>`
: `<p><strong>Maximum Profit:</strong> $${total_premium_straddle.toFixed(2)} (when S_T = ${K_straddle})</p>
<p><strong>Maximum Loss:</strong> Unlimited (as S_T moves away from K)</p>`
}
</div>`Strangle
A strangle involves buying (or selling) a call and a put with different strike prices but the same expiration date.
Investor Expectation
Long Strangle: Expects high volatility and a large price movement. Cheaper than a straddle but requires a larger move to profit.
Short Strangle: Expects low volatility with the price remaining between the two strikes.
viewof strangle_position = Inputs.radio(["Long", "Short"], {label: "Position", value: "Long"})
viewof K1_strangle = Inputs.range([50, 180], {step: 5, label: "K₁ (Put Strike)", value: 90})
viewof K2_strangle = Inputs.range([60, 200], {step: 5, label: "K₂ (Call Strike)", value: 110})
viewof ST_strangle = Inputs.range([0, 200], {step: 1, label: "S_T", value: 100})prices_strangle_k1 = blackScholes(S0_common, K1_strangle, T_common, r_common, sigma_common, q_common)
prices_strangle_k2 = blackScholes(S0_common, K2_strangle, T_common, r_common, sigma_common, q_common)
total_premium_strangle = prices_strangle_k1.put + prices_strangle_k2.call
// Generate data
x_max_strangle = Math.max(200, K2_strangle + 50);
data_strangle = d3.range(0, x_max_strangle + 1, 2).map(st => {
const put_payoff = Math.max(0, K1_strangle - st);
const call_payoff = Math.max(0, st - K2_strangle);
const payoff = strangle_position === "Long"
? put_payoff + call_payoff
: -(put_payoff + call_payoff);
const profit = strangle_position === "Long"
? payoff - total_premium_strangle
: payoff + total_premium_strangle;
return {st, payoff, profit};
});
// Current values
current_strangle_put = Math.max(0, K1_strangle - ST_strangle);
current_strangle_call = Math.max(0, ST_strangle - K2_strangle);
current_strangle_payoff = strangle_position === "Long"
? current_strangle_put + current_strangle_call
: -(current_strangle_put + current_strangle_call);
current_strangle_profit = strangle_position === "Long"
? current_strangle_payoff - total_premium_strangle
: current_strangle_payoff + total_premium_strangle;
// Breakeven points
breakeven_lower_strangle = K1_strangle - total_premium_strangle;
breakeven_upper_strangle = K2_strangle + total_premium_strangle;Plot.plot({
title: plotTitle(`${strangle_position} Strangle at Maturity`),
height: 400,
x: {label: "S_T", domain: [0, x_max_strangle]},
y: {label: "Value"},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K1_strangle], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([K2_strangle], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_lower_strangle], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_upper_strangle], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_strangle, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_strangle, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_strangle, current_strangle_profit]], {fill: "red", r: 6}),
Plot.tip(data_strangle, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_strangle, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₁ (Put) <span class="badge">${K1_strangle}</span></span>
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>K₂ (Call) <span class="badge">${K2_strangle}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_lower_strangle.toFixed(2)}, ${breakeven_upper_strangle.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>${strangle_position} Strangle Analysis</h4>
<p><strong>Position:</strong> ${strangle_position} put at K₁=${K1_strangle} and ${strangle_position.toLowerCase()} call at K₂=${K2_strangle}</p>
<p><strong>Total Premium:</strong> $${total_premium_strangle.toFixed(4)} ${strangle_position === "Long" ? "(paid)" : "(received)"}</p>
<p><strong>Breakeven Points:</strong> S_T = $${breakeven_lower_strangle.toFixed(2)} and $${breakeven_upper_strangle.toFixed(2)}</p>
${strangle_position === "Long"
? `<p><strong>Maximum Loss:</strong> $${total_premium_strangle.toFixed(2)} (when K₁ ≤ S_T ≤ K₂)</p>
<p><strong>Maximum Profit:</strong> Unlimited (as S_T moves beyond the strikes)</p>`
: `<p><strong>Maximum Profit:</strong> $${total_premium_strangle.toFixed(2)} (when K₁ ≤ S_T ≤ K₂)</p>
<p><strong>Maximum Loss:</strong> Unlimited (as S_T moves beyond the strikes)</p>`
}
</div>`Strip
A strip consists of one call and two puts with the same strike price. It profits from large moves but has a downward bias.
Investor Expectation
Long Strip: Expects high volatility with a higher probability of a large downward move.
Short Strip: Expects low volatility with the price remaining near the strike.
prices_strip = blackScholes(S0_common, K_strip, T_common, r_common, sigma_common, q_common)
total_premium_strip = prices_strip.call + 2 * prices_strip.put
// Generate data
x_max_strip = Math.max(200, K_strip + 50);
data_strip = d3.range(0, x_max_strip + 1, 2).map(st => {
const call_payoff = Math.max(0, st - K_strip);
const put_payoff = 2 * Math.max(0, K_strip - st);
const payoff = strip_position === "Long"
? call_payoff + put_payoff
: -(call_payoff + put_payoff);
const profit = strip_position === "Long"
? payoff - total_premium_strip
: payoff + total_premium_strip;
return {st, payoff, profit};
});
// Current values
current_strip_call = Math.max(0, ST_strip - K_strip);
current_strip_put = 2 * Math.max(0, K_strip - ST_strip);
current_strip_payoff = strip_position === "Long"
? current_strip_call + current_strip_put
: -(current_strip_call + current_strip_put);
current_strip_profit = strip_position === "Long"
? current_strip_payoff - total_premium_strip
: current_strip_payoff + total_premium_strip;
// Breakeven points (for long position)
// Lower breakeven: K - premium/2
// Upper breakeven: K + premium
breakeven_lower_strip = K_strip - total_premium_strip / 2;
breakeven_upper_strip = K_strip + total_premium_strip;Plot.plot({
title: plotTitle(`${strip_position} Strip at Maturity`),
height: 400,
x: {label: "S_T", domain: [0, x_max_strip]},
y: {label: "Value"},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K_strip], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_lower_strip], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_upper_strip], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_strip, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_strip, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_strip, current_strip_profit]], {fill: "red", r: 6}),
Plot.tip(data_strip, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_strip, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>Strike K <span class="badge">${K_strip}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_lower_strip.toFixed(2)}, ${breakeven_upper_strip.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>${strip_position} Strip Analysis</h4>
<p><strong>Position:</strong> ${strip_position} 1 call and ${strip_position.toLowerCase()} 2 puts at K=${K_strip}</p>
<p><strong>Total Premium:</strong> $${total_premium_strip.toFixed(4)} ${strip_position === "Long" ? "(paid)" : "(received)"}</p>
<p><strong>Breakeven Points:</strong> S_T = $${breakeven_lower_strip.toFixed(2)} and $${breakeven_upper_strip.toFixed(2)}</p>
${strip_position === "Long"
? `<p><strong>Maximum Loss:</strong> $${total_premium_strip.toFixed(2)} (when S_T = ${K_strip})</p>
<p><strong>Downside Bias:</strong> Profits more from downward moves (2:1 ratio of puts to calls)</p>`
: `<p><strong>Maximum Profit:</strong> $${total_premium_strip.toFixed(2)} (when S_T = ${K_strip})</p>
<p><strong>Downside Risk:</strong> Greater loss exposure from downward moves</p>`
}
</div>`Strap
A strap consists of two calls and one put with the same strike price. It profits from large moves but has an upward bias.
Investor Expectation
Long Strap: Expects high volatility with a higher probability of a large upward move.
Short Strap: Expects low volatility with the price remaining near the strike.
prices_strap = blackScholes(S0_common, K_strap, T_common, r_common, sigma_common, q_common)
total_premium_strap = 2 * prices_strap.call + prices_strap.put
// Generate data
x_max_strap = Math.max(200, K_strap + 50);
data_strap = d3.range(0, x_max_strap + 1, 2).map(st => {
const call_payoff = 2 * Math.max(0, st - K_strap);
const put_payoff = Math.max(0, K_strap - st);
const payoff = strap_position === "Long"
? call_payoff + put_payoff
: -(call_payoff + put_payoff);
const profit = strap_position === "Long"
? payoff - total_premium_strap
: payoff + total_premium_strap;
return {st, payoff, profit};
});
// Current values
current_strap_call = 2 * Math.max(0, ST_strap - K_strap);
current_strap_put = Math.max(0, K_strap - ST_strap);
current_strap_payoff = strap_position === "Long"
? current_strap_call + current_strap_put
: -(current_strap_call + current_strap_put);
current_strap_profit = strap_position === "Long"
? current_strap_payoff - total_premium_strap
: current_strap_payoff + total_premium_strap;
// Breakeven points (for long position)
// Lower breakeven: K - premium
// Upper breakeven: K + premium/2
breakeven_lower_strap = K_strap - total_premium_strap;
breakeven_upper_strap = K_strap + total_premium_strap / 2;Plot.plot({
title: plotTitle(`${strap_position} Strap at Maturity`),
height: 400,
x: {label: "S_T", domain: [0, x_max_strap]},
y: {label: "Value"},
grid: true,
pointer: "plot",
marks: [
Plot.ruleY([0], {stroke: "black"}),
Plot.ruleX([K_strap], {stroke: "gray", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_lower_strap], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.ruleX([breakeven_upper_strap], {stroke: "orange", strokeDasharray: "4,4"}),
Plot.line(data_strap, {x: "st", y: "payoff", stroke: "#2196F3", strokeWidth: 2.5}),
Plot.line(data_strap, {x: "st", y: "profit", stroke: "#E91E63", strokeWidth: 2.5}),
Plot.dot([[ST_strap, current_strap_profit]], {fill: "red", r: 6}),
Plot.tip(data_strap, Plot.pointerX({x: "st", y: "payoff", title: (d) => `S_T: ${d.st}\nPayoff: ${d.payoff.toFixed(2)}\nProfit: ${d.profit.toFixed(2)}`})),
Plot.crosshair(data_strap, {x: "st", y: "payoff", stroke: "#444", opacity: 0.6})
]
})html`<div class="plot-legend">
<span><span class="swatch-line" style="border-top-color: gray; border-top-style: dashed;"></span>Strike K <span class="badge">${K_strap}</span></span>
<span><span class="swatch-line" style="border-top-color: orange; border-top-style: dashed;"></span>Breakeven <span class="badge">${breakeven_lower_strap.toFixed(2)}, ${breakeven_upper_strap.toFixed(2)}</span></span>
<span><span class="swatch" style="background: #2196F3"></span> Payoff</span>
<span><span class="swatch" style="background: #E91E63"></span> Profit/Loss</span>
</div>`html`<div class="info-box">
<h4>${strap_position} Strap Analysis</h4>
<p><strong>Position:</strong> ${strap_position} 2 calls and ${strap_position.toLowerCase()} 1 put at K=${K_strap}</p>
<p><strong>Total Premium:</strong> $${total_premium_strap.toFixed(4)} ${strap_position === "Long" ? "(paid)" : "(received)"}</p>
<p><strong>Breakeven Points:</strong> S_T = $${breakeven_lower_strap.toFixed(2)} and $${breakeven_upper_strap.toFixed(2)}</p>
${strap_position === "Long"
? `<p><strong>Maximum Loss:</strong> $${total_premium_strap.toFixed(2)} (when S_T = ${K_strap})</p>
<p><strong>Upside Bias:</strong> Profits more from upward moves (2:1 ratio of calls to puts)</p>`
: `<p><strong>Maximum Profit:</strong> $${total_premium_strap.toFixed(2)} (when S_T = ${K_strap})</p>
<p><strong>Upside Risk:</strong> Greater loss exposure from upward moves</p>`
}
</div>`