目录
参考资料
VIX介绍
总公式
T的计算:
σ计算:
代码复现
参考资料
数据、代码:GitHub - Alexdachen/ivix: 中国波指的计算
阅读文章:小C:VIX指数的来龙去脉和iVIX的计算方法 - 知乎
数据的介绍:vixDate是计算当日的日期
VIX介绍
VIX计算的是未来30天的加权波动率
总公式
总公式如上图所示,可以看出有2个未知变量T、σ:
T1:VixDate到近月合约到期日之间的时间间隔
T2:VixDate到次近月合约到期日之间的时间间隔
σ1:近月合约贡献的波动率
σ2:次近月合约贡献的波动率
时间间隔T
- 使用EXE_ENDDATE筛选找到近月和次近月合约
- 计算VixDate与合约到期日之间的时间间隔T
波动率σ
?未知变量:远期价格F、
:筛选出近月合约和次近月合约后,分别使用这2个月份的数据,计算每个月份下每个执行价格下的看涨期权的价格和看跌期权之间的价格差,选择价格差最小的那个期权的执行价格和看涨期权价格和看跌期权价格来计算远期价格,计算公式如下所示:
:选择离F最近且小于F的期权执行价,找到K0后再进行一次数据筛选,选择执行价格小于K0的看跌期权,选择执行价格大于K0的看涨期权
:区分好近月和次近月合约后,筛选好用于计算的期权数据后,数据按照看涨或者看跌类型排序,这个指标表示执行价格为Ki的期权的上一个期权执行价格与下一个期权执行价格之间的间隔的二分之一,如果这个价格是最高的执行价格或者最低的执行价格,这个指标取执行价Ki和临近执行价的均值。
表示执行价格为Ki的期权价格
R:无风险利率,使用插值法获得。
代码复现
导入库
from datetime import datetime
import numpy as np
import pandas as pd
from scipy.interpolate import make_interp_spline #原代码中的spline已经不兼容了,代码会报错
读取数据
#读取利率数据
shibor_rate = pd.read_csv("shibor.csv",index_col=0,encoding='GBK') #index_col参数注明index列
#读取期权交易数据options_data
options_data = pd.read_csv("options.csv",index_col=0,encoding='GBK')
#读取交易日数据,画图时作为横坐标,一共829天
tradeday = pd.read_csv('tradeday.csv',encoding='GBK')
#读取真实披露的ivix
true_ivix = pd.read_csv("ivixx.csv",encoding='GBK')
筛选当天交易的期权数据
def getHistDayOptions(vixDate,options_data):
#提取交易日为VixDate的期权数据
options_data = options_data.loc[vixDate,:] #loc是index索引,提取所有index为vixDate的行
return options_data
计算近月合约和次近月合约
def getNearNextOptExpDate(options,vixDate):
#输入:期权交易数据、vixDate
#找到options中的当月和次月期权到期日
#如果options中近月合约期权离到期日仅剩7天以内,则选择次近月期权为近月期权
#返回:近月合约和次近月合约的到期日
vixDate = datetime.strptime(vixDate,'%Y/%m/%d') #strptime是将字符串格式的日期转换为datetime格式
#ravel:将到期日期数组去重后放入列表中
optionsExpDate = list(pd.Series(options.EXE_ENDDATE.values.ravel()).unique())
optionsExpDate = [datetime.strptime(i,'%Y/%m/%d %H:%M')for i in optionsExpDate]
near = min(optionsExpDate)
optionsExpDate.remove(near) #remove方法是删除元素
#如果近月合约到期时间小于7天,将选择次近月合约当做近月合约
if near.day-vixDate.day < 7:
near = min(optionsExpDate)
optionsExpDate.remove(near)
nt = min(optionsExpDate)
#筛选options中属于近月和次近月合约之间数据
#near、nt是datetime格式
return near,nt
无风险利率计算
def periodsSplineRiskFreeInterestRate(options,date):
#options是date日交易的期权数据
#返回date到每个到期日exoDate间的无风险利率
date = datetime.strptime(date,'%Y/%m/%d')
#这个地方的options是vixDate日交易的期权数据
exp_dates = np.sort(options.EXE_ENDDATE.unique()) #使用sort方法对所有的到期日期进行排序
#periods键是到期日期、值是到期日期与vixDate之间的时间间隔
periods = {}
for epd in exp_dates:
epd = pd.to_datetime(epd)
periods[epd] = (epd - date).days*1.0/365.0 #datetime格式的数据是一个三元组,时间元组之间求时间间隔
shibor_date = datetime.strptime(shibor_rate.index[0],"%Y-%m-%d") #shibor_rate.index[0]是指的是第1和索引“2018-07-13 ”
#插值之前先进行判断:
#shibor是按照时间降序排序,shibor_date是可获取到的最大日期
#VixDate日期大于shibor_date,使用shibor_date来插值,即“2018-07-13那日的shibor数据
if date>=shibor_date:
date_str = shibor_rate.index[0]
shibor_values = shibor_rate.ix[0].values
else:#vixDate日期小于shibor_date时,使用VixDate日披露的shibor数据进行插值
date_str = date.strftime("%Y-%m-%d")
shibor_values = shibor_rate.loc[date_str].values
shibor = {}
period = np.array([1.0,7.0,14.0,30.0,90.0,180.0,270.0,360.0])/360.0
for p in periods.keys():#之前写成了key报错了
tmp = periods[p]
#使用shibor插值
#使用spline方法会报错:module 'scipy.interpolate' has no attribute 'spline'
sh = make_interp_spline(period,shibor_values)(tmp)
#sh = interpolate.spline(period,shibor_values,tmp,order=3)
shibor[p] = sh/100.0
return shibor
#shibor是一个字典,键是时间间隔,值是插值得到的无风险利率
计算价格差距
def getStrikeMinCallMinusPutClosePrice(options):
#返回同一期权行权价对应的call和put期权价格差
call = options[options.EXE_MODE==u"认购"].set_index(u"EXE_PRICE").sort_index() #sort_index默认按照行标签升序排列
put = options[options.EXE_MODE==u"认沽"].set_index(u"EXE_PRICE").sort_index()
callMinusPut = call.CLOSE-put.CLOSE
strike = abs(callMinusPut).idxmin() #idxmin()计算能够获得获取到的最小值的索引位置(整数)
priceDiff = callMinusPut[strike].min()
return strike,priceDiff
波动率
def calSigmaSquare( options, FF, R, T):
# 计算某个到期日期权对于VIX的贡献sigma;
# 输入为期权数据options,FF为forward index price,
# R为无风险利率, T为期权剩余到期时间
#输出:近期合约或者远期合约的sigma
"""
params: options:近月合约和次近月合约的交易数据
FF: 根据上一步计算得来的strike,然后再计算得到的forward index price, 根据它对所需要的看涨看跌合约进行划分。
取小于FF的第一个行权价为中间行权价K0, 然后选取大于等于K0的所有看涨合约, 选取小于等于K0的所有看跌合约。
对行权价为K0的看涨看跌合约,删除看涨合约,不过看跌合约的价格为两者的均值。
R: 这部分期权合约到期日对应的无风险利率 shibor
T: 还有多久到期(年化)
return:Sigma:得到的结果是传入该到期日数据的Sigma
"""
callAll = options[options.EXE_MODE==u"认购"].set_index(u"EXE_PRICE").sort_index()
putAll = options[options.EXE_MODE==u"认沽"].set_index(u"EXE_PRICE").sort_index()
callAll['deltaK'] = 0.05
putAll['deltaK'] = 0.05
# 按照看涨、看跌期权排序时,计算执行价格间隔
index = callAll.index
if len(index) < 3:#只有2个看涨期权或者1个看涨期权
callAll['deltaK'] = index[-1] - index[0]
else:
for i in range(1,len(index)-1):
callAll['deltaK'].loc[index[i]] = (index[i+1]-index[i-1])/2.0 #执行价格间隔等于前后两个执行价格之差的一半
callAll['deltaK'].loc[index[0]] = index[1]-index[0] #第一个和最后一个执行价格使用它们与邻近的执行价格之间的差
callAll['deltaK'].loc[index[-1]] = index[-1] - index[-2]
#原代码中的ix需要替换为loc,因为会报 'Series' object has no attribute 'ix' ix替换为loc
index = putAll.index
if len(index) < 3:
putAll['deltaK'] = index[-1] - index[0]
else:
for i in range(1,len(index)-1):
putAll['deltaK'].loc[index[i]] = (index[i+1]-index[i-1])/2.0
putAll['deltaK'].loc[index[0]] = index[1]-index[0]
putAll['deltaK'].loc[index[-1]] = index[-1] - index[-2]
call = callAll[callAll.index > FF]
put = putAll[putAll.index < FF]
FF_idx = FF #FF是计算出来的远期价格 FF_idx是K0
if put.empty:#如果用于计算的看跌期权的数据为
FF_idx = call.index[0]
callComponent = call.CLOSE*call.deltaK/call.index/call.index
sigma = (sum(callComponent))*np.exp(T*R)*2/T
sigma = sigma - (FF/FF_idx - 1)**2/T
elif call.empty: #如果用于计算的看涨期权数据为空
FF_idx = put.index[-1]
putComponent = put.CLOSE*put.deltaK/put.index/put.index
sigma = (sum(putComponent))*np.exp(T*R)*2/T
sigma = sigma - (FF/FF_idx - 1)**2/T
else:
FF_idx = put.index[-1]
#当看涨期权和看跌期权都拥有的时候,平值期权被划分为了看跌期权,此时这个期权的价格应该去看涨期权和看跌期权价格的一半
#我把try-except去掉后,出现setting an array element with a sequence问题
#去原始数据查看了一下,有这样一种情况:执行价格相同,但是有两个看涨期权收盘价和两个看跌期权,这是因为这是两个不同的合约
#因为一开始筛选数据是通过日期和执行价格筛选的,不能区分出来,只能通过合约名字看得出来
#put['CLOSE'].iloc[-1] = (putAll.loc[FF_idx].CLOSE + callAll.loc[FF_idx].CLOSE)/2.0
try:
if len(putAll.loc[FF_idx].CLOSE.values) > 1:
put['CLOSE'].iloc[-1] = (putAll.loc[FF_idx].CLOSE.values[1] + callAll.loc[FF_idx].CLOSE.values[0])/2.0
except:
put['CLOSE'].iloc[-1] = (putAll.loc[FF_idx].CLOSE + callAll.loc[FF_idx].CLOSE)/2.0
callComponent = call.CLOSE*call.deltaK/call.index/call.index
putComponent = put.CLOSE*put.deltaK/put.index/put.index
sigma = (sum(callComponent)+sum(putComponent))*np.exp(T*R)*2/T
sigma = sigma - (FF/FF_idx - 1)**2/T
return sigma
时间转换函数
唯一有点迷糊的是这里为什么要分类讨论,但是不区分又会报错
def changeste(t):
if t.month>=10:
str_t = t.strftime('%Y/%m/%d ')+'0:00'
else:
str_t = t.strftime('%Y/%m/%d ')
str_t = str_t[:5]+str_t[6:]+'0:00'
return str_t
计算每日的Vix
def calDayVix(vixDate):
#提取在vixDate日交易的期权数据
options = getHistDayOptions(vixDate,options_data)
#获取近月合约和次近月合约的到期日期,格式为datetime格式
near,nexts = getNearNextOptExpDate(options,vixDate)
#提取近期合约期权数据和次近月合约期权数据
str_near = changeste(near)
str_next = changeste(nexts)
optionsNearTerm = options[options.EXE_ENDDATE==str_near]
optionsNextTerm = options[options.EXE_ENDDATE==str_next]
#获取vixDate到近月合约和次近月合约之间的无风险利率
shibor_near = periodsSplineRiskFreeInterestRate(optionsNearTerm,vixDate)
shibor_next = periodsSplineRiskFreeInterestRate(optionsNextTerm,vixDate)
R_near = shibor_near[datetime(near.year,near.month,near.day)]
R_next = shibor_next[datetime(nexts.year,nexts.month,nexts.day)]
#计算剩余到期时间
vixDate = datetime.strptime(vixDate,'%Y/%m/%d')
T_near = (near-vixDate).days/365.0
T_next = (nexts-vixDate).days/365.0
#计算远期价格
nearPriceDiff = getStrikeMinCallMinusPutClosePrice(optionsNearTerm)
nextPriceDiff = getStrikeMinCallMinusPutClosePrice(optionsNextTerm)
near_F = nearPriceDiff[0] + np.exp(T_near*R_near)*nearPriceDiff[1]
next_F = nextPriceDiff[0] + np.exp(T_next*R_next)*nextPriceDiff[1]
#计算不同到期日期权对于VIX的贡献
near_sigma = calSigmaSquare(optionsNearTerm,near_F,R_near,T_near)
next_sigma = calSigmaSquare(optionsNextTerm,next_F,R_next,T_next)
w = (T_next - 30.0/365.0)/(T_next - T_near)
vix = T_near*w*near_sigma + T_next*(1-w)*next_sigma
return 100*np.sqrt(abs(vix)*365.0/30.0)
主函数
因为一开始程序有错误,只能计算400多个指数,于是我把日期和指数对应起来放到了一个字典中,下面作图的时候需要列表格式,我重新写了一个格式转换函数
ivix = {}
for day in tradeday['DateTime']:
#ivix.append(calDayVix(day))
ivix[day] = calDayVix(day)
绘图
def change2f(old_list):
#保留2位小数,是因为画图的时候发现显示数据很乱
#主函数中计算出来的数据的指数是series格式,不是list格式,需要转换一下
mid_np = np.array(old_list) #列表转数组
mid_np_2f = np.round(mid_np,2) #对数组中的元素保留两位小数
new_list = list(mid_np_2f) #数组转列表
return new_list
#画图
#pyecharts因为原代码使用的版本不兼容,需要用以下方式导入,是去看pyecharts的官方文档的例子写的
from pyecharts.charts import Line
from pyecharts import options as opts
datetime = list(tradeday['DateTime'])
data1 = true_ivix[u"收盘价(元)"]
data1 = change2f(data1)
data2 = list(ivix.values())
data2 = change2f(data2)
line1 = Line()
line1.add_xaxis(datetime)
line1.add_yaxis(series_name='中证指数发布',
y_axis=data1,
label_opts=opts.LabelOpts(is_show=False),
markpoint_opts=opts.MarkPointOpts(
data=[opts.MarkPointItem(type_="max", name="最大值"),opts.MarkPointItem(type_="min", name="最小值"),]),
markline_opts=opts.MarkLineOpts(
data=[opts.MarkLineItem(type_="average", name="平均值")]
))
line1.add_yaxis(series_name='手动计算',
y_axis=data2,
label_opts=opts.LabelOpts(is_show=False),
markpoint_opts=opts.MarkPointOpts(
data=[opts.MarkPointItem(type_="max", name="最大值"),opts.MarkPointItem(type_="min", name="最小值"),]),
markline_opts=opts.MarkLineOpts(
data=[opts.MarkLineItem(type_="average", name="平均值")]
))
放一个自己画的图:
|