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.

Ilya KipnisI…did not know about the table.AnnualizedReturns functionality, since I actually use something quite similar on my blog (I leave off standard deviation and substitute in max drawdown), but the drawdown table looks especially interesting!

Do you do more posts in R in this type of style? Because this one is well done.

-Ilya (author of quantStrat TradeR)

Pingback: The Whole Street’s Daily Wrap for 1/19/2015 | The Whole Street

James PicernoPost authorIlya,

I do a lot of coding in R on macro and portfolio topics, but have only posted a handful of articles on CapitalSpectator.com over the years. But I’ll be writing more, in part to extend out this piece as an excuse to investigate/analyze how to use R to enhance a simple 60/40 portfolio. Thanks for reading!

–JP

Dave BristorExcellent post! I really look forward to following your progress on this. Any thoughts to making some of it available via an RStudio app? Perhaps allowing user-provided choice of securities, weights, and rebalancing time periods? I could be interested in helping.

James PicernoPost authorDave,

Sounds like a job for Shiny R. In theory, great idea. In fact, I’ve been thinking about something along those lines. Now if I could only figure out how to invent the 30-hour day! 🙂

–JP

ralphhi james

nice post. any books/resources youd recommend for someone starting out to learn R? thx

James PicernoPost authorRalph,

There’s a huge number of resources in books and web sites, but one can start with the official R page:

http://cran.r-project.org/index.html

As an intro, take a look at the Cookbook for R site, and the accompanying book:

http://www.cookbook-r.com/

Also, search for “Econometrics in R” on Google for a free overview by Grant Farnsworth.

–JP

CaxMJames – first thanks for such a well written piece. I’m a R novice and as a means of seeing its application for portf. management tried to simply tried to cut and paste your code as an test case. However i get this error whilst trying to chart portfolios.2:

chart.CumReturns(portfolios.2,

+ wealth.index=TRUE,

+ legend.loc=”bottomright”,

+ main=”Growth of $1 investment”,

+ ylab=”$”)

Error in checkData(R) :

The data cannot be converted into a time series. If you are trying to pass in names from a data object with one column, you should use the form ‘data[rows, columns, drop = FALSE]’. Rownames should have standard date formats, such as ‘1985-03-15’.

Portfolios.2 returns NULL also

I’ve not changed anything within your code in anyway so I would, if you have the time, please enlighten me into what im doing wrong.

Thanks

James PicernoPost authorMax, the code as shown should work fine. Double check that you’re copying and pasting without adding any characters. As a test, try charting the data at the most basic level:

chart.CumReturns(portfolios.2)

This should work; if not, you may have some issue specific to your computer. Good luck.

–JP

Pingback: Using an Allocation Table in R PerformanceAnalytics | KennethKlabunde