We explore payoff replication of exotic options using a portfolio on vanilla options. We investigate how well the replication is and what factors affect the payoff.
We consider a digital option with a payoff at maturity T = 1, where L = 80, U = 120. L and U and lower barriers and upper barriers respectively. The digital option pays $1 if Stock price remains in between the barriers.
1L<ST<U
import matplotlib.pyplot as plt
import numpy as np
def binary_call_payoff(S_T):
if S_T > 80 and S_T < 120:
return 1.0
else:
return 0.0
price = []
payoff = []
for stock in np.arange(70,130,0.1):
price.append(stock)
payoff.append(binary_call_payoff(stock))
plt.plot(price,payoff)
plt.title('Digital Option Payoff')
plt.show()
We replicate the digital option payoff above by combining european vanilla option spreads:
C0(Digital)=limϵ→0Call(80−ϵ)−Call(80+ϵ)2ϵ=−dCalldK
The digital call can be thought of as a limit of a call spread. As the distance between the call spread strikes and the digital strikes ϵ get smaller, 1ϵ call spreads with 2ϵ width are needed to replicate the digital option. This means in the limit, as ϵ tends to 0, the call spread can replicate the digital option exactly. Below plot shows the replication of a digital option payoff using 2 call spreads; as ϵ becomes smaller, the spreads replicate the digital option payoff more and more perfectly.
payoff_list1 = []
payoff_list2 = []
payoff_list3 = []
s_price1 = []
s_price2 = []
s_price3 = []
epsilon_var=(0.1,0.5,1)
for j in range(len(epsilon_var)):
S=70
s_tick=0.1
payout = 0.5 # per call spread leg (total payout=1)
epsilon=epsilon_var[j]
lots = payout*2/epsilon
K1=80-epsilon
K2=80+epsilon
K3=120-epsilon
K4=120+epsilon
for i in np.arange(70.0,130,s_tick):
payoff=0
S+=s_tick
if S>K1:
payoff+=(payout*lots)*(S-K1)
if S>K2:
payoff-=(payout*lots)*(S-K2)
if S>K3:
payoff-=(payout*lots)*(S-K3)
if S>K4:
payoff+=(payout*lots)*(S-K4)
if epsilon == 0.1:
payoff_list1.append(payoff)
s_price1.append(S)
elif epsilon == 0.5:
payoff_list2.append(payoff)
s_price2.append(S)
elif epsilon == 1:
payoff_list3.append(payoff)
s_price3.append(S)
plt.plot(s_price1,payoff_list1)
plt.plot(s_price2,payoff_list2)
plt.plot(s_price3,payoff_list3)
plt.title('Spread Replication Payoff')
plt.legend(['epsilon = 0.1','epsilon = 0.5','epsilon = 1'])
plt.show()
It is important to note that while ϵ is not 0, it does not perfectly replicate a digital option payoff:
The call-spread over-replicates the digital option because its payoff is always greater, or equal to the digital option payoff.
It is shown that the smaller the ϵ, the closer the call spread is in replicating the digital option payoff. By using ϵ = 0.1, we can set the call spread strikes required for the digital option.
Since ϵ is set at 0.1, we would need 1/ϵ amount of contracts per call spread. Therefore we have 1/0.1 = 10 contracts per call spread
from scipy.stats import norm
import numpy as np
from matplotlib import style
sigma = 0.2
r = 0
q = 0
T = 1
s_tick = 0.1
K1 = 79.9
K2 = 80.1
K3 = 119.9
K4 = 120.1
delta_profile = []
stock_price = []
def BS_Call_Delta(F,K,q,sigma,T):
d1 = (np.log(F/K)+(0.5*sigma**2*T)) / sigma*np.sqrt(T)
return np.exp(-q*T) * norm.cdf(d1)
for S in np.arange(0.1,240,s_tick):
Forward = S * np.exp(-r*T)
call_spread1 = 10*(BS_Call_Delta(Forward,K1,q,sigma,T) - BS_Call_Delta(Forward,K2,q,sigma,T))
call_spread2 = 10*(-BS_Call_Delta(Forward,K3,q,sigma,T) + BS_Call_Delta(Forward,K4,q,sigma,T))
delta_profile.append(call_spread1 + call_spread2)
stock_price.append(S)
with plt.style.context('seaborn'):
plt.plot(stock_price,delta_profile)
plt.title('Digital Option Delta Profile (BS)')
plt.show()
sigma = 0.2
r = 0
q = 0
T = 1
s_tick = 0.1
K1 = 79.9
K2 = 80.1
K3 = 119.9
K4 = 120.1
vega_profile = []
stock_price_ = []
def BS_Call_Vega(S0,F,K,q,sigma,T):
d1 = (np.log(F/K)+(0.5*sigma**2*T)) / sigma*np.sqrt(T)
return np.exp(-q*T) * S0 * np.sqrt(T) * norm.pdf(d1)
for S in np.arange(0.1,240,s_tick):
Forward = S * np.exp(-r*T)
call_spread1 = 10*(BS_Call_Vega(S,Forward,K1,q,sigma,T) - BS_Call_Vega(S,Forward,K2,q,sigma,T))
call_spread2 = 10*(-BS_Call_Vega(S,Forward,K3,q,sigma,T) + BS_Call_Vega(S,Forward,K4,q,sigma,T))
vega_profile.append(call_spread1 + call_spread2)
stock_price_.append(S)
with plt.style.context('seaborn'):
plt.plot(stock_price_,vega_profile)
plt.title('Digital Option Vega Profile (BS)')
plt.show()