How’s that rebalancing strategy working out for ‘ya? Results will vary, of course, depending on when we run the analysis, the architecture of the strategy, and a number of other variables. Deciding if the results are satisfying or disappointing could be due to any number of factors, such as the choice of assets, the rebalancing rules, or the selection of ETFs to implement the strategy. But whether you’re running a backtest to kick the tires on a rebalancing idea or monitoring an existing portfolio in real time, keeping an eye on risk and return–and understanding what’s driving results–is critical. The good news is that this essential task is relatively easy thanks to R, the data analysis software. As a brief illustration, let’s kick the tires on a simple 60%/40% US stock/bond strategy with a couple of ETFs.
The recent upgrade of the PerformanceAnalytics package in particular is a major leap forward for dissecting the finer points of rebalancing strategies. As an investor/journalist/consultant who once upon a time ran portfolio analytics in Excel, I can now see that shifting to R is the equivalent of trading in the horse and buggy for a private jet in one’s econometric travels in finance and economics. The combination of running a spectrum of sophisticated analytics quickly and with minimal brain damage with regards to data management is a huge plus with R. To understand why, I’ll be writing a series of posts going forward that explore the possibilities of portfolio analytics in R. But the 1,000-mile journey begins with the first step.
As always, we need data. To start our test, let’s download the daily price history for two ETFs to populate our 60/40 portfolio: SPDR S&P 500 ETF (SPY) and the iShares Core US Aggregate Bond (AGG).
# rebalancing analysis for 60/40 US stock/bond portfolio # load packages library(quantmod) library(tseries) library(PerformanceAnalytics) # download prices spy <-get.hist.quote(instrument="spy",start="2003-12-31",quote="AdjClose",compression="d") agg <-get.hist.quote(instrument="agg",start="2003-12-31",quote="AdjClose",compression="d") # choose asset weights w = c(0.6,0.4) # 60% / 40% # merge price histories into one dataset # calculate 1-day % returns # and label columns portfolio.prices <-as.xts(merge(spy,agg)) portfolio.returns <-na.omit(ROC(portfolio.prices,1,"discrete")) colnames(portfolio.returns) <-c("spy","agg")
Using the Return.portfolio function in the PerformanceAnalytics package, the next step is generating portfolio results for two strategies that begin with a 60/40 asset allocation on Dec. 31, 2003. The first portfolio is simply a buy-and-hold strategy; the second is rebalancing back to 60/40 weights every Dec. 31.
# calculate portfolio total returns # rebalanced portfolio portfolio.rebal <-Return.portfolio(portfolio.returns, rebalance_on="years", weights=w,wealth.index=TRUE,verbose=TRUE) # buy and hold portfolio/no rebalancing portfolio.bh <-Return.portfolio(portfolio.returns, weights=w,wealth.index=TRUE,verbose=TRUE) # merge portfolio returns into one dataset # label columns portfolios.2 <-cbind(portfolio.rebal$returns,portfolio.bh$returns) colnames(portfolios.2) <-c("rebalanced","buy and hold")
An obvious way to begin: compare the investment results of the two strategies. Here’s how a $1 investment in each of the portfolios compared from the end of 2003 through Jan. 16, 2015. Note the modestly higher performance in the rebalanced strategy.
chart.CumReturns(portfolios.2, wealth.index=TRUE, legend.loc="bottomright", main="Growth of $1 investment", ylab="$")
For a quick comparison of risk and return, we might run the table.AnnualizedReturns function, which shows that the rebalanced strategy has a modest performance edge.
# Compare return/risk table.AnnualizedReturns(portfolios.2) rebalanced buy and hold Annualized Return 0.0695 0.0653 Annualized Std Dev 0.1112 0.1118 Annualized Sharpe (Rf=0%) 0.6250 0.5840
Another perspective on risk is looking at drawdown (DD), which measures the peak-to-trough declines. Here’s how the top-five drawdowns stack up for the sample period. It’s interesting to note that the rebalanced portfolio’s worst drawdown was slightly shorter and less painful vs. the buy-and-hold strategy’s deepest DD.
# Review biggest drawdowns table.Drawdowns((portfolio.bh$returns)) # buy and hold drawdowns From Trough To Depth Length To Trough Recovery 1 2007-10-10 2009-03-09 2011-01-27 -0.3417 832 355 477 2 2011-07-25 2011-08-08 2012-01-12 -0.0935 120 11 109 3 2007-07-20 2007-08-15 2007-09-19 -0.0570 43 19 24 4 2004-03-08 2004-05-10 2004-11-03 -0.0533 168 45 123 5 2012-04-03 2012-06-04 2012-07-27 -0.0502 81 43 38 table.Drawdowns((portfolio.rebal$returns)) # rebal drawdowns From Trough To Depth Length To Trough Recovery 1 2007-10-10 2009-03-09 2010-11-02 -0.3371 773 355 418 2 2011-07-25 2011-10-03 2012-01-12 -0.0964 120 50 70 3 2012-04-03 2012-06-04 2012-07-27 -0.0536 81 43 38 4 2004-03-08 2004-05-10 2004-11-03 -0.0533 168 45 123 5 2007-07-20 2007-08-15 2007-09-18 -0.0524 42 19 23
We can also run a battery of tests to model portfolio results for a deeper look into what going on under the performance hood. For instance, let’s consider the distribution of the rebalanced portfolio’s returns. By using the qqnorm function, we can visually inspect how a portfolio’s performance compares with a normal distribution, a test that provides a quick summary of the incidence of fat tails. As you can see in the next chart below, the rebalanced portfolio suffers from a degree of fat tails (black circles). If the returns were normally distributed, the black circles would align with the red line. That’s what we see for most of the returns, but at the extremes there’s a clear divergence from the red line, which tells us that we should do some additional modeling/testing to investigate the potential for trouble related to fat tails.
# Compare portfolio return distribution vs. normal distribution qqnorm(portfolio.rebal$returns,main="Rebalanced Portfolio") qqline(portfolio.rebal$returns,col="red")
Another valuable metric is rolling one-year return, which offers some perspective on how a strategy evolves without the distracting short-term noise of daily volatility. By this standard, the two strategies look similar. Of course, it’s worth mentioning that the similarity in annual returns masks the fact that the rebalanced strategy ultimately delivered a slightly superior return through time.
# Compare rolling 1-year returns chart.RollingPerformance(portfolios.2,width=252, legend.loc="bottomright", main="Rolling 1yr % returns")
The analytics above barely scratch the surface of what’s available in R. In future posts, I’ll continue to explore how we might enhance the 60/40 portfolio and run a series of quantitative tests to validate (or reject) the results. As we’ll see, there are a variety of improvements we can make that fall under two broad headings: adding asset classes and tweaking the rebalancing rules. The acid test, of course, is generating superior risk-adjusted returns that stand up to rigorous econometric tests, some of which I’ll outline in the weeks ahead. Easier said than done, although necessary if we’re intent on developing a high degree of confidence in a given strategy. But thanks to the broad and deep resources in R’s econometric toolkit, the odds are a bit higher than you might expect for juicing results relative to what we can easily tap into with a basic 60/40 strategy.