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

来源:https://uqer.io/community/share/5604937ff9f06c597665ef34

在本文中,我们将通过量化实验室提供的数据,计算上证50ETF期权的历史成交持仓和PCR数据,并在最后利用PCR建立一个简单的择时策略

  1. from CAL.PyCAL import *
  2. import pandas as pd
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. from matplotlib import rc
  6. rc('mathtext', default='regular')
  7. import seaborn as sns
  8. sns.set_style('white')
  9. from matplotlib import dates

1. 期权数据接口

有关上证50ETF期权数据,量化实验室有三个接口,分别对应于不同的功能

  • DataAPI.OptGet: 可以获取已退市和上市的所有期权的基本信息
  • DataAPI.MktOptdGet: 拿到历史上某一天或某段时间的期权成交行情信息
  • DataAPI.MktTickRTSnapshotGet: 此为高频数据,获取期权最新市场信息快照 在接下来对于期权的数据分析中,我们将使用这三个API提供的数据,以下为API使用示例,具体API的详情可以查看帮助文档
  1. # 使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息
  2. opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE", u"L"], field='', pandas="1")
  3. opt_info.head(3)
secIDoptIDsecShortNametickerSymbolexchangeCDcurrencyCDvarSecIDvarShortNamevarTickervarExchangeCDcontMultNumcontractStatuslistDateexpYearexpMonthexpDatelastTradeDateexerDatedeliDatedelistDate
0510050C1503M02200.XSHG1000000150ETF购3月2200510050C1503M02200XSHGCNY510050.XSHG华夏上证50ETF510050XSHG10000DE2015-02-09201532015-03-252015-03-252015-03-252015-03-262015-03-25
1510050C1503M02250.XSHG1000000250ETF购3月2250510050C1503M02250XSHGCNY510050.XSHG华夏上证50ETF510050XSHG10000DE2015-02-09201532015-03-252015-03-252015-03-252015-03-262015-03-25
2510050C1503M02300.XSHG1000000350ETF购3月2300510050C1503M02300XSHGCNY510050.XSHG华夏上证50ETF510050XSHG10000DE2015-02-09201532015-03-252015-03-252015-03-252015-03-262015-03-25
  1. 3 rows × 23 columns
  1. #使用DataAPI.MktOptdGet,拿到历史上某一天的期权成交信息
  2. opt_mkt = DataAPI.MktOptdGet(tradeDate='20150921', field='', pandas="1")
  3. opt_mkt.head(2)
secIDoptIDtickersecShortNameexchangeCDtradeDatepreSettlePricepreClosePriceopenPricehighestPricelowestPriceclosePricesettlPriceturnoverVolturnoverValueopenInt
0510050C1512M02100.XSHG10000368510050C1512M0210050ETF购12月2100XSHG2015-09-210.20690.19940.19550.20870.19550.20620.20622143115457
1510050P1512M01950.XSHG10000369510050P1512M0195050ETF沽12月1950XSHG2015-09-210.10370.09990.10000.10730.09050.09050.0927272261112868
  1. # 获取期权最新市场信息快照
  2. opt_mkt_snapshot = DataAPI.MktOptionTickRTSnapshotGet(optionId=u"",field=u"",pandas="1")
  3. opt_mkt_snapshot[opt_mkt_snapshot.dataDate=='2015-09-22'].head(2)
optionIdtimestampauctionPriceauctionQtydataDatedataTimehighPriceinstrumentIDlastPricelowPriceaskBook_price1askBook_volume1askBook_price2askBook_volume2askBook_price3askBook_volume3askBook_price4askBook_volume4askBook_price5askBook_volume5
  1. 0 rows × 37 columns

2. 期权历史成交持仓数据图

  1. # 华夏上证50ETF收盘价数据
  2. secID = '510050.XSHG'
  3. begin = Date(2015, 2, 9)
  4. end = Date.todaysDate()
  5. fields = ['tradeDate', 'closePrice']
  6. etf = DataAPI.MktFunddGet(secID, beginDate=begin.toISO().replace('-', ''), endDate=end.toISO().replace('-', ''), field=fields)
  7. etf['tradeDate'] = pd.to_datetime(etf['tradeDate'])
  8. etf = etf.set_index('tradeDate')
  9. etf.tail(2)
closePrice
tradeDate
2015-09-232.180
2015-09-242.187

统计50ETF期权历史成交量和持仓量信息

  1. # 计算历史一段时间内的50ETF期权持仓量交易量数据
  2. def getOptHistVol(beginDate, endDate):
  3. optionVarSecID = u"510050.XSHG"
  4. cal = Calendar('China.SSE')
  5. cal.addHoliday(Date(2015,9,3))
  6. cal.addHoliday(Date(2015,9,4))
  7. dates = cal.bizDatesList(beginDate, endDate)
  8. dates = map(Date.toDateTime, dates)
  9. columns = ['callVol', 'putVol', 'callValue',
  10. 'putValue', 'callOpenInt', 'putOpenInt',
  11. 'nearCallVol', 'nearPutVol', 'nearCallValue',
  12. 'nearPutValue', 'nearCallOpenInt', 'nearPutOpenInt',
  13. 'netVol', 'netValue', 'netOpenInt',
  14. 'volPCR', 'valuePCR', 'openIntPCR',
  15. 'nearVolPCR', 'nearValuePCR', 'nearOpenIntPCR']
  16. hist_opt = pd.DataFrame(0.0, index=dates, columns=columns)
  17. hist_opt.index.name = 'date'
  18. # 每一个交易日数据单独计算
  19. for date in hist_opt.index:
  20. date_str = Date.fromDateTime(date).toISO().replace('-', '')
  21. try:
  22. opt_data = DataAPI.MktOptdGet(secID=u"", tradeDate=date_str, field=u"", pandas="1")
  23. except:
  24. hist_opt = hist_opt.drop(date)
  25. continue
  26. opt_type = []
  27. exp_date = []
  28. for ticker in opt_data.secID.values:
  29. opt_type.append(ticker[6])
  30. exp_date.append(ticker[7:11])
  31. opt_data['optType'] = opt_type
  32. opt_data['expDate'] = exp_date
  33. near_exp = np.sort(opt_data.expDate.unique())[0]
  34. data = opt_data.groupby('optType')
  35. # 计算所有上市期权:看涨看跌交易量、看涨看跌交易额、看涨看跌持仓量
  36. hist_opt['callVol'][date] = data.turnoverVol.sum()['C']
  37. hist_opt['putVol'][date] = data.turnoverVol.sum()['P']
  38. hist_opt['callValue'][date] = data.turnoverValue.sum()['C']
  39. hist_opt['putValue'][date] = data.turnoverValue.sum()['P']
  40. hist_opt['callOpenInt'][date] = data.openInt.sum()['C']
  41. hist_opt['putOpenInt'][date] = data.openInt.sum()['P']
  42. near_data = opt_data[opt_data.expDate == near_exp]
  43. near_data = near_data.groupby('optType')
  44. # 计算近月期权(主力合约): 看涨看跌交易量、看涨看跌交易额、看涨看跌持仓量
  45. hist_opt['nearCallVol'][date] = near_data.turnoverVol.sum()['C']
  46. hist_opt['nearPutVol'][date] = near_data.turnoverVol.sum()['P']
  47. hist_opt['nearCallValue'][date] = near_data.turnoverValue.sum()['C']
  48. hist_opt['nearPutValue'][date] = near_data.turnoverValue.sum()['P']
  49. hist_opt['nearCallOpenInt'][date] = near_data.openInt.sum()['C']
  50. hist_opt['nearPutOpenInt'][date] = near_data.openInt.sum()['P']
  51. # 计算所有上市期权: 总交易量、总交易额、总持仓量
  52. hist_opt['netVol'][date] = hist_opt['callVol'][date] + hist_opt['putVol'][date]
  53. hist_opt['netValue'][date] = hist_opt['callValue'][date] + hist_opt['putValue'][date]
  54. hist_opt['netOpenInt'][date] = hist_opt['callOpenInt'][date] + hist_opt['putOpenInt'][date]
  55. # 计算期权看跌看涨期权交易量(持仓量)的比率:
  56. # 交易量看跌看涨比率,交易额看跌看涨比率, 持仓量看跌看涨比率
  57. # 近月期权交易量看跌看涨比率,近月期权交易额看跌看涨比率, 近月期权持仓量看跌看涨比率
  58. # PCR = Put Call Ratio
  59. hist_opt['volPCR'][date] = round(hist_opt['putVol'][date]*1.0/hist_opt['callVol'][date], 4)
  60. hist_opt['valuePCR'][date] = round(hist_opt['putValue'][date]*1.0/hist_opt['callValue'][date], 4)
  61. hist_opt['openIntPCR'][date] = round(hist_opt['putOpenInt'][date]*1.0/hist_opt['callOpenInt'][date], 4)
  62. hist_opt['nearVolPCR'][date] = round(hist_opt['nearPutVol'][date]*1.0/hist_opt['nearCallVol'][date], 4)
  63. hist_opt['nearValuePCR'][date] = round(hist_opt['nearPutValue'][date]*1.0/hist_opt['nearCallValue'][date], 4)
  64. hist_opt['nearOpenIntPCR'][date] = round(hist_opt['nearPutOpenInt'][date]*1.0/hist_opt['nearCallOpenInt'][date], 4)
  65. return hist_opt
  1. begin = Date(2015, 2, 9)
  2. end = Date.todaysDate()
  3. opt_hist = getOptHistVol(begin, end)
  4. opt_hist.tail(2)
callVolputVolcallValueputValuecallOpenIntputOpenIntnearCallVolnearPutVolnearCallValuenearPutValuenearPutOpenIntnetVolnetValuenetOpenIntvolPCRvaluePCRopenIntPCRnearVolPCRnearValuePCRnearOpenIntPCR
date
2015-09-235009342910378091174151712126939514425616603114946217923104099635057693003793262384136510.85661.09810.53550.69231.67420.3738
2015-09-242935223474216968592216195514622498350197851933915693989145490465521752826438588142445740.79971.02140.67260.97750.92700.8012
  1. 2 rows × 21 columns
  1. ## ----- 50ETF期权成交持仓数据图 -----
  2. fig = plt.figure(figsize=(10,5))
  3. fig.set_tight_layout(True)
  4. ax = fig.add_subplot(111)
  5. font.set_size(16)
  6. lns1 = ax.plot(opt_hist.index, opt_hist.netOpenInt, 'grey', label = u'OpenInt')
  7. lns2 = ax.plot(opt_hist.index, opt_hist.netVol, '-r', label = 'TurnoverVolume')
  8. ax2 = ax.twinx()
  9. lns3 = ax2.plot(etf.index, etf.closePrice, '-', label = 'ETF closePrice')
  10. lns = lns1+lns2+lns3
  11. labs = [l.get_label() for l in lns]
  12. ax.legend(lns, labs, loc=2)
  13. ax.grid()
  14. ax.set_xlabel(u"tradeDate")
  15. ax.set_ylabel(r"TurnoverVolume / OpenInt")
  16. ax2.set_ylabel(r"ETF closePrice")
  17. plt.title('50ETF Option TurnoverVolume / OpenInt')
  18. plt.show()

【50ETF 期权】1. 历史成交持仓和 PCR 数据 - 图1

从上图可以看出:

  • 期权的交易量基本上是50ETF的反向指标
  • 五月之前的疯牛中,期权日交易量处于低位
  • 六月中下旬之后的暴跌时间段,期权日交易量高位运行,是不是创个新高
  • 8月17日开始的这一周中,大盘风雨飘摇,50ETF探底时,期权交易量创了新高
  • 目前来看,期权交易仍然活跃,但是交易量较之前数据有所回落,应该是大盘企稳的节奏

3. 期权的PCR比例

期权分看跌和看涨两种,买入两种不同的期权,代表着对于后市的不同看法,因此可以引进一个量化指标,来表示对后市看衰与看涨的力量的强弱:

  • PCR = Put Call Ratio
  • PCR可以是关于成交量的PCR,可以是持仓量的PCR,也可以是成交额的PCR
  1. begin = Date(2015, 2, 9)
  2. end = Date.todaysDate()
  3. opt_hist = getOptHistVol(begin, end)
  4. opt_hist.tail(2)
callVolputVolcallValueputValuecallOpenIntputOpenIntnearCallVolnearPutVolnearCallValuenearPutValuenearPutOpenIntnetVolnetValuenetOpenIntvolPCRvaluePCRopenIntPCRnearVolPCRnearValuePCRnearOpenIntPCR
date
2015-09-235009342910378091174151712126939514425616603114946217923104099635057693003793262384136510.85661.09810.53550.69231.67420.3738
2015-09-242935223474216968592216195514622498350197851933915693989145490465521752826438588142445740.79971.02140.67260.97750.92700.8012
  1. 2 rows × 21 columns

首先,我们来看看成交量PCR和ETF价格走势的关系

  1. ## ----------------------------------------------
  2. ## 50ETF期权PC比例数据图
  3. fig = plt.figure(figsize=(10,8))
  4. fig.set_tight_layout(True)
  5. # ------ 成交量PC比例 ------
  6. ax = fig.add_subplot(211)
  7. lns1 = ax.plot(opt_hist.index, opt_hist.volPCR, color='r', label = u'volPCR')
  8. ax2 = ax.twinx()
  9. lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice')
  10. lns = lns1+lns2
  11. labs = [l.get_label() for l in lns]
  12. ax.legend(lns, labs, loc=3)
  13. ax.set_ylim(0, 2)
  14. hfmt = dates.DateFormatter('%m')
  15. ax.xaxis.set_major_formatter(hfmt)
  16. ax.grid()
  17. ax.set_xlabel(u"tradeDate(Month)")
  18. ax.set_ylabel(r"PCR")
  19. ax2.set_ylabel(r"ETF ClosePrice")
  20. plt.title('Volume PCR')
  21. # ------ 近月主力期权成交量PC比例 ------
  22. ax = fig.add_subplot(212)
  23. lns1 = ax.plot(opt_hist.index, opt_hist.nearVolPCR, color='r', label = u'nearVolPCR')
  24. ax2 = ax.twinx()
  25. lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice')
  26. lns = lns1+lns2
  27. labs = [l.get_label() for l in lns]
  28. ax.legend(lns, labs, loc=3)
  29. ax.set_ylim(0, 2)
  30. hfmt = dates.DateFormatter('%m')
  31. ax.xaxis.set_major_formatter(hfmt)
  32. ax.grid()
  33. ax.set_xlabel(u"tradeDate(Month)")
  34. ax.set_ylabel(r"PCR")
  35. ax2.set_ylabel(r"ETF ClosePrice")
  36. plt.title('Dominant Contract Volume PCR')
  37. <matplotlib.text.Text at 0x6470990>

【50ETF 期权】1. 历史成交持仓和 PCR 数据 - 图2

成交量数据图中,上图为全体期权的成交量PCR,下图为近月期权的成交量PCR:

  • 上下两图中,PCR的曲线走势基本相似,因为期权交易中,近月期权最为活跃
  • ETF价格走势,和PCR走势有比较明显的负相关性 其次,我们来看看持仓量PCR和ETF价格走势的关系
  1. ## ----------------------------------------------
  2. ## 50ETF期权PC比例数据图
  3. fig = plt.figure(figsize=(10,8))
  4. fig.set_tight_layout(True)
  5. # ------ 持仓量PC比例 ------
  6. ax = fig.add_subplot(211)
  7. lns1 = ax.plot(opt_hist.index, opt_hist.openIntPCR, color='r', label = u'volPCR')
  8. ax2 = ax.twinx()
  9. lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice')
  10. lns = lns1+lns2
  11. labs = [l.get_label() for l in lns]
  12. ax.legend(lns, labs, loc=3)
  13. ax.set_ylim(0, 2)
  14. hfmt = dates.DateFormatter('%m')
  15. ax.xaxis.set_major_formatter(hfmt)
  16. ax.grid()
  17. ax.set_xlabel(u"tradeDate(Month)")
  18. ax.set_ylabel(r"PCR")
  19. ax2.set_ylabel(r"ETF ClosePrice")
  20. plt.title('OpenInt PCR')
  21. # ------ 近月主力期权持仓量PC比例 ------
  22. ax = fig.add_subplot(212)
  23. lns1 = ax.plot(opt_hist.index, opt_hist.nearOpenIntPCR, color='r', label = u'nearVolPCR')
  24. ax2 = ax.twinx()
  25. lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice')
  26. lns = lns1+lns2
  27. labs = [l.get_label() for l in lns]
  28. ax.legend(lns, labs, loc=3)
  29. ax.set_ylim(0, 2)
  30. hfmt = dates.DateFormatter('%m')
  31. ax.xaxis.set_major_formatter(hfmt)
  32. ax.grid()
  33. ax.set_xlabel(u"tradeDate(Month)")
  34. ax.set_ylabel(r"PCR")
  35. ax2.set_ylabel(r"ETF ClosePrice")
  36. plt.title('Dominant Contract OpenInt PCR')
  37. <matplotlib.text.Text at 0x69e5990>

【50ETF 期权】1. 历史成交持仓和 PCR 数据 - 图3

持仓量数据图中,上图为全体期权的持仓量PCR,下图为近月期权的持仓量PCR:

  • 上下两图中,PCR的曲线走势基本相似,因为期权交易中,近月期权最为活跃
  • 实际上,近月期权十分活跃,使得近月期权的PCR系数变动往往比整体期权PCR变化更剧烈
  • ETF价格走势,和PCR走势并无明显的负相关性
  • 相反,ETF价格的低点,往往PCR也处于低点,这其实说明:股价大跌之后大家会选择平仓看跌期权 最后,我们来看看成交额PCR和ETF价格走势的关系
  1. ## ----------------------------------------------
  2. ## 50ETF期权PC比例数据图
  3. fig = plt.figure(figsize=(10,8))
  4. fig.set_tight_layout(True)
  5. # ------ 成交额PC比例 ------
  6. ax = fig.add_subplot(211)
  7. lns1 = ax.plot(opt_hist.index, opt_hist.valuePCR, color='r', label = u'turnoverValuePCR')
  8. ax2 = ax.twinx()
  9. lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice')
  10. lns = lns1+lns2
  11. labs = [l.get_label() for l in lns]
  12. ax.legend(lns, labs, loc=3)
  13. #ax.set_ylim(0, 2)
  14. ax.set_yscale('log')
  15. hfmt = dates.DateFormatter('%m')
  16. ax.xaxis.set_major_formatter(hfmt)
  17. ax.grid()
  18. ax.set_xlabel(u"tradeDate(Month)")
  19. ax.set_ylabel(r"PCR")
  20. ax2.set_ylabel(r"ETF ClosePrice")
  21. plt.title('Turnover Value PCR')
  22. # ------ 近月主力期权成交额PC比例 ------
  23. ax = fig.add_subplot(212)
  24. lns1 = ax.plot(opt_hist.index, opt_hist.nearValuePCR, color='r', label = u'turnoverValuePCR')
  25. ax2 = ax.twinx()
  26. lns2 = ax2.plot(etf.index, etf.closePrice, '-', label = 'closePrice')
  27. lns = lns1+lns2
  28. labs = [l.get_label() for l in lns]
  29. ax.legend(lns, labs, loc=3)
  30. #ax.set_ylim(0, 2)
  31. ax.set_yscale('log')
  32. hfmt = dates.DateFormatter('%m')
  33. ax.xaxis.set_major_formatter(hfmt)
  34. ax.grid()
  35. ax.set_xlabel(u"tradeDate(Month)")
  36. ax.set_ylabel(r"PCR")
  37. ax2.set_ylabel(r"ETF ClosePrice")
  38. plt.title('Dominant Contract Turnover Value PCR')
  39. <matplotlib.text.Text at 0x70ce890>

【50ETF 期权】1. 历史成交持仓和 PCR 数据 - 图4

成交额数据图中,上图为全体期权的成交额PCR,下图为近月期权的成交额PCR:

  • 上下两图中,PCR的曲线走势基本相似,因为期权交易中,近月期权最为活跃
  • 实际上,近月期权PCR指数十分活跃,使得近月期权的PCR系数变动往往比整体期权PCR变化更剧烈
  • 相对于成交量和持仓量PCR指标,此处的成交额PCR指标峰值往往很高,上图中近月期权的成交额PCR最大值甚至接近30,这是由于市场恐慌时候,看跌期权成交量本身就大,而交易量大往往将看跌期权的价格大幅抬高
  • ETF价格走势,和PCR走势具有明显的负相关性

  • 基于期权成交额PCR的择时策略

根据成交额PCR和ETF价格走势明显的负相关性,我们建立一个非常简单的择时策略:

  • PCR下降时,市场情绪趋稳定,全仓买入50ETF
  • PCR上升时,恐慌情绪蔓延,清仓观望
  1. start = datetime(2015, 2, 9) # 回测起始时间
  2. end = datetime(2015, 9, 21) # 回测结束时间
  3. hist_pcr = getOptHistVol(start, end)
  4. start = datetime(2015, 2, 9) # 回测起始时间
  5. end = datetime(2015, 9, 21) # 回测结束时间
  6. benchmark = '510050.XSHG' # 策略参考标准
  7. universe = ['510050.XSHG'] # 股票池
  8. capital_base = 100000 # 起始资金
  9. commission = Commission(0.0,0.0)
  10. refresh_rate = 1
  11. def initialize(account): # 初始化虚拟账户状态
  12. account.fund = universe[0]
  13. def handle_data(account): # 每个交易日的买入卖出指令
  14. fund = account.fund
  15. # 获取回测当日的前一天日期
  16. dt = Date.fromDateTime(account.current_date)
  17. cal = Calendar('China.IB')
  18. cal.addHoliday(Date(2015,9,3))
  19. cal.addHoliday(Date(2015,9,4))
  20. last_day = cal.advanceDate(dt,'-1B',BizDayConvention.Preceding) #计算出倒数第一个交易日
  21. last_last_day = cal.advanceDate(last_day,'-1B',BizDayConvention.Preceding) #计算出倒数第二个交易日
  22. last_day_str = last_day.strftime("%Y-%m-%d")
  23. last_last_day_str = last_last_day.strftime("%Y-%m-%d")
  24. # 计算买入卖出信号
  25. try:
  26. # 拿取PCR数据
  27. pcr_last = hist_pcr['valuePCR'].loc[last_day_str]
  28. pcr_last_last = hist_pcr['valuePCR'].loc[last_last_day_str]
  29. long_flag = True if (pcr_last - pcr_last_last) < 0 else False
  30. except:
  31. long_flag = True
  32. if long_flag:
  33. approximationAmount = int(account.cash / account.referencePrice[fund] / 100.0) * 100
  34. order(fund, approximationAmount)
  35. else:
  36. # 卖出时,全仓清空
  37. order_to(fund, 0)

【50ETF 期权】1. 历史成交持仓和 PCR 数据 - 图5

回测结果如上,需要注意的是:

  • 期权挂牌时间较短,回测时间短,加上期权市场参与人数少,故而回测结果可能然并卵
  • 但是严格根据PCR走势买卖50ETF,还是可以比较好的避开市场大跌的风险
  • 不管怎样,PCR可以作为一个择时指标来讨论
  • 除了成交额PCR,还可以通过成交量、持仓量、近月成交额等等PCR建立择时策略