期权Delta对冲
参考文献:Ahmad, R., & Wilmott, P. (2005). Which free lunch would you like today, sir?: Delta hedging, volatility arbitrage and optimal portfolios. Wilmott, 64-79.
1. 对冲收益的一般形式
假定买入认购期权,标的资产价格为S,剩余到期时间为t,调整标的资产仓位进行对冲至到期,隐含波动率为
σ
~
\tilde{\sigma}
σ~,实际波动率为
σ
~
\tilde{\sigma}
σ~,用于对冲的波动率为
σ
h
\sigma_h
σh。
V
(
S
,
t
;
σ
h
)
−
V
(
S
,
t
;
σ
~
)
+
1
2
(
σ
2
−
σ
h
2
)
∫
t
0
T
e
−
r
(
t
−
t
0
)
S
2
Γ
h
d
t
(1)
V(S,t;\sigma_h)-V(S,t;\tilde{\sigma}) + \frac{1}{2}(\sigma^2 - \sigma^2_h) \int_{t_0}^{T} e^{-r(t-t_0)S^2} \Gamma^hdt \tag{1}
V(S,t;σh)−V(S,t;σ~)+21(σ2−σh2)∫t0Te−r(t−t0)S2Γhdt(1)
每次对冲的收益为:
1
2
(
σ
2
−
σ
~
2
)
S
2
Γ
i
d
t
+
(
Δ
i
−
Δ
h
)
(
(
μ
−
r
+
D
)
S
d
t
+
σ
S
d
z
)
(2)
\frac{1}{2}(\sigma^2 - \tilde{\sigma}^2) S^2 \Gamma^i dt +(\Delta^i - \Delta^h)((\mu - r +D)Sdt +\sigma Sdz) \tag{2}
21(σ2−σ~2)S2Γidt+(Δi−Δh)((μ−r+D)Sdt+σSdz)(2)
标的资产价格服从几何布朗运动 d S = μ S d t + σ S d z dS = \mu Sdt + \sigma S dz dS=μSdt+σSdz,dz是维纳过程, μ \mu μ是标的资产期望收益率, σ \sigma σ是收益率的年化标准差。
其中, Γ i \Gamma^i Γi是基于隐含波动率计算出的期权Gamma值, Δ i , D e l t a h \Delta^i,Delta^h Δi,Deltah分别是基于隐含波动率和实际波动率计算出的期权的delta值。D是股票的分红(连续分红),r是无风险利率。
使用实际波动率进行对冲
上式可以简化为:
V
(
S
,
t
;
σ
h
)
−
V
(
S
,
t
;
σ
~
)
(3)
_ V(S,t;\sigma_h)-V(S,t;\tilde{\sigma}) \tag{3}
V(S,t;σh)−V(S,t;σ~)(3)
使用隐含波动率进行对冲
上式可以简化为:
1
2
(
σ
2
−
σ
h
2
)
∫
t
0
T
e
−
r
(
t
−
t
0
)
S
2
Γ
h
d
t
(4)
\frac{1}{2} (\sigma^2 - \sigma^2_h) \int_{t_0}^{T} e^{-r(t-t_0)S^2} \Gamma^hdt \tag{4}
21(σ2−σh2)∫t0Te−r(t−t0)S2Γhdt(4)
收益波动与波动率选择
如果说期权是根据模型定价的(Mark to Model),而不是根据市场定价的(Mark to Market),那么自然应该选择使用实际波动率进行对冲。换句话说,就是定价的波动率其实是实际波动率,可参见2.1节的 σ = σ ~ \sigma = \tilde{\sigma} σ=σ~的对冲收益情景,这种情况下使用实际波动率进行对冲,最终的收益波动为0,且收益与最优期望收益相差较小。
如果期权是根据市场定价的,通常处于风险管理的需要,将基于期权市场价值汇报每天的损益(Profit and Loss),为了避免每天收益的波动率,在这种情况下通常会选择隐含波动率进行对冲。为什么要关心每天的PnL呢?因为如果标准差越大,风险越大,在某一天收益出现较大亏损的可能性就越大,被强行平仓的可能性就会越大,交易员或者投资者需要尽力避免这种极端情况的发生。
如公式(2)所示,每次收益的波动率是由
σ
S
∣
Δ
i
−
Δ
h
∣
d
z
\sigma S |\Delta^i - \Delta^h|dz
σS∣Δi−Δh∣dz项引起的,(2)式的标准差可以写为
σ
S
∣
Δ
i
−
Δ
h
∣
d
t
(5)
\sigma S |\Delta^i - \Delta^h|\sqrt{dt} \tag{5}
σS∣Δi−Δh∣dt(5)
可以看出,每次对冲收益的标准差(风险)与隐含波动率,实际波动率和对冲波动率都有关,如果对冲的波动率越接近实际波动率,风险就会越小。
对于一个认购欧式期权,还有六个月到期,隐含波动率为20%,对冲波动率为30%,分别计算出的相应Delta值,其与标的资产价格的关系如如上图所示。当价格处于深虚值或者深实值(far in or out of money),两种波动率计算出的Delta值差距不大,而当价格越靠近平值(越靠近行权价时),Delta值的差距较为明显,局部的收益风险较大。
两条曲线相交于:
S
=
K
e
x
p
(
−
T
−
t
σ
~
−
σ
h
(
σ
~
(
r
−
D
+
σ
h
2
/
2
)
−
σ
h
(
r
−
D
+
σ
2
/
2
)
)
S = K exp(-\frac{T-t}{\tilde{\sigma}-\sigma^h}(\tilde{\sigma}(r-D+\sigma^{h^2}/2)-\sigma^h(r-D+\sigma^2/2))
S=Kexp(−σ~−σhT−t(σ~(r−D+σh2/2)−σh(r−D+σ2/2))
2. 对冲收益跟对冲波动率的关系
假定买入欧式认购期权,一年到期,行权价 k k k为100,标的资产价格为 100 100 100,无风险利率 r r r=10%,分红D=0, μ \mu μ标的资产的价格期望收益为0。对于每种情况模拟了100次,并求收益的期望值、最大值、最小值和标准差。
2.1 实际波动率= 隐含波动率( σ = σ ~ = 20 % \sigma = \tilde{\sigma} = 20\% σ=σ~=20%)
当实际波动率等于隐含波动率时,无论采用哪个波动率来进行对冲,其期望的收益相对于期权的市场价格(13.3)来说都是比较小的。但是当使用实际波动率0.2进行对冲的时候,收益的标准差最小。最高收益曲线比最低收益曲线更加陡峭。所有的曲线相交于同一点(0.2,0)。
2.2 实际波动率>隐含波动率( σ = 40 % , σ ~ = 20 % \sigma =40\%, \tilde{\sigma} = 20\% σ=40%,σ~=20%)
当实际波动率大于隐含波动率的时候,如果对冲的波动率小于隐含波动率,或高于了0.75左右,很有可能会发生亏损(最低收益低于了0);如果对冲的波动率介于0.2到0.75左右,反而不会亏损。同时可以看出,期望收益对于对冲的波动率是不敏感的。
2.3 实际波动率<隐含波动率( σ = 20 % , σ ~ = 40 % \sigma =20\%, \tilde{\sigma} = 40\% σ=20%,σ~=40%)
当实际波动率小于隐含波动率的时候,如果对冲的波动率小于0.1左右,或高于隐含波动率,很有可能会发生亏损(最低收益低于了0);如果对冲的波动率介于0.1到0.4左右,反而不会亏损。同时可以看出,期望收益对于对冲的波动率是不敏感的。而最低收益曲线比最高收益曲线更加陡峭。
3. 代码实现
主要包括了PriceSimulator和OptionHedge两个类,前者是用来模拟期权的价格路径(基于隐含波动率)和标的资产的价格路径(基于实际波动率),后者则是基于对冲的波动率来计算对冲的delta,并多次进行模拟,得到期权对冲的收益及期望值、标准差、最大值、最小值等。参数设定及调用方式参见main函数。
# -*- coding: utf-8 -*-
"""
@time:2021/6/24 9:58
@author: Hu Yue
@email: hhhuyue@gmail.com
@file: hedge.py
Note:
"""
from math import sqrt
from typing import Type
import scipy.stats as si
import numpy as np
from matplotlib import pyplot as plt
from scipy.stats import norm
class PriceSimulator:
def __init__(self, S, K, T, rf, cp_type, IV, RV, simulation_steps, expected_return):
self.simulation_steps = simulation_steps
self.RV = RV
self.IV = IV
self.cp_type = cp_type
self.rf = rf
self.T = T
self.K = K
self.S = S
self.mu = expected_return
def get_option_price(self, S, K, T, sigma, rf, option_type):
d1 = (np.log(S / K) + (rf + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if option_type == "Call":
option_price = S * norm.cdf(d1) - K * np.exp(-rf * T) * norm.cdf(d2)
else:
option_price = K * np.exp(-rf * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
return option_price
def simulate(self):
random_seed = np.random.standard_normal((self.simulation_steps, 1))
dt = self.T / self.simulation_steps
# Monte carlo模拟 真实的波动率生成标的价格路径
stock_price = self.S * np.exp(
np.cumsum((self.mu - 0.5 * self.RV ** 2) * dt + self.RV * sqrt(dt) * random_seed, axis=0))
option_price = np.zeros((self.simulation_steps, 1))
expire_time = (np.ones((self.simulation_steps, 1)) * dt).cumsum()[::-1]
# 期权价格路径基于隐含波动率生成,期权市场价格
for step in range(self.simulation_steps):
option_price[step] = self.get_option_price(stock_price[step], self.K, expire_time[step], self.IV,
self.rf,
self.cp_type)
return stock_price, option_price
def plot(self, price):
# 制作模拟价格走势图表
plt.plot(price[:, :], lw=1.5)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.xlabel('时间')
plt.ylabel('价格')
plt.title('模拟价格走势')
plt.show()
class OptionHedge:
def __init__(self, S, K, T, rf, cp_type, IV, RV, expected_return):
self.mu = expected_return
self.RV = RV
self.IV = IV
self.cp_type = cp_type
self.rf = rf
self.T = T
self.K = K
self.S = S
self.simulation_steps = 1000
self.dt = 1 / 1000
def init_price_simulator(self, price_simulator: Type[PriceSimulator], simulation_steps):
self.simulation_steps = simulation_steps
self.dt = self.T / simulation_steps
return price_simulator(self.S, self.K, self.T, self.rf, self.cp_type, self.IV, self.RV, simulation_steps,
self.mu)
def get_delta(self, S, K, T, sigma, rf, option_type):
if option_type == "Call":
n = 1
else:
n = -1
d1 = (np.log(S / K) + (rf + 0.5 * sigma ** 2) * T) / (
sigma * np.sqrt(T))
delta = n * si.norm.cdf(n * d1)
return delta
def hedge(self, hedge_volatility, price_simulator: PriceSimulator, simulation_times):
pnl = np.zeros((self.simulation_steps - 1, simulation_times))
for simulation in range(simulation_times):
stock_price, option_price = price_simulator.simulate()
# 基于对冲波动率计算的delta值
expire_time = (np.ones((simulation_steps, 1)) * self.dt).cumsum()[::-1] # 倒序
delta = np.zeros((simulation_steps, 1))
for step in range(simulation_steps):
delta[step] = self.get_delta(stock_price[step], self.K, expire_time[step], hedge_volatility,
self.rf,
self.cp_type)
pnl_path = np.diff(option_price, axis=0) - delta[:-1] * np.diff(stock_price, axis=0) - \
self.rf * self.dt * (option_price[:-1] - delta[:-1] * stock_price[:-1])
pnl[:, simulation] = pnl_path.reshape(simulation_steps - 1)
return pnl
def plot(self, pnl):
plt.plot(pnl.cumsum(axis=0)[:, :], lw=1.5)
plt.rcParams['font.sans-serif'] = ['KaiTi', 'SimHei', 'FangSong']
plt.rcParams['font.size'] = 12
plt.rcParams['axes.unicode_minus'] = False
plt.xlabel('时间')
plt.ylabel('收益')
plt.title('期权对冲累积收益图')
plt.show()
if __name__ == '__main__':
# 参数设定,无分红
risk_free_rate = 0.1
expected_return = 0
implied_volatility = 0.2
actual_volatility = 0.2
init_stock_price = 100
strike_price = 100
option_type = "Call"
T = 1
simulation_steps = 1000 # 每次模拟有1000步
simulation_times = 100 # 模拟1000次
# 初始化期权和标的
option_hedge = OptionHedge(init_stock_price, strike_price, T, risk_free_rate, option_type, implied_volatility,
actual_volatility, expected_return)
# 期权价格和标的资产价格路径模拟器
price_simulator = option_hedge.init_price_simulator(PriceSimulator, simulation_steps)
# 多次模拟并进行对冲,对冲的波动率可自行选择
# pnl = option_hedge.hedge(implied_volatility, price_simulator, simulation_times)
# option_hedge.plot(pnl)
# 分析不同波动率对对冲收益的影响
volatility = np.arange(0, 1, 0.02)
hedge_pnl = np.ones((len(volatility), 5))
for i in range(1, len(volatility)):
pnl = option_hedge.hedge(volatility[i], price_simulator, simulation_times).sum(axis=0)
expected_profit = np.mean(pnl)
standard_deviation_profit = np.std(pnl)
max_profit = np.max(pnl)
min_profit = np.min(pnl)
print(volatility[i], expected_profit, standard_deviation_profit, min_profit, max_profit)
hedge_pnl[i, :] = [volatility[i], expected_profit, standard_deviation_profit, min_profit, max_profit]
plt.plot(hedge_pnl[1:, 0], hedge_pnl[1:, 1:], lw=1.5)
plt.rcParams['font.sans-serif'] = ['KaiTi', 'SimHei', 'FangSong']
plt.rcParams['font.size'] = 12
plt.rcParams['axes.unicode_minus'] = False
# plt.xticks(np.arange(0, 1, 0.1))
plt.xlabel('波动率')
plt.ylabel('收益')
plt.title('期权对冲累积收益图')
plt.legend(["expected profit", "Standard deviation of profit", "Minimum profit", "Maximum profit"], loc=9,
fontsize=10)
ax = plt.gca() # gca:get current axis得到当前轴
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_position(('data', 0))
plt.show()