[R-SIG-Finance] Jegadeesh & Titman Strategy Implementation

Enrico Schumann es at enricoschumann.net
Sun Feb 19 23:55:51 CET 2017


On Thu, 16 Feb 2017, ROUX, Nicolas writes:

> Hello all,
>
> Is there a package/function capable of implementing a momentum strategy
> described in Jegadeesh & Titman (1993) and backtesting it? General steps of
> the strategy are:
> 1- taking monthly stock prices/returns,
> 2- ranking monthly/period returns,
> 3- create equally weighted portfolio of top and bottom stock returns,
> 4- hold for "n" months (quarter, semester, year) with no updating in
> between,
> 5- rebalance portfolio after holding period,
> 6- return results.
>
> I have created a roundabout way using return.portfolio from
> performanceanalytics but would like to use a package which allows the
> possibility to progressively more complex strategies.
> I cannot find a way to implement the holding period in the quantstrat
> package or the ranking conditions and holding period in portfolioanalytics
> package (uses only a complex ranking method).
>
> Cheers,
>
> Nicolas Roux
>

Hi Nicolas,

I have written some code, a package actually, that
might be used for such computations. It is available
from http://enricoschumann.net/R/packages/PMwR/index.htm or
from https://github.com/enricoschumann/PMwR . It is far
from complete, but perhaps it is useful.

Below is a brief example, for which I create random
monthly prices of 100 assets. These I store in a matrix
P (see below for the code). Dates are stored in a
vector 'timestamp'. Just plug in your own data instead,
but with these random data data, you could directly run
the example below.

The key ingredient for simulating a strategy is a
function that is called at any instant of time at which
trading may take place, and which returns the desired
position (or, alternatively, the desired weights). For
a simple momentum strategy, it may look like this:

  mom_weights <- function() {
      k <- 10                              ## Number of stocks in the portfolio.
  
      M <- Close(lag = 1)/Close(lag = 13)  ## Compute 1-year return and 
      r <- order(M)                        ## rank assets. Close(lag = ...) returns a
                                           ## single-row matrix of close prices.
      
      w <- numeric(length(M))              ## Set equal-weight portfolios.
      w[head(r, k)] <- -1/k
      w[tail(r, k)] <-  1/k
      w
  }

You can then call the function 'btest' ('backtest') and
either take the raw equity curve (as a zoo object, say)
or have some stats computed.

  require("PMwR")
  result <- btest(prices = list(P),
                  signal = mom_weights,
                  b = 13,                 ## the burn-in 
                  convert.weights = TRUE, ## since mom_weights returns weights, 
  		                          ## convert them to positions
                  initial.cash = 100,
                  timestamp = timestamp,
                  include.data = TRUE)
  
  summary(as.NAVseries(result))

  ## ---------------------------------------------------------
  ## 31 Dec 1997 ==> 31 Dec 2016   (229 data points, 0 NAs)
  ##         100         81.2107 
  ## ---------------------------------------------------------
  ## High                  105.04  (30 Nov 1999)
  ## Low                    74.05  (31 Jan 2015)
  ## ---------------------------------------------------------
  ## Return (%)              -1.1  (annualised)
  ## ---------------------------------------------------------
  ## Max. drawdown (%)       29.5
  ## _ peak                105.04  (30 Nov 1999)
  ## _ trough               74.05  (31 Jan 2015)
  ## _ underwater now (%)    22.7
  ## ---------------------------------------------------------
  ## Volatility (%)           6.3  (annualised)
  ## _ upside                 4.2
  ## _ downside               4.7
  ## ---------------------------------------------------------
  ## 
  ## Monthly returns  ▁▂▅█▆▄▁ 
  ## 
  ##       Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec   YTD
  ## 1998  0.0 -1.9 -1.0  2.2  1.7  0.1 -1.3 -0.3 -1.4  1.8  1.1 -2.1  -1.2
  ## 1999 -2.3  1.1 -0.6  0.6  1.0  1.5 -0.2  0.2  3.1  1.7  0.1 -1.5   4.8
  ## 2000  0.7 -3.0 -0.3 -1.3 -2.3 -1.0  0.7  0.4 -1.1 -1.8  2.0 -1.7  -8.6
  ## [ ... ]
  
(Since I used random data, these numbers signify
nothing.)

Now, the previous call of btest computed the portfolio
every month:

  unique(journal(result)$timestamp)

  ## [1] "1998-01-31" "1998-02-28" "1998-03-31" "1998-04-30" "1998-05-31"
  ## [6] "1998-06-30" "1998-07-31" "1998-08-31" "1998-09-30" "1998-10-31"

There are several possiblities for how to trade less
often, but a simple one here would be to precompute the
dates at which trading should take place, and pass
these points in time as parameter 'do.signal'. For
example, to trade only every second timestamp:

  result <- btest(list(P),
                  signal = mom_weights,
                  do.signal = seq(1, length(timestamp), by = 2),
                  b = 13,
                  convert.weights = TRUE,
                  initial.cash = 100,
                  timestamp = timestamp,
                  include.data = TRUE)

  unique(journal(result)$timestamp)

  ## [1] "1998-02-28" "1998-04-30" "1998-06-30" "1998-08-31" "1998-10-31"
  ## [6] "1998-12-31" "1999-02-28" "1999-04-30" "1999-06-30" "1999-08-31"


Kind regards
     Enrico



## Appendix: Random data

  na <- 100 ## number of assets
  timestamp <- seq(as.Date("1996-12-01"),
                   as.Date("2016-12-31"),
                   by = "1 day")
  timestamp <- aggregate(timestamp,
                         by = list(format(timestamp, "%Y-%m")),
                         FUN = tail, 1)[[2]]
                                 
  np <- length(timestamp)
  P <- array(rnorm(np*na, mean = 0.0025, sd = 0.04),
             dim = c(np, na))
  P[1, ] <- 0
  P <- apply(P, 2, function(x) cumprod(1 + x))
  plot(P[ ,1])
  



-- 
Enrico Schumann
Lucerne, Switzerland
http://enricoschumann.net



More information about the R-SIG-Finance mailing list