Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This post will be about comparing strategies from the paper “Easy Volatility Investing”, along with a demonstration of R’s table.Drawdowns command.

First off, before going further, while I think the execution assumptions found in EVI don’t lend the strategies well to actual live trading (although their risk/reward tradeoffs also leave a lot of room for improvement), I think these strategies are great as benchmarks.

So, some time ago, I did an out-of-sample test for one of the strategies found in EVI, which can be found here.

Using the same source of data, I also obtained data for SPY (though, again, AlphaVantage can also provide this service for free for those that don’t use Quandl).

Here’s the new code.

require(downloader)
require(quantmod)
require(PerformanceAnalytics)
require(TTR)
require(Quandl)
require(data.table)

VIXdates <- VIX$Date VIX$Date <- NULL; VIX <- xts(VIX, order.by=as.Date(VIXdates, format = '%m/%d/%Y'))

ma_vRatio <- SMA(Cl(VIX)/Cl(vxv), 10)
xivSigVratio <- ma_vRatio < 1
vxxSigVratio <- ma_vRatio > 1

# V-ratio (VXV/VXMT)
vRatio <- lag(xivSigVratio) * xivRets + lag(vxxSigVratio) * vxxRets
# vRatio <- lag(xivSigVratio, 2) * xivRets + lag(vxxSigVratio, 2) * vxxRets

spy <- Quandl("EOD/SPY", start_date='1990-01-01', type = 'xts')
histVol <- runSD(spyRets, n = 10, sample = FALSE) * sqrt(252) * 100
vixDiff <- Cl(VIX) - histVol
maVixDiff <- SMA(vixDiff, 5)

vrpXivSig <- maVixDiff > 0
vrpVxxSig <- maVixDiff < 0
vrpRets <- lag(vrpXivSig, 1) * xivRets + lag(vrpVxxSig, 1) * vxxRets

obsCloseMomentum <- magicThinking # from previous post

compare <- na.omit(cbind(xivRets, obsCloseMomentum, vRatio, vrpRets))
colnames(compare) <- c("BH_XIV", "DDN_Momentum", "DDN_VRatio", "DDN_VRP")


So, an explanation: there are four return streams here–buy and hold XIV, the DDN momentum from a previous post, and two other strategies.

The simpler one, called the VRatio is simply the ratio of the VIX over the VXV. Near the close, check this quantity. If this is less than one, buy XIV, otherwise, buy VXX.

The other one, called the Volatility Risk Premium strategy (or VRP for short), compares the 10 day historical volatility (that is, the annualized running ten day standard deviation) of the S&P 500, subtracts it from the VIX, and takes a 5 day moving average of that. Near the close, when that’s above zero (that is, VIX is higher than historical volatility), go long XIV, otherwise, go long VXX.

Again, all of these strategies are effectively “observe near/at the close, buy at the close”, so are useful for demonstration purposes, though not for implementation purposes on any large account without incurring market impact.

Here are the results, since 2011 (that is, around the time of XIV’s actual inception):

To note, both the momentum and the VRP strategy underperform buying and holding XIV since 2011. The VRatio strategy, on the other hand, does outperform.

Here’s a summary statistics function that compiles some top-level performance metrics.

stratStats <- function(rets) {
stats <- rbind(table.AnnualizedReturns(rets), maxDrawdown(rets))
stats[5,] <- stats[1,]/stats[4,]
stats[6,] <- stats[1,]/UlcerIndex(rets)
rownames(stats)[4] <- "Worst Drawdown"
rownames(stats)[5] <- "Calmar Ratio"
rownames(stats)[6] <- "Ulcer Performance Index"
return(stats)
}


And the result:

> stratStats(compare['2011::'])
BH_XIV DDN_Momentum DDN_VRatio   DDN_VRP
Annualized Return         0.3801000    0.2837000  0.4539000 0.2572000
Annualized Std Dev        0.6323000    0.5706000  0.6328000 0.6326000
Annualized Sharpe (Rf=0%) 0.6012000    0.4973000  0.7172000 0.4066000
Worst Drawdown            0.7438706    0.6927479  0.7665093 0.7174481
Calmar Ratio              0.5109759    0.4095285  0.5921650 0.3584929
Ulcer Performance Index   1.1352168    1.2076995  1.5291637 0.7555808


To note, all of the benchmark strategies suffered very large drawdowns since XIV’s inception, which we can examine using the table.Drawdowns command, as seen below:

> table.Drawdowns(compare[,1]['2011::'], top = 5)
From     Trough         To   Depth Length To Trough Recovery
1 2011-07-08 2011-11-25 2012-11-26 -0.7439    349        99      250
2 2015-06-24 2016-02-11 2016-12-21 -0.6783    379       161      218
3 2014-07-07 2015-01-30 2015-06-11 -0.4718    236       145       91
4 2011-02-15 2011-03-16 2011-04-20 -0.3013     46        21       25
5 2013-04-15 2013-06-24 2013-07-22 -0.2877     69        50       19
> table.Drawdowns(compare[,2]['2011::'], top = 5)
From     Trough         To   Depth Length To Trough Recovery
1 2014-07-07 2016-06-27 2017-03-13 -0.6927    677       499      178
2 2012-03-27 2012-06-13 2012-09-13 -0.4321    119        55       64
3 2011-10-04 2011-10-28 2012-03-21 -0.3621    117        19       98
4 2011-02-15 2011-03-16 2011-04-21 -0.3013     47        21       26
5 2011-06-01 2011-08-04 2011-08-18 -0.2723     56        46       10
> table.Drawdowns(compare[,3]['2011::'], top = 5)
From     Trough         To   Depth Length To Trough Recovery
1 2014-01-23 2016-02-11 2017-02-14 -0.7665    772       518      254
2 2011-09-13 2011-11-25 2012-03-21 -0.5566    132        53       79
3 2012-03-27 2012-06-01 2012-07-19 -0.3900     80        47       33
4 2011-02-15 2011-03-16 2011-04-20 -0.3013     46        21       25
5 2013-04-15 2013-06-24 2013-07-22 -0.2877     69        50       19
> table.Drawdowns(compare[,4]['2011::'], top = 5)
From     Trough         To   Depth Length To Trough Recovery
1 2015-06-24 2016-02-11 2017-10-11 -0.7174    581       161      420
2 2011-07-08 2011-10-03 2012-02-03 -0.6259    146        61       85
3 2014-07-07 2014-12-16 2015-05-21 -0.4818    222       115      107
4 2013-02-20 2013-07-08 2014-06-10 -0.4108    329        96      233
5 2012-03-27 2012-06-01 2012-07-17 -0.3900     78        47       31


Note that the table.Drawdowns command only examines one return stream at a time. Furthermore, the top argument specifies how many drawdowns to look at, sorted by greatest drawdown first.

One reason I think that these strategies seem to suffer the drawdowns they do is that they’re either all-in on one asset, or its exact opposite, with no room for error.

One last thing, for the curious, here is the comparison with my strategy since 2011 (essentially XIV inception) benchmarked against the strategies in EVI (which I have been trading with live capital since September, and have recently opened a subscription service for):

stratStats(compare['2011::'])
QST_vol    BH_XIV DDN_Momentum DDN_VRatio   DDN_VRP
Annualized Return          0.8133000 0.3801000    0.2837000  0.4539000 0.2572000
Annualized Std Dev         0.3530000 0.6323000    0.5706000  0.6328000 0.6326000
Annualized Sharpe (Rf=0%)  2.3040000 0.6012000    0.4973000  0.7172000 0.4066000
Worst Drawdown             0.2480087 0.7438706    0.6927479  0.7665093 0.7174481
Calmar Ratio               3.2793211 0.5109759    0.4095285  0.5921650 0.3584929
Ulcer Performance Index   10.4220721 1.1352168    1.2076995  1.5291637 0.7555808