A New Harry Long Strategy and A Couple of New PerfA Functions

[This article was first published on QuantStrat TradeR » R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
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)

download("https://dl.dropboxusercontent.com/s/950x55x7jtm9x2q/VXXlong.TXT",
         destfile="longVXX.txt")
VXX <- xts(read.zoo("longVXX.txt", sep=",", header=TRUE))
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
compare <- merge(TVIXrets, Return.calculate(Ad(TVIX)), join='inner')
charts.PerformanceSummary(compare)
charts.PerformanceSummary(compare["2012::"])
charts.PerformanceSummary(compare["2013::"])
charts.PerformanceSummary(compare["2014::"])
charts.PerformanceSummary(compare["2015::"]) #okay we're good

download("https://www.dropbox.com/s/jk3ortdyru4sg4n/ZIVlong.TXT",
         destfile="longZIV.txt")
ZIV <- xts(read.zoo("longZIV.txt", sep=",", header=TRUE))
ZIVrets <- Return.calculate(Cl(ZIV))

getSymbols("SPY", from="1990-01-01")
SPXLrets <- Return.calculate(Ad(SPY))*3

getSymbols("TMF", from="1990-01-01")
TMFrets <- Return.calculate(Ad(TMF))
getSymbols("TLT", from="1990-01-01")
TLTrets <- Return.calculate(Ad(TLT))
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)
SPYrets <- Return.calculate(Ad(SPY))
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))
                          portfolio.returns SPY.Adjusted
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.

Thanks for reading.

NOTE: I am a freelance consultant in quantitative analysis on topics related to this blog. If you have contract or full time roles available for proprietary research that could benefit from my skills, please contact me through my LinkedIn here.


To leave a comment for the author, please follow the link and comment on their blog: QuantStrat TradeR » R.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)