This post will demonstrate how to take into account turnover when dealing with returns-based data using PerformanceAnalytics and the Return.Portfolio function in R. It will demonstrate this on a basic strategy on the nine sector SPDRs.

So, first off, this is in response to a question posed by one Robert Wages on the R-SIG-Finance mailing list. While there are many individuals out there with a plethora of questions (many of which can be found to be demonstrated on this blog already), occasionally, there will be an industry veteran, a PhD statistics student from Stanford, or other very intelligent individual that will ask a question on a topic that I haven’t yet touched on this blog, which will prompt a post to demonstrate another technical aspect found in R. This is one of those times.

So, this demonstration will be about computing turnover in returns space using the PerformanceAnalytics package. Simply, outside of the PortfolioAnalytics package, PerformanceAnalytics with its Return.Portfolio function is the go-to R package for portfolio management simulations, as it can take a set of weights, a set of returns, and generate a set of portfolio returns for analysis with the rest of PerformanceAnalytics’s functions.

Again, the strategy is this: take the 9 three-letter sector SPDRs (since there are four-letter ETFs now), and at the end of every month, if the adjusted price is above its 200-day moving average, invest into it. Normalize across all invested sectors (that is, 1/9th if invested into all 9, 100% into 1 if only 1 invested into, 100% cash, denoted with a zero return vector, if no sectors are invested into). It’s a simple, toy strategy, as the strategy isn’t the point of the demonstration.

Here’s the basic setup code:

require(TTR) require(PerformanceAnalytics) require(quantmod) symbols <- c("XLF", "XLK", "XLU", "XLE", "XLP", "XLF", "XLB", "XLV", "XLY") getSymbols(symbols, src='yahoo', from = '1990-01-01-01') prices <- list() for(i in 1:length(symbols)) { tmp <- Ad(get(symbols[[i]])) prices[[i]] <- tmp } prices <- do.call(cbind, prices) # Our signal is a simple adjusted price over 200 day SMA signal <- prices > xts(apply(prices, 2, SMA, n = 200), order.by=index(prices)) # equal weight all assets with price above SMA200 returns <- Return.calculate(prices) weights <- signal/(rowSums(signal)+1e-16) # With Return.portfolio, need all weights to sum to 1 weights$zeroes <- 1 - rowSums(weights) returns$zeroes <- 0 monthlyWeights <- na.omit(weights[endpoints(weights, on = 'months'),]) weights <- na.omit(weights) returns <- na.omit(returns)

So, get the SPDRs, put them together, compute their returns, generate the signal, and create the zero vector, since Return.Portfolio treats weights less than 1 as a withdrawal, and weights above 1 as the addition of more capital (big FYI here).

Now, here’s how to compute turnover:

out <- Return.portfolio(R = returns, weights = monthlyWeights, verbose = TRUE) beginWeights <- out$BOP.Weight endWeights <- out$EOP.Weight txns <- beginWeights - lag(endWeights) monthlyTO <- xts(rowSums(abs(txns[,1:9])), order.by=index(txns)) plot(monthlyTO)

So, the trick is this: when you call Return.portfolio, use the verbose = TRUE option. This creates several objects, among them returns, BOP.Weight, and EOP.Weight. These stand for Beginning Of Period Weight, and End Of Period Weight.

The way that turnover is computed is simply the difference between how the day’s return moves the allocated portfolio from its previous ending point to where that portfolio actually stands at the beginning of next period. That is, the end of period weight is the beginning of period drift after taking into account the day’s drift/return for that asset. The new beginning of period weight is the end of period weight plus any transacting that would have been done. Thus, in order to find the actual transactions (or turnover), one subtracts the previous end of period weight from the beginning of period weight.

This is what such transactions look like for this strategy.

Something we can do with such data is take a one-year rolling turnover, accomplished with the following code:

yearlyTO <- runSum(monthlyTO, 252) plot(yearlyTO, main = "running one year turnover")

It looks like this:

This essentially means that one year’s worth of two-way turnover (that is, if selling an entirely invested portfolio is 100% turnover, and buying an entirely new set of assets is another 100%, then two-way turnover is 200%) is around 800% at maximum. That may be pretty high for some people.

Now, here’s the application when you penalize transaction costs at 20 basis points per percentage point traded (that is, it costs 20 cents to transact $100).

txnCosts <- monthlyTO * -.0020 retsWithTxnCosts <- out$returns + txnCosts compare <- na.omit(cbind(out$returns, retsWithTxnCosts)) colnames(compare) <- c("NoTxnCosts", "TxnCosts20BPs") charts.PerformanceSummary(compare) table.AnnualizedReturns(compare)

And the result:

NoTxnCosts TxnCosts20BPs Annualized Return 0.0587 0.0489 Annualized Std Dev 0.1554 0.1553 Annualized Sharpe (Rf=0%) 0.3781 0.3149

So, at 20 basis points on transaction costs, that takes about one percent in returns per year out of this (admittedly, terrible) strategy. This is far from negligible.

So, that is how you actually compute turnover and transaction costs. In this case, the transaction cost model was very simple. However, given that Return.portfolio returns transactions at the individual asset level, one could get as complex as they would like with modeling the transaction costs.

Thanks for reading.

NOTE: I will be giving a lightning talk at R/Finance, so for those attending, you’ll be able to find me there.

Pingback: Quantocracy's Daily Wrap for 05/11/2016 | Quantocracy

Pingback: Best Links of the Week | Quantocracy

I tried the code with different data sets and restrictions and it work perfectly. well written and easy to follow. Thank you so much for such a nice post

I like this piece of coding. So this means that if we change this line:

`# Our signal is a simple adjusted price over 200 day SMA

signal xts(apply(prices, 2, SMA, n = 200), order.by=index(prices))`

then the backtest can be re-run and tested again?

Would love to see other posts in this way on how to backtest in R

XLF is duplicated on the symbols string list. Maybe replace it with XLI?

You wrote this three years ago. Would quantstrat now replace this?

Absolutely not. They’re different tools entirely. This is for portfolio analysis, quantstrat is signal analysis.