A Stocks On The Move Strategy By Andreas Clenow

I recently read Stocks on the Move: Beating the Market with Hedge Fund Momentum Strategies. Within this book Clenow uncovers a realistic approach to professional portfolio management. Through theoretical basics, logical reasoning behind the applied approach, simulation of the model and finally a year by year walk-through of the actual results Clenow leaves no stone unturned.

In the first chapter he sets out evidence as to why you should probably steer clear of mutual funds. Over a three year period 77.53% of US mutual funds failed to beat their benchmarks and for the privilege you would have to pay management fees on top! The remainder of the book sets out principles as to how to go about developing your own momentum based stock system, along with the critical thinking at each step of the development process. Although Clenow uses the S&P 500 index as a benchmark, there is no reason as to why the system cannot be applied to other indexes around the world. Momentum has been well documented and has been found to occur in many things not just stock prices.

I put together some R code which pulls the data in, filters stocks that are trading above their 100 day moving average without gaps > 15% then computes a momentum score as per the book.

When we have decided which index we will benchmark against we have to get a list of constituents.In my own case I wanted to use the FTSE100. Within R I built a function to scrape a list of constituents from here and clean up the stock code to be used with Yahoo finance.

Ftse100 <-  function(){
  require(rvest)
  require(data.table)
  
  urls <- paste0("https://www.londonstockexchange.com/exchange/prices-and-markets/stocks/indices/summary/summary-indices-constituents.html?index=UKX&page=", 1:6)
  
  ## Grab tickers
  Ftse100 <- sapply(urls, function(x) read_html(x) %>% html_table(1))
  Ftse100 <- rbindlist(Ftse100)
  Ftse100$Code <- paste0(gsub("[.]", "-", sub("[.]$", "", Ftse100$Code)), ".L")
  Ftse100 <- unlist(Ftse100$Code)
  
  return(Ftse100)
}

To run the function we use tickers <- Ftse100() and this will save the list of tickers to our object tickers. Using the BatchGetSymbols library of tools we can feed in our object tickers and download historical data for our stocks.

library(BatchGetSymbols)
library(data.table)

# set dates
first.date <- Sys.Date() - 360
last.date <- Sys.Date()
freq.data <- 'daily'

# set tickers
dfl <- BatchGetSymbols(tickers = tickers, 
                         first.date = first.date,
                         last.date = last.date, 
                         freq.data = freq.data,
                         cache.folder = file.path(tempdir(), 
                                                  'BGS_Cache') )

### Unlist stock data to single datatable
dt <- rbindlist(dfl[2])

#### Clean data and change NA's to zero
dt <- dt[, lapply(.SD, function(x) na.locf(x, na.rm=FALSE, fromLast = TRUE)), by = ticker]

This function runs Clenows analysis;

SOTM <- function(x){
  
 require(TTR)
  
 atr14 <- tail(ATR(dt[ticker == x, c(3,4,7)], 14)[,2], n=1)
 atr14 <- as.numeric(format(round(atr14, 3), nsmall = 3))
  
 sma100 <- tail(SMA(dt[ticker == x, 7], n = 100),n = 1)
  
 close <- as.numeric(tail(dt[ticker == x, 7, ], n = 1))
  
  maxgap <- max(dt[ticker == x, (price.adjusted - shift(price.open, n=1, type =  "lag")) / shift(price.open, n=1, type = "lag")], na.rm = T)
  
 y = 1:90
  
 ### Code checked as per clenows website 
 ## http://www.followingthetrend.com/stocks-on-the-move-figures-and-  charts/#prettyPhoto/15/
  
 slope <- coef(lm(tail(log(dt[ticker == x, price.adjusted, ]),  n = 90) ~ y))[2]
 AnnualisedSlope <- (exp(slope)^250)-1
 Rsq <- summary(lm(tail(log(dt[ticker == x, price.adjusted, ]),  n = 90) ~ y))$r.squared

 trendScore <- AnnualisedSlope * Rsq
 trendScore <- as.numeric(format(round(trendScore, 5), nsmall = 5))
    
if(close > sma100 & maxgap < 0.15){
return(data.table(x, close, slope, AnnualisedSlope, Rsq, trendScore, atr14))
}

}## end of function

Once the function is saved we run it by using the following code;

results <- do.call(rbind, lapply(unique(dt$ticker), SOTM))

Then we can sort our list by how well each stock scored;

results <- results[order(-trendScore),]

The current results for stocks matching Clenows criteria are below as of 18th November 2018 however, as the actual FTSE100 index is well below its 100 day moving average and so no new positions would be added. Current open positions would be held provided they remained on the list.

results
         x   close         slope AnnualisedSlope          Rsq trendScore   atr14
 1: BT-A.L  255.00  1.835016e-03     0.582101405 7.543629e-01    0.43912   8.459
 2:  WTB.L 4533.00  2.182868e-03     0.725845577 5.758008e-01    0.41794 101.137
 3:  EVR.L  551.20  1.569816e-03     0.480609652 4.342090e-01    0.20869  25.777
 4:  RRS.L 6408.00  1.778062e-03     0.559734155 2.479656e-01    0.13879 223.604
 5:  AZN.L 6204.00  6.675006e-04     0.181606743 2.459882e-01    0.04467 153.265
 6:  SHP.L 4563.00  4.823677e-04     0.128164439 3.221321e-01    0.04129  91.579
 7:  ADM.L 1980.50  5.710003e-04     0.153441484 2.578602e-01    0.03957  43.081
 8:  AAL.L 1702.80  6.518307e-04     0.176986861 1.418505e-01    0.02511  55.496
 9:  CNA.L  145.25  5.459467e-04     0.146239591 1.633968e-01    0.02390   4.549
10:   UU.L  738.20 -4.239573e-06    -0.001059332 1.545609e-05    0.00000  19.252
11:   SN.L 1399.00 -4.628239e-05    -0.011503917 1.616789e-03   -0.00002  25.050
12:   NG.L  823.00 -5.437290e-05    -0.013501255 2.669664e-03   -0.00004  17.571
13:  CCL.L 4520.00 -1.176632e-04    -0.028987372 4.445775e-03   -0.00013  89.435
14:  ABF.L 2448.00 -1.475662e-04    -0.036219352 9.370458e-03   -0.00034  64.234
15:  BLT.L 1606.80 -2.752219e-04    -0.066491745 4.771988e-02   -0.00317  48.223
16:  RIO.L 3910.00 -4.309863e-04    -0.102144888 7.943885e-02   -0.00811 112.370
17:  GSK.L 1562.00 -3.435307e-04    -0.082298110 1.208291e-01   -0.00994  32.018
18: PSON.L  930.40 -5.698747e-04    -0.132785276 1.195457e-01   -0.01587  21.386
19: ULVR.L 4273.00 -6.426394e-04    -0.148418305 2.943647e-01   -0.04369  76.853
20:  DGE.L 2802.50 -5.701194e-04    -0.132838325 3.613716e-01   -0.04800  48.255
21: EXPN.L 1879.00 -7.628626e-04    -0.173632469 3.162446e-01   -0.05491  46.431
22:  REL.L 1620.00 -1.524051e-03    -0.316830811 6.479692e-01   -0.20530  30.350

Now that we have our stock list and data we can easily plot one of the stocks using

plot(dt[ticker == "<TICKER CODE>", price.adjusted], type= "l")

I hope the code works alright however if you have any problems feel free to drop me a message or leave a comment.

2 thoughts on “A Stocks On The Move Strategy By Andreas Clenow

  1. Tim

    Hi jswinb007,

    What a fantastic coding try on momentum strategy from the book! In recent, I finished the book. The author tried to apply momentum strategy on S&P 500 stocks and done several back-testing (more than 10 years) for reference. It is a good try to apply the same rules on Ftse 100.

    Could you please provide the back-testing results and R codes on Ftse 100 stocks applying the momentum strategy? Many thanks and appreciate.

    Like

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s

%d bloggers like this: