Option Basics - Calls, Puts, and Payoffs › Implementing a Payoff Calculator

Michael BrenndoerferNovember 15, 20254 min read
Quantitative FinanceAll chapters
Part 3 of 15Forwards, Futures, and Options
Chapter 18Option Basics - Calls, Puts, and Payoffs › Implementing a Payoff Calculator
Reading section 11 of 15 · Implementing a Payoff Calculator · 4 minSection 11 of 15 · 4 min

Book contents

Section 11 of 15 · 4 min read

Option Basics - Calls, Puts, and Payoffs › Implementing a Payoff CalculatorOne section · Sections in this chapter11 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's build a payoff calculator that handles any combination of options and stock positions. This tool will help you analyze strategies before putting capital at risk.

In[22]:
Code
import numpy as np


class OptionPosition:
    """Represents a single option or stock position."""

    def __init__(self, position_type, quantity, strike=None, premium=0):
        """
        Initialize an option position.

        Parameters:
        -----------
        position_type : str
            'long_call', 'short_call', 'long_put', 'short_put', 'long_stock', 'short_stock'
        quantity : float
            Number of contracts (positive for long, but sign handled by position_type)
        strike : float
            Strike price (None for stock positions)
        premium : float
            Premium paid (positive) or received (negative handled by type)
        """
        self.position_type = position_type
        self.quantity = abs(quantity)
        self.strike = strike
        self.premium = premium

    def payoff(self, S_T):
        """Calculate payoff at expiration price S_T."""
        S_T = np.atleast_1d(S_T)

        if self.position_type == "long_call":
            return self.quantity * np.maximum(S_T - self.strike, 0)
        elif self.position_type == "short_call":
            return -self.quantity * np.maximum(S_T - self.strike, 0)
        elif self.position_type == "long_put":
            return self.quantity * np.maximum(self.strike - S_T, 0)
        elif self.position_type == "short_put":
            return -self.quantity * np.maximum(self.strike - S_T, 0)
        elif self.position_type == "long_stock":
            return self.quantity * S_T
        elif self.position_type == "short_stock":
            return -self.quantity * S_T
        else:
            raise ValueError(f"Unknown position type: {self.position_type}")

    def profit(self, S_T):
        """Calculate profit at expiration price S_T."""
        if "long" in self.position_type:
            return self.payoff(S_T) - self.quantity * self.premium
        else:  # short position
            return self.payoff(S_T) + self.quantity * self.premium
In[23]:
Code
def analyze_strategy(positions, S_range):
    """
    Analyze an option strategy across a range of expiration prices.

    Parameters:
    -----------
    positions : list of OptionPosition
        The positions in the strategy
    S_range : array-like
        Range of stock prices to analyze

    Returns:
    --------
    dict with total_profit, max_profit, max_loss, breakevens
    """
    S_range = np.array(S_range)
    total_profit = np.zeros_like(S_range, dtype=float)

    for pos in positions:
        total_profit += pos.profit(S_range)

    max_profit = np.max(total_profit)
    max_loss = np.min(total_profit)

    # Find breakeven points (where profit crosses zero)
    sign_changes = np.where(np.diff(np.sign(total_profit)))[0]
    breakevens = []
    for idx in sign_changes:
        # Linear interpolation to find exact breakeven
        x1, x2 = S_range[idx], S_range[idx + 1]
        y1, y2 = total_profit[idx], total_profit[idx + 1]
        breakeven = x1 - y1 * (x2 - x1) / (y2 - y1)
        breakevens.append(breakeven)

    return {
        "S_range": S_range,
        "total_profit": total_profit,
        "max_profit": max_profit,
        "max_loss": max_loss,
        "breakevens": breakevens,
    }

Let's use this calculator to analyze an iron condor, a popular income-generating strategy:

In[24]:
Code
import numpy as np

# Iron Condor: Sell OTM call spread + Sell OTM put spread
# Profits if stock stays in a range

positions = [
    OptionPosition("short_put", 1, strike=90, premium=2.0),  # Sell 90 put
    OptionPosition(
        "long_put", 1, strike=85, premium=1.0
    ),  # Buy 85 put (protection)
    OptionPosition("short_call", 1, strike=110, premium=2.0),  # Sell 110 call
    OptionPosition(
        "long_call", 1, strike=115, premium=1.0
    ),  # Buy 115 call (protection)
]

S_range = np.linspace(70, 130, 500)
results = analyze_strategy(positions, S_range)

# Calculate initial credit (net premium received)
initial_credit = sum(
    p.premium if "short" in p.position_type else -p.premium for p in positions
)
Out[25]:
Console
Iron Condor Analysis
==================================================
Net credit received: $2.00
Maximum profit: $2.00
Maximum loss: $-3.00
Breakeven points: ['$88.00', '$112.00']
Profit zone: $88.00 to $112.00
Out[26]:
Visualization
Profit diagram with a flat $2 maximum-profit plateau from stock prices $90 to $110, breakeven markers at $88 and $112, and losses capped at $3 beyond $85 and $115.
Iron condor profit at expiration. The strategy earns its maximum $2 net credit when the stock finishes between the short strikes at $90 and $110, breaks even at $88 and $112, and reaches its limited $3 maximum loss beyond the protective long strikes at $85 and $115.

The iron condor collects a net credit of $2.00 (the maximum profit) and risks a maximum loss of $3.00. It demonstrates several key option concepts:

  • Defined risk: Maximum loss is capped by the long options (protection)
  • Income generation: Profits from time decay if the stock stays in the range
  • Probability trade-off: High probability of small profit vs. low probability of larger loss