Introduction
The Chicago Board Options Exchange (CBOE) introduced the Volatility Index (VIX) in 1993 to provide a measure of the implied volatility of 30-day, at the money S&P 100 Index Options. Volatility has become a widely accepted asset class since the introduction of the VIX Futures contracts in 2004. VIX Futures are often used for hedging purpose because of its negative correlation with the Equity market return. When we talk about the term structure of Futures, we often refer to the forward curve. The VIX forward curve consists of VIX Futures prices at various delivery times in the future. Academic research states that volatility follows a mean reverting process. When the VIX Futures curve is upward sloped (in contango), the VIX is expected to rise because it is low relative to long-term average levels and vice versa for the downward sloped VIX Future curve. In this study, we will create a strategy with the term structure effect of VIX Futures and hedge the term structure risk with the S&P500 Futures.
Method
The trading strategy uses VIX Futures as a trading vehicle and S&P E-mini for hedging purposes. The spot VIX price data and the price of continuous VIX and E-mini S&P500 Futures in the daily resolution are mapped from the contract with highest open interest.
def initialize(self):
vix = self.add_index("VIX", Resolution.DAILY)
self.vix = vix.symbol
self.vix_multiplier = vix.symbol_properties.contract_multiplier
self.vx1 = self.add_future(Futures.indices.VIX,
resolution = Resolution.DAILY,
extended_market_hours = True,
data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode = DataMappingMode.OPEN_INTEREST,
contract_depth_offset = 0
)
self.vx1_multiplier = self.vx1.symbol_properties.contract_multiplier
self.es1 = self.add_future(Futures.indices.SP500E_MINI,
resolution = Resolution.DAILY,
extended_market_hours = True,
data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode = DataMappingMode.OPEN_INTEREST,
contract_depth_offset = 0
)
self.es1_multiplier = self.es1.symbol_properties.contract_multiplier
Futures basis is defined as the difference between the underlying product cash price and Futures contract price at a given time.
\[Basis_t=S_t-F_t\]
Where \(S_t\) is the adjusted VIX price, \(F_t\) is the adjusted VIX Futures price. Note that the adjustments are based on different contract multiplier for different assets. The basis is in contango means the Futures price is higher than the spot price. The opposite of Contango is Backwardation. It refers to the market condition in which the Futures price is less than the spot price.
\[Contango=S_tF_t\]
While this trading strategy takes advantage of the roll by selling VIX Futures at a premium to the VIX and by buying VIX Futures at a discount to the VIX, it is exposed to the potentially substantial risks associated with adverse moves in the VIX Futures curve. However, as the tendency of VIX Futures prices to move inversely to Equity returns, much of this risk can be hedged by opening the E-mini S&P 500 Futures position in the same direction.
The number of mini-S&P Futures contracts to buy or sell per VIX Futures contract is based on the hedge ratio estimates. 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 business days that the VIX Futures contract is from the settlement, as shown below.
\[\begin{equation} \begin{aligned} \Delta P^{VX}_t = \ & \beta_0+\beta_1* r^{ES}_t \\ & +\beta_2*(r^{ES}_t*s^{VX}_t)+\mu_t \end{aligned} \end{equation}\]
where \(r^{ES}_t\) is the return of ES at time \(t\) and \(s^{VX}_t\) is the time to settlement for VX at time \(t\). After we get the parameters \(\beta_0\), \(\beta_1\) and \(\beta_2\), the formula for the hedge ratio is
\[HR_t=\frac{\beta_1+\beta_2*s_{t-1}}{P^{ES}_{t-1}}\]
def calculate_hedge_ratio(self, es1_price):
price__v_x = np.array(list(self.price__v_x))[::-1]/self.vx1_multiplier
price__e_s = np.array(list(self.price__e_s))[::-1]/self.es1_multiplier
delta__v_x = np.diff(price__v_x)
res__e_s = np.diff(price__e_s)/price__e_s[:-1]*100
tts = np.array(list(self.days_to_maturity))[::-1][1:]
df = pd.DataFrame({"delta__v_x":delta__v_x, "SPRET":res__e_s, "product":res__e_s*tts}).dropna()
# remove rows with zero value
df = df[(df != 0).all(1)]
y = df['delta__v_x'].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 = (beta_1 + beta_2*((self.vx1.mapped.id.date-self.time).days))/float(es1_price*50)
return hedge_ratio
To perform the regression, we save the history price in deque list and update the list every day.
def initialize(self):
# the rolling window to save the front month VX future price
self.price__v_x = RollingWindow[float](252)
# the rolling window to save the front month ES future price
self.price__e_s = RollingWindow[float](252)
# the rolling window to save the time-to-maturity of the contract
self.days_to_maturity = RollingWindow[float](252)
self.set_warm_up(253, Resolution.DAILY)
def on_data(self, data):
if data.bars.contains_key(self.vx1.symbol) and data.bars.contains_key(self.es1.symbol):
# update the rolling window price and time-to-maturity series every day
vx1_price = data.bars[self.vx1.symbol].close
self.price__v_x.add(float(data.bars[self.vx1.symbol].close))
self.price__e_s.add(float(data.bars[self.es1.symbol].close))
self.days_to_maturity.add((self.vx1.mapped.id.date-self.time).days)
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. Short VIX Futures positions are entered when the VIX Futures basis is in contango and the daily roll exceeds 0.10 and long VIX Futures positions are entered when the VIX Futures basis is in backwardation and the daily roll is less than -0.10.
# calculate the daily roll
daily_roll = (vx1_price*self.vx1_multiplier - self.securities[self.vix].price*self.vix_multiplier)\
/(self.vx1.mapped.id.date - self.time).days
self.plot("Trade", "VIX", vx1_price)
self.plot("Trade", "VIX Futures", vx1_price)
self.plot("Trade", "Daily Roll", daily_roll)
if not self.portfolio[self.vx1.mapped].invested:
# Short if the contract is in contango with adaily roll greater than 0.10
if daily_roll > 0.1:
hedge_ratio = self.calculate_hedge_ratio(data.bars[self.es1.symbol].close)
self.plot("Trade", "Sell", vx1_price)
qty = self.calculate_order_quantity(self.vx1.mapped, -1)
self.market_order(self.vx1.mapped, qty // self.vx1.symbol_properties.contract_multiplier)
qty = self.calculate_order_quantity(self.es1.mapped, -1*hedge_ratio)
self.market_order(self.es1.mapped, qty // self.es1.symbol_properties.contract_multiplier)
# Long if the contract is in backwardation with adaily roll less than -0.10
elif daily_roll < -0.1:
hedge_ratio = self.calculate_hedge_ratio(data.bars[self.es1.symbol].close)
self.plot("Trade", "Buy", vx1_price)
qty = self.calculate_order_quantity(self.vx1.mapped, 1)
self.market_order(self.vx1.mapped, qty // self.vx1.symbol_properties.contract_multiplier)
qty = self.calculate_order_quantity(self.es1.mapped, 1*hedge_ratio)
self.market_order(self.es1.mapped, qty // self.es1.symbol_properties.contract_multiplier)
Trades are exited when the motivating conditions no longer exist. The exit condition is defined as the daily roll being less than 0.05 for short trades and higher than -0.05 VIX Futures points for long trades. If these exit conditions are not triggered, trades are exited two days before the contract expires.
# exit if the daily roll being less than 0.05 if holding short positions
if self.portfolio[self.vx1.mapped].is_short and daily_roll < 0.05:
self.liquidate()
return
# exit if the daily roll being greater than -0.05 if holding long positions
if self.portfolio[self.vx1.mapped].is_long and daily_roll > -0.05:
self.liquidate()
return
if self.vx1.mapped and self.es1.mapped:
# if these exit conditions are not triggered, trades are exited two days before it expires
if self.portfolio[self.vx1.mapped].invested and self.portfolio[self.es1.mapped].invested:
if (self.vx1.mapped.id.date-self.time).days <= 2 or (self.es1.mapped.id.date-self.time).days <= 2:
self.liquidate()
Derek Melchin
See the attached backtest for an updated version of the algorithm with the following changes:
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Derek Melchin
Hi everyone,
See the attached backtest for an updated version of the algorithm with the following changes:
Â
Best,Â
Derek Melchin
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Jing Wu
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!