| Overall Statistics |
|
Total Trades
506
Average Win
10.58%
Average Loss
-7.81%
Compounding Annual Return
-20.284%
Drawdown
94.800%
Expectancy
0.062
Net Profit
-92.012%
Sharpe Ratio
-0.153
Probabilistic Sharpe Ratio
0.000%
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
1.35
Alpha
-0.063
Beta
0.053
Annual Standard Deviation
0.383
Annual Variance
0.146
Information Ratio
-0.368
Tracking Error
0.405
Treynor Ratio
-1.111
Total Fees
$4625.00
Estimated Strategy Capacity
$9000000.00
Lowest Capacity Asset
ES XFH59UK0MYO1
|
# https://quantpedia.com/strategies/exploiting-term-structure-of-vix-futures/
#
# The trading strategy is using VIX futures as a trading vehicle and S&P mini for hedging purposes. The investor sells (buys) the nearest
# VIX futures with at least ten trading days to maturity when it is in contango (backwardation) with a daily roll greater than 0.10
# (less than -0.10) points and holds it for five trading days, hedged against changes in the level of spot VIX by (long) short positions
# in E-mini S&P 500 futures. The daily roll is defined as the difference between the front VIX futures price and the VIX, divided by the
# number of business days until the VIX futures contract settles, and measures potential profits assuming that the basis declines linearly
# until settlement. The hedge ratios are constructed from regressions of VIX futures price changes on a constant and on contemporaneous
# percentage changes of the front mini-S&P 500 futures contract both alone and multiplied by the number of days to the settlement of the
# VIX futures contract (see equation 3 on page 12)
import numpy as np
import pandas as pd
import statsmodels.api as sm
from collections import deque
class ExploitingTermStructureVIXFutures(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2011, 1, 1)
self.SetCash(100000)
self.vix = self.AddData(QuandlVix, "CBOE/VIX", Resolution.Daily).Symbol # Add Quandl VIX price (daily)
self.vx1 = self.AddData(QuandlFutures, "CHRIS/CBOE_VX1", Resolution.Daily).Symbol # Add Quandl VIX front month futures data (daily)
self.es1 = self.AddData(QuandlFutures, "CHRIS/CME_ES1", Resolution.Daily).Symbol # Add Quandl E-mini S&P500 front month futures data (daily)
vx_data = self.AddFuture(Futures.Indices.VIX)
vx_data.SetFilter(timedelta(0), timedelta(days=180))
vx_data.MarginModel = BuyingPowerModel(5) # leverage
es_data = self.AddFuture(Futures.Indices.SP500EMini)
es_data.SetFilter(timedelta(0), timedelta(days=180))
es_data.MarginModel = BuyingPowerModel(5) # leverage
self.front_VX = None
self.front_ES = None
# request the history to warm-up the price and time-to-maturity
hist = self.History([self.vx1, self.es1], timedelta(days=450), Resolution.Daily)
settle = hist['settle'].unstack(level=0)
# the rolling window to save the front month VX future price
self.price_VX = deque(maxlen=252)
# the rolling window to save the front month ES future price
self.price_ES = deque(maxlen=252)
# the rolling window to save the time-to-maturity of the contract
self.days_to_maturity = deque(maxlen=252)
expiry_date = self.get_expiry_calendar()
df = pd.concat([settle, expiry_date], axis=1, join='inner')
for index, row in df.iterrows():
self.price_VX.append(row[str(self.vx1) + ' 2S'])
self.price_ES.append(row[str(self.es1) + ' 2S'])
self.days_to_maturity.append((row['expiry']-index).days)
self.Schedule.On(self.DateRules.EveryDay(self.vix), self.TimeRules.AfterMarketOpen(self.vix), self.Rebalance)
def OnData(self, data):
# select the nearest VIX and E-mini S&P500 futures with at least 10 trading days to maturity
# if the front contract expires, roll forward to the next nearest contract
for chain in data.FutureChains:
future_indices = chain.Key.Value[1:] # First letter in this variable is '/'
if future_indices == Futures.Indices.VIX:
if self.front_VX is None or ((self.front_VX.Expiry-self.Time).days <= 1):
contracts = list(filter(lambda x: x.Expiry >= self.Time + timedelta(days = 10), chain.Value))
self.front_VX = sorted(contracts, key = lambda x: x.Expiry)[0]
if future_indices == Futures.Indices.SP500EMini:
if self.front_ES is None or ((self.front_ES.Expiry-self.Time).days <= 1):
contracts = list(filter(lambda x: x.Expiry >= self.Time + timedelta(days = 10), chain.Value))
self.front_ES = sorted(contracts, key = lambda x: x.Expiry)[0]
def Rebalance(self):
if self.Securities.ContainsKey(self.vx1) and self.Securities.ContainsKey(self.es1):
# update the rolling window price and time-to-maturity series every day
if self.front_VX and self.front_ES:
self.price_VX.append(float(self.Securities[self.vx1].Price))
self.price_ES.append(float(self.Securities[self.es1].Price))
self.days_to_maturity.append((self.front_VX.Expiry-self.Time).days)
# calculate the daily roll
daily_roll = (self.Securities[self.vx1].Price - self.Securities[self.vix].Price)/(self.front_VX.Expiry-self.Time).days
if not self.Portfolio[self.front_VX.Symbol].Invested:
# Short if the contract is in contango with adaily roll greater than 0.10
if daily_roll > 0.1:
hedge_ratio = self.CalculateHedgeRatio()
self.SetHoldings(self.front_VX.Symbol, -0.4)
self.SetHoldings(self.front_ES.Symbol, -0.4*hedge_ratio)
# Long if the contract is in backwardation with adaily roll less than -0.10
elif daily_roll < -0.1:
hedge_ratio = self.CalculateHedgeRatio()
self.SetHoldings(self.front_VX.Symbol, 0.4)
self.SetHoldings(self.front_ES.Symbol, 0.4*hedge_ratio)
# exit if the daily roll being less than 0.05 if holding short positions
if self.Portfolio[self.front_VX.Symbol].IsShort and daily_roll < 0.05:
self.Liquidate()
self.front_VX = None
self.front_ES = None
return
# exit if the daily roll being greater than -0.05 if holding long positions
if self.Portfolio[self.front_VX.Symbol].IsLong and daily_roll > -0.05:
self.Liquidate()
self.front_VX = None
self.front_ES = None
return
if self.front_VX and self.front_ES:
# if these exit conditions are not triggered, trades are exited two days before it expires
if self.Portfolio[self.front_VX.Symbol].Invested and self.Portfolio[self.front_ES.Symbol].Invested:
if (self.front_VX.Expiry-self.Time).days <=2 or (self.front_ES.Expiry-self.Time).days <=2:
self.Liquidate()
self.front_VX = None
self.front_ES = None
return
def CalculateHedgeRatio(self):
price_VX = np.array(self.price_VX)
price_ES = np.array(self.price_ES)
delta_VX = np.diff(price_VX)
res_ES = np.diff(price_ES) / price_ES[:-1]*100
tts = np.array(self.days_to_maturity)[1:]
df = pd.DataFrame({"delta_VX":delta_VX, "SPRET":res_ES, "product":res_ES*tts}).dropna()
# remove rows with zero value
df = df[(df != 0).all(1)]
y = df['delta_VX'].astype(float)
X = df[['SPRET', "product"]].astype(float)
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
beta_1 = model.params[1]
beta_2 = model.params[2]
hedge_ratio = abs((1000*beta_1 + beta_2*((self.front_VX.Expiry-self.Time).days)*1000)/(0.01*50*float(self.Securities[self.es1].Price)))
return hedge_ratio
def get_expiry_calendar(self):
# import the futures expiry calendar
url = "data.quantpedia.com/backtesting_data/economic/vix_futures_expiration.csv"
csv_string_file = self.Download(url)
dates = csv_string_file.split('\r\n')
dates = [datetime.strptime(x, "%Y-%m-%d") for x in dates]
df_date = pd.DataFrame(dates, index = dates, columns = [ 'expiry'])
# convert the index and expiry column to datetime format
# df_date.index = pd.to_datetime(df_date.index)
df_date['expiry'] = pd.to_datetime(df_date['expiry'])
# idx = pd.date_range('19-01-2005', '16-12-2020')
# populate the date index and backward fill the dataframe
# return df_date.reindex(idx, method='bfill')
return df_date
class QuandlVix(PythonQuandl):
def __init__(self):
self.ValueColumnName = "close"
class QuandlFutures(PythonQuandl):
def __init__(self):
self.ValueColumnName = "settle"