交易 VIX 期货展期和波动率溢价与 VIX 期权
登录后收藏学术论文
Trading the VIX Futures Roll and Volatility Premiums with VIX Options
通过 VIX 期权交易 VIX 期货展期和波动率溢价 [点击查看论文]
- Bentley University
- ?Bentley University - Department of Finance
策略概要
该策略针对VIX指数期权,由每日展期触发,展期衡量的是近月VIX期货与VIX之间的价差,除以期货结算前的交易日。小于-0.10点的展期信号表明期货贴水,大于+0.10点的展期信号表明期货溢价。在期货贴水时,交易者买入平值VIX看涨期权,在期货溢价时,买入平值VIX看跌期权。交易以买卖报价的中间价执行,持有五个交易日,每笔交易使用交易账户的5%。回测结果按5天回报的1/20进行缩放。
II. 策略合理性
学术研究表明,当VIX期货高于VIX时,VIX往往不会上涨,VIX期货往往会沿着VIX期货曲线向下滚动,在结算时达到较低的VIX,并失去其价值。研究还显示,在样本期内,VIX期权根本没有被高估,或高估程度不高,这表明直接购买VIX期权以利用VIX期货的系统性趋势可能是有吸引力的策略,并且风险有限。
回测表现
胜率24%
完整 Python 代码
from AlgorithmImports import *
class TradingTheVIXFuturesRollAndVolatilityPremiumsWithVIXOptions(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(1000000)
self.holding_period = 5 # holding each option contract for n days
self.trade_percentage = 0.05 # each option contract has 5% of portfolio weight
self.managed_queue = []
index_symbol = self.AddIndex('VIX').Symbol
option = self.AddIndexOption(index_symbol)
option.SetFilter(-5, 5, 25, 35)
self.vix_option_symbol = option.Symbol
self.AddFuture(Futures.Indices.VIX).SetFilter(timedelta(0), timedelta(90))
self.vix_symbol = self.AddData(CBOE, 'VIX').Symbol
self.vix_price = None
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Rebalance)
def OnData(self, data):
# vix data comes each day at 00:00
if self.vix_symbol in data:
# get VIX CBOE price
vix_data = data.Get(CBOE, self.vix_symbol)
self.vix_price = vix_data.Value
option_chain = data.OptionChains.get(self.vix_option_symbol)
# check if all needed data are ready
if option_chain and self.vix_price and data.FuturesChains.Count != 0:
last_spot_vix_price = self.vix_price
self.vix_price = None # make sure daily selection
# get contract with nearest expiration
futures_chain = [x for x in data.FuturesChains.Values][0]
futures_chain = [c for c in futures_chain.Contracts.Values]
# make sure, there is future contract
if len(futures_chain) == 0:
return
nearest_expiration_contract = sorted(futures_chain, key=lambda c: c.Expiry)[0]
last_vix_future_price = nearest_expiration_contract.LastPrice
# get contract expiration and today date
expiry_date = nearest_expiration_contract.Expiry.date()
today = self.Time.date()
# calculate days to contract expiration
days_to_expiration = (expiry_date - today).days
# can't perform division by zero
if days_to_expiration == 0:
return
# calculate spread according to strategy description
spread = (last_vix_future_price - last_spot_vix_price) / days_to_expiration
contract_type = None
# determinate if contract should by put or call type
if spread < -0.10:
contract_type = 1
elif spread > 0.10:
contract_type = 0
# make sure contract type is valid
if contract_type != None:
contracts = [contract for contract in option_chain if contract.Right == contract_type]
# filter ATM contract with latest expiration
contracts = sorted(contracts, key=lambda contract: abs(option_chain.Underlying.Price - contract.Strike))
# sort the ATM contracts by their expiration dates
contracts = sorted(contracts, key=lambda x:x.Expiry, reverse=True)
# make sure there is at least one ATM contract with needed type
if len(contracts) > 0:
nearest_expiration_contract = contracts[0]
# add option contract with it's weight to managed queue
underlying_symbol = nearest_expiration_contract.UnderlyingSymbol
self.managed_queue.append(RebalanceItem(nearest_expiration_contract, underlying_symbol))
def Rebalance(self):
remove_item = None
for rebalance_item in self.managed_queue:
# trade new contract
if rebalance_item.holding_period == 0:
option_contract_symbol = rebalance_item.option_contract.Symbol
underlying_symbol = rebalance_item.underlying_symbol
if self.Securities.ContainsKey(option_contract_symbol) and self.Securities.ContainsKey(underlying_symbol):
if self.Securities[option_contract_symbol].Price != 0 and self.Securities[option_contract_symbol].IsTradable and self.Securities[underlying_symbol].Price != 0:
# calculate contract quantity
underlying_price = self.Securities[underlying_symbol].Price
quantity = self.Portfolio.TotalPortfolioValue / self.holding_period
quantity = np.floor((quantity / (underlying_price*100)) * self.trade_percentage)
# buy contract
self.MarketOrder(option_contract_symbol, quantity)
rebalance_item.quantity = quantity
# liquidate option contract
elif rebalance_item.holding_period == self.holding_period:
option_contract_symbol = rebalance_item.option_contract.Symbol
quantity = rebalance_item.quantity
# liquidate only opened positions
if quantity != 0 and self.Portfolio[option_contract_symbol].Invested:
self.MarketOrder(option_contract_symbol, -quantity)
remove_item = rebalance_item
rebalance_item.holding_period += 1
# remove liquidated option contract from managed queue
if remove_item:
self.managed_queue.remove(remove_item)
class RebalanceItem():
def __init__(self, option_contract, underlying_symbol):
self.quantity = 0
self.holding_period = 0
self.option_contract = option_contract
self.underlying_symbol = underlying_symbol
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))