Enhanced Divergence Trading Algorithm.
Trading Divergences Based on a Filters to Improve Signals.
Divergences are like the joker card that we like to see when searching for a good trade. We tend to see divergences a little before a strong correction or even a reversal. However, we can always optimize the way to detect a divergence so that we improve our chances of getting a profitable trade. In this article, we will define the divergence technique and see how to add a filter that makes it work better.
I have just released a new book after the success of my previous one “The Book of Trading Strategies”. It features advanced trend-following indicators and strategies with a GitHub page dedicated to the continuously updated code. Also, this book features the original colors after having optimized for printing costs. If you feel that this interests you, feel free to visit the below Amazon link, or if you prefer to buy the PDF version, you could contact me on LinkedIn.
Moving Averages
Moving averages help us confirm and ride the trend. They are the most known technical indicator and this is because of their simplicity and their proven track record of adding value to the analyses. We can use them to find support and resistance levels, stops and targets, and to understand the underlying trend. This versatility makes them an indispensable tool in our trading arsenal.
As the name suggests, this is your plain simple mean that is used everywhere in statistics and basically any other part in our lives. It is simply the total values of the observations divided by the number of observations. Mathematically speaking, it can be written down as:
We can see that the moving average is providing decent dynamic support and resistance levels from where we can place our orders in case the market goes down there. The code for the moving average can be written down as the following:
# The function to add a number of columns inside an array
def adder(Data, times):
for i in range(1, times + 1):
new_col = np.zeros((len(Data), 1), dtype = float)
Data = np.append(Data, new_col, axis = 1)
return Data
# The function to delete a number of columns starting from an index
def deleter(Data, index, times):
for i in range(1, times + 1):
Data = np.delete(Data, index, axis = 1)
return Data
# The function to delete a number of rows from the beginning
def jump(Data, jump):
Data = Data[jump:, ]
return Data
# Example of adding 3 empty columns to an array
my_ohlc_array = adder(my_ohlc_array, 3)
# Example of deleting the 2 columns after the column indexed at 3
my_ohlc_array = deleter(my_ohlc_array, 3, 2)
# Example of deleting the first 20 rows
my_ohlc_array = jump(my_ohlc_array, 20)
# Remember, OHLC is an abbreviation of Open, High, Low, and Close and it refers to the standard historical data file
def ma(Data, lookback, close, where):
Data = adder(Data, 1)
for i in range(len(Data)):
try:
Data[i, where] = (Data[i - lookback + 1:i + 1, close].mean())
except IndexError:
pass
# Cleaning
Data = jump(Data, lookback)
return Data
The Relative Strength Index
The RSI is without a doubt the most famous momentum indicator out there, and this is to be expected as it has many strengths especially in ranging markets. It is also bounded between 0 and 100 which makes it easier to interpret. Also, the fact that it is famous, contributes to its potential.
This is because the more traders and portfolio managers look at the RSI, the more people will react based on its signals and this in turn can push market prices. Of course, we cannot prove this idea, but it is intuitive as one of the basis of Technical Analysis is that it is self-fulfilling.
The RSI is calculated using a rather simple way. We first start by taking price differences of one period. This means that we have to subtract every closing price from the one before it. Then, we will calculate the smoothed average of the positive differences and divide it by the smoothed average of the negative differences. The last calculation gives us the Relative Strength which is then used in the RSI formula to be transformed into a measure between 0 and 100.
To calculate the relative strength index, we need an OHLC array (not a data frame). This means that we will be looking at an array of 4 columns. The function for the relative strength index is therefore:
def ema(Data, alpha, lookback, what, where):
alpha = alpha / (lookback + 1.0)
beta = 1 - alpha
# First value is a simple SMA
Data = ma(Data, lookback, what, where)
# Calculating first EMA
Data[lookback + 1, where] = (Data[lookback + 1, what] * alpha) + (Data[lookback, where] * beta)
# Calculating the rest of EMA
for i in range(lookback + 2, len(Data)):
try:
Data[i, where] = (Data[i, what] * alpha) + (Data[i - 1, where] * beta)
except IndexError:
pass
return Data
def rsi(Data, lookback, close, where):
# Adding a few columns
Data = adder(Data, 5)
# Calculating Differences
for i in range(len(Data)):
Data[i, where] = Data[i, close] - Data[i - 1, close]
# Calculating the Up and Down absolute values
for i in range(len(Data)):
if Data[i, where] > 0:
Data[i, where + 1] = Data[i, where]
elif Data[i, where] < 0:
Data[i, where + 2] = abs(Data[i, where])
# Calculating the Smoothed Moving Average on Up and Down absolute values
lookback = (lookback * 2) - 1 # From exponential to smoothed
Data = ema(Data, 2, lookback, where + 1, where + 3)
Data = ema(Data, 2, lookback, where + 2, where + 4)
# Calculating the Relative Strength
Data[:, where + 5] = Data[:, where + 3] / Data[:, where + 4]
# Calculate the Relative Strength Index
Data[:, where + 6] = (100 - (100 / (1 + Data[:, where + 5])))
# Cleaning
Data = deleter(Data, where, 6)
Data = jump(Data, lookback)
return Data
If you are also interested by more technical indicators and strategies, then my book might interest you:
The Concept of Divergences
Naturally, when prices are rising and making new tops while a price-based indicator is making lower tops, a weakening is occurring and a possibility to change the bias from long to short can present itself. That is what we call a normal divergence. We know that:
When prices are making higher highs while the indicator is making lower highs, it is called a bearish divergence, and the market might stall.
When prices are making lower lows while the indicator is making higher lows, it is called a bullish divergence, and the market might show some upside potential.
The above plot shows the EURUSD in the first panel with the 21-period RSI in the second panel. The bearish divergences are colored in red where we should expect some form of consolidation and the bullish divergences are colored in blue where we should expect some form of upside reaction.
A divergence is preferably considered when the indicator is around extremes. Notice how in the above example, the RSI readings are closer to 70 or 30 when we do detect the divergences. However, we can also consider 60 or 40 as extremes. We will now try to code this in Python.
The main idea is to find divergences that confirm the trend and the way to do this is through long-term moving averages because they will be immune to very short-term movements. Here is how:
When we are looking for bullish divergences, we prefer to do so when the long-term moving average is below the market price. This can be uncommon but it is much better to be in the direction of the trend.
When we are looking for bullish divergences, we prefer to do so when the long-term moving average is below the market price. This can be uncommon but it is much better to be in the direction of the trend.
Naturally, we can use an 800-period moving average and apply the above conditions.
Reminder: We want to apply the moving average condition so that we respect the saying: “The trend is your friend.”
Therefore, we need to calculate the RSI and two 800-period moving averages applied to the highs and lows.
# Calculating a 21-period RSI
my_data = ma(my_data, 800, 3, 4)
# Calculating a long-term moving average
my_data = rsi(my_data, 21, 3, 5)
Now, we have our RSI and the 800-period moving average calculated on the close prices. We have to create a signal function that detects divergences. This can be done using the below function which we will use just after:
def divergence(Data, indicator, lower_barrier, upper_barrier, width, buy, sell):
for i in range(len(Data)):
try:
if Data[i, indicator] < lower_barrier:
for a in range(i + 1, i + width):
# First trough
if Data[a, indicator] > lower_barrier:
for r in range(a + 1, a + width):
if Data[r, indicator] < lower_barrier and \
Data[r, indicator] > Data[i, indicator] and Data[r, 3] < Data[i, 3]:
for s in range(r + 1, r + width):
# Second trough
if Data[s, indicator] > lower_barrier:
Data[s, buy] = 1
break
else:
break
else:
break
else:
break
else:
break
except IndexError:
pass
for i in range(len(Data)):
try:
if Data[i, indicator] > upper_barrier:
for a in range(i + 1, i + width):
# First trough
if Data[a, indicator] < upper_barrier:
for r in range(a + 1, a + width):
if Data[r, indicator] > upper_barrier and \
Data[r, indicator] < Data[i, indicator] and Data[r, 3] > Data[i, 3]:
for s in range(r + 1, r + width):
# Second trough
if Data[s, indicator] < upper_barrier:
Data[s, sell] = -1
break
else:
break
else:
break
else:
break
else:
break
except IndexError:
pass
return Data
Now, we can apply the function as follows:
lower_barrier = 40
upper_barrier = 60
width = 60
# Detecting divergences
my_data = divergence(my_data, 4, lower_barrier, upper_barrier, width, 6, 7)
To create the moving average condition, we can use the below snippet:
for i in range(len(my_data)):
if my_data[i, 6] == 1 and my_data[i, 3] > my_data[i, 4]:
my_data[i, 6] = 1
else:
my_data[i, 6] = 0
if my_data[i, 7] == -1 and my_data[i, 3] < my_data[i, 4]:
my_data[i, 7] = -1
else:
my_data[i, 7] = 0
Notice how in the above plot, the moving average filter has saved us from losing trades and only kept the good ones.
The conclusion is that, it is better to confirm the divergence with another indicator. Sometimes, the trend is just too strong for the divergence to provide something meaningful.
Conclusion
Remember to always do your back-tests. You should always believe that other people are wrong. My indicators and style of trading may work for me but maybe not for you.
I am a firm believer in not spoon-feeding. I have learnt by doing and not by copying. You should get the idea, the function, the intuition, the conditions of the strategy, and then elaborate (an even better) one yourself so that you back-test and improve it before deciding to take it live or to eliminate it. My choice of not providing specific Back-testing results should lead the reader to explore more herself the strategy and work on it more.
To sum up, are the strategies I provide realistic? Yes, but only by optimizing the environment (robust algorithm, low costs, honest broker, proper risk management, and order management). Are the strategies provided only for the sole use of trading? No, it is to stimulate brainstorming and getting more trading ideas as we are all sick of hearing about an oversold RSI as a reason to go short or a resistance being surpassed as a reason to go long. I am trying to introduce a new field called Objective Technical Analysis where we use hard data to judge our techniques rather than rely on outdated classical methods.
One Last Word
I have recently started an NFT collection that aims to support different humanitarian and medical causes. The Society of Light is a set of limited collectibles which will help make the world slightly better as each sale will see a percentage of it sent directly to the charity attributed to the avatar. As I always say, nothing better than a bullet list to outline the benefits of buying these NFT’s:
High-potential gain: By concentrating the remaining sales proceedings on marketing and promoting The Society of Light, I am aiming to maximize their value as much as possible in the secondary market. Remember that trading in the secondary market also means that a portion of royalties will be donated to the same charity.
Art collection and portfolio diversification: Having a collection of avatars that symbolize good deeds is truly satisfying. Investing does not need to only have selfish needs even though there is nothing wrong with investing to make money. But what about investing to make money, help others, and collect art?
Donating to your preferred cause(s): This is a flexible way of allocating different funds to your charities.
A free copy of my book in PDF: Any buyer of any NFT will receive a free copy of my latest book shown in the link of the article.