Academy/Strategy Code Breakdown/Bear Put Spread — Code Walkthrough
Strategy Code BreakdownLesson 6

Bear Put Spread — Code Walkthrough

Walk through the Python implementation: CRR put pricing, the spread assembly with two put legs, and how the P&L diagram is computed over a price range.

14 minute read
4 key takeaways

Bear Put Spread — Code Walkthrough

A Bear Put Spread profits when the underlying falls. It's a defined-risk alternative to a naked long put: you buy an ATM put and sell an OTM put to reduce cost, accepting a cap on maximum profit. This Python implementation uses the same CRR binomial tree as the TypeScript strategies.

Step 1 — crr_price: Python CRR Implementation

python
def crr_price(S, K, T, r, sigma, option_type='put', steps=100, style='american'):
    if T <= 0 or sigma <= 0:
        return max(S - K, 0.0) if option_type == 'call' else max(K - S, 0.0)

    dt   = T / steps
    u    = math.exp(sigma * math.sqrt(dt))
    d    = 1.0 / u
    disc = math.exp(-r * dt)
    p    = (math.exp(r * dt) - d) / (u - d)

    # Terminal payoffs — list comprehension builds all n+1 nodes at once
    values = [
        max(S * (u**j) * (d**(steps-j)) - K, 0.0) if option_type == 'call'
        else max(K - S * (u**j) * (d**(steps-j)), 0.0)
        for j in range(steps + 1)
    ]

    # Backward induction — same logic as TypeScript, just Python syntax
    for i in range(steps - 1, -1, -1):
        for j in range(i + 1):
            cont = disc * (p * values[j+1] + (1-p) * values[j])
            if style == 'american':
                spot_ij = S * (u**j) * (d**(i-j))
                intrinsic = max(spot_ij - K, 0.0) if option_type == 'call' else max(K - spot_ij, 0.0)
                values[j] = max(intrinsic, cont)
            else:
                values[j] = cont
    return round(values[0], 6)

Identical math to bull-call-spread-strategy.ts — but now option_type defaults to "put" since this is a put-based strategy. The backward induction loop is written with Python's range(steps-1, -1, -1) which iterates from n-1 down to 0 inclusive.

Step 2 — BearPutSpreadStrategy.evaluate()

python
def evaluate(self, spot_price, historical_vol=0.25, target_price=None):
    sigma = self.implied_volatility or historical_vol
    T     = self.days_to_expiry / 365.0

    # Long put = ATM (high_strike_offset=0.0), Short put = OTM (low_strike_offset=-0.05)
    high_strike = round(spot_price * (1 + self.high_strike_offset), 2)
    low_strike  = round(spot_price * (1 + self.low_strike_offset),  2)

    long_put  = crr_price(spot_price, high_strike, T, self.risk_free_rate, sigma, 'put')
    short_put = crr_price(spot_price, low_strike,  T, self.risk_free_rate, sigma, 'put')

    net_debit    = (long_put - short_put) * self.contracts * 100
    spread_width = (high_strike - low_strike) * self.contracts * 100
    max_profit   = spread_width - net_debit  # capped at low_strike
    max_loss     = net_debit
    breakeven    = high_strike - (net_debit / (self.contracts * 100))
VariableTypical ValueMeaning
high_strikespot × 1.0 = ATMThe put you own — intrinsically valuable when stock falls
low_strikespot × 0.95 = 5% OTMThe put you sold — caps profit at this floor
net_debit$3–6 per share typicalWhat you paid: long_put cost − short_put premium received
breakevenhigh_strike − net_debit/100Stock must fall below this for the trade to profit

Step 3 — pnl_at_expiry: The P&L Diagram

python
def pnl_at_expiry(self, spot_price, price_range, steps=50):
    high_strike = spot_price * (1 + self.high_strike_offset)
    low_strike  = spot_price * (1 + self.low_strike_offset)
    net_debit   = long_cost - short_cred  # computed from CRR prices

    lo, hi = price_range
    prices = [lo + (hi - lo) * i / (steps - 1) for i in range(steps)]

    # For each terminal price, compute: long put payoff - short put payoff - cost paid
    pnls = [(max(high_strike - p, 0) - max(low_strike - p, 0) - net_debit)
             * self.contracts * 100 for p in prices]
    return {'prices': prices, 'pnl': pnls}

max(high_strike − p, 0) is the long put payoff. max(low_strike − p, 0) is what the short put costs you at expiry. Subtracting them and then the net_debit gives the diagram used in the Lab's P&L chart.

Reading the P&L Diagram

Below high_strike: profit grows as stock falls. Below low_strike: both legs are in-the-money, so the short put cancels additional gains — flat maximum profit line. Above high_strike: both expire worthless, max loss = net debit.

Key Takeaways
  • Buy a higher-strike put (ATM), sell a lower-strike put (OTM) — same expiry
  • The short put offsets cost but caps your downside profit at the short strike
  • Breakeven = high strike − net debit per share
  • The CRR Python implementation is identical in logic to the TypeScript version

Open Bear Put Spread in Lab

Clone and analyze the bear put spread strategy