An Out of Sample Update on DDN’s Volatility Momentum Trading Strategy and Beta Convexity

The first part of this post is a quick update on Tony Cooper’s of Double Digit Numerics’s volatility ETN momentum strategy from the volatility made simple blog (which has stopped updating as of a year and a half ago). The second part will cover Dr. Jonathan Kinlay’s Beta Convexity concept.

So, now that I have the ability to generate a term structure and constant expiry contracts, I decided to revisit some of the strategies on Volatility Made Simple and see if any of them are any good (long story short: all of the publicly detailed ones aren’t so hot besides mine–they either have a massive drawdown in-sample around the time of the crisis, or a massive drawdown out-of-sample).

Why this strategy? Because it seemed different from most of the usual term structure ratio trades (of which mine is an example), so I thought I’d check out how it did since its first publishing date, and because it’s rather easy to understand.

Here’s the strategy:

Take XIV, VXX, ZIV, VXZ, and SHY (this last one as the “risk free” asset), and at the close, invest in whichever has had the highest 83 day momentum (this was the result of optimization done on volatilityMadeSimple).

Here’s the code to do this in R, using the Quandl EOD database. There are two variants tested–observe the close, buy the close (AKA magical thinking), and observe the close, buy tomorrow’s close.



symbols <- c("XIV", "VXX", "ZIV", "VXZ", "SHY")

prices <- list()
for(i in 1:length(symbols)) {
  price <- Quandl(paste0("EOD/", symbols[i]), start_date="1990-12-31", type = "xts")$Adj_Close
  colnames(price) <- symbols[i]
  prices[[i]] <- price
prices <- na.omit(, prices))
returns <- na.omit(Return.calculate(prices))

# find highest asset, assign column names
topAsset <- function(row, assetNames) {
  out <- row==max(row, na.rm = TRUE)
  names(out) <- assetNames
  out <- data.frame(out)

# compute momentum
momentums <- na.omit(xts(apply(prices, 2, ROC, n = 83),

# find highest asset each day, turn it into an xts
highestMom <- apply(momentums, 1, topAsset, assetNames = colnames(momentums))
highestMom <- xts(t(, highestMom)),

# observe today's close, buy tomorrow's close
buyTomorrow <- na.omit(xts(rowSums(returns * lag(highestMom, 2)),

# observe today's close, buy today's close (aka magic thinking)
magicThinking <- na.omit(xts(rowSums(returns * lag(highestMom)),

out <- na.omit(cbind(buyTomorrow, magicThinking))
colnames(out) <- c("buyTomorrow", "magicalThinking")

# results
charts.PerformanceSummary(out['2014-04-11::'], legend.loc = 'top')
rbind(table.AnnualizedReturns(out['2014-04-11::']), maxDrawdown(out['2014-04-11::']))

Pretty simple.

Here are the results.


> rbind(table.AnnualizedReturns(out['2014-04-11::']), maxDrawdown(out['2014-04-11::']))
                          buyTomorrow magicalThinking
Annualized Return          -0.0320000       0.0378000
Annualized Std Dev          0.5853000       0.5854000
Annualized Sharpe (Rf=0%)  -0.0547000       0.0646000
Worst Drawdown              0.8166912       0.7761655

Looks like this strategy didn’t pan out too well. Just a daily reminder that if you’re using fine grid-search to select a particularly good parameter (EG n = 83 days? Maybe 4 21-day trading months, but even that would have been n = 82), you’re asking for a visit from, in the words of Mr. Tony Cooper, a visit from the grim reaper.


Moving onto another topic, whenever Dr. Jonathan Kinlay posts something that I think I can replicate that I’d be very wise to do so, as he is a very skilled and experienced practitioner (and also includes me on his blogroll).

A topic that Dr. Kinlay covered is the idea of beta convexity–namely, that an asset’s beta to a benchmark may be different when the benchmark is up as compared to when it’s down. Essentially, it’s the idea that we want to weed out firms that are what I’d deem as “losers in disguise”–I.E. those that act fine when times are good (which is when we really don’t care about diversification, since everything is going up anyway), but do nothing during bad times.

The beta convexity is calculated quite simply: it’s the beta of an asset to a benchmark when the benchmark has a positive return, minus the beta of an asset to a benchmark when the benchmark has a negative return, then squaring the difference. That is, (beta_bench_positive – beta_bench_negative) ^ 2.

Here’s some R code to demonstrate this, using IBM vs. the S&P 500 since 1995.

ibm <- Quandl("EOD/IBM", start_date="1995-01-01", type = "xts")
ibmRets <- Return.calculate(ibm$Adj_Close)

spy <- Quandl("EOD/SPY", start_date="1995-01-01", type = "xts")
spyRets <- Return.calculate(spy$Adj_Close)

rets <- na.omit(cbind(ibmRets, spyRets))
colnames(rets) <- c("IBM", "SPY")

betaConvexity <- function(Ra, Rb) {
  positiveBench <- Rb[Rb > 0]
  assetPositiveBench <- Ra[index(positiveBench)]
  positiveBeta <- CAPM.beta(Ra = assetPositiveBench, Rb = positiveBench)
  negativeBench <- Rb[Rb < 0]
  assetNegativeBench <- Ra[index(negativeBench)]
  negativeBeta <- CAPM.beta(Ra = assetNegativeBench, Rb = negativeBench)
  out <- (positiveBeta - negativeBeta) ^ 2

betaConvexity(rets$IBM, rets$SPY)

For the result:

> betaConvexity(rets$IBM, rets$SPY)
[1] 0.004136034

Thanks for reading.

NOTE: I am always looking to network, and am currently actively looking for full-time opportunities which may benefit from my skill set. If you have a position which may benefit from my skills, do not hesitate to reach out to me. My LinkedIn profile can be found here.


17 thoughts on “An Out of Sample Update on DDN’s Volatility Momentum Trading Strategy and Beta Convexity

  1. Pingback: Quantocracy's Daily Wrap for 06/20/2017 | Quantocracy

  2. Pingback: An Out of Sample Update on DDN’s Volatility Momentum Trading Strategy and Beta Convexity | A bunch of data

  3. Thanks for generous sharing the great articles & R codes.
    Willing to comments on using Quandl EOD data and Yahoo data (of course, as we know, Yahoo data don’t have the dividend adjusted)?

  4. There are two things going on here – the grim reaper (regression to the mean) and the changed market. The latter is the more important. The VIX futures curve used to be C-shaped, but now so many people are trading the strategy that it has become S-shaped. In other words, it’s flatter at the front. Since it was the slope at the front that provided the profit the flatness means that the profit has gone.
    It would be interesting to see some R code that quantifies this.

    • Tony,

      Glad to hear from you. How are things? I have a way to create a term structure using CBOE settle data. How would you define “C-shaped” vs. “S-shaped”? Would it be say, C3 – C2 and C2 – C1 (i.e. the first two differences), or maybe C1 – spot, C2 – C1, C3 – C2 vs. say, C4 – C3 and C5 – C4, or something along those lines? Would love to do a post on this, but some formal definitions from a more experienced practitioner would be a great place to start for me, so some assistance is always very appreciated.

      • Fitting a Nelson-Siegel model might help. The term structure is split into 3 factors: level, slope and curvature that you can interpret as long, medium and short term expected volatility.

      • Fabrizio,

        How does a Nelson-Siegel model apply? Are my term structure contract prices my rates, and my times to expiry my times to maturity?

      • If you do a PCA analysis you get the main components being level, slope and curvature. So the Nelson-Siegel model sounds like a good idea – I’ve never fitted one.

        But the S shape that I mentioned is a bit more subtle than just curvature. By C-shaped I mean one wiggle in the curve, S-shaped I mean two wiggles (in the first 5 months). the shape starts off flat, then goes steep, then goes flat again. The flat bit at the beginning is due to people trading the strategy and thereby messing up the relationship of the two front months.

    • I really enjoyed reading your paper and I guess your VRP strategy must have been very successful in the last year(s). One problem I see is when the market correctly prices future changes in volatility and the risk premium (regardless how you measure it) does not change sign, resulting in a massive draw-down. For example when volatility gradually increases but the risk premium is more or less constant during the ascent and the buy signal is never triggered.

  5. If find also Dr Kinlay’s blog extremely interesting and inspiring. A more elaborated way to quantify the asymmetry of beta is to capture the structure of correlation using a quantile regression. If correlation is not constant across the joint distribution of returns, your hedge becomes (even more) non-linear, in substance you are short/long an invisible option (on VVIX, for VIX futures or ETFs).

      • He never provides much details, but at least he points you always in the right direction. Regarding Nelson-Siegel you need tenors rather than fixed expiries. I interpolate VIX prices with cubic splines (although strictly speaking is not the most robust solution).

      • There is a paper: Guo, Han, Zhao – The Nelson-Siegel Model of the Term Structure of Option (2014) although I am not so sure RMSE is the best cost function and lambda is certainly not constant (though reasonably stable).

  6. Pease help me.. I can’t install quantstrat and blotter packages in R on my MacBook.
    Can anyone tell me where to find the source file? .tar.gz for MacBook?
    I’ve searched online for so long, still no lucky.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s