option-pricing¶
modelingprivate (curator-owned)formal-modelingCurator-private skill — copy text from 100xOS/shared/skills/modeling/option-pricing.md.
Option Pricing¶
Overview¶
Option pricing theory values contingent claims — contracts whose payoff depends on the future value of an underlying asset. The fundamental insight is that options can be priced by constructing replicating portfolios (no-arbitrage) or by computing expected payoffs under the risk-neutral measure.
Foundations¶
Option Basics¶
Call option: Right to buy the underlying at strike K at/before expiration T. - Payoff at expiration: max(S_T - K, 0)
Put option: Right to sell the underlying at strike K at/before expiration T. - Payoff at expiration: max(K - S_T, 0)
European: Exercisable only at expiration. American: Exercisable at any time up to expiration.
Moneyness: S/K ratio. - In-the-money (ITM): S > K for calls, S < K for puts. - At-the-money (ATM): S ~ K. - Out-of-the-money (OTM): S < K for calls, S > K for puts.
Put-Call Parity¶
For European options on a non-dividend-paying stock:
C - P = S - K * exp(-rT)
This is a no-arbitrage relationship. Violations indicate mispricing, transaction costs, or early exercise premium (American options).
Risk-Neutral Pricing¶
Under no-arbitrage, there exists a risk-neutral probability measure Q such that:
V_0 = exp(-rT) * E^Q[Payoff(S_T)]
The underlying grows at the risk-free rate under Q: E^Q[S_T] = S_0 * exp(rT)
This does NOT mean investors are risk-neutral — it is a mathematical equivalence derived from no-arbitrage.
Black-Scholes-Merton Model¶
Assumptions¶
- Geometric Brownian Motion: dS/S = mudt + sigmadW
- Constant volatility sigma
- Constant risk-free rate r
- No dividends (or continuous dividend yield q)
- No transaction costs or taxes
- Continuous trading
- No arbitrage opportunities
Black-Scholes Formula¶
Call: C = S * N(d1) - K * exp(-rT) * N(d2) Put: P = K * exp(-rT) * N(-d2) - S * N(-d1)
where: - d1 = [ln(S/K) + (r + sigma^2/2) * T] / (sigma * sqrt(T)) - d2 = d1 - sigma * sqrt(T) - N(.) = standard normal CDF
With continuous dividend yield q: - Replace S with S * exp(-qT) everywhere. - d1 = [ln(S/K) + (r - q + sigma^2/2) * T] / (sigma * sqrt(T))
import numpy as np
from scipy.stats import norm
def bs_price(S, K, T, r, sigma, q=0, option_type='call'):
"""Black-Scholes European option price."""
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':
return S * np.exp(-q * T) * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
else:
return K * np.exp(-r * T) * norm.cdf(-d2) - S * np.exp(-q * T) * norm.cdf(-d1)
The Greeks¶
Sensitivities of option price to model inputs.
| Greek | Definition | Call | Put |
|---|---|---|---|
| Delta | dV/dS | N(d1) | N(d1) - 1 |
| Gamma | d^2V/dS^2 | n(d1) / (Ssigmasqrt(T)) | same |
| Theta | dV/dT | -(Ssigman(d1))/(2sqrt(T)) - rKexp(-rT)N(d2) | ... + rKexp(-rT)*N(-d2) |
| Vega | dV/dsigma | S * sqrt(T) * n(d1) | same |
| Rho | dV/dr | KTexp(-rT)*N(d2) | -KTexp(-rT)*N(-d2) |
where n(.) = standard normal PDF.
def bs_greeks(S, K, T, r, sigma, q=0):
"""Compute all Greeks for a European call."""
d1 = (np.log(S / K) + (r - q + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
delta = np.exp(-q * T) * norm.cdf(d1)
gamma = np.exp(-q * T) * norm.pdf(d1) / (S * sigma * np.sqrt(T))
vega = S * np.exp(-q * T) * np.sqrt(T) * norm.pdf(d1)
theta = (-(S * sigma * np.exp(-q * T) * norm.pdf(d1)) / (2 * np.sqrt(T))
- r * K * np.exp(-r * T) * norm.cdf(d2)
+ q * S * np.exp(-q * T) * norm.cdf(d1))
rho = K * T * np.exp(-r * T) * norm.cdf(d2)
return {'delta': delta, 'gamma': gamma, 'vega': vega, 'theta': theta, 'rho': rho}
Delta Hedging¶
- A delta-neutral portfolio: hold the option and short Delta shares.
- Rebalance as Delta changes (Gamma measures how fast Delta moves).
- Hedging error arises from discrete rebalancing, transaction costs, and vol misspecification.
Implied Volatility¶
The volatility sigma_imp that, when plugged into Black-Scholes, reproduces the observed market price. Found by numerical inversion (Newton-Raphson or bisection).
from scipy.optimize import brentq
def implied_vol(market_price, S, K, T, r, q=0, option_type='call'):
"""Compute implied volatility via root-finding."""
def objective(sigma):
return bs_price(S, K, T, r, sigma, q, option_type) - market_price
return brentq(objective, 1e-6, 5.0)
Volatility Surface¶
IV varies across strike (K) and maturity (T):
Volatility smile/skew: IV is higher for OTM puts and ITM calls (especially in equity indices). Reflects crash risk / fat tails not captured by BS.
Term structure: IV varies with maturity. Short-term IV reacts more to current events; long-term IV is more stable.
The surface: IV(K, T) is a 2D object. Interpolation methods: SVI (Stochastic Volatility Inspired) parameterization, SABR model, or spline interpolation.
Binomial Tree (Cox-Ross-Rubinstein)¶
Discrete-time model. The stock moves up by factor u or down by factor d each period.
u = exp(sigma * sqrt(dt)) d = 1/u p = (exp((r-q)*dt) - d) / (u - d) # risk-neutral probability
European Options¶
- Build tree forward: compute stock prices at each node.
- Work backward: at expiration, option value = payoff. At each prior node, discount expected value.
American Options¶
- Same backward induction, but at each node compare continuation value with immediate exercise value.
- Take the maximum: V = max(payoff_if_exercise, discounted_expected_continuation)
def binomial_tree(S, K, T, r, sigma, q, N, option_type='call', american=False):
"""CRR binomial tree for European or American options."""
dt = T / N
u = np.exp(sigma * np.sqrt(dt))
d = 1 / u
p = (np.exp((r - q) * dt) - d) / (u - d)
disc = np.exp(-r * dt)
# Terminal stock prices
ST = S * u ** np.arange(N, -1, -1) * d ** np.arange(0, N + 1)
# Terminal option values
if option_type == 'call':
V = np.maximum(ST - K, 0)
else:
V = np.maximum(K - ST, 0)
# Backward induction
for i in range(N - 1, -1, -1):
V = disc * (p * V[:-1] + (1 - p) * V[1:])
if american:
Si = S * u ** np.arange(i, -1, -1) * d ** np.arange(0, i + 1)
if option_type == 'call':
V = np.maximum(V, Si - K)
else:
V = np.maximum(V, K - Si)
return V[0]
Monte Carlo Option Pricing¶
Simulate paths of the underlying under Q, compute payoffs, and average.
Geometric Brownian Motion Simulation¶
S_T = S_0 * exp((r - q - sigma^2/2)T + sigmasqrt(T)*Z), Z ~ N(0,1)
def monte_carlo_european(S, K, T, r, sigma, q, n_paths=100000, option_type='call'):
"""Monte Carlo pricing for European options."""
Z = np.random.standard_normal(n_paths)
ST = S * np.exp((r - q - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * Z)
if option_type == 'call':
payoffs = np.maximum(ST - K, 0)
else:
payoffs = np.maximum(K - ST, 0)
price = np.exp(-r * T) * payoffs.mean()
se = np.exp(-r * T) * payoffs.std() / np.sqrt(n_paths)
return price, se
Variance Reduction¶
- Antithetic variates: Use both Z and -Z. Halves variance for monotonic payoffs.
- Control variates: Use a correlated variable with known expectation (e.g., the stock itself, or a simpler option with closed-form price).
- Importance sampling: Shift the distribution to sample more from regions that matter for the payoff.
Path-Dependent Options¶
Monte Carlo is the natural method for path-dependent exotics: - Asian options (average price) - Barrier options (knock-in/knock-out) - Lookback options (max/min price)
For path simulation, discretize: S_{t+dt} = S_t * exp((r-q-sigma^2/2)dt + sigmasqrt(dt)*Z_t)
Stochastic Volatility Models¶
Heston Model (1993)¶
dS_t = (r - q) * S_t * dt + sqrt(V_t) * S_t * dW_1 dV_t = kappa * (theta - V_t) * dt + xi * sqrt(V_t) * dW_2 Corr(dW_1, dW_2) = rho
Parameters: - V_0: initial variance - theta: long-run variance - kappa: mean reversion speed - xi: vol of vol - rho: correlation between stock and variance shocks (typically rho < 0 for equities — leverage effect)
The Heston model has a semi-closed-form solution via characteristic functions, making calibration feasible.
SABR Model¶
Popular in interest rate derivatives: dF = sigma * F^beta * dW_1 dsigma = alpha * sigma * dW_2 Corr(dW_1, dW_2) = rho
- beta: controls backbone (beta=1 lognormal, beta=0 normal)
- alpha: vol of vol
- rho: correlation
Hagan et al. (2002) provide an approximate implied vol formula, widely used for interpolation.
Jump-Diffusion Models¶
Merton (1976)¶
dS/S = (r - q - lambdak)dt + sigmadW + JdN
- dN: Poisson process with intensity lambda (average number of jumps per year).
- J: jump size, typically log(1+J) ~ N(mu_J, sigma_J^2).
- k = E[J] = exp(mu_J + sigma_J^2/2) - 1.
Option price is an infinite sum of BS prices weighted by Poisson probabilities:
C = Sum_{n=0}^{inf} (exp(-lambda'T) * (lambda'T)^n / n!) * BS(S, K, T, r_n, sigma_n)
where lambda' = lambda(1+k), r_n = r - lambdak + nmu_J/T, sigma_n^2 = sigma^2 + nsigma_J^2/T.
In practice, truncate at n=20-50.
Calibration¶
Fitting to Market Data¶
- Collect market option prices (or implied vols) across strikes and maturities.
- Define objective: minimize sum of squared differences between model and market prices (or implied vols).
- Use optimization (Levenberg-Marquardt, differential evolution, or basin-hopping for global search).
- Check fit quality: root mean squared error, examine residuals across the surface.
Heston Calibration Example¶
from scipy.optimize import differential_evolution
def heston_objective(params, market_data):
"""Sum of squared implied vol errors."""
v0, theta, kappa, xi, rho = params
total_error = 0
for K, T, market_iv in market_data:
model_price = heston_call_price(S, K, T, r, v0, theta, kappa, xi, rho)
model_iv = implied_vol(model_price, S, K, T, r)
total_error += (model_iv - market_iv) ** 2
return total_error
bounds = [(0.001, 1), (0.001, 1), (0.1, 10), (0.01, 2), (-0.99, 0.99)]
result = differential_evolution(heston_objective, bounds, args=(market_data,))
Practical Checklist¶
- For European options on liquid underlyings: start with Black-Scholes.
- Compute implied volatilities. If the surface shows significant smile/skew, BS is insufficient for pricing but useful as a quoting convention.
- For American options: use binomial trees (or finite differences). BS does not apply directly.
- For path-dependent exotics: use Monte Carlo with variance reduction.
- For calibration to market data: Heston or SABR depending on asset class (equity vs rates).
- Always check put-call parity as a sanity check.
- Use Greeks for hedging and risk management. Delta-hedge and monitor Gamma exposure.
- In empirical work: implied vol is often the object of study (not the option price itself).
- Report moneyness (K/S or delta), not just strike, for comparability across firms/dates.
- For academic papers: cite the model clearly, state all assumptions, and discuss their violations in your setting.
Key References¶
- Black, F. and Scholes, M. (1973). The pricing of options and corporate liabilities. Journal of Political Economy.
- Merton, R.C. (1973). Theory of rational option pricing. Bell Journal of Economics.
- Cox, J.C., Ross, S.A., and Rubinstein, M. (1979). Option pricing: A simplified approach. Journal of Financial Economics.
- Heston, S.L. (1993). A closed-form solution for options with stochastic volatility. Review of Financial Studies.
- Merton, R.C. (1976). Option pricing when underlying stock returns are discontinuous. Journal of Financial Economics.
- Hull, J.C. (2021). Options, Futures, and Other Derivatives, 11th ed. Pearson.
- Gatheral, J. (2006). The Volatility Surface: A Practitioner's Guide. Wiley.