Quantitative Investment: Momentum Scoring

"Technical" indicators are useful for measuring short-term momentum of stock prices. Although technically we can measure momentum of other metrics like Price-Over-Eraning (P/E) or EV/EBITDA, price of a stock is an important metric to apply momentum over.

There are various indicators already -

  • Relative Strength Index (RSI)
RSI = 100 – [100 / ( 1 + (Average of Upward Price Change / Average of Downward Price Change ) ) ]  
  • Moving Averages: SMA50, SMA200, EMA (Exponential Moving Average: gives more weight to recent prices compared to old prices)

But instead of these short-term momentum indicators (used primarily by "traders" as opposed to long-term investors), was keen on designing some indicator for long-term trends which takes both - volatility and momentum into account. Also the designed metric should work well for both - Bull market and Bear market, given the fact that it's easier to design such a momentum indicator for a rising Bull market.

To facilitate the discussion we are going to refer three BSE-listed Indian companies -

Leaving out Infosys, even though both companies would have given fantastic returns, the volatility of Vardhman Holdings Ltd is lesser. Standard measure of volatility - standard deviation - is too granular and downright wrong, IMO.

> sd(t.Vardhman)
[1] 662.1091
> sd(t.Triveni)
[1] 11.22787

FYI, I'm using Quandl API to fetch the pricing data -

Quandl_Fetch <- function(ticker, look_back_window = years(1)) {  
  stock <- Quandl(ticker,             
start_date=as.character(ymd(Sys.Date()) - look_back_window),  
end_date=as.character(ymd(Sys.Date())))

  # Create the time series
  t <- xts(stock$Close, stock$Date)
  t
}
> t.Triveni <- Quandl_Fetch(ticker = "BSE/BOM533655", look_back_window = years(2))
> t.Vardhman <- Quandl_Fetch(ticker = "BSE/BOM500439", look_back_window = years(2))

RSI

  • Triveni Turbine Ltd

  • Vardhman Holdings Ltd

There are some patterns in RSI plots, but not enough to differentiate these two companies on the ground of growth and volatility through a singular metric.

Let's see whether we can design such a metric !

Coppock curve

The Coppock curve or Coppock indicator is a technical analysis indicator for long-term stock market investors created by E.S.C. Coppock, first published in Barron's Magazine on October 15, 1962. This is a "behaviourally"-inspired momentum indicator.

coppock <- function(df) {  
  WMA(ROC(df, n = 11) + ROC(df, n = 14)) 
}

ROC is the Rate of Change for a given time series.

> mean(coppock(t.Triveni), na.rm = TRUE)
[1] 0.3807641
> mean(coppock(t.Vardhman), na.rm = TRUE)
[1] 1.711664
> mean(coppock(t.Infosys), na.rm = TRUE)
[1] -0.2949913

So Coppock Curve could be a good long-term momentum indicator. However, 11 and 14 numbers seem arbitrary even though they have strong "behavioural" underpinning.

MACD

Moving Average Convergence Divergence

It's a difference of fast-moving 12-period EMA and slow-moving 24-period EMA. You can compare this with a ZERO line, or you can construct a 9-period EMA of the MCAD itself to form a SIGNAL line for comparison.

Again, this is heavily used for short-term momentum indicator for trading, but not sure whether it fits the bill for a long-term indicator (although period could be replaced with month for long-term indicator. example - 12-period EMA becomes 12-month EMA etc.)

LM2Score

Linear Model Momentum Scoring

Here is how it's derived -

  • Smooth by deriving 30-period EMA
  • Fit a simple linear regression to the smoothed series
  • Derive the momentum indicator by
(slope * days) / (RSE * sqrt(days))

sqrt has been used to scale daily volatility.

  • Add a volatility factor (divide by). This is just standard-deviation of the first-order discrete difference (scaled by mean)
volatility_measure <- function(t) {  
  sd(diff(t), na.rm = TRUE)/abs(mean(diff(t), na.rm = TRUE))
}

Complete code -

momentum_scoring <- function(ticker, momentum_window = 100, look_back_window = years(1)) {  
  t <- Quandl_Fetch(ticker, look_back_window)

  old.par <- par(mfrow=c(1, 2))
  par(mfrow=c(2,1))
  plot(t, major.ticks="months", major.format="%b %d", main = ticker) 

  # Smooth it
  t.smoothed <- EMA(t, n = 30)
  plot(index(t.smoothed), coredata(t.smoothed),main = paste(c("EMA smoothed - ", ticker), collapse = ""))
  reg <- lm(coredata(t.smoothed) ~ index(t.smoothed))
  abline(reg, col = "red")

  par(old.par)
  round((momentum_indicator(reg, days = momentum_window) / volatility_measure(t))*100,2)
}

momentum_indicator <- function(fit, days = 100) {  
  (as.numeric(coef(fit)[2]) * days)/(sd(residuals(fit)) * sqrt(days))
}

volatility_measure <- function(t) {  
  sd(diff(t), na.rm = TRUE)/abs(mean(diff(t), na.rm = TRUE))
}

Now results -

  • Triveni

> momentum_scoring(ticker = "BSE/BOM533655", momentum_window = 200, look_back_window = years(2))
[1] 0.11
  • Vardhman

> momentum_scoring(ticker = "BSE/BOM500439", momentum_window = 200, look_back_window = years(2))
[1] 1.24
  • Infosys

> momentum_scoring(ticker = "BSE/BOM500209", momentum_window = 200, look_back_window = years(2))
[1] -0.34