Labeling Momentum & Trends

Posted July 11, 2020

There are times when need to label a time series, identifying periods of momentum, trend, mean-reversion, etc. Directionaly labeling timeseries has a wide variety of applications:

  • labels can be used for supervised learning
  • analysis of microstructure around larger price moves
  • conditional analysis using label (pattern) sequences
  • testing online signals versus idealized ex-post labeled trend / momentum or MR targets

The Problem

The naive approach to labeling might just note the sign of individual returns in a series. However, most bars or samples in a timeseries present a non-zero return, though may be noise for a given trading target.

Another approach, often used, is to apply smoothing to the price series or vwap, removing noise (for example with a penalized spline, smoothing kalman filter, lowess spline, etc). Traditional smoothing algorithms do not adapt smoothing to slow or fast regimes, and therefore fail to capture:

  • high degree of smoothing: fails to capture fast moves OR
  • low degree of smoothing: captures fast moves but does not mask high frequency low amplitude noise

Top-Down Approach

From a trading perspective I wanted an algorithm that detected moves with:

  • amplitude > some minimum return (for example in intraday FX, 15bps minimum)
  • noise < some maximum

A few years ago developed an algorithm to label momentum and trend patterns in intra-day or daily price data based on the above two criteria. In spite of its simplicity, has performed quite well as compared to a number of more complicated statistical approaches have developed. I have implemented this in my own proprietary libraries, but thought I would contribute more broadly for a change of pace - launched a new python and R package implementing the approach here:

  • python version:
  • R version:

I have moved to using python primarily for research purposes, so will not be adjusting the R package often.


Below are some examples of the same (intra-day) data series, parameterized for more noise, less noise, higher or lower minimum amplitudes.

Labeling (minamp = 20bps, Tinactive = 5mins)

Note that this was performed on 30sec sampled bars, so Tinactive = 10, corresponds to 10 30sec bars = 5mins

labeler = AmplitudeBasedLabeler (minamp = 20, Tinactive = 10)
labels = labeler.label (df)

Graph of labels

Labeling (minamp = 20bps, Tinactive = 15mins)

Note that this was performed on 30sec sampled bars, so Tinactive = 30, corresponds to 15mins

labeler = AmplitudeBasedLabeler (minamp = 20, Tinactive = 30)
labels = labeler.label (df)

Graph of labels

Further Enhancements

The labeler does a good job of finding momentum / trend extents, however does not have a formal view of noise, other than a notion of price inactivity, where no new highs (lows) are achieved within a broader move. The identified sections can be further filtered, evaluating noise in a variety of ways:

  1. Traditional volatility measures (however these may not capture what a trader is looking for)
  2. Wavelet decomposition of price during the move, evaluating the power spectrum
  3. Finding a best-fit, 0 inflection spline for the move and determining ratio of RSS to slope (for example)

I have implemented and use a variety of these in determining whether to use a move conditionally in trading.