<< All versions

Skill v1.0.0

currentAutomated scan100/100
leecyno1/newma-desk/options-payoff
──Details
PublishedSeptember 30, 2026 at 01:59 AM
Content Hashsha256:34ebb9889c1ba88b...
Git SHAc33da63a6a38
──Files
Files (1 file, 28.5 KB)
SKILL.md28.5 KBactive
SKILL.md · 862 lines · 28.5 KB

version: "1.0.0" name: options-payoff description: "Option P&L analysis methodology: payoff diagrams, breakeven calculation, multi-leg strategy visualization, and Greeks-based scenario analysis." category: asset-class


Options Payoff — Option P&L Analysis Methodology

Overview

This skill is designed for option strategy analysis scenarios within the Vibe-Trading quantitative framework, covering:

  • P&L curve generation for single-leg and multi-leg option portfolios
  • Black-Scholes pricing and Greeks calculation
  • Implied volatility inversion
  • Strategy selection decision support

Constraint: For research and backtesting only. Do not output live trading instructions, in line with the project's guardrails.


1. Supported Strategy Types

1.1 Single-Leg Strategies

StrategyBiasPremiumMax ProfitMax Loss
Long CallBullishPaidUnlimitedPremium
Long PutBearishPaidStrike - premiumPremium
Short CallNeutral / mildly bearishReceivedPremiumUnlimited
Short PutNeutral / mildly bullishReceivedPremiumStrike - premium

1.2 Vertical Spreads

StrategyStructureMarket ViewNet Premium
Bull Call SpreadLong Call (lower K) + Short Call (higher K)Moderately bullishNet debit
Bear Put SpreadLong Put (higher K) + Short Put (lower K)Moderately bearishNet debit
Bull Put SpreadShort Put (higher K) + Long Put (lower K)Moderately bullishNet credit
Bear Call SpreadShort Call (lower K) + Long Call (higher K)Moderately bearishNet credit

1.3 Straddles / Strangles (Volatility Strategies)

StrategyStructureMarket View
Long StraddleLong Call (ATM) + Long Put (ATM)Large move up or down, low volatility
Short StraddleShort Call (ATM) + Short Put (ATM)Range-bound market, high volatility
Long StrangleLong Call (OTM) + Long Put (OTM)Large move, lower cost than a straddle
Short StrangleShort Call (OTM) + Short Put (OTM)Tight range, collect two-sided premium

1.4 Butterflies / Iron Butterflies

StrategyStructureFeature
Long Butterfly (Call)Long Call (K1) + 2× Short Call (K2) + Long Call (K3)Low-cost bet that the underlying expires near K2
Long Butterfly (Put)Long Put (K3) + 2× Short Put (K2) + Long Put (K1)Same logic, built with puts
Iron ButterflyShort Call (K2) + Short Put (K2) + Long Call (K3) + Long Put (K1)Net credit, max profit at K2

1.5 Condors / Iron Condors

StrategyStructureFeature
Long Condor (Call)Long Call (K1) + Short Call (K2) + Short Call (K3) + Long Call (K4)Bet that the underlying stays between K2 and K3
Iron CondorShort Put (K2) + Long Put (K1) + Short Call (K3) + Long Call (K4)Most common neutral strategy with capped risk on both sides

Here K1 < K2 < K3 < K4, and K2 / K3 are usually OTM.

1.6 Calendar Spreads (Time Spreads)

StrategyStructureMarket View
Calendar SpreadShort near-month Call/Put (K) + Long far-month Call/Put (K)Short-term range-bound market + rising forward volatility
Diagonal SpreadShort near-month Call/Put (K1) + Long far-month Call/Put (K2)Calendar spread with mild directional bias

Calendar spreads profit because near-month Theta decay is faster than far-month Theta decay.

1.7 Ratio Spreads

StrategyStructureFeature
Ratio Call SpreadLong 1× Call (K1) + Short N× Call (K2), N>1Limited upside profit, losses if the upside move becomes extreme
Ratio Put SpreadLong 1× Put (K2) + Short N× Put (K1)Limited downside profit, losses if the downside move becomes extreme
Call Back SpreadShort 1× Call (K1) + Long N× Call (K2), N>1Profits from extreme upside, loses on a modest rally
Put Back SpreadShort 1× Put (K2) + Long N× Put (K1), N>1Profits from extreme downside, loses on a mild decline

1.8 Protective / Hedging Strategies

StrategyStructureUse Case
Covered CallLong underlying + Short Call (K)Generate income on an existing position, give up gains above K
Protective PutLong underlying + Long Put (K)Downside protection on an existing position, pay an insurance premium
CollarLong underlying + Long Put (K1) + Short Call (K2)Lock the position into a zero-cost / low-cost range

2. Black-Scholes Pricing Model

2.1 Core Assumptions

  • The underlying price follows geometric Brownian motion (lognormal distribution)
  • Risk-free rate r is constant
  • Volatility σ is constant (historical or implied)
  • No dividends, or adjust with a continuous dividend yield q
  • European options only (exercise at expiration)

2.2 Full Formula

S = current underlying price
K = strike price
T = time to expiration (years)
r = risk-free rate (annualized continuous compounding)
q = continuous dividend yield (commonly used for China A-share / index options)
σ = annualized volatility
N = standard normal CDF
d1 = [ln(S/K) + (r - q + σ²/2) × T] / (σ × √T)
d2 = d1 - σ × √T
Call = S × e^(-qT) × N(d1) - K × e^(-rT) × N(d2)
Put = K × e^(-rT) × N(-d2) - S × e^(-qT) × N(-d1)

2.3 Put-Call Parity

Call - Put = S × e^(-qT) - K × e^(-rT)

Use this to verify pricing consistency and detect arbitrage. When dividends exist, replace S with S × e^(-qT).

2.4 Greeks Calculation

Delta (Price Sensitivity)

Delta(Call) = e^(-qT) × N(d1)
Delta(Put) = e^(-qT) × (N(d1) - 1)
  • Range: Call [0, 1], Put [-1, 0]
  • ATM ≈ ±0.5, deep ITM → ±1, deep OTM → 0

Gamma (Rate of Change of Delta)

Gamma = e^(-qT) × N'(d1) / (S × σ × √T)
N'(x) = (1/√(2π)) × e^(-x²/2) [standard normal PDF]
  • Calls and puts have the same Gamma
  • Gamma is highest near ATM and explodes as expiration approaches

Theta (Time Decay, per day)

Theta(Call) = [-S × e^(-qT) × N'(d1) × σ / (2√T)
- r × K × e^(-rT) × N(d2)
+ q × S × e^(-qT) × N(d1)] / 365
Theta(Put) = [-S × e^(-qT) × N'(d1) × σ / (2√T)
+ r × K × e^(-rT) × N(-d2)
- q × S × e^(-qT) × N(-d1)] / 365
  • Usually negative for option holders
  • ATM options near expiration have the largest Theta magnitude, which benefits option sellers the most

Vega (Volatility Sensitivity, per 1% vol change)

Vega = S × e^(-qT) × N'(d1) × √T / 100
  • Calls and puts have the same Vega
  • ATM Vega is the largest, and Vega approaches 0 at expiration

Rho (Interest Rate Sensitivity, per 1% rate change)

Rho(Call) = K × T × e^(-rT) × N(d2) / 100
Rho(Put) = -K × T × e^(-rT) × N(-d2) / 100
  • The rate effect is usually small and often negligible for short-dated options

2.5 Implied Volatility Inversion (Newton-Raphson)

Given a market price P_market, solve for σ such that BS(σ) = P_market:

Iteration:
σ_{n+1} = σ_n - [BS(σ_n) - P_market] / Vega(σ_n)
Stopping condition: |BS(σ_n) - P_market| < 1e-6
Initial guess:
σ_0 = √(2π/T) × P_market/S (Brenner-Subrahmanyam approximation)
Notes:
- If Vega is close to 0 (deep OTM / ITM), switch to bisection
- If the iteration does not converge (>100 rounds), return NaN and raise a warning
- IV > 500% is usually an outlier and should be filtered

3. Payoff Diagram Analysis

3.1 Expiry Payoff Curve

Calculation logic:

For each leg i (Call/Put, Long/Short, strike K_i, quantity n_i):
Payoff_i(S_T) = n_i × direction_i × max(0, S_T - K_i) # Call
Payoff_i(S_T) = n_i × direction_i × max(0, K_i - S_T) # Put
Where direction = +1 (Long) / -1 (Short)
Portfolio payoff = Σ Payoff_i - net premium cost
(paid premium is positive, received premium is negative)

X-axis range: [min(K) × 0.7, max(K) × 1.3], step size 0.5 or 1

3.2 Theoretical Value Curve (Current Black-Scholes Pricing)

For each underlying price S, hold T, r, and σ constant and compute current theoretical PnL using the Black-Scholes formula:

TheoValue(S) = Σ n_i × direction_i × BS_price(S, K_i, T, r, σ, type_i) - net premium cost

The gap between the theoretical value curve and the expiry curve equals the remaining time value.

3.3 Break-Even Points

Numerically solve for the roots of Payoff(S_T) = 0:

  • Use scipy.optimize.brentq to solve within adjacent intervals where the sign changes
  • Single-leg strategies:
  • Long Call BEP = K + premium
  • Long Put BEP = K - premium
  • Short Call BEP = K + premium received
  • Short Put BEP = K - premium received
  • Multi-leg strategies: solve numerically, possibly resulting in 0 to 2 BEPs

3.4 Max Profit / Max Loss

python
max_profit = max(payoff_curve) # If inf, label as "Unlimited"
max_loss = min(payoff_curve) # If -inf, label as "Unlimited"
# Corresponding underlying price region
profit_range = S_range[payoff_curve > 0]

3.5 P&L Under Different Volatility Scenarios

Generate a σ scenario matrix using current IV × [0.5, 0.75, 1.0, 1.25, 1.5]. Plot one theoretical value curve for each σ and distinguish them by color to observe Vega sensitivity.


4. Python Code Templates

4.1 Black-Scholes Pricing Functions

python
import numpy as np
from scipy.stats import norm
from scipy.optimize import brentq
from typing import Literal
def bs_price(
S: float,
K: float,
T: float,
r: float,
sigma: float,
option_type: Literal["call", "put"],
q: float = 0.0,
) -> float:
"""Black-Scholes option pricing.
Args:
S: Current underlying price
K: Strike price
T: Time to expiration in years
r: Risk-free rate in annualized continuous compounding, e.g. 0.03
sigma: Annualized volatility, e.g. 0.20
option_type: "call" or "put"
q: Continuous dividend yield, defaults to 0
Returns:
Theoretical option price
Raises:
ValueError: If sigma <= 0
"""
if T <= 0:
# After expiration, return intrinsic value directly.
if option_type == "call":
return max(0.0, S - K)
return max(0.0, K - S)
if sigma <= 0:
raise ValueError(f"sigma must be > 0, got {sigma}")
d1 = (np.log(S / K) + (r - q + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if option_type == "call":
price = S * np.exp(-q * T) * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
else:
price = K * np.exp(-r * T) * norm.cdf(-d2) - S * np.exp(-q * T) * norm.cdf(-d1)
return float(price)
def bs_greeks(
S: float,
K: float,
T: float,
r: float,
sigma: float,
option_type: Literal["call", "put"],
q: float = 0.0,
) -> dict:
"""Calculate the five major Greeks under the Black-Scholes model.
Returns:
A dict with keys: delta, gamma, theta, vega, rho.
Theta and Vega are already converted to per-day and per-1% units.
"""
if T <= 1e-6:
return {"delta": 0.0, "gamma": 0.0, "theta": 0.0, "vega": 0.0, "rho": 0.0}
d1 = (np.log(S / K) + (r - q + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
n_prime_d1 = norm.pdf(d1)
exp_qt = np.exp(-q * T)
exp_rt = np.exp(-r * T)
if option_type == "call":
delta = exp_qt * norm.cdf(d1)
rho = K * T * exp_rt * norm.cdf(d2) / 100
theta = (
-S * exp_qt * n_prime_d1 * sigma / (2 * np.sqrt(T))
- r * K * exp_rt * norm.cdf(d2)
+ q * S * exp_qt * norm.cdf(d1)
) / 365
else:
delta = exp_qt * (norm.cdf(d1) - 1)
rho = -K * T * exp_rt * norm.cdf(-d2) / 100
theta = (
-S * exp_qt * n_prime_d1 * sigma / (2 * np.sqrt(T))
+ r * K * exp_rt * norm.cdf(-d2)
- q * S * exp_qt * norm.cdf(-d1)
) / 365
gamma = exp_qt * n_prime_d1 / (S * sigma * np.sqrt(T))
vega = S * exp_qt * n_prime_d1 * np.sqrt(T) / 100
return {
"delta": round(delta, 6),
"gamma": round(gamma, 6),
"theta": round(theta, 6),
"vega": round(vega, 6),
"rho": round(rho, 6),
}
def implied_volatility(
market_price: float,
S: float,
K: float,
T: float,
r: float,
option_type: Literal["call", "put"],
q: float = 0.0,
tol: float = 1e-6,
max_iter: int = 200,
) -> float:
"""Solve implied volatility with Newton-Raphson.
Args:
market_price: Observed market price
tol: Convergence tolerance
max_iter: Maximum number of iterations
Returns:
Annualized implied volatility. Returns np.nan on failure.
Raises:
ValueError: If the market price is below intrinsic value
"""
# Check intrinsic value first.
intrinsic = max(0.0, S - K if option_type == "call" else K - S)
if market_price < intrinsic - 1e-6:
raise ValueError(f"Market price {market_price} is below intrinsic value {intrinsic}")
# Brenner-Subrahmanyam initial approximation.
sigma = np.sqrt(2 * np.pi / T) * market_price / S
sigma = max(0.001, min(sigma, 5.0))
for _ in range(max_iter):
price = bs_price(S, K, T, r, sigma, option_type, q)
vega = bs_greeks(S, K, T, r, sigma, option_type, q)["vega"] * 100 # restore per-1.0 unit
diff = price - market_price
if abs(diff) < tol:
return round(sigma, 6)
if abs(vega) < 1e-10:
# Vega is near zero, fall back to bisection.
try:
return float(brentq(
lambda v: bs_price(S, K, T, r, v, option_type, q) - market_price,
1e-4, 10.0, xtol=tol, maxiter=200
))
except ValueError:
return np.nan
sigma -= diff / vega
sigma = max(1e-4, min(sigma, 10.0)) # clamp to a reasonable range
return np.nan # did not converge

4.2 Multi-Leg Portfolio Payoff Calculation

python
from dataclasses import dataclass
import numpy as np
@dataclass
class OptionLeg:
"""Single option leg definition.
Attributes:
option_type: "call" or "put"
K: Strike price
direction: +1 for Long / -1 for Short
quantity: Number of contracts, defaults to 1
premium: Actual traded premium, positive when paid and negative when received
T: Time to expiration in years, used for theoretical Black-Scholes pricing
sigma: Volatility used in pricing
"""
option_type: Literal["call", "put"]
K: float
direction: int # +1 or -1
quantity: float = 1.0
premium: float = 0.0
T: float = 0.25
sigma: float = 0.20
def compute_expiry_payoff(
legs: list[OptionLeg],
S_range: np.ndarray,
) -> np.ndarray:
"""Calculate the expiry payoff curve.
Args:
legs: Option legs
S_range: Array of underlying prices
Returns:
Payoff array aligned with S_range, including premium cost
"""
total_payoff = np.zeros(len(S_range))
net_premium = sum(leg.direction * leg.quantity * leg.premium for leg in legs)
for leg in legs:
if leg.option_type == "call":
intrinsic = np.maximum(S_range - leg.K, 0)
else:
intrinsic = np.maximum(leg.K - S_range, 0)
total_payoff += leg.direction * leg.quantity * intrinsic
return total_payoff - net_premium
def compute_theo_value(
legs: list[OptionLeg],
S_range: np.ndarray,
r: float = 0.03,
q: float = 0.0,
) -> np.ndarray:
"""Calculate the theoretical value curve under current Black-Scholes pricing.
Args:
legs: Option legs, each carrying T and sigma
S_range: Array of underlying prices
r: Risk-free rate
q: Continuous dividend yield
Returns:
Theoretical PnL array
"""
total_value = np.zeros(len(S_range))
net_premium = sum(leg.direction * leg.quantity * leg.premium for leg in legs)
for leg in legs:
prices = np.array([
bs_price(S, leg.K, leg.T, r, leg.sigma, leg.option_type, q)
for S in S_range
])
total_value += leg.direction * leg.quantity * prices
return total_value - net_premium
def find_breakeven_points(
S_range: np.ndarray,
payoff: np.ndarray,
) -> list[float]:
"""Solve for break-even points numerically.
Returns:
A list of break-even points, from 0 to many depending on the structure
"""
beps = []
for i in range(len(S_range) - 1):
if payoff[i] * payoff[i + 1] < 0:
bep = brentq(
lambda s: np.interp(s, S_range, payoff),
S_range[i], S_range[i + 1],
xtol=0.01
)
beps.append(round(bep, 2))
return beps

4.3 Matplotlib Payoff Diagram

python
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
def plot_payoff_diagram(
legs: list[OptionLeg],
S_current: float,
r: float = 0.03,
q: float = 0.0,
title: str = "Option Payoff Diagram",
figsize: tuple = (10, 6),
) -> plt.Figure:
"""Plot the payoff diagram for an option portfolio.
Args:
legs: Option legs
S_current: Current underlying price
r: Risk-free rate
q: Continuous dividend yield
title: Chart title
figsize: Figure size
Returns:
A matplotlib Figure object
"""
K_values = [leg.K for leg in legs]
S_lo = min(K_values) * 0.70
S_hi = max(K_values) * 1.30
S_range = np.linspace(S_lo, S_hi, 500)
expiry_pnl = compute_expiry_payoff(legs, S_range)
theo_pnl = compute_theo_value(legs, S_range, r, q)
beps = find_breakeven_points(S_range, expiry_pnl)
fig, ax = plt.subplots(figsize=figsize)
# Shade profit and loss regions.
ax.fill_between(S_range, expiry_pnl, 0,
where=(expiry_pnl >= 0), alpha=0.15, color="green", label="_nolegend_")
ax.fill_between(S_range, expiry_pnl, 0,
where=(expiry_pnl < 0), alpha=0.15, color="red", label="_nolegend_")
# Expiry payoff curve.
ax.plot(S_range, expiry_pnl, color="steelblue", linewidth=2.0, label="Expiry P&L")
# Theoretical value curve.
ax.plot(S_range, theo_pnl, color="darkorange", linewidth=1.5,
linestyle="--", label="Current theoretical value")
# Zero axis.
ax.axhline(0, color="black", linewidth=0.8, linestyle="-")
# Current price line.
ax.axvline(S_current, color="gray", linewidth=1.0, linestyle=":",
label=f"Spot {S_current:.2f}")
# Strike annotations.
for K in K_values:
ax.axvline(K, color="purple", linewidth=0.6, linestyle="--", alpha=0.5)
ax.text(K, ax.get_ylim()[0], f"K={K}", fontsize=8,
rotation=90, va="bottom", color="purple")
# Break-even points.
for bep in beps:
ax.scatter([bep], [0], color="red", zorder=5, s=50)
ax.annotate(f"BEP\n{bep:.2f}", xy=(bep, 0),
xytext=(bep, max(expiry_pnl) * 0.15),
fontsize=8, ha="center", color="red",
arrowprops=dict(arrowstyle="->", color="red", lw=0.8))
# Max profit / max loss summary.
max_p = max(expiry_pnl)
max_l = min(expiry_pnl)
stats_text = (
f"Max profit: {'Unlimited' if max_p > 1e6 else f'{max_p:.2f}'}\n"
f"Max loss: {'Unlimited' if max_l < -1e6 else f'{max_l:.2f}'}\n"
f"Break-even: {', '.join([str(b) for b in beps]) if beps else 'None'}"
)
ax.text(0.02, 0.97, stats_text, transform=ax.transAxes,
fontsize=9, va="top", bbox=dict(boxstyle="round", fc="white", alpha=0.8))
ax.set_xlabel("Underlying price")
ax.set_ylabel("P&L")
ax.set_title(title)
ax.legend(loc="upper right")
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f"{x:,.0f}"))
ax.grid(True, alpha=0.3)
plt.tight_layout()
return fig

4.4 Plotly Interactive Payoff Diagram (Recommended for Frontend Display)

python
import plotly.graph_objects as go
def plot_payoff_plotly(
legs: list[OptionLeg],
S_current: float,
r: float = 0.03,
q: float = 0.0,
title: str = "Option Payoff Diagram",
sigma_scenarios: list[float] | None = None,
) -> go.Figure:
"""Generate a Plotly interactive payoff diagram with optional multi-sigma scenarios.
Args:
sigma_scenarios: For example [0.10, 0.15, 0.20, 0.25, 0.30].
If None, use each leg's own sigma.
"""
K_values = [leg.K for leg in legs]
S_range = np.linspace(min(K_values) * 0.70, max(K_values) * 1.30, 500)
expiry_pnl = compute_expiry_payoff(legs, S_range)
fig = go.Figure()
# Expiry payoff.
fig.add_trace(go.Scatter(
x=S_range, y=expiry_pnl,
name="Expiry P&L", line=dict(color="steelblue", width=2),
fill="tozeroy",
fillcolor="rgba(70,130,180,0.1)",
))
# Theoretical value under multiple volatility scenarios.
if sigma_scenarios:
colors = ["#FF6B6B", "#FFA500", "#4CAF50", "#2196F3", "#9C27B0"]
for i, sigma in enumerate(sigma_scenarios):
scenario_legs = [
OptionLeg(
option_type=leg.option_type, K=leg.K,
direction=leg.direction, quantity=leg.quantity,
premium=leg.premium, T=leg.T, sigma=sigma
)
for leg in legs
]
theo = compute_theo_value(scenario_legs, S_range, r, q)
fig.add_trace(go.Scatter(
x=S_range, y=theo,
name=f"IV={sigma*100:.0f}%",
line=dict(color=colors[i % len(colors)], width=1.5, dash="dash"),
))
else:
theo_pnl = compute_theo_value(legs, S_range, r, q)
fig.add_trace(go.Scatter(
x=S_range, y=theo_pnl,
name="Current theoretical value",
line=dict(color="darkorange", width=1.5, dash="dash"),
))
# Zero line and current price line.
fig.add_hline(y=0, line_dash="solid", line_color="black", line_width=0.8)
fig.add_vline(x=S_current, line_dash="dot", line_color="gray",
annotation_text=f"Spot {S_current:.2f}", annotation_position="top right")
# Strikes.
for K in set(K_values):
fig.add_vline(x=K, line_dash="dash", line_color="purple",
line_width=0.8, opacity=0.5)
fig.update_layout(
title=title,
xaxis_title="Underlying price",
yaxis_title="P&L",
hovermode="x unified",
template="plotly_white",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
return fig

4.5 Greeks Profile vs Underlying Price

python
def plot_greeks_profile(
legs: list[OptionLeg],
S_current: float,
r: float = 0.03,
q: float = 0.0,
greeks_to_plot: list[str] | None = None,
) -> go.Figure:
"""Plot portfolio Greeks as functions of the underlying price.
Args:
greeks_to_plot: Defaults to ["delta", "gamma", "vega", "theta"]
"""
if greeks_to_plot is None:
greeks_to_plot = ["delta", "gamma", "vega", "theta"]
K_values = [leg.K for leg in legs]
S_range = np.linspace(min(K_values) * 0.70, max(K_values) * 1.30, 300)
# Compute portfolio Greeks.
greek_values = {g: np.zeros(len(S_range)) for g in greeks_to_plot}
for leg in legs:
for j, S in enumerate(S_range):
g = bs_greeks(S, leg.K, leg.T, r, leg.sigma, leg.option_type, q)
for name in greeks_to_plot:
greek_values[name][j] += leg.direction * leg.quantity * g[name]
# Plot subplots.
from plotly.subplots import make_subplots
n = len(greeks_to_plot)
fig = make_subplots(rows=n, cols=1, shared_xaxes=True,
subplot_titles=[g.capitalize() for g in greeks_to_plot])
greek_colors = {"delta": "steelblue", "gamma": "green",
"theta": "red", "vega": "darkorange", "rho": "purple"}
for i, name in enumerate(greeks_to_plot, start=1):
fig.add_trace(
go.Scatter(x=S_range, y=greek_values[name],
name=name.capitalize(),
line=dict(color=greek_colors.get(name, "gray"), width=2)),
row=i, col=1
)
fig.add_hline(y=0, line_dash="dot", line_color="black",
line_width=0.5, row=i, col=1)
fig.add_vline(x=S_current, line_dash="dash", line_color="gray",
line_width=0.8, row=i, col=1)
fig.update_layout(
title="Greeks Profile",
height=200 * n,
showlegend=False,
template="plotly_white",
)
return fig

5. Practical Usage

5.1 Strategy Selection Decision Tree by Market View

Market view
├── Strongly bullish
│ ├── Willing to pay premium → Long Call
│ └── Want lower cost → Bull Call Spread
├── Moderately bullish
│ ├── Already hold the underlying → Covered Call (income enhancement)
│ └── No existing position → Bull Put Spread (net credit)
├── Moderately bearish
│ ├── Already hold the underlying → Protective Put or Collar
│ └── No existing position → Bear Call Spread (net credit)
├── Strongly bearish
│ ├── Willing to pay premium → Long Put
│ └── Want lower cost → Bear Put Spread
├── Range-bound market (low-IV environment)
│ ├── Wide range → Short Strangle
│ ├── Narrow range → Short Straddle
│ └── Want limited risk → Iron Condor / Iron Butterfly
└── Large move expected (low-IV environment)
├── Direction unclear → Long Straddle / Long Strangle
└── Slight directional bias → Call / Put Back Spread

5.2 Volatility Environment → Strategy Mapping

IV RegimeRule of ThumbSuitable StrategiesStrategies to Avoid
Low IV (< 20th percentile)IV Rank < 20Long Straddle, Long Strangle, Back SpreadShort strategies, because premium is too thin
Normal IV (20th to 80th percentile)IV Rank 20 to 80Vertical spreads, Calendar Spread, DiagonalSingle-leg positions with asymmetric risk
High IV (> 80th percentile)IV Rank > 80Short Straddle, Iron Condor, Covered CallLong single-leg options due to rich premium

IV Rank formula:

python
iv_rank = (current_iv - iv_52w_low) / (iv_52w_high - iv_52w_low) * 100

IV Percentile: The historical percentile rank of current IV over the last 252 trading days.

5.3 When to Roll or Adjust

Rolling

  • Trigger: Option Delta moves outside the target range, or time to expiration < 21 days
  • Rolling Up / Down: Close the current leg and reopen at a higher / lower strike while keeping the same directional bias
  • Rolling Out: Close the near-month leg and reopen further out on the curve to harvest additional time value
  • Cost assessment: Compare the net debit / credit of the roll with the payoff from simply holding to expiration

Adjusting

  • Delta-neutral rebalancing: Hedge with underlying or options when portfolio Delta deviates from target by more than ±0.10
  • Gamma scalping: Under a Long Gamma portfolio, hedge Delta after large underlying moves to lock in gains
  • Stop-loss rule: Force liquidation when losses reach 2× the initial premium received, a common rule for Iron Condors

Common Adjustment Examples

Iron Condor gets breached:

Underlying rallies above the short call:
1. Close the call spread and realize the loss
2. Reassess directional view:
- Still bullish → reopen a higher put spread to preserve neutrality
- Not bullish → close the entire portfolio

Covered Call faces assignment risk:

Underlying approaches the call strike:
1. Assess whether you are willing to sell the underlying at that price
- Yes → allow assignment and keep premium + capital gain
- No → Roll Up & Out to a higher strike and/or later expiration

Quick Usage Example

python
# Example: Iron Condor payoff diagram
legs = [
OptionLeg("put", K=90, direction=-1, premium=1.5, T=0.083, sigma=0.20),
OptionLeg("put", K=85, direction=+1, premium=0.5, T=0.083, sigma=0.20),
OptionLeg("call", K=110, direction=-1, premium=1.5, T=0.083, sigma=0.20),
OptionLeg("call", K=115, direction=+1, premium=0.5, T=0.083, sigma=0.20),
]
fig = plot_payoff_plotly(
legs, S_current=100.0,
title="Iron Condor (85/90/110/115, 1 month)",
sigma_scenarios=[0.15, 0.20, 0.25, 0.30],
)
fig.show()
# Implied volatility example
iv = implied_volatility(
market_price=5.0, S=100, K=100,
T=0.25, r=0.03, option_type="call"
)
print(f"Implied volatility: {iv:.2%}") # about 0.20
All versions