期权专题9:雪球期权(一)(BSM+蒙特卡罗定价)

2024-01-20 15:10

本文主要是介绍期权专题9:雪球期权(一)(BSM+蒙特卡罗定价),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

   

目录

1. 前期介绍

1.1 定价思路

1.2 雪球要素

1.3 标的定价

2. 代码复现

2.1 价格路径

2.1.1 单次路径

2.1.2 多次路径

2.2 雪球定价

2.2.1 加入要素

2.2.2 估值定价

3. 完整代码


  雪球期权是近两年来备受青睐的衍生品投资标的之一,几个月前就想写一篇关于它的文章。但是因为其定价模型确实比较复杂,无论在思路和代码复现上都存在一定的难度,直到今日方才鼓起勇气一试。由于自我认知上的局限,分享的内容可能不够准确或者全面,望读者见谅,同时对有疑惑的地方欢迎探讨交流。

1. 前期介绍

1.1 定价思路

    雪球期权本质上是障碍期权的组合,关于其一些基础性的介绍此处省略(网上相关介绍太多)。本文选用蒙特卡罗模拟法来实现雪球定价的净值化,定价的核心思路主要是以下三点:

1. 使用蒙特卡罗模拟出雪球标的未来可能出现的路径。

2. 根据雪球要素,计算每一条路径的折现净值。

3. 计算所有路径下折现净值的均值,即作为雪球定价的净值。

    同时,还需要格外注意的一点是,雪球定价存在2种状态:一种是发生敲入状态下的,一种是未发生敲入状态下的。

1.2 雪球要素

  本文选用经典雪球作为案例,其要素如下:

雪球要素
挂钩标的中证500指数(399905.SZ)
存续期12个月
封闭期3
敲入线1.03
敲出线0.75
敲入观察频率每日
敲出观察频率每月
票息(年化)20%

  同时,为了简化定价模型的复杂程度,本文做出以下假设:

1. 假设波动率的数值为常数,值为22%(近五年指数年化波动率均值)。

2. 假设无风险利率为2.5%,一年的交易日为252天。

3. 假设不考虑股指升贴水,展期带来的损益。

4. 假设不考虑交易过程中所有的摩擦成本。

1.3 标的定价

    假设标的满足对数正态分布的条件,将微分方程引入BSM模型,可以推导出雪球所挂钩标的对应的定价公式。

    使用蒙特卡罗的思路,对上述公式进行逐逐步的模拟迭代,可以获得未来标的可能出现的价格路径。

2. 代码复现

   为了计算的速度以及整体过程的简洁性,考虑将数据抽象出来,使用矩阵的思路来复现整个过程。考虑到文章的适读性,本章节将诸多步骤进行细化,尽可能将过程解释清楚,读者可根据自身理解,对各个小节的内容进行选择性的详略阅读。

2.1 价格路径

2.1.1 单次路径

  此小节为步骤的细化分享,可选择性阅读。

  首先设置基本的参数,得到初始的路径矩阵。

import numpy as np# 设置参数,分别是无风险利率,波动率和剩余期限
r = 0.025
vol = 0.22
residual_day = 252
# 设置初始值,需要模拟的路径数量
St = 1
path_num = residual_day
# 创建矩阵,并把初始值设为1,将时间差折算成年
s_path = np.zeros((path_num, 1))
s_path[0] = 1
Tt = 1/252 # 折算成年的时间差,此处以交易日为单位

  得到的结果如下:

    s_path是一个252行,1列的矩阵。接下来,根据初始值1,迭代矩阵的第二个数据,考虑到随机数可能产生异常的数值,因此需要对迭代的数据进行涨跌停的限制。

# 由初始值迭代第二个数据
N = np.random.standard_normal(1)  # 标准正态分布随机数
ST = s_path[0] * np.exp((r - 0.5 * vol ** 2) * Tt + vol * np.sqrt(Tt) * N)
# 进行涨跌停的限制
max_st = s_path[0] * 1.1
min_st = s_path[0] * 0.9
new_st = np.where(ST < min_st,  min_st, ST)
new_st = np.where(ST > max_st, max_st, new_st)
s_path[1] = new_st

对应得到的结果是:

  通过上述代码进行循环迭代,得到单次路径的所有数据:

for i in range(1,path_num):N = np.random.standard_normal(1)  # 标准正态分布随机数ST = s_path[i-1] * np.exp((r - 0.5 * vol ** 2) * Tt + vol * np.sqrt(Tt) * N)# 进行涨跌停的限制max_st = s_path[i-1] * 1.1min_st = s_path[i-1] * 0.9new_st = np.where(ST < min_st, min_st, ST)new_st = np.where(ST > max_st, max_st, new_st)s_path[i] = new_st

   当前即可得到对应单次的完整路径,对应的结果为:

  将s_path对应得到的数据进行可视化:

# 单次路径可视化
import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
x = [x+1 for x in range(path_num)]
plt.plot(x,s_path,label='单次路径')
plt.legend()
plt.show()

  对应等到的结果为:

2.1.2 多次路径

    由于单次的路径存在较大的随机性,因此需要进行多次的模拟,此处暂时设定模拟次数为5000次。如果在单次路径的基础上添加循环继续模拟其他的路径,速度相对会比较慢,因此考虑还是使用矩阵来进行迭代。

   首先设立基本参数,得到基础的路径矩阵。

import numpy as np# 设置参数,分别是无风险利率,波动率和剩余期限
r = 0.025
vol = 0.22
residual_day = 252
# 设置初始值,需要模拟的路径数量
St = 1
path_num = residual_day
times = 5000 # 模拟次数
# 创建矩阵,并把初始值设为1,将时间差折算成年
s_path = np.zeros((path_num, times))
s_path[0] = 1
Tt = 1/252 # 折算成年的时间差,此处以交易日为单位

  对应得到的结果为:

   此处的s_path是一个252行(代表日期),5000列(代表路径数量)。接下来,演示一下第二列价格的生成方式。

N = np.random.standard_normal(times)  # 标准正态分布随机数
ST = s_path[0] * np.exp((r - 0.5 * vol ** 2) * Tt + vol * np.sqrt(Tt) * N)
# 进行涨跌停的限制
max_st = s_path[0] * 1.1
min_st = s_path[0] * 0.9
new_st = np.where(ST < min_st,  min_st, ST)
new_st = np.where(ST > max_st, max_st, new_st)
s_path[1] = new_st

  对应得到的解惑为:

   到此处,个人感觉基本将路径生成的思路说清楚了。后续不再进行赘述,开始封装一个函数,来实现路径模拟的功能。

import numpy as np
import matplotlib.pyplot as pltdef get_s_path(St, r, vol, residual_day, times):'''模拟标的未来可能出现的价格路径:param St: int,标的初始价格,例如1:param r: int, 无风险利率,例如0.02:param vol: int, 波动率,例如0.02:param residual_day: int, 剩余期限(单位为天),例如252:param times: int, 模拟次数,例如5000:return:  价格路径对应的矩阵'''path_num = residual_days_path = np.zeros((path_num, times))s_path[0] = StTt = 1 / 252for i in range(1, path_num):N = np.random.standard_normal(times)  # 标准正态分布随机数ST = s_path[i - 1] * np.exp((r - 0.5 * vol ** 2) * Tt + vol * np.sqrt(Tt) * N)# 进行涨跌停的限制max_st = s_path[i - 1] * 1.1min_st = s_path[i - 1] * 0.9new_st = np.where(ST < min_st, min_st, ST)new_st = np.where(ST > max_st, max_st, new_st)s_path[i] = new_streturn s_pathif __name__ == '__main__':r = 0.025vol = 0.22residual_day = 252St = 1times = 5000price_path = get_s_path(St, r, vol, residual_day, times)

   对应得到的结果是:

 将得到的结果进行可视化:

    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']plt.rcParams['axes.unicode_minus'] = Falsex = [x+1 for x in range(residual_day)]for i in range(times):plt.plot(x,price_path[:,i])plt.title('标的价格模拟路径'+'('+'模拟次数:'+str(times)+')')plt.show()

2.2 雪球定价

2.2.1 加入要素

   在获取标的价格的模拟路径后,需要在路径上添加雪球对应的要素结构。此处代码续接上一小节的price_path。

    # 雪球要素knock_out = 1.03  # 敲出价格knock_in = 0.75  # 敲入价格coupon = 0.2  # 年化票息lock_time = 3  # 锁定期,单位为月T = 1  # 期限,折算为年all_time = T * 252  # 期限,折算为天survival_day = all_time - residual_day  # 已存续期限# 利用要素对路径进行判断one_path = price_path[:, 0]# 价格低于敲入价格的交易日对应的序号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个交易日,因此一个月设定为12obs_may_day = new_out_day[new_out_day % 21 == 0]# 由于有锁定期,需要剔除前三个月的敲出观察日obs_day = obs_may_day[obs_may_day / 21 > 3]

  此处,需要注意两点:

1. 未使用具体日期来作为观察日的检测,由于数据本身是模拟的,同时模拟的次数较大。因此采取估算的思路来处理观察日与使用具体日期的差异不大,同时这样处理能有效提升运算的效率。

2. 由于敲出观察日事先是固定的,因此对于已存续一段时间的雪球,其敲出观察日的序号需要加上已存续的交易日,这样才能使得观察日的序号对称。举个例子:某雪球已经存续60个交易日,在对第61个交易日进行估值时,模拟路径对应的第一个交易日,实际是作为雪球的第61个交易日。

   上述思路确实有点绕,难以用文字完全表述清楚,只能靠读者自身去理解感悟,有种‘名可名,非常名’的意思。

2.2.2 估值定价

   定价计算需要区分雪球在已存续的时间里是否发生过敲入。首先,讨论常规的情况,即在当前估值节点前,雪球未发生过敲入。

    status = 'out'# 过去未发生敲入if status == 'out':# 情况1:未来发生过敲出if len(obs_day) > 0:t = obs_day[0]re = coupon * (t / 252) * np.exp(-r * t / 252)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:# 期末净值大于等于1if one_path[-1] >= 1:re = 0nav = 1 * (1 + re)# 期末净值小于1elif 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 / 252) * np.exp(-r * t / 252)nav = 1 * (1 + re)  # 1表示期初的净值else:# 情况2:未来未发生过敲出# 期末净值大于等于1if one_path[-1] >= 1:re = 0nav = 1 * (1 + re)# 期末净值小于1elif one_path[-1] < 1:re = (one_path[-1] - 1) * np.exp(-r * T)nav = 1 * (1 + re)

   在上述代码的基础上,循环5000次,计算所有nav的均值,即对应当前节点雪球的预估净值。 

3. 完整代码

    前文讲的相对较细,也显得有些凌乱,希望整体上是说清楚了。此处,对以上代码进行封装处理,以便查阅代码的完整性。

import numpy as npdef get_s_path(St, r, vol, residual_day, times):'''模拟标的未来可能出现的价格路径:param St: int,标的初始价格,例如1:param r: int, 无风险利率,例如0.02:param vol: int, 波动率,例如0.02:param residual_day: int, 剩余期限(单位为天),例如252:param times: int, 模拟次数,例如5000:return:  价格路径对应的矩阵'''path_num = residual_days_path = np.zeros((path_num, times))s_path[0] = StTt = 1 / 252for i in range(1, path_num):N = np.random.standard_normal(times)  # 标准正态分布随机数ST = s_path[i - 1] * np.exp((r - 0.5 * vol ** 2) * Tt + vol * np.sqrt(Tt) * N)# 进行涨跌停的限制max_st = s_path[i - 1] * 1.1min_st = s_path[i - 1] * 0.9new_st = np.where(ST < min_st, min_st, ST)new_st = np.where(ST > max_st, max_st, new_st)s_path[i] = new_streturn s_pathdef 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,预估的雪球净值'''all_time = T * 252  # 期限,折算为天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个交易日,因此一个月设定为12obs_may_day = new_out_day[new_out_day % 21 == 0]# 由于有锁定期,需要剔除前三个月的观察日obs_day = obs_may_day[obs_may_day / 21 > lock_time]# 过去未发生敲入if status == 'out':# 情况1:未来发生过敲出if len(obs_day) > 0:t = obs_day[0]re = coupon * (t / 252) * np.exp(-r * t / 252)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:# 期末净值大于等于1if one_path[-1] >= 1:re = 0nav = 1 * (1 + re)# 期末净值小于1elif 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 / 252) * np.exp(-r * t / 252)nav = 1 * (1 + re)  # 1表示期初的净值else:# 情况2:未来未发生过敲出# 期末净值大于等于1if one_path[-1] >= 1:re = 0nav = 1 * (1 + re)# 期末净值小于1elif one_path[-1] < 1:re = (one_path[-1] - 1) * np.exp(-r * T)nav = 1 * (1 + re)else:nav = 0new_value = navreturn new_valuedef get_snowball_nav(status, coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path):# 获取多次模拟路径下雪球的估值# price_path:模拟的标的路径# 其余参数和get_one_nav相同nav_list = []times = np.array([price_path]).shape[-1]  # 价格矩阵的列数for num in range(times):one_path = price_path[:, num]one_nav = get_one_nav(status, coupon, T, r, residual_day, one_path, knock_in, knock_out, lock_time)nav_list.append(one_nav)return np.mean(nav_list)if __name__ == '__main__':r = 0.025vol = 0.22residual_day = 252St = 1times = 5000price_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  # 期限,折算为年status_in = 'in'in_nav = get_snowball_nav(status_in, coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path)status_out = 'out'out_nav = get_snowball_nav(status_out, coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path)

  对应得到的结果为:

   关于定价部分的代码已经实现,文章篇幅已经较长,因此决定在后续的文章中继续分享代码的实际应用,以及各个自变量对雪球估值的影响等。

本期分享到此结束,有何问题欢迎交流。

这篇关于期权专题9:雪球期权(一)(BSM+蒙特卡罗定价)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/626418

相关文章

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

专题二_滑动窗口_算法专题详细总结

目录 滑动窗口,引入: 滑动窗口,本质:就是同向双指针; 1.⻓度最⼩的⼦数组(medium) 1.解析:给我们一个数组nums,要我们找出最小子数组的和==target,首先想到的就是暴力解法 1)暴力: 2)优化,滑动窗口: 1.进窗口 2.出窗口 3.更新值 2.⽆重复字符的最⻓⼦串(medium) 1)仍然是暴力解法: 2)优化: 进窗口:hash[s[rig

hot100刷题第1-9题,三个专题哈希,双指针,滑动窗口

求满足条件的子数组,一般是前缀和、滑动窗口,经常结合哈希表; 区间操作元素,一般是前缀和、差分数组 数组有序,更大概率会用到二分搜索 目前已经掌握一些基本套路,重零刷起leetcode hot 100, 套路题按套路来,非套路题适当参考gpt解法。 一、梦开始的地方, 两数之和 class Solution:#注意要返回的是数组下标def twoSum(self, nums: Lis

数字电路专题:verilog 阻塞赋值和非阻塞赋值

verilog 阻塞赋值 和 非阻塞赋值 “=”阻塞赋值, ”<=”非阻塞赋值。阻塞赋值为执行完一条赋值语句,再执行下一条,可理解为顺序执行,而且赋值是立即执行; 非阻塞赋值可理解为并行执行,不考虑顺序,在 always 块语句执行完成后,才进行赋值。 如下面的阻塞赋值: //代码如下:module top(din,a,b,c,clk);input din;input clk;out

算法专题一: 双指针

目录 前言1. 移动零(easy)2. 复写零(easy)3. 快乐数(medium)4. 盛水最多的容器(medium)5. 有效三角形的个数(medium)6. 和为 s 的两个数字(easy)7. 三数之和(medium)8. 四数之和(medium) 前言 常见的双指针有两种形式,一种是对撞指针,一种是左右指针。 1. 对撞指针: ⼀般用于顺序结构中,也称左右指针。

《黑神话:悟空》专题合集MOD/修改器/壁纸/音乐/CG剧情

《黑神话:悟空》专题合集」 链接:https://pan.quark.cn/s/d67857f4e308 包含内容: 《黑神话:悟空》MOD合集 《黑神话:悟空》修改器(风灵月影) 《黑神话:悟空》壁纸合集 《黑神话:悟空》3小时CG完整剧情合集 4K120帧最高画质!国语 简中字幕 附:4K 结尾动画合集 ​​​国语 简中字幕 《黑神话:悟空》主题曲 《黑神话

2014暑假集训搜索专题

A - 漫步校园 Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Status Description LL最近沉迷于AC不能自拔,每天寝室、机房两点一线。由于长时间坐在电脑边,缺乏运动。他决定充分利用每次从寝室到机房的时间,在校园里散散步。整个HDU校园呈方形布局,可划

2014级寒假特训之并查集专题

Problem A: Double和XXZ的生日宴请 Time Limit: 1 Sec   Memory Limit: 128 MB Submit: 9   Solved: 7 [ Submit][ Status][ Web Board] [ Edit] [ TestData] Description Double 和 XXZ同一天生日,他们俩30岁生日那天,当年

用于资产定价的FAFA三因素模型的案例实现

一:FAFA三因素模型的介绍 FAFA三因素模型,即Fama-French三因子模型,是在1992年提出的资产定价模型。该模型是对传统的资本资产定价模型(CAPM)的扩展,它认为除了市场风险之外,还有其他两个因素对股票的预期收益率有重要影响,这两个因素是公司规模(Size)和账面市值比(Book-to-Market Ratio)。 Fama-French三因子模型的核心观点是,投资者在承担额外