Option Payoff & Breakeven Calculator¶
Multi-leg payoff curves, breakevens, and max profit/loss at expiry.
Part 07 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).")
Payoff at expiry vs. mark-to-market P&L¶
Everything in this notebook is the payoff at expiry — intrinsic value only, ignoring time value and any premium you could realize by closing early. That's a deliberate simplification: it's the easiest way to see a strategy's worst case and best case, but it is not what your broker terminal shows you intraday, where the P&L also reflects theta already collected/paid and any change in implied volatility. Two positions with the same expiry payoff can have very different mark-to-market P&L on day 3 of a 7-day trade.
Breakeven is the underlying price at which net P&L crosses zero — for a single long call, that's strike + premium paid, not the strike itself. It's easy to forget the premium and think of the strike as the breakeven; the two only coincide for a position with zero net cost.
Max profit/loss matters beyond curiosity — your broker's margin requirement, and any risk-management guardrail you build (see notebook 15), should be sized off the realistic worst case for the combination of legs, not each leg in isolation. A defined-risk spread and a naked short option can have identical initial margin but wildly different max loss.
Python port of shared/lib/strategyMath.js — payoff is computed at expiry (intrinsic value only), same as the payoff chart on /tools/strategy-builder.
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)}
legs = [
{"side": "buy", "type": "CE", "strike": 24000, "premium": 180, "qty": 75},
{"side": "sell", "type": "CE", "strike": 24200, "premium": 90, "qty": 75},
]
points = payoff_curve(legs, spot=24000)
print("Net premium:", net_premium(legs))
print("Breakevens:", breakevens(points))
print("Max P/L:", max_profit_loss(points))
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()
plot_payoff(points, "Bull call spread payoff at expiry")
« Previous: Black-Scholes Pricing & Greeks
Next: Strategy: Straddle & Strangle »
Try the concepts above interactively: Options Strategy Builder · Docs · Get your static IP