Strategy: Straddle & Strangle¶
Long straddle, short straddle, and long strangle templates with payoff charts.
Part 08 of 37 in the ServLoci algo/options trading notebook series — full index in notebooks/README.md.
Setup¶
# Get your dedicated static IPv6 + SOCKS5 credentials free:
# https://comm.servloci.in/register (or /auth/google?free=1 for an instant trial)
# Your api_key / api_secret pair shows up in the portal after signup:
# https://comm.servloci.in/user
!pip install -q "requests[socks]"
!curl -sL https://comm.servloci.in/sdk/servloci.py -o servloci.py
import os
from servloci import ServLoci
SERVLOCI_API_KEY = os.environ.get("SERVLOCI_API_KEY", "dhan:1000000001") # broker:client_id
SERVLOCI_API_SECRET = os.environ.get("SERVLOCI_API_SECRET", "") # from the portal — leave blank to run this notebook in demo mode
sl = None
if SERVLOCI_API_SECRET:
sl = ServLoci(api_key=SERVLOCI_API_KEY, api_secret=SERVLOCI_API_SECRET)
print("ServLoci configured:", sl.host, sl.port)
else:
print("SERVLOCI_API_SECRET not set — running in demo mode (no live proxy calls).")
Betting on volatility itself, not direction¶
A straddle or strangle is a bet on how much the underlying moves, not which way. That makes it fundamentally different from the vertical spreads in the next notebook, which express a directional view.
- Long straddle (buy ATM call + buy ATM put): you profit if the move — in either direction — is bigger than the combined premium paid. Traders reach for this ahead of a known catalyst (results, a policy announcement, an election outcome) when they expect a large move but don't know which way. The risk is capped at the premium paid, but IV is usually already elevated going into the event, so you're paying up for that uncertainty — and if the move doesn't happen, both legs decay together.
- Short straddle (sell ATM call + sell ATM put): the mirror position, collecting premium on the belief that the underlying will stay range-bound. This is a genuinely popular income strategy on NIFTY/BANKNIFTY weeklies because theta decay is fast into expiry — but the risk is undefined on both sides. A short straddle has no built-in stop; a sharp move against either leg can lose far more than the premium collected, which is why position sizing (notebook 15) and a hard max-loss guardrail matter more here than almost anywhere else in this series.
- Long strangle: the same volatility bet as a long straddle, using OTM strikes instead of ATM. Lower premium outlay, but the underlying has to move further before you're profitable — a real tradeoff between cost and breakeven width, not a strictly better version of the straddle.
The common thread: straddles and strangles are trades on implied volatility versus realized volatility. If you buy one and realized volatility turns out lower than what you paid for, you lose even if you correctly predicted the direction of the eventual move — timing matters as much as the thesis.
Port of the long_straddle / short_straddle / long_strangle templates from shared/data/strategyTemplates.js. Premiums are backfilled with Black-Scholes (notebook 06) since no live chain is passed in demo mode — swap in a real chain from notebook 11 for live premiums.
from scipy.stats import norm
import math
def bs_price(opt_type, spot, strike, t_years, vol, rate=0.065):
if t_years <= 0 or vol <= 0:
return max(spot - strike, 0) if opt_type == "CE" else max(strike - spot, 0)
d1 = (math.log(spot / strike) + (rate + vol * vol / 2) * t_years) / (vol * math.sqrt(t_years))
d2 = d1 - vol * math.sqrt(t_years)
if opt_type == "CE":
return spot * norm.cdf(d1) - strike * math.exp(-rate * t_years) * norm.cdf(d2)
return strike * math.exp(-rate * t_years) * norm.cdf(-d2) - spot * norm.cdf(-d1)
def leg_payoff_at_expiry(leg, underlying_price):
iv = max(underlying_price - leg["strike"], 0) if leg["type"] == "CE" else max(leg["strike"] - underlying_price, 0)
sign = 1 if leg["side"] == "buy" else -1
return sign * (iv - leg["premium"]) * leg["qty"]
def strategy_payoff_at_expiry(legs, underlying_price):
return sum(leg_payoff_at_expiry(leg, underlying_price) for leg in legs)
def net_premium(legs):
return sum((-1 if leg["side"] == "buy" else 1) * leg["premium"] * leg["qty"] for leg in legs)
def payoff_curve(legs, spot, rng=0.15, steps=120):
lo, hi = spot * (1 - rng), spot * (1 + rng)
pts = []
for i in range(steps + 1):
p = lo + (hi - lo) * i / steps
pts.append({"price": p, "pnl": strategy_payoff_at_expiry(legs, p)})
return pts
def breakevens(points):
crossings = []
for a, b in zip(points, points[1:]):
if (a["pnl"] < 0 <= b["pnl"]) or (a["pnl"] > 0 >= b["pnl"]):
t = 0 if a["pnl"] == b["pnl"] else -a["pnl"] / (b["pnl"] - a["pnl"])
crossings.append(a["price"] + t * (b["price"] - a["price"]))
return crossings
def max_profit_loss(points):
pnls = [p["pnl"] for p in points]
return {"maxProfit": max(pnls), "maxLoss": min(pnls)}
def nearest_strike(spot, step):
return round(spot / step) * step
def find_premium(chain, strike, opt_type):
if not chain:
return 0
row = next((r for r in chain if r["strike"] == strike), None)
if not row:
return 0
leg = row.get("ce" if opt_type == "CE" else "pe")
return leg["ltp"] if leg and isinstance(leg.get("ltp"), (int, float)) else 0
def fill_demo_premiums(legs, spot, t_years=7 / 365, vol=0.13):
for leg in legs:
if not leg["premium"]:
leg["premium"] = round(bs_price(leg["type"], spot, leg["strike"], t_years, vol), 2)
return legs
def long_straddle(spot, step, chain=None, qty=1):
atm = nearest_strike(spot, step)
return [
{"side": "buy", "type": "CE", "strike": atm, "premium": find_premium(chain, atm, "CE"), "qty": qty},
{"side": "buy", "type": "PE", "strike": atm, "premium": find_premium(chain, atm, "PE"), "qty": qty},
]
def short_straddle(spot, step, chain=None, qty=1):
atm = nearest_strike(spot, step)
return [
{"side": "sell", "type": "CE", "strike": atm, "premium": find_premium(chain, atm, "CE"), "qty": qty},
{"side": "sell", "type": "PE", "strike": atm, "premium": find_premium(chain, atm, "PE"), "qty": qty},
]
def long_strangle(spot, step, chain=None, qty=1):
atm = nearest_strike(spot, step)
call_strike, put_strike = atm + 2 * step, atm - 2 * step
return [
{"side": "buy", "type": "CE", "strike": call_strike, "premium": find_premium(chain, call_strike, "CE"), "qty": qty},
{"side": "buy", "type": "PE", "strike": put_strike, "premium": find_premium(chain, put_strike, "PE"), "qty": qty},
]
SPOT, STEP = 24000, 50
import matplotlib.pyplot as plt
def plot_payoff(points, title):
xs = [p["price"] for p in points]
ys = [p["pnl"] for p in points]
plt.axhline(0, color="grey", linewidth=0.8)
plt.plot(xs, ys)
plt.title(title)
plt.xlabel("Underlying spot")
plt.ylabel("P&L (INR)")
plt.show()
for name, builder in [("Long straddle", long_straddle), ("Short straddle", short_straddle), ("Long strangle", long_strangle)]:
legs = fill_demo_premiums(builder(SPOT, STEP), SPOT)
points = payoff_curve(legs, SPOT)
print(name, "max P/L:", max_profit_loss(points))
plot_payoff(points, name)
« Previous: Option Payoff & Breakeven Calculator
Next: Strategy: Vertical Spreads »
Try the concepts above interactively: Options Strategy Builder · Docs · Get your static IP