4 min read

Trend following for growth stocks

Introduction

The objective of this post is to explore how common momentum set-ups/strategies would have work with “growth stocks”. Concretely, the idea is to backtest how going to cash when a growth stock closes below the 20 day EMA would work. By looking at some examples, it seems this simple strategy decreases the drawdowns significantly and captures most of the upside.

Backtest function

I define a function that takes as argument a ticker, an EMA lenght and a date range.

suppressPackageStartupMessages(library(xts))
suppressPackageStartupMessages(library(TTR))
suppressPackageStartupMessages(library(quantmod))
suppressPackageStartupMessages(library(PerformanceAnalytics))

options("getSymbols.warning4.0"=FALSE)

backtest <- function(ticker="CRWD", n_ema=20, date_range='1995/'){
  df = Ad(getSymbols(ticker, from='1995-01-01', to=Sys.Date(), auto.assign=FALSE))
  names(df)[1] = gsub("\\.Adjusted", "", names(df)[1])
  df = df[complete.cases(df)]
  
  df = df[date_range, ]

  # Compute return
  df$R = ROC(df[,1], n=1, type='discrete')
  df = df[-1, ]
  
  # Compute EMA
  df$EMA = EMA(df[,1], n=n_ema)
  df = df[complete.cases(df)]
  
  # create signal
  df$signal = Lag(ifelse(df[,1] > df$EMA, 1, 0), 1)
  df = df[-1, ]
  
  # Compute trend following return
  df$R_trend_follow = df$R * df$signal

  rets = df[, c("R", "R_trend_follow")]
  names(rets) = c("Buy_Hold", "Trend_Follow")
  
  charts.PerformanceSummary(rets, main=sprintf("Ticker: %s - EMA: %s", ticker, n_ema))

  tbl = table.AnnualizedReturns(rets)
  maxDD = data.frame(maxDrawdown(rets$Buy_Hold), maxDrawdown(rets$Trend_Follow))
  names(maxDD) = names(tbl)
  
  rownames(maxDD) = "Max Drawdown"
  tbl = rbind(tbl, maxDD)
  
  cat("Results for ticker: ", ticker, "EMA: ", n_ema, "\n")
  print(tbl)
  cat("\n")
}

A few examples from the dot-com bubble.

The results here are mixed. It did a bit better with AMZN, almost the same with CSCO and worst with QCOM.

AMZN

backtest(ticker="AMZN", n_ema=20, date_range="1998/2002")

## Results for ticker:  AMZN EMA:  20 
##                            Buy_Hold Trend_Follow
## Annualized Return         0.3012000    0.4927000
## Annualized Std Dev        0.9871000    0.6638000
## Annualized Sharpe (Rf=0%) 0.3052000    0.7422000
## Max Drawdown              0.9440422    0.6127732

CSCO

backtest(ticker="CSCO", n_ema=20, date_range="1998/2002")

## Results for ticker:  CSCO EMA:  20 
##                            Buy_Hold Trend_Follow
## Annualized Return         0.0430000    0.0354000
## Annualized Std Dev        0.6277000    0.3667000
## Annualized Sharpe (Rf=0%) 0.0686000    0.0965000
## Max Drawdown              0.8925838    0.7347732

QCOM

backtest(ticker="QCOM", n_ema=20, date_range="1998/2002")

## Results for ticker:  QCOM EMA:  20 
##                            Buy_Hold Trend_Follow
## Annualized Return         0.4049000    0.1463000
## Annualized Std Dev        0.7679000    0.5466000
## Annualized Sharpe (Rf=0%) 0.5273000    0.2677000
## Max Drawdown              0.8675496    0.9186577

Recent examples

AVGO

AVGO is an example of the ideal outcome where the drawdown gets reduced from 0.48 to 0.14 and the return is not very far off. This implies a higher sharpe ratio.

backtest(ticker="AVGO", n_ema=20, date_range="2020/")

## Results for ticker:  AVGO EMA:  20 
##                            Buy_Hold Trend_Follow
## Annualized Return         0.6117000    0.4536000
## Annualized Std Dev        0.5151000    0.2890000
## Annualized Sharpe (Rf=0%) 1.1876000    1.5697000
## Max Drawdown              0.4829997    0.1480315

FSLY

FSLY seems to be a neutral case.

backtest(ticker="FSLY", n_ema=20, date_range="2020/")

## Results for ticker:  FSLY EMA:  20 
##                           Buy_Hold Trend_Follow
## Annualized Return         3.761900    2.7539000
## Annualized Std Dev        1.049700    0.8829000
## Annualized Sharpe (Rf=0%) 3.583700    3.1190000
## Max Drawdown              0.547377    0.3290543

CRWD

In CRWD the strategy actually performed better than buy-hold with a much lower drawdown.

backtest(ticker="CRWD", n_ema=20, date_range="")

## Results for ticker:  CRWD EMA:  20 
##                            Buy_Hold Trend_Follow
## Annualized Return         1.1125000    1.7261000
## Annualized Std Dev        0.6824000    0.4881000
## Annualized Sharpe (Rf=0%) 1.6303000    3.5365000
## Max Drawdown              0.6687406    0.2101531

TDOC

In both TDOC and VEEV the result is much worse than buy-hold. Still the return was positive

backtest(ticker="TDOC", n_ema=20, date_range="")

## Results for ticker:  TDOC EMA:  20 
##                            Buy_Hold Trend_Follow
## Annualized Return         0.4723000    0.2142000
## Annualized Std Dev        0.5878000    0.4218000
## Annualized Sharpe (Rf=0%) 0.8035000    0.5079000
## Max Drawdown              0.7271683    0.4171187

VEEV

backtest(ticker="VEEV", n_ema=20, date_range="2020/")

## Results for ticker:  VEEV EMA:  20 
##                            Buy_Hold Trend_Follow
## Annualized Return         1.0019000    0.1586000
## Annualized Std Dev        0.4842000    0.3682000
## Annualized Sharpe (Rf=0%) 2.0693000    0.4308000
## Max Drawdown              0.2669576    0.2979046

Based on this simple analysis using a short EMA to move to cash has mixed results. Still it’s not a terrible idea if you are interested in improving risk-adjusted returns by reducing drawdowns. In practical terms it can prevent behavioural errors and provide a simple strategy for growth stocks. However this just provides an intuition that this strategy is reasonable but it’s not clear the aggregate effect of doing this with a portfolio of stocks.