【前方高能!】Gifts from Santa Claus——股指期货趋势交易研究

最后更新于:2022-04-01 21:59:22

# 【前方高能!】Gifts from Santa Claus——股指期货趋势交易研究 > 来源:https://uqer.io/community/share/567ca9b1228e5b3444688438 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-31_579d7a0360495.png) ## 股指期货的前世今生 股指期货,名曰期指,乃期货界的明珠,现货对应的是股票指数,其逼格比之大豆棉油不知道甩几条巷子。期指的上市为股票市场提供了很好的风险管理和做策略的工具,矿工们也觉得自己终于能吃上一碗带肉的康师傅牛肉面。正所谓侠之大者,为国背锅,这一年股指期货经历了不平凡的跌宕起伏。上半年市场一片开朗,10000点不是梦,期指也由原来的 'IF(沪深300)'一个品种增加到了‘IH(上证50)’,‘IF(沪深300)’,‘IC(中证500)’三个品种。“放开让孩儿们玩吧”,不愿透露姓名的领导心里想着,顺势掐灭了手上的烟头。不幸的是,市场下半年被玩坏了,从去杠杆到查做空、从基金经理被约谈到公安部出动、从券商砸奖金到救市基金强势入场,轰轰烈烈的市场拯救活动持续了一两个月,终于把市场给摁了下来。比较不幸的是,量化交易和衍生品受到了比较严重的打击和监管。股指期货的交易方式、保证金比例、交易频率都受到了严厉的限制,并成为众矢之的。 虽不知股指期货的未来何去何从,但效率的提升才能代表未来的发展方向,投资管理变得更为理性化、程序化、智能化对于专业的投资机构来讲是大势所趋。我们相信期指的严冬期总会过去,我们有理想,我们有愿望,宣誓完毕! ## 股指期货的才艺 一个品种的股指期货有四份合约,分为当月合约、次月合约、第一季月合约和第二季月合约。作为衍生品,它为二级市场投资带来了丰富的投资方式,并在投资策略中担当着各种各样的角色。基于股指期货可做的事情例如: + 1、构建alpha市场中性策略; + 2、期限套利; + 3、期指间跨期跨品种套利; + 4、趋势交易; + 5、高频交易(现在估计比较难)。 这次我们主要研究一种单品种的股指期货趋势交易策略,提供了一种基于市场情绪的择时指标,称之为ITS(Informed Trader Sentiment),参考文献为。 ## ITS——基于市场情绪的择时指标 中金所每日收盘后会公布股指期货的成交持仓量表,表单中会有三个项目的排名:成交量、持买单量和持卖单量,表单会列出前20名的会员单位。这些会员单位代表了期货市场上最大的机构投资者。我们将同时出现在三个表单排名中的单位视作VIP单位,他们是这个市场的中流砥柱,他们的投资情绪会影响到市场未来的走势,我们需要做的就是找到对的大佬,跟着大佬的情绪飞。那如何才能跟到对的大佬?我们基于这样一个逻辑:如果某位大佬更有预知市场的能力,那么他会在交易中更为坚定的选择某一个买卖方向,它由于买卖带来的成交量就会相对较小,进而(持仓量/成交量)数值相对会大,我们认定他是知情者(对的大佬);若某个大佬的持仓量与前一位大佬相当但成交量明显偏大,说明这位大佬的交易有更多的不确定性,我们也就认定他不是一个知情者(没有缘分的大佬)。找到对的大佬之后,便开始估测一下大佬的情绪。我们用“对的大佬”们的(持买单量-持卖单量)/(持买单量+持卖单量)(此处的持买单和卖单量都是对的大佬们求和的总量)作为归一化的指标,根据指标是否大于某个阈值Thres来判定大佬对于市场是看多还是看空。那么我们的信号提取过程为: + step1. 表单中筛选VIP单位(三个排名均上榜)→ 备选大佬; + step2. 备选大佬中找到(持仓量/成交量)大于Avg的VIP单位 → 对的大佬; + step3. ITS = (持买单量-持卖单量)/(持买单量+持卖单量) ## ITS信号生成器 ```py from CAL.PyCAL import * import copy as cp import numpy as np ########################################################################################################### # generate the its signal ########################################################################################################### class itsFutSignal: def __init__(self,secID,currentDate): ####input self.secID = secID self.currentDate = currentDate ####output self.sigDate = self.lastDateGet() self.contract = self.currContractGet() self.posTable = self.posTableGet() self.vipDict = self.vipDictGet() self.sentiSig = self.sentiSigGet() self.inforedInvestor = self.inforedInvestorGet() self.itsSig = self.itsSigGet() def lastDateGet(self): calendar = Calendar('China.SSE') date = calendar.advanceDate(self.currentDate,'-'+str(1)+'B').toDateTime() return date def currContractGet(self): future = DataAPI.MktMFutdGet(mainCon=u"1",contractMark=u"",contractObject=u"",tradeDate=self.sigDate,startDate=u"",endDate=u"",field=u"",pandas="1") for index in future.ticker: if index[:2] == self.secID: return future[future.ticker==index].ticker.tolist()[0] def posTableGet(self): try: pos = DataAPI.MktFutMLRGet(secID=u"",ticker=self.contract,beginDate=self.sigDate,endDate=self.sigDate,field=u"",pandas="1") neg = DataAPI.MktFutMSRGet(secID=u"",ticker=self.contract,beginDate=self.sigDate,endDate=self.sigDate,field=u"",pandas="1") vol = DataAPI.MktFutMTRGet(secID=u"",ticker=self.contract,beginDate=self.sigDate,endDate=self.sigDate,field=u"",pandas="1") return {'pos':pos,'neg':neg,'vol':vol} except: calendar = Calendar('China.SSE') self.sigDate = calendar.advanceDate(self.sigDate,'-'+str(1)+'B').toDateTime() self.contract = self.currContractGet() return self.posTableGet() def list2Dict(self,list): keys = list[0] values = list[1] resultDict = {} for index in range(len(keys)): resultDict[keys[index]] = values[index] return resultDict def vipDictGet(self): ####get data longDict = self.list2Dict([self.posTable['pos'].partyShortName.tolist(),self.posTable['pos'].longVol]) shortDict = self.list2Dict([self.posTable['neg'].partyShortName.tolist(),self.posTable['neg'].shortVol]) volDict = self.list2Dict([self.posTable['vol'].partyShortName.tolist(),self.posTable['vol'].turnoverVol]) ####get vip list vipList = [] for index in longDict.keys(): if index in shortDict.keys(): if index in volDict.keys(): vipList.append(index) ####get vip dict vipDict = {} for index in vipList: vipDict[index] = [longDict[index],shortDict[index],volDict[index]] return vipDict def sentiSigGet(self): sentiSig = {} for index in self.vipDict: sentiSig[index] = sum(self.vipDict[index][:2])*1.0/self.vipDict[index][-1] return sentiSig def inforedInvestorGet(self): if len(self.sentiSig) != 0: sentiAvg = sum(self.sentiSig.values())/len(self.sentiSig) inforedInvestor = [index for index in self.sentiSig if self.sentiSig[index] > sentiAvg] return inforedInvestor else: sentiAvg = 0 return [] def itsSigGet(self): totalBuy = 0 totalSell = 0 if len(self.inforedInvestor) != 0: for index in self.inforedInvestor: totalBuy += self.vipDict[index][0] totalSell += self.vipDict[index][1] if totalBuy + totalSell != 0: return 1.0*(totalBuy - totalSell)/(totalBuy + totalSell) else: return 'null' else: return 'null' ``` ## ITS趋势交易信号 ITS信号代表了对的大佬们的情绪,那么如何利用它给出每日的交易信号呢?我们利用最为直观有效的阈值策略,设定某阈值`Thres`: 1)`ITS > Thres`: 大佬看多,买买买,交易信号为1; 2)`ITS < Thres`: 大佬看空,卖卖卖,交易信号为-1; 3)`ITS = Thres & DataErr`: 形势不明朗或找不到大佬,停止交易观望,交易信号为0 此处我们取 `Thres = -0.12`? Why?其实是这样的,由于期现套利交易的存在,因此期指本身有一部分的空单是由于期限套利造成的,由于这部分资金通常会留存较久,因此通常情况下期指的持仓总量应该是空单偏多,而我们判断市场情绪的时候要把这部分期货市场上的“裸空单”给剔除掉,因此Thres应该设置为负。 ## IF趋势交易测试 由于目前Strategy部分还不好支持期指的交易回测,因此用脚本生成了测试数据。 1)交易标的:沪深300主力合约。用IF来测试的原因很简单,样本数多呀! 2)每日的交易信号:根据前一日收盘后的持仓量表单计算ITS后根据阈值产生; 3)每日收益率:我们假定在获取当日的信号后,在开盘的一段时间内以某个价格买入期指,持有至临近收盘后以某个价格卖出,做日内交易。那么买卖价如何界定?有三种方式来计算:①昨收-今收; ②今开-今收; ③昨结算-今结算。 ①和②都是时点价格,而③是均价。①必然不合理因为无法在昨日收盘前得到今日的交易信号,不具有可操作性;②是时点价格,可操作性也不强;对③来讲,由于结算价是一段时间的均价,我们认为拿这个均价作为买卖的期望价格是合理的。所以每日收益率的计算方式是③; ```py startDate = '20110101' endDate = '20150801' future = DataAPI.MktMFutdGet(mainCon=u"1",contractMark=u"",contractObject=u"",tradeDate=u'',startDate=startDate,endDate=endDate,field=u"",pandas="1") # print future closePrice = [] openPrice = [] preClosePrice = [] settlePrice = [] preSettlePrice = [] tradeDate = [] ticker = [] for index in future.ticker.tolist(): if index[:2] == 'IF': if index not in ticker: ticker.append(index) for index in ticker: closePrice += future[future.ticker==index]['closePrice'].tolist() openPrice += future[future.ticker==index]['openPrice'].tolist() preClosePrice += future[future.ticker==index]['preClosePrice'].tolist() settlePrice += future[future.ticker==index]['settlePrice'].tolist() preSettlePrice += future[future.ticker==index]['preSettlePrice'].tolist() tradeDate += future[future.ticker==index]['tradeDate'].tolist() closePrice = np.array(closePrice) openPrice = np.array(openPrice) preClosePrice = np.array(preClosePrice) settlePrice = np.array(settlePrice) preSettlePrice = np.array(preSettlePrice) closeRetRate = (closePrice - preClosePrice)/preClosePrice settleRetRate = (settlePrice - preSettlePrice)/preSettlePrice clopenRetRate = (closePrice - openPrice)/openPrice tradeDate = tradeDate itsValue = [itsFutSignal('IF',index).itsSig for index in tradeDate] itsSignal = [] thres = -0.12 for index in itsValue: if index != 'null': if index > thres: itsSignal.append(1) else: itsSignal.append(-1) else: itsSignal.append(0) itsSignal = np.array(itsSignal) benchMark = DataAPI.MktIdxdGet(tradeDate=u"",indexID=u"",ticker=u"000300",beginDate=startDate,endDate=endDate,field=u"closeIndex",pandas="1")['closeIndex'].tolist() benchMark = benchMark/benchMark[0] ``` ## 回测展示 下面为回测结果展示,测算细节如下: 1) 每日收益率根据结算价来计算,前结算价作为买入的参考均价,结算价作为卖出的参考均价; 2)收益率曲线计算采用单利计算; 3)无风险利率取5%; 4)最后一小段曲线为平是由于股指期货受到限制导致交易停止 ```py import matplotlib.pyplot as plt ####calculate the daily return dailyRet = settleRetRate*itsSignal ####calculate the winRate count = 0 denom = 0 for index in dailyRet: if index > 0: count += 1 if index != 0: denom += 1 print '策略胜率 : ' + str(round((count*1.0/denom)*100,2)) + '%' ####calculate the curve curve = [1] position = 0.8 for index in dailyRet: curve.append(curve[-1] + index*position) ####calculate the location print '策略仓位 : ' + str(position) ####calculate the max drawDown maxDrawDown = [] for index in range(len(curve)): tmp = [ele/curve[index] for ele in curve[index:]] maxDrawDown.append(min(tmp)) print '最大回撤 : ' + str(round((1-min(maxDrawDown))*100,2)) + '%' ####calculate the sharpRatio stDate = Date(int(startDate[:4]),int(startDate[4:6]),int(startDate[6:8])) edDate = Date(int(endDate[:4]),int(endDate[4:6]),int(endDate[6:8])) duration = round((edDate-stDate)/365.0,1) retYearly = curve[-1]/duration interest = 0.05 fluctuation = np.std(curve)/np.sqrt(duration) print '年化收益 : ' + str(round(retYearly,2)*100.0) + '%' print '夏普比率 : ' + str(round((retYearly-interest)/fluctuation,2)) ####plot the figure print '\n' plt.plot(curve) plt.plot(benchMark) plt.legend(['Strategy','BenchMark'],0) 策略胜率 : 55.03% 策略仓位 : 0.8 最大回撤 : 10.06% 年化收益 : 49.0% 夏普比率 : 2.44 ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc2a490e.png) 被曲线美哭了,不叨叨了,Merry Xmas! ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-31_579d7a0394716.jpg)
';

四 期货分析

最后更新于:2022-04-01 21:59:20

# 四 期货分析
';

期权每日成交额PC比例计算

最后更新于:2022-04-01 21:59:17

# 期权每日成交额PC比例计算 > 来源:https://uqer.io/community/share/55bed777f9f06c915418c62f ## P/C作为市场情绪指标 计算方式 P/C比例作为一种反向情绪指标,是看跌期权的成交量(成交额,持仓量等)与看涨期权的成交量(持仓量)的比值。 指标含义 + 看跌期权的成交量可以作为市场看空力量多寡的衡量; + 看涨期权的成交量可以描述市场看多力量。 指标应用 + 当P/C比例过小达到一个极端时,被视为市场过度乐观,此时市场将遏制原来的上涨趋势; + 当P/C比例过大到达另一个极端时,被视为市场过度悲观,此时市场可能出现反弹。 ```py from matplotlib import pylab import numpy as np import pandas as pd import DataAPI import seaborn as sns sns.set_style('white') ``` ## 1. 定义计算PCR的函数 此处计算看跌看涨期权每日成交额的比值 ```py def getHistDayOptions(var, date): # 使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息; # 同时使用DataAPI.MktOptdGet,拿到历史上某一天的期权成交信息; # 返回历史上指定日期交易的所有期权信息,包括: # optID varSecID contractType strikePrice expDate tradeDate closePrice turnoverValue # 以optID为index。 vixDateStr = date.toISO().replace('-', '') optionsMkt = DataAPI.MktOptdGet(tradeDate = vixDateStr, field = [u"optID", "tradeDate", "closePrice", "turnoverValue"], pandas = "1") optionsMkt = optionsMkt.set_index(u"optID") optionsMkt.closePrice.name = u"price" optionsID = map(str, optionsMkt.index.values.tolist()) fieldNeeded = ["optID", u"varSecID", u'contractType', u'strikePrice', u'expDate'] optionsInfo = DataAPI.OptGet(optID=optionsID, contractStatus = [u"DE", u"L"], field=fieldNeeded, pandas="1") optionsInfo = optionsInfo.set_index(u"optID") options = pd.concat([optionsInfo, optionsMkt], axis=1, join='inner').sort_index() return options[options.varSecID==var] def calDayTurnoverValuePCR(optionVarSecID, date): # 计算历史每日的看跌看涨期权交易额的比值 # PCR: put call ratio options = getHistDayOptions(optionVarSecID, date) call = options[options.contractType==u"CO"] put = options[options.contractType==u"PO"] callTurnoverValue = call.turnoverValue.sum() putTurnoverValue = put.turnoverValue.sum() return 1.0 * putTurnoverValue / callTurnoverValue def getHistPCR(beginDate, endDate): # 计算历史一段时间内的PCR指数并返回 optionVarSecID = u"510050.XSHG" cal = Calendar('China.SSE') dates = cal.bizDatesList(beginDate, endDate) dates = map(Date.toDateTime, dates) histPCR = pd.DataFrame(0.0, index=dates, columns=['PCR']) histPCR.index.name = 'date' for date in histPCR.index: histPCR['PCR'][date] = calDayTurnoverValuePCR(optionVarSecID, Date.fromDateTime(date)) return histPCR def getDayPCR(date): # 计算历史某一天的PCR指数并返回 optionVarSecID = u"510050.XSHG" return calDayTurnoverValuePCR(optionVarSecID, date) ``` ## 2. 计算PCR指标 ```py begin = Date(2015, 2, 9) end = Date(2015, 7, 30) getHistPCR(begin, end).tail() ``` | | PCR | | --- | --- | | date | | | 2015-07-24 | 1.032107 | | 2015-07-27 | 2.097952 | | 2015-07-28 | 2.288790 | | 2015-07-29 | 1.971831 | | 2015-07-30 | 1.527717 | ```py date = Date(2015, 7, 30) getDayPCR(date) 1.5277173819619587 ``` ## 3. PC指标历史走势 ```py secID = '510050.XSHG' begin = Date(2015, 2, 9) end = Date(2015, 7, 30) # 历史PCR histPCR = getHistPCR(begin, end) # 华夏上证50ETF etf = DataAPI.MktFunddGet(secID, beginDate=begin.toISO().replace('-', ''), endDate=end.toISO().replace('-', ''), field=['tradeDate', 'closePrice']) etf['tradeDate'] = pd.to_datetime(etf['tradeDate']) etf = etf.set_index('tradeDate') font.set_size(12) pylab.figure(figsize = (12,6)) ax1 = histPCR.plot(x=histPCR.index, y='PCR', style='r') ax1.set_xlabel(u'日期', fontproperties=font) ax1.set_ylabel(u'VIX(%)', fontproperties=font) ax2 = ax1.twinx() ax2.plot(etf.index,etf.closePrice) ax2.set_ylabel(u'ETF Price', fontproperties=font) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc281366.png) 从上图可以看出,每次PC指标的上升都对应着标的价格的下挫
';

基于期权PCR指数的择时策略

最后更新于:2022-04-01 21:59:15

# 基于期权PCR指数的择时策略 > 来源:https://uqer.io/community/share/55bedc1af9f06c91f818c62d ## P/C作为市场情绪指标 计算方式 P/C比例作为一种反向情绪指标,是看跌期权的成交量(成交额,持仓量等)与看涨期权的成交量(持仓量)的比值。 指标含义 + 看跌期权的成交量可以作为市场看空力量多寡的衡量; + 看涨期权的成交量可以描述市场看多力量。 指标应用 + 当P/C比例过小达到一个极端时,被视为市场过度乐观,此时市场将遏制原来的上涨趋势; + 当P/C比例过大到达另一个极端时,被视为市场过度悲观,此时市场可能出现反弹。 策略思路 比较交易日之前两日的PCR(Put Call Ratio)指数: + PCR上升时,市场恐慌情绪蔓延,卖出 + PCR下降时,恐慌情绪有所舒缓,买入 注:国内唯一一只期权上证50ETF期权,跟踪标的为华夏上证50ETF(510050)基金 ## 1. 计算历史PCR指数 ```py from matplotlib import pylab import numpy as np import pandas as pd import DataAPI import seaborn as sns sns.set_style('white') ``` ```py def getHistDayOptions(var, date): # 使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息; # 同时使用DataAPI.MktOptdGet,拿到历史上某一天的期权成交信息; # 返回历史上指定日期交易的所有期权信息,包括: # optID varSecID contractType strikePrice expDate tradeDate closePrice turnoverValue # 以optID为index。 dateStr = date.toISO().replace('-', '') optionsMkt = DataAPI.MktOptdGet(tradeDate = dateStr, field = [u"optID", "tradeDate", "closePrice", "turnoverValue"], pandas = "1") optionsMkt = optionsMkt.set_index(u"optID") optionsMkt.closePrice.name = u"price" optionsID = map(str, optionsMkt.index.values.tolist()) fieldNeeded = ["optID", u"varSecID", u'contractType', u'strikePrice', u'expDate'] optionsInfo = DataAPI.OptGet(optID=optionsID, contractStatus = [u"DE", u"L"], field=fieldNeeded, pandas="1") optionsInfo = optionsInfo.set_index(u"optID") options = concat([optionsInfo, optionsMkt], axis=1, join='inner').sort_index() return options[options.varSecID==var] def calDayTurnoverValuePCR(optionVarSecID, date): # 计算历史每日的看跌看涨期权交易额的比值 # PCR: put call ratio options = getHistDayOptions(optionVarSecID, date) call = options[options.contractType==u"CO"] put = options[options.contractType==u"PO"] callTurnoverValue = call.turnoverValue.sum() putTurnoverValue = put.turnoverValue.sum() return 1.0 * putTurnoverValue / callTurnoverValue def getHistPCR(beginDate, endDate): # 计算历史一段时间内的PCR指数并返回 optionVarSecID = u"510050.XSHG" cal = Calendar('China.SSE') dates = cal.bizDatesList(beginDate, endDate) dates = map(Date.toDateTime, dates) histPCR = pd.DataFrame(0.0, index=dates, columns=['PCR']) histPCR.index.name = 'date' for date in histPCR.index: histPCR['PCR'][date] = calDayTurnoverValuePCR(optionVarSecID, Date.fromDateTime(date)) return histPCR def getDayPCR(date): # 计算历史一段时间内的PCR指数并返回 optionVarSecID = u"510050.XSHG" return calDayTurnoverValuePCR(optionVarSecID, date) ``` ```py secID = '510050.XSHG' begin = Date(2015, 2, 9) end = Date(2015, 7, 30) getHistPCR(begin, end).tail() ``` | | PCR | | --- | --- | | date | | | 2015-07-24 | 1.032107 | | 2015-07-27 | 2.097952 | | 2015-07-28 | 2.288790 | | 2015-07-29 | 1.971831 | | 2015-07-30 | 1.527717 | ## 2. PCR指数与华夏上证50ETF基金的走势对比 ```py secID = '510050.XSHG' begin = Date(2015, 2, 9) end = Date(2015, 7, 30) # 历史PCR histPCR = getHistPCR(begin, end) # 华夏上证50ETF etf = DataAPI.MktFunddGet(secID, beginDate=begin.toISO().replace('-', ''), endDate=end.toISO().replace('-', ''), field=['tradeDate', 'closePrice']) etf['tradeDate'] = pd.to_datetime(etf['tradeDate']) etf = etf.set_index('tradeDate') ``` ```py font.set_size(12) pylab.figure(figsize = (16,8)) ax1 = histPCR.plot(x=histPCR.index, y='PCR', style='r') ax1.set_xlabel(u'日期', fontproperties=font) ax1.set_ylabel(u'PCR(%)', fontproperties=font) ax2 = ax1.twinx() ax2.plot(etf.index,etf.closePrice) ax2.set_ylabel(u'ETF Price', fontproperties=font) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc247b6d.png) 从上图可以看出,每次PC指标的上升都对应着标的价格的下挫 ## 3. 基于PCR指数的择时策略示例 ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 7, 31) # 回测结束时间 benchmark = '510050.XSHG' # 策略参考标准 universe = ['510050.XSHG'] # 股票池 capital_base = 100000 # 起始资金 commission = Commission(0.0,0.0) longest_history = 1 histPCR = getHistPCR(start, end) def initialize(account): # 初始化虚拟账户状态 account.fund = universe[0] def handle_data(account): # 每个交易日的买入卖出指令 hist = account.get_history(longest_history) fund = account.fund # 获取回测当日的前一天日期 dt = Date.fromDateTime(account.current_date) cal = Calendar('China.IB') lastTDay = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日 lastLastTDay = cal.advanceDate(lastTDay,'-1B',BizDayConvention.Preceding) #计算出倒数第二个交易日 last_day_str = lastTDay.strftime("%Y-%m-%d") last_last_day_str = lastLastTDay.strftime("%Y-%m-%d") # 计算买入卖出信号 try: pcr_last = histPCR['PCR'].loc[last_day_str] # 计算短均线值 pcr_last_last = histPCR['PCR'].loc[last_last_day_str] # 计算长均线值 long_flag = True if (pcr_last - pcr_last_last) < 0 else False except: return if long_flag: if account.position.secpos.get(fund, 0) == 0: # 空仓时全仓买入,买入股数为100的整数倍 approximationAmount = int(account.cash / hist[fund]['closePrice'][-1]/100.0) * 100 order(fund, approximationAmount) else: # 卖出时,全仓清空 if account.position.secpos.get(fund, 0) >= 0: order_to(fund, 0) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc267299.jpg) 基于PCR指数上升时空仓、下降时进场的策略来买卖标的,可以比较有效地降低标的大跌的风险
';

期权市场一周纵览

最后更新于:2022-04-01 21:59:13

# 期权市场一周纵览 > 来源:https://uqer.io/community/share/55027679f9f06c7a9ae9a53a 本文档依赖的数据 `option_data.csv` 可以通过运行 期权高频数据准备 notebook而获取。 ```py from matplotlib import pylab import pandas as pd import seaborn as sns sns.set(style="white", context="talk") import pandas as pd pd.options.display.float_format = '{:,>.4f}'.format ``` ```py res = pd.read_csv('option_data.csv', parse_dates=['pdDateTime']) res['timeStamp'] = res['dataDate'] + ' ' + res['dataTime'] res['timeStamp'] = pd.to_datetime(res['timeStamp']) res.optionId = res.optionId.astype('str') res = res.drop('Unnamed: 0', axis=1) res.pdDateTime = res.pdDateTime.apply(lambda x:Date(x.year,x.month,x.day)) print('开始日期: ' + res['dataDate'].iloc[0]) print('结束日期: ' + res['dataDate'].iloc[-1]) print('Market Sample: ') res[['dataDate', 'dataTime', 'optionId', 'lastPrice', 'bidPrice1', 'askPrice1', 'lastPrice(vol)']].head() 开始日期: 2015-03-05 结束日期: 2015-03-09 Market Sample: ``` | | dataDate | dataTime | optionId | lastPrice | bidPrice1 | askPrice1 | lastPrice(vol) | | --- | --- | | 0 | 2015-03-05 | 09:30:00 | 10000001 | 0.1677 | 0.1717 | 0.1765 | 0.3468 | | 1 | 2015-03-05 | 09:30:14 | 10000001 | 0.1717 | 0.1717 | 0.1765 | 0.3768 | | 2 | 2015-03-05 | 09:30:15 | 10000001 | 0.1717 | 0.1610 | 0.1798 | 0.3768 | | 3 | 2015-03-05 | 09:30:16 | 10000001 | 0.1678 | 0.1610 | 0.1798 | 0.3525 | | 4 | 2015-03-05 | 09:30:18 | 10000001 | 0.1798 | 0.1641 | 0.1798 | 0.4205 | ## 1. 买卖价差分析 ### 1.1 买卖价差(到期时间) ```py bidAskSample = res[[u'optionId', 'pdDateTime', 'dataDate', 'contractType', 'strikePrice', 'bidAskSpread(bps)']] bidAskSample.columns = ['optionId', 'maturity', 'tradingDate', 'contractType', 'strikePrice', 'bidAskSpread(bps)'] tmp = bidAskSample.groupby(['maturity'])[['bidAskSpread(bps)']] ax = tmp.mean().plot(kind = 'bar', figsize = (12,6), rot = 45) ax.set_title(u'买卖价差(按照期权到期时间)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'到期时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc03680e.png) ### 1.2 买卖价差(行权价) ```py tmp = bidAskSample.groupby(['maturity', 'strikePrice'])[['bidAskSpread(bps)']].mean().unstack() ax = tmp.plot(kind = 'bar', figsize = (12,6), legend = True, rot = 45) patches, labels = ax.get_legend_handles_labels() labels = ['Strike/' + l.strip('()').split()[1] for l in labels] ax.legend(patches, labels, loc='best', prop = font) ax.set_title(u'买卖价差(按照期权行权价)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'到期时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc049d88.png) ### 1.3 买卖价差(期权类型) ```py tmp = bidAskSample.groupby(['maturity', 'contractType'])[['bidAskSpread(bps)']].mean().unstack() ax = tmp.plot(kind = 'bar', figsize = (12,6), rot = 45) patches, labels = ax.get_legend_handles_labels() labels = [l.strip('()').split()[1] for l in labels] ax.legend(patches, labels, loc='best') ax.set_title(u'买卖价差(按照期权类型)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'到期时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc064d71.png) ## 2. 日交易量分析 ```py volumeSample = res[[u'optionId', 'pdDateTime', 'dataDate', 'contractType', 'strikePrice', 'volume']] volumeSample.columns = ['optionId', 'maturity', 'tradingDate', 'contractType', 'strikePrice', 'volume'] tmp = volumeSample.groupby(['tradingDate'])[['volume']].sum() ax = tmp.plot(kind = 'bar', figsize = (12,6), rot = 45) ax.set_title(u'日交易量(按交易日期)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'交易日期', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc07bb20.png) ### 2.1 日交易量(到期时间) ```py tmp = volumeSample.groupby(['maturity', 'tradingDate'])[['volume']].sum().unstack() ax = tmp.plot(kind = 'bar', figsize = (12,6), rot = 45) patches, labels = ax.get_legend_handles_labels() labels = [l.strip('()').split()[1] for l in labels] ax.legend(patches, labels, loc='best') ax.set_title(u'日交易量(按照期权到期时间)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'到期时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc09125c.png) 每个交易日不同到期期限期权的交易量: ```py tmp ``` | | volume | | --- | --- | | tradingDate | 2015-03-05 | 2015-03-06 | 2015-03-09 | 2015-03-10 | 2015-03-11 | | maturity | | | | | | | March 25th, 2015 | 18767.0000 | 16704.0000 | 31115.0000 | 11888.0000 | 11562.0000 | | April 22nd, 2015 | 7791.0000 | 4468.0000 | 13355.0000 | 6909.0000 | 5632.0000 | | June 24th, 2015 | 965.0000 | 326.0000 | 3091.0000 | 619.0000 | 604.0000 | | September 23rd, 2015 | 635.0000 | 101.0000 | 2426.0000 | 240.0000 | 178.0000 | ### 2.2 日交易量(行权价) ```py tmp = volumeSample.groupby(['tradingDate','strikePrice'])[['volume']].sum().unstack() ax = tmp.plot(kind = 'bar', figsize = (16,8), rot = 45) patches, labels = ax.get_legend_handles_labels() labels = ['Strike/' + l.strip('()').split()[1] for l in labels] ax.legend(patches, labels, loc='best') ax.set_title(u'日交易量(按照期权行权价)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'交易日期', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc0a837e.png) 每个交易日不同行权价期权的交易量: ```py tmp ``` | | volume | | --- | --- | | strikePrice | 2.2000 | 2.2500 | 2.3000 | 2.3500 | 2.4000 | 2.4500 | 2.5000 | 2.5500 | | tradingDate | | | | | | | | | | 2015-03-05 | 2597.0000 | 1725.0000 | 3077.0000 | 5351.0000 | 5430.0000 | 4231.0000 | 3148.0000 | 2599.0000 | | 2015-03-06 | 1352.0000 | 750.0000 | 1435.0000 | 5219.0000 | 4395.0000 | 3301.0000 | 3143.0000 | 2004.0000 | | 2015-03-09 | 4576.0000 | 3407.0000 | 3599.0000 | 8954.0000 | 9564.0000 | 9015.0000 | 5969.0000 | 4903.0000 | | 2015-03-10 | 2225.0000 | 1649.0000 | 1532.0000 | 3237.0000 | 3588.0000 | 2832.0000 | 2343.0000 | 2250.0000 | | 2015-03-11 | 2021.0000 | 1286.0000 | 1299.0000 | 2959.0000 | 3121.0000 | 2648.0000 | 2565.0000 | 2077.0000 | ### 2.3 日交易量(期权类型) ```py tmp = volumeSample.groupby(['tradingDate','contractType'])[['volume']].sum().unstack() ax = tmp.plot(kind = 'bar', y = ['volume'], figsize = (12,6), rot = 45) patches, labels = ax.get_legend_handles_labels() labels = [l.strip('()').split()[1] for l in labels] ax.legend(patches, labels, loc='best') ax.set_title(u'日交易量(按照期权类型)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'交易日期', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc0c01ea.png) ## 3. 波动率价差分析 ```py bidAskVolSample = res[[u'optionId', 'pdDateTime', 'dataDate', 'contractType', 'strikePrice', 'bidAskSpread(vol bps)']] bidAskVolSample.columns = ['optionId', 'maturity', 'tradingDate', 'contractType', 'strikePrice', 'bidAskSpread(vol bps)'] ``` ### 3.1 波动率价差(到期时间) ```py tmp = bidAskVolSample.groupby(['maturity'])[['bidAskSpread(vol bps)']] ax = tmp.mean().plot(kind = 'bar', figsize = (12,6), rot = 45) ax.set_title(u'波动率价差(按照期权到期时间)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'到期时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc0d4e99.png) ### 3.2 波动率价差(行权价) ```py tmp = bidAskVolSample.groupby(['maturity', 'strikePrice'])[['bidAskSpread(vol bps)']].mean().unstack() ax = tmp.plot(kind = 'bar', figsize = (14,6), legend = True, rot = 45) patches, labels = ax.get_legend_handles_labels() labels = ['strike/' + l.strip('()').split()[-1] for l in labels] ax.legend(patches, labels, loc='best') ax.set_title(u'波动率价差(按照期权行权价)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'到期时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc0ea73c.png) ### 3.3 波动率价差(期权类型) ```py tmp = bidAskVolSample.groupby(['maturity', 'contractType'])[['bidAskSpread(vol bps)']].mean().unstack() ax = tmp.plot(kind = 'bar', figsize = (12,6), rot = 45) patches, labels = ax.get_legend_handles_labels() labels = [l.split()[-1].strip('()') for l in labels] ax.legend(patches, labels, loc='best') ax.set_title(u'波动率价差(按照期权类型)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'到期时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc10e5c3.png) ### 3.4 波动率价差(交易时间) ```py tmp = bidAskVolSample.groupby(['tradingDate', 'maturity'])[['bidAskSpread(vol bps)']].mean().unstack() ax = tmp.plot(kind = 'bar', figsize = (12,6), rot = 45) patches, labels = ax.get_legend_handles_labels() labels = [l.split(',')[1].strip('()') for l in labels] ax.legend(patches, labels, loc='best') ax.set_title(u'波动率价差(按照交易时间)', fontproperties = font, fontsize = 25) ax.set_xlabel(u'交易日期', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc121ed6.png) ## 4. 个券分析 ### 4.1 交易量 ```py tmp = volumeSample.groupby(['tradingDate','optionId'])[['volume']].sum().unstack() fig, axs = pylab.subplots(len(tmp)/2 + len(tmp)%2, 2, figsize = (16,8 * len(tmp)/2)) for i in range(len(tmp)): sample = pd.DataFrame(tmp.iloc[i]['volume']) sample.columns = ['volume'] sample = sample.sort('volume', ascending = False) sample = sample[:10] row = i / 2 col = i % 2 sample.plot(kind = 'PIE',y = 'volume', sharex= False, ax = axs[row][col], legend = False, rot = 45) axs[row][col].set_title(u'交易日: ' + str(tmp.index[i]), fontproperties = font, fontsize = 18) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc13bbb9.png) ### 4.2 买卖价差 ```py tmp = bidAskSample.groupby(['tradingDate','optionId'])[['bidAskSpread(bps)']].mean().unstack() fig, axs = pylab.subplots(len(tmp)/2 + len(tmp)%2, 2, figsize = (16,8*len(tmp)/2)) for i in range(len(tmp)): sample = pd.DataFrame(tmp.iloc[i]['bidAskSpread(bps)']) sample.columns = ['bidAskSpread(bps)'] sample = sample.sort('bidAskSpread(bps)') sample = sample[:10] row = i / 2 col = i % 2 sample.plot(kind = 'bar',y = 'bidAskSpread(bps)', sharex= False, ax = axs[row][col], legend = False, rot = 20) axs[row][col].set_title(u'交易日: ' + str(tmp.index[i]), fontproperties = font, fontsize = 18) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc16d57f.png) ### 4.3 波动率价差 ```py tmp = bidAskVolSample.groupby(['tradingDate','optionId'])[['bidAskSpread(vol bps)']].mean().unstack() fig, axs = pylab.subplots(len(tmp)/2 + len(tmp)%2, 2, figsize = (16,8*len(tmp)/2)) for i in range(len(tmp)): sample = pd.DataFrame(tmp.iloc[i]['bidAskSpread(vol bps)']) sample.columns = ['bidAskSpread(vol bps)'] sample = sample.sort('bidAskSpread(vol bps)') sample = sample[:10] row = i / 2 col = i % 2 sample.plot(kind = 'bar',y = 'bidAskSpread(vol bps)', sharex= False, ax = axs[row][col], legend = False, rot = 20) axs[row][col].set_title(u'交易日: ' + str(tmp.index[i]), fontproperties = font, fontsize = 18) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc1902b4.png) ### 4.4 时间序列分析 ```py tmp = volumeSample.groupby(['tradingDate','optionId'])[['volume']].sum().unstack() for i, d in enumerate(tmp.index): fig, axs = pylab.subplots(2, 1, figsize = (16,5)) sample = tmp.loc(d) sample = sample[d] sample.sort('volume', ascending = False) base = res[res['dataDate'] == d] base = base[base.optionId == sample.index[0][1]] base.index = range(len(base)) base['calTimeStamp'] = base.timeStamp.apply(lambda s: DateTime(s.year, s.month, s.day, s.hour, s.minute, s.second)) ax = base.plot(x = 'calTimeStamp', y = ['volume'], kind = 'bar', sharex=True, xticks = [], color = 'r', ax = axs[0]) ax.set_title(u'交易日: ' + unicode(d) + u' 最活跃期权:'+ unicode(sample.index[0][1]), fontproperties = font, fontsize = 18) ax = base.plot(x= 'calTimeStamp', y = ['lastPrice(vol)'], sharex=True, legend = True,ax = axs[1], rot = 45) ax.set_xlabel(u'交易时间', fontproperties = font, fontsize = 15) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc1ac54d.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc1c5b0a.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc1e4272.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc20a686.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdc2267cc.png)
';

期权探秘2

最后更新于:2022-04-01 21:59:10

# 期权探秘2 > 来源:https://uqer.io/community/share/54c479f1f9f06c276f651a4a 版本:1.0 作者:李自龙 联系:zilong.li@datayes.com 上一篇中,李博简单介绍了期权和影响期权价格的各种因素。本篇中,我们重点介绍期权与不同资产的组合能够给我们带来什么样新颖的盈利方式,例如: + 期权 + 零息债券 + 期权 + 标的资产 + 同一标的资产上的多个期权 如此通过构造包含期权的头寸组合,能够实现丰富多彩的未来收益形式,给我们的想象力提供了最大的舞台,请看下文! ```py import numpy as np from matplotlib import pylab ``` ## 1. 期权 + 零息债券 = 保本债券 期权推出后,银行可以向客户提供以下形式的价格1000元的理财产品: + 面值为1000元的三年期零息债券 + 标的为股票组合的三年期欧式平值看涨期权 该产品三年到期后:如果股票组合的价值增长,通过行使期权,投资者将得到1000元债券收益外加1000元股票组合头寸对应的增值部分;如果股票组合下跌,期权便没有价值,但是投资者仍然可以得到1000元的债券收益。总的来看,投资者此时的1000元投资在三年后是保本的,即本金不会有任何风险,这就是保本债券(principle-protected notes)的由来。 对于银行来说,发行保本债券也不是赔本的买卖。如果三年期复利利率为5%,那么三年后1000元的今日帖现值为1000e−0.05×3=860.71元。也就是说,投资者今日花1000元买入该产品,在完全无风险的情况下,银行也可以拿出差额139.29元来购买该产品中的看涨期权。我们来看看该看涨期权在今日价值几何(利用BSM期权定价公式,假定股票组合的波动率为10%、收益率为1.5%,无风险利率为5%): ```py price = BSMPrice(Option.Call, 1000, 1000, 0.05, 0.015, 0.1, 3)['price'] print "看涨期权价格: ", price[1] 看涨期权价格: 121.470487448 ``` 这就是说,差额139.29元完全足够购买该理财产品中的必须的看涨期权,银行在推出该保本债券理财产品时,其成本是低于售价1000元的。聪明的投资者可能会想,比购买该理财产品更好地投资做法是:自己购买标的期权,并将剩余的本金投入到无风险投资上。但这个想法有点理想化: + 普通投资者入市期权门槛太高,也会面临更大的买入卖出差价 + 剩余本金的投资利息比银行要低 因此银行推出这一产品可以给投资者带来收益,同时自己也能得到盈利。由于期权价格和标的资产波动率密切相关,如果波动率过高,差额139.29元可能不够购买这一看涨期权: ```py price = BSMPrice(Option.Call, 1000, 1000, 0.05, 0.015, 0.2, 3)['price'] print "波动率为20%时看涨期权价格: ", price[1] 波动率为20%时看涨期权价格: 178.183351319 ``` 此时银行的可以通过购买更高行权价格的期权: ```py price = BSMPrice(Option.Call, 1100, 1000, 0.05, 0.015, 0.2, 3)['price'] print "波动率为20%、行权价为1100时的看涨期权价格: ", price[1] 波动率为20%、行权价为1100时的看涨期权价格: 135.48518416 ``` 此时,三年后,只有该标的资产的价格增长超过10%时,投资者才能获利。 在此基础上(标的波动率为20%、期权行权价为1100),我们讨论投资者今日投入1000元、三年后到期的收益情况:如果标的组合三年期间价格增加30%,那么投资者到期收益1200元;这一收益仍大于将1000元直接存入银行的收益`1000e ** 0.05*3=1161.83`元。总之,投资者投资这一产品的收益和标的资产的价格增长情况息息相关;如果标的资产为上证50ETF,那么在接下来普遍看好的牛市中该产品应该可以带给投资者不错的收益,且投资该产品是保本的。 ## 2. 股票与单一期权相组合的策略 前一节我们讨论了,单一期权与零息债券的组合,本节我们将讨论单一期权与股票的组合。 ```py # 定义看涨看跌期权到期收益函数 def call(S): return max(S - 100.0,0.0) - 40.0 def put(S): return max(100.0 - S,0.0) - 40.0 callfunc = np.frompyfunc(call, 1, 1) putfunc = np.frompyfunc(put, 1, 1) ``` 2.1 备保看涨期权承约(writing covered call) 策略构造: + 卖出以股票为标的的看涨期权 + 持有相应于期权空头的该标的股票 策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,21) pylab.figure(figsize=(10,7)) pylab.plot(spots, -callfunc(spots), 'b-.',linewidth = 2) pylab.plot(spots, spots, 'r--',linewidth = 2) pylab.plot(spots, -callfunc(spots) + spots, 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看涨期权空头',u'股票多头',u'策略组合收益'], prop = font, loc = 'best') pylab.title(u'备保看涨期权承约', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbdd9a51.png) 由上图中所见,在股票价格急剧上涨时,投资者持有的股票保护(cover)了其看涨期权空头将带来的损失。可以看出,采用Covered Call策略的投资者对未来该股票的表现持中性态度,认为该股票价格在未来一段时间内将保持在当前价格附件区间。 和Covered Call相反,如果对未来某股票的表现持中性或者看跌态度,投资者利用该股票看涨期权构建策略: + 持有以股票为标的的看涨期权 + 卖空相应份额的该股票 策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,21) pylab.figure(figsize=(10,7)) pylab.plot(spots, callfunc(spots), 'b-.',linewidth = 2) pylab.plot(spots, -spots, 'r--',linewidth = 2) pylab.plot(spots, callfunc(spots) - spots, 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看涨期权多头',u'股票空头',u'策略组合收益'], prop = font, loc = 'best') pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbdf25d7.png) ## 2.2 保护性看跌期权策略(Protective Put) 策略构造: + 持有以某股票为标的的看跌期权 + 买入相应于看跌期权份额的该股票 策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,21) pylab.figure(figsize=(10,7)) pylab.plot(spots, putfunc(spots), 'b-.',linewidth = 2) pylab.plot(spots, spots, 'r--',linewidth = 2) pylab.plot(spots, putfunc(spots) + spots, 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看跌期权多头',u'股票多头',u'策略组合收益'], prop = font, loc = 'best') pylab.title(u'受保护看跌期权策略', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbe12889.png) 由上图可以看到,投资者对于自身持有的股票持看涨态度,但是又不愿意承担股票下跌的损失,所以采用保护性看跌期权策略(Protective Put),能够在获取股票升值收益的情况下,同时利用持有的看跌期权保护自己不受股票下跌的影响。 与保护性看跌期权策略相反,如果投资者对于股票持中性或者轻微看跌态度时,可以通过卖出看跌期权来构造如下策略: + 卖出以某股票为标的的看跌期权 + 卖空相应于看跌期权份额的该股票 策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,21) pylab.figure(figsize=(10,7)) pylab.plot(spots, -putfunc(spots), 'b-.',linewidth = 2) pylab.plot(spots, -spots, 'r--',linewidth = 2) pylab.plot(spots, -putfunc(spots) - spots, 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看跌期权空头',u'股票空头',u'策略组合收益'], prop = font, loc = 'best') pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbe29620.png) ## 3. 差价策略(spread) 差价策略,是指将两个或者多个相同类型(同为看涨或同为看跌)期权组合在一起的期权交易策略。 ```py def call(S, K, c): return max(S - K,0.0) - c def put(S, K, p): return max(K - S,0.0) - p callfunc = np.frompyfunc(call, 3, 1) putfunc = np.frompyfunc(put, 3, 1) ``` ## 3.1 牛市差价(bull spread) 策略构造: + 买入一份股票标的看涨期权 + 卖出同一股票标的、期权期限相同,但执行价格较高的看涨期权(对于看涨期权来说,执行价格高意味着期权费用较低) 策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,41) pylab.figure(figsize=(10,7)) pylab.plot(spots, callfunc(spots, 75, 40), 'b-.',linewidth = 2) pylab.plot(spots, -callfunc(spots, 125, 20), 'r--',linewidth = 2) pylab.plot(spots, callfunc(spots, 75, 40) - callfunc(spots, 125, 20), 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看涨期权多头,执行价格75',u'看涨期权空头,执行价格125',u'策略组合收益'], prop = font, loc = 'best') pylab.title(u'看涨期权构造的牛市差价策略', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbe4002f.png) 如上图所示,牛市价差策略虽然限制了投资者在股票大涨时的收益,但也控制了股价下跌时候的损失幅度。换句话说,投资者持有一个执行价格75的看涨期权,同时卖出一个执行价格较高为125的看涨期权而放弃了股票上涨时候的潜在收益。可以看出,放弃获取这些潜在收益的补偿,就是卖出执行价格125的期权而得到的期权费用。 进一步的,市场上的牛市差价策略可以分成3种类型: + 两个看涨期权均为虚值期权 + 持有的看涨期权为实值期权,卖出的期权为虚值期权 + 两个看涨期权均为实值期权 第一种牛市差价策略最为激进,这一策略的成本很低;最后一种牛市差价策略趋于保守。 实际上,利用看跌期权也可以构造牛市差价策略,策略的结构为: + 买入一份股票标的看跌期权 + 卖出同一股票标的、期权期限相同,但执行价格较高的看跌期权(对于看跌期权来说,执行价格高意味着期权费用较高) 看跌期权构造的牛市差价策略在开始时带给投资者一个正的现金流,策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,41) pylab.figure(figsize=(10,7)) pylab.plot(spots, putfunc(spots, 75, 20), 'b-.',linewidth = 2) pylab.plot(spots, -putfunc(spots, 125, 40), 'r--',linewidth = 2) pylab.plot(spots, putfunc(spots, 75, 20) - putfunc(spots, 125, 40), 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看跌期权多头,执行价格75',u'看跌期权空头,执行价格125',u'策略组合收益'], prop = font, loc = 'best') pylab.title(u'看跌期权构造的牛市差价策略', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbe5c4ca.png) ## 3.2 熊市差价(bear spread) 投资者构造3.1节中的牛市差价策略时,看好的是股票价格上涨;类似的,此处构造熊市差价策略时,投资者看好股票价格下跌。 策略构造: + 买入一份股票标的看跌期权 + 卖出同一股票标的、期权期限相同,但执行价格较低的看跌期权(对于看跌期权来说,执行价格低意味着期权费用较低) 与牛市差价策略中卖出较高执行价格看跌期权相反,熊市差价策略中我们总是卖出执行价格较低的期权。 策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,41) pylab.figure(figsize=(10,7)) pylab.plot(spots, putfunc(spots, 125, 40), 'b-.',linewidth = 2) pylab.plot(spots, -putfunc(spots, 75, 20), 'r--',linewidth = 2) pylab.plot(spots, putfunc(spots, 125, 40) - putfunc(spots, 75, 20), 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看跌期权多头,执行价格125',u'看跌期权空头,执行价格75',u'策略组合收益'], prop = font, loc = 'best') pylab.title(u'看跌期权构造的熊市差价策略', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbe76b54.png) 由上图可以看到,熊市差价策略限定了收益上限,也控制了损失幅度。投资者看好股票价格下跌,买入执行价格为125的看跌期权;同时,投资者卖出一份执行价格为75的看跌期权,虽然放弃了股票大幅下跌时候的额外收益,但也收取了卖出期权的期权费用。 和牛市差价策略类似,也可以通过看涨期权构造熊市差价策略: + 买入一份股票标的看涨期权 + 卖出同一股票标的、期权期限相同,但执行价格较低的看涨期权(对于看涨期权来说,执行价格低意味着期权费用较高) 策略到期收益和股票价格的关系如下图: ```py spots = np.linspace(0,200,41) pylab.figure(figsize=(10,7)) pylab.plot(spots, callfunc(spots, 125, 20), 'b-.',linewidth = 2) pylab.plot(spots, -callfunc(spots, 75, 40), 'r--',linewidth = 2) pylab.plot(spots, callfunc(spots, 125, 20) - callfunc(spots, 75, 40), 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看涨期权多头,执行价格125',u'看涨期权空头,执行价格75',u'策略组合收益'], prop = font, loc = 'best') pylab.title(u'看涨期权构造的熊市差价策略', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbe9214d.png) ## 3.3 盒式差价(box spread) 盒式差价是指由看涨期权构造的一个牛市差价和由看跌期权构造的一个熊市差价的组合。 利用上面的讨论,我们具体地构造这一策略: + 牛市差价:价格`c1`买入执行价格为K1的看涨期权,同时卖出执行价格为`K2`的看涨期权并获得期权费用c2 + 熊市差价:价格`p1`买入执行价格为K1的看跌期权,同时卖出执行价格为`K2`的看跌期权并获得期权费用p2 可以看出,构造这一策略我们需要付出初始现金:`(c2−c1)+(p2−p1)`。 策略到期收益和股票价格的关系示意如下图(相应参数为 `c1=40`,`c2=20`,`p1=45`,`p2=20`,`K1=75`,`K2=125`): ```py spots = np.linspace(0,200,41) pylab.figure(figsize=(10,7)) pylab.plot(spots, callfunc(spots, 75, 40) - callfunc(spots, 125, 20), 'b-.',linewidth = 2) pylab.plot(spots, putfunc(spots, 125, 45) - putfunc(spots, 75, 20), 'r--',linewidth = 2) pylab.plot(spots, callfunc(spots, 75, 40) - callfunc(spots, 125, 20) + putfunc(spots, 125, 45) - putfunc(spots, 75, 20), 'k-',linewidth = 2) font.set_size(15) pylab.legend([u'看涨期权构造的牛市差价',u'看跌期权构造的熊市差价',u'策略组合收益'], prop = font, loc = 'best') pylab.title(u'盒式差价', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.ylim(-50,50) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbea95d8.png) 由上图可见,由四个期权构成的盒式差价策略,其期权到期日的收益为一常数;换句话说,盒式差价策略的收益是无风险的! 实际上,盒式差价策略的到期收益贴现值为`K2−K1`的贴现值![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbebf880.jpg)(上面作图时,我们假定了无风险利率`r=0`)减去初始构建策略投资现金`(c2−c1)+(p2−p1)`: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbed2220.jpg) 因为这一收益是无风险的,所以如果期权市场价格合理的话,该收益应该为0;也就是,盒式差价策略是一个套利策略,如果我们能够通过购买以上四个期权头寸而构建出`Rbox>0`的盒式差价组合的话,说明市场对期权定价不合理,可以通过盒式差价套利。 ###3.4 蝶式差价(butterfly spread) 蝶式差价由三个相同标的,且到期日相同而行权价不同的看涨期权构成。 策略构造: + 买入一个具有较低执行价格`K1`的欧式看涨期权 + 买入一个具有较高执行价格`K3`的欧式看涨期权 + 卖出两个具有中间执行价格K2的欧式看涨期权,其中`K1 ';

期权探秘1

最后更新于:2022-04-01 21:59:08

# 期权探秘1 > 来源:https://uqer.io/community/share/54b39784f9f06c276f651a0e 版本:1.0 作者:李丞 联系:cheng.li@datayes.com ## 1. 什么是期权 期权是期权买方与期权卖方达成的未来交割协议:期权的卖方承诺在未来的指定到期日向期权买方以指定的敲定价格出售(或者买入)指定的标的,这个合约是期权卖方的义务;同时是期权买方的权利。 + 当期权卖方有标的的卖出义务,期权买方有标的的买入权利时,这个期权称为看涨期权或者买入期权 + 当期权卖方有标的的买入义务,期权买方有标的的卖出权利时,这个期权称为看跌期权或者卖出期权 这样的期权可以在到期日之间在投资者之间自由的交易,这个价格的形成机制成为了期权投资者、套利者、套期保值者的研究核心。 首先我们从期权的买方(投资者)出发,研究买入期权的支付结构(Payoff): + 如果到期日,标的资产价格高于敲定价格,那么投资者会很高兴行使买入的权利(Exercise); + 反之在到期日,标的资产价格低于敲定价格,投资者宁可选择从市场上直接买入标的而不是行权。 如果我们假设投资者在行权之后会直接在市场上卖出标的,那么投资者的最终到期收益很容易的用下面的式子表示: ``` Payoff=MAX(S−K,0) ``` 这里面`S`就是到期日标的价格,`K`是敲定价格。 考虑敲定价格为50的情形,让我们绘制支付函数的具体形状: ```py import numpy as np from matplotlib import pylab def future(S): return S - 50.0 def call(S): return max(S - 50.0,0.0) callfunc = np.frompyfunc(call, 1, 1) futurefunc = np.frompyfunc(future, 1, 1) spots = np.linspace(0,100,20) pylab.figure(figsize=(12,6)) pylab.plot(spots, callfunc(spots), 'k-.') pylab.plot(spots, futurefunc(spots), 'ko') font.set_size(15) pylab.legend([u'期权',u'期货'], prop = font, loc = 'best') pylab.title(u'期权 v.s.期货(不含期权本身价格)', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbd45906.png) 很容易的可以从上图看到,期权相对于期货,具有上涨的完全收益,同时规避了下跌的风险。当然这样的“权利”自然不会是免费的,期权的价格就是期权买方需要付出的代价。我们这里假设期权的价格为10: ```py def future(S): return S - 50.0 def call(S): return max(S - 50.0,0.0) - 10.0 callfunc = np.frompyfunc(call, 1, 1) spots = np.linspace(0,100,20) pylab.figure(figsize=(12,6)) pylab.plot(spots, callfunc(spots), 'k-.') pylab.plot(spots, futurefunc(spots), 'ko') pylab.legend([u'期权',u'期货'], prop = font, loc = 'best') pylab.title(u'期权 v.s.期货(含期权本身价格)', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'偿付', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbd62938.png) 上图中可以清晰的看到,期权价格会直接影响到期的损益。在我们的这个例子中,除非标的价格到期涨到超过60元,客户不会有正的收益! ## 2. 影响期权价格的因素 这里我们暂不讨论期权的定价方法,这个我们将在后续的报告中讨论。这里我们首先讨论影响期权价格的几个显著要素。这里我们仍然选取上面的看涨期权的例子,先使用最常见的Black - Scholes模型[1](关于这个模型的详细介绍会在以后给出)。这里例子的数据取自经典教材[2] + 期权类型:看涨期权 + 敲定价格:50 + 标的价格:50 + 无风险利率: 5% + 红利率:0% + 波动率:30% + 到期时间:1年 在这篇报告中我们将只讨论:标的价格、敲定价格以及到期时间的影响。剩余的因子我们会在以后讨论。 ### 2.1 标的价格 显然的,随着标的价格的上升,客户手上的期权未来值得行权的可能性越高,并且行权以后获得的收益也越高。由此可知期权价格是标的价格单调增函数: ```py optinType = Option.Call strike = 50.0 riskFree = 0.05 dividend = 0.0 volatility = 0.3 maturity = 1.0 ``` ```py spots = np.linspace(10,80,20) prices = BSMPrice(optinType, strike, spots, riskFree, dividend, volatility, maturity)['price'] pylab.figure(figsize=(12,6)) pylab.plot(spots, prices, 'k-.') pylab.title(u'期权价格 v.s.标的价格', fontproperties = font) pylab.xlabel(u'标的价格', fontproperties = font) pylab.ylabel(u'期权价格', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbd7af5d.png) ### 2.2 敲定价格 与敲定价格恰恰相反,敲定价格与看涨期权价格是反向关系:敲定价格越高,看涨期权行权的可能性越小;即使可以行权,获得收益也相对于低敲定价格的期权低 ```py strikes = np.linspace(10,80,20) spot = 50.0 prices = BSMPrice(optinType, strikes, spot, riskFree, dividend, volatility, maturity)['price'] pylab.figure(figsize=(12,6)) pylab.plot(strikes, prices, 'k-.') pylab.title(u'期权价格 v.s.敲定价格', fontproperties = font) pylab.xlabel(u'敲定价格', fontproperties = font) pylab.ylabel(u'期权价格', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbd8f599.png) ### 2.3 到期时间 到期时间的作用不像标的价格以及行权价格那样一目了然,但是在假设的情形下吗,仍然是可以分析的: 如果离到期时间还有距离,那么意味着即使现在的标的价格仍然低于到期价格,仍然有机会“咸鱼翻身”。这时候到期时间对于期权价格的贡献是正的。 ```py spot = 50 strike = 50 maturities = np.linspace(0.2,2.0,20) prices = BSMPrice(optinType, strike, spot, riskFree, 0.0, volatility, maturities)['price'] pylab.figure(figsize=(12,6)) pylab.plot(maturities, prices, 'k-.') pylab.title(u'期权价格 v.s.到期时间', fontproperties = font) pylab.xlabel(u'到期时间', fontproperties = font) pylab.ylabel(u'期权价格', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbdaa939.png) 直接可以看到,期权的价格是到期时间单调增函数。 但是期权到期时间的影响没有那么简单,让我们看下面这张图:这里假设红利比率为20%,期权价格在1年左右达到最大值,随后开始下降。这里面的原因比较复杂,留待我们以后的报告中解释。 ```py spot = 50 strike = 50 maturities = np.linspace(0.2,2.0,20) prices = BSMPrice(optinType, strike, spot, riskFree, 0.2, volatility, maturities)['price'] pylab.figure(figsize=(12,6)) pylab.plot(maturities, prices, 'k-.') pylab.title(u'期权价格 v.s.到期时间(红利水平20%)', fontproperties = font) pylab.xlabel(u'到期时间', fontproperties = font) pylab.ylabel(u'期权价格', fontproperties = font) pylab.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbdc16f1.png)
';

期权头寸计算

最后更新于:2022-04-01 21:59:06

# 期权头寸计算 > 来源:https://uqer.io/community/share/54c846cdf9f06c276f651a50 版本:1.0 作者:李丞 联系:cheng.li@datayes.com 在本篇中,我们假设客户已经拥有了自己的期权头寸,希望利用量化实验室的功能进行风险监控。 ## 数据准备 用户的期权头寸数据保存在[期权头寸.csv](http://pan.baidu.com/s/1kTEDhwr)文件中(请点击链接下载),样例如下: ```py optionData = pd.read_csv(u'期权头寸.csv', encoding='gbk', parse_dates = [3], dtype = {u'代码':str}) pd.options.display.float_format = '{:,.2f}'.format print(optionData) 代码 方向 行权价 到期时间 头寸 0 000001 C 2.20 2015-02-19 1000 1 000002 C 2.10 2015-02-19 -500 2 000003 P 1.90 2015-03-19 1000 3 000004 P 1.80 2015-03-19 -500 ``` ## 计算方法 这里我们简单展示如何用内置函数,方便的计算基于Black-Scholes模型的价格以及风险值: ```py def processOptionBook(optionData): # 由于我们现在还无法获取市场数据,制作一些假的市场数据数据 # 例如波动率水平,利率水平 vol = 0.3 * np.random.random(len(optionData)) + 0.2 spot = 2.0 riskFree = 0.04 dividend = 0.0 evaluationDate = Date.todaysDate() # 根据用户的输入计算期权的价格以及各种greeks t2m = (optionData[u'到期时间'].apply(lambda x:Date(x.year,x.month,x.day)) - evaluationDate)/ 365.0 optionType = optionData[u'方向'].apply(lambda x: x=='C' and 1 or -1) strike = optionData[u'行权价'] calculateResult = BSMPrice(optionType,strike,spot,riskFree,dividend, vol,t2m) # 整理数据 calculateResult = calculateResult.multiply(optionData[u'头寸'].values, axis = 0) calculateResult.index = optionData[u'代码'] portfolio = pd.DataFrame(dict(np.sum(calculateResult)), index = ['portfolio']) calculateResult = calculateResult.append(portfolio) calculateResult.index.name = u'代码' calculateResult = calculateResult.reindex_axis(['price', 'delta', 'gamma', 'rho', 'theta', 'vega'], axis = 1) return calculateResult ``` ```py res= processOptionBook(optionData) print(res) price delta gamma rho theta vega 代码 000001 27.36 222.15 1,317.92 26.27 -550.43 149.48 000002 -5.13 -94.07 -1,326.48 -11.53 116.11 -67.71 000003 24.81 -238.18 1,822.42 -70.03 -168.14 231.44 000004 -8.43 71.48 -514.95 21.15 82.48 -84.38 portfolio 38.62 -38.62 1,298.91 -34.13 -519.98 228.84 ```
';

每日期权风险数据整理

最后更新于:2022-04-01 21:59:03

# 每日期权风险数据整理 histDayGreeksIVOpt50ETF(Date(2015,10,12)).head() ``` | | Call | Call-Put | Put | | --- | -- | | | close | iv | delta | theta | gamma | vega | rho | strike | spot | forward | close | iv | delta | theta | gamma | vega | rho | | expDate | | | | | | | | | | | | | | | | | | | 2015-10-28 | 0.4331 | 0.4790 | 0.9830 | -0.1615 | 0.1779 | 0.0208 | 0.0842 | 1.85 | 2.288 | 2.283 | 0.0015 | 0.4793 | -0.0170 | -0.1059 | 0.1783 | 0.0208 | -0.0019 | | 2015-10-28 | 0.3821 | 0.4772 | 0.9692 | -0.2310 | 0.2949 | 0.0343 | 0.0850 | 1.90 | 2.288 | 2.283 | 0.0029 | 0.4765 | -0.0306 | -0.1725 | 0.2939 | 0.0341 | -0.0034 | | 2015-10-28 | 0.3335 | 0.4485 | 0.9568 | -0.2740 | 0.4144 | 0.0453 | 0.0859 | 1.95 | 2.288 | 2.283 | 0.0040 | 0.4473 | -0.0428 | -0.2129 | 0.4124 | 0.0450 | -0.0048 | | 2015-10-28 | 0.2874 | 0.4218 | 0.9381 | -0.3289 | 0.5861 | 0.0603 | 0.0862 | 2.00 | 2.288 | 2.283 | 0.0058 | 0.4220 | -0.0620 | -0.2690 | 0.5866 | 0.0604 | -0.0069 | | 2015-10-28 | 0.2420 | 0.2613 | 0.9773 | -0.1349 | 0.4175 | 0.0266 | 0.0929 | 2.05 | 2.288 | 2.283 | 0.0077 | 0.3873 | -0.0849 | -0.3130 | 0.8130 | 0.0768 | -0.0094 | 期权的隐含波动率微笑 + 下图中,竖直虚线表示当日的标的50ETF收盘价 + 实际上计算PCIVD就是仅仅考虑竖直虚线附近的平值期权 + 看跌看涨隐含波动率微笑曲线中间的 Gap 的变化,正是我们关注点 ```py histDayPlotSmileVolatilityOpt50ETF(Date(2015,10,12)) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbccebd1.png) ```py def histDayPCIVD50ETF(date): ## PCIVD: Put Call Implied Volatility Diff; ## 看跌看涨期权隐含波动率价差,选取平值附近的近月和次近月合约构建 ## 看跌和看涨期权的隐含波动率指数,PCIVD即为两指数之差。 # Uqer 计算期权的风险数据 opt = histDayGreeksIVOpt50ETF(date) # 下面展示波动率微笑 exp_dates = np.sort(opt.index.unique())[0:2] pcivd = pd.DataFrame(0.0, index=map(Date.toDateTime, [date]), columns=['nearPCIVD','nextPCIVD']) pcivd.index.name = 'date' ivd = [] for epd in exp_dates: opt_epd = opt[opt.index==epd] opt_epd[('Call-Put', 'diffKF')] = np.abs(opt_epd[('Call-Put', 'strike')] - opt_epd[('Call-Put', 'spot')]) opt_epd = opt_epd.set_index(('Call-Put', 'strike')) opt_epd.index.name = 'strike' opt_epd = opt_epd.sort([('Call-Put', 'diffKF')]).head(2) ivd_epd = opt_epd[('Put', 'iv')].mean() - opt_epd[('Call', 'iv')].mean() ivd.append(ivd_epd) pcivd.ix[Date.toDateTime(date)] = ivd return pcivd def histDayPCIVD50ETF_check(date): ## PCIVD: Put Call Implied Volatility Diff; ## 看跌看涨期权隐含波动率价差,选取平值附近的近月和次近月合约构建 ## 看跌和看涨期权的隐含波动率指数,PCIVD即为两指数之差。 # Uqer 计算期权的风险数据 opt = histDayGreeksIVOpt50ETF(date) # 下面展示波动率微笑 exp_dates = np.sort(opt.index.unique())[0:2] pcivd = pd.DataFrame(0.0, index=map(Date.toDateTime, [date]), columns=['nearPCIVD', 'nearPutIV', 'nearCallIV','nextPCIVD', 'nextPutIV', 'nextCallIV']) pcivd.index.name = 'date' ivd = [] for epd in exp_dates: opt_epd = opt[opt.index==epd] opt_epd[('Call-Put', 'diffKF')] = np.abs(opt_epd[('Call-Put', 'strike')] - opt_epd[('Call-Put', 'spot')]) opt_epd = opt_epd.set_index(('Call-Put', 'strike')) opt_epd.index.name = 'strike' opt_epd = opt_epd.sort([('Call-Put', 'diffKF')]).head(2) ivd_epd = opt_epd[('Put', 'iv')].mean() - opt_epd[('Call', 'iv')].mean() ivd.append(ivd_epd) ivd.append(opt_epd[('Put', 'iv')].mean()) ivd.append(opt_epd[('Call', 'iv')].mean()) pcivd.ix[Date.toDateTime(date)] = ivd return pcivd def histPCIVD50ETF(beginDate, endDate): begin = Date.fromDateTime(beginDate) end = Date.fromDateTime(endDate) cal = Calendar('China.SSE') dates = cal.bizDatesList(begin, end) pcivd = pd.DataFrame() for dt in dates: pcivd_dt = histDayPCIVD50ETF(dt) pcivd = concat([pcivd, pcivd_dt]) pcivd['nearDiff'] = pcivd['nearPCIVD'].diff() pcivd['nextDiff'] = pcivd['nextPCIVD'].diff() return pcivd def histPCIVD50ETF_check(beginDate, endDate): begin = Date.fromDateTime(beginDate) end = Date.fromDateTime(endDate) cal = Calendar('China.SSE') dates = cal.bizDatesList(begin, end) pcivd = pd.DataFrame() for dt in dates: pcivd_dt = histDayPCIVD50ETF_check(dt) pcivd = concat([pcivd, pcivd_dt]) pcivd['nearPutDiff'] = pcivd['nearPutIV'].diff() pcivd['nearCallDiff'] = pcivd['nearCallIV'].diff() pcivd['nextPutDiff'] = pcivd['nextPutIV'].diff() pcivd['nextCallDiff'] = pcivd['nextCallIV'].diff() return pcivd ``` 计算PCIVD + 期权自15年2月9号上市 + 此处计算得到的数据可以用在后面几条策略中 结果中的列分别为: + nearPCIVD:当月PCIVD + nextPCIVD:次月PCIVD + nearDiff:当月PCIVD与前一日值的变化量 + nextDiff:次月PCIVD与前一日值的变化量 ```py ## PCIVD计算示例 start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 10, 12) # 回测结束时间 pcivd = histPCIVD50ETF(start, end) pcivd.tail() ``` | | nearPCIVD | nextPCIVD | nearDiff | nextDiff | | --- | --- | | date | | | | | | 2015-09-29 | 0.15540 | 0.15915 | 0.02660 | 0.0073 | | 2015-09-30 | 0.10205 | 0.14915 | -0.05335 | -0.0100 | | 2015-10-08 | 0.08845 | 0.10645 | -0.01360 | -0.0427 | | 2015-10-09 | 0.08320 | 0.10375 | -0.00525 | -0.0027 | | 2015-10-12 | 0.04635 | 0.07065 | -0.03685 | -0.0331 | ### 2.1 结合使用当月、次月 PCIVD 的择时策略 策略思路:考虑当月 PCIVD 和 次月 PCIVD 的日变化量 + 当月 PCIVD 和 次月 PCIVD 同时变小(当月和次月的 PCIVDDiff 同时小于0),则今天全仓50ETF + 否则,清仓观望 ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 10, 8) # 回测结束时间 benchmark = '510050.XSHG' # 策略参考标准 universe = ['510050.XSHG'] # 股票池 capital_base = 100000 # 起始资金 commission = Commission(0.0,0.0) refresh_rate = 1 # pcivd = histPCIVD50ETF(start, end) def initialize(account): # 初始化虚拟账户状态 account.fund = universe[0] def handle_data(account): # 每个交易日的买入卖出指令 fund = account.fund # 获取回测当日的前一天日期 dt = Date.fromDateTime(account.current_date) cal = Calendar('China.IB') last_day = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日 last_day_str = last_day.strftime("%Y-%m-%d") # 计算买入卖出信号 try: # 拿取PCIVD数据 pcivd_near = pcivd.nearDiff.loc[last_day_str] pcivd_next = pcivd.nextDiff.loc[last_day_str] long_flag = True if pcivd_near < 0 and pcivd_next < 0 else False except: long_flag = False if long_flag: # 买入时,全仓杀入 try: approximationAmount = int(account.cash / account.referencePrice[fund] / 100.0) * 100 order(fund, approximationAmount) except: return else: # 卖出时,全仓清空 order_to(fund, 0) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbcea261.jpg) PCR 和 PCIVD 的良好择时效果表明,虽然回测时间短,但它们均可以通过期权市场的信息来给出在现货市场的买卖择时信号,必要时建议我们空仓 ## 3. 监视最近 PCR 和 PCIVD 走势 + 每日监视 PCR 和 PCIVD 近期走势,指导次日操作 + 如果 PCR 和 PCIVD 的值降低,那么我们就在第二天买入 ```py cal = Calendar('China.IB') # Dates end = Date.todaysDate() end = cal.advanceDate(end,'-1B',BizDayConvention.Preceding) # 这里结束点选择昨天,因为DataAPI的今日数据要到收盘后比较晚才能拿到;实际中可以自己调整 start = cal.advanceDate(end,'-15B',BizDayConvention.Preceding) # 开始点为七天前 ## 计算 PCR 和 PCIVD start = start.toDateTime() end = end.toDateTime() hist_pcr = histPCR50ETF(start, end) # 计算PCR hist_pcivd = histPCIVD50ETF(start, end) # 计算PCIVD hist_pcr[['nearVolPCR', 'nearValuePCR']].plot(style='s-') hist_pcivd[['nearPCIVD', 'nextPCIVD']].plot(style='s-') ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbd11845.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbd27bc2.png) PCIVD 图中,近月期权的 PCIVD 在行权日为0,需要注意;行权日附近,可以以次近月期权的 PCIVD 走势为参考
';

【50ETF期权】 期权择时指数 1.0

最后更新于:2022-04-01 21:59:01

# 【50ETF期权】 期权择时指数 1.0 > 来源:https://uqer.io/community/share/561c883df9f06c4ca72fb5f7 本文中,我们使用期权的日行情数据,计算期权情绪指标,并用以指导实战择时 初步讨论只包括两个指标 + 成交量(成交额) PCR:看跌看涨期权的成交量(成交额)比率 + PCIVD:Put Call Implied Volatility Difference 看跌看涨期权隐含波动率差 ```py from CAL.PyCAL import * import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib import rc rc('mathtext', default='regular') import seaborn as sns sns.set_style('white') from matplotlib import dates from pandas import concat from scipy import interpolate import math ``` ## 1. 看跌看涨成交量(成交额)比率 PCR + 计算每日看跌看涨成交量或成交额的比率,即PCR + 我们考虑PCR每日变化量与现货50ETF隔日收益率的关系 + 每日PCR变化量PCRD为:当日PCR减去前一日PCR得到的值,即对PCR做差分 ```py def histVolumeOpt50ETF(beginDate, endDate): ## 计算历史一段时间内的50ETF期权持仓量交易量数据 optionVarSecID = u"510050.XSHG" cal = Calendar('China.SSE') dates = cal.bizDatesList(beginDate, endDate) dates = map(Date.toDateTime, dates) columns = ['callVol', 'putVol', 'callValue', 'putValue', 'callOpenInt', 'putOpenInt', 'nearCallVol', 'nearPutVol', 'nearCallValue', 'nearPutValue', 'nearCallOpenInt', 'nearPutOpenInt', 'netVol', 'netValue', 'netOpenInt', 'volPCR', 'valuePCR', 'openIntPCR', 'nearVolPCR', 'nearValuePCR', 'nearOpenIntPCR'] hist_opt = pd.DataFrame(0.0, index=dates, columns=columns) hist_opt.index.name = 'date' # 每一个交易日数据单独计算 for date in hist_opt.index: date_str = Date.fromDateTime(date).toISO().replace('-', '') try: opt_data = DataAPI.MktOptdGet(secID=u"", tradeDate=date_str, field=u"", pandas="1") except: hist_opt = hist_opt.drop(date) continue opt_type = [] exp_date = [] for ticker in opt_data.secID.values: opt_type.append(ticker[6]) exp_date.append(ticker[7:11]) opt_data['optType'] = opt_type opt_data['expDate'] = exp_date near_exp = np.sort(opt_data.expDate.unique())[0] data = opt_data.groupby('optType') # 计算所有上市期权:看涨看跌交易量、看涨看跌交易额、看涨看跌持仓量 hist_opt['callVol'][date] = data.turnoverVol.sum()['C'] hist_opt['putVol'][date] = data.turnoverVol.sum()['P'] hist_opt['callValue'][date] = data.turnoverValue.sum()['C'] hist_opt['putValue'][date] = data.turnoverValue.sum()['P'] hist_opt['callOpenInt'][date] = data.openInt.sum()['C'] hist_opt['putOpenInt'][date] = data.openInt.sum()['P'] near_data = opt_data[opt_data.expDate == near_exp] near_data = near_data.groupby('optType') # 计算近月期权(主力合约): 看涨看跌交易量、看涨看跌交易额、看涨看跌持仓量 hist_opt['nearCallVol'][date] = near_data.turnoverVol.sum()['C'] hist_opt['nearPutVol'][date] = near_data.turnoverVol.sum()['P'] hist_opt['nearCallValue'][date] = near_data.turnoverValue.sum()['C'] hist_opt['nearPutValue'][date] = near_data.turnoverValue.sum()['P'] hist_opt['nearCallOpenInt'][date] = near_data.openInt.sum()['C'] hist_opt['nearPutOpenInt'][date] = near_data.openInt.sum()['P'] # 计算所有上市期权: 总交易量、总交易额、总持仓量 hist_opt['netVol'][date] = hist_opt['callVol'][date] + hist_opt['putVol'][date] hist_opt['netValue'][date] = hist_opt['callValue'][date] + hist_opt['putValue'][date] hist_opt['netOpenInt'][date] = hist_opt['callOpenInt'][date] + hist_opt['putOpenInt'][date] # 计算期权看跌看涨期权交易量(持仓量)的比率: # 交易量看跌看涨比率,交易额看跌看涨比率, 持仓量看跌看涨比率 # 近月期权交易量看跌看涨比率,近月期权交易额看跌看涨比率, 近月期权持仓量看跌看涨比率 # PCR = Put Call Ratio hist_opt['volPCR'][date] = round(hist_opt['putVol'][date]*1.0/hist_opt['callVol'][date], 4) hist_opt['valuePCR'][date] = round(hist_opt['putValue'][date]*1.0/hist_opt['callValue'][date], 4) hist_opt['openIntPCR'][date] = round(hist_opt['putOpenInt'][date]*1.0/hist_opt['callOpenInt'][date], 4) hist_opt['nearVolPCR'][date] = round(hist_opt['nearPutVol'][date]*1.0/hist_opt['nearCallVol'][date], 4) hist_opt['nearValuePCR'][date] = round(hist_opt['nearPutValue'][date]*1.0/hist_opt['nearCallValue'][date], 4) hist_opt['nearOpenIntPCR'][date] = round(hist_opt['nearPutOpenInt'][date]*1.0/hist_opt['nearCallOpenInt'][date], 4) return hist_opt def histPrice50ETF(beginDate, endDate): # 华夏上证50ETF收盘价数据 secID = '510050.XSHG' begin = Date.fromDateTime(beginDate).toISO().replace('-', '') end = Date.fromDateTime(endDate).toISO().replace('-', '') fields = ['tradeDate', 'closePrice', 'preClosePrice'] etf = DataAPI.MktFunddGet(secID, beginDate=begin, endDate=end, field=fields) etf['tradeDate'] = pd.to_datetime(etf['tradeDate']) etf['dailyReturn'] = etf['closePrice'] / etf['preClosePrice'] - 1.0 etf = etf.set_index('tradeDate') return etf def histPCR50ETF(beginDate, endDate): # PCRD: Put Call Ratio Diff # 计算每日PCR变化量:当日PCR减去前一日PCR得到的值,即对PCR做差分 # 专注于某一项PCR,例如:成交额PCR --- valuePCR pcr_names = ['volPCR', 'valuePCR', 'openIntPCR', 'nearVolPCR', 'nearValuePCR', 'nearOpenIntPCR'] pcr_diff_names = [pcr + 'Diff' for pcr in pcr_names] pcr = histVolumeOpt50ETF(beginDate, endDate) for pcr_name in pcr_names: pcr[pcr_name + 'Diff'] = pcr[pcr_name].diff() return pcr[pcr_names + pcr_diff_names] ``` 计算PCR + 期权自15年2月9号上市 + 此处计算得到的数据可以用在后面几条策略中 ```py ## PCRD计算示例 start = datetime(2015,2, 9) # 回测起始时间 end = datetime(2015, 10, 13) # 回测结束时间 hist_pcrd = histPCR50ETF(start, end) # 计算PCRD hist_pcrd.tail() ``` | | volPCR | valuePCR | openIntPCR | nearVolPCR | nearValuePCR | nearOpenIntPCR | volPCRDiff | valuePCRDiff | openIntPCRDiff | nearVolPCRDiff | nearValuePCRDiff | nearOpenIntPCRDiff | | --- | --- | | date | | | | | | | | | | | | | | 2015-09-29 | 1.0863 | 1.5860 | 0.6680 | 1.2372 | 1.6552 | 0.7632 | 0.0255 | 0.4779 | -0.0058 | 0.0801 | 0.6352 | -0.0193 | | 2015-09-30 | 0.9664 | 1.1366 | 0.6709 | 1.1153 | 1.1460 | 0.7579 | -0.1199 | -0.4494 | 0.0029 | -0.1219 | -0.5092 | -0.0053 | | 2015-10-08 | 0.8997 | 0.5940 | 0.6726 | 0.9244 | 0.4646 | 0.7480 | -0.0667 | -0.5426 | 0.0017 | -0.1909 | -0.6814 | -0.0099 | | 2015-10-09 | 1.0979 | 0.7708 | 0.7068 | 1.1542 | 0.6672 | 0.8121 | 0.1982 | 0.1768 | 0.0342 | 0.2298 | 0.2026 | 0.0641 | | 2015-10-12 | 0.6494 | 0.2432 | 0.7713 | 0.6604 | 0.2002 | 1.0197 | -0.4485 | -0.5276 | 0.0645 | -0.4938 | -0.4670 | 0.2076 | ### 1.1 使用基于成交量 PCR 日变化量的择时策略 策略思路:考虑成交量 PCR 日变化量 PCRD(volume) + 前一日PCRD(volume)小于0,则今天全仓50ETF + 否则,清仓观望 + 简单来说,就是PCR上升,空仓;PCR下降,买入 ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 10, 7) # 回测结束时间 benchmark = '510050.XSHG' # 策略参考标准 universe = ['510050.XSHG'] # 股票池 capital_base = 100000 # 起始资金 commission = Commission(0.0,0.0) refresh_rate = 1 # hist_pcrd = histPCR50ETF(start, end) # 计算PCRD def initialize(account): # 初始化虚拟账户状态 account.fund = universe[0] def handle_data(account): # 每个交易日的买入卖出指令 fund = account.fund # 获取回测当日的前一天日期 dt = Date.fromDateTime(account.current_date) cal = Calendar('China.IB') last_day = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日 last_day_str = last_day.strftime("%Y-%m-%d") # 计算买入卖出信号 try: # 拿取PCRD数据 pcrd_last_vol = hist_pcrd.volPCRDiff.loc[last_day_str] # PCRD(volumn) long_flag = True if pcrd_last_vol < 0 else False # 调仓条件 except: long_flag = False if long_flag: # 买入时,全仓杀入 try: approximationAmount = int(account.cash / account.referencePrice[fund] / 100.0) * 100 order(fund, approximationAmount) except: return else: # 卖出时,全仓清空 order_to(fund, 0) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc8457f.jpg) ### 1.2 使用基于成交额 PCR 日变化量的择时策略 策略思路:考虑成交额 PCR 日变化量 PCRD(value) + 前一日PCRD(value)小于0,则今天全仓50ETF + 否则,清仓观望 + 简单来说,就是PCR上升,空仓;PCR下降,买入 ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 10, 7) # 回测结束时间 benchmark = '510050.XSHG' # 策略参考标准 universe = ['510050.XSHG'] # 股票池 capital_base = 100000 # 起始资金 commission = Commission(0.0,0.0) refresh_rate = 1 # hist_pcrd = histPCR50ETF(start, end) # 计算PCRD def initialize(account): # 初始化虚拟账户状态 account.fund = universe[0] def handle_data(account): # 每个交易日的买入卖出指令 fund = account.fund # 获取回测当日的前一天日期 dt = Date.fromDateTime(account.current_date) cal = Calendar('China.IB') last_day = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日 last_day_str = last_day.strftime("%Y-%m-%d") # 计算买入卖出信号 try: # 拿取PCRD数据 pcrd_last_value = hist_pcrd.valuePCRDiff.loc[last_day_str] # PCRD(value) long_flag = True if pcrd_last_value < 0 else False # 调仓条件 except: long_flag = False if long_flag: # 买入时,全仓杀入 try: approximationAmount = int(account.cash / account.referencePrice[fund] / 100.0) * 100 order(fund, approximationAmount) except: return else: # 卖出时,全仓清空 order_to(fund, 0) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc9cceb.jpg) ### 1.3 结合使用成交量、成交额 PCR 日变化量的择时策略 策略思路:考虑成交量PCRD(volume) 和成交额PCRD(value) + 前一日PCRD(volume)和PCRD(value)同时小于0,则今天全仓50ETF + 否则,清仓观望 ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 10, 7) # 回测结束时间 benchmark = '510050.XSHG' # 策略参考标准 universe = ['510050.XSHG'] # 股票池 capital_base = 100000 # 起始资金 commission = Commission(0.0,0.0) refresh_rate = 1 hist_pcrd = histPCR50ETF(start, end) # 计算PCRD def initialize(account): # 初始化虚拟账户状态 account.fund = universe[0] def handle_data(account): # 每个交易日的买入卖出指令 fund = account.fund # 获取回测当日的前一天日期 dt = Date.fromDateTime(account.current_date) cal = Calendar('China.IB') last_day = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日 last_day_str = last_day.strftime("%Y-%m-%d") # 计算买入卖出信号 try: # 拿取PCRD数据 pcrd_last_value = hist_pcrd.valuePCRDiff.loc[last_day_str] # PCRD(value) pcrd_last_vol = hist_pcrd.volPCRDiff.loc[last_day_str] # PCRD(volumn) long_flag = True if pcrd_last_value < 0.0 and pcrd_last_vol < 0.0 else False # 调仓条件 except: long_flag = False if long_flag: # 买入时,全仓杀入 try: approximationAmount = int(account.cash / account.referencePrice[fund] / 100.0) * 100 order(fund, approximationAmount) except: return else: # 卖出时,全仓清空 order_to(fund, 0) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbcb34cb.jpg) ## 2. 看跌看涨隐含波动率价差 PCIVD + 相同到期日、行权价的看跌看涨期权,其隐含波动率会有差异 + 由于套保需要,一般看跌期权隐含波动率高于看涨期权 + 看跌、看涨期权隐含波动率之差 PCIVD 的每日变化可以用来指导实际操作 + 在计算中,我们使用平值附近的期权计算 PCIVD ```py ## 银行间质押式回购利率 def histDayInterestRateInterbankRepo(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的银行间质押式回购利率周期为: # 1D, 7D, 14D, 21D, 1M, 3M, 4M, 6M, 9M, 1Y indicID = [u"M120000067", u"M120000068", u"M120000069", u"M120000070", u"M120000071", u"M120000072", u"M120000073", u"M120000074", u"M120000075", u"M120000076"] period = np.asarray([1.0, 7.0, 14.0, 21.0, 30.0, 90.0, 120.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interbank_repo = DataAPI.ChinaDataInterestRateInterbankRepoGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interbank_repo = interbank_repo.groupby('indicID').first() interbank_repo = concat([interbank_repo, period_matrix], axis=1, join='inner').sort_index() return interbank_repo ## 银行间同业拆借利率 def histDaySHIBOR(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的SHIBOR周期为: # 1D, 7D, 14D, 1M, 3M, 6M, 9M, 1Y indicID = [u"M120000057", u"M120000058", u"M120000059", u"M120000060", u"M120000061", u"M120000062", u"M120000063", u"M120000064"] period = np.asarray([1.0, 7.0, 14.0, 30.0, 90.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interest_shibor = DataAPI.ChinaDataInterestRateSHIBORGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interest_shibor = interest_shibor.groupby('indicID').first() interest_shibor = concat([interest_shibor, period_matrix], axis=1, join='inner').sort_index() return interest_shibor ## 插值得到给定的周期的无风险利率 def periodsSplineRiskFreeInterestRate(date, periods): # 此处使用SHIBOR来插值 init_shibor = histDaySHIBOR(date) shibor = {} min_period = min(init_shibor.period.values) min_period = 25.0/360.0 max_period = max(init_shibor.period.values) for p in periods.keys(): tmp = periods[p] if periods[p] > max_period: tmp = max_period * 0.99999 elif periods[p] < min_period: tmp = min_period * 1.00001 sh = interpolate.spline(init_shibor.period.values, init_shibor.dataValue.values, [tmp], order=3) shibor[p] = sh[0]/100.0 return shibor ## 使用DataAPI.OptGet, DataAPI.MktOptdGet拿到计算所需数据 def histDayDataOpt50ETF(date): date_str = date.toISO().replace('-', '') #使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息 info_fields = [u'optID', u'varSecID', u'varShortName', u'varTicker', u'varExchangeCD', u'varType', u'contractType', u'strikePrice', u'contMultNum', u'contractStatus', u'listDate', u'expYear', u'expMonth', u'expDate', u'lastTradeDate', u'exerDate', u'deliDate', u'delistDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE",u"L"], field=info_fields, pandas="1") #使用DataAPI.MktOptdGet,拿到历史上某一天的期权成交信息 mkt_fields = [u'ticker', u'optID', u'secShortName', u'exchangeCD', u'tradeDate', u'preSettlePrice', u'preClosePrice', u'openPrice', u'highestPrice', u'lowestPrice', u'closePrice', u'settlPrice', u'turnoverVol', u'turnoverValue', u'openInt'] opt_mkt = DataAPI.MktOptdGet(tradeDate=date_str, field=mkt_fields, pandas = "1") opt_info = opt_info.set_index(u"optID") opt_mkt = opt_mkt.set_index(u"optID") opt = concat([opt_info, opt_mkt], axis=1, join='inner').sort_index() return opt # 旧版forward计算稍有差别 def histDayMktForwardPriceOpt50ETF(opt, risk_free): exp_dates_str = np.sort(opt.expDate.unique()) trade_date = Date.parseISO(opt.tradeDate.values[0]) forward = {} for date_str in exp_dates_str: opt_date = opt[opt.expDate == date_str] opt_call_date = opt_date[opt_date.contractType == 'CO'] opt_put_date = opt_date[opt_date.contractType == 'PO'] opt_call_date = opt_call_date[[u'strikePrice', u'price']].set_index('strikePrice').sort_index() opt_put_date = opt_put_date[[u'strikePrice', u'price']].set_index('strikePrice').sort_index() opt_call_date.columns = [u'callPrice'] opt_put_date.columns = [u'putPrice'] opt_date = concat([opt_call_date, opt_put_date], axis=1, join='inner').sort_index() opt_date['diffCallPut'] = opt_date.callPrice - opt_date.putPrice strike = abs(opt_date['diffCallPut']).idxmin() priceDiff = opt_date['diffCallPut'][strike] date = Date.parseISO(date_str) ttm = abs(float(date - trade_date + 1.0)/365.0) rf = risk_free[date] fw = strike + np.exp(ttm*rf) * priceDiff forward[date] = fw return forward ## 分析历史某一日的期权收盘价信息,得到隐含波动率微笑和期权风险指标 def histDayAnalysisOpt50ETF(date): opt_var_sec = u"510050.XSHG" # 期权标的 opt = histDayDataOpt50ETF(date) #使用DataAPI.MktFunddGet拿到期权标的的日行情 date_str = date.toISO().replace('-', '') opt_var_mkt = DataAPI.MktFunddGet(secID=opt_var_sec,tradeDate=date_str,beginDate=u"",endDate=u"",field=u"",pandas="1") #opt_var_mkt = DataAPI.MktFunddAdjGet(secID=opt_var_sec,beginDate=date_str,endDate=date_str,field=u"",pandas="1") # 计算shibor exp_dates_str = opt.expDate.unique() periods = {} for date_str in exp_dates_str: exp_date = Date.parseISO(date_str) periods[exp_date] = (exp_date - date)/360.0 shibor = periodsSplineRiskFreeInterestRate(date, periods) # 计算forward price opt_tmp = opt[[u'contractType', u'tradeDate', u'strikePrice', u'expDate', u'settlPrice']] opt_tmp.columns = [[u'contractType', u'tradeDate', u'strikePrice', u'expDate', u'price']] forward_price = histDayMktForwardPriceOpt50ETF(opt_tmp, shibor) settle = opt.settlPrice.values # 期权 settle price close = opt.closePrice.values # 期权 close price strike = opt.strikePrice.values # 期权 strike price option_type = opt.contractType.values # 期权类型 exp_date_str = opt.expDate.values # 期权行权日期 eval_date_str = opt.tradeDate.values # 期权交易日期 mat_dates = [] eval_dates = [] spot = [] for epd, evd in zip(exp_date_str, eval_date_str): mat_dates.append(Date.parseISO(epd)) eval_dates.append(Date.parseISO(evd)) spot.append(opt_var_mkt.closePrice[0]) time_to_maturity = [float(mat - eva + 1.0)/365.0 for (mat, eva) in zip(mat_dates, eval_dates)] risk_free = [] # 无风险利率 forward = [] # 市场远期 for s, mat, time in zip(spot, mat_dates, time_to_maturity): #rf = math.log(forward_price[mat] / s) / time rf = shibor[mat] risk_free.append(rf) forward.append(forward_price[mat]) opt_types = [] # 期权类型 for t in option_type: if t == 'CO': opt_types.append(1) else: opt_types.append(-1) # 使用通联CAL包中 BSMImpliedVolatity 计算隐含波动率 calculated_vol = BSMImpliedVolatity(opt_types, strike, spot, risk_free, 0.0, time_to_maturity, settle) calculated_vol = calculated_vol.fillna(0.0) # 使用通联CAL包中 BSMPrice 计算期权风险指标 greeks = BSMPrice(opt_types, strike, spot, risk_free, 0.0, calculated_vol.vol.values, time_to_maturity) # vega、rho、theta 的计量单位参照上交所的数据,以求统一对比 greeks.vega = greeks.vega #/ 100.0 greeks.rho = greeks.rho #/ 100.0 greeks.theta = greeks.theta #* 365.0 / 252.0 #/ 365.0 opt['strike'] = strike opt['forward'] = np.around(forward, decimals=3) opt['optType'] = option_type opt['expDate'] = exp_date_str opt['spotPrice'] = spot opt['riskFree'] = risk_free opt['timeToMaturity'] = np.around(time_to_maturity, decimals=4) opt['settle'] = np.around(greeks.price.values.astype(np.double), decimals=4) opt['iv'] = np.around(calculated_vol.vol.values.astype(np.double), decimals=4) opt['delta'] = np.around(greeks.delta.values.astype(np.double), decimals=4) opt['vega'] = np.around(greeks.vega.values.astype(np.double), decimals=4) opt['gamma'] = np.around(greeks.gamma.values.astype(np.double), decimals=4) opt['theta'] = np.around(greeks.theta.values.astype(np.double), decimals=4) opt['rho'] = np.around(greeks.rho.values.astype(np.double), decimals=4) fields = [u'ticker', u'contractType', u'strikePrice', 'forward', u'expDate', u'tradeDate', u'closePrice', u'settlPrice', 'spotPrice', u'iv', u'delta', u'vega', u'gamma', u'theta', u'rho'] opt = opt[fields].reset_index().set_index('ticker').sort_index() #opt['iv'] = opt.iv.replace(to_replace=0.0, value=np.nan) return opt # 每日期权分析数据整理 def histDayGreeksIVOpt50ETF(date): # Uqer 计算期权的风险数据 opt = histDayAnalysisOpt50ETF(date) # 整理数据部分 opt.index = [index[-10:] for index in opt.index] opt = opt[['contractType','strikePrice','spotPrice','forward','expDate','closePrice','iv','delta','theta','gamma','vega','rho']] opt.columns = [['contractType','strike','spot','forward','expDate','close','iv','delta','theta','gamma','vega','rho']] opt_call = opt[opt.contractType=='CO'] opt_put = opt[opt.contractType=='PO'] opt_call.columns = pd.MultiIndex.from_tuples([('Call', c) for c in opt_call.columns]) opt_call[('Call-Put', 'strike')] = opt_call[('Call', 'strike')] opt_call[('Call-Put', 'spot')] = opt_call[('Call', 'spot')] opt_call[('Call-Put', 'forward')] = opt_call[('Call', 'forward')] opt_put.columns = pd.MultiIndex.from_tuples([('Put', c) for c in opt_put.columns]) opt = concat([opt_call, opt_put], axis=1, join='inner').sort_index() opt = opt.set_index(('Call','expDate')).sort_index() opt = opt.drop([('Call','contractType'), ('Call','strike'), ('Call','forward'), ('Call','spot')], axis=1) opt = opt.drop([('Put','expDate'), ('Put','contractType'), ('Put','strike'), ('Put','forward'), ('Put','spot')], axis=1) opt.index.name = 'expDate' ## 以上得到完整的历史某日数据,格式简洁明了 return opt # 做图展示某一天的隐含波动率微笑 def histDayPlotSmileVolatilityOpt50ETF(date): cal = Calendar('China.SSE') if not cal.isBizDay(date): print date, ' is not a trading day!' return # Uqer 计算期权的风险数据 opt = histDayGreeksIVOpt50ETF(date) spot = opt[('Call-Put', 'spot')].values[0] # 下面展示波动率微笑 exp_dates = np.sort(opt.index.unique()) ## ---------------------------------------------- fig = plt.figure(figsize=(10,8)) fig.set_tight_layout(True) for i in range(exp_dates.shape[0]): date = exp_dates[i] ax = fig.add_subplot(2,2,i+1) opt_date = opt[opt.index==date].set_index(('Call-Put', 'strike')) opt_date.index.name = 'strike' ax.plot(opt_date.index, opt_date[('Call', 'iv')], '-o') ax.plot(opt_date.index, opt_date[('Put', 'iv')], '-s') (y_min, y_max) = ax.get_ylim() ax.plot([spot, spot], [y_min, y_max], '--') ax.set_ylim(y_min, y_max) ax.legend(['call', 'put'], loc=0) ax.grid() ax.set_xlabel(u"strike") ax.set_ylabel(r"Implied Volatility") plt.title(exp_dates[i]) ``` ```py
';

三 期权分析

最后更新于:2022-04-01 21:58:59

# 三 期权分析
';

【50ETF期权】 5. 日内即时监控 Greeks 和隐含波动率微笑

最后更新于:2022-04-01 21:58:56

# 【50ETF期权】 5. 日内即时监控 Greeks 和隐含波动率微笑 > 来源:https://uqer.io/community/share/5615d10ff9f06c4ca72fb5be 和上一篇类似,计算上证50ETF期权的隐含波动率微笑,但这里通过DataAPI提供的高频数据,在交易时间实时监控Greeks 和隐含波动率变化! ```py from CAL.PyCAL import * import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib import rc rc('mathtext', default='regular') import seaborn as sns sns.set_style('white') import math from scipy import interpolate from scipy.stats import mstats from pandas import Series, DataFrame, concat import time from matplotlib import dates ``` 上海银行间同业拆借利率 SHIBOR,用来作为无风险利率参考 ```py ## 银行间质押式回购利率 def getHistDayInterestRateInterbankRepo(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的银行间质押式回购利率周期为: # 1D, 7D, 14D, 21D, 1M, 3M, 4M, 6M, 9M, 1Y indicID = [u"M120000067", u"M120000068", u"M120000069", u"M120000070", u"M120000071", u"M120000072", u"M120000073", u"M120000074", u"M120000075", u"M120000076"] period = np.asarray([1.0, 7.0, 14.0, 21.0, 30.0, 90.0, 120.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interbank_repo = DataAPI.ChinaDataInterestRateInterbankRepoGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interbank_repo = interbank_repo.groupby('indicID').first() interbank_repo = concat([interbank_repo, period_matrix], axis=1, join='inner').sort_index() return interbank_repo ## 银行间同业拆借利率 def getHistDaySHIBOR(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的SHIBOR周期为: # 1D, 7D, 14D, 1M, 3M, 6M, 9M, 1Y indicID = [u"M120000057", u"M120000058", u"M120000059", u"M120000060", u"M120000061", u"M120000062", u"M120000063", u"M120000064"] period = np.asarray([1.0, 7.0, 14.0, 30.0, 90.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interest_shibor = DataAPI.ChinaDataInterestRateSHIBORGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interest_shibor = interest_shibor.groupby('indicID').first() interest_shibor = concat([interest_shibor, period_matrix], axis=1, join='inner').sort_index() return interest_shibor ## 插值得到给定的周期的无风险利率 def periodsSplineRiskFreeInterestRate(date, periods): # 此处使用SHIBOR来插值 init_shibor = getHistDaySHIBOR(date) shibor = {} min_period = min(init_shibor.period.values) min_period = 10.0/360.0 max_period = max(init_shibor.period.values) for p in periods.keys(): tmp = periods[p] if periods[p] > max_period: tmp = max_period * 0.99999 elif periods[p] < min_period: tmp = min_period * 1.00001 sh = interpolate.spline(init_shibor.period.values, init_shibor.dataValue.values, [tmp], order=3) shibor[p] = sh[0]/100.0 return shibor ``` ## 1. Greeks 和 隐含波动率 本文中计算的Greeks包括: + `delta` 期权价格关于标的价格的一阶导数 + `gamma` 期权价格关于标的价格的二阶导数 + `rho` 期权价格关于无风险利率的一阶导数 + `theta` 期权价格关于到期时间的一阶导数 + `vega` 期权价格关于波动率的一阶导数 注意: + 计算隐含波动率,我们采用Black-Scholes-Merton模型,此模型在平台Python包CAL中已有实现 + 无风险利率使用SHIBOR + 期权的时间价值为负时(此种情况在50ETF期权里时有发生),没法通过BSM模型计算隐含波动率,故此时将期权隐含波动率设为0.0,实际上,此时的隐含波动率和各风险指标并无实际参考价值 + 此处计算即时隐含波动率和Greeks时候,我们使用买一和卖一的中间价作为期权价格 ```py ## 使用DataAPI.OptGet, DataAPI.MktOptionTickRTSnapshotGet 拿到计算所需数据 def getOptTickSnapshot(opt_var_sec, date): date_str = date.toISO().replace('-', '') #使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息 info_fields = [u'optID', u'varSecID', u'varShortName', u'varTicker', u'varExchangeCD', u'varType', u'contractType', u'strikePrice', u'contMultNum', u'contractStatus', u'listDate', u'expYear', u'expMonth', u'expDate', u'lastTradeDate', u'exerDate', u'deliDate', u'delistDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE",u"L"], field=info_fields, pandas="1") #使用DataAPI.MktOptionTickRTSnapshotGet,拿到期权实时成交数据 date_str = date.toISO().replace('-', '') fields_mkt = ['instrumentID', u'optionId', u'dataDate', u'lastPrice', u'preSettlePrice', u'bidBook_price1', u'bidBook_volume1', u'askBook_price1', u'askBook_volume1'] opt_mkt = DataAPI.MktOptionTickRTSnapshotGet(optionId=u"", field='', pandas="1") opt_mkt = opt_mkt[opt_mkt.dataDate == date.toISO()] opt_mkt['optID'] = map(int, opt_mkt['optionId']) opt_mkt[u"price"] = (opt_mkt['bidBook_price1'] + opt_mkt['askBook_price1'])/2.0 opt_info = opt_info.set_index(u"optID") opt_mkt = opt_mkt.set_index(u"optID") opt = concat([opt_info, opt_mkt], axis=1, join='inner').sort_index() return opt ## 分析期权即时交易信息,得到隐含波动率微笑和期权风险指标 def getOptAnalysisSnapshot(opt_var_sec): date = Date.todaysDate() opt = getOptTickSnapshot(opt_var_sec, date) #使用DataAPI.MktTickRTSnapshotGet 拿到期权标的的即时行情 date_str = date.toISO().replace('-', '') opt_var_mkt = DataAPI.MktTickRTSnapshotGet(securityID=opt_var_sec,field=u"lastPrice",pandas="1") # 计算shibor exp_dates_str = opt.expDate.unique() periods = {} for date_str in exp_dates_str: exp_date = Date.parseISO(date_str) periods[exp_date] = (exp_date - date)/360.0 shibor = periodsSplineRiskFreeInterestRate(date, periods) settle = opt.price.values # 期权 settle price close = opt.price.values # 期权 close price strike = opt.strikePrice.values # 期权 strike price option_type = opt.contractType.values # 期权类型 exp_date_str = opt.expDate.values # 期权行权日期 eval_date_str = opt.dataDate.values # 期权交易日期 mat_dates = [] eval_dates = [] spot = [] for epd, evd in zip(exp_date_str, eval_date_str): mat_dates.append(Date.parseISO(epd)) eval_dates.append(Date.parseISO(evd)) spot.append(opt_var_mkt.lastPrice[0]) time_to_maturity = [float(mat - eva + 1.0)/365.0 for (mat, eva) in zip(mat_dates, eval_dates)] risk_free = [] # 无风险利率 for s, mat, time in zip(spot, mat_dates, time_to_maturity): #rf = math.log(forward_price[mat] / s) / time rf = shibor[mat] risk_free.append(rf) opt_types = [] # 期权类型 for t in option_type: if t == 'CO': opt_types.append(1) else: opt_types.append(-1) # 使用通联CAL包中 BSMImpliedVolatity 计算隐含波动率 calculated_vol = BSMImpliedVolatity(opt_types, strike, spot, risk_free, 0.0, time_to_maturity, settle) calculated_vol = calculated_vol.fillna(0.0) # 使用通联CAL包中 BSMPrice 计算期权风险指标 greeks = BSMPrice(opt_types, strike, spot, risk_free, 0.0, calculated_vol.vol.values, time_to_maturity) # vega、rho、theta 的计量单位参照上交所的数据,以求统一对比 greeks.vega = greeks.vega #/ 100.0 greeks.rho = greeks.rho #/ 100.0 greeks.theta = greeks.theta #* 365.0 / 252.0 #/ 365.0 opt['strike'] = strike opt['optType'] = option_type opt['expDate'] = exp_date_str opt['spotPrice'] = spot opt['riskFree'] = risk_free opt['timeToMaturity'] = np.around(time_to_maturity, decimals=4) opt['settle'] = np.around(greeks.price.values.astype(np.double), decimals=4) opt['iv'] = np.around(calculated_vol.vol.values.astype(np.double), decimals=4) opt['delta'] = np.around(greeks.delta.values.astype(np.double), decimals=4) opt['vega'] = np.around(greeks.vega.values.astype(np.double), decimals=4) opt['gamma'] = np.around(greeks.gamma.values.astype(np.double), decimals=4) opt['theta'] = np.around(greeks.theta.values.astype(np.double), decimals=4) opt['rho'] = np.around(greeks.rho.values.astype(np.double), decimals=4) fields = [u'instrumentID', u'contractType', u'strikePrice', u'expDate', u'dataDate', u'dataTime', u'price', 'spotPrice', u'iv', u'delta', u'vega', u'gamma', u'theta', u'rho'] opt = opt[fields].reset_index().set_index('instrumentID').sort_index() #opt['iv'] = opt.iv.replace(to_replace=0.0, value=np.nan) return opt # 期权分析数据整理 def getOptSnapshotGreeksIV(): # Uqer 计算期权的风险数据 opt_var_sec = u"510050.XSHG" # 期权标的 opt = getOptAnalysisSnapshot(opt_var_sec) # 整理数据部分 opt.index = [index[-10:] for index in opt.index] opt = opt[['spotPrice', 'contractType','strikePrice','expDate','price','iv','delta','theta','gamma','vega','rho']] opt_call = opt[opt.contractType=='CO'] opt_put = opt[opt.contractType=='PO'] opt_call.columns = pd.MultiIndex.from_tuples([('Call', c) for c in opt_call.columns]) opt_call[('Call-Put', 'spotPrice')] = opt_call[('Call', 'spotPrice')] opt_call[('Call-Put', 'strikePrice')] = opt_call[('Call', 'strikePrice')] opt_put.columns = pd.MultiIndex.from_tuples([('Put', c) for c in opt_put.columns]) opt = concat([opt_call, opt_put], axis=1, join='inner').sort_index() opt = opt.set_index(('Call','expDate')).sort_index() opt = opt.drop([('Call','contractType'), ('Call','strikePrice'), ('Call','spotPrice')], axis=1) opt = opt.drop([('Put','expDate'), ('Put','contractType'), ('Put','strikePrice'), ('Put','spotPrice')], axis=1) opt.index.name = 'expDate' ## 以上得到完整的历史某日数据,格式简洁明了 return opt ``` 即时风险数据计算,其中的price就是买一、卖一价格的平均 ```py DataAPI.MktTickRTSnapshotGet(securityID="510050.XSHG",field=u"",pandas="1").columns Index([u'exchangeCD', u'ticker', u'timestamp', u'AggQty', u'amplitude', u'change', u'changePct', u'currencyCD', u'dataDate', u'dataTime', u'deal', u'extraLargeOrderValue', u'highPrice', u'IEP', u'largeOrderValue', u'lastPrice', u'localTimestamp', u'lowPrice', u'mediumOrderValue', u'negMarketValue', u'nominalPrice', u'openPrice', u'orderType', u'prevClosePrice', u'shortNM', u'smallOrderValue', u'staticPE', u'suspension', u'totalOrderValue', u'tradSessionID', u'tradSessionStatus', u'tradSessionSubID', u'tradStatus', u'tradType', u'turnoverRate', u'utcOffset', u'value', u'volume', u'VWAP', u'Yield', u'bidBook_price1', u'bidBook_volume1', u'bidBook_price2', u'bidBook_volume2', u'bidBook_price3', u'bidBook_volume3', u'bidBook_price4', u'bidBook_volume4', u'bidBook_price5', u'bidBook_volume5', u'askBook_price1', u'askBook_volume1', u'askBook_price2', u'askBook_volume2', u'askBook_price3', u'askBook_volume3', u'askBook_price4', u'askBook_volume4', u'askBook_price5', u'askBook_volume5'], dtype='object') ``` ```py opt = getOptSnapshotGreeksIV() opt.head(20) ``` | | Call | Call-Put | Put | | --- | --- | | price | iv | delta | theta | gamma | vega | rho | spotPrice | strikePrice | price | iv | delta | theta | gamma | vega | rho | | expDate | | | | | | | | | | | | | | | | | | 2015-10-28 | 0.35250 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.85 | 0.00265 | 0.4139 | -0.0300 | -0.1281 | 0.3097 | 0.0362 | -0.0040 | | 2015-10-28 | 0.30390 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.90 | 0.00490 | 0.4093 | -0.0517 | -0.1965 | 0.4868 | 0.0562 | -0.0069 | | 2015-10-28 | 0.25710 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.95 | 0.00810 | 0.3985 | -0.0810 | -0.2706 | 0.7086 | 0.0797 | -0.0108 | | 2015-10-28 | 0.20990 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 2.00 | 0.01130 | 0.3716 | -0.1133 | -0.3219 | 0.9729 | 0.1021 | -0.0151 | | 2015-10-28 | 0.16690 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 2.05 | 0.01710 | 0.3539 | -0.1649 | -0.3942 | 1.3199 | 0.1318 | -0.0220 | | 2015-10-28 | 0.12500 | 0.1998 | 0.8793 | -0.2390 | 1.8916 | 0.1067 | 0.1049 | 2.215 | 2.10 | 0.02605 | 0.3391 | -0.2367 | -0.4668 | 1.7125 | 0.1639 | -0.0317 | | 2015-10-28 | 0.09155 | 0.2392 | 0.7182 | -0.4171 | 2.6574 | 0.1794 | 0.0863 | 2.215 | 2.15 | 0.03965 | 0.3287 | -0.3305 | -0.5273 | 2.0746 | 0.1925 | -0.0444 | | 2015-10-28 | 0.06285 | 0.2510 | 0.5679 | -0.4908 | 2.9485 | 0.2089 | 0.0688 | 2.215 | 2.20 | 0.06060 | 0.3297 | -0.4416 | -0.5701 | 2.2532 | 0.2097 | -0.0598 | | 2015-10-28 | 0.04160 | 0.2615 | 0.4241 | -0.4994 | 2.8189 | 0.2081 | 0.0516 | 2.215 | 2.25 | 0.09095 | 0.3483 | -0.5500 | -0.5979 | 2.1391 | 0.2103 | -0.0753 | | 2015-10-28 | 0.02795 | 0.2780 | 0.3064 | -0.4697 | 2.3766 | 0.1865 | 0.0374 | 2.215 | 2.30 | 0.12510 | 0.3612 | -0.6450 | -0.5751 | 1.9401 | 0.1978 | -0.0894 | | 2015-10-28 | 0.01655 | 0.2793 | 0.2049 | -0.3791 | 1.9141 | 0.1509 | 0.0252 | 2.215 | 2.35 | 0.16490 | 0.3831 | -0.7188 | -0.5449 | 1.6571 | 0.1792 | -0.1011 | | 2015-11-25 | 0.17915 | 0.1663 | 0.9149 | -0.1371 | 1.1545 | 0.1264 | 0.2480 | 2.215 | 2.05 | 0.04815 | 0.3692 | -0.2509 | -0.3361 | 1.0627 | 0.2584 | -0.0811 | | 2015-11-25 | 0.14805 | 0.2196 | 0.7751 | -0.2490 | 1.6819 | 0.2433 | 0.2106 | 2.215 | 2.10 | 0.06685 | 0.3775 | -0.3136 | -0.3803 | 1.1574 | 0.2878 | -0.1022 | | 2015-11-25 | 0.12025 | 0.2438 | 0.6649 | -0.3116 | 1.8413 | 0.2957 | 0.1816 | 2.215 | 2.15 | 0.08985 | 0.3880 | -0.3780 | -0.4163 | 1.2072 | 0.3085 | -0.1245 | | 2015-11-25 | 0.09455 | 0.2541 | 0.5657 | -0.3391 | 1.9085 | 0.3194 | 0.1555 | 2.215 | 2.20 | 0.11335 | 0.3891 | -0.4408 | -0.4293 | 1.2495 | 0.3202 | -0.1463 | | 2015-11-25 | 0.07355 | 0.2631 | 0.4721 | -0.3475 | 1.8635 | 0.3230 | 0.1305 | 2.215 | 2.25 | 0.14205 | 0.3964 | -0.5023 | -0.4380 | 1.2402 | 0.3238 | -0.1684 | | 2015-11-25 | 0.05525 | 0.2667 | 0.3849 | -0.3335 | 1.7659 | 0.3102 | 0.1070 | 2.215 | 2.30 | 0.17425 | 0.4052 | -0.5598 | -0.4381 | 1.1993 | 0.3201 | -0.1899 | | 2015-12-23 | 0.38610 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.80 | 0.02025 | 0.3907 | -0.0997 | -0.1573 | 0.4406 | 0.1782 | -0.0509 | | 2015-12-23 | 0.34710 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.85 | 0.02735 | 0.3885 | -0.1280 | -0.1862 | 0.5295 | 0.2129 | -0.0656 | | 2015-12-23 | 0.30415 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.90 | 0.03625 | 0.3865 | -0.1610 | -0.2152 | 0.6212 | 0.2485 | -0.0829 | ## 2. 隐含波动率微笑 利用上一小节的代码,给出隐含波动率微笑结构 隐含波动率微笑 ```py # 做图展示某一天的隐含波动率微笑 def plotSnapshotSmileVolatility(): # Uqer 计算期权的风险数据 opt = getOptSnapshotGreeksIV() # 下面展示波动率微笑 exp_dates = np.sort(opt.index.unique()) ## ---------------------------------------------- fig = plt.figure(figsize=(10,8)) fig.set_tight_layout(True) for i in range(exp_dates.shape[0]): date = exp_dates[i] ax = fig.add_subplot(2,2,i+1) opt_date = opt[opt.index==date].set_index(('Call-Put', 'strikePrice')) opt_date.index.name = 'strikePrice' ax.plot(opt_date.index, opt_date[('Call', 'iv')], '-o') ax.plot(opt_date.index, opt_date[('Put', 'iv')], '-s') ax.legend(['call', 'put'], loc=0) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Implied Volatility") plt.title(exp_dates[i]) ``` ```py plotSnapshotSmileVolatility() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc03be3.png) 行权价和行权日期两个方向上的隐含波动率微笑 ```py from mpl_toolkits.mplot3d import Axes3D from matplotlib import cm # 做图展示某一天的隐含波动率结构 def plotSnapshotSmileVolatilitySurface(): # Uqer 计算期权的风险数据 date = Date.todaysDate() opt = getOptSnapshotGreeksIV() # 下面展示波动率结构 exp_dates = np.sort(opt.index.unique()) strikes = np.sort(opt[('Call-Put', 'strikePrice')].unique()) risk_mt = {'Call': pd.DataFrame(index=strikes), 'Put': pd.DataFrame(index=strikes) } # 将数据整理成Call和Put分开来,分别的结构为: # 行为行权价,列为剩余到期天数(以自然天数计算) for epd in exp_dates: exp_days = Date.parseISO(epd) - date opt_date = opt[opt.index==epd].set_index(('Call-Put', 'strikePrice')) opt_date.index.name = 'strikePrice' for cp in risk_mt.keys(): risk_mt[cp][exp_days] = opt_date[(cp, 'iv')] for cp in risk_mt.keys(): for strike in risk_mt[cp].index: if np.sum(np.isnan(risk_mt[cp].ix[strike])) > 0: risk_mt[cp] = risk_mt[cp].drop(strike) # Call和Put分开显示,行index为行权价,列index为剩余到期天数 #print risk_mt # 画图 for cp in ['Call', 'Put']: opt = risk_mt[cp] x = [] y = [] z = [] for xx in opt.index: for yy in opt.columns: x.append(xx) y.append(yy) z.append(opt[yy][xx]) fig = plt.figure(figsize=(10,8)) fig.suptitle(cp) ax = fig.gca(projection='3d') ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2) return risk_mt ``` 画出某一天的波动率微笑曲面结构 ```py opt = plotSnapshotSmileVolatilitySurface() opt # Call和Put分开显示,行index为行权价,列index为剩余到期天数 {'Call': 20 48 76 167 2.05 0.0000 0.1663 0.2027 0.2263 2.10 0.1998 0.2196 0.2283 0.2430 2.15 0.2392 0.2438 0.2446 0.2502 2.20 0.2510 0.2541 0.2570 0.2579 2.25 0.2615 0.2631 0.2646 0.2639 2.30 0.2780 0.2667 0.2763 0.2673, 'Put': 20 48 76 167 2.05 0.3535 0.3692 0.3965 0.3965 2.10 0.3391 0.3775 0.4002 0.4002 2.15 0.3287 0.3877 0.4116 0.4030 2.20 0.3297 0.3891 0.4185 0.4069 2.25 0.3483 0.3964 0.4228 0.4084 2.30 0.3612 0.4052 0.4287 0.4149} ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc1ff3d.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc433f6.png) 波动率曲面结构图中: + 上图为Call,下图为Put,此处没有进行任何插值处理,略显粗糙 + Put的隐含波动率明显大于Call + 期限结构来说,波动率呈现远高近低的特征 + 切记:所有的隐含波动率为0代表着期权的时间价值为负,此时的风险数据实际上并无多大参考意义!!
';

【50ETF期权】 5. 日内即时监控 Greeks 和隐含波动率微笑

最后更新于:2022-04-01 21:58:54

# 【50ETF期权】 5. 日内即时监控 Greeks 和隐含波动率微笑 > 来源:https://uqer.io/community/share/5615d10ff9f06c4ca72fb5be 和上一篇类似,计算上证50ETF期权的隐含波动率微笑,但这里通过DataAPI提供的高频数据,在交易时间实时监控Greeks 和隐含波动率变化! ```py from CAL.PyCAL import * import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib import rc rc('mathtext', default='regular') import seaborn as sns sns.set_style('white') import math from scipy import interpolate from scipy.stats import mstats from pandas import Series, DataFrame, concat import time from matplotlib import dates ``` 上海银行间同业拆借利率 SHIBOR,用来作为无风险利率参考 ```py ## 银行间质押式回购利率 def getHistDayInterestRateInterbankRepo(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的银行间质押式回购利率周期为: # 1D, 7D, 14D, 21D, 1M, 3M, 4M, 6M, 9M, 1Y indicID = [u"M120000067", u"M120000068", u"M120000069", u"M120000070", u"M120000071", u"M120000072", u"M120000073", u"M120000074", u"M120000075", u"M120000076"] period = np.asarray([1.0, 7.0, 14.0, 21.0, 30.0, 90.0, 120.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interbank_repo = DataAPI.ChinaDataInterestRateInterbankRepoGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interbank_repo = interbank_repo.groupby('indicID').first() interbank_repo = concat([interbank_repo, period_matrix], axis=1, join='inner').sort_index() return interbank_repo ## 银行间同业拆借利率 def getHistDaySHIBOR(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的SHIBOR周期为: # 1D, 7D, 14D, 1M, 3M, 6M, 9M, 1Y indicID = [u"M120000057", u"M120000058", u"M120000059", u"M120000060", u"M120000061", u"M120000062", u"M120000063", u"M120000064"] period = np.asarray([1.0, 7.0, 14.0, 30.0, 90.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interest_shibor = DataAPI.ChinaDataInterestRateSHIBORGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interest_shibor = interest_shibor.groupby('indicID').first() interest_shibor = concat([interest_shibor, period_matrix], axis=1, join='inner').sort_index() return interest_shibor ## 插值得到给定的周期的无风险利率 def periodsSplineRiskFreeInterestRate(date, periods): # 此处使用SHIBOR来插值 init_shibor = getHistDaySHIBOR(date) shibor = {} min_period = min(init_shibor.period.values) min_period = 10.0/360.0 max_period = max(init_shibor.period.values) for p in periods.keys(): tmp = periods[p] if periods[p] > max_period: tmp = max_period * 0.99999 elif periods[p] < min_period: tmp = min_period * 1.00001 sh = interpolate.spline(init_shibor.period.values, init_shibor.dataValue.values, [tmp], order=3) shibor[p] = sh[0]/100.0 return shibor ``` ## 1. Greeks 和 隐含波动率 本文中计算的Greeks包括: + `delta` 期权价格关于标的价格的一阶导数 + `gamma` 期权价格关于标的价格的二阶导数 + `rho` 期权价格关于无风险利率的一阶导数 + `theta` 期权价格关于到期时间的一阶导数 + `vega` 期权价格关于波动率的一阶导数 注意: + 计算隐含波动率,我们采用Black-Scholes-Merton模型,此模型在平台Python包CAL中已有实现 + 无风险利率使用SHIBOR + 期权的时间价值为负时(此种情况在50ETF期权里时有发生),没法通过BSM模型计算隐含波动率,故此时将期权隐含波动率设为0.0,实际上,此时的隐含波动率和各风险指标并无实际参考价值 + 此处计算即时隐含波动率和Greeks时候,我们使用买一和卖一的中间价作为期权价格 ```py ## 使用DataAPI.OptGet, DataAPI.MktOptionTickRTSnapshotGet 拿到计算所需数据 def getOptTickSnapshot(opt_var_sec, date): date_str = date.toISO().replace('-', '') #使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息 info_fields = [u'optID', u'varSecID', u'varShortName', u'varTicker', u'varExchangeCD', u'varType', u'contractType', u'strikePrice', u'contMultNum', u'contractStatus', u'listDate', u'expYear', u'expMonth', u'expDate', u'lastTradeDate', u'exerDate', u'deliDate', u'delistDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE",u"L"], field=info_fields, pandas="1") #使用DataAPI.MktOptionTickRTSnapshotGet,拿到期权实时成交数据 date_str = date.toISO().replace('-', '') fields_mkt = ['instrumentID', u'optionId', u'dataDate', u'lastPrice', u'preSettlePrice', u'bidBook_price1', u'bidBook_volume1', u'askBook_price1', u'askBook_volume1'] opt_mkt = DataAPI.MktOptionTickRTSnapshotGet(optionId=u"", field='', pandas="1") opt_mkt = opt_mkt[opt_mkt.dataDate == date.toISO()] opt_mkt['optID'] = map(int, opt_mkt['optionId']) opt_mkt[u"price"] = (opt_mkt['bidBook_price1'] + opt_mkt['askBook_price1'])/2.0 opt_info = opt_info.set_index(u"optID") opt_mkt = opt_mkt.set_index(u"optID") opt = concat([opt_info, opt_mkt], axis=1, join='inner').sort_index() return opt ## 分析期权即时交易信息,得到隐含波动率微笑和期权风险指标 def getOptAnalysisSnapshot(opt_var_sec): date = Date.todaysDate() opt = getOptTickSnapshot(opt_var_sec, date) #使用DataAPI.MktTickRTSnapshotGet 拿到期权标的的即时行情 date_str = date.toISO().replace('-', '') opt_var_mkt = DataAPI.MktTickRTSnapshotGet(securityID=opt_var_sec,field=u"lastPrice",pandas="1") # 计算shibor exp_dates_str = opt.expDate.unique() periods = {} for date_str in exp_dates_str: exp_date = Date.parseISO(date_str) periods[exp_date] = (exp_date - date)/360.0 shibor = periodsSplineRiskFreeInterestRate(date, periods) settle = opt.price.values # 期权 settle price close = opt.price.values # 期权 close price strike = opt.strikePrice.values # 期权 strike price option_type = opt.contractType.values # 期权类型 exp_date_str = opt.expDate.values # 期权行权日期 eval_date_str = opt.dataDate.values # 期权交易日期 mat_dates = [] eval_dates = [] spot = [] for epd, evd in zip(exp_date_str, eval_date_str): mat_dates.append(Date.parseISO(epd)) eval_dates.append(Date.parseISO(evd)) spot.append(opt_var_mkt.lastPrice[0]) time_to_maturity = [float(mat - eva + 1.0)/365.0 for (mat, eva) in zip(mat_dates, eval_dates)] risk_free = [] # 无风险利率 for s, mat, time in zip(spot, mat_dates, time_to_maturity): #rf = math.log(forward_price[mat] / s) / time rf = shibor[mat] risk_free.append(rf) opt_types = [] # 期权类型 for t in option_type: if t == 'CO': opt_types.append(1) else: opt_types.append(-1) # 使用通联CAL包中 BSMImpliedVolatity 计算隐含波动率 calculated_vol = BSMImpliedVolatity(opt_types, strike, spot, risk_free, 0.0, time_to_maturity, settle) calculated_vol = calculated_vol.fillna(0.0) # 使用通联CAL包中 BSMPrice 计算期权风险指标 greeks = BSMPrice(opt_types, strike, spot, risk_free, 0.0, calculated_vol.vol.values, time_to_maturity) # vega、rho、theta 的计量单位参照上交所的数据,以求统一对比 greeks.vega = greeks.vega #/ 100.0 greeks.rho = greeks.rho #/ 100.0 greeks.theta = greeks.theta #* 365.0 / 252.0 #/ 365.0 opt['strike'] = strike opt['optType'] = option_type opt['expDate'] = exp_date_str opt['spotPrice'] = spot opt['riskFree'] = risk_free opt['timeToMaturity'] = np.around(time_to_maturity, decimals=4) opt['settle'] = np.around(greeks.price.values.astype(np.double), decimals=4) opt['iv'] = np.around(calculated_vol.vol.values.astype(np.double), decimals=4) opt['delta'] = np.around(greeks.delta.values.astype(np.double), decimals=4) opt['vega'] = np.around(greeks.vega.values.astype(np.double), decimals=4) opt['gamma'] = np.around(greeks.gamma.values.astype(np.double), decimals=4) opt['theta'] = np.around(greeks.theta.values.astype(np.double), decimals=4) opt['rho'] = np.around(greeks.rho.values.astype(np.double), decimals=4) fields = [u'instrumentID', u'contractType', u'strikePrice', u'expDate', u'dataDate', u'dataTime', u'price', 'spotPrice', u'iv', u'delta', u'vega', u'gamma', u'theta', u'rho'] opt = opt[fields].reset_index().set_index('instrumentID').sort_index() #opt['iv'] = opt.iv.replace(to_replace=0.0, value=np.nan) return opt # 期权分析数据整理 def getOptSnapshotGreeksIV(): # Uqer 计算期权的风险数据 opt_var_sec = u"510050.XSHG" # 期权标的 opt = getOptAnalysisSnapshot(opt_var_sec) # 整理数据部分 opt.index = [index[-10:] for index in opt.index] opt = opt[['spotPrice', 'contractType','strikePrice','expDate','price','iv','delta','theta','gamma','vega','rho']] opt_call = opt[opt.contractType=='CO'] opt_put = opt[opt.contractType=='PO'] opt_call.columns = pd.MultiIndex.from_tuples([('Call', c) for c in opt_call.columns]) opt_call[('Call-Put', 'spotPrice')] = opt_call[('Call', 'spotPrice')] opt_call[('Call-Put', 'strikePrice')] = opt_call[('Call', 'strikePrice')] opt_put.columns = pd.MultiIndex.from_tuples([('Put', c) for c in opt_put.columns]) opt = concat([opt_call, opt_put], axis=1, join='inner').sort_index() opt = opt.set_index(('Call','expDate')).sort_index() opt = opt.drop([('Call','contractType'), ('Call','strikePrice'), ('Call','spotPrice')], axis=1) opt = opt.drop([('Put','expDate'), ('Put','contractType'), ('Put','strikePrice'), ('Put','spotPrice')], axis=1) opt.index.name = 'expDate' ## 以上得到完整的历史某日数据,格式简洁明了 return opt ``` 即时风险数据计算,其中的`price`就是买一、卖一价格的平均 ```py DataAPI.MktTickRTSnapshotGet(securityID="510050.XSHG",field=u"",pandas="1").columns Index([u'exchangeCD', u'ticker', u'timestamp', u'AggQty', u'amplitude', u'change', u'changePct', u'currencyCD', u'dataDate', u'dataTime', u'deal', u'extraLargeOrderValue', u'highPrice', u'IEP', u'largeOrderValue', u'lastPrice', u'localTimestamp', u'lowPrice', u'mediumOrderValue', u'negMarketValue', u'nominalPrice', u'openPrice', u'orderType', u'prevClosePrice', u'shortNM', u'smallOrderValue', u'staticPE', u'suspension', u'totalOrderValue', u'tradSessionID', u'tradSessionStatus', u'tradSessionSubID', u'tradStatus', u'tradType', u'turnoverRate', u'utcOffset', u'value', u'volume', u'VWAP', u'Yield', u'bidBook_price1', u'bidBook_volume1', u'bidBook_price2', u'bidBook_volume2', u'bidBook_price3', u'bidBook_volume3', u'bidBook_price4', u'bidBook_volume4', u'bidBook_price5', u'bidBook_volume5', u'askBook_price1', u'askBook_volume1', u'askBook_price2', u'askBook_volume2', u'askBook_price3', u'askBook_volume3', u'askBook_price4', u'askBook_volume4', u'askBook_price5', u'askBook_volume5'], dtype='object') ``` ```py opt = getOptSnapshotGreeksIV() opt.head(20) ``` | | Call | Call-Put | Put | | --- | --- | | price | iv | delta | theta | gamma | vega | rho | spotPrice | strikePrice | price | iv | delta | theta | gamma | vega | rho | | expDate | | | | | | | | | | | | | | | | | | 2015-10-28 | 0.35250 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.85 | 0.00265 | 0.4139 | -0.0300 | -0.1281 | 0.3097 | 0.0362 | -0.0040 | | 2015-10-28 | 0.30390 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.90 | 0.00490 | 0.4093 | -0.0517 | -0.1965 | 0.4868 | 0.0562 | -0.0069 | | 2015-10-28 | 0.25710 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.95 | 0.00810 | 0.3985 | -0.0810 | -0.2706 | 0.7086 | 0.0797 | -0.0108 | | 2015-10-28 | 0.20990 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 2.00 | 0.01130 | 0.3716 | -0.1133 | -0.3219 | 0.9729 | 0.1021 | -0.0151 | | 2015-10-28 | 0.16690 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 2.05 | 0.01710 | 0.3539 | -0.1649 | -0.3942 | 1.3199 | 0.1318 | -0.0220 | | 2015-10-28 | 0.12500 | 0.1998 | 0.8793 | -0.2390 | 1.8916 | 0.1067 | 0.1049 | 2.215 | 2.10 | 0.02605 | 0.3391 | -0.2367 | -0.4668 | 1.7125 | 0.1639 | -0.0317 | | 2015-10-28 | 0.09155 | 0.2392 | 0.7182 | -0.4171 | 2.6574 | 0.1794 | 0.0863 | 2.215 | 2.15 | 0.03965 | 0.3287 | -0.3305 | -0.5273 | 2.0746 | 0.1925 | -0.0444 | | 2015-10-28 | 0.06285 | 0.2510 | 0.5679 | -0.4908 | 2.9485 | 0.2089 | 0.0688 | 2.215 | 2.20 | 0.06060 | 0.3297 | -0.4416 | -0.5701 | 2.2532 | 0.2097 | -0.0598 | | 2015-10-28 | 0.04160 | 0.2615 | 0.4241 | -0.4994 | 2.8189 | 0.2081 | 0.0516 | 2.215 | 2.25 | 0.09095 | 0.3483 | -0.5500 | -0.5979 | 2.1391 | 0.2103 | -0.0753 | | 2015-10-28 | 0.02795 | 0.2780 | 0.3064 | -0.4697 | 2.3766 | 0.1865 | 0.0374 | 2.215 | 2.30 | 0.12510 | 0.3612 | -0.6450 | -0.5751 | 1.9401 | 0.1978 | -0.0894 | | 2015-10-28 | 0.01655 | 0.2793 | 0.2049 | -0.3791 | 1.9141 | 0.1509 | 0.0252 | 2.215 | 2.35 | 0.16490 | 0.3831 | -0.7188 | -0.5449 | 1.6571 | 0.1792 | -0.1011 | | 2015-11-25 | 0.17915 | 0.1663 | 0.9149 | -0.1371 | 1.1545 | 0.1264 | 0.2480 | 2.215 | 2.05 | 0.04815 | 0.3692 | -0.2509 | -0.3361 | 1.0627 | 0.2584 | -0.0811 | | 2015-11-25 | 0.14805 | 0.2196 | 0.7751 | -0.2490 | 1.6819 | 0.2433 | 0.2106 | 2.215 | 2.10 | 0.06685 | 0.3775 | -0.3136 | -0.3803 | 1.1574 | 0.2878 | -0.1022 | | 2015-11-25 | 0.12025 | 0.2438 | 0.6649 | -0.3116 | 1.8413 | 0.2957 | 0.1816 | 2.215 | 2.15 | 0.08985 | 0.3880 | -0.3780 | -0.4163 | 1.2072 | 0.3085 | -0.1245 | | 2015-11-25 | 0.09455 | 0.2541 | 0.5657 | -0.3391 | 1.9085 | 0.3194 | 0.1555 | 2.215 | 2.20 | 0.11335 | 0.3891 | -0.4408 | -0.4293 | 1.2495 | 0.3202 | -0.1463 | | 2015-11-25 | 0.07355 | 0.2631 | 0.4721 | -0.3475 | 1.8635 | 0.3230 | 0.1305 | 2.215 | 2.25 | 0.14205 | 0.3964 | -0.5023 | -0.4380 | 1.2402 | 0.3238 | -0.1684 | | 2015-11-25 | 0.05525 | 0.2667 | 0.3849 | -0.3335 | 1.7659 | 0.3102 | 0.1070 | 2.215 | 2.30 | 0.17425 | 0.4052 | -0.5598 | -0.4381 | 1.1993 | 0.3201 | -0.1899 | | 2015-12-23 | 0.38610 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.80 | 0.02025 | 0.3907 | -0.0997 | -0.1573 | 0.4406 | 0.1782 | -0.0509 | | 2015-12-23 | 0.34710 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.85 | 0.02735 | 0.3885 | -0.1280 | -0.1862 | 0.5295 | 0.2129 | -0.0656 | | 2015-12-23 | 0.30415 | 0.0000 | NaN | NaN | NaN | NaN | NaN | 2.215 | 1.90 | 0.03625 | 0.3865 | -0.1610 | -0.2152 | 0.6212 | 0.2485 | -0.0829 | ## 2. 隐含波动率微笑 利用上一小节的代码,给出隐含波动率微笑结构 隐含波动率微笑 ```py # 做图展示某一天的隐含波动率微笑 def plotSnapshotSmileVolatility(): # Uqer 计算期权的风险数据 opt = getOptSnapshotGreeksIV() # 下面展示波动率微笑 exp_dates = np.sort(opt.index.unique()) ## ---------------------------------------------- fig = plt.figure(figsize=(10,8)) fig.set_tight_layout(True) for i in range(exp_dates.shape[0]): date = exp_dates[i] ax = fig.add_subplot(2,2,i+1) opt_date = opt[opt.index==date].set_index(('Call-Put', 'strikePrice')) opt_date.index.name = 'strikePrice' ax.plot(opt_date.index, opt_date[('Call', 'iv')], '-o') ax.plot(opt_date.index, opt_date[('Put', 'iv')], '-s') ax.legend(['call', 'put'], loc=0) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Implied Volatility") plt.title(exp_dates[i]) ``` ```py plotSnapshotSmileVolatility() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc03be3.png) 行权价和行权日期两个方向上的隐含波动率微笑 ```py from mpl_toolkits.mplot3d import Axes3D from matplotlib import cm # 做图展示某一天的隐含波动率结构 def plotSnapshotSmileVolatilitySurface(): # Uqer 计算期权的风险数据 date = Date.todaysDate() opt = getOptSnapshotGreeksIV() # 下面展示波动率结构 exp_dates = np.sort(opt.index.unique()) strikes = np.sort(opt[('Call-Put', 'strikePrice')].unique()) risk_mt = {'Call': pd.DataFrame(index=strikes), 'Put': pd.DataFrame(index=strikes) } # 将数据整理成Call和Put分开来,分别的结构为: # 行为行权价,列为剩余到期天数(以自然天数计算) for epd in exp_dates: exp_days = Date.parseISO(epd) - date opt_date = opt[opt.index==epd].set_index(('Call-Put', 'strikePrice')) opt_date.index.name = 'strikePrice' for cp in risk_mt.keys(): risk_mt[cp][exp_days] = opt_date[(cp, 'iv')] for cp in risk_mt.keys(): for strike in risk_mt[cp].index: if np.sum(np.isnan(risk_mt[cp].ix[strike])) > 0: risk_mt[cp] = risk_mt[cp].drop(strike) # Call和Put分开显示,行index为行权价,列index为剩余到期天数 #print risk_mt # 画图 for cp in ['Call', 'Put']: opt = risk_mt[cp] x = [] y = [] z = [] for xx in opt.index: for yy in opt.columns: x.append(xx) y.append(yy) z.append(opt[yy][xx]) fig = plt.figure(figsize=(10,8)) fig.suptitle(cp) ax = fig.gca(projection='3d') ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2) return risk_mt ``` 画出某一天的波动率微笑曲面结构 ```py opt = plotSnapshotSmileVolatilitySurface() opt # Call和Put分开显示,行index为行权价,列index为剩余到期天数 {'Call': 20 48 76 167 2.05 0.0000 0.1663 0.2027 0.2263 2.10 0.1998 0.2196 0.2283 0.2430 2.15 0.2392 0.2438 0.2446 0.2502 2.20 0.2510 0.2541 0.2570 0.2579 2.25 0.2615 0.2631 0.2646 0.2639 2.30 0.2780 0.2667 0.2763 0.2673, 'Put': 20 48 76 167 2.05 0.3535 0.3692 0.3965 0.3965 2.10 0.3391 0.3775 0.4002 0.4002 2.15 0.3287 0.3877 0.4116 0.4030 2.20 0.3297 0.3891 0.4185 0.4069 2.25 0.3483 0.3964 0.4228 0.4084 2.30 0.3612 0.4052 0.4287 0.4149} ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc1ff3d.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbc433f6.png) 波动率曲面结构图中: + 上图为Call,下图为Put,此处没有进行任何插值处理,略显粗糙 + Put的隐含波动率明显大于Call + 期限结构来说,波动率呈现远高近低的特征 + 切记:所有的隐含波动率为0代表着期权的时间价值为负,此时的风险数据实际上并无多大参考意义!!
';

【50ETF期权】 4. Greeks 和隐含波动率微笑

最后更新于:2022-04-01 21:58:52

# 【50ETF期权】 4. Greeks 和隐含波动率微笑 > 来源:https://uqer.io/community/share/560769faf9f06c597165ef75 在本文中,我们将通过量化实验室提供的数据,计算上证50ETF期权的隐含波动率微笑。 ```py from CAL.PyCAL import * import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib import rc rc('mathtext', default='regular') import seaborn as sns sns.set_style('white') import math from scipy import interpolate from scipy.stats import mstats from pandas import Series, DataFrame, concat import time from matplotlib import dates ``` 上海银行间同业拆借利率 SHIBOR,用来作为无风险利率参考 ```py ## 银行间质押式回购利率 def getHistDayInterestRateInterbankRepo(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的银行间质押式回购利率周期为: # 1D, 7D, 14D, 21D, 1M, 3M, 4M, 6M, 9M, 1Y indicID = [u"M120000067", u"M120000068", u"M120000069", u"M120000070", u"M120000071", u"M120000072", u"M120000073", u"M120000074", u"M120000075", u"M120000076"] period = np.asarray([1.0, 7.0, 14.0, 21.0, 30.0, 90.0, 120.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interbank_repo = DataAPI.ChinaDataInterestRateInterbankRepoGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interbank_repo = interbank_repo.groupby('indicID').first() interbank_repo = concat([interbank_repo, period_matrix], axis=1, join='inner').sort_index() return interbank_repo ## 银行间同业拆借利率 def getHistDaySHIBOR(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的SHIBOR周期为: # 1D, 7D, 14D, 1M, 3M, 6M, 9M, 1Y indicID = [u"M120000057", u"M120000058", u"M120000059", u"M120000060", u"M120000061", u"M120000062", u"M120000063", u"M120000064"] period = np.asarray([1.0, 7.0, 14.0, 30.0, 90.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interest_shibor = DataAPI.ChinaDataInterestRateSHIBORGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interest_shibor = interest_shibor.groupby('indicID').first() interest_shibor = concat([interest_shibor, period_matrix], axis=1, join='inner').sort_index() return interest_shibor ## 插值得到给定的周期的无风险利率 def periodsSplineRiskFreeInterestRate(date, periods): # 此处使用SHIBOR来插值 init_shibor = getHistDaySHIBOR(date) shibor = {} min_period = min(init_shibor.period.values) min_period = 10.0/360.0 max_period = max(init_shibor.period.values) for p in periods.keys(): tmp = periods[p] if periods[p] > max_period: tmp = max_period * 0.99999 elif periods[p] < min_period: tmp = min_period * 1.00001 sh = interpolate.spline(init_shibor.period.values, init_shibor.dataValue.values, [tmp], order=3) shibor[p] = sh[0]/100.0 return shibor ``` 1. Greeks 和 隐含波动率计算 本文中计算的Greeks包括: + `delta` 期权价格关于标的价格的一阶导数 + `gamma` 期权价格关于标的价格的二阶导数 + `rho` 期权价格关于无风险利率的一阶导数 + `theta` 期权价格关于到期时间的一阶导数 + `vega` 期权价格关于波动率的一阶导数 注意: + 计算隐含波动率,我们采用Black-Scholes-Merton模型,此模型在平台Python包CAL中已有实现 + 无风险利率使用SHIBOR + 期权的时间价值为负时(此种情况在50ETF期权里时有发生),没法通过BSM模型计算隐含波动率,故此时将期权隐含波动率设为0.0,实际上,此时的隐含波动率和各风险指标并无实际参考价值 ```py ## 使用DataAPI.OptGet, DataAPI.MktOptdGet拿到计算所需数据 def getOptDayData(opt_var_sec, date): date_str = date.toISO().replace('-', '') #使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息 info_fields = [u'optID', u'varSecID', u'varShortName', u'varTicker', u'varExchangeCD', u'varType', u'contractType', u'strikePrice', u'contMultNum', u'contractStatus', u'listDate', u'expYear', u'expMonth', u'expDate', u'lastTradeDate', u'exerDate', u'deliDate', u'delistDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE",u"L"], field=info_fields, pandas="1") #使用DataAPI.MktOptdGet,拿到历史上某一天的期权成交信息 mkt_fields = [u'ticker', u'optID', u'secShortName', u'exchangeCD', u'tradeDate', u'preSettlePrice', u'preClosePrice', u'openPrice', u'highestPrice', u'lowestPrice', u'closePrice', u'settlPrice', u'turnoverVol', u'turnoverValue', u'openInt'] opt_mkt = DataAPI.MktOptdGet(tradeDate=date_str, field=mkt_fields, pandas = "1") opt_info = opt_info.set_index(u"optID") opt_mkt = opt_mkt.set_index(u"optID") opt = concat([opt_info, opt_mkt], axis=1, join='inner').sort_index() return opt ## 分析历史某一日的期权收盘价信息,得到隐含波动率微笑和期权风险指标 def getOptDayAnalysis(opt_var_sec, date): opt = getOptDayData(opt_var_sec, date) #使用DataAPI.MktFunddGet拿到期权标的的日行情 date_str = date.toISO().replace('-', '') opt_var_mkt = DataAPI.MktFunddGet(secID=opt_var_sec,tradeDate=date_str,beginDate=u"",endDate=u"",field=u"",pandas="1") #opt_var_mkt = DataAPI.MktFunddAdjGet(secID=opt_var_sec,beginDate=date_str,endDate=date_str,field=u"",pandas="1") # 计算shibor exp_dates_str = opt.expDate.unique() periods = {} for date_str in exp_dates_str: exp_date = Date.parseISO(date_str) periods[exp_date] = (exp_date - date)/360.0 shibor = periodsSplineRiskFreeInterestRate(date, periods) settle = opt.settlPrice.values # 期权 settle price close = opt.closePrice.values # 期权 close price strike = opt.strikePrice.values # 期权 strike price option_type = opt.contractType.values # 期权类型 exp_date_str = opt.expDate.values # 期权行权日期 eval_date_str = opt.tradeDate.values # 期权交易日期 mat_dates = [] eval_dates = [] spot = [] for epd, evd in zip(exp_date_str, eval_date_str): mat_dates.append(Date.parseISO(epd)) eval_dates.append(Date.parseISO(evd)) spot.append(opt_var_mkt.closePrice[0]) time_to_maturity = [float(mat - eva + 1.0)/365.0 for (mat, eva) in zip(mat_dates, eval_dates)] risk_free = [] # 无风险利率 for s, mat, time in zip(spot, mat_dates, time_to_maturity): #rf = math.log(forward_price[mat] / s) / time rf = shibor[mat] risk_free.append(rf) opt_types = [] # 期权类型 for t in option_type: if t == 'CO': opt_types.append(1) else: opt_types.append(-1) # 使用通联CAL包中 BSMImpliedVolatity 计算隐含波动率 calculated_vol = BSMImpliedVolatity(opt_types, strike, spot, risk_free, 0.0, time_to_maturity, settle) calculated_vol = calculated_vol.fillna(0.0) # 使用通联CAL包中 BSMPrice 计算期权风险指标 greeks = BSMPrice(opt_types, strike, spot, risk_free, 0.0, calculated_vol.vol.values, time_to_maturity) greeks.vega = greeks.vega #/ 100.0 greeks.rho = greeks.rho #/ 100.0 greeks.theta = greeks.theta #* 365.0 / 252.0 #/ 365.0 opt['strike'] = strike opt['optType'] = option_type opt['expDate'] = exp_date_str opt['spotPrice'] = spot opt['riskFree'] = risk_free opt['timeToMaturity'] = np.around(time_to_maturity, decimals=4) opt['settle'] = np.around(greeks.price.values.astype(np.double), decimals=4) opt['iv'] = np.around(calculated_vol.vol.values.astype(np.double), decimals=4) opt['delta'] = np.around(greeks.delta.values.astype(np.double), decimals=4) opt['vega'] = np.around(greeks.vega.values.astype(np.double), decimals=4) opt['gamma'] = np.around(greeks.gamma.values.astype(np.double), decimals=4) opt['theta'] = np.around(greeks.theta.values.astype(np.double), decimals=4) opt['rho'] = np.around(greeks.rho.values.astype(np.double), decimals=4) fields = [u'ticker', u'contractType', u'strikePrice', u'expDate', u'tradeDate', u'closePrice', u'settlPrice', 'spotPrice', u'iv', u'delta', u'vega', u'gamma', u'theta', u'rho'] opt = opt[fields].reset_index().set_index('ticker').sort_index() #opt['iv'] = opt.iv.replace(to_replace=0.0, value=np.nan) return opt ``` 尝试用 `getOptDayAnalysis` 计算 2015-09-24 这一天的风险指标 ```py # Uqer 计算期权的风险数据 opt_var_sec = u"510050.XSHG" # 期权标的 date = Date(2015, 9, 24) option_risk = getOptDayAnalysis(opt_var_sec, date) option_risk.head(2) ``` | | optID | contractType | strikePrice | expDate | tradeDate | closePrice | settlPrice | spotPrice | iv | delta | vega | gamma | theta | rho | | --- | --- | | ticker | | | | | | | | | | | | | | | | 510050C1510M01850 | 10000405 | CO | 1.85 | 2015-10-28 | 2015-09-24 | 0.3268 | 0.3555 | 2.187 | 0.4317 | 0.9101 | 0.1099 | 0.5550 | -0.2992 | 0.1568 | | 510050C1510M01900 | 10000406 | CO | 1.90 | 2015-10-28 | 2015-09-24 | 0.2791 | 0.3102 | 2.187 | 0.4161 | 0.8810 | 0.1347 | 0.7058 | -0.3435 | 0.1550 | 进一步,我们和上交所给出的对应日期的风险指标参考数据对比一下 + 上交所的数据需要自行下载,注意选择日期下载相应csv文件,http://www.sse.com.cn/assortment/derivatives/options/risk/ + 下载完后,不做内容改动,请上传到UQER平台的 Data 中;文件名请相应修改,此处我设为了 `option_risk_sse_0924.csv` + 为了避免冗余,下面我们仅仅对比近月期权的各个风险指标 ```py # 读取上交所数据 def readRiskDataSSE(file_str): # 按照上交所下载到的risk数据排版格式,做以处理 opt = pd.read_csv(file_str, encoding='gb2312').reset_index() opt.columns = [['tradeDate','optID','ticker','secShortName','delta','theta','gamma','vega','rho','margin']] opt = opt[['tradeDate','optID','ticker','delta','theta','gamma','vega','rho']] opt['ticker'] = [tic[1:-2] for tic in opt['ticker']] opt['tradeDate'] = [td[0:-1] for td in opt['tradeDate']] #使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息 info_fields = [u'optID', u'varSecID', u'varShortName', u'varTicker', u'varExchangeCD', u'varType', u'contractType', u'strikePrice', u'contMultNum', u'contractStatus', u'listDate', u'expYear', u'expMonth', u'expDate', u'lastTradeDate', u'exerDate', u'deliDate', u'delistDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE",u"L"], field=info_fields, pandas="1") # 上交所的数据和期权基本信息合并,得到比较完整的期权数据 opt_info = opt_info.set_index(u"optID") opt = opt.set_index(u"optID") opt = concat([opt_info, opt], axis=1, join='inner').sort_index() fields = [u'ticker', u'contractType', u'strikePrice', u'expDate', u'tradeDate', u'delta', u'vega', u'gamma', u'theta', u'rho'] opt = opt[fields].reset_index().set_index('ticker').sort_index() return opt ``` 读取 2015-09-24 上交所数据 ```py option_risk_sse = readRiskDataSSE('option_risk_sse_0924.csv') option_risk_sse.head(2) ``` | | optID | contractType | strikePrice | expDate | tradeDate | delta | vega | gamma | theta | rho | | --- | --- | | ticker | | | | | | | | | | | | 510050C1510M01850 | 10000405 | CO | 1.85 | 2015-10-28 | 2015-09-24 | 0.910 | 0.109 | 0.555 | -0.303 | 0.154 | | 510050C1510M01900 | 10000406 | CO | 1.90 | 2015-10-28 | 2015-09-24 | 0.881 | 0.134 | 0.706 | -0.349 | 0.153 | `getOptDayAnalysis` 函数计算结果和上交所数据的对比 ```py # 对比本文计算结果 option_risk 和上交所结果 option_risk_sse 中的近月期权风险指标 near_exp = np.sort(option_risk.expDate.unique())[0] # 近月期权行权日 opt_call_uqer = option_risk[option_risk.expDate==near_exp][option_risk.contractType=='CO'].set_index('strikePrice') opt_call_sse = option_risk_sse[option_risk_sse.expDate==near_exp][option_risk_sse.contractType=='CO'].set_index('strikePrice') opt_put_uqer = option_risk[option_risk.expDate==near_exp][option_risk.contractType=='PO'].set_index('strikePrice') opt_put_sse = option_risk_sse[option_risk_sse.expDate==near_exp][option_risk_sse.contractType=='PO'].set_index('strikePrice') ## ---------------------------------------------- ## 风险指标对比 fig = plt.figure(figsize=(10,12)) fig.set_tight_layout(True) # ------ Delta ------ ax = fig.add_subplot(321) ax.plot(opt_call_uqer.index, opt_call_uqer['delta'], '-') ax.plot(opt_call_sse.index, opt_call_sse['delta'], 's') ax.plot(opt_put_uqer.index, opt_put_uqer['delta'], '-') ax.plot(opt_put_sse.index, opt_put_sse['delta'], 's') ax.legend(['call-uqer', 'call-sse', 'put-uqer', 'put-sse']) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Delta") plt.title('Delta Comparison') # ------ Theta ------ ax = fig.add_subplot(322) ax.plot(opt_call_uqer.index, opt_call_uqer['theta'], '-') ax.plot(opt_call_sse.index, opt_call_sse['theta'], 's') ax.plot(opt_put_uqer.index, opt_put_uqer['theta'], '-') ax.plot(opt_put_sse.index, opt_put_sse['theta'], 's') ax.legend(['call-uqer', 'call-sse', 'put-uqer', 'put-sse']) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Theta") plt.title('Theta Comparison') # ------ Gamma ------ ax = fig.add_subplot(323) ax.plot(opt_call_uqer.index, opt_call_uqer['gamma'], '-') ax.plot(opt_call_sse.index, opt_call_sse['gamma'], 's') ax.plot(opt_put_uqer.index, opt_put_uqer['gamma'], '-') ax.plot(opt_put_sse.index, opt_put_sse['gamma'], 's') ax.legend(['call-uqer', 'call-sse', 'put-uqer', 'put-sse'], loc=0) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Gamma") plt.title('Gamma Comparison') # # ------ Vega ------ ax = fig.add_subplot(324) ax.plot(opt_call_uqer.index, opt_call_uqer['vega'], '-') ax.plot(opt_call_sse.index, opt_call_sse['vega'], 's') ax.plot(opt_put_uqer.index, opt_put_uqer['vega'], '-') ax.plot(opt_put_sse.index, opt_put_sse['vega'], 's') ax.legend(['call-uqer', 'call-sse', 'put-uqer', 'put-sse'], loc=4) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Vega") plt.title('Vega Comparison') # ------ Rho ------ ax = fig.add_subplot(325) ax.plot(opt_call_uqer.index, opt_call_uqer['rho'], '-') ax.plot(opt_call_sse.index, opt_call_sse['rho'], 's') ax.plot(opt_put_uqer.index, opt_put_uqer['rho'], '-') ax.plot(opt_put_sse.index, opt_put_sse['rho'], 's') ax.legend(['call-uqer', 'call-sse', 'put-uqer', 'put-sse'], loc=3) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Rho") plt.title('Rho Comparison') ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbb7928c.png) 上述五张图中,对于近月期权,我们分别对比了五个Greeks风险指标:`Delta`, `Theta`, `Gamma`, `Vega`, `Rho`: + 每张图中,`Call` 和 `Put` 分开比较,横轴为行权价 + 可以看出,本文中的计算结果和上交所的参考数值符合的比较好 + 在接下来的50ETF期权分析中,我们将使用本文中的计算方法来计算期权隐含波动率和Greeks风险指标 把上面的数据整理整理,格式更简洁一点 ```py # 每日期权分析数据整理 def getOptDayGreeksIV(date): # Uqer 计算期权的风险数据 opt_var_sec = u"510050.XSHG" # 期权标的 opt = getOptDayAnalysis(opt_var_sec, date) # 整理数据部分 opt.index = [index[-10:] for index in opt.index] opt = opt[['contractType','strikePrice','expDate','closePrice','iv','delta','theta','gamma','vega','rho']] opt_call = opt[opt.contractType=='CO'] opt_put = opt[opt.contractType=='PO'] opt_call.columns = pd.MultiIndex.from_tuples([('Call', c) for c in opt_call.columns]) opt_call[('Call-Put', 'strikePrice')] = opt_call[('Call', 'strikePrice')] opt_put.columns = pd.MultiIndex.from_tuples([('Put', c) for c in opt_put.columns]) opt = concat([opt_call, opt_put], axis=1, join='inner').sort_index() opt = opt.set_index(('Call','expDate')).sort_index() opt = opt.drop([('Call','contractType'), ('Call','strikePrice')], axis=1) opt = opt.drop([('Put','expDate'), ('Put','contractType'), ('Put','strikePrice')], axis=1) opt.index.name = 'expDate' ## 以上得到完整的历史某日数据,格式简洁明了 return opt ``` ```py date = Date(2015, 9, 24) option_risk = getOptDayGreeksIV(date) option_risk.head(10) ``` | | Call | Call-Put | Put | | --- | --- | | closePrice | iv | delta | theta | gamma | vega | rho | strikePrice | closePrice | iv | delta | theta | gamma | vega | rho | | expDate | | | | | | | | | | | | | | | | | 2015-10-28 | 0.3268 | 0.4317 | 0.9101 | -0.2992 | 0.5550 | 0.1099 | 0.1568 | 1.85 | 0.0129 | 0.4319 | -0.0900 | -0.2410 | 0.5551 | 0.1100 | -0.0201 | | 2015-10-28 | 0.2791 | 0.4161 | 0.8810 | -0.3435 | 0.7058 | 0.1347 | 0.1550 | 1.90 | 0.0176 | 0.4174 | -0.1197 | -0.2854 | 0.7063 | 0.1352 | -0.0268 | | 2015-10-28 | 0.2360 | 0.3990 | 0.8449 | -0.3862 | 0.8823 | 0.1615 | 0.1517 | 1.95 | 0.0232 | 0.3992 | -0.1552 | -0.3247 | 0.8822 | 0.1615 | -0.0348 | | 2015-10-28 | 0.1955 | 0.1811 | 0.9532 | -0.1225 | 0.7980 | 0.0663 | 0.1811 | 2.00 | 0.0345 | 0.4020 | -0.2105 | -0.3940 | 1.0601 | 0.1954 | -0.0474 | | 2015-10-28 | 0.1599 | 0.2453 | 0.8237 | -0.2764 | 1.5588 | 0.1754 | 0.1574 | 2.05 | 0.0474 | 0.3975 | -0.2703 | -0.4441 | 1.2290 | 0.2241 | -0.0612 | | 2015-10-28 | 0.1275 | 0.2698 | 0.7137 | -0.3696 | 1.8625 | 0.2304 | 0.1374 | 2.10 | 0.0643 | 0.3952 | -0.3381 | -0.4847 | 1.3660 | 0.2476 | -0.0771 | | 2015-10-28 | 0.0990 | 0.2814 | 0.6081 | -0.4208 | 2.0162 | 0.2602 | 0.1180 | 2.15 | 0.0869 | 0.4013 | -0.4114 | -0.5200 | 1.4317 | 0.2635 | -0.0946 | | 2015-10-28 | 0.0768 | 0.2955 | 0.5057 | -0.4489 | 1.9934 | 0.2701 | 0.0987 | 2.20 | 0.1146 | 0.4121 | -0.4836 | -0.5428 | 1.4284 | 0.2699 | -0.1124 | | 2015-10-28 | 0.0584 | 0.3068 | 0.4132 | -0.4487 | 1.8746 | 0.2637 | 0.0810 | 2.25 | 0.1450 | 0.4200 | -0.5517 | -0.5438 | 1.3908 | 0.2679 | -0.1296 | | 2015-10-28 | 0.0470 | 0.3264 | 0.3381 | -0.4434 | 1.6538 | 0.2476 | 0.0664 | 2.30 | 0.1826 | 0.4426 | -0.6091 | -0.5520 | 1.2809 | 0.2600 | -0.1452 | ## 2. 隐含波动率微笑 利用上一小节的代码,给出隐含波动率微笑结构 隐含波动率微笑 ```py # 做图展示某一天的隐含波动率微笑 def plotSmileVolatility(date): # Uqer 计算期权的风险数据 opt = getOptDayGreeksIV(date) # 下面展示波动率微笑 exp_dates = np.sort(opt.index.unique()) ## ---------------------------------------------- fig = plt.figure(figsize=(10,8)) fig.set_tight_layout(True) for i in range(exp_dates.shape[0]): date = exp_dates[i] ax = fig.add_subplot(2,2,i+1) opt_date = opt[opt.index==date].set_index(('Call-Put', 'strikePrice')) opt_date.index.name = 'strikePrice' ax.plot(opt_date.index, opt_date[('Call', 'iv')], '-o') ax.plot(opt_date.index, opt_date[('Put', 'iv')], '-s') ax.legend(['call', 'put'], loc=0) ax.grid() ax.set_xlabel(u"strikePrice") ax.set_ylabel(r"Implied Volatility") plt.title(exp_dates[i]) ``` ```py plotSmileVolatility(Date(2015,9,24)) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbb989ed.png) 行权价和行权日期两个方向上的隐含波动率微笑 ```py from mpl_toolkits.mplot3d import Axes3D from matplotlib import cm # 做图展示某一天的隐含波动率结构 def plotSmileVolatilitySurface(date): # Uqer 计算期权的风险数据 opt = getOptDayGreeksIV(date) # 下面展示波动率结构 exp_dates = np.sort(opt.index.unique()) strikes = np.sort(opt[('Call-Put', 'strikePrice')].unique()) risk_mt = {'Call': pd.DataFrame(index=strikes), 'Put': pd.DataFrame(index=strikes) } # 将数据整理成Call和Put分开来,分别的结构为: # 行为行权价,列为剩余到期天数(以自然天数计算) for epd in exp_dates: exp_days = Date.parseISO(epd) - date opt_date = opt[opt.index==epd].set_index(('Call-Put', 'strikePrice')) opt_date.index.name = 'strikePrice' for cp in risk_mt.keys(): risk_mt[cp][exp_days] = opt_date[(cp, 'iv')] for cp in risk_mt.keys(): for strike in risk_mt[cp].index: if np.sum(np.isnan(risk_mt[cp].ix[strike])) > 0: risk_mt[cp] = risk_mt[cp].drop(strike) # Call和Put分开显示,行index为行权价,列index为剩余到期天数 #print risk_mt # 画图 for cp in ['Call', 'Put']: opt = risk_mt[cp] x = [] y = [] z = [] for xx in opt.index: for yy in opt.columns: x.append(xx) y.append(yy) z.append(opt[yy][xx]) fig = plt.figure(figsize=(10,8)) fig.suptitle(cp) ax = fig.gca(projection='3d') ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2) return risk_mt ``` 画出某一天的波动率微笑曲面结构 ```py opt = plotSmileVolatilitySurface(Date(2015,9,24)) opt # Call和Put分开显示,行index为行权价,列index为剩余到期天数 {'Call': 34 62 90 181 2.10 0.2698 0.2817 0.2823 0.3042 2.15 0.2814 0.2888 0.2916 0.3063 2.20 0.2955 0.3008 0.2922 0.3237 2.25 0.3068 0.3067 0.3093 0.3157 2.30 0.3264 0.3155 0.3128 0.3172, 'Put': 34 62 90 181 2.10 0.3952 0.4403 0.4740 0.4449 2.15 0.4013 0.4442 0.4794 0.4632 2.20 0.4121 0.4498 0.4802 0.4451 2.25 0.4200 0.4581 0.4863 0.4547 2.30 0.4426 0.4673 0.4893 0.4691} ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbbb5536.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbbd80e3.png) 波动率曲面结构图中: + 上图为Call,下图为Put,此处没有进行任何插值处理,所以略显粗糙 + Put的隐含波动率明显大于Call + 期限结构来说,波动率呈现远高近低的特征
';

【50ETF期权】 3. 中国波指 iVIX

最后更新于:2022-04-01 21:58:50

# 【50ETF期权】 3. 中国波指 iVIX > 来源:https://uqer.io/community/share/560493f7f9f06c590c65ef21 在本文中,我们将通过量化实验室提供的数据,计算基于50ETF期权的中国波指 iVIX 波动率VIX指数是跟踪市场波动性的指数,一般通过标的期权的隐含波动率计算得来。当VIX越高,表示市场参与者预期后市波动程度会更加激烈,同时也反映其不安的心理状态;相反,VIX越低时,则反映市场参与者预期后市波动程度会趋于缓和。因此,VIX又被称为投资人恐慌指标(The Investor Fear Gauge)。 中国波指由上交所发布,用于衡量上证50ETF未来30日的预期波动。按照上交所网页描述:该指数是根据方差互换的原理,结合50ETF期权的实际运作特点,并通过对上证所交易的50ETF期权价格的计算编制而得。网址为: http://www.sse.com.cn/assortment/derivatives/options/volatility/ , 该网页中发布历史 iVIX 和当日日内 iVIX 数据 ```py from CAL.PyCAL import * import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib import rc rc('mathtext', default='regular') import seaborn as sns sns.set_style('white') from matplotlib import dates from pandas import Series, DataFrame, concat from scipy import interpolate import math import time ``` 上证50ETF收盘价,用来和iVIX对比走势 ```py # 华夏上证50ETF secID = '510050.XSHG' begin = Date(2015, 2, 9) end = Date.todaysDate() fields = ['tradeDate', 'closePrice'] etf = DataAPI.MktFunddGet(secID, beginDate=begin.toISO().replace('-', ''), endDate=end.toISO().replace('-', ''), field=fields) etf['tradeDate'] = pd.to_datetime(etf['tradeDate']) etf = etf.set_index('tradeDate') etf.tail(2) ``` | | closePrice | | --- | --- | | tradeDate | | | 2015-09-23 | 2.180 | | 2015-09-24 | 2.187 | 上海银行间同业拆借利率 SHIBOR,用来作为无风险利率参考 ```py ## 银行间质押式回购利率 def getHistDayInterestRateInterbankRepo(date): cal = Calendar('China.SSE') period = Period('-10B') begin = cal.advanceDate(date, period) begin_str = begin.toISO().replace('-', '') date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的银行间质押式回购利率周期为: # 1D, 7D, 14D, 21D, 1M, 3M, 4M, 6M, 9M, 1Y indicID = [u"M120000067", u"M120000068", u"M120000069", u"M120000070", u"M120000071", u"M120000072", u"M120000073", u"M120000074", u"M120000075", u"M120000076"] period = np.asarray([1.0, 7.0, 14.0, 21.0, 30.0, 90.0, 120.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interbank_repo = DataAPI.ChinaDataInterestRateInterbankRepoGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1") interbank_repo = interbank_repo.groupby('indicID').first() interbank_repo = concat([interbank_repo, period_matrix], axis=1, join='inner').sort_index() return interbank_repo ## 银行间同业拆借利率 def getHistDaySHIBOR(date): date_str = date.toISO().replace('-', '') # 以下的indicID分别对应的SHIBOR周期为: # 1D, 7D, 14D, 1M, 3M, 6M, 9M, 1Y indicID = [u"M120000057", u"M120000058", u"M120000059", u"M120000060", u"M120000061", u"M120000062", u"M120000063", u"M120000064"] period = np.asarray([1.0, 7.0, 14.0, 30.0, 90.0, 180.0, 270.0, 360.0]) / 360.0 period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period']) field = u"indicID,indicName,publishTime,periodDate,dataValue,unit" interest_shibor = DataAPI.ChinaDataInterestRateSHIBORGet(indicID=indicID,beginDate=date_str,endDate=date_str,field=field,pandas="1") interest_shibor = interest_shibor.set_index('indicID') interest_shibor = concat([interest_shibor, period_matrix], axis=1, join='inner').sort_index() return interest_shibor ## 插值得到给定的周期的无风险利率 def periodsSplineRiskFreeInterestRate(date, periods): # 此处使用SHIBOR来插值 init_shibor = getHistDaySHIBOR(date) shibor = {} min_period = min(init_shibor.period.values) max_period = max(init_shibor.period.values) for p in periods.keys(): tmp = periods[p] if periods[p] > max_period: tmp = max_period * 0.99999 elif periods[p] < min_period: tmp = min_period * 1.00001 sh = interpolate.spline(init_shibor.period.values, init_shibor.dataValue.values, [tmp], order=3) shibor[p] = sh[0]/100.0 return shibor ``` 50ETF历史波动率,用来和iVIX走势作对比 ```py ## 计算一段时间标的的历史波动率,返回值包括以下不同周期的波动率: # 一周,半月,一个月,两个月,三个月,四个月,五个月,半年,九个月,一年,两年 def getHistVolatilityEWMA(secID, beginDate, endDate): cal = Calendar('China.SSE') spotBeginDate = cal.advanceDate(beginDate,'-520B',BizDayConvention.Preceding) spotBeginDate = Date(2006, 1, 1) begin = spotBeginDate.toISO().replace('-', '') end = endDate.toISO().replace('-', '') fields = ['tradeDate', 'preClosePrice', 'closePrice', 'settlePrice', 'preSettlePrice'] security = DataAPI.MktFunddGet(secID, beginDate=begin, endDate=end, field=fields) security['dailyReturn'] = security['closePrice']/security['preClosePrice'] # 日回报率 security['u2'] = (np.log(security['dailyReturn']))**2 # u2为复利形式的日回报率平方 # security['u2'] = (security['dailyReturn'] - 1.0)**2 # u2为日价格变化百分比的平方 security['tradeDate'] = pd.to_datetime(security['tradeDate']) periods = {'hv1W': 5, 'hv2W': 10, 'hv1M': 21, 'hv2M': 41, 'hv3M': 62, 'hv4M': 83, 'hv5M': 104, 'hv6M': 124, 'hv9M': 186, 'hv1Y': 249, 'hv2Y': 497} # 利用pandas中的ewma模型计算波动率 for prd in periods.keys(): # 此处的span实际上就是上面计算波动率公式中lambda表达式中的N security[prd] = np.round(np.sqrt(pd.ewma(security['u2'], span=periods[prd], adjust=False)), 5)*math.sqrt(252.0) security = security[security.tradeDate >= beginDate.toISO()] security = security.set_index('tradeDate') return security ``` ## 1. 计算历史每日 iVIX 计算方法参考CBOE的手册:http://www.cboe.com/micro/vix/part2.aspx ```py # 计算历史某一天的iVIX def calDayVIX(date, opt_info): var_sec = u"510050.XSHG" # 使用DataAPI.MktOptdGet,拿到历史上某一天的期权行情信息 date_str = date.toISO().replace('-', '') fields_mkt = [u"optID", "tradeDate", "closePrice", 'settlPrice'] opt_mkt = DataAPI.MktOptdGet(tradeDate=date_str, field=fields_mkt, pandas="1") opt_mkt = opt_mkt.set_index(u"optID") opt_mkt[u"price"] = opt_mkt['closePrice'] # concat某一日行情和期权基本信息,得到所需数据 opt = concat([opt_info, opt_mkt], axis=1, join='inner').sort_index() opt = opt[opt.varSecID==var_sec] exp_dates = map(Date.parseISO, np.sort(opt.expDate.unique())) trade_date = date exp_periods = {} for epd in exp_dates: exp_periods[epd] = (epd - date)*1.0/365.0 risk_free = periodsSplineRiskFreeInterestRate(trade_date, exp_periods) sigma_square = {} for date in exp_dates: # 计算某一日的vix opt_date = opt[opt.expDate==date.toISO()] rf = risk_free[date] #rf = 0.05 opt_call = opt_date[opt_date.contractType == 'CO'].set_index('strikePrice') opt_put = opt_date[opt_date.contractType == 'PO'].set_index('strikePrice') opt_call_price = opt_call[[u'price']].sort_index() opt_put_price = opt_put[[u'price']].sort_index() opt_call_price.columns = [u'callPrice'] opt_put_price.columns = [u'putPrice'] opt_call_put_price = concat([opt_call_price, opt_put_price], axis=1, join='inner').sort_index() opt_call_put_price['diffCallPut'] = opt_call_put_price.callPrice - opt_call_put_price.putPrice strike = abs(opt_call_put_price['diffCallPut']).idxmin() price_diff = opt_call_put_price['diffCallPut'][strike] ttm = exp_periods[date] fw = strike + np.exp(ttm*rf) * price_diff strikes = np.sort(opt_call_put_price.index.values) delta_K_tmp = np.concatenate((strikes, strikes[-1:], strikes[-1:])) delta_K_tmp = delta_K_tmp - np.concatenate((strikes[0:1], strikes[0:1], strikes)) delta_K = np.concatenate((delta_K_tmp[1:2], delta_K_tmp[2:-2]/2, delta_K_tmp[-2:-1])) delta_K = pd.DataFrame(delta_K, index=strikes, columns=['deltaStrike']) # opt_otm = opt_out_of_money opt_otm = concat([opt_call[opt_call.index>fw], opt_put[opt_put.index 0: strike_ref = max([k for k in strikes[strikes < fw]]) opt_otm['price'][strike_ref] = (opt_call['price'][strike_ref] + opt_call['price'][strike_ref])/2.0 exp_rt = np.exp(rf*ttm) opt_otm['sigmaTerm'] = opt_otm.deltaStrike*opt_otm.price/(opt_otm.index)**2 sigma = opt_otm.sigmaTerm.sum() sigma = (sigma*2.0*exp_rt - (fw*1.0/strike_ref - 1.0)**2)/ttm sigma_square[date] = sigma # d_one, d_two 将被用来计算VIX(30): if exp_periods[exp_dates[0]] >= 1.0/365.0: d_one = exp_dates[0] d_two = exp_dates[1] else: d_one = exp_dates[1] d_two = exp_dates[2] w = (exp_periods[d_two] - 30.0/365.0)/(exp_periods[d_two] - exp_periods[d_one]) vix30 = exp_periods[d_one]*w*sigma_square[d_one] + exp_periods[d_two]*(1 - w)*sigma_square[d_two] vix30 = 100*np.sqrt(vix30*365.0/30.0) # d_one, d_two 将被用来计算VIX(60): d_one = exp_dates[1] d_two = exp_dates[2] w = (exp_periods[d_two] - 60.0/365.0)/(exp_periods[d_two] - exp_periods[d_one]) vix60 = exp_periods[d_one]*w*sigma_square[d_one] + exp_periods[d_two]*(1 - w)*sigma_square[d_two] vix60 = 100*np.sqrt(vix60*365.0/60.0) return vix30, vix60 def getHistDailyVIX(beginDate, endDate): # 计算历史一段时间内的VIX指数并返回 optionVarSecID = u"510050.XSHG" # 使用DataAPI.OptGet,一次拿取所有存在过的期权信息,以备后用 fields_info = ["optID", u"varSecID", u'contractType', u'strikePrice', u'expDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE", u"L"], field=fields_info, pandas="1") opt_info = opt_info.set_index(u"optID") cal = Calendar('China.SSE') cal.addHoliday(Date(2015,9,3)) cal.addHoliday(Date(2015,9,4)) dates = cal.bizDatesList(beginDate, endDate) histVIX = pd.DataFrame(0.0, index=map(Date.toDateTime, dates), columns=['VIX30','VIX60']) histVIX.index.name = 'tradeDate' for date in histVIX.index: try: vix30, vix60 = calDayVIX(Date.fromDateTime(date), opt_info) except: histVIX = histVIX.drop(date) continue histVIX['VIX30'][date] = vix30 histVIX['VIX60'][date] = vix60 return histVIX def getHistOneDayVIX(date): # 计算历史某天的VIX指数并返回 optionVarSecID = u"510050.XSHG" # 使用DataAPI.OptGet,一次拿取所有存在过的期权信息,以备后用 fields_info = ["optID", u"varSecID", u'contractType', u'strikePrice', u'expDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE", u"L"], field=fields_info, pandas="1") opt_info = opt_info.set_index(u"optID") cal = Calendar('China.SSE') cal.addHoliday(Date(2015,9,3)) cal.addHoliday(Date(2015,9,4)) if cal.isBizDay(date): vix30, vix60 = 0.0, 0.0 vix30, vix60 = calDayVIX(date, opt_info) return vix30, vix60 else: print date, "不是工作日" ``` 历史每日iVIX 数据 ```py begin = Date(2015, 2, 9) # 起始日 end = Date.todaysDate() # 截至今天 hist_VIX = getHistDailyVIX(begin, end) hist_VIX.tail() ``` | | VIX30 | VIX60 | | --- | --- | | tradeDate | | | | 2015-09-18 | 38.057648 | 39.074643 | | 2015-09-21 | 37.610259 | 38.559095 | | 2015-09-22 | 34.507456 | 36.788384 | | 2015-09-23 | 36.413426 | 37.837454 | | 2015-09-24 | 37.114348 | 24.346747 | iVIX、50ETF收盘价、50ETF波动率比较 ```py start = Date(2007, 1, 1) end = Date.todaysDate() secID = '510050.XSHG' hist_HV = getHistVolatilityEWMA(secID, start, end) ## ----- 50ETF VIX指数和历史波动率比较 ----- fig = plt.figure(figsize=(10,6)) ax = fig.add_subplot(111) font.set_size(16) hist_HV_plot = hist_HV[hist_HV.index >= Date(2015,2,9).toISO()] etf_plot = etf[etf.index >= Date(2015,2,9).toISO()] lns1 = ax.plot(hist_HV_plot.index, hist_HV_plot.hv1M, '-', label = u'HV(30)') lns2 = ax.plot(hist_VIX.index, hist_VIX.VIX30/100.0, '-r', label = u'VIX(30)') #lns3 = ax.plot(hist_VIX.index, hist_VIX.VIX60/100.0, '-g', label = u'VIX(60)') ax2 = ax.twinx() lns4 = ax2.plot(etf_plot.index, etf_plot.closePrice, 'grey', label = '50ETF closePrice') lns = lns1+lns2+lns4 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=2) ax.grid() ax.set_xlabel(u"tradeDate") ax.set_ylabel(r"VIX") ax2.set_ylabel(r"closePrice") #ax.set_ylim(0, 0.80) ax2.set_ylim(1.5, 4) plt.title('50ETF VIX') ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbae930e.png) ## 2. 基于iVIX的择时策略 策略思路: + 计算 VIX 三日均线 + 前一日 VIX 向上穿过三日均线一定比例,则卖出 + 前一日 VIX 向下穿过三日均线一定比例,则买入 + 只买卖50ETF ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 9, 24) # 回测结束时间 hist_VIX = getHistDailyVIX(start, end) hist_VIX.tail(2) ``` | | VIX30 | VIX60 | | --- | --- | | tradeDate | | | | 2015-09-23 | 36.413426 | 37.837454 | | 2015-09-24 | 37.114348 | 24.346747 | ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 9, 24) # 回测结束时间 benchmark = '510050.XSHG' # 策略参考标准 universe = ['510050.XSHG'] # 股票池 capital_base = 100000 # 起始资金 commission = Commission(0.0,0.0) window_short = 1 window_long = 3 SD = 0.1 hist_VIX['short_window'] = pd.rolling_mean(hist_VIX['VIX30'], window=window_short) hist_VIX['long_window'] = pd.rolling_mean(hist_VIX['VIX30'], window=window_long) def initialize(account): # 初始化虚拟账户状态 account.fund = universe[0] def handle_data(account): # 每个交易日的买入卖出指令 fund = account.fund # 获取回测当日的前一天日期 dt = Date.fromDateTime(account.current_date) cal = Calendar('China.IB') cal.addHoliday(Date(2015,9,3)) cal.addHoliday(Date(2015,9,4)) last_day = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日 last_last_day = cal.advanceDate(last_day,'-1B',BizDayConvention.Preceding) #计算出倒数第二个交易日 last_day_str = last_day.strftime("%Y-%m-%d") last_last_day_str = last_last_day.strftime("%Y-%m-%d") # 计算买入卖出信号 try: short_mean = hist_VIX['short_window'].loc[last_day_str] # 短均线值 long_mean = hist_VIX['long_window'].loc[last_day_str] # 长均线值 long_flag = True if (short_mean - long_mean) < - SD * long_mean else False short_flag = True if (short_mean - long_mean) > SD * long_mean else False except: long_flag = True short_flag = True if long_flag: approximationAmount = int(account.cash / account.referencePrice[fund] / 100.0) * 100 order(fund, approximationAmount) elif short_flag: # 卖出时,全仓清空 order_to(fund, 0) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbb137ec.jpg) ## 3. 日内跟踪计算 iVIX 计算方法和日间iVIX类似 ```py def calSnapshotVIX(date, opt_info): var_sec = u"510050.XSHG" # 使用DataAPI.MktOptdGet,拿到历史上某一天的期权行情信息 date_str = date.toISO().replace('-', '') fields_mkt = [u'optionId', u'dataDate', u'highPrice', u'lastPrice', u'lowPrice', u'openPrice', u'preSettlePrice', u'bidBook_price1', u'bidBook_volume1', u'askBook_price1', u'askBook_volume1'] # opt_mkt = DataAPI.MktOptdGet(tradeDate=date_str, field=fields_mkt, pandas="1") opt_mkt = DataAPI.MktOptionTickRTSnapshotGet(optionId=u"", field='', pandas="1") opt_mkt = opt_mkt[opt_mkt.dataDate == date.toISO()] opt_mkt['optID'] = map(int, opt_mkt['optionId']) opt_mkt = opt_mkt.set_index(u"optID") opt_mkt[u"price"] = (opt_mkt['bidBook_price1'] + opt_mkt['askBook_price1'])/2.0 # concat某一日行情和期权基本信息,得到所需数据 opt = concat([opt_info, opt_mkt], axis=1, join='inner').sort_index() #opt = opt[opt.varSecID==var_sec] exp_dates = map(Date.parseISO, np.sort(opt.expDate.unique())) trade_date = date exp_periods = {} for epd in exp_dates: exp_periods[epd] = (epd - date)*1.0/365.0 risk_free = periodsSplineRiskFreeInterestRate(trade_date, exp_periods) sigma_square = {} for date in exp_dates: # 计算某一日的vix opt_date = opt[opt.expDate==date.toISO()] rf = risk_free[date] #rf = 0.05 opt_call = opt_date[opt_date.contractType == 'CO'].set_index('strikePrice') opt_put = opt_date[opt_date.contractType == 'PO'].set_index('strikePrice') opt_call_price = opt_call[[u'price']].sort_index() opt_put_price = opt_put[[u'price']].sort_index() opt_call_price.columns = [u'callPrice'] opt_put_price.columns = [u'putPrice'] opt_call_put_price = concat([opt_call_price, opt_put_price], axis=1, join='inner').sort_index() opt_call_put_price['diffCallPut'] = opt_call_put_price.callPrice - opt_call_put_price.putPrice strike = abs(opt_call_put_price['diffCallPut']).idxmin() price_diff = opt_call_put_price['diffCallPut'][strike] ttm = exp_periods[date] fw = strike + np.exp(ttm*rf) * price_diff strikes = np.sort(opt_call_put_price.index.values) delta_K_tmp = np.concatenate((strikes, strikes[-1:], strikes[-1:])) delta_K_tmp = delta_K_tmp - np.concatenate((strikes[0:1], strikes[0:1], strikes)) delta_K = np.concatenate((delta_K_tmp[1:2], delta_K_tmp[2:-2]/2, delta_K_tmp[-2:-1])) delta_K = pd.DataFrame(delta_K, index=strikes, columns=['deltaStrike']) # opt_otm = opt_out_of_money opt_otm = concat([opt_call[opt_call.index>fw], opt_put[opt_put.index 0: strike_ref = max([k for k in strikes[strikes < fw]]) opt_otm['price'][strike_ref] = (opt_call['price'][strike_ref] + opt_call['price'][strike_ref])/2.0 exp_rt = np.exp(rf*ttm) opt_otm['sigmaTerm'] = opt_otm.deltaStrike*opt_otm.price/(opt_otm.index)**2 sigma = opt_otm.sigmaTerm.sum() sigma = (sigma*2.0*exp_rt - (fw*1.0/strike_ref - 1.0)**2)/ttm sigma_square[date] = sigma # d_one, d_two 将被用来计算VIX(30): if exp_periods[exp_dates[0]] >= 1.0/365.0: d_one = exp_dates[0] d_two = exp_dates[1] else: d_one = exp_dates[1] d_two = exp_dates[2] w = (exp_periods[d_two] - 30.0/365.0)/(exp_periods[d_two] - exp_periods[d_one]) vix30 = exp_periods[d_one]*w*sigma_square[d_one] + exp_periods[d_two]*(1 - w)*sigma_square[d_two] vix30 = 100*np.sqrt(vix30*365.0/30.0) # d_one, d_two 将被用来计算VIX(60): d_one = exp_dates[1] d_two = exp_dates[2] w = (exp_periods[d_two] - 60.0/365.0)/(exp_periods[d_two] - exp_periods[d_one]) vix60 = exp_periods[d_one]*w*sigma_square[d_one] + exp_periods[d_two]*(1 - w)*sigma_square[d_two] vix60 = 100*np.sqrt(vix60*365.0/60.0) return vix30, vix60 def getTodaySnapshotVIX(): # 计算历史某天的VIX指数并返回 optionVarSecID = u"510050.XSHG" date = Date.todaysDate() # 使用DataAPI.OptGet,一次拿取所有存在过的期权信息,以备后用 fields_info = ["optID", u"varSecID", u'contractType', u'strikePrice', u'expDate'] opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE", u"L"], field=fields_info, pandas="1") opt_info = opt_info.set_index(u"optID") cal = Calendar('China.SSE') cal.addHoliday(Date(2015,9,3)) cal.addHoliday(Date(2015,9,4)) if cal.isBizDay(date): now_long = datetime.now() now = now_long.time().isoformat() if (now > '09:25:00' and now < '11:30:00') or (now > '13:00:00' and now < '15:00:00'): vix30, vix60 = calSnapshotVIX(date, opt_info) vix = pd.DataFrame([[date, vix30, vix60]], index=[now_long], columns=['dataDate', 'VIX30', 'VIX60']) vix.index.name = 'time' else: vix = pd.DataFrame(0.0, index=[], columns=['dataDate', 'VIX30', 'VIX60']) vix.index.name = 'time' return vix else: print "今天: ", date, " 不是工作日" ``` 计算即时的VIX 如果在工作日非交易时间运行计算函数,则得到一个空的`dataframe` ```py getTodaySnapshotVIX() ``` | | dataDate | VIX30 | VIX60 | | --- | --- | | time | | | | 跟踪计算当日日内 VIX 走势 ```py ## 此函数跟踪计算并记录当日日内VIX走势,数据记录在: # 文件 'VIX_intraday_' + Date.todaysDate().toISO() + '.csv' 中 # 该文件保存在登录uqer账号的 Data 空间中 # seconds 为跟踪计算间隔秒数 def trackTodayIntradayVIX(seconds): vix_file_str = 'VIX_intraday_' + Date.todaysDate().toISO() + '.csv' vix = pd.DataFrame(0.0, index=[], columns=['dataDate', 'VIX30', 'VIX60']) vix.index.name = 'time' vix.to_csv(vix_file_str) now = datetime.now().time() while now.isoformat() < '15:00:00': vix = pd.read_csv(vix_file_str).set_index('time') vix_now = getTodaySnapshotVIX() if vix_now.shape[0] > 0: vix = vix.append(vix_now) vix.to_csv(vix_file_str) # print vix_now.index[0], '\t', vix_now.VIX30[0], '\t', vix_now.VIX60[0] time.sleep(seconds) now = datetime.now().time() ``` 注意: `trackTodayIntradayVIX` 函数一经运行,便持续到当日收盘时,除非手动终止运行 ```py # 追踪当前iVIX走势,每隔60秒计算一次即时iVIX time_interval = 60 trackTodayIntradayVIX(time_interval) --------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) in () 1 # 追踪当前iVIX走势,每隔60秒计算一次即时iVIX 2 time_interval = 60 ----> 3 trackTodayIntradayVIX(time_interval) in trackTodayIntradayVIX(seconds) 17 vix.to_csv(vix_file_str) 18 # print vix_now.index[0], '\t', vix_now.VIX30[0], '\t', vix_now.VIX60[0] ---> 19 time.sleep(seconds) 20 now = datetime.now().time() KeyboardInterrupt: ``` 将当日追踪到的iVIX日内走势作图,注意读取数据文件名和 trackTodayIntradayVIX 函数中的存储文件名一致 ```py vix_file_str = 'VIX_intraday_2015-09-23-backup.csv' vix = pd.read_csv(vix_file_str) vix['time'] = [x[11:19] for x in vix.time] vix = vix.set_index('time') ax = vix.plot(figsize=(10,5)) ax.set_xlabel('time') ax.set_ylabel('VIX(%)') ax.set_ylim(35, 39) (35, 39) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbb5b87e.png)
';

【50ETF期权】 2. 历史波动率

最后更新于:2022-04-01 21:58:47

# 【50ETF期权】 2. 历史波动率 > 来源:https://uqer.io/community/share/560493a4f9f06c597565ef03 在本文中,我们将通过量化实验室提供的数据,计算上证50ETF的历史波动率数据 ```py from CAL.PyCAL import * import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib import rc rc('mathtext', default='regular') import seaborn as sns sns.set_style('white') import math from scipy.stats import mstats ``` 50ETF收盘价 ```py # 华夏上证50ETF secID = '510050.XSHG' begin = Date(2015, 2, 9) end = Date.todaysDate() fields = ['tradeDate', 'closePrice'] etf = DataAPI.MktFunddGet(secID, beginDate=begin.toISO().replace('-', ''), endDate=end.toISO().replace('-', ''), field=fields) etf['tradeDate'] = pd.to_datetime(etf['tradeDate']) etf = etf.set_index('tradeDate') etf.tail(3) ``` | | closePrice | | --- | --- | | tradeDate | | | 2015-09-22 | 2.237 | | 2015-09-23 | 2.180 | | 2015-09-24 | 2.187 | ## 1. EWMA模型计算历史波动率 EWMA(Exponentially Weighted Moving Average)指数加权移动平均计算历史波动率: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdba3d1f7.jpg) 其中 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdba51703.jpg) 上式中的 `Si` 为 `i` 天的收盘价,`λ` 为介于0和1之间的常数。也就是说,在第 `n−1` 天估算的第 `n` 天的波动率估计值 `σn` 由第 `n−1` 天的波动率估计值 `σn−1` 和收盘价在最近一天的变化百分比 `un−1` 决定。 计算周期为 `N` 天的波动率时, `λ` 可以取为: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdba648b1.jpg) ```py def getHistVolatilityEWMA(secID, beginDate, endDate): cal = Calendar('China.SSE') spotBeginDate = cal.advanceDate(beginDate,'-520B',BizDayConvention.Preceding) spotBeginDate = Date(2006, 1, 1) begin = spotBeginDate.toISO().replace('-', '') end = endDate.toISO().replace('-', '') fields = ['tradeDate', 'preClosePrice', 'closePrice', 'settlePrice', 'preSettlePrice'] security = DataAPI.MktFunddGet(secID, beginDate=begin, endDate=end, field=fields) security['dailyReturn'] = security['closePrice']/security['preClosePrice'] # 日回报率 security['u2'] = (np.log(security['dailyReturn']))**2 # u2为复利形式的日回报率平方 # security['u2'] = (security['dailyReturn'] - 1.0)**2 # u2为日价格变化百分比的平方 security['tradeDate'] = pd.to_datetime(security['tradeDate']) periods = {'hv1W': 5, 'hv2W': 10, 'hv1M': 21, 'hv2M': 41, 'hv3M': 62, 'hv4M': 83, 'hv5M': 104, 'hv6M': 124, 'hv9M': 186, 'hv1Y': 249, 'hv2Y': 497} # 利用pandas中的ewma模型计算波动率 for prd in periods.keys(): # 此处的span实际上就是上面计算波动率公式中lambda表达式中的N security[prd] = np.round(np.sqrt(pd.ewma(security['u2'], span=periods[prd], adjust=False)), 5)*math.sqrt(252.0) security = security[security.tradeDate >= beginDate.toISO()] security = security.set_index('tradeDate') return security ``` ```py secID = '510050.XSHG' start = Date(2015, 2, 9) end = Date.todaysDate() hist_HV = getHistVolatilityEWMA(secID, start, end) hist_HV.tail(2) ``` | | preClosePrice | closePrice | dailyReturn | u2 | hv2M | hv1W | hv1Y | hv3M | hv4M | hv5M | hv2Y | hv1M | hv2W | hv6M | hv9M | | --- | --- | | tradeDate | | | | | | | | | | | | | | | | | 2015-09-23 | 2.237 | 2.180 | 0.974519 | 0.000666 | 0.511318 | 0.304791 | 0.446550 | 0.523224 | 0.519890 | 0.511635 | 0.379718 | 0.449090 | 0.344477 | 0.502269 | 0.472743 | | 2015-09-24 | 2.180 | 2.187 | 1.003211 | 0.000010 | 0.499095 | 0.250658 | 0.444804 | 0.514969 | 0.513699 | 0.506714 | 0.378925 | 0.428453 | 0.312410 | 0.498142 | 0.470203 | ```py secID = '510050.XSHG' start = Date(2007, 1, 1) end = Date.todaysDate() hist_HV = getHistVolatilityEWMA(secID, start, end) ## ----- 50ETF历史波动率 ----- fig = plt.figure(figsize=(10,12)) ax = fig.add_subplot(211) font.set_size(16) hist_plot = hist_HV[hist_HV.index >= Date(2015,2,9).toISO()] etf_plot = etf[etf.index >= Date(2015,2,9).toISO()] lns1 = ax.plot(hist_plot.index, hist_plot.hv1M, '-', label = u'HV(1M)') lns2 = ax.plot(hist_plot.index, hist_plot.hv2M, '-', label = u'HV(2M)') ax2 = ax.twinx() lns3 = ax2.plot(etf_plot.index, etf_plot.closePrice, '-r', label = '50ETF closePrice') lns = lns1+lns2+lns3 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=2) ax.grid() ax.set_xlabel(u"tradeDate") ax.set_ylabel(r"Historical Volatility") ax2.set_ylabel(r"closePrice") ax.set_ylim(0, 0.9) ax2.set_ylim(1.5, 4) plt.title('50ETF Historical EWMA Volatility') ## ----------------------------------- ## ----- 50ETF历史波动率统计数据 ----- # 注意: 该统计数据基于07年以来将近九年的历史波动率得出 ax3 = fig.add_subplot(212) font.set_size(16) hist_plot = hist_HV[[u'hv2W', u'hv1M', u'hv2M', u'hv3M', u'hv4M', u'hv5M', u'hv6M', u'hv9M', u'hv1Y']] # Calculate the quantiles column wise quantiles = mstats.mquantiles(hist_plot, prob=[0.0, 0.25, 0.5, 0.75, 1.0], axis=0) labels = ['Minimum', '1st quartile', 'Median', '3rd quartile', 'Maximum'] for i, q in enumerate(quantiles): ax3.plot(q, label=labels[i]) # 在统计图中标出某一天的波动率 date = Date(2015,8,27) last_day_HV = hist_plot.ix[date.toDateTime()].T ax3.plot(last_day_HV.values, 'dr', label=date.toISO()) # 在统计图中标出最近一天的波动率 last_day_HV = hist_plot.tail(1).T ax3.plot(last_day_HV.values, 'sb', label=Date.fromDateTime(last_day_HV.columns[0]).toISO()) ax3.set_ylabel(r"Volatility") plt.xticks((0,1,2,3,4,5,6,7,8),(0.5,1,2,3,4,5,6,9,12)) plt.xlabel('Periods(Months)') plt.legend() plt.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdba79424.png) 波动率图中,上图表示50ETF收盘价格和历史波动率的走势关系: + 显然,短周期波动率对于近期的波动更敏感 + 收盘价的下跌往往伴随着波动率的上升,两者的负相关性质明显 波动率图中,下图表示50ETF历史波动率的统计数据,图中给出了四分位波动率锥: + 8月底时,各个周期历史波动率均处于历史高位 + 目前,短周期波动率已经有所回落 ## 2. Close to Close 模型计算历史波动率 m 天周期的Close to Close波动率: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbaa113a.jpg) 其中 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbab4554.jpg) 也就是说,在第 `n−1` 天估算的第 `n` 天的波动率估计值 `σn` 由前面 `m `天的每日收盘价变化百分比 `ui` 的标准差决定。 ```py ## 计算一段时间标的的历史波动率,返回值包括以下不同周期的波动率: # 一周,半月,一个月,两个月,三个月,四个月,五个月,半年,九个月,一年,两年 def getHistVolatilityC2C(secID, beginDate, endDate): cal = Calendar('China.SSE') spotBeginDate = cal.advanceDate(beginDate,'-520B',BizDayConvention.Preceding) spotBeginDate = Date(2006, 1, 1) begin = spotBeginDate.toISO().replace('-', '') end = endDate.toISO().replace('-', '') fields = ['tradeDate', 'preClosePrice', 'closePrice', 'settlePrice', 'preSettlePrice'] security = DataAPI.MktFunddGet(secID, beginDate=begin, endDate=end, field=fields) security['dailyReturn'] = security['closePrice']/security['preClosePrice'] # 日回报率 security['u'] = np.log(security['dailyReturn']) # u2为复利形式的日回报率 security['tradeDate'] = pd.to_datetime(security['tradeDate']) periods = {'hv1W': 5, 'hv2W': 10, 'hv1M': 21, 'hv2M': 41, 'hv3M': 62, 'hv4M': 83, 'hv5M': 104, 'hv6M': 124, 'hv9M': 186, 'hv1Y': 249, 'hv2Y': 497} # 利用方差模型计算波动率 for prd in periods.keys(): security[prd] = np.round(pd.rolling_std(security['u'], window=periods[prd]), 5)*math.sqrt(252.0) security = security[security.tradeDate >= beginDate.toISO()] security = security.set_index('tradeDate') return security ``` ```py secID = '510050.XSHG' start = Date(2007, 1, 1) end = Date.todaysDate() hist_HV = getHistVolatilityC2C(secID, start, end) ## ----- 50ETF历史波动率 ----- fig = plt.figure(figsize=(10,12)) ax = fig.add_subplot(211) font.set_size(16) hist_plot = hist_HV[hist_HV.index >= Date(2015,2,9).toISO()] etf_plot = etf[etf.index >= Date(2015,2,9).toISO()] lns1 = ax.plot(hist_plot.index, hist_plot.hv1M, '-', label = u'HV(1M)') lns2 = ax.plot(hist_plot.index, hist_plot.hv2M, '-', label = u'HV(2M)') ax2 = ax.twinx() lns3 = ax2.plot(etf_plot.index, etf_plot.closePrice, '-r', label = '50ETF closePrice') lns = lns1+lns2+lns3 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=2) ax.grid() ax.set_xlabel(u"tradeDate") ax.set_ylabel(r"Historical Volatility") ax2.set_ylabel(r"closePrice") ax.set_ylim(0, 0.9) ax2.set_ylim(1.5, 4) plt.title('50ETF Historical Close-to-Close Volatility') ## ----------------------------------- ## ----- 50ETF历史波动率统计数据 ----- # 注意: 该统计数据基于07年以来将近九年的历史波动率得出 ax3 = fig.add_subplot(212) font.set_size(16) hist_plot = hist_HV[[u'hv2W', u'hv1M', u'hv2M', u'hv3M', u'hv4M', u'hv5M', u'hv6M', u'hv9M', u'hv1Y']] # Calculate the quantiles column wise quantiles = mstats.mquantiles(hist_plot, prob=[0.0, 0.25, 0.5, 0.75, 1.0], axis=0) labels = ['Minimum', '1st quartile', 'Median', '3rd quartile', 'Maximum'] for i, q in enumerate(quantiles): ax3.plot(q, label=labels[i]) # 在统计图中标出某一天的波动率 date = Date(2015,8,27) last_day_HV = hist_plot.ix[date.toDateTime()].T ax3.plot(last_day_HV.values, 'dr', label=date.toISO()) # 在统计图中标出最近一天的波动率 last_day_HV = hist_plot.tail(1).T ax3.plot(last_day_HV.values, 'sb', label=Date.fromDateTime(last_day_HV.columns[0]).toISO()) ax3.set_ylabel(r"Volatility") plt.xticks((0,1,2,3,4,5,6,7,8),(0.5,1,2,3,4,5,6,9,12)) plt.xlabel('Periods(Months)') plt.legend() plt.grid() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdbac620e.png) 波动率图中,上图表示50ETF收盘价格和历史波动率的走势关系: + 显然,短周期波动率对于近期的波动更敏感 + 收盘价的下跌往往伴随着波动率的上升,两者的负相关性质明显 波动率图中,下图表示50ETF历史波动率的统计数据,图中给出了四分位波动率锥: + 8月底时,各个周期历史波动率均处于历史高位 + 目前,短周期波动率已经有所回落 明显地,相对于EWMA计算的历史波动率,Close to Close波动率对于最近价格波动反应比较迟钝
';

[ 50ETF 期权] 1. 历史成交持仓和 PCR 数据

最后更新于:2022-04-01 21:58:45

# [ 50ETF 期权] 1. 历史成交持仓和 PCR 数据 > 来源:https://uqer.io/community/share/5604937ff9f06c597665ef34 在本文中,我们将通过量化实验室提供的数据,计算上证50ETF期权的历史成交持仓和PCR数据,并在最后利用PCR建立一个简单的择时策略 ```py from CAL.PyCAL import * import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib import rc rc('mathtext', default='regular') import seaborn as sns sns.set_style('white') from matplotlib import dates ``` ## 1. 期权数据接口 有关上证50ETF期权数据,量化实验室有三个接口,分别对应于不同的功能 + `DataAPI.OptGet`: 可以获取已退市和上市的所有期权的基本信息 + `DataAPI.MktOptdGet`: 拿到历史上某一天或某段时间的期权成交行情信息 + `DataAPI.MktTickRTSnapshotGet`: 此为高频数据,获取期权最新市场信息快照 在接下来对于期权的数据分析中,我们将使用这三个API提供的数据,以下为API使用示例,具体API的详情可以查看帮助文档 ```py # 使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息 opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE", u"L"], field='', pandas="1") opt_info.head(3) ``` | | secID | optID | secShortName | tickerSymbol | exchangeCD | currencyCD | varSecID | varShortName | varTicker | varExchangeCD | ... | contMultNum | contractStatus | listDate | expYear | expMonth | expDate | lastTradeDate | exerDate | deliDate | delistDate | | --- | --- | | 0 | 510050C1503M02200.XSHG | 10000001 | 50ETF购3月2200 | 510050C1503M02200 | XSHG | CNY | 510050.XSHG | 华夏上证50ETF | 510050 | XSHG | ... | 10000 | DE | 2015-02-09 | 2015 | 3 | 2015-03-25 | 2015-03-25 | 2015-03-25 | 2015-03-26 | 2015-03-25 | | 1 | 510050C1503M02250.XSHG | 10000002 | 50ETF购3月2250 | 510050C1503M02250 | XSHG | CNY | 510050.XSHG | 华夏上证50ETF | 510050 | XSHG | ... | 10000 | DE | 2015-02-09 | 2015 | 3 | 2015-03-25 | 2015-03-25 | 2015-03-25 | 2015-03-26 | 2015-03-25 | | 2 | 510050C1503M02300.XSHG | 10000003 | 50ETF购3月2300 | 510050C1503M02300 | XSHG | CNY | 510050.XSHG | 华夏上证50ETF | 510050 | XSHG | ... | 10000 | DE | 2015-02-09 | 2015 | 3 | 2015-03-25 | 2015-03-25 | 2015-03-25 | 2015-03-26 | 2015-03-25 | ``` 3 rows × 23 columns ``` ```py #使用DataAPI.MktOptdGet,拿到历史上某一天的期权成交信息 opt_mkt = DataAPI.MktOptdGet(tradeDate='20150921', field='', pandas="1") opt_mkt.head(2) ``` | | secID | optID | ticker | secShortName | exchangeCD | tradeDate | preSettlePrice | preClosePrice | openPrice | highestPrice | lowestPrice | closePrice | settlPrice | turnoverVol | turnoverValue | openInt | | --- | --- | | 0 | 510050C1512M02100.XSHG | 10000368 | 510050C1512M02100 | 50ETF购12月2100 | XSHG | 2015-09-21 | 0.2069 | 0.1994 | 0.1955 | 0.2087 | 0.1955 | 0.2062 | 0.2062 | 21 | 43115 | 457 | | 1 | 510050P1512M01950.XSHG | 10000369 | 510050P1512M01950 | 50ETF沽12月1950 | XSHG | 2015-09-21 | 0.1037 | 0.0999 | 0.1000 | 0.1073 | 0.0905 | 0.0905 | 0.0927 | 272 | 261112 | 868 | ```py # 获取期权最新市场信息快照 opt_mkt_snapshot = DataAPI.MktOptionTickRTSnapshotGet(optionId=u"",field=u"",pandas="1") opt_mkt_snapshot[opt_mkt_snapshot.dataDate=='2015-09-22'].head(2) ``` | | optionId | timestamp | auctionPrice | auctionQty | dataDate | dataTime | highPrice | instrumentID | lastPrice | lowPrice | ... | askBook_price1 | askBook_volume1 | askBook_price2 | askBook_volume2 | askBook_price3 | askBook_volume3 | askBook_price4 | askBook_volume4 | askBook_price5 | askBook_volume5 | | --- | --- | ``` 0 rows × 37 columns ``` ## 2. 期权历史成交持仓数据图 ```py # 华夏上证50ETF收盘价数据 secID = '510050.XSHG' begin = Date(2015, 2, 9) end = Date.todaysDate() fields = ['tradeDate', 'closePrice'] etf = DataAPI.MktFunddGet(secID, beginDate=begin.toISO().replace('-', ''), endDate=end.toISO().replace('-', ''), field=fields) etf['tradeDate'] = pd.to_datetime(etf['tradeDate']) etf = etf.set_index('tradeDate') etf.tail(2) ``` | | closePrice | | --- | --- | | tradeDate | | | 2015-09-23 | 2.180 | | 2015-09-24 | 2.187 | 统计50ETF期权历史成交量和持仓量信息 ```py # 计算历史一段时间内的50ETF期权持仓量交易量数据 def getOptHistVol(beginDate, endDate): optionVarSecID = u"510050.XSHG" cal = Calendar('China.SSE') cal.addHoliday(Date(2015,9,3)) cal.addHoliday(Date(2015,9,4)) dates = cal.bizDatesList(beginDate, endDate) dates = map(Date.toDateTime, dates) columns = ['callVol', 'putVol', 'callValue', 'putValue', 'callOpenInt', 'putOpenInt', 'nearCallVol', 'nearPutVol', 'nearCallValue', 'nearPutValue', 'nearCallOpenInt', 'nearPutOpenInt', 'netVol', 'netValue', 'netOpenInt', 'volPCR', 'valuePCR', 'openIntPCR', 'nearVolPCR', 'nearValuePCR', 'nearOpenIntPCR'] hist_opt = pd.DataFrame(0.0, index=dates, columns=columns) hist_opt.index.name = 'date' # 每一个交易日数据单独计算 for date in hist_opt.index: date_str = Date.fromDateTime(date).toISO().replace('-', '') try: opt_data = DataAPI.MktOptdGet(secID=u"", tradeDate=date_str, field=u"", pandas="1") except: hist_opt = hist_opt.drop(date) continue opt_type = [] exp_date = [] for ticker in opt_data.secID.values: opt_type.append(ticker[6]) exp_date.append(ticker[7:11]) opt_data['optType'] = opt_type opt_data['expDate'] = exp_date near_exp = np.sort(opt_data.expDate.unique())[0] data = opt_data.groupby('optType') # 计算所有上市期权:看涨看跌交易量、看涨看跌交易额、看涨看跌持仓量 hist_opt['callVol'][date] = data.turnoverVol.sum()['C'] hist_opt['putVol'][date] = data.turnoverVol.sum()['P'] hist_opt['callValue'][date] = data.turnoverValue.sum()['C'] hist_opt['putValue'][date] = data.turnoverValue.sum()['P'] hist_opt['callOpenInt'][date] = data.openInt.sum()['C'] hist_opt['putOpenInt'][date] = data.openInt.sum()['P'] near_data = opt_data[opt_data.expDate == near_exp] near_data = near_data.groupby('optType') # 计算近月期权(主力合约): 看涨看跌交易量、看涨看跌交易额、看涨看跌持仓量 hist_opt['nearCallVol'][date] = near_data.turnoverVol.sum()['C'] hist_opt['nearPutVol'][date] = near_data.turnoverVol.sum()['P'] hist_opt['nearCallValue'][date] = near_data.turnoverValue.sum()['C'] hist_opt['nearPutValue'][date] = near_data.turnoverValue.sum()['P'] hist_opt['nearCallOpenInt'][date] = near_data.openInt.sum()['C'] hist_opt['nearPutOpenInt'][date] = near_data.openInt.sum()['P'] # 计算所有上市期权: 总交易量、总交易额、总持仓量 hist_opt['netVol'][date] = hist_opt['callVol'][date] + hist_opt['putVol'][date] hist_opt['netValue'][date] = hist_opt['callValue'][date] + hist_opt['putValue'][date] hist_opt['netOpenInt'][date] = hist_opt['callOpenInt'][date] + hist_opt['putOpenInt'][date] # 计算期权看跌看涨期权交易量(持仓量)的比率: # 交易量看跌看涨比率,交易额看跌看涨比率, 持仓量看跌看涨比率 # 近月期权交易量看跌看涨比率,近月期权交易额看跌看涨比率, 近月期权持仓量看跌看涨比率 # PCR = Put Call Ratio hist_opt['volPCR'][date] = round(hist_opt['putVol'][date]*1.0/hist_opt['callVol'][date], 4) hist_opt['valuePCR'][date] = round(hist_opt['putValue'][date]*1.0/hist_opt['callValue'][date], 4) hist_opt['openIntPCR'][date] = round(hist_opt['putOpenInt'][date]*1.0/hist_opt['callOpenInt'][date], 4) hist_opt['nearVolPCR'][date] = round(hist_opt['nearPutVol'][date]*1.0/hist_opt['nearCallVol'][date], 4) hist_opt['nearValuePCR'][date] = round(hist_opt['nearPutValue'][date]*1.0/hist_opt['nearCallValue'][date], 4) hist_opt['nearOpenIntPCR'][date] = round(hist_opt['nearPutOpenInt'][date]*1.0/hist_opt['nearCallOpenInt'][date], 4) return hist_opt ``` ```py begin = Date(2015, 2, 9) end = Date.todaysDate() opt_hist = getOptHistVol(begin, end) opt_hist.tail(2) ``` | | callVol | putVol | callValue | putValue | callOpenInt | putOpenInt | nearCallVol | nearPutVol | nearCallValue | nearPutValue | ... | nearPutOpenInt | netVol | netValue | netOpenInt | volPCR | valuePCR | openIntPCR | nearVolPCR | nearValuePCR | nearOpenIntPCR | | --- | --- | | date | | | | | | | | | | | | | | | | | | | | | | | 2015-09-23 | 50093 | 42910 | 37809117 | 41517121 | 269395 | 144256 | 16603 | 11494 | 6217923 | 10409963 | ... | 50576 | 93003 | 79326238 | 413651 | 0.8566 | 1.0981 | 0.5355 | 0.6923 | 1.6742 | 0.3738 | | 2015-09-24 | 29352 | 23474 | 21696859 | 22161955 | 146224 | 98350 | 19785 | 19339 | 15693989 | 14549046 | ... | 55217 | 52826 | 43858814 | 244574 | 0.7997 | 1.0214 | 0.6726 | 0.9775 | 0.9270 | 0.8012 | ``` 2 rows × 21 columns ``` ```py ## ----- 50ETF期权成交持仓数据图 ----- fig = plt.figure(figsize=(10,5)) fig.set_tight_layout(True) ax = fig.add_subplot(111) font.set_size(16) lns1 = ax.plot(opt_hist.index, opt_hist.netOpenInt, 'grey', label = u'OpenInt') lns2 = ax.plot(opt_hist.index, opt_hist.netVol, '-r', label = 'TurnoverVolume') ax2 = ax.twinx() lns3 = ax2.plot(etf.index, etf.closePrice, '-', label = 'ETF closePrice') lns = lns1+lns2+lns3 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=2) ax.grid() ax.set_xlabel(u"tradeDate") ax.set_ylabel(r"TurnoverVolume / OpenInt") ax2.set_ylabel(r"ETF closePrice") plt.title('50ETF Option TurnoverVolume / OpenInt') plt.show() ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb9814d7.png) 从上图可以看出: + 期权的交易量基本上是50ETF的反向指标 + 五月之前的疯牛中,期权日交易量处于低位 + 六月中下旬之后的暴跌时间段,期权日交易量高位运行,是不是创个新高 + 8月17日开始的这一周中,大盘风雨飘摇,50ETF探底时,期权交易量创了新高 + 目前来看,期权交易仍然活跃,但是交易量较之前数据有所回落,应该是大盘企稳的节奏 ## 3. 期权的PCR比例 期权分看跌和看涨两种,买入两种不同的期权,代表着对于后市的不同看法,因此可以引进一个量化指标,来表示对后市看衰与看涨的力量的强弱: + PCR = Put Call Ratio + PCR可以是关于成交量的PCR,可以是持仓量的PCR,也可以是成交额的PCR ```py begin = Date(2015, 2, 9) end = Date.todaysDate() opt_hist = getOptHistVol(begin, end) opt_hist.tail(2) ``` | | callVol | putVol | callValue | putValue | callOpenInt | putOpenInt | nearCallVol | nearPutVol | nearCallValue | nearPutValue | ... | nearPutOpenInt | netVol | netValue | netOpenInt | volPCR | valuePCR | openIntPCR | nearVolPCR | nearValuePCR | nearOpenIntPCR | | --- | --- | | date | | | | | | | | | | | | | | | | | | | | | | | 2015-09-23 | 50093 | 42910 | 37809117 | 41517121 | 269395 | 144256 | 16603 | 11494 | 6217923 | 10409963 | ... | 50576 | 93003 | 79326238 | 413651 | 0.8566 | 1.0981 | 0.5355 | 0.6923 | 1.6742 | 0.3738 | | 2015-09-24 | 29352 | 23474 | 21696859 | 22161955 | 146224 | 98350 | 19785 | 19339 | 15693989 | 14549046 | ... | 55217 | 52826 | 43858814 | 244574 | 0.7997 | 1.0214 | 0.6726 | 0.9775 | 0.9270 | 0.8012 | ``` 2 rows × 21 columns ``` 首先,我们来看看成交量PCR和ETF价格走势的关系 ```py ## ---------------------------------------------- ## 50ETF期权PC比例数据图 fig = plt.figure(figsize=(10,8)) fig.set_tight_layout(True) # ------ 成交量PC比例 ------ ax = fig.add_subplot(211) lns1 = ax.plot(opt_hist.index, opt_hist.volPCR, color='r', label = u'volPCR') ax2 = ax.twinx() lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice') lns = lns1+lns2 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=3) ax.set_ylim(0, 2) hfmt = dates.DateFormatter('%m') ax.xaxis.set_major_formatter(hfmt) ax.grid() ax.set_xlabel(u"tradeDate(Month)") ax.set_ylabel(r"PCR") ax2.set_ylabel(r"ETF ClosePrice") plt.title('Volume PCR') # ------ 近月主力期权成交量PC比例 ------ ax = fig.add_subplot(212) lns1 = ax.plot(opt_hist.index, opt_hist.nearVolPCR, color='r', label = u'nearVolPCR') ax2 = ax.twinx() lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice') lns = lns1+lns2 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=3) ax.set_ylim(0, 2) hfmt = dates.DateFormatter('%m') ax.xaxis.set_major_formatter(hfmt) ax.grid() ax.set_xlabel(u"tradeDate(Month)") ax.set_ylabel(r"PCR") ax2.set_ylabel(r"ETF ClosePrice") plt.title('Dominant Contract Volume PCR') ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb99fb51.png) 成交量数据图中,上图为全体期权的成交量PCR,下图为近月期权的成交量PCR: + 上下两图中,PCR的曲线走势基本相似,因为期权交易中,近月期权最为活跃 + ETF价格走势,和PCR走势有比较明显的负相关性 其次,我们来看看持仓量PCR和ETF价格走势的关系 ```py ## ---------------------------------------------- ## 50ETF期权PC比例数据图 fig = plt.figure(figsize=(10,8)) fig.set_tight_layout(True) # ------ 持仓量PC比例 ------ ax = fig.add_subplot(211) lns1 = ax.plot(opt_hist.index, opt_hist.openIntPCR, color='r', label = u'volPCR') ax2 = ax.twinx() lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice') lns = lns1+lns2 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=3) ax.set_ylim(0, 2) hfmt = dates.DateFormatter('%m') ax.xaxis.set_major_formatter(hfmt) ax.grid() ax.set_xlabel(u"tradeDate(Month)") ax.set_ylabel(r"PCR") ax2.set_ylabel(r"ETF ClosePrice") plt.title('OpenInt PCR') # ------ 近月主力期权持仓量PC比例 ------ ax = fig.add_subplot(212) lns1 = ax.plot(opt_hist.index, opt_hist.nearOpenIntPCR, color='r', label = u'nearVolPCR') ax2 = ax.twinx() lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice') lns = lns1+lns2 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=3) ax.set_ylim(0, 2) hfmt = dates.DateFormatter('%m') ax.xaxis.set_major_formatter(hfmt) ax.grid() ax.set_xlabel(u"tradeDate(Month)") ax.set_ylabel(r"PCR") ax2.set_ylabel(r"ETF ClosePrice") plt.title('Dominant Contract OpenInt PCR') ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb9c9bcb.png) 持仓量数据图中,上图为全体期权的持仓量PCR,下图为近月期权的持仓量PCR: + 上下两图中,PCR的曲线走势基本相似,因为期权交易中,近月期权最为活跃 + 实际上,近月期权十分活跃,使得近月期权的PCR系数变动往往比整体期权PCR变化更剧烈 + ETF价格走势,和PCR走势并无明显的负相关性 + 相反,ETF价格的低点,往往PCR也处于低点,这其实说明:股价大跌之后大家会选择平仓看跌期权 最后,我们来看看成交额PCR和ETF价格走势的关系 ```py ## ---------------------------------------------- ## 50ETF期权PC比例数据图 fig = plt.figure(figsize=(10,8)) fig.set_tight_layout(True) # ------ 成交额PC比例 ------ ax = fig.add_subplot(211) lns1 = ax.plot(opt_hist.index, opt_hist.valuePCR, color='r', label = u'turnoverValuePCR') ax2 = ax.twinx() lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice') lns = lns1+lns2 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=3) #ax.set_ylim(0, 2) ax.set_yscale('log') hfmt = dates.DateFormatter('%m') ax.xaxis.set_major_formatter(hfmt) ax.grid() ax.set_xlabel(u"tradeDate(Month)") ax.set_ylabel(r"PCR") ax2.set_ylabel(r"ETF ClosePrice") plt.title('Turnover Value PCR') # ------ 近月主力期权成交额PC比例 ------ ax = fig.add_subplot(212) lns1 = ax.plot(opt_hist.index, opt_hist.nearValuePCR, color='r', label = u'turnoverValuePCR') ax2 = ax.twinx() lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice') lns = lns1+lns2 labs = [l.get_label() for l in lns] ax.legend(lns, labs, loc=3) #ax.set_ylim(0, 2) ax.set_yscale('log') hfmt = dates.DateFormatter('%m') ax.xaxis.set_major_formatter(hfmt) ax.grid() ax.set_xlabel(u"tradeDate(Month)") ax.set_ylabel(r"PCR") ax2.set_ylabel(r"ETF ClosePrice") plt.title('Dominant Contract Turnover Value PCR') ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb9ed549.png) 成交额数据图中,上图为全体期权的成交额PCR,下图为近月期权的成交额PCR: + 上下两图中,PCR的曲线走势基本相似,因为期权交易中,近月期权最为活跃 + 实际上,近月期权PCR指数十分活跃,使得近月期权的PCR系数变动往往比整体期权PCR变化更剧烈 + 相对于成交量和持仓量PCR指标,此处的成交额PCR指标峰值往往很高,上图中近月期权的成交额PCR最大值甚至接近30,这是由于市场恐慌时候,看跌期权成交量本身就大,而交易量大往往将看跌期权的价格大幅抬高 + ETF价格走势,和PCR走势具有明显的负相关性 4. 基于期权成交额PCR的择时策略 根据成交额PCR和ETF价格走势明显的负相关性,我们建立一个非常简单的择时策略: + PCR下降时,市场情绪趋稳定,全仓买入50ETF + PCR上升时,恐慌情绪蔓延,清仓观望 ```py start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 9, 21) # 回测结束时间 hist_pcr = getOptHistVol(start, end) start = datetime(2015, 2, 9) # 回测起始时间 end = datetime(2015, 9, 21) # 回测结束时间 benchmark = '510050.XSHG' # 策略参考标准 universe = ['510050.XSHG'] # 股票池 capital_base = 100000 # 起始资金 commission = Commission(0.0,0.0) refresh_rate = 1 def initialize(account): # 初始化虚拟账户状态 account.fund = universe[0] def handle_data(account): # 每个交易日的买入卖出指令 fund = account.fund # 获取回测当日的前一天日期 dt = Date.fromDateTime(account.current_date) cal = Calendar('China.IB') cal.addHoliday(Date(2015,9,3)) cal.addHoliday(Date(2015,9,4)) last_day = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日 last_last_day = cal.advanceDate(last_day,'-1B',BizDayConvention.Preceding) #计算出倒数第二个交易日 last_day_str = last_day.strftime("%Y-%m-%d") last_last_day_str = last_last_day.strftime("%Y-%m-%d") # 计算买入卖出信号 try: # 拿取PCR数据 pcr_last = hist_pcr['valuePCR'].loc[last_day_str] pcr_last_last = hist_pcr['valuePCR'].loc[last_last_day_str] long_flag = True if (pcr_last - pcr_last_last) < 0 else False except: long_flag = True if long_flag: approximationAmount = int(account.cash / account.referencePrice[fund] / 100.0) * 100 order(fund, approximationAmount) else: # 卖出时,全仓清空 order_to(fund, 0) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdba1ff1e.jpg) 回测结果如上,需要注意的是: + 期权挂牌时间较短,回测时间短,加上期权市场参与人数少,故而回测结果可能然并卵 + 但是严格根据PCR走势买卖50ETF,还是可以比较好的避开市场大跌的风险 + 不管怎样,PCR可以作为一个择时指标来讨论 + 除了成交额PCR,还可以通过成交量、持仓量、近月成交额等等PCR建立择时策略
';

二 期权系列

最后更新于:2022-04-01 21:58:43

# 二 期权系列
';

期权高频数据准备

最后更新于:2022-04-01 21:58:40

# 期权高频数据准备 > 来源:https://uqer.io/community/share/55027e68f9f06c7a9ae9a53b 本notebook根据指定的时间区间整理并保存`option_data.csv` 文件,请与 期权市场一周纵览 notebook配合使用。 ```py import pandas as pd import numpy as np pd.options.display.float_format = '{:,>.4f}'.format ``` ```py calendar = Calendar('China.SSE') class _format_checker: def __init__(self, calendar): self.calendar = calendar def _format_check(self, instrumentID): contractType = instrumentID[6] + 'O' contractYear = int(instrumentID[7:9]) + 2000 contractMonth = int(instrumentID[9:11]) contractExp = Date.NthWeekDay(4, Wednesday, contractMonth, contractYear) contractExp = self.calendar.adjustDate(contractExp, BizDayConvention.Following) contractStrike = float(instrumentID[-4:]) / 1000.0 return contractType, contractExp, contractStrike checker = _format_checker(calendar) ``` ```py tradingDays = calendar.bizDatesList(Date(2015,3,5), Date(2015,3,12)) names, instrumentIDs = (OptionsDataSnapShot().optionId.unique(), OptionsDataSnapShot().instrumentID.unique()) data = pd.DataFrame(names, columns = ['optionId']) instrumentIDs = pd.Series(instrumentIDs) data = data.join(pd.DataFrame(list(instrumentIDs.apply(checker._format_check)), columns= ['contractType', 'expDate', 'strikePrice'])) data[:5] ``` | | optionId | contractType | expDate | strikePrice | | --- | --- | | 0 | 10000001 | CO | March 25th, 2015 | 2.2000 | | 1 | 10000002 | CO | March 25th, 2015 | 2.2500 | | 2 | 10000003 | CO | March 25th, 2015 | 2.3000 | | 3 | 10000004 | CO | March 25th, 2015 | 2.3500 | | 4 | 10000005 | CO | March 25th, 2015 | 2.4000 | ```py tradingDaysStr = [''.join(date.toISO().split('-')) for date in tradingDays] tradingDaysStr ['20150305', '20150306', '20150309', '20150310', '20150311'] ``` ```py res = pd.DataFrame() spotData = [] for day in tradingDaysStr: tmp = spotData try: spotData = DataAPI.MktTicksHistOneDayGet('510050.XSHG', date = day, field = ['dataDate', 'datasTime', 'secOffset', 'lastPrice']) spotData = spotData.drop(0) except Exception, e: print e spotData = tmp for opt in names: try: sample = DataAPI.MktOptionTicksHistOneDayGet(optionId = opt,date = day)#field = ['optionId', 'dataDate', 'dataTime' 'secOffset', 'lastPrice']) sample = sample.drop_duplicates(['secOffset']) spotPrice = np.zeros((len(sample),)) j = 0 index = spotData.index for i, secOffset in enumerate(sample.secOffset): currentSpotSecOffset = spotData.loc[index[j], 'secOffset']*1000 while currentSpotSecOffset < secOffset and j < len(index)-1: j = j + 1 currentSpotSecOffset = spotData.loc[index[j], 'secOffset']*1000 if j>=1: spotPrice[i] = spotData.loc[index[j-1], 'lastPrice'] else: spotPrice[i] = spotData.loc[index[j], 'lastPrice'] sample['spotPrice'] = spotPrice res = res.append(sample) except Exception, e: print e print day + ' finished!' 20150305 finished! -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000030&date=20150306&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000032&date=20150306&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000033&date=20150306&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000035&date=20150306&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000054&date=20150306&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000056&date=20150306&startSecOffset=&endSecOffset= 20150306 finished! 20150309 finished! -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000039&date=20150310&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000056&date=20150310&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000064&date=20150310&startSecOffset=&endSecOffset= 20150310 finished! -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000039&date=20150311&startSecOffset=&endSecOffset= -1:No Data Returned for request: /market/getOptionTicksHistOneDay.csv?field=&optionId=10000064&date=20150311&startSecOffset=&endSecOffset= 20150311 finished! ``` ```py res.optionId = res.optionId.astype('str') res = res.merge(data, how = 'left', on = 'optionId') dateData, idData, volumeData = res.dataDate, res.optionId, res['volume'] previous = [dateData[0], idData[0], 0] newVolume = np.zeros((len(dateData),)) count = 0 for date, ids, volume in zip(dateData, idData, volumeData ): if date == previous[0] and ids == previous[1]: newVolume[count] = volume - previous[2] else: newVolume[count] = volume previous[0] = date previous[1] = ids previous[2] = volume count = count + 1 res.volume = newVolume res['pdDateTime'] = res.expDate.apply(lambda x: x.toDateTime()) optData = pd.DataFrame() optData['contractType'] = res['contractType'] optData['valuationDate'] = res['dataDate'] optData['expDate'] = res['expDate'] optData['strikePrice'] = res['strikePrice'] optData['lastPrice'] = res['lastPrice'] optData['optionId'] = res['optionId'].astype('str') optData['Type'] = Option.Call optData['spotPrice'] = res.spotPrice optData.loc[optData['contractType'] == 'PO','Type'] = Option.Put optData['valuationDate'] = [Date(int(date.split('-')[0]),int(date.split('-')[1]),int(date.split('-')[2])) for date in optData['valuationDate']] dc = DayCounter('Actual/365 (Fixed)') optData['ttm'] = [dc.yearFraction(date1, date2) for date1, date2 in zip(optData['valuationDate'], optData['expDate'])] optData['lastPrice(vol)'] = BSMImpliedVolatity(optData['Type'], optData['strikePrice'], optData['spotPrice'], 0.0, 0.0, optData['ttm'], optData['lastPrice']) optData['bid1(vol)'] = BSMImpliedVolatity(optData['Type'], optData['strikePrice'], optData['spotPrice'], 0.0, 0.0, optData['ttm'], res.bidPrice1) optData['ask1(vol)'] = BSMImpliedVolatity(optData['Type'], optData['strikePrice'], optData['spotPrice'], 0.0, 0.0, optData['ttm'], res.askPrice1) res1 = res.merge(optData[[u'spotPrice', u'ttm', u'lastPrice(vol)', u'bid1(vol)', u'ask1(vol)']], left_index=True, right_index=True) res1 = res1.dropna(how = 'any') res1['bidAskSpread(bps)'] = (res1.askPrice1 - res1.bidPrice1) * 10000 res1['bidAskSpread(vol bps)'] = (res1['ask1(vol)'] - res1['bid1(vol)']) * 10000 res1.to_csv('option_data.csv') ```
';

如何获取期权市场数据快照

最后更新于:2022-04-01 21:58:38

# 如何获取期权市场数据快照 > 来源:https://uqer.io/community/share/550274e4f9f06c7a9ae9a535 在本文中,我们将通过实际的市场的例子,展示如何在量化实验室中计算和展示期权的隐含波动率微笑。 ```py import pandas as pd from matplotlib import pylab pd.options.display.float_format = '{:,>.4f}'.format ``` ## 1. 获取市场数据 在本节中,我们使用数据API获取数据,并进行一些必要的数据转换。这里我们获取的是实时报价,是本 notebook 运行时的市场快照。 + `dataDate` 交易日 + `dataTime` 快照时间戳 + `optionId` 期权代码 + `instrumentID` 期权交易代码 + `contractType` 期权类型,CO为看着,PO为看跌 + `strikePrice` 行权价 + `expDate` 到期日 + `lastPrice` 最新价 ```py optionSnapShot = OptionsDataSnapShot() optionSnapShot[optionSnapShot.expDate == Date(2015,9,23)] ``` | | dataDate | dataTime | optionId | instrumentID | contractType | strikePrice | expDate | lastPrice | | --- | --- | | 30 | 2015-03-13 | 13:24:12 | 10000031 | 510050C1509M02200 | CO | 2.2000 | September 23rd, 2015 | 0.3388 | | 31 | 2015-03-13 | 13:24:17 | 10000032 | 510050C1509M02250 | CO | 2.2500 | September 23rd, 2015 | 0.3019 | | 32 | 2015-03-13 | 13:24:22 | 10000033 | 510050C1509M02300 | CO | 2.3000 | September 23rd, 2015 | 0.2816 | | 33 | 2015-03-13 | 13:24:27 | 10000034 | 510050C1509M02350 | CO | 2.3500 | September 23rd, 2015 | 0.2484 | | 34 | 2015-03-13 | 13:24:32 | 10000035 | 510050C1509M02400 | CO | 2.4000 | September 23rd, 2015 | 0.2070 | | 35 | 2015-03-13 | 13:24:36 | 10000036 | 510050P1509M02200 | PO | 2.2000 | September 23rd, 2015 | 0.0690 | | 36 | 2015-03-13 | 13:24:41 | 10000037 | 510050P1509M02250 | PO | 2.2500 | September 23rd, 2015 | 0.0804 | | 37 | 2015-03-13 | 13:24:47 | 10000038 | 510050P1509M02300 | PO | 2.3000 | September 23rd, 2015 | 0.0955 | | 38 | 2015-03-13 | 13:24:52 | 10000039 | 510050P1509M02350 | PO | 2.3500 | September 23rd, 2015 | 0.1194 | | 39 | 2015-03-13 | 13:24:58 | 10000040 | 510050P1509M02400 | PO | 2.4000 | September 23rd, 2015 | 0.1322 | | 46 | 2015-03-13 | 13:24:52 | 10000047 | 510050C1509M02450 | CO | 2.4500 | September 23rd, 2015 | 0.1889 | | 47 | 2015-03-13 | 13:24:58 | 10000048 | 510050P1509M02450 | PO | 2.4500 | September 23rd, 2015 | 0.1555 | | 54 | 2015-03-13 | 13:24:32 | 10000055 | 510050C1509M02500 | CO | 2.5000 | September 23rd, 2015 | 0.1629 | | 55 | 2015-03-13 | 13:24:36 | 10000056 | 510050P1509M02500 | PO | 2.5000 | September 23rd, 2015 | 0.1900 | | 62 | 2015-03-13 | 13:24:32 | 10000063 | 510050C1509M02550 | CO | 2.5500 | September 23rd, 2015 | 0.1443 | | 63 | 2015-03-13 | 13:24:36 | 10000064 | 510050P1509M02550 | PO | 2.5500 | September 23rd, 2015 | 0.2169 | ## 2. 计算隐含波动率以及相关Greeks 接着我们可以方便的使用内置函数 BSMImpliedVolatity 计算期权的隐含波动率。 + `price` 市场报价或者模型价格 + `delta` 期权价格关于标的价格的一阶导数 + `gamma` 期权价格关于标的价格的二阶导数 + `rho` 期权价格关于无风险利率的一阶导数 + `theta` 期权价格关于到期时间的一阶导数(每日) + `vega` 期权价格关于波动率的一阶导数 ```py analyticResult = OptionsAnalyticResult() analyticResult.loc[:10, ['optionId', 'contractType', 'strikePrice', 'expDate', 'lastPrice', 'vol', 'delta', 'gamma', 'rho', 'theta', 'vega']] ``` | | optionId | contractType | strikePrice | expDate | lastPrice | vol | delta | gamma | rho | theta | vega | | --- | --- | | 1 | 10000002 | CO | 2.2500 | March 25th, 2015 | 0.2184 | 0.2259 | 0.9886 | 0.2947 | 0.0730 | -0.0458 | 0.0133 | | 2 | 10000003 | CO | 2.3000 | March 25th, 2015 | 0.1730 | 0.2867 | 0.9165 | 1.1965 | 0.0687 | -0.2996 | 0.0687 | | 3 | 10000004 | CO | 2.3500 | March 25th, 2015 | 0.1229 | 0.2177 | 0.8963 | 1.8495 | 0.0687 | -0.2670 | 0.0806 | | 4 | 10000005 | CO | 2.4000 | March 25th, 2015 | 0.0814 | 0.2166 | 0.7676 | 3.1504 | 0.0596 | -0.4503 | 0.1367 | | 8 | 10000009 | PO | 2.3500 | March 25th, 2015 | 0.0076 | 0.2482 | -0.1332 | 1.9373 | -0.0111 | -0.3633 | 0.0963 | | 9 | 10000010 | PO | 2.4000 | March 25th, 2015 | 0.0159 | 0.2346 | -0.2488 | 3.0197 | -0.0207 | -0.5061 | 0.1419 | | 10 | 10000011 | CO | 2.2000 | April 22nd, 2015 | 0.2778 | 0.2703 | 0.9081 | 0.7466 | 0.2152 | -0.1661 | 0.1347 | ## 3. 构造波动率曲面 但是对于市场参与者而言,像刚才这样仅仅观察的线的结构不够。他们需要看到整个市场以到期时间,行权价为轴的波动率曲面(Volatility Surface)。除此之外,他们更想知道,波动率曲面上,那些并不是市场报价点的值,至少是个估计。这样的波动率曲面构造,往往需要依赖某种模型,或者某种插值方法。在这一节中,我们将介绍使用 CAL 中的波动率曲面构造函数。 以下的例子基于 CAL 函数: `VolatilitySurfaceSnapShot` ### 3.1 基于SABR模型的波动率曲面 ```py volInterpolatorSABR = VolatilitySurfaceSnapShot(optionType = 'CALL', interpType = 'SABR') volInterpolatorSABR.plotSurface(startStrike = 2.2,endStrike = 2.6) volInterpolatorSABR.volalitltyProfileFromPeriods([2.2, 2.3, 2.4, 2.5, 2.6], ['1M', '2M', '3M', '6M', '9M']) ``` | | 1M | 2M | 3M | 6M | 9M | | --- | --- | | 2.2000 | 0.2720 | 0.2406 | 0.2327 | 0.2531 | 0.2545 | | 2.3000 | 0.2048 | 0.2207 | 0.2345 | 0.2546 | 0.2557 | | 2.4000 | 0.2245 | 0.2341 | 0.2389 | 0.2525 | 0.2533 | | 2.5000 | 0.2241 | 0.2328 | 0.2381 | 0.2479 | 0.2484 | | 2.6000 | 0.2311 | 0.2356 | 0.2362 | 0.2425 | 0.2429 | ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb8b73a6.png) ### 3.2 基于SVI模型的波动率曲面 ```py volInterpolatorSVI = VolatilitySurfaceSnapShot(optionType = 'CALL', interpType = 'SVI') volInterpolatorSVI.plotSurface(startStrike = 2.2,endStrike = 2.6) volInterpolatorSVI.volalitltyProfileFromPeriods([2.2, 2.3, 2.4, 2.5, 2.6], ['1M', '2M', '3M', '6M', '9M']) ``` | | 1M | 2M | 3M | 6M | 9M | | --- | --- | | 2.2000 | 0.2769 | 0.2476 | 0.2369 | 0.2566 | 0.2580 | | 2.3000 | 0.2121 | 0.2223 | 0.2340 | 0.2535 | 0.2545 | | 2.4000 | 0.2170 | 0.2292 | 0.2365 | 0.2504 | 0.2512 | | 2.5000 | 0.2290 | 0.2357 | 0.2389 | 0.2474 | 0.2479 | | 2.6000 | 0.2401 | 0.2417 | 0.2413 | 0.2508 | 0.2514 | ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb8eda62.png) ### 3.3 基于Balck波动率插值的波动率曲面 ```py volInterpolatorVariance = VolatilitySurfaceSnapShot(optionType = 'CALL', interpType = 'BlackVariance') volInterpolatorVariance.plotSurface(startStrike = 2.2,endStrike = 2.6) volInterpolatorVariance.volalitltyProfileFromPeriods([2.2, 2.3, 2.4, 2.5, 2.6], ['1M', '2M', '3M', '6M', '9M']) ``` | | 1M | 2M | 3M | 6M | 9M | | --- | --- | | 2.2000 | 0.2676 | 0.2380 | 0.2202 | 0.2516 | 0.2537 | | 2.3000 | 0.2082 | 0.2270 | 0.2441 | 0.2660 | 0.2672 | | 2.4000 | 0.2277 | 0.2325 | 0.2341 | 0.2404 | 0.2408 | | 2.5000 | 0.2278 | 0.2363 | 0.2408 | 0.2463 | 0.2466 | | 2.6000 | 0.2252 | 0.2324 | 0.2365 | 0.2517 | 0.2526 | ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb925a60.png) ## 4. 组合计算 在本节中,我们假设客户已经拥有了自己的期权头寸,希望利用量化实验室的功能进行风险监控。我们假设有以下的期权头寸: | 期权代码 | 数量 | 行权价(¥) | 到期时间 | | --- | --- | | 10000004 | -7000 | 2.35 | 2015-03-25 | | 10000011 | 2000 | 2.20 | 2015-04-22 | | 10000027 | 5000 | 2.25 | 2015-06-24 | | 10000047 | 3000 | 2.45 | 2015-09-23 | 然后我们构造 `OptionBook`: ```py optionIDs = ['10000011', '10000027', '10000004', '10000047'] amounts = [2000, 5000, -7000, 3000] optBook = OptionBook(optionIDs, amounts) print u'期权头寸:' optBook.description() 期权头寸: ``` | | dataDate | dataTime | optionId | instrumentID | contractType | strikePrice | expDate | lastPrice | amount | | --- | --- | | 0 | 2015-03-13 | 13:24:58 | 10000004 | 510050C1503M02350 | CO | 2.3500 | March 25th, 2015 | 0.1229 | -7000 | | 1 | 2015-03-13 | 13:24:32 | 10000011 | 510050C1504M02200 | CO | 2.2000 | April 22nd, 2015 | 0.2778 | 2000 | | 2 | 2015-03-13 | 13:24:52 | 10000027 | 510050P1506M02250 | PO | 2.2500 | June 24th, 2015 | 0.0450 | 5000 | | 3 | 2015-03-13 | 13:24:52 | 10000047 | 510050C1509M02450 | CO | 2.4500 | September 23rd, 2015 | 0.1889 | 3000 | ### 4.1 使用Black插值模型计算组合风险 ```py optBook.riskReport(volInterpolatorVariance) ``` | | optionId | vol | price | delta | gamma | rho | theta | vega | | --- | --- | | 0 | 10000004 | 0.2060 | -860.3000 | -6370.8417 | -12316.8687 | -488.8540 | 1592.3418 | -508.3851 | | 1 | 10000011 | 0.2634 | 555.6000 | 1828.2807 | 1456.7054 | 433.8000 | -307.9681 | 256.2961 | | 2 | 10000027 | 0.2484 | 220.5000 | -1103.5286 | 4552.0335 | -831.0864 | -856.2742 | 1945.3136 | | 3 | 10000047 | 0.2509 | 566.7000 | 1659.5347 | 2626.2983 | 1876.5865 | -503.9843 | 2135.1356 | | portfolio | NaN | nan | 482.5000 | -3986.5549 | -3681.8315 | 990.4461 | -75.8848 | 3828.3602 | ### 4.2 使用SABR模型组合风险 ```py optBook.riskReport(volInterpolatorSABR) ``` | | optionId | vol | price | delta | gamma | rho | theta | vega | | --- | --- | | 0 | 10000004 | 0.2157 | -865.4365 | -6301.5462 | -12703.7735 | -483.0602 | 1800.8937 | -549.0791 | | 1 | 10000011 | 0.2523 | 552.8686 | 1845.2432 | 1405.9255 | 438.6890 | -272.7679 | 236.9632 | | 2 | 10000027 | 0.2347 | 194.1368 | -1048.6009 | 4677.9921 | -785.3771 | -785.2668 | 1888.5079 | | 3 | 10000047 | 0.2511 | 566.9933 | 1659.5667 | 2624.8517 | 1876.4726 | -504.2584 | 2135.1279 | | portfolio | NaN | nan | 448.5622 | -3845.3372 | -3995.0043 | 1046.7243 | 238.6007 | 3711.5199 | ### 4.3 使用SVI模型组合风险 ```py optBook.riskReport(volInterpolatorSVI) ``` | | optionId | vol | price | delta | gamma | rho | theta | vega | | --- | --- | | 0 | 10000004 | 0.2126 | -863.7639 | -6323.4081 | -12591.1718 | -484.8898 | 1734.2876 | -536.4362 | | 1 | 10000011 | 0.2634 | 555.6000 | 1828.2807 | 1456.7054 | 433.8000 | -307.9681 | 256.2961 | | 2 | 10000027 | 0.2355 | 195.6318 | -1051.9049 | 4670.9045 | -788.1010 | -789.3710 | 1892.0017 | | 3 | 10000047 | 0.2495 | 563.6855 | 1659.2077 | 2641.2567 | 1877.7596 | -501.1669 | 2135.2142 | | portfolio | NaN | nan | 451.1534 | -3887.8246 | -3822.3052 | 1038.5689 | 135.7815 | 3747.0758 | ## 5 比较不同模型的拟合市场数据的能力 这里我们比较不同的模型,对于市场数据的拟合能力。这里我们可以观察到单论你和能力 `BlackVarianceSurface > SviCalibratedVolSruface > SABRCalibratedVolSruface `。这里我们并不想下这样的结论:这些模型的优劣也有相同的排序。 另一个我们可以观察到的现象,对于近月合约(流动性最好),波动率微笑是最规则的。在这个期限上,三种模型的拟合都很到位。随着期限的上升,流动性的下降,买卖价差也随之扩大。这时候波动率微笑变得愈发不规则,这个时候一个完美拟合至市场的模型是否必要,是一个很大的问题:如果市场报价并不理性,一个优秀的模型应该可以指出这种不合理点,而不是简单的接受市场的非理性。 ```py from matplotlib import pylab strikes = sorted(analyticResult['strikePrice'].unique()) expiries = [Date(2015,3,25),Date(2015,4,25),Date(2015,6,25),Date(2015,9,25)] maturity = [(date - EvaluationDate())/ 365.0 for date in expiries] volSurfaces = [volInterpolatorSABR, volInterpolatorSVI] def plotModelFitting(index, volSurfaces, legends = ['Market Quote', 'SABR', 'SVI']): # Using Black variance surface to extrace the rar wolatility data = volInterpolatorVariance.volatility(strikes, maturity[index], True) pylab.plot(strikes, data, 'r+-.', markersize = 8) for s in volSurfaces: data = s.volatility(strikes, maturity[index], True) pylab.plot(strikes, data) pylab.xlabel('Strike') pylab.ylabel('Volatility') pylab.legend(legends, loc = 'best', fontsize = 12) pylab.title(u'行权结算日: ' + str(expiries[index]), fontproperties = font, fontsize = 20) pylab.grid(True) pylab.subplots(2,2, figsize = (16,14)) for i in range(1,5): pylab.subplot('22' + str(i)) plotModelFitting(i-1, volSurfaces) ``` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-30_579cbdb959f20.png)
';