Greeks demo (European options on futures, Black-76)

We start from a single call option instance and, for each Greek,

  1. explain what it means, 2) compute it analytically using volkit.future,

  2. approximate it numerically using a small central difference bump, and

  3. print the values and the approximation error.

import numpy as np
from volkit import (
    price_euro_future,
    delta_euro_future,
    gamma_euro_future,
    vega_euro_future,
    theta_euro_future,
    rho_euro_future,
    dual_delta_euro_future,
    vanna_euro_future,
    vomma_euro_future,
    lambda_euro_future,
)

np.set_printoptions(precision=10, suppress=True)

# Base parameters (edit me)
F = 100.0
K = 100.0
T = 0.5    # years
r = 0.02   # cc rate
sigma = 0.20
cp = 'call'

Numerical greeks

Below, we compare the analytical greeks from this library with numerical approximation. The goal is to

  • show alignment between the two methods

  • illustrate the definitions of various greeks

We use the following numerical approximation schemes:

Central difference for first derivative

\[ f'(x) \approx \dfrac{f(x+h)-f(x-h)}{2h} \]

Central difference for second derivative

\[ f''(x) \approx \dfrac{f(x+h)-2f(x)+f(x-h)}{h^2} \]

Mixed partial derivatives (vanna)

\[ \partial^2 f/\partial x\,\partial y \approx \dfrac{f(x+h,y+k)-f(x+h,y-k)-f(x-h,y+k)+f(x-h,y-k)}{4hk} \]

Baseline price

This is the present value (discounted) of the option under Black-76.

P0 = price_euro_future(F, K, T, r, sigma, cp)
print('Price:', P0)
Price: 5.581106724604813

Delta: sensitivity to futures price \(\partial V / \partial F\)

How much the option price \(V\) moves per 1-unit change in the futures price \(F\).

Delta_ana = delta_euro_future(F, K, T, r, sigma, cp)

hF = 1e-4 * F

Pp = price_euro_future(F + hF, K, T, r, sigma, cp)
Pm = price_euro_future(F - hF, K, T, r, sigma, cp)
Delta_num = (Pp - Pm) / (2 * hF)

print('Delta  analytic :', Delta_ana)
print('Delta  numerical:', Delta_num)
print('abs err:', abs(Delta_ana - Delta_num))
Delta  analytic : 0.522930450497608
Delta  numerical: 0.5229304435332427
abs err: 6.9643653066719935e-09

Gamma: curvature w.r.t. future price

Gamma measures how much delta changes when the future price \(F\) moves by a small amount \(dF\): $\( \Gamma \,=\, \frac{\partial^2 V}{\partial F^2}. \)\( Below we compute Gamma **analytically** using `gamma_euro_future`, and **numerically** by bumping \)F\( in `price_euro_future` with a central difference in \)F$.

Gamma_ana = gamma_euro_future(F, K, T, r, sigma, cp)

hF = np.maximum(1e-4 * (np.abs(F) + 1.0), 1e-6)

P0 = price_euro_future(F, K, T, r, sigma, cp)
Pp = price_euro_future(F + hF, K, T, r, sigma, cp)
Pm = price_euro_future(F - hF, K, T, r, sigma, cp)
Gamma_num = (Pp - 2.0*P0 + Pm) / (hF**2)

print('Gamma  analytic :', Gamma_ana)
print('Gamma  numerical:', Gamma_num)
print('abs err:', abs(Gamma_ana - Gamma_num))
Gamma  analytic : 0.0278590553990826
Gamma  numerical: 0.027859054269892318
abs err: 1.1291902829446698e-09

Vega: sensitivity to volatility

Vega measures how much the option price changes for a small change \(d\sigma\) in volatility: $\( \text{Vega} \,=\, \frac{\partial V}{\partial \sigma}. \)\( We compare `vega_euro_future` with a central difference bump in \)\sigma$ using price_euro_future.

Vega_ana = vega_euro_future(F, K, T, r, sigma, cp)

hs = np.maximum(1e-4 * (np.abs(sigma) + 1.0), 1e-6)
Pp = price_euro_future(F, K, T, r, sigma + hs, cp)
Pm = price_euro_future(F, K, T, r, sigma - hs, cp)
Vega_num = (Pp - Pm) / (2.0 * hs)

print('Vega  analytic :', Vega_ana)
print('Vega  numerical:', Vega_num)
print('abs err:', abs(Vega_ana - Vega_num))
Vega  analytic : 27.85905539908261
Vega  numerical: 27.85905539074799
abs err: 8.33462010518815e-09

Theta: sensitivity to the passage of time

Theta is the rate at which the option price changes with a small change \(dT\) in time-to-expiry \(T\): $\( \Theta \,=\, \frac{\partial V}{\partial T}. \)\( We compare `theta_euro_future` with a central difference bump in \)T\( using `price_euro_future` (ensuring \)T>0$).

Theta_ana = theta_euro_future(F, K, T, r, sigma, cp)

hT = 1e-4
Pp = price_euro_future(F, K, T+hT, r, sigma, cp)
Pm = price_euro_future(F, K, T-hT, r, sigma, cp)
Theta_num = -(Pp - Pm) / (2.0 * hT)

print(hT, P0, Pm, Pp)
print('Theta  analytic :', Theta_ana)
print('Theta  numerical:', Theta_num)
print('abs err:', abs(Theta_ana - Theta_num))
0.0001 5.581106724604813 5.5805606766058755 5.581652714400655
Theta  analytic : -5.460188945324425
Theta  numerical: -5.460188973898106
abs err: 2.8573681198906797e-08

Rho: sensitivity to the interest rate

Rho measures how much the option price changes for a small change \(dr\) in the interest rate \(r\): $\( \rho \,=\, \frac{\partial V}{\partial r}. \)\( We compare `rho_euro_future` with a central difference bump in \)r$ using price_euro_future.

Rho_ana = rho_euro_future(F, K, T, r, sigma, cp)

hr = 1e-6
Pp = price_euro_future(F, K, T, r + hr, sigma, cp)
Pm = price_euro_future(F, K, T, r - hr, sigma, cp)
Rho_num = (Pp - Pm) / (2.0 * hr)

print('Rho  analytic :', Rho_ana)
print('Rho  numerical:', Rho_num)
print('abs err:', abs(Rho_ana - Rho_num))
Rho  analytic : -2.7905533623024064
Rho  numerical: -2.790553362164161
abs err: 1.3824541511553434e-10

Dual Delta: sensitivity to the strike

Dual delta measures how much the option price changes for a small change \(dK\) in the strike \(K\): $\( \text{Dual-}\Delta \,=\, \frac{\partial V}{\partial K}. \)\( We compare `dual_delta_euro_future` with a central difference bump in \)K$ using price_euro_future.

Dual_ana = dual_delta_euro_future(F, K, T, r, sigma, cp)

hK = 1e-4 * (np.abs(K) + 1.0)
Pp = price_euro_future(F, K + hK, T, r, sigma, cp)
Pm = price_euro_future(F, K - hK, T, r, sigma, cp)
Dual_num = (Pp - Pm) / (2.0 * hK)

print('DualΔ  analytic :', Dual_ana)
print('DualΔ  numerical:', Dual_num)
print('abs err:', abs(Dual_ana - Dual_num))
DualΔ  analytic : -0.46711938325155994
DualΔ  numerical: -0.4671193903562059
abs err: 7.104645982636271e-09

Vanna: mixed sensitivity to \(F\) and \(\sigma\)

Vanna is the mixed partial derivative w.r.t. the future price \(F\) and volatility \(\sigma\): $\( \text{Vanna} \,=\, \frac{\partial^2 V}{\partial F\,\partial \sigma}. \)\( We compare `vanna_euro_future` with a **central mixed difference**: bump \)F\( by \)dF\( and \)\sigma\( by \)d\sigma$.

Vanna_ana = vanna_euro_future(F, K, T, r, sigma, cp)

hF = np.maximum(1e-4 * (np.abs(F) + 1.0), 1e-6)
hs = np.maximum(1e-4 * (np.abs(sigma) + 1.0), 1e-6)

Ppp = price_euro_future(F + hF, K, T, r, sigma + hs, cp)
Ppm = price_euro_future(F + hF, K, T, r, sigma - hs, cp)
Pmp = price_euro_future(F - hF, K, T, r, sigma + hs, cp)
Pmm = price_euro_future(F - hF, K, T, r, sigma - hs, cp)

Vanna_num = (Ppp - Ppm - Pmp + Pmm) / (4.0 * hF * hs)

print('Vanna  analytic :', Vanna_ana)
print('Vanna  numerical:', Vanna_num)
print('abs err:', abs(Vanna_ana - Vanna_num))
Vanna  analytic : 0.13929527699541303
Vanna  numerical: 0.1392953132744114
abs err: 3.6278998361005677e-08

Vomma (Volga): curvature w.r.t. volatility

Vomma (also called Volga) is the second derivative of price with respect to volatility \(\sigma\): $\( \text{Vomma} \,=\, \frac{\partial^2 V}{\partial \sigma^2}. \)\( We compare `vomma_euro_future` with a central second difference in \)\sigma$ using price_euro_future.

Vomma_ana = vomma_euro_future(F, K, T, r, sigma, cp)

hs = np.maximum(1e-4 * (np.abs(sigma) + 1.0), 1e-6)
P0 = price_euro_future(F, K, T, r, sigma, cp)
Pp = price_euro_future(F, K, T, r, sigma + hs, cp)
Pm = price_euro_future(F, K, T, r, sigma - hs, cp)
Vomma_num = (Pp - 2.0 * P0 + Pm) / (hs**2)

print('Vomma  analytic :', Vomma_ana)
print('Vomma  numerical:', Vomma_num)
print('abs err:', abs(Vomma_ana - Vomma_num))
Vomma  analytic : -0.6964763849770655
Vomma  numerical: -0.696476580112441
abs err: 1.9513537541371306e-07

Lambda (elasticity): percentage sensitivity to \(F\)

Lambda (elasticity) scales delta by the price-to-future ratio: $\( \Lambda \,=\, \frac{F}{V}\,\frac{\partial V}{\partial F} \;=\; \frac{F}{V}\,\Delta. \)\( We compare `lambda_euro_future` with a numerical version built from a central \)dF$ delta and the price.

Lambda_ana = lambda_euro_future(F, K, T, r, sigma, cp)

hF = np.maximum(1e-4 * (np.abs(F) + 1.0), 1e-6)
P0 = price_euro_future(F, K, T, r, sigma, cp)
Pp = price_euro_future(F + hF, K, T, r, sigma, cp)
Pm = price_euro_future(F - hF, K, T, r, sigma, cp)
Delta_num = (Pp - Pm) / (2.0 * hF)

Lambda_num = np.where(P0 != 0.0, (F * Delta_num) / P0, np.nan)

print('Lambda  analytic :', Lambda_ana)
print('Lambda  numerical:', Lambda_num)
print('abs err:', abs(Lambda_ana - Lambda_num))
Lambda  analytic : 9.369655093535874
Lambda  numerical: 9.369654966233444
abs err: 1.273024299308645e-07