Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
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
The dollar delta
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
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
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
A forecast of a positive trend would be that today’s returns are a repeat of yesterdays
The following strategy consists of determining the weights
Considering returns
Typically the relative performance vector
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
Thus setting for example
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
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 control gain
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) }
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.