Strategy: Straddle & Strangle¶

Open In Colab

Long straddle, short straddle, and long strangle templates with payoff charts.

Part 08 of 20 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).")

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