Back-testing the RSI divergence strategy on FX in Python. Should it be used as a stand-alone strategy?

This article will deal with a famous technical trading strategy called the divergence. We will define what a divergence is, learn how to code it, and present the results of a back-test over several currency pairs. A reminder, that we can buy the FX pair and short (sell) it whenever we have a trading signal generated by the system we’re about to build together.

There are multiple ways of using an indicator such as the RSI in FX trading, among them:

What is the RSI?

The RSI has been created by J. Welles Wilder in 1978 as a momentum indicator with an optimal look-back period of 14 bars. It seeks to find overbought and oversold zones, in a way that fundamental analysts seek to find overvalued and undervalued assets. Below is a chart of the USDCAD and the RSI using a 14-period lookback.

The formula of the RSI can be found below:

As opposed to Wilder’s original smoothed moving average, the exponential moving average has been used in this study (as in most trading packages).

Coding the divergence through simple walk-forward steps.

Let’s first start by applying the code to the modified RSI (the one that uses the exponential moving average rather than the smoother original average)

`'''Assuming you have a pandas OHLC Dataframe downloaded from Metatrader 5 historical data. '''# Get the difference in price from previous stepData = pd.DataFrame(Data)delta = Data.iloc[:, 3].diff()delta = delta[1:]# Make the positive gains (up) and negative gains (down) Seriesup, down = delta.copy(), delta.copy()up[up < 0] = 0down[down > 0] = 0roll_up = pd.stats.moments.ewma(up, lookback)roll_down = pd.stats.moments.ewma(down.abs(), lookback)# Calculate the SMAroll_up = roll_up[lookback:]roll_down = roll_down[lookback:]Data = Data.iloc[lookback + 1:,].values# Calculate the RSI based on SMARS = roll_up / roll_downRSI = (100.0 - (100.0 / (1.0 + RS)))RSI = np.array(RSI)RSI = np.reshape(RSI, (-1, 1))Data = np.concatenate((Data, RSI), axis = 1)`

Now, we have an array with OHLC data and a fifth column that has the RSI in it. Now, let’s add two columns for the signals such that:

Let’s check the full code below and we’ll understand it one step at a time. Remember that the fifth column Data[:, 4] refers to the RSI values calculated above. The variables we will be using are:

lower_barrier = 30

upper_barrier = 70

width = 10

`1 # Bullish Divergence2 for i in range(len(Data)):3    4    try:5        if Data[i, 4] < lower_barrier:6            7            for a in range(i + 1, i + width):8                if Data[a, 4] > lower_barrier:9                    10                    for r in range(a + 1, a + width):11                       if Data[r, 4] < lower_barrier and \12                        Data[r, 4] > Data[i, 4] and Data[r, 3] <                                     13                        Data[i, 3]:14                            15                            for s in range(r + 1, r + width): 16                                if Data[s, 4] > lower_barrier:17                                    Data[s + 1, 5] = 118                                    break19                                20                                else:21                                    continue22                        else:23                            continue24                    else:25                        continue26                else:27                    continue28                29  except IndexError:30        pass3132 # Bearish Divergence33 for i in range(len(Data)):34    35    try:36        if Data[i, 4] > upper_barrier:37            38            for a in range(i + 1, i + width): 39                if Data[a, 4] < upper_barrier:40                    for r in range(a + 1, a + width):41                        if Data[r, 4] > upper_barrier and \42                        Data[r, 4] < Data[i, 4] and Data[r, 3] >                 43                        Data[i, 3]:44                            for s in range(r + 1, r + width):45                                if Data[s, 4] < upper_barrier:46                                    Data[s + 1, 6] = -147                                    break48                                else:49                                    continue50                        else:51                            continue52                    else:53                        continue54                else:55                    continue56    except IndexError:57        pass`

The width variable refers to the window which we’ll calculate the divergence on. For example, on the chart you may see a big divergence spanning over multiple bars (big width) and you can also see small divergence that happen fast over a few bars (small width). Enlarging the width will make the algorithm take into account those big ones. It is up to the coder to tweak it.

Back-testing and presenting the results.

The performance metrics we will be using are:

The full table below summarized these metrics for each back-test:

In conclusion, it seems that with these parameters and no risk management, divergence trading is not an option. Tweaking, adding stop orders, and fixing the divergence parameters will certainly yield better results. It helps to know what happens if we enlarge the width and narrow the barriers. The conditions of the new below back-test will be:

lower_barrier = 35

upper_barrier = 65

width = 120

holding_period = 30

The slope of the equity curves seems better in the second back-test proving that even with small tweaks, the results can change.

Written by