Academy/Strategy Code Breakdown/Bull Call Spread — Code Walkthrough
Strategy Code BreakdownLesson 5

Bull Call Spread — Code Walkthrough

Walk through the CRR binomial tree pricing model, how Greeks are computed via finite differences, and how the spread P&L is assembled from two option legs.

18 minute read
4 key takeaways

Bull Call Spread — Code Walkthrough

This is the most mathematically rich strategy in the library. It prices options using a Cox-Ross-Rubinstein (CRR) binomial tree — a discrete-time model that converges to Black-Scholes as steps → ∞ but correctly handles American early-exercise.

Step 1 — CRR Tree Parameters

typescript
export function crrPrice(inputs: CRRInputs): number {
  const { spotPrice: S, strikePrice: K, timeToExpiry: T,
          riskFreeRate: r, volatility: sigma,
          optionType, optionStyle = 'american', steps: n = 100 } = inputs;

  if (T <= 0 || sigma <= 0) {
    // At/past expiry: return intrinsic value only
    return optionType === 'call' ? Math.max(S - K, 0) : Math.max(K - S, 0);
  }

  const dt   = T / n;                          // length of one time step (years)
  const u    = Math.exp(sigma * Math.sqrt(dt)); // up-move factor
  const d    = 1 / u;                           // down-move factor (1/u = recombining)
  const disc = Math.exp(-r * dt);               // risk-free discount per step
  const p    = (Math.exp(r * dt) - d) / (u - d);// risk-neutral up-probability
}
VariableFormulaIntuition
u (up factor)e^(σ√Δt)How much stock rises in an up-move. Higher vol → bigger moves.
d (down factor)1/uEnsures the tree recombines: up then down = down then up.
disc (discount)e^(−rΔt)Present value factor for one time step.
p (risk-neutral prob)(e^(rΔt) − d) / (u − d)Probability that makes the tree arbitrage-free. Not the real-world probability.

Step 2 — Terminal Payoffs (Forward Pass)

typescript
// At expiry (step n), node j has stock price S × u^j × d^(n-j)
// There are n+1 terminal nodes: j = 0 (all down), 1, 2, ..., n (all up)
const values: number[] = Array.from({ length: n + 1 }, (_, j) => {
  const spotT = S * Math.pow(u, j) * Math.pow(d, n - j);
  return optionType === 'call'
    ? Math.max(spotT - K, 0) // call payoff
    : Math.max(K - spotT, 0); // put payoff
});

This array has n+1 elements indexed by number of up-moves j. Because d = 1/u, the tree recombines: an up-then-down path lands at the same price as a down-then-up path. Without this, you'd need 2^n nodes instead of (n+1), making n=100 feasible.

Step 3 — Backward Induction (American Early Exercise)

typescript
// Walk backward from expiry to today
for (let i = n - 1; i >= 0; i--) {
  for (let j = 0; j <= i; j++) {
    // Continuation value: expected discounted payoff if we wait
    const continuation = disc * (p * values[j + 1] + (1 - p) * values[j]);

    if (optionStyle === 'american') {
      // Early exercise value at this node
      const spotIJ = S * Math.pow(u, j) * Math.pow(d, i - j);
      const intrinsic = optionType === 'call'
        ? Math.max(spotIJ - K, 0)
        : Math.max(K - spotIJ, 0);

      // American: holder chooses the better of exercise now vs wait
      values[j] = Math.max(intrinsic, continuation);
    } else {
      values[j] = continuation; // European: can't exercise early
    }
  }
}
return Math.round(values[0] * 1e6) / 1e6; // values[0] = today's fair price

Why American Options Need the Tree

Black-Scholes has a closed-form solution for European options but not American ones. The tree evaluates "should I exercise today?" at every node — this is the key advantage of the binomial model. For American puts especially, early exercise can be optimal when deep in-the-money.

Step 4 — Greeks via Finite Differences

typescript
export function crrGreeks(inputs: CRRInputs): Greeks {
  const { spotPrice: S, volatility: sigma, riskFreeRate: r, timeToExpiry: T } = inputs;
  const dS = S * 0.01; // bump spot by 1%

  const base = crrPrice(inputs);                         // base price
  const pu   = crrPrice({ ...inputs, spotPrice: S + dS }); // price if spot +1%
  const pd   = crrPrice({ ...inputs, spotPrice: S - dS }); // price if spot -1%

  return {
    // Delta: ∂Price/∂Spot (central difference, most accurate)
    delta: (pu - pd) / (2 * dS),

    // Gamma: ∂²Price/∂Spot² (second derivative)
    gamma: (pu - 2 * base + pd) / dS ** 2,

    // Theta: ∂Price/∂Time (how much value lost per day)
    theta: (crrPrice({ ...inputs, timeToExpiry: T - 1/365 }) - base) / (1/365) / 365,

    // Vega: ∂Price/∂Volatility per 1% vol change
    vega: (crrPrice({ ...inputs, volatility: sigma + 0.01 }) -
           crrPrice({ ...inputs, volatility: sigma - 0.01 })) / (2 * 0.01) / 100,
  };
}

Central finite differences are used for Delta and Gamma because they're more accurate than one-sided differences (error is O(h²) vs O(h)). Each Greek is just the option price sensitivity to one input — by bumping that input slightly and re-pricing the full tree.

Step 5 — Assembling the Spread

typescript
// Buy lower-strike call (long), sell higher-strike call (short)
const longCallPrice  = crrPrice(base(lowStrike));   // costs money
const shortCallPrice = crrPrice(base(highStrike));  // earns money

const netDebit    = (longCallPrice - shortCallPrice) * contracts * 100;
const spreadWidth = (highStrike - lowStrike) * contracts * 100;
const maxProfit   = spreadWidth - netDebit; // capped at highStrike
const maxLoss     = netDebit;               // limited to what you paid
const breakeven   = lowStrike + netDebit / (contracts * 100);
Scenario at ExpiryP&LWhy
Stock below low strike−netDebit (max loss)Both calls expire worthless
Stock at breakeven$0Long call gain equals net debit paid
Stock above high strike+maxProfit (capped)Long call fully profitable, short call fully losses — net is spread width minus debit
Key Takeaways
  • CRR builds a recombining lattice of stock prices and backward-inducts option values
  • American options can exercise early — the tree checks intrinsic value at every node
  • Greeks are finite differences: bump one input, re-price, take the derivative
  • A bull call spread costs less than a naked call but caps upside at the short strike

Open Bull Call Spread in Lab

Clone and analyze the bull call spread strategy