Modeling Options commodities with Black 76 and QuantLib
Mathematics and Assumptions
- Use the Black-Scholes equation to price commodity futures options.
-
We will not model commodities as the same as equity options because the prices do not fluctuate as much. Therefore, the commodiities will not be in a stochastic process.
- Futures prices are log-normally distributed.
- All options will be exercised at maturity.
- Volatility is not constant (depends on time to maturity)
Use a stochastic process to model the time, t, and Ft:
Valuing a Call option
Valuing a Put option
Processes
Walkthrough example
Let’s assume that we want to price a 3000 copper future on a call option. The date till maturity (T) is 31 days with a volatility of 35 or 0.35 and annual interest of 0.005. The strike price (X) is 3000 while the futures price is 2900.
Let’s solve for D1 to fnd D2 first.
where F = 2900 and X = 3000 The logarithm base e for F/X or futures price over strike price is: -0.03390….
Sigma squared with respect to T/2 is: 0.0052019625 volaitlity is 0.35 multiplied to interest rate and time to maturity 31/365 = 0.08493
solve for T gives us: 0.1019996
The result for D1 is:
Solving for D2:
through cumulative normal distribution, we get (d1) = 0.38922098 (d2) = 0.35073028
Terminal price = (2900 * 0.38922098) - (3000 * 0.308547) = 203.099842
Let’s assume discount factor of 0.99
Calculate priceof option 0.99 * 203.099842 = 201.10
QuantLib Implementation
Instantiate the global data parameters and nacessary library packages.
import QuantLib as ql
import pandas as pd
import math
# Global Data
calendar = ql.UnitedStates()
businessConvention = ql.ModifiedFollowing
settlementDays = 0
daysCount = ql.ActualActual(ql.ActualActual.Bond)
Treasury Notes Futures Contract (2-Year Note)
Option on Treasury Futures Contracts. For these examples, we will assume the same volatility, strike, and spot price to showcase the differentiation of the option greeks.
2-Year note yields.
# 2-Year Note
interestRate = 0.003
calcDate = ql.Date(1,12,2023)
yieldCurve = ql.FlatForward(calcDate, interestRate, daysCount, ql.Compounded, ql.Continuous)
ql.Settings.instance().evaluationDate = calcDate
optionMaturityDate = ql.Date(24,12,2025)
strike = 100
spot = 125 # spot price is the futures price
volatility = 20/100.
optionType = ql.Option.Call
discount = yieldCurve.discount(optionMaturityDate)
strikepayoff = ql.PlainVanillaPayoff(optionType, strike)
T = yieldCurve.dayCounter().yearFraction(calcDate, optionMaturityDate)
stddev = volatility*math.sqrt(T)
black = ql.BlackCalculator(strikepayoff, spot, stddev, discount)
print("%-20s: %4.4f" %("Option Price for Treasury Futures Contract (2-Year Note)", black.value() ))
print("%-20s: %4.4f" %("Delta", black.delta(spot)))
print("%-20s: %4.4f" %("Gamma", black.gamma(spot)))
print("%-20s: %4.4f" %("Theta", black.theta(spot, T)))
print("%-20s: %4.4f" %("Vega", black.vega(T)))
print("%-20s: %4.4f" %("Rho", black.rho( T)))
3-Year Note yields.
# 3-Year Note
interestRate = 0.003
calcDate = ql.Date(1,12,2023)
yieldCurve = ql.FlatForward(calcDate, interestRate, daysCount, ql.Compounded, ql.Continuous)
ql.Settings.instance().evaluationDate = calcDate
optionMaturityDate = ql.Date(24,12,2026)
strike = 100
spot = 125 # spot price is the futures price
volatility = 20/100.
optionType = ql.Option.Call
discount = yieldCurve.discount(optionMaturityDate)
strikepayoff = ql.PlainVanillaPayoff(optionType, strike)
T = yieldCurve.dayCounter().yearFraction(calcDate, optionMaturityDate)
stddev = volatility*math.sqrt(T)
black = ql.BlackCalculator(strikepayoff, spot, stddev, discount)
print("%-20s: %4.4f" %("Option Price for Treasury Futures Contract (3-Year Note)", black.value() ))
print("%-20s: %4.4f" %("Delta", black.delta(spot)))
print("%-20s: %4.4f" %("Gamma", black.gamma(spot)))
print("%-20s: %4.4f" %("Theta", black.theta(spot, T)))
print("%-20s: %4.4f" %("Vega", black.vega(T)))
print("%-20s: %4.4f" %("Rho", black.rho( T)))
5-Year Note yields.
# 5-Year Note
interestRate = 0.003
calcDate = ql.Date(1,12,2023)
yieldCurve = ql.FlatForward(calcDate, interestRate, daysCount, ql.Compounded, ql.Continuous)
ql.Settings.instance().evaluationDate = calcDate
optionMaturityDate = ql.Date(24,12,2028)
strike = 100
spot = 125 # spot price is the futures price
volatility = 20/100.
optionType = ql.Option.Call
discount = yieldCurve.discount(optionMaturityDate)
strikepayoff = ql.PlainVanillaPayoff(optionType, strike)
T = yieldCurve.dayCounter().yearFraction(calcDate, optionMaturityDate)
stddev = volatility*math.sqrt(T)
black = ql.BlackCalculator(strikepayoff, spot, stddev, discount)
print("%-20s: %4.4f" %("Option Price for Treasury Futures Contract (5-Year Note)", black.value() ))
print("%-20s: %4.4f" %("Delta", black.delta(spot)))
print("%-20s: %4.4f" %("Gamma", black.gamma(spot)))
print("%-20s: %4.4f" %("Theta", black.theta(spot, T)))
print("%-20s: %4.4f" %("Vega", black.vega(T)))
print("%-20s: %4.4f" %("Rho", black.rho( T)))
10-Year Note yields.
# 10-Year Note
interestRate = 0.003
calcDate = ql.Date(1,12,2023)
yieldCurve = ql.FlatForward(calcDate, interestRate, daysCount, ql.Compounded, ql.Continuous)
ql.Settings.instance().evaluationDate = calcDate
optionMaturityDate = ql.Date(24,12,2033)
strike = 100
spot = 125 # futures price
volatility = 20/100.
optionType = ql.Option.Call
discount = yieldCurve.discount(optionMaturityDate)
strikepayoff = ql.PlainVanillaPayoff(optionType, strike)
T = yieldCurve.dayCounter().yearFraction(calcDate, optionMaturityDate)
stddev = volatility*math.sqrt(T)
black = ql.BlackCalculator(strikepayoff, spot, stddev, discount)
print("%-20s: %4.4f" %("Option Price for Treasury Futures Contract (10-Year Note)", black.value() ))
print("%-20s: %4.4f" %("Delta", black.delta(spot)))
print("%-20s: %4.4f" %("Gamma", black.gamma(spot)))
print("%-20s: %4.4f" %("Theta", black.theta(spot, T)))
print("%-20s: %4.4f" %("Vega", black.vega(T)))
print("%-20s: %4.4f" %("Rho", black.rho( T)))
Natural Gas Futures Option.
# Natural Gas Futures Option
interestRate = 0.0015
calcDate = ql.Date(23,9,2015)
yieldCurve = ql.FlatForward(calcDate, interestRate, daysCount, ql.Compounded, ql.Continuous)
ql.Settings.instance().evaluationDate = calcDate
T = 31/365.
strike = 3.3
spot = 2.4360
volatility = 0.4251
contract = ql.Option.Call
discount = yieldCurve.discount(T)
strikepayoff = ql.PlainVanillaPayoff(contract, strike)
stddev = volatility*math.sqrt(T)
strikepayoff = ql.PlainVanillaPayoff(contract, strike)
black = ql.BlackCalculator(strikepayoff, spot, stddev, discount)
print("%-20s: %4.4f" %("Option Price for Natural Gas Futures ", black.value() ))
print("%-20s: %4.4f" %("Delta", black.delta(spot)))
print("%-20s: %4.4f" %("Gamma", black.gamma(spot)))
print("%-20s: %4.4f" %("Theta", black.theta(spot, T)))
print("%-20s: %4.4f" %("Vega", black.vega(T)))
print("%-20s: %4.4f" %("Rho", black.rho( T)))
Gold Futures Options (31 days).
# Gold Futures Option for April 2023
# Time to maturity is 31 days; 31/365
interestRate = 0.0015
calcDate = ql.Date(23,9,2015)
yieldCurve = ql.FlatForward(calcDate, interestRate, daysCount, ql.Compounded, ql.Continuous)
ql.Settings.instance().evaluationDate = calcDate
T = 31/365.
strike = 2000
spot = 1867.20
volatility = 0.11
contract = ql.Option.Call
discount = yieldCurve.discount(T)
strikepayoff = ql.PlainVanillaPayoff(contract, strike)
stddev = volatility*math.sqrt(T)
strikepayoff = ql.PlainVanillaPayoff(contract, strike)
black = ql.BlackCalculator(strikepayoff, spot, stddev, discount)
print("%-20s: %4.4f" %("Option Price for Natural Gas Futures ", black.value() ))
print("%-20s: %4.4f" %("Delta", black.delta(spot)))
print("%-20s: %4.4f" %("Gamma", black.gamma(spot)))
print("%-20s: %4.4f" %("Theta", black.theta(spot, T)))
print("%-20s: %4.4f" %("Vega", black.vega(T)))
print("%-20s: %4.4f" %("Rho", black.rho( T)))