Interactive dashboard illustrating how arbitrageurs enforce price consistency in forward markets (no dividends).
Arbitrage enforces price consistency between the spot market and the forward market. When the observed forward price \(F_0\) deviates from the no-arbitrage benchmark \(S_0 e^{rT}\) (for a non-dividend-paying asset), an arbitrageur can lock in a risk-free profit using cash-and-carry (if the forward is overpriced) or reverse cash-and-carry (if the forward is underpriced).
Market Dashboard
Use the sliders to set the spot price, market forward price, risk-free rate, and time to maturity. The no-arbitrage forward price \(S_0 e^{rT}\) is computed for reference. When mispricing exists, the Execute button becomes active and glows.
import {Inputs} from"@observablehq/inputs"// Inputsviewof S0 = Inputs.range([10,200], {step:1,value:100,label:"Spot Price S₀"})viewof r = Inputs.range([0,0.2], {step:0.001,value:0.05,label:"Risk-free rate r"})viewof T = Inputs.range([0.0,3.0], {step:0.01,value:1.00,label:"Time to maturity T (years)"})// No‑arbitrage benchmark used to seed F0noArb = S0 *Math.exp(r * T)// Forward price (allow finer decimals, start at no‑arb)viewof F0 = Inputs.range([10,300], {step:0.001,value: noArb,label:"Market Forward Price F₀"})// Derived quantitiesmispricing = F0 - noArb// Define no-arbitrage precision to 3 decimalsNA_DECIMALS =3fairTol =0.5*Math.pow(10,-NA_DECIMALS)isFair =Math.abs(mispricing) <= fairToldirection = isFair ?"fair": (mispricing >0?"overpriced":"underpriced")fmt = d => d.toLocaleString(undefined, {maximumFractionDigits:4})viewof execute = {if (isFair) {// Hide the button entirely when no arbitrageconst placeholder =html`<div></div>` placeholder.style.display='none'return placeholder }const b = Inputs.button("Execute Arbitrage") b.classList.add("arb-btn") b.classList.toggle("glow",true)return b}md`**No-arbitrage forward price**: $${fmt(noArb)}**Status**: ${direction ==="fair"?"No arbitrage (fairly priced)": (direction ==="overpriced"?"Forward overpriced → cash-and-carry":"Forward underpriced → reverse cash-and-carry")}**Potential Profit at Maturity**: $${fmt(isFair ?0:Math.abs(mispricing))}`
Note
The no-arbitrage forward price for a non-dividend-paying asset is \(S_0 e^{rT}\). This model uses a continuously compounded rate (\(r\)) because it is the standard for pricing derivatives, reflecting interest that accrues constantly. If \(F_0 > S_0 e^{rT}\), sell the forward and carry the asset (cash-and-carry). If \(F_0 < S_0 e^{rT}\), do the reverse (reverse cash-and-carry).
Execute Arbitrage
Click Execute to see the sequence of trades and the risk-free payoff at maturity. The animation reverses depending on whether \(F_0\) is above or below \(S_0 e^{rT}\).
// import {tex} from "@observablehq/tex"// Wrap the animation in a single reactive cell that depends on `execute`.viewof arb_steps = {// Re-run this cell on every click execute;const container =html`<div class="arb-container"></div>`const overpriced = mispricing > fairTolconst underpriced = mispricing <-fairTol// Simple inline SVG icon set for a professional lookfunctioniconSVG(name) {const wrap =document.createElement("span") wrap.className="icon"const common ='width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"'let svg =""switch (name) {case"cash": svg =`<svg ${common}><rect x="2" y="6" width="20" height="12" rx="2"></rect><circle cx="12" cy="12" r="3"></circle></svg>`;breakcase"asset": svg =`<svg ${common}><rect x="4" y="6" width="16" height="12" rx="2"></rect><path d="M8 10h8M8 14h5"/></svg>`;breakcase"contract": svg =`<svg ${common}><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M9 13h6M9 17h6"/></svg>`;breakcase"ff": svg =`<svg ${common}><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;breakcase"deliver": svg =`<svg ${common}><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 5 17 10"/><line x1="12" y1="5" x2="12" y2="19"/></svg>`;breakcase"repay": svg =`<svg ${common}><path d="M3 10h11a4 4 0 0 1 0 8H7"/><polyline points="7 14 3 10 7 6"/></svg>`;breakcase"short": svg =`<svg ${common}><path d="M3 3v18h18"/><path d="M7 15l4-4 4 4 4-4"/></svg>`;breakcase"invest": svg =`<svg ${common}><path d="M3 3v18h18"/><path d="M7 17l4-6 4 4 4-8"/></svg>`;breakcase"receive": svg =`<svg ${common}><path d="M21 9V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;breakdefault: svg =`<svg ${common}><circle cx="12" cy="12" r="4"/></svg>` } wrap.innerHTML= svgreturn wrap }functionstep(content, icon) {const row =document.createElement("div") row.className="step"const badge =iconSVG(icon)const body =document.createElement("div") body.className="body"if (typeof content ==='string') { body.textContent= content } else { body.append(content) } row.append(badge, body) container.append(row) }functionwait(ms) { returnnewPromise(res =>setTimeout(res, ms)) }asyncfunctionrun() { container.innerHTML=""if (!overpriced &&!underpriced) {step(md`No arbitrage opportunity: the forward is fairly priced.`,"✅")return }if (overpriced) {// Cash-and-carry (F0 > S0 e^{rT})step(md`Step 1: Borrow ${tex`S_0`} at rate ${tex`r`}. Receive ${fmt(S0)} in cash.`,"cash")awaitwait(700)step(md`Step 2: Buy the asset in the spot market for ${fmt(S0)}.`,"asset")awaitwait(700)step(md`Step 3: Sell a forward contract at the high price ${fmt(F0)}.`,"contract")awaitwait(700)step(md`Fast forward to maturity ${tex`T`}.`,"ff")awaitwait(700)step(md`Step 4: Deliver the asset into the forward and receive ${fmt(F0)}.`,"deliver")awaitwait(700)step(md`Step 5: Repay loan principal and interest ${tex`S_0 e^{rT}`} = ${fmt(noArb)}.`,"repay") } else {// Reverse cash-and-carry (F0 < S0 e^{rT})step(md`Step 1: Short the asset in the spot market; receive ${fmt(S0)}.`,"short")awaitwait(700)step(md`Step 2: Invest the proceeds at the risk-free rate ${tex`r`} to maturity.`,"invest")awaitwait(700)step(md`Step 3: Buy a forward contract at the low price ${fmt(F0)}.`,"contract")awaitwait(700)step(md`Fast forward to maturity ${tex`T`}.`,"ff")awaitwait(700)step(md`Step 4: Receive the asset via the long forward by paying ${fmt(F0)}.`,"receive")awaitwait(700)step(md`Step 5: Your investment matures to ${fmt(noArb)}. Use ${fmt(F0)} of this to settle the forward, receiving the asset, and then return the asset to close the short position.`,"repay") }awaitwait(400)const profit =Math.abs(mispricing)const res =document.createElement("div") res.className="result"const heading =document.createElement("div") heading.innerHTML=`<strong>Risk‑free Profit at Maturity:</strong> ${fmt(profit)}`const explain =document.createElement("div") explain.className="explain"const explNode = overpriced?md`Profit = ${tex`F_0 - S_0 e^{rT}`} = ${fmt(F0)} − ${fmt(noArb)} = ${fmt(profit)}`:md`Profit = ${tex`S_0 e^{rT} - F_0`} = ${fmt(noArb)} − ${fmt(F0)} = ${fmt(profit)}` explain.append(explNode) res.append(heading, explain) container.append(res) }run()return container}
Short the asset, invest proceeds, buy the forward; at maturity receive the asset via the forward for \(F_0\), return the asset, and keep \(S_0 e^{rT} − F_0\).
Tip
This demo assumes no dividends, no carry costs, and frictionless borrowing/lending at the same continuously compounded rate \(r\) for horizon \(T\).