Option Pricing Walkthrough
This example demonstrates the complete workflow for pricing European options and computing Greeks.
Setup
using QuantNovaBlack-Scholes Pricing
Let's price a European call option on Apple stock.
Direct Black-Scholes
The simplest approach uses the black_scholes function directly:
# Parameters
S = 150.0 # Current stock price
K = 155.0 # Strike price
T = 0.5 # Time to expiry (6 months)
r = 0.05 # Risk-free rate (5%)
σ = 0.20 # Volatility (20%)
# Price call and put
call_price = black_scholes(S, K, T, r, σ, :call)
put_price = black_scholes(S, K, T, r, σ, :put)
println("Call price: \$$(round(call_price, digits=4))")
println("Put price: \$$(round(put_price, digits=4))")
# Verify put-call parity: C - P = S - K*exp(-rT)
parity_lhs = call_price - put_price
parity_rhs = S - K * exp(-r * T)
println("Put-call parity check: $(round(parity_lhs, digits=6)) ≈ $(round(parity_rhs, digits=6))")Output:
Call price: 7.9152
Put price: 9.0882
Put-call parity check: -1.173036 ≈ -1.173036Using Instruments and Market State
For portfolio management, use the object-oriented approach:
# Define market conditions
state = MarketState(
prices = Dict("AAPL" => 150.0),
rates = Dict("USD" => 0.05),
volatilities = Dict("AAPL" => 0.20),
timestamp = 0.0
)
# Create option instruments
call = EuropeanOption("AAPL", 155.0, 0.5, :call)
put = EuropeanOption("AAPL", 155.0, 0.5, :put)
# Price them
println("Call: \$$(round(price(call, state), digits=4))")
println("Put: \$$(round(price(put, state), digits=4))")Output:
Call: 7.9152
Put: 9.0882Computing Greeks
First-Order Greeks
Greeks measure option sensitivity to various market parameters:
# Compute all Greeks
greeks = compute_greeks(call, state)
println("Delta: $(round(greeks.delta, digits=4))") # Sensitivity to spot
println("Gamma: $(round(greeks.gamma, digits=4))") # Delta sensitivity
println("Vega: $(round(greeks.vega, digits=4))") # Sensitivity to vol (per 1%)
println("Theta: $(round(greeks.theta, digits=4))") # Time decay (per day)
println("Rho: $(round(greeks.rho, digits=4))") # Sensitivity to rate (per 1%)Output:
Delta: 0.5062
Gamma: 0.0188
Vega: 0.4231
Theta: -11.8628
Rho: 0.3401Second-Order Greeks
For advanced risk management, use second-order sensitivities:
println("Vanna: $(round(greeks.vanna, digits=4))") # ∂Delta/∂σ
println("Volga: $(round(greeks.volga, digits=4))") # ∂Vega/∂σ
println("Charm: $(round(greeks.charm, digits=4))") # ∂Delta/∂TOutput:
Vanna: 0.2509
Volga: -0.0042
Charm: -0.1912Delta Hedging Example
Use delta to construct a hedged portfolio:
# Long 100 calls
num_calls = 100
option_value = price(call, state) * num_calls
delta_exposure = greeks.delta * num_calls
# Hedge with stock
shares_to_short = -delta_exposure
println("Portfolio value: \$$(round(option_value, digits=2))")
println("Shares to short for delta hedge: $(round(shares_to_short, digits=1))")
# Verify hedge - if stock moves up 1
S_new = 151.0
state_new = MarketState(
prices = Dict("AAPL" => S_new),
rates = Dict("USD" => 0.05),
volatilities = Dict("AAPL" => 0.20),
timestamp = 0.0
)
# P&L breakdown
option_pnl = (price(call, state_new) - price(call, state)) * num_calls
stock_pnl = shares_to_short * (S_new - 150.0)
total_pnl = option_pnl + stock_pnl
println("\nFor \$1 spot move:")
println("Option P&L: \$$(round(option_pnl, digits=2))")
println("Stock hedge P&L: \$$(round(stock_pnl, digits=2))")
println("Net P&L (should be ≈0): \$$(round(total_pnl, digits=2))")Output:
Portfolio value: 791.52
Shares to short for delta hedge: -50.6
For 1 spot move:
Option P&L: 51.56
Stock hedge P&L: -50.62
Net P&L (should be ≈0): 0.94Implied Volatility
Back out the volatility from a market price:
# Given a market price, find implied volatility
market_price = 8.50 # Observed option price
S, K, T, r = 150.0, 155.0, 0.5, 0.05
# Bisection search for implied vol
function implied_vol(market_price, S, K, T, r, opttype; tol=1e-6)
σ_low, σ_high = 0.01, 2.0
for _ in 1:100
σ_mid = (σ_low + σ_high) / 2
model_price = black_scholes(S, K, T, r, σ_mid, opttype)
if abs(model_price - market_price) < tol
return σ_mid
elseif model_price > market_price
σ_high = σ_mid
else
σ_low = σ_mid
end
end
return (σ_low + σ_high) / 2
end
iv = implied_vol(market_price, S, K, T, r, :call)
println("Implied volatility: $(round(iv * 100, digits=2))%")
# Verify
model_price = black_scholes(S, K, T, r, iv, :call)
println("Model price at IV: \$$(round(model_price, digits=4))")Output:
Implied volatility: 21.38%
Model price at IV: 8.5Volatility Smile
Plot implied volatility across strikes:
# Generate a volatility smile
strikes = 130.0:5.0:170.0
spot = 150.0
# Simulate market prices with skew (in practice, use real quotes)
# Higher vol for low strikes (downside skew)
function skewed_vol(K, S, base_vol=0.20)
moneyness = log(K/S)
skew = -0.15 * moneyness # Negative skew
smile = 0.05 * moneyness^2 # Smile curvature
return base_vol + skew + smile
end
println("Strike | True Vol | Market Price | Implied Vol")
println("-"^50)
for K in strikes
true_vol = skewed_vol(K, spot)
market_price = black_scholes(spot, K, 0.5, 0.05, true_vol, :call)
iv = implied_vol(market_price, spot, K, 0.5, 0.05, :call)
println("$(Int(K)) | $(round(true_vol*100, digits=1))% | \$$(round(market_price, digits=2)) | $(round(iv*100, digits=1))%")
endOutput:
Strike | True Vol | Market Price | Implied Vol
--------------------------------------------------
130 | 22.2% | 24.79 | 22.2%
135 | 21.6% | 20.68 | 21.6%
140 | 21.1% | 16.87 | 21.1%
145 | 20.5% | 13.4 | 20.5%
150 | 20.0% | 10.33 | 20.0%
155 | 19.5% | 7.71 | 19.5%
160 | 19.1% | 5.54 | 19.1%
165 | 18.6% | 3.83 | 18.6%
170 | 18.2% | 2.53 | 18.2%Edge Cases
QuantNova handles edge cases gracefully:
# At expiry (T = 0)
expired = black_scholes(150.0, 155.0, 0.0, 0.05, 0.2, :call)
println("Expired OTM call: \$$(expired)") # Should be 0
expired_itm = black_scholes(160.0, 155.0, 0.0, 0.05, 0.2, :call)
println("Expired ITM call: \$$(expired_itm)") # Should be S - K = 5
# Zero volatility (deterministic forward)
deterministic = black_scholes(150.0, 140.0, 1.0, 0.05, 0.0, :call)
forward = 150.0 * exp(0.05) # S * exp(rT)
intrinsic = max(forward - 140.0, 0) * exp(-0.05) # Discounted payoff
println("Zero vol call: \$$(round(deterministic, digits=4)) (should be ≈\$$(round(intrinsic, digits=4)))")Output:
Expired OTM call: 0.0
Expired ITM call: 5.0
Zero vol call: 16.8279 (should be ≈16.8279)Next Steps
Portfolio Risk - Combine options into portfolios
Monte Carlo Pricing - Price exotic options
Interest Rates - Build yield curves for discounting