Momentum in R: Part 4 with Quantstrat

The past few posts on momentum with R focused on a relatively simple way to backtest momentum strategies. In part 4, I use the quantstrat framework to backtest a momentum strategy. Using quantstrat opens the door to several features and options as well as an order book to check the trades at the completion of the backtest.

I introduce a few new functions that are used to prep the data and compute the ranks. I won’t go through them in detail, these functions are available in my github repo in the rank-functions folder.

This first chunk of code just loads the necessary libraries, data, and applies the ave3ROC function to rank the assets based on averaging the 2, 4, and 6 month returns. Note that you will need to load the functions in Rank.R and monthly-fun.R.

```library(quantstrat)
library(PerformanceAnalytics)

currency("USD")
symbols <- c("XLY", "XLP", "XLE", "AGG", "IVV")
stock(symbols, currency="USD")

# get data for the symbols
getSymbols(symbols, from="2005-01-01", to="2012-12-31")

# create an xts object of monthly adjusted close prices
symbols.close <- monthlyPrices(symbols)

# create an xts object of the symbol ranks
sym.rank <- applyRank(x=symbols.close, rankFun=ave3ROC, n=c(2, 4, 6))```

Created by Pretty R at inside-R.org

The next chunk of code is a critical step in preparing the data to be used in quantstrat. With the ranks computed, the next step is to bind the ranks to the actual market data to be used with quantstrat. It is also important to change the column names to e.g. XLY.Rank because that will be used as the trade signal column when quantstrat is used.

```# this is an important step in naming the columns, e.g. XLY.Rank
# the "Rank" column is used as the trade signal (similar to an indicator)
# in the qstratRank function

# ensure the order of order symbols is equal to the order of columns
# in symbols.close

# bind the rank column to the appropriate symbol market data
# loop through symbols, convert the data to monthly and cbind the data
# to the rank
for(i in 1:length(symbols)) {
x <- get(symbols[i])
x <- to.monthly(x,indexAt='lastof',drop.time=TRUE)
indexFormat(x) <- '%Y-%m-%d'
colnames(x) <- gsub("x",symbols[i],colnames(x))
x <- cbind(x, sym.rank[,i])
assign(symbols[i],x)
}```

Created by Pretty R at inside-R.org

Now the backtest can be run. The function qstratRank is just a convenience function that hides the quantstrat implementation for my Rank strategy.

For this first backtest, I am trading the top 2 assets with a position size of 1000 units.

```# run the backtest
bt <- qstratRank(symbols=symbols, init.equity=100000, top.N=2,
max.size=1000, max.levels=1)

# chart of returns
charts.PerformanceSummary(bt\$returns[,"total"], geometric=FALSE,
wealth.index=TRUE, main="Total Performance")```

Created by Pretty R at inside-R.org

Changing the argument to max.levels=2 gives the flexibility of “scaling” in a trade. In this example, say asset ABC is ranked 1 in the first month — I buy 500 units. In month 2, asset ABC is still ranked 1 — I buy another 500 units.

```# run the backtest
bt <- qstratRank(symbols=symbols, init.equity=100000, top.N=2,
max.size=1000, max.levels=2)

# chart of returns
charts.PerformanceSummary(bt\$returns[,"total"], geometric=FALSE,
wealth.index=TRUE, main="Total Performance")```

Created by Pretty R at inside-R.org

Full code available here: quantstrat-rank-backtest.R

19 thoughts on “Momentum in R: Part 4 with Quantstrat”

1. Hi,

I apologize for this dump. I have not tried to debug why this is occuring. It is probably something I have done. Nonetheless, I thought I would send it along, just in case it makes quicksense to you.

George

Error in if ((orderqty + pos) < PosLimit[, "MaxPos"]) { :
argument is of length zero
Time difference of 0.254015 secs
Error in ifelse(is.na(tmpPL\$Pos.Qty) & !is.na(tmpPL\$Pos.Qty.1), tmpPL\$Pos.Qty.1, :
dims [product 97] do not match the length of object [0]
In is.na(tmpPL\$Pos.Qty.1) :
is.na() applied to non-(list or vector) of type 'NULL'

• Hi George,

I’m afraid I’m not able to tell much from the error message. Can you post all your code in a gist and send a link?

• daniel says:

Same error

• daniel says:

Error in if ((orderqty + pos) < PosLimit[, "MaxPos"]) { :
argumento tiene longitud cero

• daniel says:

Hi guys,

The problem is the time zone specification. It can be solved including this at the beggining of the script.

AGG.Open AGG.High AGG.Low AGG.Close AGG.Volume AGG.Adjusted AGG.Rank
2005-01-31 102.34 103.08 102.06 102.90 2301700 74.50 NA
2005-02-28 102.50 103.50 102.06 102.21 2129800 74.22 NA
2005-03-31 101.69 102.27 100.25 100.93 3359900 73.50 NA
2005-04-30 101.24 102.49 100.72 102.32 2768100 74.76 NA
2005-05-31 102.09 102.90 101.51 102.83 2653800 75.38 NA
2005-06-30 102.69 103.47 102.20 103.38 2735500 76.04 NA

With the time zone issue the stock data is incorrect:

AGG.Open AGG.High AGG.Low AGG.Close AGG.Volume AGG.Adjusted AGG.Rank
2005-01-31 102.34 103.08 102.06 102.90 2301700 74.50 NA
NA NA NA NA NA
2005-02-28 102.50 103.50 102.06 102.21 2129800 74.22 NA
NA NA NA NA NA

• daniel says:

Include this at the beggining of the script

ttz<-Sys.getenv('TZ')
Sys.setenv(TZ='UTC')

Regards

• Thanks Daniel, works fine after including:

ttz<-Sys.getenv('TZ')
Sys.setenv(TZ='UTC')

and sorry about not including the line that caused the error in my original post. I am generally more observant and complete. I was not cruel by intent, only by omission and carelessness.

George

2. Horst R. Wolf says:

Error in addOrder(portfolio = portfolio, symbol = symbol, timestamp = timestamp, :
order at timestamp 2012-12-29 must not have price of NA

• Hi Horst,

Could you please provide some reproducible code so that I can replicate your error?

Thanks,
Ross

• Horst R. Wolf says:

Horst R. Wolf e mail horstrwolf@msn.com

Date: Fri, 22 Feb 2013 11:55:37 +0000 To: horstrwolf@msn.com

• Hi Horst,

I was able to run and rerun my code without any errors. It is likely that any changes you made are causing the error. Without seeing those changes, it is tough for me to troubleshoot the error.

Ross

3. daniel says:

Hi Ross,

I haven’t had much time to work with the quantstrat package, but it seems that the quantstrtat version of your strategy provides different stadistics than the plain version (Momentum in R: Part 3). Using the same lookback periods (6,9,12) with ave3ROC function, and selecting the top 4 best assets, the quantstrat version underperform the plain versión. Surely I missing sth. Any thoughts?

Daniel

• Hi Daniel,

The backtest in part 3 is simplified and just uses the 1 period simple returns on the overall return calculation. Using quantstrat, the returns are based on actual transactions (e.g. buy 1000 shares of IVV at 141.88). This is the reason why the backtest statistics are different.

• daniel says:

Ok Ross,

I see what you mean, thank you very much!

4. daniel says:

Hi Ross,

I’m checking the symbol.rank given for the AGG symbol (for instance), and the order execution of the order book, and it seems that there is a look ahead bias in the strategy execution. You haven’t apply any lag to the symbol.rank signals. I enclose you what can be found in the documentation:

Default behaviour of appplyStrategy:
=> for monthly, quarterly and yearly data, quantstrat will use the current bar to get the price.
=> for anything with a higher frequency (eg. weekly, daily, intraday), quantstrat will use the next bar to get the price.”

Did you have this into account?

Thx and best regards
Daniel

• Hi Daniel,

Great question, I am glad to see you have been exploring this in detail.

Let us look at XLE.
```> tail(XLE) XLE.Open XLE.High XLE.Low XLE.Close XLE.Volume XLE.Adjusted XLE.Rank 2012-07-31 66.37 70.68 64.64 69.65 291007200 68.99 4 2012-08-31 69.91 73.03 68.16 71.53 212709700 70.85 5 2012-09-30 71.46 77.35 70.40 73.43 206863800 73.06 1 2012-10-31 73.94 75.19 71.02 71.94 236902800 71.58 1 2012-11-30 71.66 73.06 67.77 71.06 234855900 70.70 2 2012-12-31 71.60 73.39 69.57 71.42 198245000 71.42 3```

We would expect a signal to be generated on 2012-09-30 to buy XLE at 73.43.

```> tail(getOrderBook("Rank")\$Rank\$XLE)[,1:2] Order.Qty Order.Price 2012-05-30 19:00:08 "all" "63.63" 2012-06-29 19:00:08 "all" "66.37" 2012-07-30 19:00:08 "all" "69.65" 2012-08-30 19:00:08 "all" "71.53" 2012-09-29 19:00:08 "1000" "73.43" 2012-12-30 18:00:08 "all" "71.42" ```

That makes sense and we see an order for 1000 shares at 73.43. That doesn’t mean the trade was executed at that price. What was it actually executed at? You can look at what is printed from applyStrategy or get the transactions with getTxns.
```> tail(getTxns("Rank", "XLE"))[,1:2] Txn.Qty Txn.Price 2009-11-29 18:00:08 -1000 57.01 2010-10-30 19:00:08 1000 62.71 2011-06-29 19:00:08 -1000 76.45 2011-11-29 18:00:08 1000 69.13 2011-12-30 18:00:08 -1000 70.69 2012-09-29 19:00:08 1000 71.94```

We can see here that the trade is actually executed at 71.94, the closing price of next month. So there is a lag of one month before the trade is executed.

• daniel says:

Ok Ross, I didn’t know about getTxns() function. Now it’s clear. Thank you very much for the support and congratulations again for this impressive job!
Best regards.

5. Aaron says:

I’m getting the following error in the very first part that is preventing me from proceeding:

> symbols.close <- monthlyPrices(symbols)
Error: could not find function "monthlyPrices"

All the prior code works fine.

• Hi Aaron,

The monthlyPrices() function as well as a few other functions in that post are on my github repo and you can access that here: https://github.com/rbresearch/Ranking

That should take care of the error you were getting, let me know if you have any other questions.

Ross