# Adding a Risk-Free Rate To Your Analyses

First off, before beginning this post, I’d like to make my readers aware of the release of a book that I contributed almost an entire chapter for.

Quantitative Trading With R is a primer on quantitative trading in R written by Harry Georgakopoulos, one of Chicago’s better quants. I contributed almost the entire chapter on quantstrat. If you’ve been able to follow and understand the code I write on this blog, then said chapter will mostly be review and a basic nuts and bolts reference. But for those of my readers who gloss over the code and wait for the punchline, I highly recommend it. In addition, there are chapters on high frequency trading, options, spreads, and other things that I do not believe are available in any other book that actually teaches readers the details of implementation. Now, onto the post.

As part of my continuation of Elastic Asset Allocation, I wanted to cover how to implement a measure of a risk-free rate in your analyses. In this post, I’ll analyze two slight variations of EAA from the last post.

Essentially, the idea that rather than look at absolute return, we should look at *excess* return over some sort of risk-free rate, such as the 13-week treasury bill.

Luckily, Yahoo actually *has* a way of getting the returns of the risk-free asset, namely, IRX. But first, let’s get the similarities to the last post out of the way.

```require(quantmod)
require(PerformanceAnalytics)

symbols <- c("VTSMX", "FDIVX", "VEIEX", "VBMFX", "VFISX", "VGSIX", "QRAAX")

getSymbols(symbols, from="1990-01-01")
prices <- list()
for(i in 1:length(symbols)) {
}
prices <- do.call(cbind, prices)
colnames(prices) <- gsub("\\.[A-z]*", "", colnames(prices))
ep <- endpoints(prices, "months")
prices <- prices[ep,]
prices <- prices["1997-03::"]
```

Okay, everything fine so far, same as before. Now here’s the new innovation, brought to my attention by TrendXplorer. It turns out that the IRX index is actually the annualized yield for the short-term (three month) treasuries. So by adding 1, raising it to the 252nd root, and taking the cumulative product, we can actually get the “price” of the risk-free rate, and from that, compute daily returns (this is most likely redundant, but I want all my returns computed the same way).

```getSymbols("^IRX", from="1990-01-01")
dailyYield <- (1+(Cl(IRX)/100))^(1/252) - 1
threeMoPrice <- cumprod(1+dailyYield)
threeMoPrice <- threeMoPrice["1997-03::"]
threeMoPrice <- threeMoPrice[endpoints(threeMoPrice, "months"),]
```

So how does this fit into EAA? Well, simply, I added a new argument called monthlyRiskFree, which will let a user pass in the monthly price series of the risk-free asset, in this case the derived IRX price series. That information is then used to compute a risk-free return, which is subtracted from the returns of all assets, and rather than taking the absolute return of the assets in the universe, instead the algorithm computes the return in excess of the risk-free asset.

Here’s the modified function:

```EAA <- function(monthlyPrices, wR=1, wV=0, wC=.5, wS=2, errorJitter=1e-6,
cashAsset=NULL, bestN=1+ceiling(sqrt(ncol(monthlyPrices))),
enableCrashProtection = TRUE, returnWeights=FALSE, monthlyRiskFree=NULL) {
returns <- Return.calculate(monthlyPrices)
returns <- returns[-1,] #return calculation uses one observation
if(!is.null(monthlyRiskFree)) {
returnsRF <- Return.calculate(monthlyRiskFree)
returnsRF <- returnsRF[-1,]
}

if(is.null(cashAsset)) {
returns\$zeroes <- 0
cashAsset <- "zeroes"
warning("No cash security specified. Recommended to use one of: quandClean('CHRIS/CME_US'), SHY, or VFISX.
}

cashCol <- grep(cashAsset, colnames(returns))

weights <- list()
for(i in 1:(nrow(returns)-11)) {
returnsData <- returns[i:(i+11),] #each chunk will be 12 months of returns data
#per-month mean of cumulative returns of 1, 3, 6, and 12 month periods
periodReturn <- ((returnsData[12,] + Return.cumulative(returnsData[10:12,]) +
Return.cumulative(returnsData[7:12,]) + Return.cumulative(returnsData)))/22

if(!is.null(monthlyRiskFree)) {
rfData <- returnsRF[i:(i+11),]
rfReturn <- ((rfData[12,] + Return.cumulative(rfData[10:12,]) +
Return.cumulative(rfData[7:12,]) + Return.cumulative(rfData)))/22
periodReturn <- periodReturn - as.numeric(rfReturn)
}

vols <- StdDev.annualized(returnsData)
mktIndex <- xts(rowMeans(returnsData), order.by=index(returnsData)) #equal weight returns of universe
cors <- cor(returnsData, mktIndex) #correlations to market index

weightedRets <- periodReturn ^ wR
weightedCors <- (1 - as.numeric(cors)) ^ wC
weightedVols <- (vols + errorJitter) ^ wV
wS <- wS + errorJitter

z <- (weightedRets * weightedCors / weightedVols) ^ wS #compute z_i and zero out negative returns
z[periodReturn < 0] <- 0
crashProtection <- sum(z==0)/length(z) #compute crash protection cash cushion

orderedZ <- sort(as.numeric(z), decreasing=TRUE)
selectedSecurities <- z >= orderedZ[bestN]
preNormalizedWeights <- z*selectedSecurities #select top N securities, keeping z_i scores
periodWeights <- preNormalizedWeights/sum(preNormalizedWeights) #normalize
if (enableCrashProtection) {
periodWeights <- periodWeights * (1-crashProtection) #CP rule
}
weights[[i]] <- periodWeights
}

weights <- do.call(rbind, weights)
weights[, cashCol] <- weights[, cashCol] + 1-rowSums(weights) #add to risk-free asset all non-invested weight
strategyReturns <- Return.rebalancing(R = returns, weights = weights) #compute strategy returns
if(returnWeights) {
return(list(weights, strategyReturns))
} else {
return(strategyReturns)
}
}
```

Essentially, the key new block of code is this:

```    if(!is.null(monthlyRiskFree)) {
rfData <- returnsRF[i:(i+11),]
rfReturn <- ((rfData[12,] + Return.cumulative(rfData[10:12,]) +
Return.cumulative(rfData[7:12,]) + Return.cumulative(rfData)))/22
periodReturn <- periodReturn - as.numeric(rfReturn)
}
```

Which does exactly as I mentioned above — computes the EAA variant of the returns for the period for the risk-free asset and subtracts it from the other returns.

So how does using this new innovation compare to simply looking at absolute returns?

Let’s find out.

```offensive <- EAA(prices, cashAsset="VBMFX", bestN=3)
defensive <- EAA(prices, cashAsset="VBMFX", bestN=3, wS=.5, wC=1)
offRF <- EAA(prices, cashAsset="VBMFX", bestN=3, monthlyRiskFree = threeMoPrice)
defRF <- EAA(prices, cashAsset="VBMFX", bestN=3, wS=.5, wC=1, monthlyRiskFree = threeMoPrice)
compare <- cbind(offensive, defensive, offRF, defRF)
colnames(compare) <- c("Offensive", "Defensive", "OffRF", "DefRF")
stats <- rbind(Return.annualized(compare)*100, StdDev.annualized(compare)*100, maxDrawdown(compare)*100, SharpeRatio.annualized(compare))
rownames(stats)[3] <- "Worst Drawdown"
charts.PerformanceSummary(compare)
```

Here’s the table of statistics:

```                                Offensive Defensive     OffRF     DefRF
Annualized Return               12.206133 10.262766 11.415583 10.269146
Annualized Standard Deviation   11.352728  8.615134 10.551722  8.129250
Worst Drawdown                  12.629251  8.134785 14.351895  9.376533
Annualized Sharpe Ratio (Rf=0%)  1.075172  1.191249  1.081869  1.263234
```

And the corresponding chart:

In short, nope. No dice there. On the defensive portfolio, the change is negligible, and on the offensive side, it seems to encourage more risk than necessary, with…nothing to show for it, really.

That stated, just because this method didn’t pan out doesn’t mean that the actual mechanics of obtaining risk-free data are without value.