Momentum with R: Part 1

Time really flies… it is hard to believe that it has been over a month since my last post. Work and life in general have consumed much of my time lately and left little time for research and blog posts. Anyway, on to the post!

This post will be the first in a series of to cover a momentum strategy using R.

One of my favorite strategies is a momentum or relative strength strategy. Here are just a few of the reasons why I like momentum:

  1. Simple to implement
  2. Long only or long/short portfolios
  3. Many ways to define the strength or momentum measure
  4. It just works

Also, a momentum strategy lends itself well to potential for diversification. The universe of instruments can be infinite, but the instruments traded are finite. Think about it this way… Investor A looks at 10 instruments and invests $1000 in the top 5 instruments ranked by momentum. Investor B looks at 100 instruments and invests $1000 in the top 5 instruments ranked by momentum. Investor A is limiting his potential for diversification by only having a universe of 10 instruments. Investor B has a much larger universe of instruments and can in theory be more diversified. Theoretically speaking, you can trade an infinite number of instruments with a finite amount of trading capital using a momentum or relative strength strategy.

Check out these links for further reading

In this first post of the series on momentum, I will go over some of the basic setup and functions we will be using.

The first step is to get data from yahoo.

require(quantstrat)

#Load ETFs from yahoo
currency("USD")
symbols = c("XLY", "XLP", "XLE", "XLF")
stock(symbols, currency="USD",multiplier=1)
getSymbols(symbols, src='yahoo', index.class=c("POSIXt","POSIXct"), from='2000-01-01')

#Convert to monthly and drop all columns except Adjusted Close
for(symbol in symbols) {
  x <- get(symbol)
  x <- to.monthly(x,indexAt='lastof',drop.time=TRUE)
  indexFormat(x) <- '%Y-%m-%d'
  colnames(x) <- gsub("x",symbol,colnames(x))
  x <- x[,6] #drops all columns except Adjusted Close which is 6th column
  assign(symbol,x)
}

Note that the for loop converts the data to monthly and subsets the data so that the only column we keep is the adjusted close column. We now have four objects (XLY, XLP, XLE, XLF) that have the Adjusted Close price.

> head(XLE)
           XLE.Adjusted
2000-01-31        22.89
2000-02-29        21.92
2000-03-31        24.56
2000-04-30        24.19
2000-05-31        27.04
2000-06-30        25.55

The next step is to merge these four objects into a single object holding the Adjusted Close price. We can do this in a simple one-liner in R!

#merge the symbols into a single object with just the close prices
symbols_close <- do.call(merge, lapply(symbols, get))
> head(symbols_close)
           XLY.Adjusted XLP.Adjusted XLE.Adjusted XLF.Adjusted
2000-01-31        24.06        18.36        22.89        18.09
2000-02-29        22.72        16.22        21.92        16.15
2000-03-31        25.89        16.77        24.56        19.04
2000-04-30        25.35        17.65        24.19        19.22
2000-05-31        23.98        18.92        27.04        19.65
2000-06-30        22.68        19.98        25.55        18.70

For the factor that will be ranked, I will use the 3 period rate of change (ROC).

#xts object of the 3 period ROC of each column in the close object
#The 3 period ROC will be used as the ranking factor
roc <- ROC(symbols_close, n = 3, type = "discrete")
> head(roc)
           XLY.Adjusted XLP.Adjusted XLE.Adjusted XLF.Adjusted
2000-01-31           NA           NA           NA           NA
2000-02-29           NA           NA           NA           NA
2000-03-31           NA           NA           NA           NA
2000-04-30   0.05361596  -0.03867102   0.05679336   0.06246545
2000-05-31   0.05545775   0.16646116   0.23357664   0.21671827
2000-06-30  -0.12398610   0.19141324   0.04030945  -0.01785714

Then we apply the rank function across each row of the roc object.

#xts object with ranks
#symbol with a rank of 1 has the highest ROC
r <- as.xts(t(apply(-roc, 1, rank)))

 

> head(r)
           XLY.Adjusted XLP.Adjusted XLE.Adjusted XLF.Adjusted
2000-01-31            1            2            3            4
2000-02-29            1            2            3            4
2000-03-31            1            2            3            4
2000-04-30            3            4            2            1
2000-05-31            4            3            1            2
2000-06-30            4            1            2            3

That will wrap up this first post for a quick and easy way to rank assets based on 3 month simple returns. Future posts will explore other methods for ranking and using quantstrat to backtest momentum.

Here is the code in full.

require(quantstrat)

#Load ETFs from yahoo
currency("USD")
symbols = c("XLY", "XLP", "XLE", "XLF")
stock(symbols, currency="USD",multiplier=1)
getSymbols(symbols, src='yahoo', index.class=c("POSIXt","POSIXct"), from='2000-01-01')

#Convert to monthly and drop all columns except Adjusted Close
for(symbol in symbols) {
  x <- get(symbol)
  x <- to.monthly(x,indexAt='lastof',drop.time=TRUE)
  indexFormat(x) <- '%Y-%m-%d'
  colnames(x) <- gsub("x",symbol,colnames(x))
  x <- x[,6] #drops all columns except Adjusted Close which is 6th column
  assign(symbol,x)
}

#merge the symbols into a single object with just the close prices
symbols_close <- do.call(merge, lapply(symbols, get))

#xts object of the 3 period ROC of each column in the close object
#The 3 period ROC will be used as the ranking factor
roc <- ROC(symbols_close, n = 3, type = "discrete")

#xts object with ranks
#symbol with a rank of 1 has the highest ROC
r <- as.xts(t(apply(-roc, 1, rank)))

Created by Pretty R at inside-R.org

13 thoughts on “Momentum with R: Part 1

  1. Hi Ross,
    Nice post. Thanks. I am planning to experiment with your posts as template in parallel to get better handle on R. If it interests your other readers, as part of the series can you cover little bit of position sizing as well like volatility based sizing or adaptive asset allocation.

    Regards

    • pie,

      Thanks for the suggestions. I plan on covering other methods of ranking factors – absolute, relative, and volatility based. I will also cover position sizing.

      Ross

  2. I am new to R. When i copied and pasted you code:

    r <- as.xts(t(apply(-roc, 1, rank)))
    Error in apply(-roc, 1, rank) : object 'roc' not found

  3. Here’s another way to create the symbols_close object.

    # create new environment to store data
    symEnv <- new.env()
    # getSymbols assigns to new environment
    getSymbols(symbols, from='2000-01-01', env=symEnv)
    # function aggregates and returns Adjusted column
    f <- function(x) {
    sym <- sub("\\..*$","",names(x)[1])
    Ad(to.monthly(x,indexAt='lastof',drop.time=TRUE,name=sym))
    }
    # use eapply to call f() on each symbol
    symbols_close <- do.call(merge, eapply(symEnv,f))

  4. I have problems replicating this analysis.

    First of all, the to.monthly(x, indexAt=”lastof”) does not get me the last day of the month, as I would expect, for example:

    > x to.monthly(x, indexAt=”lastof”)

    x.Open x.High x.Low x.Close x.Volume
    2012-12-29 69.71 71.44 69.57 71.42 16384800
    2013-01-29 72.95 78.59 72.00 77.35 195100300
    2013-02-26 77.76 79.51 77.16 77.70 128912200
    x.Adjusted
    2012-12-29 71.42
    2013-01-29 77.35
    2013-02-26 77.70

    If I run the first part of code and type head(XLE) I get the following:
    > head(XLE)
    XLE.Adjusted
    2000-01-29 22.67
    2000-02-27 21.71
    2000-03-29 24.33
    2000-04-28 23.96
    2000-05-29 26.78
    2000-06-28 25.30

    as you can see this is totally different from what is shown in the article above, the prices are different and the index is different (not the last day of the month).
    Can someone explain why this is like that? Why is data different? Why indexAt=”lastof” produces the above results? What is the difference between “lastof” and “endof”?
    Thank you!

    • Hi Sergey,

      I am unable to replicate your error.

      One thing to note is that the blog post uses data from 2005 to 2012 and it looks like you are using data from 2000 to 2013 so the data will be different.


      library(FinancialInstrument)

      currency("USD")
      sym <- "XLE"
      stock(sym, currency="USD")

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

      x head(x)
      XLE.Adjusted
      2000-01-31 22.67
      2000-02-29 21.71
      2000-03-31 24.33
      2000-04-30 23.96
      2000-05-31 26.78
      2000-06-30 25.30

      Maybe it is a timezone thing… what is the output of sessionInfo()?

  5. Hi All,

    Anybodoy could explain me why is the rank apply to the negative value of the roc matrix?
    r <- as.xts(t(apply(-roc, 1, rank)))

    Thx in advance!

    • Hi Sergey G,

      You can solve your problem by setting a fix timezone in your execution: Sys.setenv(TZ = “UTC”)

      XLY.Adjusted XLP.Adjusted XLE.Adjusted XLF.Adjusted
      2000-01-31 1 2 3 4
      2000-02-29 1 2 3 4
      2000-03-31 1 2 3 4
      2000-04-30 2 1 3 4
      2000-05-31 1 2 4 3
      2000-06-30 1 4 3 2

    • Hi Daniel,

      The negative value is so that the asset with the greatest return will receive a rank of 1. This can be demonstrated with the following snippet of code:
      set.seed(12)
      x <- rnorm(5)
      rank(x)
      rank(-x)

      Ross

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s