import {Inputs} from "@observablehq/inputs"
// Inputs
viewof S0 = Inputs.range([10, 200], {step: 1, value: 100, label: "Spot Price S₀"})
viewof K = Inputs.range([10, 200], {step: 1, value: 100, label: "Strike Price K"})
viewof r = Inputs.range([0, 0.2], {step: 0.001, value: 0.05, label: "Risk-free rate r"})
viewof T = Inputs.range([0.1, 3.0], {step: 0.1, value: 1.00, label: "Time to maturity T (years)"})
viewof D = Inputs.range([0, 50], {step: 0.1, value: 0, label: "PV of Dividends D"})
// Present value of strike
PV_K = K * Math.exp(-r * T)
// Start with fair prices, then allow adjustment (adjusted for dividends)
fairCallStart = Math.max(0, S0 - D - PV_K)
fairPutStart = Math.max(0, PV_K + D - S0)
viewof c = Inputs.range([0, 100], {step: 0.01, value: fairCallStart, label: "Market Call Price c"})
viewof p = Inputs.range([0, 100], {step: 0.01, value: fairPutStart, label: "Market Put Price p"})
// Put-call parity calculations (with dividends)
leftSide = c + D + PV_K
rightSide = p + S0
imbalance = leftSide - rightSide
// Define precision for parity check
PARITY_DECIMALS = 2
parityTol = 0.5 * Math.pow(10, -PARITY_DECIMALS)
isBalanced = Math.abs(imbalance) <= parityTol
// Determine which side is overpriced
direction = isBalanced ? "balanced" : (imbalance > 0 ? "call_expensive" : "put_expensive")
fmt = d => d.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})
viewof execute = {
if (isBalanced) {
const placeholder = html`<div></div>`
placeholder.style.display = 'none'
return placeholder
}
const b = Inputs.button("Correct the Imbalance!")
b.classList.add("parity-btn")
b.classList.toggle("glow", true)
return b
}
md`### Put-Call Parity Check
**Left Side** (Call + Dividends + PV of Strike): c + D + Ke<sup>-rT</sup> = ${fmt(c)} + ${fmt(D)} + ${fmt(PV_K)} = **${fmt(leftSide)}**
**Right Side** (Put + Stock): p + S₀ = ${fmt(p)} + ${fmt(S0)} = **${fmt(rightSide)}**
**Imbalance**: ${fmt(Math.abs(imbalance))}
**Status**: ${isBalanced ? "✅ Parity holds (no arbitrage)" : (direction === "call_expensive" ? "⚠️ Call side is overpriced → Sell call, buy put" : "⚠️ Put side is overpriced → Sell put, buy call")}
**Potential Profit**: $${fmt(isBalanced ? 0 : Math.abs(imbalance))}`Put-Call Parity
Interactive dashboard illustrating how put-call parity maintains equilibrium and the arbitrage strategies when parity is violated.
Put-call parity is a fundamental relationship between European call and put options with the same strike price and expiration date. The parity states that a portfolio of a call option plus cash equal to the present value of the strike price has the same value as a portfolio of a put option plus the underlying stock, adjusted for dividends.
\[c + D + Ke^{-rT} = p + S_0\]
where \(D\) is the present value of dividends paid during the life of the option. This formula can also be seen as a rearrangement of the more direct formulation \(c + Ke^{-rT} = p + (S_0 - D)\), where the stock price is adjusted for the present value of dividends. When this relationship is violated, arbitrageurs can lock in risk-free profits by constructing opposing portfolios.
Market Dashboard
Use the sliders to set market parameters and option prices. The theoretical prices based on put-call parity are computed for reference. When mispricing exists, the “Correct the Imbalance!” button becomes active and glows.
Note
Put-call parity for European options states: \(c + D + Ke^{-rT} = p + S_0\)
where \(D\) is the present value of dividends paid during the option’s life.
- If \(c + D + Ke^{-rT} > p + S_0\): The call side is expensive. Arbitrage: Sell the call, buy the put, and buy the stock. Finance the stock with a loan for \(S_0\). Invest the net proceeds from the options and the PV of future dividends, \(c - p + D\), at the risk-free rate. This creates a zero-cost portfolio with a guaranteed profit at expiration.
- If \(c + D + Ke^{-rT} < p + S_0\): The put side is expensive. Arbitrage: Buy the call, sell the put, and short the stock. Invest the proceeds from the short sale, \(S_0\). Invest (or borrow) the net proceeds from the options less the PV of dividend payments, \(p - c - D\). This creates a zero-cost portfolio with a guaranteed profit at expiration.
Payoff Summary
What’s Going On?
- Call side overpriced (\(c + D + Ke^{-rT} > p + S_0\)):
- Strategy: Sell the call, buy the put, and buy the stock.
- Financing: The stock purchase is financed by borrowing \(S_0\). The net proceeds from options (\(c-p\)) plus the present value of dividends to be received (\(D\)) are invested at rate \(r\). This makes the initial cost zero.
- Maturity: The options/stock position is settled for a cash amount of \(K\). The loan is repaid (\(-S_0e^{rT}\)) and the investment matures (\(+(c-p+D)e^{rT}\)).
- Profit (PV): The present value of the final cash position is exactly the initial imbalance: \((c + D + Ke^{-rT}) - (p + S_0)\).
- Put side overpriced (\(p + S_0 > c + D + Ke^{-rT}\)):
- Strategy: Buy the call, sell the put, and short the stock.
- Financing: The proceeds from the short sale (\(S_0\)) are invested at rate \(r\). The net proceeds from options (\(p-c\)) less the present value of the dividend liability (\(D\)) are also invested (or borrowed if negative). The initial cost is zero.
- Maturity: The options/stock position is settled by paying \(K\) to acquire a share and close the short. The investments mature \(+S_0e^{rT}\) and \(+(p-c-D)e^{rT}\).
- Profit (PV): The present value of the final cash position is exactly the initial imbalance: \((p + S_0) - (c + D + Ke^{-rT})\).
Tip
Key Insight: Put-call parity creates two synthetic ways to replicate a position:
- Synthetic stock: \(c - p + D + Ke^{-rT} = S_0\) (a long call plus a short put plus dividends plus cash equals a long stock)
- Synthetic call: \(c = p + S_0 - D - Ke^{-rT}\) (a call equals a put plus stock minus dividends minus the PV of the strike)
When these relationships break down, arbitrageurs trade the expensive synthetic against the cheap one to lock in risk-free profit. Dividends favor the stock holder (long position) and disadvantage the short seller.