Source Paper: Easy Volatility Investing

Tony Cooper, Double Digit Numerics

Feb 2013

Abstract

Historically volatility trading are only available through options, OTC variance swaps, and later VIX futures.

Volatility ETPs are making volatility trading accessible to retail investors and fund managers.

This purpose of this paper is to devise trading strategies using volatility ETPs.

This paper is structured in following topics:

  • Where volaitlity returns come from
  • 5 trading strategies
  • Portfolio diversification with volatiltiy as an asset class

1. Introduction

VIX Stylized Facts:

  • VIX, unlike stock market returns, is predictable
  • Changes in VIX are negatively correlated with changes in market returns
  • Investors pay Volatiltiy Risk Premium to hedge their volatility risks

Paper Structure:

  • Volatility Risk Premium (VRP) provides our volatility returns, not Roll Yield
  • Volatilty ETPs: VXX/XIV
  • Strategies
  • Risks:
    • Volatility Drag
    • Timing Synchronization
    • VRP-Roll Yield Convergence
    • Regime Change Risk
    • Steamrollr Risk
  • Diversification using volatility ETPs
    • Low correlation to S&P500
    • Boost Returns
    • Lower Volatilities
    • Reduce Drawdowns
    • Improve Sharpe

2. The Lure and Intrigue of Volatiltiy

CBOE VIX

VIX is predictable - It tends to mean revert

  • Simple mean-reverting strategy based on 11-day moving average (long VIX when below MA-11, else go short) provides 215% annualized returns

Changes in VIX is negative correlated with changes in S&P500 (refer to diagram below, notice the correlation is becoming more negative)

  • Long position in VIX, provided it generates positive returns, would provide portfolio diversification

Diagram below shows rolling 1-year correlation between daily SPX return and VIX return.

In [194]:
import pandas as pd
import pandas_datareader as web
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

vix = web.DataReader('^vix','yahoo','1990-01-01')
spx = web.DataReader('^GSPC','yahoo','1990-01-01')
xiv = web.DataReader('xiv','yahoo','2010-10-30')
data = pd.DataFrame()
data['VIX'] = vix['Adj Close']
data['SPX'] = spx['Adj Close']
data['XIV'] = xiv['Adj Close']
data['VIX_Ret'] = data['VIX'].pct_change()
data['SPX_Ret'] = data['SPX'].pct_change()
data['VIX_Ret'].rolling(252).corr(data['SPX_Ret']).plot(figsize=(15,4))
Out[194]:
<matplotlib.axes._subplots.AxesSubplot at 0x122cc5950>

Clearly, the above graph shows the correlation is consistently negative and it is trending toward more negative

Take a look for VIX vs. XIV performance duing 2012-04-03 and 2012-10-01 period:

In [195]:
data_scaled = pd.DataFrame()
data_scaled['VIX'] = data['VIX']/data.ix['2012-04-03']['VIX']
data_scaled['XIV'] = data['XIV']/data.ix['2012-04-03']['XIV']
data_scaled.ix['2012-04-01':'2012-10-01'][['VIX','XIV']].plot(figsize=(15,8))
Out[195]:
<matplotlib.axes._subplots.AxesSubplot at 0x122ccfcd0>

The above graph shows that during the period, while VIX finishes almost flat, XIV is 40% up

3. The Volatility Risk Premium

VRP - Volatility hedgers prepared to pay volatility speculators to offload volatility risk

  • It represents the amount that VIX overpredicts S&P500 volatility
  • The following diagram illustrates spot VIX vs. rolling realized historical volatility 30 days after (using 21 trade days)
  • It's obvious that VIX dominates 30-day realized volaitlity (after shift forward for 30 days) most of the time
  • This represents the VRP is positive most of the time
In [197]:
data['SPX_HV21'] = data['SPX_Ret'].rolling(21).std() * np.sqrt(252) * 100
data['SPX_HV21_Shift'] = data['SPX_HV21'].shift(-21)
data[['VIX','SPX_HV21_Shift']].plot(figsize=(15,8))
Out[197]:
<matplotlib.axes._subplots.AxesSubplot at 0x122032cd0>

Furthermore, below diagram shows the log differences between spot VIX and realized volatility 30 days after.

In [198]:
(np.log(data['VIX']) - np.log(data['SPX_HV21_Shift'])).plot(figsize=(15,8),grid=1)
Out[198]:
<matplotlib.axes._subplots.AxesSubplot at 0x123926190>

4. VIX Futures

The VIX ETPs are constructed using VIX Futures. VIX futures are cash settled using VIX value on expiry/settlement day.

Since VIX Index is not directly tradable, VIX futures price reflects market collective estimate of future VIX level.

Below diagram illustrates VIX Futures Term Structure in Contango on Feb 23, 2017.

In [199]:
import sys
sys.path.append("/Users/valley11/Google Drive/Projects/Python/Samples")
import cboe_vx as cboe

VXF = pd.DataFrame()
VXF['VIX'] = data['VIX']
f = cboe.getCboeData(2017,3)
VXF['Mar'] = f['Settle']
f = cboe.getCboeData(2017,4)
VXF['Apr'] = f['Settle']
f = cboe.getCboeData(2017,5)
VXF['May'] = f['Settle']
f = cboe.getCboeData(2017,6)
VXF['Jun'] = f['Settle']
f = cboe.getCboeData(2017,7)
VXF['Jul'] = f['Settle']
f = cboe.getCboeData(2017,8)
VXF['Aug'] = f['Settle']
f = cboe.getCboeData(2017,9)
VXF['Sep'] = f['Settle']
f = cboe.getCboeData(2017,10)
VXF['Oct'] = f['Settle']
VXF.ix['2017-02-23'].plot(figsize=(15,5))
Out[199]:
<matplotlib.axes._subplots.AxesSubplot at 0x123cb6b10>

Diagram below illustrates a backwardation term structure - happened on Oct 03, 2008, during Financial Crisis.

In [200]:
VXF = pd.DataFrame()
VXF['VIX'] = data['VIX']
f = cboe.getCboeData(2008,10)
VXF['Oct'] = f['Settle']
f = cboe.getCboeData(2008,11)
VXF['Nov'] = f['Settle']
f = cboe.getCboeData(2008,12)
VXF['Dec'] = f['Settle']
f = cboe.getCboeData(2009,1)
VXF['Jan'] = f['Settle']
f = cboe.getCboeData(2009,2)
VXF['Feb'] = f['Settle']
f = cboe.getCboeData(2009,3)
VXF['Mar'] = f['Settle']
f = cboe.getCboeData(2009,4)
VXF['Apr'] = f['Settle']
f = cboe.getCboeData(2009,5)
VXF['May'] = f['Settle']
f = cboe.getCboeData(2009,6)
VXF['Jun'] = f['Settle']
VXF.ix['2008-10-03'].plot(figsize=(15,5))
Out[200]:
<matplotlib.axes._subplots.AxesSubplot at 0x12381c0d0>

Now the question is - is VRP present in VIX futures?

This can be tested using a hypothetical constant one-month future against VIX at 30 days later (when future settles).

Diagram below tries to illustrate this:

  • the constant one-month future is constructed using time-weighted values from F1 and F2
  • VIX is shifted forward for 30 days (21 trade days)
In [208]:
import Quandl
VXF30 = pd.DataFrame()
x = Quandl.get("CHRIS/CBOE_VX1") # continuous F1
VXF30['F1'] = x['Settle'] 
x = Quandl.get("CHRIS/CBOE_VX2") # continuous F2
VXF30['F2'] = x['Settle']
calendar = pd.read_csv('f1_f2_ttm.csv') # read in expiry dates and days till maturity
calendar = calendar.set_index('Date')
VXF30 = pd.merge(VXF30, calendar, how = 'left', left_index = True, right_index = True)
VXF30['X1'] = 30 - VXF30['TTM1']
VXF30['X2'] = VXF30['TTM2'] - 30
VXF30['W1'] = VXF30['X2'] / (VXF30['X1'] + VXF30['X2'])
VXF30['W2'] = VXF30['X1'] / (VXF30['X1'] + VXF30['X2'])
VXF30['VXF30'] = VXF30['F1'] * VXF30['W1'] + VXF30['F2'] * VXF30['W2']
VXF30['VIX'] = data['VIX']
VXF30['VIX_ShiftF21'] = data['VIX'].shift(-21)
#VXF30['VIX'] = data['VIX'].shift(-21)
VXF30[['VXF30','VIX_ShiftF21']].ix['2007-10-01':].plot(figsize=(15,8))
Out[208]:
<matplotlib.axes._subplots.AxesSubplot at 0x12389fc10>

As we can see, the predicted 30-day VIX future dominates subsequent 30-day VIX spot for the majority of times, i.e., Volatility Risk Premium presents in the futures market.

Diagram below shows log difference between one-month VIX futures and subsequent 30-day VIX spot.

In [228]:
(np.log(VXF30['VXF30'].ix['2007-10-01':]) - np.log(VXF30['VIX_ShiftF21'].ix['2007-10-01':])).plot(figsize=(15,8),grid=True)
(np.log(VXF30['VXF30'].ix['2007-10-01':]) - np.log(VXF30['VIX_ShiftF21'].ix['2007-10-01':])).mean()
Out[228]:
0.07114057198623923

5. Roll Yield

An important question in trading VIX futures is:

  • does spot VIX converges to future's price, or does the future's price converges to spot VIX?

Roll Yield describes the differnce between spot VIX and future's price.

Roll yield is measurable, VRP is not.

Roll yield is positive when term structure is in Contango, negative when in Backwardation.

The roll yield is shown in the diagram below.

In [229]:
VXF['F1_VIX_Yield'] = (VXF30['F1'] - VXF30['VIX']) / VXF30['VIX']
VXF['F1_VIX_Yield'].ix['2007-10-01':].plot(figsize=(15,8),grid = True)
Out[229]:
<matplotlib.axes._subplots.AxesSubplot at 0x127c09d50>

And it averages at 4.3% since Oct 01, 2007.

In [230]:
VXF['F1_VIX_Yield'].ix['2007-10-01':].mean()
Out[230]:
0.04375067982717356

6. ETPs based on VIX Futures

Based on S&P 500 Short Term Futures: VXX/XIV

Based on S&P 500 Mid Term Futures: VXZ/ZIV

Many other ETNs including leveraged and inverse leveraged, all share similiar structure.

7. XIV Dynamics

XIV invests in short positions of VIX futures

  • When futures go down, XIV goes up
  • Due to the positive correlation between VIX futures and VIX spot, usually when VIX goes down, XIV goes up (not always)

XIV shifts positions between 1st and 2nd month futures to maintain CONSTANT one-month futures.

Diagram below illustrates daily XIV returns vs. VIX returns.

In [231]:
import OLS_Regression as ols
data['XIV_Ret'] = data['XIV'].pct_change()
ols.linreg(data['VIX_Ret'].ix['2010-12-01':].values, data['XIV_Ret'].ix['2010-12-01':].values)
Out[231]:
OLS Regression Results
Dep. Variable: y R-squared: 0.777
Model: OLS Adj. R-squared: 0.777
Method: Least Squares F-statistic: 5467.
Date: Mon, 27 Feb 2017 Prob (F-statistic): 0.00
Time: 17:05:29 Log-Likelihood: 3985.9
No. Observations: 1569 AIC: -7968.
Df Residuals: 1567 BIC: -7957.
Df Model: 1
Covariance Type: nonrobust
coef std err t P>|t| [95.0% Conf. Int.]
const 0.0032 0.000 6.540 0.000 0.002 0.004
x1 -0.4615 0.006 -73.940 0.000 -0.474 -0.449
Omnibus: 325.757 Durbin-Watson: 2.115
Prob(Omnibus): 0.000 Jarque-Bera (JB): 2976.427
Skew: -0.697 Prob(JB): 0.00
Kurtosis: 9.602 Cond. No. 13.0

Further, we define:

  • XIV Expected Daily Roll Yield = (F2 - F1)/F1/30

We will show expected daily roll yield since XIV inception, and its cumulative roll yields.

In [237]:
VXF30['F2_F1_Yield'] = (VXF30['F2'] - VXF30['F1'])/(VXF30['F1'])/30
(VXF30['F2_F1_Yield'].ix['2010-10-30':]*100).plot(figsize=(15,8), grid = True)
Out[237]:
<matplotlib.axes._subplots.AxesSubplot at 0x129ca7090>

Above graph also clearly shows F1~F2 contango vs. backwardation periods:

  • Days above zero, F1 and F2 are in Contango
  • Days below zero, F1 and F2 are in Backwardation
In [240]:
VXF30['F2_F1_Yield'].ix['2010-10-30':].add(1).cumprod().plot(figsize=(15,8),grid=True)
Out[240]:
<matplotlib.axes._subplots.AxesSubplot at 0x12a2aed50>

The above diagram illustrates if the expected daily roll yield can be realized, XIV will harvest magnificant returns over time. Of course, it didn't happen.

8. More XIV Dynamics

Compare price performance since inception for VXX (2009-01-29) and XIV (2010-11-30).

In [216]:
data['VXX'] = web.DataReader('vxx','yahoo','2009-01-29')['Adj Close']
data[['VXX']].ix['2009-01-29':].plot(figsize=(15,4))
Out[216]:
<matplotlib.axes._subplots.AxesSubplot at 0x126511390>
In [217]:
data[['XIV']].ix['2010-11-30':].plot(figsize=(15,4))
Out[217]:
<matplotlib.axes._subplots.AxesSubplot at 0x1267cc1d0>

Notes for above two diagrams:

  • XIV is the inverse of VXX, however XIV does not have corresponding gains vs. the magnitude of VXX losses
  • This is due to the compounding effect for daily inverse returns
  • There is also a volatility drag that hinders the return for the leveraged and inverse ETFs
  • For XIV, the biggest one-day loss of -26.8% happened on June 24, 2016
  • For XIV, maximum drawdown is -74%, happened during July 07, 2011 and Nov 25, 2011; using hypothetical data going back to the start of VIX futures in 2004, the maximum drawdown would have been -93%
In [241]:
ret = data['XIV_Ret'].add(1).cumprod()
dd = ret.div(ret.cummax()).sub(1)
mdd = dd.min()
end = dd.argmin()
start = ret.loc[:end].argmax()
print "Maximum Drawdown and Period: ", mdd, start, end
Maximum Drawdown and Period:  -0.743870631195 2011-07-07 00:00:00 2011-11-25 00:00:00

9. Trading Strategies

The paper suggests 5 basic trading strategies.

Strategy 1 - Buy and Hold

  • This is the same as XIV price chart shown above

  • 30+% annualized return since inception

  • Large drawdowns


Strategy 2 - Momentum

(i) hold the ETN that has the best return as measured over the last k days

(ii) if all measured k-day returns are zero stay out of the market

Top Pick: the paper suggests to use 83-day


Strategy 3 - Contango-Backwardation Roll Yield

Seek to maximize the roll yield by investing in XIV when the VIX term structure is in contango and in VXX when the term structure is in backwardation.

Basic signal is clear and straightforward:

  • If VXV > VIX, invest in XIV
  • If VXV < VIX, invest in VXX

There are variation of the signals:

  • Vratio - VXV/VIX (In practice, use Vratio10 which has 10-day moving average applied)

  • ERY - Expected Roll Yield

  • T1ratio - VIX1/VIX (Constant one-month future vs. VIX spot)

  • T2ratio - VIX2/VIX (Constant two-month future vs. VIX spot)

  • T5ratio - VIX5/VIX

  • T51ratio - VIX5/VIX1 (Constant five-month future vs Constant one-month future)

  • T52ratio - VIX5/VIX2

  • T21ratio - VIX2/VIX1

Top Pick: Vratio or Vratio10


Strategy 4 - Volatility Risk Premium

Signal Variations:

  • HVOL21: spot VIX - HV21 (Spot VIX less historical 21-trade-day SPX realized volatility)

  • HVOL10: spot VIX - HV10 (10-trade-day realized)

  • HVOL10S: spot VIX - HV10(5-day moving average of 10-trade-day realized)

  • EGARCH: spot VIX - EGARCH(1,1) (Spot VIX less EGARCH estimate)

  • EGARCH1: VIX1 - EGARCH(1,1)

  • EGARCH2: VIX2 - EGARCH(1,1)

  • EGARCH5: VIX5 - EGARCH(1,1)

  • VRPO21: actual realized VRP in options market 21 trade days ago

  • VRPF21: actual realized VRP in futures market 21 trade days ago

Top Pick: HVOL10S - 10 day historical realized volatility smoothed by 5-day moving average


Strategy 5 - Hedging

More appeal to ETN providers due to their constant hedging and rebalancing activities.

Beta-neutral strategies:

  • XVIX
  • XVZ
  • CVIX
  • CVZ -BLVDLM


Other Strategies

Incorporating volatility of VIX into trading rules may help to improve returns.

stdlVIX - standard deviation of the log of VIX

e.g. Go long XIV when Vratio > 1 and stdlVIX < 0.14

10. Risks

The paper states that all strategies produced nice returns in the backtests up until Feb 2013.

Two aspects of risks:

1) risk of not getting similar returns in the future

2) the benefits of incorporating these returns into existing portfolios

Will VRP persist?

  • People will continue to pay a premium to have someone else take on their risk
  • The size of VRP may decrease: more people learn the rules to harvest the profits
  • Regulation may limit VIX ETPs as they impose higher volatilities more frequently with their feedbacks on VIX and SPX options market

Risks from Emperical Analysis

  • volatility drag
  • timing synchronization
  • VRP roll yield risk
  • regime change risk
  • drawdown risk (steamroller risk)

Volatility Drag

The compounded return is reduced the more volatility there is in the daily returns, i.e., the higher the volatiltiy the less the compounded return.

In [242]:
data.ix['2012-03-25':'2012-08-15'][['VIX','XIV']].plot(figsize=(15,8))
Out[242]:
<matplotlib.axes._subplots.AxesSubplot at 0x127aa45d0>

Above chart shows VIX and XIV during the period of Mar 25, 2012 and Aug 15, 2012:

  • VIX finished flat during the period, i.e., no contribution to XIV's return
  • XIV's return shall come from roll yield
In [222]:
VXF30.ix['2012-03-25':'2012-08-15']['F2_F1_Yield'].plot(figsize=(15,4))
VXF30.ix['2012-03-25':'2012-08-15']['F2_F1_Yield'].mean()
Out[222]:
0.32925691048866845

Above chart shows during the period, daily expected roll yield for XIV was significant:

  • it never goes below zero
  • we would expect XIV gain considerably

We further demonstrate using following chart which shows VIX, F1 and F2 during the period

In [249]:
data = pd.merge(data, VXF30[['F1','F2','F2_F1_Yield']], how='left',left_index=True,right_index=True)

data.ix['2012-03-25':'2012-08-15'][['F1','F2','VIX']].plot(figsize=(15,8))
Out[249]:
<matplotlib.axes._subplots.AxesSubplot at 0x12640c310>

Connecting above three charts, we know XIV return is flat during the period, which is contrary to our expectation.

The reason is that the volatiltiy drag cancelled out the roll yield.

We can estimate the volatility drag because it is based on the volatility of VIX and the beta of VIX with respect to VIX.

Timing Synchronization Risk (TSR)

The risk when term structure swings between contango and backwardation frequently, causing continued mis-steps for taking positions in VXX and XIV.

TSR is cyclic risk in that it occurs when the term structure cycles between contango and backwardation.

Potential risk control: apply moving average to contango indicator to smooth out whipsaws. The paper suggests good period for averaging is 10 days.

VRP - Roll Yield Risk (VRP-RYR)

The risk when we pursue roll yield instead of VRP.

Although these two are correlated, this correlation could disappear - especially during times when VIX frequently reverts to its mean (rejecting the roll yield).

This risk menifests itself as a tendency for VIX to rise when the term structure is in contango (when we short VIX), and to fall when the term structure is in backwardation (when we long VIX) - this causes drag on our return.

Regime Change Risk

Drawdown Risk

11. Portfolio Diversification

Reduce drawdown

12. Topics for Future Research

Develop predictive models for VRP

  • Support Vector Regression combined with Time Series Cross-Validation (or block bootstrap) to produce models that predict well rather than fit well
  • So there is no danger of overfitting
  • Aim to improve Sharpe ratio to as high as 2 to 3.

13. Summary and Conclusion

1 - Buy and Hold

2 - Momentum

3 - Roll Yield: based on contango/backwardation; number of annual trades is low

  • If the 10 day moving average of VXV/VIX > 1, long XIV; otherwise, long VXX

4 - VRP: based on fundamental Volatility Risk Premium; number of trades is high

  • If the 5 day moving average of (VIX - HV10) > 0, long XIV; otherwise, long VXX

5 - Hedged: requires frequent rebalance and software to calculate Dynamic Linear Model in order to achieve Sharpe ratio on par to other strategies but with lower drawdowns and zero correlation to S&P 500; appeals to ETF providers

To illustrate the backtest performances, we use codes below to generate trading signals, then use Quantopian platform to run backtests.

In [257]:
# show Vratio10 smoothed by 10 day moving average
# If the 10 day moving average of VXV/VIX > 1, long XIV; otherwise, long VXX
Vratio = pd.DataFrame()
Vratio['VIX'] = web.DataReader('^vix','yahoo','2000-01-01')['Adj Close']
Vratio['VXV'] = web.DataReader('^vxv','yahoo','2000-01-01')['Adj Close']
Vratio['VXV/VIX'] = Vratio['VXV']/Vratio['VIX']
Vratio['VXV/VIX MA10'] = Vratio['VXV/VIX'].rolling(10).mean()
Vratio.ix['2011-08-01':]['VXV/VIX MA10'].plot(figsize=(15,8))
Vratio['VXV/VIX MA10'].to_csv('vratio_ma10.csv')
In [258]:
# show VRP
# If the 5 day moving average of (VIX - HV10) > 0, long XIV; otherwise, long VXX
VRP = pd.DataFrame()
VRP['VIX'] = web.DataReader('^vix','yahoo','2000-01-01')['Adj Close']
VRP['SPX'] = web.DataReader('^gspc','yahoo','2000-01-01')['Adj Close']
VRP['SPX_Ret'] = VRP['SPX'].pct_change()
VRP['HV10'] = VRP['SPX_Ret'].rolling(10).std() * np.sqrt(252)
VRP['HV10 MA5'] = VRP['HV10'].rolling(5).mean()
(VRP['VIX'] - VRP['HV10 MA5']*100).plot(figsize=(15,8),grid=True)
(VRP['VIX'] - VRP['HV10 MA5']*100).to_csv('vrp_hv10_ma5.csv')

14. References