# Backtesting Rebalancing methods

December 15, 2011
By

(This article was first published on Systematic Investor » R, and kindly contributed to R-bloggers)

I wrote about Rebalancing in the Asset Allocation Process Summary post. Deciding how and when to rebalance (update the portfolio to the target mix) is one of the critical steps in the Asset Allocation Process. I want to study the portfolio performance and turnover for the following Rebalancing methods:

• Periodic Rebalancing: rebalance to the target mix every month, quarter, year.
• Maximum Deviation Rebalancing: rebalance to the target mix when asset weights deviate more than a given percentage from the target mix.
• Maximum Deviation Rebalancing: rebalance half-way to the target mix when asset weights deviate more than a given percentage from the target mix.

I will study a very simple target mix: 50% allocation to equities (SPY) and 50% allocation to fixed income (TLT).

First, let’s examine Periodic Rebalancing. Following code implements this strategy using the backtesting library in the Systematic Investor Toolbox:

```# Load Systematic Investor Toolbox (SIT)
setInternet2(TRUE)
con = gzcon(url('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', 'rb'))
source(con)
close(con)

#*****************************************************************
#******************************************************************
tickers = spl('SPY,TLT')

data <- new.env()
getSymbols(tickers, src = 'yahoo', from = '1900-01-01', env = data, auto.assign = T)
bt.prep(data, align='remove.na', dates='1900::2011')

#*****************************************************************
# Code Strategies
#******************************************************************
prices = data\$prices
nperiods = nrow(prices)
target.allocation = matrix(c(0.5, 0.5), nrow=1)

data\$weight[] = NA
data\$weight[1,] = target.allocation
capital = 100000
data\$weight[] = (capital / prices) * data\$weight

# Rebalance periodically
models = list()
for(period in spl('months,quarters,years')) {
data\$weight[] = NA
data\$weight[1,] = target.allocation

period.ends = endpoints(prices, period)
period.ends = period.ends[period.ends > 0]
data\$weight[period.ends,] = repmat(target.allocation, len(period.ends), 1)

capital = 100000
data\$weight[] = (capital / prices) * data\$weight
models[[period]] = bt.run(data, type='share', capital=capital)
}

#*****************************************************************
# Create Report
#******************************************************************
plotbt.custom.report(models)

# Plot BuyHold and Monthly Rebalancing Weights
layout(1:2)
abline(h=50)
plotbt.transition.map(models\$months\$weight, 'months', spl('red,orange'))
abline(h=50)

# helper function to create barplot with labels
barplot.with.labels <- function(data, main, plotX = TRUE) {
par(mar=c( iif(plotX, 6, 2), 4, 2, 2))
x = barplot(100 * data, main = main, las = 2, names.arg = iif(plotX, names(data), ''))
text(x, 100 * data, round(100 * data,1), adj=c(0.5,1), xpd = TRUE)
}
# Plot Portfolio Turnover for each Rebalancing method
layout(1)
barplot.with.labels(sapply(models, compute.turnover, data), 'Average Annual Portfolio Turnover')
```

As expected, the Buy and Hold strategy has 0 turnover, but deviates a lot from the Target Mix. On the other hand, Monthly Rebalancing keeps portfolio close to the Target Mix, but at the cost of the 36% annual turnover.

Next to trigger Rebalancing based on the Maximum Deviation from the Target Mix, I created a function, bt.max.deviation.rebalancing, that iteratively searches for a violation of Maximum Deviation and forces the rebalancing once the violation took place. This function can rebalance all the way to the target mix or partially based on the rebalancing.ratio. For example, for rebalancing.ratio equal to 0, each time the violation of Maximum Deviation is found, the portfolio is rebalanced all the way to the target mix, and for rebalancing.ratio equal to 0.5, the portfolio is rebalanced half way to the target mix.

```# Rebalancing method based on maximum deviation
bt.max.deviation.rebalancing <- function
(
data,
model,
target.allocation,
max.deviation = 3/100,
rebalancing.ratio = 0	# 0 means rebalance all-way to target.allocation
# 0.5 means rebalance half-way to target.allocation
)
{
start.index = 1
nperiods = nrow(model\$weight)
action.index = rep(F, nperiods)

while(T) {
# find rows that violate max.deviation
weight = model\$weight
index = which( apply(abs(weight - repmat(target.allocation, nperiods, 1)), 1, max) > max.deviation)

if( len(index) > 0 ) {
index = index[ index > start.index ]

if( len(index) > 0 ) {
action.index[index[1]] = T

data\$weight[] = NA
data\$weight[1,] = target.allocation

temp = repmat(target.allocation, sum(action.index), 1)
data\$weight[action.index,] = temp +
rebalancing.ratio * (weight[action.index,] - temp)

capital = 100000
data\$weight[] = (capital / data\$prices) * data\$weight
model = bt.run(data, type='share', capital=capital, silent=T)
start.index = index[1]
} else break
} else break
}
return(model)
}
```

Now, let’s compare 5% Maximum Deviation Rebalancing to the Periodic Rebalancing.

```	#*****************************************************************
# Code Strategies that rebalance based on maximum deviation
#******************************************************************

# rebalance to target.allocation when portfolio weights are 5% away from target.allocation
models\$smart5.all = bt.max.deviation.rebalancing(data, buy.hold, target.allocation, 5/100, 0)

# rebalance half-way to target.allocation when portfolio weights are 5% away from target.allocation
models\$smart5.half = bt.max.deviation.rebalancing(data, buy.hold, target.allocation, 5/100, 0.5)

#*****************************************************************
# Create Report
#******************************************************************
# Plot BuyHold, Years and Max Deviation Rebalancing Weights
layout(1:4)
abline(h=50)
plotbt.transition.map(models\$smart5.all\$weight, 'Max Deviation 5%, All the way', spl('red,orange'))
abline(h=50)
plotbt.transition.map(models\$smart5.half\$weight, 'Max Deviation 5%, Half the way', spl('red,orange'))
abline(h=50)
plotbt.transition.map(models\$years\$weight, 'years', spl('red,orange'))
abline(h=50)

# Plot Portfolio Turnover and Maximum Deviation for each Rebalancing method
layout(1:2)
barplot.with.labels(sapply(models, compute.turnover, data), 'Average Annual Portfolio Turnover', F)
barplot.with.labels(sapply(models, compute.max.deviation, target.allocation), 'Maximum Deviation from Target Mix')

# Plot Strategy Statistics  Side by Side
plotbt.strategy.sidebyside(models)
```

As expected, the Maximum Deviation Rebalancing keeps portfolio close to the Target Mix. Moreover, the Maximum Deviation Rebalancing strategy has lower turnover than the Monthly Rebalancing. So in this case the Maximum Deviation Rebalancing wins over Periodic Rebalancing method.

To view the complete source code for this example, please have a look at the bt.rebalancing.test() function in bt.test.r at github.

R-bloggers.com offers daily e-mail updates about R news and tutorials on topics such as: Data science, Big Data, R jobs, visualization (ggplot2, Boxplots, maps, animation), programming (RStudio, Sweave, LaTeX, SQL, Eclipse, git, hadoop, Web Scraping) statistics (regression, PCA, time series, trading) and more...

Tags: , , , , ,