Pullback and Trend Following Strategy with Sideways Filter

Mis à jour
This is a strategy of short to medium term trading which combine two famous strats, they are: Pullback (mean reversion) and Trend Following. I recently code this strategy using pine script to see how profitable in long run, so later on I can set alert to the stocks in my watchlist (the pine-script source code is in the end of the post). I only apply in long position because in my country doesn't allow short trading, but feel free if you want to extend short position. As of now the result of back-tests are quite promising which I expected (overall 10-50% profit for 3 year back-test data). Okay let's begin.

Trend following can be catch up with simple golden crosses between fast and medium moving average. This strategy will make the most of your profit source, I guarantee you. In this strategy I apply SMA which set by default 20 and 50 period for fast and medium MA respectively. One of the weakness of trend following is on sideways market. In order to prevent false signal generated in sideways market, I need to filter sideways range out of the trend. I use ADX indicator which can use to identify sideways market, but some methods can also be applied as per this blog post (quantnews.com/3-ways-to-identify-a-ranging-market-with-your-algo/). Basically trend following will allows you to buy high and sell higher, the risk of this strategy is the false signals. Entry signals at golden cross (fast MA cross-over medium MA from down to up) and exit signal when dead cross (fast MA cross-under medium MA from top to down) happens. If you can catch a good strong uptrend you can combine with pyramiding strategy to average up your position. Back-test with pyramiding strategy is so tricky in TradingView, I already try it but end-up with lower profit result. So, I will do pyramiding things manually once I found a good strong uptrend. The important message is YOU CANNOT MISSED STRONG UPTREND, when the alert of trend-following golden cross happens, tighten your seat belt and don't hesitate to put your position high with strict stop loss.

The signal of strong uptrend usually after breakout its resistance with a good amount of volume. In the next update I will try to consider volume as well, as a confirmation of breakout. So the signals would be filtered only for the strong uptrend. Valid signals will give you a good profit margin.

In summary below are the points for trend following part:
  • Using Simple Moving Average
  • Medium SMA by default is 50-periods
  • Fast SMA by default is 20-periods
  • MA periods shall be chosen based on the stocks chart trend characteristics to maximize profit.
  • Entry when golden cross signal happens (fast MA cross-over medium MA from down to up)
  • Exit when dead cross signal happens (fast MA cross-under medium MA from top to down)
  • Reject false signals by using sideways range filter


Second part is mean-reversion or pullback trade strategy. This is the strategy which allows you to buy low sell high and the risk is when you buy low, the market will keep going lower. The key of mean-reversion is the momentum. Once your momentum guessing is correct you can achieve a very good profit in relatively short time. Here, I will use oscillator based momentum indicator RSI (Relative Strength Index) as a criteria. For entry I use 2-period RSI which not more than 5%. Why 5% ?, that's experimental value which can provide me an enough confirmation of weakness momentum. Then for exit setup I use 5-period RSI which pass 40%. A strong weak momentum in overall will be pushed as high as 40% and 40% RSI can be considered as lower bound of sideways market swing. Last but not least, this pullback trade shall be executed only in above 200-period MA, as a confirmation that overall swing is in uptrend. Thus, if the market going sideways I will use pullback trade and if the market going to form an uptrend, the strategy will sense a golden cross SMA. Inside the chart of this post, you see red and green background color. That is indicate sideways or trend market relatively to ADX index. You can adjust the parameter in order to maximize profit.

To summarize the second part (pullback trade) are:
  • Use 200-period SMA as a first filter for sideways market.
  • Got a sideways market confirmation with ADX index.
  • Entry on below 5% of its 2-period RSI
  • Exit on above 40% of its 5-period RSI or after 10 days of trade.


In the other part of my script also included the rule to size your entry position. Please find below the full pine-script source code of above explained strategy.
Hopes it will drive your profit well. Let me know your feedback then. Thanks.

// START ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// This source code is subject to the terms of the Mozilla Public License 2.0 at mozilla.org/MPL/2.0/
// © m4cth0r

//version=4
// 1. Define strategy settings
commValue = 0.19 * 0.01
strategy(title="PB/TF+SWAY-NOTPYRM", overlay=true,
pyramiding=0, initial_capital=20000000,
commission_type=strategy.commission.percent,
commission_value=commValue, slippage=2, calc_on_every_tick=true)

//----- MA Length
slowMALen = input(200,step=1,minval=5,title="Slow MA Length")
midMALen = input(40,step=1,minval=5,title="Mid MA Length")
fastMALen = input(20,step=1,minval=5,title="Fast MA Length")

//----- DMI Length
DiLen = input(20,minval=1,title="DI Length")
ADXSmoothLen = input(15,minval=1,title="ADX Smoothing", maxval=50)
ADXThreshold = input(21,step=1,title="ADX Sideway Threshold")

//----- RSI2 for Entry, RSI5 for Exit
RSIEntryLen = input(title="PB RSI Length (Entry)", type=input.integer, defval=2)
RSIExitLen = input(title="PB RSI Length (Exit)", type=input.integer, defval=5)
RSIEntryThreshold = input(title="PB RSI Threshold % (Entry)", type=input.integer, defval=5)
RSIExitThreshold = input(title="PB RSI Threshold % (Exit)", type=input.integer, defval=40)

//----- Backtest Window
startMonth = input(title="Start Month Backtest", type=input.integer,defval=1)
startYear = input(title="Start Year Backtest", type=input.integer, defval=2018)
endMonth = input(title="End Month Backtest", type=input.integer, defval = 12)
endYear = input(title="End Year Backtest", type=input.integer, defval=2021)

//----- Position Size
usePosSize = input(title="Use Position Sizing?", type=input.bool, defval=true)
riskPerc = input(title="Risk %", type=input.float, defval=1, step=0.25)

//----- Stop Loss
atrLen = input(title="ATR Length", type=input.integer, defval=20)
stopLossMulti = input(title="Stop Loss Multiple", type=input.float, defval=2)

// 2. Calculate strategy values
//----- RSI based
RSIEntry = rsi(close,RSIEntryLen)
RSIExit = rsi(close,RSIExitLen)

//----- SMA
slowMA = sma(close,slowMALen)
midMA = sma(close,midMALen)
fastMA = sma(close,fastMALen)

//----- ATR
atrValue = atr(atrLen)

//----- Sideways Detection
[diplus,diminus,adx] = dmi(DiLen,ADXSmoothLen)
is_sideways = adx <= ADXThreshold

//----- Position Size
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = (atrValue * syminfo.pointvalue)
posSize = usePosSize ? floor(riskEquity / atrCurrency) : 1

//----- Trade Window
tradeWindow = time >= timestamp(startYear,startMonth,1,0,0) and time <= timestamp(endYear,endMonth,1,0,0)

// 3. Determine long trading conditions
//----- Entry
enterPB = (close > slowMA) and (RSIEntry < RSIEntryThreshold) and tradeWindow and is_sideways
enterTF = crossover(fastMA,midMA) and (fastMA > midMA) and tradeWindow and not is_sideways and (strategy.position_size < 1)

//----- Bar Count
opened_order = strategy.position_size[0] != strategy.position_size[1] and strategy.position_size[0] != 0
bars = barssince(opened_order) + 1

//----- Stop Loss (CANCELLED)
// stopLoss = 0.0
// stopLoss := enterTF ? close - (atrValue * stopLossMulti) : stopLoss[1]

//----- Exit
exitPB = RSIExit >= RSIExitThreshold or bars >= 10
exitTF = crossunder(fastMA,midMA)

// 4. Output strategy data
plot(series=slowMA, color=color.purple, title="SMA 200", linewidth=2)
plot(series=midMA, color=color.navy, title="SMA 50", linewidth=2)
plot(series=fastMA, color=color.orange, title="SMA 20", linewidth=2)
bgcolor(is_sideways ? color.green : color.red) // Green is sideways trending market region and red is all others.

// 5. Submit entry orders
if(enterPB)
strategy.entry(id="ENTRY-PB", long=true, qty=posSize, limit = open*0.98)
//labelText1 = tostring(round(RSIEntry * 100)/100)
//RSIlabel1 = label.new(x=bar_index,y=na, text=labelText1, yloc=yloc.belowbar,color=color.green, textcolor=color.white, style=label.style_label_up, size=size.normal)

if(enterTF)
strategy.entry(id="ENTRY-TF",long=true, qty=posSize)

// 6. Submit exit orders
if(exitPB)
strategy.close("ENTRY-PB", when = exitPB, qty_percent = 100, comment = "EXIT-PB")

if(exitTF)
strategy.close("ENTRY-TF", when = exitTF, qty_percent = 100, comment = "EXIT-TF")

strategy.close_all(when=not tradeWindow)

// END ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Note
I forget to explain pullback trade entry position. It was found quite often that pullback would experience a gap down before it would be pulled back. To make the entry position I suggest to put limit order on 2% below open price. That's only based on my experience, if you don't patience enough you can try to execute with market order :).
meanreversionpullbacktradeTrend Analysistrendfollowing

Clause de non-responsabilité