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

来源:https://uqer.io/community/share/5615d10ff9f06c4ca72fb5be

和上一篇类似,计算上证50ETF期权的隐含波动率微笑,但这里通过DataAPI提供的高频数据,在交易时间实时监控Greeks 和隐含波动率变化!

  1. from CAL.PyCAL import *
  2. import numpy as np
  3. import pandas as pd
  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. import math
  10. from scipy import interpolate
  11. from scipy.stats import mstats
  12. from pandas import Series, DataFrame, concat
  13. import time
  14. from matplotlib import dates

上海银行间同业拆借利率 SHIBOR,用来作为无风险利率参考

  1. ## 银行间质押式回购利率
  2. def getHistDayInterestRateInterbankRepo(date):
  3. cal = Calendar('China.SSE')
  4. period = Period('-10B')
  5. begin = cal.advanceDate(date, period)
  6. begin_str = begin.toISO().replace('-', '')
  7. date_str = date.toISO().replace('-', '')
  8. # 以下的indicID分别对应的银行间质押式回购利率周期为:
  9. # 1D, 7D, 14D, 21D, 1M, 3M, 4M, 6M, 9M, 1Y
  10. indicID = [u"M120000067", u"M120000068", u"M120000069", u"M120000070", u"M120000071",
  11. u"M120000072", u"M120000073", u"M120000074", u"M120000075", u"M120000076"]
  12. 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
  13. period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period'])
  14. field = u"indicID,indicName,publishTime,periodDate,dataValue,unit"
  15. interbank_repo = DataAPI.ChinaDataInterestRateInterbankRepoGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1")
  16. interbank_repo = interbank_repo.groupby('indicID').first()
  17. interbank_repo = concat([interbank_repo, period_matrix], axis=1, join='inner').sort_index()
  18. return interbank_repo
  19. ## 银行间同业拆借利率
  20. def getHistDaySHIBOR(date):
  21. cal = Calendar('China.SSE')
  22. period = Period('-10B')
  23. begin = cal.advanceDate(date, period)
  24. begin_str = begin.toISO().replace('-', '')
  25. date_str = date.toISO().replace('-', '')
  26. # 以下的indicID分别对应的SHIBOR周期为:
  27. # 1D, 7D, 14D, 1M, 3M, 6M, 9M, 1Y
  28. indicID = [u"M120000057", u"M120000058", u"M120000059", u"M120000060",
  29. u"M120000061", u"M120000062", u"M120000063", u"M120000064"]
  30. period = np.asarray([1.0, 7.0, 14.0, 30.0, 90.0, 180.0, 270.0, 360.0]) / 360.0
  31. period_matrix = pd.DataFrame(index=indicID, data=period, columns=['period'])
  32. field = u"indicID,indicName,publishTime,periodDate,dataValue,unit"
  33. interest_shibor = DataAPI.ChinaDataInterestRateSHIBORGet(indicID=indicID,beginDate=begin_str,endDate=date_str,field=field,pandas="1")
  34. interest_shibor = interest_shibor.groupby('indicID').first()
  35. interest_shibor = concat([interest_shibor, period_matrix], axis=1, join='inner').sort_index()
  36. return interest_shibor
  37. ## 插值得到给定的周期的无风险利率
  38. def periodsSplineRiskFreeInterestRate(date, periods):
  39. # 此处使用SHIBOR来插值
  40. init_shibor = getHistDaySHIBOR(date)
  41. shibor = {}
  42. min_period = min(init_shibor.period.values)
  43. min_period = 10.0/360.0
  44. max_period = max(init_shibor.period.values)
  45. for p in periods.keys():
  46. tmp = periods[p]
  47. if periods[p] > max_period:
  48. tmp = max_period * 0.99999
  49. elif periods[p] < min_period:
  50. tmp = min_period * 1.00001
  51. sh = interpolate.spline(init_shibor.period.values, init_shibor.dataValue.values, [tmp], order=3)
  52. shibor[p] = sh[0]/100.0
  53. return shibor

1. Greeks 和 隐含波动率

本文中计算的Greeks包括:

  • delta 期权价格关于标的价格的一阶导数
  • gamma 期权价格关于标的价格的二阶导数
  • rho 期权价格关于无风险利率的一阶导数
  • theta 期权价格关于到期时间的一阶导数
  • vega 期权价格关于波动率的一阶导数 注意:

  • 计算隐含波动率,我们采用Black-Scholes-Merton模型,此模型在平台Python包CAL中已有实现

  • 无风险利率使用SHIBOR
  • 期权的时间价值为负时(此种情况在50ETF期权里时有发生),没法通过BSM模型计算隐含波动率,故此时将期权隐含波动率设为0.0,实际上,此时的隐含波动率和各风险指标并无实际参考价值
  • 此处计算即时隐含波动率和Greeks时候,我们使用买一和卖一的中间价作为期权价格
  1. ## 使用DataAPI.OptGet, DataAPI.MktOptionTickRTSnapshotGet 拿到计算所需数据
  2. def getOptTickSnapshot(opt_var_sec, date):
  3. date_str = date.toISO().replace('-', '')
  4. #使用DataAPI.OptGet,拿到已退市和上市的所有期权的基本信息
  5. info_fields = [u'optID', u'varSecID', u'varShortName', u'varTicker', u'varExchangeCD', u'varType',
  6. u'contractType', u'strikePrice', u'contMultNum', u'contractStatus', u'listDate',
  7. u'expYear', u'expMonth', u'expDate', u'lastTradeDate', u'exerDate', u'deliDate',
  8. u'delistDate']
  9. opt_info = DataAPI.OptGet(optID='', contractStatus=[u"DE",u"L"], field=info_fields, pandas="1")
  10. #使用DataAPI.MktOptionTickRTSnapshotGet,拿到期权实时成交数据
  11. date_str = date.toISO().replace('-', '')
  12. fields_mkt = ['instrumentID', u'optionId', u'dataDate', u'lastPrice', u'preSettlePrice',
  13. u'bidBook_price1', u'bidBook_volume1', u'askBook_price1', u'askBook_volume1']
  14. opt_mkt = DataAPI.MktOptionTickRTSnapshotGet(optionId=u"", field='', pandas="1")
  15. opt_mkt = opt_mkt[opt_mkt.dataDate == date.toISO()]
  16. opt_mkt['optID'] = map(int, opt_mkt['optionId'])
  17. opt_mkt[u"price"] = (opt_mkt['bidBook_price1'] + opt_mkt['askBook_price1'])/2.0
  18. opt_info = opt_info.set_index(u"optID")
  19. opt_mkt = opt_mkt.set_index(u"optID")
  20. opt = concat([opt_info, opt_mkt], axis=1, join='inner').sort_index()
  21. return opt
  22. ## 分析期权即时交易信息,得到隐含波动率微笑和期权风险指标
  23. def getOptAnalysisSnapshot(opt_var_sec):
  24. date = Date.todaysDate()
  25. opt = getOptTickSnapshot(opt_var_sec, date)
  26. #使用DataAPI.MktTickRTSnapshotGet 拿到期权标的的即时行情
  27. date_str = date.toISO().replace('-', '')
  28. opt_var_mkt = DataAPI.MktTickRTSnapshotGet(securityID=opt_var_sec,field=u"lastPrice",pandas="1")
  29. # 计算shibor
  30. exp_dates_str = opt.expDate.unique()
  31. periods = {}
  32. for date_str in exp_dates_str:
  33. exp_date = Date.parseISO(date_str)
  34. periods[exp_date] = (exp_date - date)/360.0
  35. shibor = periodsSplineRiskFreeInterestRate(date, periods)
  36. settle = opt.price.values # 期权 settle price
  37. close = opt.price.values # 期权 close price
  38. strike = opt.strikePrice.values # 期权 strike price
  39. option_type = opt.contractType.values # 期权类型
  40. exp_date_str = opt.expDate.values # 期权行权日期
  41. eval_date_str = opt.dataDate.values # 期权交易日期
  42. mat_dates = []
  43. eval_dates = []
  44. spot = []
  45. for epd, evd in zip(exp_date_str, eval_date_str):
  46. mat_dates.append(Date.parseISO(epd))
  47. eval_dates.append(Date.parseISO(evd))
  48. spot.append(opt_var_mkt.lastPrice[0])
  49. time_to_maturity = [float(mat - eva + 1.0)/365.0 for (mat, eva) in zip(mat_dates, eval_dates)]
  50. risk_free = [] # 无风险利率
  51. for s, mat, time in zip(spot, mat_dates, time_to_maturity):
  52. #rf = math.log(forward_price[mat] / s) / time
  53. rf = shibor[mat]
  54. risk_free.append(rf)
  55. opt_types = [] # 期权类型
  56. for t in option_type:
  57. if t == 'CO':
  58. opt_types.append(1)
  59. else:
  60. opt_types.append(-1)
  61. # 使用通联CAL包中 BSMImpliedVolatity 计算隐含波动率
  62. calculated_vol = BSMImpliedVolatity(opt_types, strike, spot, risk_free, 0.0, time_to_maturity, settle)
  63. calculated_vol = calculated_vol.fillna(0.0)
  64. # 使用通联CAL包中 BSMPrice 计算期权风险指标
  65. greeks = BSMPrice(opt_types, strike, spot, risk_free, 0.0, calculated_vol.vol.values, time_to_maturity)
  66. # vega、rho、theta 的计量单位参照上交所的数据,以求统一对比
  67. greeks.vega = greeks.vega #/ 100.0
  68. greeks.rho = greeks.rho #/ 100.0
  69. greeks.theta = greeks.theta #* 365.0 / 252.0 #/ 365.0
  70. opt['strike'] = strike
  71. opt['optType'] = option_type
  72. opt['expDate'] = exp_date_str
  73. opt['spotPrice'] = spot
  74. opt['riskFree'] = risk_free
  75. opt['timeToMaturity'] = np.around(time_to_maturity, decimals=4)
  76. opt['settle'] = np.around(greeks.price.values.astype(np.double), decimals=4)
  77. opt['iv'] = np.around(calculated_vol.vol.values.astype(np.double), decimals=4)
  78. opt['delta'] = np.around(greeks.delta.values.astype(np.double), decimals=4)
  79. opt['vega'] = np.around(greeks.vega.values.astype(np.double), decimals=4)
  80. opt['gamma'] = np.around(greeks.gamma.values.astype(np.double), decimals=4)
  81. opt['theta'] = np.around(greeks.theta.values.astype(np.double), decimals=4)
  82. opt['rho'] = np.around(greeks.rho.values.astype(np.double), decimals=4)
  83. fields = [u'instrumentID', u'contractType', u'strikePrice', u'expDate', u'dataDate', u'dataTime',
  84. u'price', 'spotPrice', u'iv',
  85. u'delta', u'vega', u'gamma', u'theta', u'rho']
  86. opt = opt[fields].reset_index().set_index('instrumentID').sort_index()
  87. #opt['iv'] = opt.iv.replace(to_replace=0.0, value=np.nan)
  88. return opt
  89. # 期权分析数据整理
  90. def getOptSnapshotGreeksIV():
  91. # Uqer 计算期权的风险数据
  92. opt_var_sec = u"510050.XSHG" # 期权标的
  93. opt = getOptAnalysisSnapshot(opt_var_sec)
  94. # 整理数据部分
  95. opt.index = [index[-10:] for index in opt.index]
  96. opt = opt[['spotPrice', 'contractType','strikePrice','expDate','price','iv','delta','theta','gamma','vega','rho']]
  97. opt_call = opt[opt.contractType=='CO']
  98. opt_put = opt[opt.contractType=='PO']
  99. opt_call.columns = pd.MultiIndex.from_tuples([('Call', c) for c in opt_call.columns])
  100. opt_call[('Call-Put', 'spotPrice')] = opt_call[('Call', 'spotPrice')]
  101. opt_call[('Call-Put', 'strikePrice')] = opt_call[('Call', 'strikePrice')]
  102. opt_put.columns = pd.MultiIndex.from_tuples([('Put', c) for c in opt_put.columns])
  103. opt = concat([opt_call, opt_put], axis=1, join='inner').sort_index()
  104. opt = opt.set_index(('Call','expDate')).sort_index()
  105. opt = opt.drop([('Call','contractType'), ('Call','strikePrice'), ('Call','spotPrice')], axis=1)
  106. opt = opt.drop([('Put','expDate'), ('Put','contractType'), ('Put','strikePrice'), ('Put','spotPrice')], axis=1)
  107. opt.index.name = 'expDate'
  108. ## 以上得到完整的历史某日数据,格式简洁明了
  109. return opt

即时风险数据计算,其中的price就是买一、卖一价格的平均

  1. DataAPI.MktTickRTSnapshotGet(securityID="510050.XSHG",field=u"",pandas="1").columns
  2. 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')
  1. opt = getOptSnapshotGreeksIV()
  2. opt.head(20)
CallCall-PutPut
priceivdeltathetagammavegarhospotPricestrikePricepriceivdeltathetagammavegarho
expDate
2015-10-280.352500.0000NaNNaNNaNNaNNaN2.2151.850.002650.4139-0.0300-0.12810.30970.0362-0.0040
2015-10-280.303900.0000NaNNaNNaNNaNNaN2.2151.900.004900.4093-0.0517-0.19650.48680.0562-0.0069
2015-10-280.257100.0000NaNNaNNaNNaNNaN2.2151.950.008100.3985-0.0810-0.27060.70860.0797-0.0108
2015-10-280.209900.0000NaNNaNNaNNaNNaN2.2152.000.011300.3716-0.1133-0.32190.97290.1021-0.0151
2015-10-280.166900.0000NaNNaNNaNNaNNaN2.2152.050.017100.3539-0.1649-0.39421.31990.1318-0.0220
2015-10-280.125000.19980.8793-0.23901.89160.10670.10492.2152.100.026050.3391-0.2367-0.46681.71250.1639-0.0317
2015-10-280.091550.23920.7182-0.41712.65740.17940.08632.2152.150.039650.3287-0.3305-0.52732.07460.1925-0.0444
2015-10-280.062850.25100.5679-0.49082.94850.20890.06882.2152.200.060600.3297-0.4416-0.57012.25320.2097-0.0598
2015-10-280.041600.26150.4241-0.49942.81890.20810.05162.2152.250.090950.3483-0.5500-0.59792.13910.2103-0.0753
2015-10-280.027950.27800.3064-0.46972.37660.18650.03742.2152.300.125100.3612-0.6450-0.57511.94010.1978-0.0894
2015-10-280.016550.27930.2049-0.37911.91410.15090.02522.2152.350.164900.3831-0.7188-0.54491.65710.1792-0.1011
2015-11-250.179150.16630.9149-0.13711.15450.12640.24802.2152.050.048150.3692-0.2509-0.33611.06270.2584-0.0811
2015-11-250.148050.21960.7751-0.24901.68190.24330.21062.2152.100.066850.3775-0.3136-0.38031.15740.2878-0.1022
2015-11-250.120250.24380.6649-0.31161.84130.29570.18162.2152.150.089850.3880-0.3780-0.41631.20720.3085-0.1245
2015-11-250.094550.25410.5657-0.33911.90850.31940.15552.2152.200.113350.3891-0.4408-0.42931.24950.3202-0.1463
2015-11-250.073550.26310.4721-0.34751.86350.32300.13052.2152.250.142050.3964-0.5023-0.43801.24020.3238-0.1684
2015-11-250.055250.26670.3849-0.33351.76590.31020.10702.2152.300.174250.4052-0.5598-0.43811.19930.3201-0.1899
2015-12-230.386100.0000NaNNaNNaNNaNNaN2.2151.800.020250.3907-0.0997-0.15730.44060.1782-0.0509
2015-12-230.347100.0000NaNNaNNaNNaNNaN2.2151.850.027350.3885-0.1280-0.18620.52950.2129-0.0656
2015-12-230.304150.0000NaNNaNNaNNaNNaN2.2151.900.036250.3865-0.1610-0.21520.62120.2485-0.0829

2. 隐含波动率微笑

利用上一小节的代码,给出隐含波动率微笑结构

隐含波动率微笑

  1. # 做图展示某一天的隐含波动率微笑
  2. def plotSnapshotSmileVolatility():
  3. # Uqer 计算期权的风险数据
  4. opt = getOptSnapshotGreeksIV()
  5. # 下面展示波动率微笑
  6. exp_dates = np.sort(opt.index.unique())
  7. ## ----------------------------------------------
  8. fig = plt.figure(figsize=(10,8))
  9. fig.set_tight_layout(True)
  10. for i in range(exp_dates.shape[0]):
  11. date = exp_dates[i]
  12. ax = fig.add_subplot(2,2,i+1)
  13. opt_date = opt[opt.index==date].set_index(('Call-Put', 'strikePrice'))
  14. opt_date.index.name = 'strikePrice'
  15. ax.plot(opt_date.index, opt_date[('Call', 'iv')], '-o')
  16. ax.plot(opt_date.index, opt_date[('Put', 'iv')], '-s')
  17. ax.legend(['call', 'put'], loc=0)
  18. ax.grid()
  19. ax.set_xlabel(u"strikePrice")
  20. ax.set_ylabel(r"Implied Volatility")
  21. plt.title(exp_dates[i])
  1. plotSnapshotSmileVolatility()

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

行权价和行权日期两个方向上的隐含波动率微笑

  1. from mpl_toolkits.mplot3d import Axes3D
  2. from matplotlib import cm
  3. # 做图展示某一天的隐含波动率结构
  4. def plotSnapshotSmileVolatilitySurface():
  5. # Uqer 计算期权的风险数据
  6. date = Date.todaysDate()
  7. opt = getOptSnapshotGreeksIV()
  8. # 下面展示波动率结构
  9. exp_dates = np.sort(opt.index.unique())
  10. strikes = np.sort(opt[('Call-Put', 'strikePrice')].unique())
  11. risk_mt = {'Call': pd.DataFrame(index=strikes),
  12. 'Put': pd.DataFrame(index=strikes) }
  13. # 将数据整理成Call和Put分开来,分别的结构为:
  14. # 行为行权价,列为剩余到期天数(以自然天数计算)
  15. for epd in exp_dates:
  16. exp_days = Date.parseISO(epd) - date
  17. opt_date = opt[opt.index==epd].set_index(('Call-Put', 'strikePrice'))
  18. opt_date.index.name = 'strikePrice'
  19. for cp in risk_mt.keys():
  20. risk_mt[cp][exp_days] = opt_date[(cp, 'iv')]
  21. for cp in risk_mt.keys():
  22. for strike in risk_mt[cp].index:
  23. if np.sum(np.isnan(risk_mt[cp].ix[strike])) > 0:
  24. risk_mt[cp] = risk_mt[cp].drop(strike)
  25. # Call和Put分开显示,行index为行权价,列index为剩余到期天数
  26. #print risk_mt
  27. # 画图
  28. for cp in ['Call', 'Put']:
  29. opt = risk_mt[cp]
  30. x = []
  31. y = []
  32. z = []
  33. for xx in opt.index:
  34. for yy in opt.columns:
  35. x.append(xx)
  36. y.append(yy)
  37. z.append(opt[yy][xx])
  38. fig = plt.figure(figsize=(10,8))
  39. fig.suptitle(cp)
  40. ax = fig.gca(projection='3d')
  41. ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2)
  42. return risk_mt

画出某一天的波动率微笑曲面结构

  1. opt = plotSnapshotSmileVolatilitySurface()
  2. opt # Call和Put分开显示,行index为行权价,列index为剩余到期天数
  3. {'Call': 20 48 76 167
  4. 2.05 0.0000 0.1663 0.2027 0.2263
  5. 2.10 0.1998 0.2196 0.2283 0.2430
  6. 2.15 0.2392 0.2438 0.2446 0.2502
  7. 2.20 0.2510 0.2541 0.2570 0.2579
  8. 2.25 0.2615 0.2631 0.2646 0.2639
  9. 2.30 0.2780 0.2667 0.2763 0.2673,
  10. 'Put': 20 48 76 167
  11. 2.05 0.3535 0.3692 0.3965 0.3965
  12. 2.10 0.3391 0.3775 0.4002 0.4002
  13. 2.15 0.3287 0.3877 0.4116 0.4030
  14. 2.20 0.3297 0.3891 0.4185 0.4069
  15. 2.25 0.3483 0.3964 0.4228 0.4084
  16. 2.30 0.3612 0.4052 0.4287 0.4149}

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

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

波动率曲面结构图中:

  • 上图为Call,下图为Put,此处没有进行任何插值处理,略显粗糙
  • Put的隐含波动率明显大于Call
  • 期限结构来说,波动率呈现远高近低的特征
  • 切记:所有的隐含波动率为0代表着期权的时间价值为负,此时的风险数据实际上并无多大参考意义!!