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

So, Harry Long came out with a new strategy on SeekingAlpha involving some usual mix of SPXL (3x leveraged SPY), TMF (3x leveraged TLT), and some volatility indices (in this case, ZIV and TVIX). Now, since we’ve tread this path before, expectations are rightfully set. It’s a strategy that’s going to look good in the sample he used, it’s going to get hit hard during the crisis, and it’ll ultimately prove to be a simple-to-implement, simple-to-backtest strategy with its own set of ups and downs.

Once again, a huge thanks to Mr. Helmuth Vollmeier for the long history volatility data.

So here’s the code (I’ll skip a lot of the comparing equity curves of my synthetic instruments to the Yahoo-finance variants, as you’ve all seen that before) to get to the initial equity curve comparison.

require(downloader)
require(PerformanceAnalytics)

destfile="longVXX.txt")
TVIXrets <- Return.calculate(Cl(VXX))*2
getSymbols("TVIX", from="1990-01-01")
TVIX <- TVIX[-which(index(TVIX)=="2014-12-30"),] #trashy Yahoo data, removing obvious bad print
charts.PerformanceSummary(compare)
charts.PerformanceSummary(compare["2012::"])
charts.PerformanceSummary(compare["2013::"])
charts.PerformanceSummary(compare["2014::"])
charts.PerformanceSummary(compare["2015::"]) #okay we're good

destfile="longZIV.txt")
ZIVrets <- Return.calculate(Cl(ZIV))

getSymbols("SPY", from="1990-01-01")

getSymbols("TMF", from="1990-01-01")
getSymbols("TLT", from="1990-01-01")
tmf3TLT <- merge(TMFrets, 3*TLTrets, join='inner')
charts.PerformanceSummary(tmf3TLT)
discrepancy <- as.numeric(Return.annualized(tmf3TLT[,2]-tmf3TLT[,1]))
tmf3TLT[,2] <- tmf3TLT[,2] - ((1+discrepancy)^(1/252)-1)
charts.PerformanceSummary(tmf3TLT)
modifiedTLT <- 3*TLTrets - ((1+discrepancy)^(1/252)-1)
TMFrets <- modifiedTLT

components <- cbind(SPXLrets, ZIVrets, TMFrets, TVIXrets)
components <- components["2004-03-29::"]
stratRets <- Return.portfolio(R = components, weights = c(.4, .2, .35, .05), rebalance_on="years")
charts.PerformanceSummary(stratRets)
compare <- merge(stratRets, SPYrets, join='inner')
charts.PerformanceSummary(compare["2011::"])


With the following equity curve display:

So far, so good. Let’s look at the full backtest performance.

charts.PerformanceSummary(compare)


With the resultant equity curve:

Which, given what we’ve seen before, isn’t outside the realm of expectation.

For those interested in the log equity curves, here you go:

compare[is.na(compare)] <- 0
plot(log(cumprod(1+compare)), legend.loc="topleft")


And for fun, let’s look at the outperformance equity curve.

diff <- compare[,1] - compare[,2]
charts.PerformanceSummary(diff, main="relative performance")


And the result:

Now this is somewhere in the ballpark of what you’d love to see from your strategy against a benchmark — aside from a couple of spikes which do a number on the corresponding drawdowns chart, it looks like a steady outperformance.

However, the new features I’d like to introduce in this blog post are a quicker way of generating the usual statistics table I display, and a more in-depth drawdown analysis.

Here’s how:

rbind(table.AnnualizedReturns(compare), maxDrawdown(compare))


Which gives us the following result:

> rbind(table.AnnualizedReturns(compare), maxDrawdown(compare))
Annualized Return                 0.2181000    0.0799000
Annualized Std Dev                0.2159000    0.1982000
Annualized Sharpe (Rf=0%)         1.0100000    0.4030000
Worst Drawdown                    0.4326138    0.5518552


Since this saves me typing, I’ll be using this format from now on. And as a bonus, it displays annualized standard deviation. While I don’t particularly care for that statistic as I believe that max drawdown captures the notion of “here’s the pain on the other end of your returns” better than “here’s how much your strategy wiggles from day to day”, the fact that it’s thrown in and is a statistic that a lot of other people (particularly portfolio managers, pension fund managers, etc.) are interested in, so much the better.

Now, moving onto a more in-depth analysis of drawdown, PerformanceAnalytics has the following functionality:

dd <- table.Drawdowns(compare[,1], top=100)
dd <- dd[dd$Depth < -.05,] dd sum(dd$"To Trough")/nrow(compare)


This brings up the following table (it seems that with multiple return streams, it’ll just default to the first one), and a derived statistic.

> dd
From     Trough         To   Depth Length To Trough Recovery
1  2008-12-19 2009-03-09 2010-03-16 -0.4326    310        53      257
2  2007-10-30 2008-10-15 2008-12-04 -0.3275    278       243       35
3  2013-05-22 2013-06-24 2013-09-18 -0.1617     83        23       60
4  2004-04-02 2004-05-10 2004-09-17 -0.1450    116        26       90
5  2010-04-26 2010-07-02 2010-09-21 -0.1230    104        49       55
6  2006-03-20 2006-06-19 2006-09-20 -0.1229    129        64       65
7  2007-05-08 2007-08-15 2007-10-01 -0.1229    102        70       32
8  2005-07-29 2005-10-27 2005-12-14 -0.1112     97        64       33
9  2011-06-01 2011-08-11 2011-09-06 -0.1056     68        51       17
10 2005-02-09 2005-03-22 2005-05-31 -0.1051     77        29       48
11 2010-11-05 2010-11-17 2011-02-07 -0.1022     64         9       55
12 2011-09-23 2011-10-27 2011-12-19 -0.0836     61        25       36
13 2013-09-19 2013-10-09 2013-10-17 -0.0815     21        15        6
14 2012-05-02 2012-05-18 2012-06-29 -0.0803     42        13       29
15 2012-10-18 2012-11-15 2012-11-29 -0.0721     28        19        9
16 2014-09-02 2014-10-13 2014-11-05 -0.0679     47        30       17
17 2008-12-05 2008-12-08 2008-12-16 -0.0589      8         2        6
18 2011-02-18 2011-03-16 2011-04-01 -0.0580     30        18       12
19 2014-07-23 2014-08-06 2014-08-15 -0.0536     18        11        7
20 2012-04-03 2012-04-10 2012-04-26 -0.0517     17         5       12


What I did was I simply wanted to query the table for all drawdowns that were more than 5%, or the 100 biggest drawdowns (though considering that we have about 20 drawdowns over about a decade, it seems the rule is 2 drawdowns over 5% per year, give or take, and this is a pretty volatile strategy). Lastly, I wanted to know the proportion of the time that someone watching the strategy will be feeling the pain of watching the strategy go to those depths, so I took the sum of the “To Trough” column and divided it by the amount of days of the backtest. This is the result:

> sum(dd$"To Trough")/nrow(compare) [1] 0.3005505  I’m fairly certain some individuals more seasoned than I am would do something different given this information and functionality, and if so, feel free to leave a comment, but this is just a licked-finger-in-the-air calculation I did. So, 30% of the time, whoever is investing real money into this will want to go and grab a few more drinks than usual. Let’s do the same analysis for the relative performance. tmp <- rbind(table.AnnualizedReturns(diff), maxDrawdown(diff)) rownames(tmp)[4] <- "Worst Drawdown" tmp  When running only one set of returns, apparently the last row will just simply be called “4”, so I had to manually rename that row. Here’s the result: > tmp portfolio.returns Annualized Return 0.1033000 Annualized Std Dev 0.2278000 Annualized Sharpe (Rf=0%) 0.4535000 Worst Drawdown 0.3874082  Far from spectacular, but there it is for what it’s worth. Now the drawdowns. dd <- table.Drawdowns(diff, top=100) dd <- dd[dd$Depth < -.05,]
dd
sum(dd$"To Trough")/nrow(diff)  With the following result: > dd From Trough To Depth Length To Trough Recovery 1 2008-11-21 2009-06-22 2013-04-04 -0.3874 1097 145 952 2 2008-10-28 2008-11-04 2008-11-19 -0.2328 17 6 11 3 2007-11-27 2008-06-12 2008-10-07 -0.1569 218 137 81 4 2013-05-03 2013-06-25 2013-12-26 -0.1386 165 37 128 5 2008-10-13 2008-10-13 2008-10-24 -0.1327 10 1 9 6 2005-06-08 2006-06-28 2006-11-08 -0.1139 360 267 93 7 2004-03-29 2004-05-10 2004-09-20 -0.1102 121 30 91 8 2007-03-08 2007-06-12 2007-11-26 -0.0876 183 67 116 9 2005-02-10 2005-03-28 2005-05-31 -0.0827 76 31 45 10 2014-09-02 2014-09-17 2014-10-14 -0.0613 31 12 19  In short, the spikes in outperformance gave us some pretty…interesting…drawdown statistics, which just essentially meant that the strategy wasn’t roaring at the same exact time that the SPY had its bounce from the bottom. And for interest, my finger in the air pain statistic: > sum(dd$"To Trough")/nrow(diff)
[1] 0.2689908


So approximately 27% of the time, the strategy is really underperforming its benchmark.

In short, overall, more of the same from Harry Long, complete with a very promotional article title. The strategy is what it is–something that boasts strong absolute returns, with the capacity to burn whoever’s investing in it on the occasion, but definitely possesses a better risk/reward profile than the benchmark SPY. However, the quicker statistics table functionality combined with the more in-depth drawdown analysis is something that I am definitely happy to have stumbled upon.