Wednesday, January 4, 2017

Systematic Trading: Back-testing Classical Technical Patterns

Following up from my last post on systematic pattern identification in time series, here is the part on identifying and back-testing classical technical analysis patterns. This is based on the classic paper by Lo, Mamaysky and Wang (2000). The major improvement added here lies in defining local extrema in terms of perceptually important points (as opposed to the kernel regression based slope change technique proposed in the paper). In my view, the kernel method can be too noisy and much less robust with real data.

The R package techchart has two functions for identifying classical technical patterns. The function find.tpattern will sweep through the entire time series and find all pattern matches. It takes in the time series as the first parameter (an xts object), a pattern definition to search for, and a couple of tolerance parameters. The first one is used for matching the pattern itself. The second one pip.tolerance is used for finding the highs and the lows (perceptually important points) on which the pattern matching is based. These tolerance numbers are in terms of multiple of standard deviation. Below is an example:

x <- getSymbols("^GSPC", auto.assign = F)
tpattern <- find.tpattern(x["2015"], tolerance = 0.5, pip.tolerance = 1.5)

add_TA(tpattern$matches[[1]]$data, on=1, col = alpha("yellow",0.4), lwd=5)

Apart from returning the pattern matches, it also returns some descriptions and characteristics of the match. As below:

## ------pattern matched on: 2015-06-23 --------
## name: Head and shoulder
## type: complete
## move: 1.49 (percentage annualized)
## threshold: 2079.52
## duration: 57 (days)

While this is useful, you already must have spotted the catch. As this function looks at all available data at once to find a pattern, future prices influences past patterns. While this is useful for looking at a time series we need another function for rigorous back-testing. The second function available, find.pattern is to be used for this purpose. This function takes in similar arguments. It returns matched patterns. The match is based on either a completed pattern, or a forming one. A forming pattern is extracted by bumping the last closing price up or down by 1 standard deviation in the next bar and checking if it completes the pattern.

The process of identification of pattern is decoupled from the process of extracting patterns from the data - as proposed in the Lo et al (2000). The pattern defining function in the package is pattern.db.  This follows a similar implementation as here by Systematic Investor Blog, with some added features. The implementation of pattern.db in the package techchart contains some basic patterns - head and shoulder (HS), inverse head and shoulder (IHS), broadening top (BTOP) and broadening bottom (BBOT) - the default in the above functions being HS. However it is trivial to define any pattern (as long as it can be expressed in terms of local highs and lows) and customize this pattern library.

With this framework, it becomes quite straightforward to test and analyze pattern performance, run back-test on pattern based strategies and/ or combine patterns along with other indicators to devise trading strategies at any given frequency. 

Here is a straightforward implementation of such a back-test, using the quantstrat package. The strategy is quite straightforward. For a given underlying, we scan data for a head-and-should (or inverse head-and-shoulder) match. Once we find a match, we enter a short (long) position if a short term moving average is below (above) a long term one. Once we enter in to a short (long) position, we hold it for at least 5 days, and exit on or after that if a short term moving average is above (below) a long term one. We apply this strategy across S&P500, DAX, Nikkei 225 and KOSPI. The chart below shows the strategy performance.

The thick transparent purple line is the average performance across these underlying indices.  The performance metrics are as below. It also has (not shown here) a strong positive skew characteristics. 

Performance metrics
Annualized Return
Annualized Std Dev
Annualized Sharpe (Rf=0%)

Not spectacular, but nonetheless interesting. The R code for this back-test is here. Apart from techchart, you would need to install quantmod and quantstrat (and associated packages) to run this. Please note, running this pattern finding algorithm can take considerable time depending on the length of the time series and system characteristics.


  1. Thank you for your work! I've tried to reproduce your code but it failed as 'short.threshold' variable is not defined (in quantstat part lines 149-151). Can you help with that?

  2. thx for pointing this out. I have fixed the GitHub version, add missing variables (not run).