Showing posts with label quantitative trading. Show all posts
Showing posts with label quantitative trading. Show all posts

Saturday, October 21, 2017

Systematic Trading | Using Autoencoder for Momentum Trading

In a previous post, we discussed the basic nature of various technical indicators and noted some observations. One of the ideas was: at a basic level, most indicators captures the concept of momentum vs mean-reversion. Most do so in the price returns space, but some in a non-linear transformation of the returns space, like signed returns or time since new high/ low. We presented the idea of a PCA approach to extract the momentum signals embedded in these indicators. From there to a trading model, the steps will be to collate this momentum signal (1st PCA component or higher if required) along with other input variables (like returns volatility and/ or other fundamental indicators) to train a separate regression/ classification model (like a random forest or a deep NN).

One of the issues with using simple PCA is that it is linear and hence may not be appropriate to summarize different measures captured across all these indicators. Here we discuss the next logical improvement - a nonlinear dimensional reduction approach using autoencoder.

As discussed here, the new Keras R interface has now made it very easy to develop deep learning models in R using the TensorFlow framework. Here we use this interface to train an autoencoder to fit the same set of technical indicators on NSE Nifty 50 Index as before. The steps involved are relatively straight-forward. First we generate and standardize the inputs (technical indicators levels). Then we build the computation graph.

To do so, first we define the encoding layers (2 hidden layers, the latent coded unit size is 3, to match the first 3 components of the PCA we use for comparison), and two different decoding layers. The two different decoding layers are to  enable us to train the auto-encoder as well as compute only decoding independently.


Next we combine these layers to create the computational graph. One for the encoder only, another for the decoder, and a third one for the end-to-end autoencoder, that we will actually train.

The rest of it is standard. We define a loss function to map the input to the output, measuring mean squared losses, and train the model. The training is done on data till 2013, and test set is since 2014 till present. Once the training is done, we can use the encoder and decoder separately to generate a dimensionality reduction of the input space and vice-versa.

The output of the dimensionality reduction is compared with the PCA. As it appears from the correlations, the PCAs are almost one-to-one mapped to the three latent dimensions in the hidden layer generating the encoding. So the encoded layers are orthogonal in our case, although this need not be true always.

V1
V2
V3
PC1
1
-0.3
0.2
PC2
0.1
-0.2
0.8
PC3
-0.2
-0.9
0.5

The scatter plot below captures the same, but also highlights the some non-linearity, especially the first component of PCA vs the first latent dimension from the autoencoder.


From here the next step is obvious, replace the PCA factors inputs in the momentum trading model in the first paragraph with these latent dimensions from the autoencoder and re-evaluate. This will capture a richer set of inputs that can handle non-linearity and hopefully performs better than linear PCA. Here are some results what other reported (opens PDF). Here are some more (opens PDF) on the using autoencoder for cross-sectional momentum trading. The entire code is available here.

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)
chart_Series(x["2015"])

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:

summary(tpattern)
## ------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
S&P
DAX
NKY
KOSPI
ALL
Annualized Return
0.0566
0.0536
0.0678
0.0528
0.0639
Annualized Std Dev
0.1233
0.0982
0.1413
0.1205
0.0692
Annualized Sharpe (Rf=0%)
0.4591
0.546
0.4797
0.4382
0.9234

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.

Saturday, October 22, 2016

Systematic Trading | An R Package for Automated Technical Analysis

This is an R package for automated technical analysis and some ground stuff for some pattern matching algorithm I plan to build. This is available at github - you can directly install it from github or you can fork or download. Currently it has three functionalities - 1) perceptually important points 2) change points for time series with linear deterministic trends and 3) automated technical support/ resistance/ price envelope identification (useful for back-test, but I have not found the time yet). It has also an undocumented module for technical pattern identification, which is in fluid state. Please note the is in early version and features/ data structures may undergo substantial changes in later version. I copy paste the R vignette below.


Techchart: Technical Feature Extraction of Time Series Data The R package techchart is a collection of tools to extract features from time series data for technical analysis and related quantitative applications. While R is not the most suitable platform for carrying out technical analysis with human inputs, this package makes it possible to extract and match technical features and patterns and use them to back-test trading ideas. At present, the package covers four major areas:
  • Perceptually Important Points (PIPs) identification
  • Supports/resistance identification (either based on PIPs or the old-fashioned Fibonacci method)
  • Change point analysis of trends and segmentation of time series based on underlying trend
  • Identification of technical envelopes (like trend channels or triangles) of a time series

Perceptually Important Points

PIPs are an effort to algorithmically derive a set of important points as perceived by a human to describe a time series. This typically can be a set of minima or maxima points or a set of turning points which are important from a feature extraction perspective. Traditional technical analysis - like technical pattern identification - relies heavily on PIPs. In addition, a set of PIPs can be used to compress a time series in a very useful way. This compressed representation then can be used for comparing segments of time series (match finding) or other purposes. In this package, we have implemented the approach detailed here.
spx <- quantmod::getSymbols("^GSPC", auto.assign = FALSE)
spx <- spx["2014::2015"]
imppts <- techchart::find.imppoints(spx,2)
head(imppts)
##            pos sign   value
## 2014-02-03  22   -1 1741.89
## 2014-03-07  45    1 1878.52
## 2014-03-14  50   -1 1841.13
## 2014-04-03  64    1 1891.43
quantmod::chart_Series(spx)
points(as.numeric(imppts$maxima$pos),as.numeric(imppts$maxima$value),bg="green",pch=24,cex=1.25)
points(as.numeric(imppts$minima$pos),as.numeric(imppts$minima$value),bg="red",pch=25,cex=1.25)

The function takes in a time series object (in xts format), and a tolerance level for extreme points identification (can be either a percentage or a multiple of standard deviation). It returns an object which has the list of all PIPs identified, marked by either a -1 (minima) or 1 (maxima), as well as the maxima and minima points separately as xts objects

Supports/ Resistance

Supports and resistance levels are very popular tools for technical analysis. The function find.pivots implements a couple of ways to identify supports and resistance levels for a price series. Using the option FIB will produce a set of Fibonacci levels around the most recent price point. The option SR will run an algorithm to find co-linear points along x-axis (horizontal line) to find levels most tested in recent times. A set of levels as well as xts representation of the lines defined by them are returned
spx <- quantmod::getSymbols("^GSPC", auto.assign = FALSE)
spx <- spx["2014::2015"]
sups <- techchart::find.pivots(spx, type = "FIB")
summary(sups)
## supports and resistance:
## next 3 supports:1982.249 1936.355 1890.461
## next 3 resistance:2130.82
sups <- techchart::find.pivots(spx, type = "SR", strength = 5)
summary(sups)
## supports and resistance:
## next 3 supports:2043.688 1992.551 1895.028
## next 3 resistance:2070.407 2111.588

Price Envelop Identification

Price envelopes features are an integral part of technical analysis. For example technical analysts look for features like trending channel, or ascending triangles etc to identify continuation or breakout from current price actions. The function find.tchannel identifies the most recent such envelopes using an implementation of the popular Hough transform algorithm in image processing, along with some heuristics.
spx <- quantmod::getSymbols("^GSPC", auto.assign = FALSE)
spx <- spx["2016-01-01::2016-09-30"]
tchannel <- techchart::find.tchannel(spx,1.25)
tchannel
## name: channel
## type: neutral
## direction: 0
## threshold: NA
quantmod::chart_Series(spx)

quantmod::add_TA(tchannel$xlines$maxlines[[1]],on=1, lty=3, col="brown")

quantmod::add_TA(tchannel$xlines$minlines[[1]],on=1, lty=3, col="brown")

The function returns an object with parameters of the envelopes found (if any), as well as the xts representation of the envelopes lines

Thursday, August 18, 2016

Systematic Trading: Getting Technical with Technical Indicators

There are few investors and traders who have never used a technical indicator. Some use them as part of their core trading strategies, others as confirmation or as a timing tool. I am reasonably certain even the most ardent value investors perhaps look at them in time of trials and tribulations. The set of these indicators are large (and ever increasing) as different ones developed over course of time, often from different markets and asset classes1. This is usually not a problem, as most practioner will settle down with one or two favorites.

However, most indicators have a lot in common among them. They are usually a function of past and present market data. They can be usually expressed as a function of returns of the underlying, and they tend to move in a range (though not always statistically stationary2).

Taking the example of a simple one - the moving average cross-over indicator. This is expressed as a difference of two moving averages (a short and a long ones). Mathematically, this can be represented as $mom=\sum_{i=0}^{n_1} a_i.P_i - \sum_{i=0}^{n_2} b_i.P_i$, where $n_1$ and $n_2$ are the short and long moving average periods, $a_i$s and $b_i$s are the weights (for simple moving average $a_i=1/n_1$ etc.) and $P_i$s are the prices. It can be shown that this can be converted from this price space to returns space, as $mom=\sum_{i=0}^{n} w_i.r_i$. Here $r_i=P_i - P_{i-1}$ (returns assuming log prices) and $n=n_2$ from above.

Similar treatment can be applied to other common indicators to convert them as a function of returns $r_i$s. A few example 3 below:
  • Momentum cross-over = $\sum_{i=0}^{n} w_i.r_i$
  • MACD Histogram = MACD line - signal line = $\sum_{i=0}^{n1} w_i^1.r_i$ - $\sum_{i=0}^{n2} w_i^2.r_i$ $\Rightarrow$ $\sum_{i=0}^{n} w_i.\Delta{r_i}$, Here $\Delta{r_i}=r_i - r_{i-1}$. This follows from logic similar to the momentum crossover above, and noting the difference of sum is in returns terms instead of prices.
  • CCI = (Price - Average Price)/(0.15 x Mean Deviation) = $\frac{1}{\sigma}\sum (P_i - \bar P)$ $\Rightarrow$ $\frac{1}{n.\sigma}\sum (r^{n}+r^{n-1}+..+r)$ $\Rightarrow$ $\sum w_i.r_i$, where $r^k = r_i - r_{i-k}$
  • Know Sure Thing = (RCMA1 x 0.1) + (RCMA2 x 0.2) + (RCMA3 x 0.3) + (RCMA4 x 0.4) = $a1.\sum w_1.r^{n_1} + a2.\sum w_2.r^{n_2} + a3.\sum w_3.r^{n_3} + a4.\sum w_3.r^{n_3}$ $\Rightarrow$ $\sum w_i.r_i$

Similarly most others can be expressed as a function of returns, although not all of them as linear (or even polynomial) as above. Broadly, we can divide all common technical indicators that can be expressed as function of returns in three different classes 4
  • Indicators that are linear (or polynomial) combination of past returns in returns space ($f(r)$). Examples - the ones above. Under certain condition (stationarity) they can be modeled as Gaussian distribution
  • Indicators that are functions of sign of the returns in signed returns space ($f(r^+, r^-)$). Examples - like RSI or Chande Momentum Oscillator. They can be analyzed using folded normal distribution
  • Indicators that are function of returns in time space ($f(t(r))$). An examples is the Aroon indicator

One objective of analyzing commonality of technical indicators can be to choose the one that is best suited to a particular purpose (depending on the time series characteristics of the underlying and the trading strategy). Another, and perhaps more common, can be dimensionality reduction as part of inputs to advanced machine learning based trading systems.

Following figure shows the outcome of principal component analysis of different technical indicators run on different equity indices5 - showing the first two principal components. Interestingly, for most cases (both in real market data and simulations6) the first two components will explain close to 85% or more variance in the indicators. As we can see all indicators load similarly on the first component. This is the underlying momentum component. This component typically explain around 70% variance, and will probably be the choice of inputs in a support vector machine or neural network system incorporating technical indicators. 


The second component is where the indicators differ a lot. This component captures the signature of the filtering carried out by the indicator. This signature has two parts, one is the intrinsic method of the filter computation. For example from the above formulate, we see MACD is a function of difference of returns and hence will tend to behave more like over-differenced series (assuming the returns are stationary). In contrast, KST will have a large component which is simply sum of returns, and hence will behave more like a non-stationary series in the limit. Indeed, for common parameters for these indicators (representing a look back of 20 days), the time series characteristics of these signals can be captured in the following (inverse) unit root circle plot (here roughly speaking, closer the plotted points, i.e. roots, towards the center of the circle, more mean-reverting is the series)


We can see from the PCA plot there are four major groups of indicators based on their time series characteristics - MACD, which is very much mean-reverting (i.e. suitable for short term trends), KST (which is quite the opposite) and then we have two groups - one consisting the first type of indicators noted above (function of returns) and the other group consists of the second and third types (function of signed returns and returns in time space). This is validated in the unit root plot as well, we see MACD has roots much closer to the center, KST almost on the circle perimeter, RSI quite close to it, and Bollinger bands closer to the center relatively.

Another way to appreciate how different indicators impact the momentum signal differently, is to look at how they filter the components of the underlying (returns in this case) at different frequencies - as seen in the AR spectral analysis chart below. Click on the indicators on the right hand side legend to turn them off or on.

A spectrum that has higher values towards zero frequency (like KST) means they will tend to filter out higher frequency in the data,whereas the ones that has a peak away from zero, or drop off slowly from peak at zero will tend to pick up faster components (in the extreme resembling high negative correlation of a over-differenced signal). Of course as we increase look back period, an indicator will tend to move away from the second kind and towards the first kind.




Using this insight, one can design an appropriate set of indicators to extract an "average" momentum signal, to be used in other strategy or as inputs to a neural networks or similar system.

For this purpose, the first PCA component is the one we seek to use as input as momentum signal, straight and simple. The usefulness of the second component is that it allows us to fine-tune the momentum signal for our purpose. A momentum signal depends on our time frame - a short period momentum can look like mean-reversion in longer time frame. To extract a consistent signal we need to tune the choice of the indicators and parameters. If we are looking to extract momentum signals averaged over different filtering methods, but not over time, we need to ensure all factor loadings on the second component are within acceptable limits. Whereas if we want to span as much frequency spectrum as possible we want the loadings to span much larger space. Depending on out choice we extract the kind of signal we want from the first component7.

Note, while I mention the first component as momentum signal, it is NOT same as what is known as the time series momentum factor. However, it can easily computed by back-testing trading PnL based on this momentum signal. As we have seen in general these signals can be expressed as $\sum w.r$, the PnL will be (using a linear sizing function) $\sum (w_i.r_i).r_j$, or (using a sign function) $sign(\sum (w_i.r_i)).r_j$. Of course we can approximate the signum function, and then in general, the PnL becomes a polynomial of auto-covariances of the underlying returns.


1. This is a useful place with good introductory materials on different indicators
2. In general an indicators will tend to become non-stationary at a given periodic frequency (e.g. daily) as we increase the look-back parameter
3. There is no guarantee the sum of weights adds up to one. Please feel free to notify me in comments if you spot any error.
4. Here we ignore the indicators that take volume as an input as well
5. All data from Yahoo Finance
6. Based on simulations assuming expected market behaviours, i.e. AR or ARMA type return characteristics.
7. One can design an algorithm for this purpose, that will maximize the explained variance by the first component of the PCA, by optimizing over the parameter space of the indicators within a pre-defined set.