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.