Skip to content
Prev 14154 / 15274 Next

Jegadeesh & Titman Strategy Implementation

On Thu, 16 Feb 2017, ROUX, Nicolas writes:
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])
Message-ID: <874lzpvlhk.fsf@enricoschumann.net>
In-Reply-To: <CAFF3=2qfvaGaoR2pSj3NKzwaUkNKg9TWrodvZK=5m_ZoGDXvWA@mail.gmail.com> (Nicolas ROUX's message of "Thu, 16 Feb 2017 15:21:35 +0100")