目录
免责声明:本文由作者参考相关资料,并结合自身实践和思考独立完成,对全文内容的准确性、完整性或可靠性不作任何保证。同时,文中提及的数据仅作为举例使用,不构成推荐;文中所有观点均不构成任何投资建议。请读者仔细阅读本声明,若读者阅读此文章,默认知晓此声明。
在期权专题9中,分享了雪球期权的定价思路及相应的代码实现。本期主要分享一下定价的应用以及主要参数对雪球期权的影响。
1. 定价应用
1.1 定价比对
在期权专题9中,介绍了雪球存在两种状态下的定价模式:过去发生过敲入和过未发生过敲入。现在对这两种模式就行探讨。假设雪球已经运行了4个月(84天),分别作出标的在不同价格下(价格区间设置为0.73到1.05),两种估值模式对应的估值。为了便捷,将期权专题9的代码打包放在Pricing_model的py中(对应文件夹名为snow_ball)。
from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt
# 雪球要素
r = 0.025
vol = 0.22
residual_day = 168
knock_out = 1.03 # 敲出价格
knock_in = 0.75 # 敲入价格
coupon = 0.2 # 年化票息
lock_time = 3 # 锁定期,单位为月
T = 1 # 期限,折算为年
times = 5000
# 获取两种估值模式下,标的不同价格对应估值
St_list = [0.73 + 0.01 * x for x in range(33)]
in_nav_list = []
out_nav_list = []
for St in St_list:
print(St)
price_path = get_s_path(St, r, vol, residual_day, times)
in_nav = get_snowball_nav('in', coupon, T, r,residual_day, knock_in, knock_out, lock_time, price_path)
out_nav = get_snowball_nav('out', coupon, T, r,residual_day, knock_in, knock_out, lock_time, price_path)
in_nav_list.append(in_nav)
out_nav_list.append(out_nav)
# 作图
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(St_list, in_nav_list, label='发生过敲入')
plt.plot(St_list, out_nav_list, label='未发生过敲入')
plt.title('雪球估值模式比对(变量为标的价格)')
plt.legend()
plt.show()
对应的结果为:
由上图可以看出:在敲入线(75%)到敲出线(103%)之间,发生敲入情况下的定价估值会远低于未发生敲入情况下的估值。对于发生过敲入的雪球,只有当标的价格临近敲出线时,才存在获利的可能,因此其估值基本是是趋近于标的的价格。对于未发生过敲入的雪球,随着标的价格的上涨,其发生敲入的概率会越来低,因此估值呈现非线性的增长,在临近敲出线附近时,因为触发敲出的概率加大,获取全额票息的概率降低,因此其估值增速会放慢,但整体的预期收益会高于敲出对应的票息(依然存在获取全额票息的可能)。
接下来,假设标的价格为0.9,雪球运行了200天,以剩余期限作为自变量,比较两种估值的差异。
由上图可以看出:随着剩余期限接近于0,发生敲入情况下的定价估值会远低于未发生敲入情况下的估值。对于发生过敲入的雪球,假设标的价格一直在0.9,随着到期日的临近,其发生敲出的概率会越来越低,因此其估值接近于0.9;对于未发生过敲入的雪球,假设标的价格一直在0.9,随着到期日的临近,其大概率可以获得全额的票息,因此估值接近于1.2。
综上,未发生过敲入的定价整体会高于发生过敲入的定价,原因主要有两个:1. 发生敲入后,不具备获取全额票息(未敲入敲出)的可能;2. 发生敲入后,意味指数已经下跌25%,未来发生敲出的概率很低。在应用过程中,首先要判断雪球当前的状态,然后使用不同的估值模式。
1.2 模拟估值
此处以中证500为标的,选取2018年1月18日到2019年1月18日的数据,进行模拟估值的演示。需要注意,由于本文采取的是相对简单的估值处理(尤其是对于日期的处理),因此估值会存在一定的误差,此方法更适合理解估值逻辑以及做回测。
首先,使用akshare获取中证500的收盘价数据,并对其进行归一处理,同时生成相应的剩余日期序列。
import akshare as ak
import pandas as pd
# 获取数据,进行净值归一
code = 'sh000905'
st, et = '2018-01-18', '2019-01-18'
data = ak.stock_zh_index_daily_em(symbol=code).sort_values(by=['date'], ascending=True)
index_df = data.loc[(data['date'] >= st) & (data['date'] <= et)]
gy_nav = round(index_df['close'] / index_df['close'].values[0], 3)
new_index = pd.DataFrame({'日期': index_df['date'], '净值': gy_nav})
# 增加剩余期限
new_index['剩余期限'] = [len(new_index) - x for x in range(len(new_index))]
对应得到的结果为:
接下来,判断估值节点的对应的状态,即是否发生敲入,需要注意的是一但某个节点发生敲入,则后续的状态均是敲入;同时还需要判断整个过程是否有发生敲出(从实际数据来看,无敲出情况,可以使用实际的日期来作为判断,这里可以使用相应的程序来细化实现,此处不再做进一步的复现)。
# 判断估值节点的状态
def get_status_list(status_list):
# 一旦发生敲入,将后续的状态全部填充为敲入
new_list = status_list.copy()
end = len(status_list)
for x in range(0, end):
num = status_list[x]
if num == 'out':
pass
else:
a = x
break
start = a
new_list[start:end] = ['in' for y in range(0, end - start)]
return new_list
# 生成状态序列
status_list = ['out' if x >= 0.75 else 'in' for x in new_index['净值']]
# 对状态序列进行判断,并加入净值表格中
new_index['状态'] = get_status_list(status_list)
对应得到结果为:
考虑到当前估值的时间区间只有244个交易日,因此get_one_nav函数的参数进行调整。主要是将252参数调整为244,同时每月21天调整为20。此处的主要调整如下:
def get_one_nav(status, coupon, T, r, residual_day, one_path, knock_in, knock_out, lock_time):
'''
获取雪球单次的模拟路径下,对应的净值
:param status: str,雪球过去的状态,'out'表示过去未发生过敲入,'in'表示过去发生过敲入
:param coupon: int,雪球对应的年化票息,例如0.2
:param T: int,雪球对应的期限(折算成年),例如1
:param r: int,无风险利率,例如0.025
:param residual_day: int,剩余天数,例如252
:param one_path: 矩阵,单次对应的模拟路径
:param knock_in: int,雪球对应的敲出价格
:param knock_out: int,需求对应的敲入价格
:return: int,预估的雪球净值
'''
year_day = 244 # 一年的交易日天数
month_day = round(year_day/12,0) # 一个月的交易日天数
all_time = T * year_day # 期限,折算为天
survival_day = all_time - residual_day # 已存续期限
# 价格低于敲入价格的交易日对应的序号
knock_in_day = np.where(one_path <= knock_in)
# 价格高于敲出价格的交易日对应的序号
knock_out_day = np.where(one_path >= knock_out)
# 上述每个交易日的序号需要加上雪球已存续期限
new_out_day = knock_out_day[0] + survival_day
# 判断上述日期是否是观察日,1年252个交易日,因此一个月设定为12
obs_may_day = new_out_day[new_out_day % month_day == 0]
# 由于有锁定期,需要剔除前三个月的观察日
obs_day = obs_may_day[obs_may_day /month_day > lock_time]
# 过去未发生敲入
if status == 'out':
# 情况1:未来发生过敲出
if len(obs_day) > 0:
t = obs_day[0]
re = coupon * (t / year_day) * np.exp(-r * t / year_day)
nav = 1 * (1 + re) # 1表示期初的净值
else:
# 情况2:未来未发生敲入和敲出
if len(knock_in_day[0]) == 0:
re = coupon * np.exp(-r * T)
nav = 1 + re
# 情况3:未来未发敲出,但发生敲入
else:
# 期末净值大于等于1
if one_path[-1] >= 1:
re = 0
nav = 1 * (1 + re)
# 期末净值小于1
elif one_path[-1] < 1:
re = (one_path[-1] - 1) * np.exp(-r * T)
nav = 1 * (1 + re)
elif status == 'in':
# 情况1:未来发生过敲出
if len(obs_day) > 0:
t = obs_day[0]
re = coupon * (t / year_day) * np.exp(-r * t /year_day)
nav = 1 * (1 + re) # 1表示期初的净值
else:
# 情况2:未来未发生过敲出
# 期末净值大于等于1
if one_path[-1] >= 1:
re = 0
nav = 1 * (1 + re)
# 期末净值小于1
elif one_path[-1] < 1:
re = (one_path[-1] - 1) * np.exp(-r * T)
nav = 1 * (1 + re)
else:
nav = 0
new_value = nav
return new_value
然后,引入雪球估值(一)中介绍的估值函数(调整过交易日参数后的),通过循环得到对应的模拟估值结果。此处,对应的变量主要是标的价格,剩余天数,以及当前对应的状态。
# 计算模拟估值
from snow_ball.Pricing_model import *
def get_one_day_nav(status, residual_day, St):
# 根据参数,计算雪球对应的模拟估值
r = 0.025
vol = 0.22
times = 5000
price_path = get_s_path(St, r, vol, residual_day, times)
# 雪球要素
knock_out = 1.03 # 敲出价格
knock_in = 0.75 # 敲入价格
coupon = 0.2 # 年化票息
lock_time = 3 # 锁定期,单位为月
T = 1 # 期限,折算为年
nav = get_snowball_nav(status, coupon, T, r, residual_day,
knock_in, knock_out, lock_time, price_path)
return nav
nav_list = []
for num in range(0, len(new_index)):
st = new_index['净值'].values[num]
residual_day = new_index['剩余期限'].values[num]
print(residual_day)
status = new_index['状态'].values[num]
one_nav = get_one_day_nav(status, residual_day, st)
nav_list.append(one_nav)
new_index['模拟估值'] = nav_list
对应得到的结果为:
对结果进行可视化操作,得到的结果为:
import matplotlib.pyplot as plt
date = pd.to_datetime(new_index['日期'])
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(date, new_index['净值'], label='标的净值走势')
plt.plot(date, new_index['模拟估值'], label='模拟估值')
plt.title('模拟估值案例')
plt.legend()
plt.show()
由上图可以发现,当雪球未发生敲入时,其估值是远高于标的的净值,这是因为此种情况下未敲入敲出发生的可能较大,因此预期收益会高于标的本身;当发生敲入后,二者估值基本趋于一致 ,这是因为未来再发生敲出的概率极低,等到雪球到期时,二者收敛为一致。
2. 主要参数分析
影响估值的参数有很多,此处主要用例举三个主要的参数。同时,为了更好的说明参数的敏感性,后文均使用未敲入状态下的雪球估值(敲入状态下的雪球估值对各参数的敏感性较弱)。同时,由于不同标的价格位置受参数的影响也不同,因此设置标的价格为1和0.8,作为对照组。
2.1 模拟次数
以模拟次数作为唯一变量,标的价格分别设置为1和0.8;其余参数固定。
from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt
# 雪球要素
r = 0.025
vol = 0.22
residual_day = 252
knock_out = 1.03 # 敲出价格
knock_in = 0.75 # 敲入价格
coupon = 0.2 # 年化票息
lock_time = 3 # 锁定期,单位为月
T = 1 # 期限,折算为年
# times = 5000
time_list = [1000 * x for x in range(20)]
nav_list = []
nav_list1 = []
for times in time_list:
print(times)
price_path = get_s_path(0.8, r, vol, residual_day, times)
one_nav = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path)
price_path1 = get_s_path(1, r, vol, residual_day, times)
one_nav1 = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path1)
nav_list.append(one_nav)
nav_list1.append(one_nav1)
# 作图
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
zt = plt.figure()
p1 = zt.add_subplot(2, 1, 1)
p1.plot(time_list, nav_list, label='标的价格0.8')
plt.legend(loc='best')
plt.title('模拟次数对雪球估值的影响')
p2 = zt.add_subplot(2, 1, 2)
p2.plot(time_list, nav_list1, label='标的价格1.0')
plt.legend(loc='best')
plt.show()
从上述结果可以看出,当次数到达5000次以后,估值影响基本是在千分位上的差异。若是追求较高的精确度,可以将次数上调至10万次;如果只是做一些理解测算,或者是大量的回测数据,那么5000或者10000次是一个比较好的选择。
2.2 剩余期限
以剩余期限作为唯一变量,标的价格分别设置为1和0.8;其余参数固定。
from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt
# 雪球要素
r = 0.025
vol = 0.22
knock_out = 1.03 # 敲出价格
knock_in = 0.75 # 敲入价格
coupon = 0.2 # 年化票息
lock_time = 3 # 锁定期,单位为月
T = 1 # 期限,折算为年
times = 5000
day_list = [252 - x for x in range(252)]
nav_list = []
nav_list1 = []
for day in day_list:
print(day)
price_path = get_s_path(0.8, r, vol, day, times)
one_nav = get_snowball_nav('out', coupon, T, r, day, knock_in, knock_out, lock_time, price_path)
price_path1 = get_s_path(1, r, vol, day, times)
one_nav1 = get_snowball_nav('out', coupon, T, r, day, knock_in, knock_out, lock_time, price_path1)
nav_list.append(one_nav)
nav_list1.append(one_nav1)
# 作图
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(day_list, nav_list, label='标的价格0.8')
plt.plot(day_list, nav_list1, label='标的价格1.0')
plt.title('剩余期限对雪球估值的影响')
plt.legend()
plt.show()
从上图可以看出,再不发生敲入的情况下,越临近到期,雪球的估值对应会越高,同时在临近到期时估值会有一个加速上涨的过程(普通期权依然存在这一特性),二者最终估值收敛于1.2(均获取到了全额票息)。相比于标的价格为0.8的雪球,标的价格为1的雪球走势会相对更加平滑,这是因为这个位置基本不会存在敲入的情况,同时发生敲出的概率极大,因此随着存续时间的增加,其估值随之增加。位于0.8位置的雪球,随着到期日的临近,其获取全额票息的概率相比前期更高,因此估值存在一个加速上涨的趋势。
2.3 波动率
以波动率作为唯一变量,标的价格分别设置为1和0.8;其余参数固定。
from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt
# 雪球要素
r = 0.025
# vol = 0.22
residual_day = 252
knock_out = 1.03 # 敲出价格
knock_in = 0.75 # 敲入价格
coupon = 0.2 # 年化票息
lock_time = 3 # 锁定期,单位为月
T = 1 # 期限,折算为年
times = 5000
vol_list = [0.1+x*0.01 for x in range(40)]
nav_list = []
nav_list1 = []
for vol in vol_list:
print(vol)
price_path = get_s_path(0.8, r, vol, residual_day, times)
one_nav = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path)
price_path1 = get_s_path(1, r, vol, residual_day, times)
one_nav1 = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path1)
nav_list.append(one_nav)
nav_list1.append(one_nav1)
# 作图
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
zt = plt.figure()
p1 = zt.add_subplot(2, 1, 1)
p1.plot(vol_list, nav_list, label='标的价格0.8')
plt.legend(loc='best')
plt.title('波动率对雪球估值的影响')
p2 = zt.add_subplot(2, 1, 2)
p2.plot(vol_list, nav_list1, label='标的价格1.0')
plt.legend(loc='best')
plt.show()
波动率作为雪球最为核心的要素,同时还会影响票息的水平。由于雪球的投资者更多预期未来标的小幅震荡,即波动率处于小幅区间内;因此,当波动率水平越高,对应雪球的整体估值会越低,因为这种状态下获取全额票息的可能性非常低,对应的票息就会较高(可以视为补偿)。波动率水平的预测方式主要有GARCH类模型以及随机波动模型,但是整体预测难度比较大。笔者也做了下关于波动率取值方式的一些探讨,但最终发现多重模式下波动率的差异不大,同期基本位于一个正负2%的区间内。所以,对于波动率的取值,笔者倾向于选择一些较为简单的方式,移动平滑或者均值基本上就可以了。
本期内容到此结束,有问题欢迎交流。
免责声明:本文由作者参考相关资料,并结合自身实践和思考独立完成,对全文内容的准确性、完整性或可靠性不作任何保证。同时,文中提及的数据仅作为举例使用,不构成推荐;文中所有观点均不构成任何投资建议。请读者仔细阅读本声明,若读者阅读此文章,默认知晓此声明。