In finance and investing the term portfolio refers to the collection of assets one owns. Compared to just holding a single asset at a time a portfolio has a number of potential benefits. A universe of asset holdings within the portfolio gives a greater access to potentially favorable trends across markets. At the same time the expected risk and return of the overall holding is subject to its specific composition.

A previous post on this blog illustrated a portfolio selection methodology that screens a universe of possible assets. The object there was to find a particular combination that reduces the expected risk-return ratio (sharp-ratio). The assumption there was an underlying buy-and-hold strategy, that once a particular portfolio is selected its composition is hold static until the final position unwound.

This post concerns with dynamic portfolio trading strategies where the portfolio is periodically rebalanced. At prescribed intervals the composition of the portfolio is changed by selling out existing holdings and buying new assets. Typically it is assumed that reallocation does not change the overall portfolio value, with the possible exception of losses due to transaction costs and fees. Another classification concerning this type of strategies is if borrowings are allowed (long-short portfolio) or if all relative portfolio weights are restricted to be positive (long-only portfolio).

Following closely recent publications by Steven Hoi and Bin Li we first set the stage by illustrating fundamental portfolio mechanics and then detailing their passive aggressive mean reversion strategy. We test drive the model in R and test it with 1/2011-11/2012 NYSE and NASDAQ daily stock data.

# Portfolio Trading Terminology

Let’s say we want to invest a notional amount of say into a portfolio and record each day the total portfolio value . The portfolio is composed of members with a per share dollar price of . Assuming we hold shares of each component, then the relative portfolio weight for each member is

.

The dollar delta expresses the value of each stock holding. Since the aggregated portfolio value is the sum over its components , the relative portfolio weights are always normalized to

# Buy and Hold

An instructive example is to look at ideal perfectly antithetic pair of artificial price paths. Both assumes assets alternate between a *100%* and *50%* return. A static portfolio would not experience any growth after a full round trip is completed. The column shows the individual relative open to close or close to next day’s close returns.

# Dollar Cost Averaging

Dollar cost averaging is a classic strategy that takes advantage of periodic mean reversion. After each close rebalance the portfolio by distributing equal dollar amounts between each component. After the rebalance trades each delta dollar position in the portfolio is the same at the open for each component. Rebalancing itself does not change the total portfolio value. Expressed in terms of the relative portfolio weights dollar cost averaging is the allocation strategy to rebalance the weight back to the uniform value of the inverse number of components inside the portfolio.

Dollar cost averaging:

# Perfect Prediction

With the benefit of perfect foresight its is possible to allocate all weights into the single asset with highest predicted gain.

# Mean Reversion Strategy

The one period portfolio return is given by the product of relative weights and component relative returns as

The task of the trading strategy is to determine the rebalancing weights for the opening position of day given yesterdays relative returns at the close .

A forecast of a positive trend would be that today’s returns are a repeat of yesterdays . In contrast mean reversion would assume that today’s returns are the inverse of yesterdays .

The following strategy consists of determining the weights such that a positive trend assumption would lead to a loss with threshold parameter .

Considering returns with

Typically the relative performance vector consists of a mix of growing and retracting assets. Then we have a balanced number of positive and negative components within the vector. The condition that the product chooses an weight vector that is perpendicular to and lies on the unit simplex .

Now assume a mean reverting process that consists of a rotation of performances within the asset vector. Graphically the corresponds to a reflection at a degree axis. After rotation the angle between the the weight vector and the new performance vectors reduces. This results in an increase of the portfolio return value.

Thus setting for example generates a flat portfolio in case the performance vector does not change while it increases the portfolio value in the case of an actual mean reversion.

The strategy implicitly assumes a price process that rotates between outperforming and lagging components within the portfolio. This does not include the situation where all stocks are out or underperforming at the same time. The long-only portfolio strategy relies on the ability to be able to reallocate funds between winners and losers.

For the two share example we have

using and

The table below shows the result of computing the portfolio weights at the open of day 2 according to above formulas.

With more than two assets the loss condition together with the normalization is not sufficient to uniquely determine the portfolio weights. The additional degrees of freedoms can be used to allow the posing of additional criteria one wishes the strategy to observe. A good choice seems to be requiring that the new portfolio weights are close to the previous selection, as this should minimize rebalance transaction costs.

Minimizing the squared distance between the weights under the normalization and loss constraint leads to the following expression for the new weight

as a function of the previous portfolio weights , the return vector and the average return across the portfolio components .

The control gain is taken as the ratio of the access loss over the return variance

# Simulation

This post contains the R code to test the mean reversion strategy. Before running this the function block in the appendix need to be initialized. The following code runs the artificial pair of stocks as a demonstration of the fundamental strategy

# create the antithetic pair of stocks and publish it into the environment # generate sequence of dates times <- seq(as.POSIXct(strptime('2011-01-1 16:00:00', '%Y-%m-%d %H:%M:%S')), as.POSIXct(strptime('2011-12-1 16:00:00', '%Y-%m-%d %H:%M:%S')), by="1 months") # generate and store dummy price series = 200,100,200,100,200 ... prices.xts <- xts(rep(c(200,100),length(times)/2), order.by=as.POSIXct(times)) stk <- 'STK1' colnames(prices.xts) <- paste(stk,'.Adjusted',sep="") assign(x=stk, value=prices.xts, envir=global.env ) # generate and store dummy price series = 100,200,100,200,100 ... prices.xts <- xts(rep(c(100,200),length(times)/2), order.by=as.POSIXct(times)) stk <- 'STK2' colnames(prices.xts) <- paste(stk,'.Adjusted',sep="") assign(x=stk, value=prices.xts, envir=global.env ) # run the strategy and create a plot strategy(portfolio= c("STK1","STK2"),threshold=.9,doplot=TRUE,title="Artificial Portfolio Example")

As a benchmark I like always to testing against a portfolio of DJI member stocks using recent 2011 history. The strategy does not do very well for this selection of stocks and recent time frame.

As an enhancement screening stocks by ranking according to their most negative single one-day auto-correlation. The 5 best ranked candidates out of the DJI picked this way are “TRV”,XOM”,”CVX”,PG” and “JPM” .

Rerunning the strategy on the sub portfolio of these five stocks gives a somewhat better performance.

Again, the following code is use to load DJI data from Yahoo and run the strategy

portfolio.DJI <- getIndexComposition('^DJI') getSymbols(portfolio.DJI,env=global.env) strategy(portfolio.DJI,.9,TRUE,"2011-01-01::2012-11-01",title="Portfolio DJI, Jan-2011 - Nov 2012") strategy(c("TRV",XOM","CVX",PG","JPM"),.9,TRUE,"2011-01-01::2012-11-01",title="Portfolio 5 out of DJI, Jan-2011 - Nov 2012")

Next a screening a universe of about 5000 NYSE and NASDAQ stocks for those with the best individual auto-correlation within the 2011 time frame. Then running the strategy for 2011 and out of sample in Jan-Nov 2012, with either the 5 or the 100 best names. The constituent portfolio consists of “UBFO”,”TVE”,”GLDC”,”ARI”,”CTBI”.

# Discussion

We test drove the passive aggressive trading strategy on recent daily price data. While not doing very well on a mainstream ticker like DJI, pre-screening the stock universe according to a auto-correlation did improve results, even when running the strategy out of sample for the purpose of cross validation. Other extensions, like including a cash asset or allowing for short positions require further investigation.

The strategy has interesting elements, such as minimizing the chance in the portfolio weights and the use of a loss function. It does only directly take the immediate one-period lag into account. For use in other time domains, like high frequency, one could consider having a collection of competing trading agents with a range of time lags that filter out the dominate mean reverting mode.

There are a verity of approaches in the literature that I like to demonstrate as well in the framework of this blog, stay posted.

# Appendix: R functions

require(tawny) require(quantmod) require(ggplot2) # global market data environment global.env <- new.env() # search anti correlated stocks # also return daily variance and maximum one day variance portfolio.screen <- function(portfolio, daterange="") { ac <- matrix() for(stk in portfolio) { p.xts <- Ad(get(stk,global.env)[daterange]) dr.xts <- dailyReturn(p.xts) dr.maxvar <- max( (dr.xts-mean(dr.xts) )^2) dr.var <- coredata(var(dr.xts)) dr.var.ratio <- dr.maxvar/(dr.var) dr.ac <- coredata( acf(dr.xts)$acf[1:5] ) dr.ac.res <- c(stk, dr.var.ratio, dr.maxvar, dr.var, dr.ac) if(length(ac)==1) { ac <- dr.ac.res } else { ac <- rbind(ac, dr.ac.res) } } # sort by decreasing auto-correlations ac_sort <- (ac[order(as.numeric(ac[,6]),decreasing='FALSE'),]) # return sorted matrix of stocks and correlations ac_sort } ############################################################################# # portfolio weight vector b # price relative return vector x # threshold parameter e, usually e <= 1 for # returns intrinsic value of call on portfolio return with strike e strategy.loss <- function(b, x, e) { max(b%*%x-e,0) } strategy.gearing <- function(b, x, e) { loss <- strategy.loss(b,x,e) varx <- var(x) if(varx>0) { -loss/varx } else { 0} } # return a vector w so that w is close to v # by minimizing the distance (w-v)^2 # with the condition that w is on the simplex # sum(w)=1 strategy.simplex <- function(v,z) { # initialize projection to zero w <- rep(0, length(v) ) # Sort v into u in descending order idx <- order(v,decreasing=TRUE) u <- v[idx] #index of number of non zero elements in w rho <-max(index(u)[index(u)*u >= cumsum(u) -z]) if(rho>0) { theta <-(sum(u[1:rho])-z)/rho w[idx[1:rho]] <- v[idx[1:rho]] - theta; } w } strategy.rebalance <- function(b,x,e) { dbdx <- strategy.gearing <- strategy.gearing(b, x, e) b_new <- b + dbdx*(x-mean(x)) b_new <- strategy.simplex(b_new,1) } ############################################ ################################### # Simulate the portfolio trading strategy # Arguments: # portfolio : portfolio of stocks f # threshold: mean reversion loss parameter ( should be in the order but smaller than 1) # doplot: create an optional plot # daterange: range of dates to test # # returns the accumulated gain of the strategy # strategy <- function(portfolio, threshold=1, doplot=FALSE,daterange="",title="" ) { portfolio.prices.xts <- xts() # raw portfolio components price time series for( stk in portfolio) { portfolio.prices.xts <- cbind(portfolio.prices.xts, Ad(get(stk,envir = global.env)[daterange])) } # downfill missing closing prices (last observation carried forward) portfolio.prices.xts <- na.locf(portfolio.prices.xts) # not backfill missing history backwards, for example for IPO assets that did # not exist, we set these to the constant initial 'IPO' price , resembling a cash asset portfolio.prices.xts <- na.locf(portfolio.prices.xts,fromLast=TRUE) times <- index(portfolio.prices.xts) nprices <- NROW(portfolio.prices.xts) # relative prices S(t)/S(t-1) portfolio.price.relatives.xts <- (portfolio.prices.xts/lag(portfolio.prices.xts,1))[2:nprices,] # initialize portfolio weights time series portfolio.weights.xts <- xts(matrix(0,ncol=length(portfolio),nrow=nprices-1), order.by=as.POSIXct(times[2:nprices])) colnames(portfolio.weights.xts) = portfolio # initialize strategy with equal weights portfolio.weights.xts[1,] = rep(1/length(portfolio),length(portfolio)) # run strategy: for( i in 1:(nprices-2)) { #portfolio weights at opening of the day b_open <- portfolio.weights.xts[i,] #price relative return at close x = S(t)/S(t-1) x_close <- portfolio.price.relatives.xts[i,] # compute end of day rebalancing portfolio with strategy b_new <- strategy.rebalance(as.vector(b_open),as.vector(x_close),threshold) # assign portfolio composition for next day opening portfolio.weights.xts[i+1,] <- b_new } #aggregate portfolio returns portfolio.total.relatives.xts <- xts( rowSums(portfolio.weights.xts * portfolio.price.relatives.xts), order.by=as.POSIXct(times[2:nprices])) portfolio.cum.return.xts <- cumprod( portfolio.total.relatives.xts) # cummulative wealth gained by portfolio if(doplot==TRUE) { Times <- times df<-data.frame(Date=Times, Performance=c(1,coredata(portfolio.cum.return.xts)), pane='Portfolio') myplot <- ggplot()+theme_bw()+ facet_grid (pane ~ ., scale='free_y')+ labs(title=title) + ylab("Cumulative Performance ") + geom_line(data = df, aes(x = Date, y = Performance), color="blue") for(i in 1:length(portfolio)) { df1<-data.frame(Date=Times, Performance=c(1,coredata(cumprod(portfolio.price.relatives.xts[,i] ) ) ),pane='Components') names(df1)[2] <- "Performance" myplot <- myplot + geom_line(data = df1, aes_string(x = "Date", y = "Performance"),color=i) df3<-data.frame(Date=Times, Performance=c(1,coredata(cumprod(portfolio.price.relatives.xts[,i] ) ) ), pane='Components') names(df1)[2] <- "Performance" myplot <- myplot + geom_line(data = df1, aes_string(x = "Date", y = "Performance"),color=i) df2<-data.frame(Date=Times[1:NROW(Times)-1], Weights=coredata(portfolio.weights.xts[,i]) , pane='Weights') names(df2)[2] <- "Weights" myplot <- myplot + geom_line(data = df2, aes_string(x = "Date", y = "Weights"),color=i) } print(myplot) } # end of plotting # return performance last(portfolio.cum.return.xts) }

Hi,

Nice post and idea. My comment is that it does seem to work when you hand-pick the most suitable 5 out of the 5000. However, if you have a look at the actual symbols (via “google finance” for example) you will see that the high return are actually a compensation for high risk. Few symbols for this portfolio (e.g. “GLDC”) have days wtn zore trades.

(1) This has implication on the calculaton of the autocorrealtion and on

(2) fees and likage (which with this kind of rebalance can be substuntial).

(3) Another issue is that when you rebalance, if some “real changes” occur, you might end up holding just because there is no buyers, so heavy illiquidity risk.

(4) Also, capital for few of these symbols is low, which can make one feel unease trading these, even if the rebalance is frequent.

My main bullet points are that the high returns presented should

(1) be scaled down and

(2) interperted as a compensation for proper underlying risks.\

Again, great post and code.

thanks

Eran

Eran, thanks for your comments and valid points. Including risk / growth measures ( like sharp ratio), draw-downs, transaction costs, liquidity limits would be interesting and make the study more realistic. Also screening stocks for trading volume to remove names that can’t be traded in size is a good idea. One thing I did not mention in the post is that when screening for stocks I did already remove those with large one-day jumps that also skew the autocorrelation, but I did not further hand-pick stocks in order not to further bias the results.

Thank you for the article.

Just one question, maybe I am silly, but you define bi(t) as P(t) / [di(t)*Si(t)], should it not be the other way around, like bi(t) = di(t)*Si(t) / P(t), because if P(t)=1000 and d1(t)*S1(t)=100, then b1(t)=10, and SUM(bi) 1.

Sergey: thank you for catching this, I now changed the equation in the text into the correct ratio, so that bi(t) = [di(t)*Si(t)]/P(t) = $10/$1000 = 0.1 as you would expect it.

Hello, I maintain a R package (logopt) that implements some less traditional portfolio algorithms. PAMR has been on my todo list. As a minimum I would use your code as a reference implementation, but using (part of) it directly could also be a solution. However I did not see any explicit permission yet. Do you have an idea of which licence you would like to have attached to your code?

I am also having a blog detailing what can be done with logopt at http://optimallog.blogspot.com.

Thanks

Hi Marc, thanks for your inquiry. Looks like you are doing interesting work with your R package. You are welcome to make use of the R code from this posting in your package. If you do so, I would appreciate an acknowledgement.

My implementation is an adaptation of the PARM algorithm that has been published by the NTU Singapore team. I’m not associated with them and I don’t know if they maintain any copyright restriction on the algorithm itself.

Cheers