Backtest Period
2007-2014
Markets Traded
Equities / Options
Maximum Drawdown
Period of Rebalancing
Daily
Return (Annual)
22.5%
Sharpe Ratio
Standard Deviation (Annual)
Original paper
SSRN-id2624713.pdf637.8KB
Trading rules
- Investment universe is options on VIX Index.
- Option trades are triggered by the daily roll which is the spread between front VIX futures and the VIX divided by the number of business days until the settlement of the VIX futures contract.
- For backwardation, the daily roll has to be less than -0.10 VIX futures point, and for contango, it has to be greater than +0.10 point.
- Trader purchases the closest to expiration at-the-money VIX calls when the VIX futures curve is in backwardation and closest to expiration at-the-money VIX puts when in contango.
- Trades are entered and exited at the mid-point of closing bid-ask quotes, and new trades are entered as soon as they are triggered.
- The holding period for each trade is 5 business days.
- 5% of trading account is used for each trade.
- Backtested results from the academic paper are scaled accordingly with a multiplication factor of 1/20.
Python code
Backtrader
# Import necessary libraries
import backtrader as bt
# Define the options on VIX index as investment universe
class VIXOptions(bt.Strategy):
params = dict(
rolldays=bt.num2date(7),
atmf=0.5,
contract='VX1')
def __init__(self):
self.rollover = btind.RollingFuture(
self.data1, period=self.p.rolldays, calendar=btind.BusinessDay,
name='FC')
self.roll = self.rollover - self.data0.close
def next(self):
business_days = len(btind.businessdays(
self.datas[self.p.contract].datetime.datetime(0).date(),
self.datas[self.p.contract].datetime.date(0)
))
# Calculate daily roll
daily_roll = self.roll[0] / business_days
# Buy at-the-money VIX calls in backwardation and VIX puts in contango
if daily_roll < -0.10 and self.data0.close[0] > self.p.atmf:
order = self.buy(size=0.05 * self.broker.getvalue() / self.data1.close[0],
exectype=bt.Order.Market)
elif daily_roll > 0.10 and self.data0.close[0] < self.p.atmf:
order = self.sell(size=0.05 * self.broker.getvalue() / self.data1.close[0],
exectype=bt.Order.Market)
# Exit trades after 5 business days
for order in self.broker.open_orders:
if (self.datas[order.params.contract[0]] <= order.params.expiry.datetime(0)) and order.params.expirycheck:
self.cancel(order)
for p in self.broker.positions:
if p.data == self.data0 and len(btind.businessdays(p.opened.dt.date(), self.datetime.date(0))) > 5:
self.close(p)