Bonds Much Sharpe -r Than Buffett
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Mebane Faber’s post Buffett’s Alpha points out Warren Buffett’s 0.76 Sharpe Ratio discussed in the similarly title paper Buffet’s Alpha. I of course immediately think about the 8th Wonder of the World – the US Bond Market, whose Sharpe Ratio has trounced Buffett’s for the last 30 years. What I like even better are all the tactical systems that employ US bonds in their backtests and make no adjustment for substantially different returns going forward. For those of you who do not know, bonds with absolute certainty cannot achieve >8% annualized returns with max drawdown < 5% for the next 30 years with a starting yield to worst at 1.86% (Barclays Agg 8/23/2012).
If anyone can show me where to get 8% annualized returns with max drawdown of 5% for the next 30 years, please let me know, and I will buy that with leverage and enjoy life. I’ll be happy to share my gains with whomever has the answer.
![]() |
From TimelyPortfolio |
In addition to an unbelievable Sharpe Ratio, bonds have exhibited a low/negative correlation with stocks during stocks’ bear market, which is also historically very anomalous.
![]() |
From TimelyPortfolio |
Just because I love horizon plots.
![]() |
From TimelyPortfolio |
For a longer perspective, here is rolling correlation since 1900 from the CSFB 2011 Yearbook.
So the experience can be dramatically different than what we have been fortunate enough to experience recently.
R code from GIST (install xtsExtra):
require(quantmod) | |
require(PerformanceAnalytics) | |
require(xtsExtra) | |
require(RColorBrewer) | |
#unfortunately don't feel like fighting IP lawyers so I cannot share this index data | |
portfolio <- read.csv("file.csv",stringsAsFactors=FALSE) | |
portfolio <- portfolio[2:NROW(portfolio),2:NCOL(portfolio)] | |
portfolio <- portfolio[,c(1,3,5)] | |
#since export has duplicate colnames we need to remove the .1 added | |
#colnames(portfolio) <- substr(colnames(portfolio),1,nchar(colnames(portfolio))-2) | |
len <- nchar(portfolio[,1]) | |
xtsdate <- paste(substr(portfolio[,1],len-3,len),"-", | |
ifelse(len==9,"0",""),substr(portfolio[,1],1,len-8),"-01",sep="") | |
portfolio.xts <- xts(data.matrix(portfolio[,2:NCOL(portfolio)]),order.by=as.Date(xtsdate)) | |
portfolio.xts <- portfolio.xts/100 | |
portfolio.xts[1,]<-0 | |
charts.RollingPerformance(portfolio.xts[,1], #Barclays Aggregate | |
width = 60, #rolling 5 year (60 months) | |
Rf = portfolio.xts[,2], #Barclays 3-month | |
main = "Barclays Aggregate Performance (Rolling 5 Year)") | |
getSymbols("SP500", src = "FRED") | |
getSymbols("GS10", src = "FRED") | |
require(RQuantLib) | |
GS10pricereturn<-GS10 | |
GS10pricereturn[1,1]<-0 | |
colnames(GS10pricereturn)<-"US10yPrice" | |
for (i in 1:(NROW(GS10)-1)) { | |
GS10pricereturn[i+1,1]<-FixedRateBondPriceByYield(yield=GS10[i+1,1]/100,issueDate=Sys.Date(), | |
maturityDate= advance("UnitedStates/GovernmentBond", Sys.Date(), 10, 3), | |
rates=GS10[i,1]/100,period=2)[1]/100-1 | |
} | |
#total return will be the price return + yield/12 for one month | |
GS10totalreturn<-GS10pricereturn+lag(GS10,k=1)/12/100 | |
colnames(GS10totalreturn)<-"US10yTotalReturn" | |
#thanks to PerformanceAnalytics package for this handy rolling correlation calculation | |
rollcorr <- rollapplyr(na.omit(merge(ROC(SP500,n=1,type="discrete"), | |
GS10totalreturn)), | |
width = 60, #5 year rolling | |
FUN = function(x) cor(x[, 1, drop = FALSE], | |
x[, 2, drop = FALSE]), | |
by = 1, | |
by.column = FALSE, | |
na.pad = TRUE) | |
rollcorr[is.na(rollcorr)] <- 0 #change leading NAs to 0 | |
custom.panel <- function (index, x, col, lwd, ...) { | |
default.panel(index, ifelse(x > 0, x, 0), col = brewer.pal("Greens", n = 9)[7], lwd = 2, ...) | |
default.panel(index, ifelse(x < 0, x, 0), col = brewer.pal("Reds",n = 9)[7], lwd = 2, ...) | |
abline(h=pretty(c(par("yaxp")[1],par("yaxp")[2]),n=par("yaxp")[3]),col="gray60",lwd=0.5) | |
abline (h = 0, col = "black", lwd = 2) | |
} | |
plot.xts(rollcorr, | |
auto.grid = FALSE, #turn off grid for more precise control | |
bty = "n", #turn off box because I like better | |
las = 1, | |
ylim = c(-1,1), | |
main = NA, | |
panel = custom.panel, #use our customized panel | |
type = "l", | |
major.format = "%Y", | |
minor.tick = FALSE, | |
yaxs = "i", | |
cex.axis = 0.8, | |
lwd = 2, | |
blocks = list(start.time = "2000-03-01", end.time = "2012-12-31")) | |
title(main = "Correlation of US 10y Bonds with S&P 500 (Rolling 5 Year)", outer = TRUE, line = -2, adj = 0.05) | |
mtext("Source: Federal Reserve Bank of St. Louis (FRED)", side = 1, cex = 0.7, font = 3, adj = 0.9, outer = TRUE, line = -2) | |
horizon.panel <- function(index,x,...) { | |
#get some decent colors from RColorBrewer | |
#we will use colors on the edges so 2:4 for red and 7:9 for blue | |
require(RColorBrewer) | |
col.brew <- brewer.pal(name="RdBu",n=10) | |
#ease this reference later | |
n=NROW(x) | |
#clean up NA with either of the two methods below | |
#x[which(is.na(x),arr.ind=TRUE)[,1], | |
# unique(which(is.na(x),ar.ind=TRUE)[,2])] <- 0 | |
x <- apply(x,MARGIN=2,FUN=na.fill,fill=0) | |
#get number of bands for the loop | |
#limit to 3 | |
nbands = 3 | |
#first tried this but will not work since each series needs to have same number of bands | |
#min(4,ceiling(max(abs(coredata(x)))/horizonscale)) | |
par(usr=c(index[1],index[n],origin,horizonscale)) | |
for (i in 1:nbands) { | |
#draw positive | |
polygon( | |
c(index[1], index, index[n]), | |
c(origin, coredata(x) - (i-1) * horizonscale,origin), | |
col=col.brew[length(col.brew)-nbands+i-1], | |
border=NA | |
) | |
#draw negative | |
polygon( | |
c(index[1], index, index[n]), | |
c(origin, -coredata(x) - (i-1) * horizonscale,origin), | |
col=col.brew[nbands-i+1], | |
border=NA | |
) | |
} | |
#delete trash drawn below origin that we keep so no overlap between positive and negative | |
polygon( | |
c(index[1], index, index[n]), | |
c(origin, -ifelse(coredata(x)==origin,horizonscale*5,abs(coredata(x))),origin), | |
col=par("bg"), | |
border=NA | |
) | |
#draw a line at the origin | |
abline(h=origin,col="black") | |
} | |
horizonscale = 0.25 | |
origin = 0 | |
plot.xts(rollcorr, | |
auto.grid = FALSE, #turn off grid for more precise control | |
bty = "n", #turn off box because I like better | |
las = 1, | |
ylim = c(origin,horizonscale), | |
main = NA, | |
panel = horizon.panel, #use our customized panel | |
type = "l", | |
major.format = "%Y", | |
minor.tick = FALSE, | |
yax.loc = "none", | |
yaxt = "n", xaxt = "n", | |
cex.axis = 0.6, | |
lwd = 2) | |
title(main = "Correlation of US 10y Bonds with S&P 500 (Rolling 5 Year)", outer = TRUE, line = -2, adj = 0.05, cex = 0.7) | |
mtext("Source: Federal Reserve Bank of St. Louis (FRED)", side = 1, cex = 0.7, font = 3, adj = 0.9, outer = TRUE, line = -2) |
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.