April 1

11 comments

Building A Better Trend Filter

By Jeff Swanson

April 1, 2019

Automated Trading Development, EasyLanguage, filter, Trend Following

In this article I will create a trend filter (also known as market mode filter or regime filter) that is adaptable to volatility and utilizes some of the basic principles of hysteresis to reduce false signals (whipsaws). As you may know, I often will use the 200-period simple moving average (200-SMA) to determine when a market is within a bull or bear mode on a daily chart. When price closes above our 200-SMA we are in a bull market. Likewise, when price is below our 200-SMA we are in a bear market. Naturally, such rules will create some false signals. By the end of this article you will have a market mode filter that can be used in your system development that may produce better results than a standard 200-SMA filter. To build our better market trend filter we will use the following concepts:

  • Hysteresis
  • Price proxy

Hysteresis Basics

When building trading systems many of the decisions have a binary outcome. For example, the market is bearish or bullish. You take the trade or you don't. Introducing a "gray area" is not always considered. In this article I'm going to introduce a concept called Hysteresis and how it can be applied to our trading. Hysteresis was used in a previous article on reducing whipsaws within a moving average crossover trading system. While the word Hysteresis was not used specifically in that article, it was a good example.

The common analogy to help understand the concept of hysteresis is to imagine how a thermostat works. Let's say we are living in a cool weather climate and we are using a thermostat to keep the temperature of a room at 70 degrees F (critical threshold). When the temperature falls below our critical threshold the heaters turn on and begin blowing warm air into the room. Taking this literally as soon as the temperature moves to 69.9 our heater kicks on and begins blowing warm air into the room driving the temperature up. Once the temperature reaches 70.0 our heaters turn off. In a short time the room begins to cool and our heaters must turn on again. What we have is a system that is constantly turning off and on to keep the temperature at 70 degrees. This is inefficient as it produces a lot of wear on the mechanical components and wastes fuel. As you might have guessed, hysteresis is a way to correct this issue. More in just a moment.

The purpose of this article is to improve our market mode filter. Below is the result of buying the S&P cash index when price closes above the 200-SMA and selling when price closes below the 200-SMA. This is similar to our thermostat example. Instead of turning on the furnace to heat a room we are going to open a new position when a critical threshold (200-SMA) is crossed. 

In order to keep things simple, there is no shorting. For all the examples in this article, we are starting with a $100,000 account and risking $1,000 for each trade. The number of shares is scaled based upon a 20-day ATR calculation. To account for slippage and commissions $30 is deducted for each round trip. The backtest was performed from August 1, 2020 going back 50 years.

Below is the code for opening and closing a new position.

SMA_Line = Average( Close, 200 );
If ( Close > SMA_Line ) then Buy next bar at market;
If ( Close < SMA_Line ) then Sell next bar at market;


Below is the code for opening and closing a new position.

SMA Cross Only

SMA Crossover

Net Profit

$49.233

Profit Factor

3.13

Total Traders

170

%Winners

23%

Avg. Trade Net Profit

$290

Annual Rate of Return

1.39%

Sharpe Ratio

-.02

Max Drawdown(intraday)

$5,215

Expectancy

1.63

Expectancy Score

5.63

In the above image we can see we entered into the market six times before the trade moves significantly in our favor. The first five attempts were closed at a loss as price moved from bull territory back to bear territory. That’s five consecutive losing trades!

Trading Bands

Going back to our thermostat example, how do we fix the problem of the furnace turning on and tuning off so many times? How do we reduce the number of signals? Let’s create a zone around our ideal temperature of 70 degrees. This zone will turn on the heaters when the temperature reaches 69 degrees and turn off when the temperature reaches 71 degrees. Our ideal temperature is in the middle of a band with the upper band at 71 and the lower band at 69. The lower band is when we turn on the furnace and the upper band is when we turn off the furnace. The zone in the middle is our hysteresis.

In our thermostat example we are reducing “whipsaws” or false signals, by providing hysteresis around our ideal temperature of 70 degrees. Let’s use the concept of hysteresis to attempt to remove some of these false signals. But like our ideal temperature we want an upper band and a lower band to designate our “lines in the sand” where we take action. There are many ways to create these bands. For simplicity let’s create the bands from the price extremes for each bar. That is, for our upper band we will use the 200-SMA of the daily highs and for the lower band we will use the 200-SMA of the daily lows. This band floats around our ideal point which is the 200-SMA. Both the upper and lower bands vary based upon the recent past. In short, our system has memory and adjusts to expanding or contracting volatility. The EasyLanguage code for our new system look something like this:

SMA_Line = Average( Close, 200 );
UpperBand = Average( High, 200 );
LowerBand = Average( Low, 200 );
If ( Close crosses over UpperBand ) then Buy next bar at market;
If ( Close crosses under LowerBand ) then Sell next bar at market;

Below is a screen shot showing the effect of opening a new trade after the daily bar closes above the upper band. We have reduced our five consecutive losing trades down to two trades.

Here are the results with using our new bands as trigger points.

Band Cross

SMA Crossover

Band Cross

Net Profit

$49.233

$53,786

Profit Factor

3.13

4.70

Total Traders

170

79

%Winners

23%

40%

Avg. Trade Net Profit

$290

$681

Annual Rate of Return

1.39%

1.48%

Sharpe Ratio

-.02

0

Max Drawdown(intraday)

$5,215

$4,029

Expectancy

1.63

2.20

Expectancy Score

5.63

3.53

Looking at the performance table above we can see an improvement in nearly all aspects of the system’s key performance. Most notably, increased Net Profit, Profit Factor, Percent Winners  and Average Trade net Profit. We also reduced the number of trades. We really end up with about the same amount of net profit but we accomplish this task with fewer, more profitable trades.

It’s interesting to note that our Expectancy Score falls even as we increase the Expectancy value from 1.63 to 2.20. This is due to the reduced number of trading opportunities.

Price Proxy

A price proxy is nothing more than using the result of a price-based indicator instead of price directly. This is often done to smooth price. There are many ways to smooth price. I won’t get into them here. Such a topic is great for another article. For now, we can smooth our daily price by using a fast period exponential moving average (EMA). Let’s pick a 5-day EMA (5-EMA). Each day we compute the 5-EMA and it’s this value that must be above or below our trigger thresholds. By using the EMA as a proxy for our price we are attempting to remove some of the noise of the daily price fluctuations.

Below is what the EasyLanguage code may look like.


SMA_Line = Average( Close, 200 );
UpperBand = Average( High, 200 );
LowerBand = Average( Low, 200 );
PriceProxy = XAverage( Close, 5 );

If ( PriceProxy crosses over UpperBand ) then Buy next bar at market;
If ( PriceProxy crosses under LowerBand ) then Sell next bar at market;

Below is an example of a trade entry. Notice the trade is opened when our price proxy (yellow line) crosses over the upper band. We have also reduced our losing trades to one.

Let’s see how this affects our performance.

Price Proxy Band Cross

SMA Crossover

Band Cross

Band Cross Price Proxy

Net Profit

$49.233

$53,786

$58,004

Profit Factor

3.13

4.70

5.87

Total Traders

170

79

45

%Winners

23%

40%

51%

Avg. Trade Net Profit

$290

$681

$1,289

Annual Rate of Return

1.39%

1.48%

1.57%

Sharpe Ratio

-.02

0

.01

Max Drawdown (intraday)

$5,215

$4,029

$12,667

Expectancy

1.63

2.20

2.38

Expectancy Score

5.63

3.53

2.17

Looking at the strategy performance table above we are making more money. We are being more efficient with our trades by eliminating unprofitable trades. We have increased our percent winners to around 50%. Our average trade net profit is above $1,200. However, our max drawdown has increased.

So, which strategy is better? It all depends on what you want or what you are comfortable with. Some people will wish to simply take as much money as possible. Others will wish to reduce the number of consecutive losing trades.

The above examples are designed to demonstrate the effect a “better” trend filter can have on a simple trading strategy. If we want to use this in a trading system it would be ideal to create a function from this code that would pass back if we are in a bear or bull trend. However, the programming aspect of such a task is really beyond the scope of this article. Nonetheless, below is a quick example of setting two boolean variables (in EasyLanguage) that could be used as trend flags:

BullMarket = PriceProxy > UpperBand;
BearMarket = PriceProxy <= LowerBand;

In this article we have created a dynamic trend filter that smooths price, adapts to market volatility and utilizes hysteresis principles. With just a few lines of code we can significantly reduced the number of false signals commonly associated with this style of trading strategy . This type of filter can be effective in building the trading systems for ETFs, futures and Forex.

Jeff Swanson

About the author

Jeff has built and traded automated trading systems for the futures markets since 2008. He is the creator of the online courses System Development Master Class and Alpha Compass. Jeff is also the founder of EasyLanguage Mastery - a website and mission to empower the EasyLanguage trader with the proper knowledge and tools to become a profitable trader.

  • Hi Jeff,

    I have an easy question and (maybe) a harder question.

    First, you write “The number of shares is scaled based upon a 20-day ATR calculation.” I’m trying to rectify that with $1000 risk/trade. Was the stop ATR(20) and then # shares = $1000 / ATR(20)?

    Second, what are your thoughts about taking redundant signals in backtesting? Suppose the exit for SMA Cross Only were a 5-bar stop and on Days 1, 3, and 5 you got crosses. Typically, redundant signals aren’t included and you would backtest just one open position at a time but can this introduce bias? When should or should these not be included in backtesting analysis and how do they potentially factor into position sizing when making the transition from strategy to system?

    Thanks,
    Mark

      • Redundant signals are subsequent trade signals that occur when a position (in the same direction) has already been taken. For example, if I’m looking for a close above yesterday’s upper Bollinger Band then I might see this on three consecutive days. Typically, backtests wouldn’t include the redundant signals. I just wonder how NOT including them could possibly skew the results. On different occasions, I’ve considered arguments for and against including the redundant signals (how one would trade them in the context of a system is then another question altogether).

        What are your thoughts?

        • Redundant signals, pyramiding or scaling into a trade can be a legitimate way to trade. It all depends upon how you wish to trade. By deciding not to take redundant signals during backtesting, one is not skewing the results. You are simply making a choice not to trade redundant signals, thus those redundant signals are invalid during backtesting. In short they are not part of the plan thus, they are legitimately ignored.

          If you do decide to take redundant signals you will find you need to adjust your risk so that each open position risks no more than your maximum per determined limit. From a coding standpoint, this can complicate the code.

          • This is not a case of “trade like you backtest.” While you would take the redundant signals for backtesting purposes, you would still trade one position at a time.

            Let me borrow an example from the options world. Many options trades are placed X days to expiration (DTE). To backtest this, you could take 12 trades per year * many years to get a decent sample. If we take 30 as an example, there’s nothing special about 30 DTE. What you really want to backtest is the strategy rather than when the trade is placed. Therefore, you could take the same approach and apply it with 40 DTE, 39 DTE, 38 DTE, 37 DTE, 36 DTE, … 20 DTE, etc. Now, instead of having 12 trades per year in the backtest, you have over 200, perhaps, or maybe every single day of the year. It’s not that in practice you would actually place a trade every single day of the year but rather you’re trying to flush out the arbitrary.

            Any thoughts?

          • Mark, I think I see what you’re saying now. If I understand you correctly and you are simply using a variable to change the days to DTE, I don’t see how this is any different from testing any other trading variable. You have a given trading system and when a setup occurs, the system can trade X days to DTE. This input value (‘X’) can be tested over a range of 1-30 which should be done to test for stability over that range. I think this is a valid test. A poor test result would show dramatic changes between neighboring values. I hope I understood you correctly.

    • That could be an issue. It would need more investigation. In the article I was demonstrating entry techniques thus, the example code does not have stops so, that might be the first item you would need to add.

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Learn To Code & Build Strategies
    Using EasyLanguage. 

    >