Calculating Portfolio Turnover from Consecutive Weights
Summary
The document discusses calculating average portfolio turnover from beginning-of-period and end-of-period weights. It warns that random return data can produce extreme or unrealistic results because the simulated asset returns include very large losses. In an equal-weight example, turnover is computed from absolute weight changes and averaged across periods, excluding the initial allocation from cash into the portfolio.
For portfolios with changing target weights, the timing of the comparison matters: turnover should compare end-of-period weights with the next period’s beginning weights. The second answer shows an alternative calculation that first adjusts weights for asset and portfolio returns, then averages the absolute changes. Its hand calculation agrees with the portfolio-return package when weights are lagged appropriately. The equal-weight shortcut is described as a sanity check only for equal-weight portfolios, and the examples do not establish a universal turnover convention across all applications.
Key ideas
- Extreme simulated asset returns can make calculated portfolio turnover appear implausibly large.
- Average turnover is calculated from absolute changes in portfolio weights across periods.
- Exclude the initial allocation from cash when following the cited turnover convention.
- For changing target weights, compare end-of-period weights with the next period’s beginning weights.
- The equal-weight sanity check applies only to equal-weight portfolios.
Tags
Full text
# Calculate turnover for portfolio
# Calculate turnover for portfolio
I am trying to calculate the turnover for a portfolio strategy.
First I generate some random data and assign it dates:
```
data <- replicate(6,rnorm(1000))
data <- as.data.frame(data)
dates<-seq(as.Date("1932/09/01"), as.Date("2015/12/01"), by = "1 month",tzone="GMT")-1
rownames(data)<-dates
```
I convert it to xts format:
```
library(xts)
data<-as.xts(data,dateFormat="Date")
```
Then I use the Return.portfolio() function to calculate the rebalanced weights assuming an equal weighted strategy:
```
library(PerformanceAnalytics)
results <- Return.portfolio(data,rebalance_on="months",geometric=F,verbose=T)
```
In order to calculate the turnover I'm assuming that I need the beginning of period weights and end of period weight.
I extract these from the results:
```
bop <- results$BOP.Weight #beginning of period weights
eop <- results$EOP.Weight #end of period weights
```
Then to calculate the turnover I substract `bop` from `eop` and take the absolute value:
```
f<-abs(bop-eop)
```
Finally, to calculate the turnover I use the following formula:
```
sum(f)*(1/(nrow(data)-1))
```
However, when I test this on real-data (where I know what the turnover should be) I get huge, unrealistic numbers with this method.
What am I doing wrong?
My definition of turnover comes from: Demiguel et al Constraining Portfolio Norms http://faculty.london.edu/avmiguel/DeMiguelGarlappiNogalesUppalMS.pdf page 806
The definition is:
## Answer by Kyle Balkissoon (score 5, accepted)
https://quant.stackexchange.com/a/19281
First and foremost you are using bad data. min(data) gets me -3.67 (it's random remember) which would be -367% as in the position went bankrupt and took out two other ones (could be possible in a levered porftolio). However for the sake of an reproducible answer lets use the edhec data set, very little changes to your original code need to be done.
```
library(PerformanceAnalytics)
data(edhec)
results <- Return.portfolio(edhec,rebalance_on="months",verbose=T)
bop <- results$BOP.Weight #beginning of period weights
eop <- results$EOP.Weight #end of period weights
```
There is a potential for error here in the non equal weight case as you are subtracting the beginning of period weights from the end of period weights when it SHOULD be the following period weights. E.g. the market has shifted your weights and you need to reset.
```
f<-abs(bop-eop)
YourTurnover=sum(f)*(1/(nrow(edhec)-1)) # 0.01242465
SanityCheck=sum(abs(eop-1/ncol(edhec)))/(nrow(edhec)-1) #0.01242465
```
Please note that the above definition of turnover is the sum of all the % weight changes divided by number of trading periods. Also the authors do NOT count the initial allocation in their turnover (e.g. the 100% change from cash to investments.) The sanity check is ONLY for the EW case.
An average turnover of 1.24% which appears to be in line for an equal weight strategy.
My suspicion if you are not running an equal weight strategy is the bop-eop line. As it should be EoP weights - next BoP weights.
## Answer by Robert (score 1)
https://quant.stackexchange.com/a/55329
I was trying to compute the turnover when you have the initial monthly weights. Is not so easy to figure out the logic in the package used in the previous answer.
Here I show how to compute the DeMiguel and Nogales (Portfolio Selection with Robust Estimation, Operations Research 57(3), pp. 560–577, 2009) turnover.
```
# 10 last periods and some assets from edhec
edhecselect = c("Emerging Markets","Long/Short Equity","Short Selling","Funds of Funds")
datar = edhec[(nrow(edhec)-9):nrow(edhec),edhecselect]
#nrow(datar)
set.seed(1234) # weights at beginning of the month
ws = data.frame(replicate(ncol(datar),rnorm(nrow(datar),sd=.1))) #weigths according to a rule, here random
ws = ws/rowSums(ws) #sum(ws)=1 rowSums(ws)
colnames(ws)=colnames(datar)
rownames(ws) = index(datar) # you know are bop weights for "hand" calculation
library(xts)
datar<-as.xts(datar,dateFormat="Date")
ws<-as.xts(ws,dateFormat="Date")
```
Compute by hand
```
#Create a function
pturnoverDN = function(weight,rets,port_ret){
weight[is.na(weight)] <- 0 # NAs = 0
weighteop = weight*(1+rets)/(1+port_ret)
dweight = abs(weight-lag(weighteop,1))
out = (rowSums( dweight) )
return(out)
}
PortRet =apply(datar*ws,1,sum) # compute returns
mean(turnovDN <- pturnoverDN(coredata(ws),datar,PortRet),na.rm=T)
#[1] 6.952423
```
Using the `Return.portfolio`function
```
# for Return.portfolio you need dates of previous month
wsrp= ws
rownames(wsrp)<-index(edhec)[(nrow(edhec)-10):(nrow(edhec)-1)]
wsrp<-as.xts(wsrp,dateFormat="Date")
library(PerformanceAnalytics)
results <- Return.portfolio(datar,weights =wsrp,geometric=F,verbose=T)
results$returns # are the same of hand calculation
bop <- results$BOP.Weight #beginning of period weights
eop <- results$EOP.Weight #end of period weights
f<-abs(bop-eop)
(YourTurnover=sum(f)*(1/(nrow(datar)-1))) # 1.140454 Wrong?
toDN<-rowSums(abs(bop-lag(eop,1))) # to get the correct value
mean(toDN[-1]) # 6.952423
#Using the pturnoverDN function
mean(pturnoverDN(bop,datar,as.numeric(results$returns)),na.rm=T) # 6.952423
```Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.