Debugging a Black–Scholes Delta-Gamma Hedge Simulation
Summary
The document presents a Python simulation intended to dynamically hedge a short Black–Scholes call using stock and a second call with a longer maturity. The hedge sizes are formed from the options’ deltas and gammas, with the second option used to offset the short call’s gamma. The reported terminal profit and loss looks incorrect, prompting proposed code corrections.
One answer identifies a time-to-maturity error: the code’s remaining maturity should decline as the simulation advances, rather than increase. Another points out a sign error in the terminal payoff, where the call payoff should be added. It also notes that strategy cost includes changing hedge positions and their financing, not only initial option premiums. These observations help diagnose the example, but the discussion does not validate a corrected simulation or establish performance. The model also assumes Black–Scholes inputs and simplified path dynamics, so its results would depend on implementation and market assumptions.
Key ideas
- A dynamic delta-gamma hedge uses stock and another option to manage the target option’s delta and gamma exposure.
- Time to maturity in option valuation should decrease as simulated time advances.
- The terminal call payoff sign must match the position being closed out.
- Profit and loss accounting should include cash flows from rebalancing hedge instruments and financing them.
- The thread identifies likely errors but does not report results from a corrected simulation.
Tags
Full text
# Classic dynamic delta-gamma hedging in Python
# Classic dynamic delta-gamma hedging in Python
I am trying to run a delta-gamma hedge for a Black-Scholes model in Python.The Euler disceretizatioin of the paths is the simplest possible. I wrote the code below but the PnL looks undesirable and wrong.
I have 2 Options: The one that I am going short and an additional option with a longer maturity (1.5) for the hedge.
```
#install pandas
import math
import numpy as np
import scipy as sp
import numpy.random as npr
import scipy.stats as scs
import matplotlib.pyplot as plt
import numpy.random as npr
import seaborn
seaborn.set_style("ticks")
%matplotlib inline
np.set_printoptions(suppress=True)
# sets the plotting style
#--------------------------
#functions
def callprice(S,K,T,sigma,r):
d1=(sp.log(S/K) + (r + 0.5 * sigma**2)*T) / (sigma * sp.sqrt(T))
d2=(sp.log(S/K) + (r - 0.5 * sigma**2)*T) / (sigma * sp.sqrt(T))
return S*scs.norm.cdf(d1) - math.exp(-r *T) * K * scs.norm.cdf(d2)
def deltafunc(S, K, T, sigma, r):
d1=(sp.log(S/K) + (r + 0.5 * sigma**2)*T) / (sigma * sp.sqrt(T))
return scs.norm.cdf(d1)
def gamma(S, K, T, sigma, r):
d1=(sp.log(S/K) + (r + 0.5 * sigma**2)*T) / (sigma * sp.sqrt(T))
return scs.norm.pdf(d1) / (S * sigma * sp.sqrt(T))
S0 = 100
k = 100
K2 = 100
T1 = 1 # time to maturity
T2 = 1.1 # time to maturity
sigma = 0.2 # vola
n = 10000 # number of simulations
m = 252 # number of realisations of stock
r = 0.05 # interest rate
dt = T1/m
s = np.zeros([n,m+1])
w = npr.standard_normal([n,m])
ttm1 = T1- np.arange(1, m+1, 1)/m
ttm2 = T2- np.arange(1, m+1, 1)/m
s[:,0] = S0
#simple Euler discretization of GBM
for i in range(1,m+1):
s[:,i] = s[:,i-1]*((1 + r*dt) + sigma / np.sqrt(252) * w[:,i-1])
#Computation of greeks
bscall = np.zeros([n,m+1])
deltabs = np.zeros([n,m+1])
gamma1 = np.zeros([n,m+1])
gamma2 = np.zeros([n,m+1])
delta2 = np.zeros([n,m+1])
ttm = np.arange(1, m+1, 1)/m
mu = r
bscall[:,0] = callprice(S0, k, T1, sigma, mu)
deltabs[:,0] = deltafunc(S0, k, T1, sigma, mu)
#delta gamma
gamma1[:,0] = gamma(s[:,0], k, T1, sigma, mu)
gamma2[:,0] = gamma(s[:,0], k, T2, sigma, mu)
delta2[:,0] = deltafunc(s[:,0], k, T2, sigma, mu)
for i in range(1,m+1):
bscall[:,i] = callprice(s[:,i], k, T1-ttm[i-1], sigma, mu)
deltabs[:,i] = deltafunc(s[:,i], k, T1-ttm[i-1], sigma, mu)
gamma1[:,i] = gamma(s[:,i], k, T1-ttm[i-1], sigma, mu)
gamma2[:,i] = gamma(s[:,i], k, T2-ttm[i-1], sigma, mu)
delta2[:,i] = deltafunc(s[:,i], k, T2-ttm[i-1], sigma, mu)
#coef between gammas
h2 = gamma1/gamma2
#delta rebalance
strategy = deltabs - delta2*h2
#initialization
st = s[:,0]
amount = callprice(s[:,0], k, T1, sigma, mu)
delta = deltabs[:,0] - delta2[:,0]*gamma1[:,0]/gamma2[:,0]
gammacoef = gamma1[:,0]/gamma2[:,0]
Pnl = amount - delta*st - gamma1[:,0]/gamma2[:,0]*callprice(s[:,0], k, T2, sigma, mu)
interest = np.exp(r * dt)
for i in range(1, m):
Pnl = interest * Pnl
newdelta = deltabs[:,i] - delta2[:,i]*gamma1[:,i]/gamma2[:,i]
newgammacoef = gamma1[:,i]/gamma2[:,i]
Pnl = Pnl - ((newdelta-delta)*s[:,i] + (newgammacoef-gammacoef)*callprice(s[:,i], k, T2-ttm[i-1], sigma, r))
delta = newdelta
gammacoef = newgammacoef
Pnl = Pnl * interest
PnL_final = Pnl + strategy[:,-2] * s[:,-1] + gamma1[:,-2]/gamma2[:,-2]*callprice(s[:,-1], k, T2-ttm[-1], sigma, r)/interest - np.max(s[:,-1]-k,0)
PnL_final2 = [x/callprice(s[0,0], k, T1, sigma, mu) for x in PnL_final]
print(PnL_final)
plt.hist(PnL_final)
```
## Answer by Kirill Ozerov (score 1)
https://quant.stackexchange.com/a/51045
I think your problem is here: `T1-ttm[i-1]` (or `T2`). Rather it should be just `ttm[i-1]`.
In your code time to maturity increases with time, it obviously should be the other way around.
## Answer by JM_BJ (score 0)
https://quant.stackexchange.com/a/78887
Problem is at the tail of this: PnL_final = Pnl + strategy[:,-2] * s[:,-1] + gamma1[:,-2]/gamma2[:,-2]*callprice(s[:,-1], k, T2-ttm[-1], sigma, r)/interest - np.max(s[:,-1]-k,0)
It should be a "+ np.max(s[:,-1]-k,0)" not "- np.max(s[:,-1]-k,0)". More basic error includes that the cost of this strategy is NOT just the premium of the call options, but also the margin required for short spot or capital to buy spot when doing delta-hedging part. Also, the second call option also requires capital to cover it varying premium on the way of gamma rebalancing.Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.