Portfolio Trading
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 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) }
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.