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.
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
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()
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))| Variable | Typical Value | Meaning |
|---|---|---|
| high_strike | spot × 1.0 = ATM | The put you own — intrinsically valuable when stock falls |
| low_strike | spot × 0.95 = 5% OTM | The put you sold — caps profit at this floor |
| net_debit | $3–6 per share typical | What you paid: long_put cost − short_put premium received |
| breakeven | high_strike − net_debit/100 | Stock must fall below this for the trade to profit |
Step 3 — pnl_at_expiry: The P&L Diagram
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.
- 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