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.

Image for post
Image for post
USDCAD with RSI divergences and oversold/overbough levels

The formula of the RSI can be found below:

Image for post
Image for post
The RSI formula

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 step
Data = pd.DataFrame(Data)
delta = Data.iloc[:, 3].diff()
delta = delta[1:]
# Make the positive gains (up) and negative gains (down) Series
up, down = delta.copy(), delta.copy()
up[up < 0] = 0
down[down > 0] = 0
roll_up = pd.stats.moments.ewma(up, lookback)
roll_down = pd.stats.moments.ewma(down.abs(), lookback)
# Calculate the SMA
roll_up = roll_up[lookback:]
roll_down = roll_down[lookback:]
Data = Data.iloc[lookback + 1:,].values
# Calculate the RSI based on SMA
RS = roll_up / roll_down
RSI = (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 Divergence
2 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] = 1
18 break
19
20 else:
21 continue
22 else:
23 continue
24 else:
25 continue
26 else:
27 continue
28
29 except IndexError:
30 pass
31
32 # Bearish Divergence
33 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] = -1
47 break
48 else:
49 continue
50 else:
51 continue
52 else:
53 continue
54 else:
55 continue
56 except IndexError:
57 pass
Image for post
Image for post
Divergence steps summarized

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:

Image for post
Image for post
Image for post
Image for post
The equity curve from the strategy: Blue: EURUSD, Orange: USDCHF, Green: GBPUSD, Red: USDCAD, Purple: AUDUSD, Brown: NZDUSD, Pink: EURCAD

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

Image for post
Image for post
Image for post
Image for post
The equity curve from the strategy: Blue: EURUSD, Orange: USDCHF, Green: GBPUSD, Red: USDCAD, Purple: AUDUSD, Brown: NZDUSD, Pink: EURCAD

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

Image for post
Image for post
https://ak.picdn.net/shutterstock/videos/9134366/thumb/1.jpg

Written by

Institutional FOREX Strategist | Trader | Data Science Enthusiast. Author of the Book of Back-tests: https://www.amazon.com/dp/B089CWQWF8

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store