Rolling Sharpe Ratios

Similar to my rolling cumulative returns from last post, in this post, I will present a way to compute and plot rolling Sharpe ratios. Also, I edited the code to compute rolling returns to be more general with an option to annualize the returns, which is necessary for computing Sharpe ratios.

In any case, let’s look at some more code. First off, the new running cumulative returns:

"runCumRets" <- function(R, n = 252, annualized = FALSE, scale = NA) {
  R <- na.omit(R)
  if (is.na(scale)) {
    freq = periodicity(R)
    switch(freq$scale, minute = {
      stop("Data periodicity too high")
    }, hourly = {
      stop("Data periodicity too high")
    }, daily = {
      scale = 252
    }, weekly = {
      scale = 52
    }, monthly = {
      scale = 12
    }, quarterly = {
      scale = 4
    }, yearly = {
      scale = 1
    })
  }
  cumRets <- cumprod(1+R)
  if(annualized) {
    rollingCumRets <- (cumRets/lag(cumRets, k = n))^(scale/n) - 1 
  } else {
    rollingCumRets <- cumRets/lag(cumRets, k = n) - 1
  }
  return(rollingCumRets)
}

Essentially, a more general variant, with an option to annualize returns over longer (or shorter) periods of time. This is necessary for the following running Sharpe ratio code:

"runSharpe" <- function(R, n = 252, scale = NA, volFactor = 1) {
  if (is.na(scale)) {
    freq = periodicity(R)
    switch(freq$scale, minute = {
      stop("Data periodicity too high")
    }, hourly = {
      stop("Data periodicity too high")
    }, daily = {
      scale = 252
    }, weekly = {
      scale = 52
    }, monthly = {
      scale = 12
    }, quarterly = {
      scale = 4
    }, yearly = {
      scale = 1
    })
  }
  rollingAnnRets <- runCumRets(R, n = n, annualized = TRUE)
  rollingAnnSD <- sapply(R, runSD, n = n)*sqrt(scale)
  rollingSharpe <- rollingAnnRets/rollingAnnSD ^ volFactor
  return(rollingSharpe)
}

The one little innovation I added is the vol factor parameter, allowing users to place more or less emphasis on the volatility. While changing it from 1 will make the calculation different from the standard Sharpe ratio, I added this functionality due to the Logical Invest strategy I did in the past, and thought that I might as well have this function run double duty.

And of course, this comes with a plotting function.

"plotRunSharpe" <- function(R, n = 252, ...) {
  sharpes <- runSharpe(R = R, n = n)
  sharpes <- sharpes[!is.na(sharpes[,1]),]
  chart.TimeSeries(sharpes, legend.loc="topleft", main=paste("Rolling", n, "period Sharpe Ratio"),
                   date.format="%Y", yaxis=FALSE, ylab="Sharpe Ratio", auto.grid=FALSE, ...)
  meltedSharpes <- do.call(c, data.frame(sharpes))
  axisLabels <- pretty(meltedSharpes, n = 10)
  axisLabels <- unique(round(axisLabels, 1))
  axisLabels <- axisLabels[axisLabels > min(axisLabels) & axisLabels < max(axisLabels)]
  axis(side=2, at=axisLabels, label=axisLabels, las=1)
}

So what does this look like, in the case of a 252-day FAA vs. SPY test?

Like this:

par(mfrow = c (2,1))
plotRunSharpe(comparison, n=252)
plotRunSharpe(comparison, n=756)

Essentially, similar to what we saw last time–only having poor performance at the height of the crisis and for a much smaller amount of time than SPY, and always possessing a three-year solid performance. One thing to note about the Sharpe ratio is that the interpretation in the presence of negative returns doesn’t make too much sense. That is, when returns are negative, having a small variance actually works against the Sharpe ratio, so a strategy that may have lost only 10% while SPY lost 50% might look every bit as bad on the Sharpe Ratio plots due to the nature of a small standard deviation punishing smaller negative returns as much as it benefits smaller positive returns.

In conclusion, this is a fast way of computing and plotting a running Sharpe ratio, and this function doubles up as a utility for use with strategies such as the Universal Investment Strategy from Logical Invest.

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.

Introduction to my New IKReporting Package

This post will introduce my up and coming IKReporting package, and functions that compute and plot rolling returns, which are useful to compare recent performance, since simply looking at two complete equity curves may induce sample bias (EG SPY in 2008), which may not reflect the state of the markets going forward.

In any case, the motivation for this package was brought about by one of my readers, who has reminded me in the past of the demand for the in-the-ditches work of pretty performance reports. This package aims to make creating such thing as painless as possible, and I will be updating it rapidly in the near future.

The strategy in use for this post will be Flexible Asset Allocation from my IKTrading package, in order to celebrate the R/Finance lightning talk I’m approved for on FAA, and it’ll be compared to SPY.

Here’s the code:

require(IKTrading)
require(quantmod)
require(PerformanceAnalytics)

options("getSymbols.warning4.0"=FALSE)

symbols <- c("XLB", #SPDR Materials sector
             "XLE", #SPDR Energy sector
             "XLF", #SPDR Financial sector
             "XLP", #SPDR Consumer staples sector
             "XLI", #SPDR Industrial sector
             "XLU", #SPDR Utilities sector
             "XLV", #SPDR Healthcare sector
             "XLK", #SPDR Tech sector
             "XLY", #SPDR Consumer discretionary sector
             "RWR", #SPDR Dow Jones REIT ETF

             "EWJ", #iShares Japan
             "EWG", #iShares Germany
             "EWU", #iShares UK
             "EWC", #iShares Canada
             "EWY", #iShares South Korea
             "EWA", #iShares Australia
             "EWH", #iShares Hong Kong
             "EWS", #iShares Singapore
             "IYZ", #iShares U.S. Telecom
             "EZU", #iShares MSCI EMU ETF
             "IYR", #iShares U.S. Real Estate
             "EWT", #iShares Taiwan
             "EWZ", #iShares Brazil
             "EFA", #iShares EAFE
             "IGE", #iShares North American Natural Resources
             "EPP", #iShares Pacific Ex Japan
             "LQD", #iShares Investment Grade Corporate Bonds
             "SHY", #iShares 1-3 year TBonds
             "IEF", #iShares 3-7 year TBonds
             "TLT" #iShares 20+ year Bonds
)

from="2003-01-01"

#SPDR ETFs first, iShares ETFs afterwards
if(!"XLB" %in% ls()) {
  suppressMessages(getSymbols(symbols, from="2003-01-01", src="yahoo", adjust=TRUE))
}

prices <- list()
for(i in 1:length(symbols)) {
  prices[[i]] <- Cl(get(symbols[i]))
}
prices <- do.call(cbind, prices)
colnames(prices) <- gsub("\\.[A-z]*", "", colnames(prices))

faa <- FAA(prices = prices, riskFreeName = "SHY", bestN = 6, stepCorRank = TRUE)

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

comparison <- merge(faa, Return.calculate(Cl(SPY)), join='inner')
colnames(comparison) <- c("FAA", "SPY")

And now here’s where the new code comes in:

This is a simple function for computing running cumulative returns of a fixed window. It’s a quick three-liner function that can compute the cumulative returns over any fixed period near-instantaneously.

"runCumRets" <- function(R, n = 252) {
  cumRets <- cumprod(1+R)
  rollingCumRets <- cumRets/lag(cumRets, k = n) - 1
  return(rollingCumRets)
}

So how does this get interesting? Well, with some plotting, of course.

Here’s a function to create a plot of these rolling returns.

"plotCumRets" <- function(R, n = 252, ...) {
  cumRets <- runCumRets(R = R, n = n)
  cumRets <- cumRets[!is.na(cumRets[,1]),]
  chart.TimeSeries(cumRets, legend.loc="topleft", main=paste(n, "day rolling cumulative return"),
                   date.format="%Y", yaxis=FALSE, ylab="Return", auto.grid=FALSE)
  
  meltedCumRets <- do.call(c, data.frame(cumRets))
  axisLabels <- pretty(meltedCumRets, n = 10)
  axisLabels <- round(axisLabels, 1)
  axisLabels <- axisLabels[axisLabels > min(axisLabels) & axisLabels < max(axisLabels)]
  axis(side=2, at=axisLabels, label=paste(axisLabels*100, "%"), las=1)
}

While the computation is done in the first line, the rest of the code is simply to make a prettier plot.

Here’s what the 252-day rolling return comparison looks like.

require(IKReporting)
plotCumRets(comparison)

So here’s the interpretation: assuming that there isn’t too much return degradation in the implementation of the FAA strategy, it essentially delivers most of the upside of SPY while doing a much better job protecting the investor when things hit the fan. Recently, however, seeing as to how the stock market has been on a roar, there’s a slight bit of underperformance over the past several years.

However, let’s look at a longer time horizon — the cumulative return over 756 days.

plotCumRets(comparison, n = 756)

With the following result:

This offers a much clearer picture–essentially, what this states is that over any 756-day period, the strategy has not lost money, ever, unlike SPY, which would have wiped out three years of gains (and then some) at the height of the crisis. More recently, as the stock market is in yet another run-up, there has been some short-term (well, if 756 days can be called short-term) underperformance, namely due to SPY having some historical upward mobility.

On another unrelated topic, some of you (perhaps from Seeking Alpha) may have seen the following image floating around:

This is a strategy I have collaborated with Harry Long from Seeking Alpha on. While I’m under NDA and am not allowed to discuss the exact rules of this particular strategy, I can act as a liaison for those that wish to become a client of ZOMMA, LLC. While the price point is out of the reach of ordinary retail investors (the price point is into the six figures), institutions that are considering licensing one of these indices can begin by sending me an email at ilya.kipnis@gmail.com. I can also set up a phone call.

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.