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:
1. Introduction
VIX Stylized Facts:
Paper Structure:
2. The Lure and Intrigue of Volatiltiy
CBOE VIX
VIX is predictable - It tends to mean revert
Changes in VIX is negative correlated with changes in S&P500 (refer to diagram below, notice the correlation is becoming more negative)
Diagram below shows rolling 1-year correlation between daily SPX return and VIX return.
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))
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:
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))
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
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))
Furthermore, below diagram shows the log differences between spot VIX and realized volatility 30 days after.
(np.log(data['VIX']) - np.log(data['SPX_HV21_Shift'])).plot(figsize=(15,8),grid=1)
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.
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))
Diagram below illustrates a backwardation term structure - happened on Oct 03, 2008, during Financial Crisis.
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))
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:
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))
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.
(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()
5. Roll Yield
An important question in trading VIX futures is:
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.
VXF['F1_VIX_Yield'] = (VXF30['F1'] - VXF30['VIX']) / VXF30['VIX']
VXF['F1_VIX_Yield'].ix['2007-10-01':].plot(figsize=(15,8),grid = True)
And it averages at 4.3% since Oct 01, 2007.
VXF['F1_VIX_Yield'].ix['2007-10-01':].mean()
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
XIV shifts positions between 1st and 2nd month futures to maintain CONSTANT one-month futures.
Diagram below illustrates daily XIV returns vs. VIX returns.
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)
Further, we define:
We will show expected daily roll yield since XIV inception, and its cumulative roll yields.
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)
Above graph also clearly shows F1~F2 contango vs. backwardation periods:
VXF30['F2_F1_Yield'].ix['2010-10-30':].add(1).cumprod().plot(figsize=(15,8),grid=True)
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).
data['VXX'] = web.DataReader('vxx','yahoo','2009-01-29')['Adj Close']
data[['VXX']].ix['2009-01-29':].plot(figsize=(15,4))
data[['XIV']].ix['2010-11-30':].plot(figsize=(15,4))
Notes for above two diagrams:
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
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:
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:
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?
Risks from Emperical Analysis
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.
data.ix['2012-03-25':'2012-08-15'][['VIX','XIV']].plot(figsize=(15,8))
Above chart shows VIX and XIV during the period of Mar 25, 2012 and Aug 15, 2012:
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()
Above chart shows during the period, daily expected roll yield for XIV was significant:
We further demonstrate using following chart which shows VIX, F1 and F2 during the period
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))
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
13. Summary and Conclusion
1 - Buy and Hold
2 - Momentum
3 - Roll Yield: based on contango/backwardation; number of annual trades is low
4 - VRP: based on fundamental Volatility Risk Premium; number of trades is high
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.
# 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')
# 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