Trend-Following With the MACD & the SuperTrend
Creating a Combined Strategy Using the MACD & the SuperTrend
Combining strategies and indicators is always the right path towards a robust technical or quantitative trading system. In this article, the quest continues towards combining different elements in the hope of finding a reliable system. We will code and discuss the MACD and the SuperTrend so that they provide meaningful and intuitive signals.
I have just released a new book after the success of the previous book. 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.
Trend Following Strategies in Python: How to Use Indicators to Follow the Trend.
Amazon.com: Trend Following Strategies in Python: How to Use Indicators to Follow the Trend.: 9798756939620: Kaabar…www.amazon.com
The MACD Oscillator
The MACD is probably the second most known oscillator after the RSI. One that is heavily followed by traders. It stands for moving average convergence divergence and it is used mainly for divergences and flips. Many people also consider it a trend-following indicator but others use graphical analysis on it to find reversal points, making the MACD a versatile indicator.
Before we start building this oscillator, we must understand what moving averages are and how do we code them. 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:
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
data = jump(data, lookback)
return data
The below states that the moving average function will be called on the array named my_data for a lookback period of 200, on the column indexed at 3 (closing prices in an OHLC array). The moving average values will then be put in the column indexed at 4 which is the one we have added using the adder function.
my_data = ma(my_data, 200, 3, 4)
An exponential moving average is a special type that gives more weight to the recent values. To spare you the boring details, here is the code.
def ema(data, alpha, lookback, what, where):
alpha = alpha / (lookback + 1.0)
beta = 1 - alpha
data = ma(data, lookback, what, where) data[lookback + 1, where] = (data[lookback + 1, what] * alpha) + (data[lookback, where] * beta)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
How is the MACD calculated? It is the difference between the 26-period exponential moving average applied to the closing price and the 12-period exponential moving average also applied to the closing price. The value found after taking the difference is called the MACD line. The 9-period exponential moving average of that calculation is called the MACD signal.
def macd(Data, what, long_ema, short_ema, signal_ema, where):
Data = adder(Data, 1)
Data = ema(Data, 2, long_ema, what, where)
Data = ema(Data, 2, short_ema, what, where + 1)
Data[:, where + 2] = Data[:, where + 1] - Data[:, where]
Data = jump(Data, long_ema)
Data = ema(Data, 2, signal_ema, where + 2, where + 3)
Data = deleter(Data, where, 2)
Data = jump(Data, signal_ema)
return Data
The SuperTrend Indicator
The first concept we should understand before creating the SuperTrend indicator is volatility. We sometimes measure volatility using the average true range. Although the ATR is considered a lagging indicator, it gives some insights as to where volatility is right now and where has it been last period (day, week, month, etc.). But before that, we should understand how the true range is calculated (the ATR is just the average of that calculation). The true range is simply the greatest of the three price differences:
High — Low
| High — Previous close |
| Previous close — Low |
Once we have gotten the maximum out of the above three, we simply take an average of n periods of the true ranges to get the average true range. Generally, since in periods of panic and price depreciation we see volatility go up, the ATR will most likely trend higher during these periods, similarly in times of steady uptrends or downtrends, the ATR will tend to go lower.
One should always remember that this indicator is lagging and therefore has to be used with extreme caution. Below is the function code that calculates the ATR. Make sure you have an OHLC array of historical data.
def atr(data, lookback, high, low, close, where):
data = adder(data, 1)
for i in range(len(data)):
try:
data[i, where] = max(data[i, high] - data[i, low], abs(data[i, high] - data[i - 1, close]), abs(data[i, low] - data[i - 1, close]))
except ValueError:
pass
data[0, where] = 0
data = ema(data, 2, (lookback * 2) - 1, where, where + 1)
data = deleter(data, where, 1)
data = jump(data, lookback)
return data
Now that we have understood what the ATR is and how to calculate it, we can proceed further with the SuperTrend indicator. The indicator seeks to provide entry and exit levels for trend followers. You can think of it as a moving average or an MACD. Its uniqueness is its main advantage and although its calculation method is much more complicated than the other two indicators, it is intuitive in nature and not that hard to understand. Basically, we have two variables to choose from. The ATR lookback and the multiplier’s value. The former is just the period used to calculated the ATR while the latter is generally an integer (usually 2 or 3).
The first thing to do is to average the high and low of the price bar, then we will either add or subtract the average with the selected multiplier multiplied by the ATR as shown in the above formulas. This will give us two arrays, the basic upper band and the basic lower band which form the first building blocks in the SuperTrend indicator. The next step is to calculate the final upper band and the final lower band using the below formulas.
It may seem complicated but most of these conditions are repetitive and in any case, I will provide the Python code below so that you can play with the function and optimize it to your trading preferences. Finally, using the previous two calculations, we can find the SuperTrend.
def supertrend(data, multiplier, high, low, close, atr_col, where):
data = adder(data, 6)
for i in range(len(data)):
data[i, where] = (data[i, high] + data[i, low]) / 2
data[i, where + 1] = data[i, where] + (multiplier * data[i, atr_col])
data[i, where + 2] = data[i, where] - (multiplier * data[i, atr_col])
for i in range(len(data)):
if i == 0:
data[i, where + 3] = 0
else:
if (data[i, where + 1] < data[i - 1, where + 3]) or (data[i - 1, close] > data[i - 1, where + 3]):
data[i, where + 3] = data[i, where + 1]
else:
data[i, where + 3] = data[i - 1, where + 3]
for i in range(len(data)):
if i == 0:
data[i, where + 4] = 0
else:
if (data[i, where + 2] > data[i - 1, where + 4]) or (data[i - 1, close] < data[i - 1, where + 4]):
data[i, where + 4] = data[i, where + 2]
else:
data[i, where + 4] = data[i - 1, where + 4]
for i in range(len(data)):
if i == 0:
data[i, where + 5] = 0
elif (data[i - 1, where + 5] == data[i - 1, where + 3]) and (data[i, close] <= data[i, where + 3]):
data[i, where + 5] = data[i, where + 3]
elif (data[i - 1, where + 5] == data[i - 1, where + 3]) and (data[i, close] > data[i, where + 3]):
data[i, where + 5] = data[i, where + 4]
elif (data[i - 1, where + 5] == data[i - 1, where + 4]) and (data[i, close] >= data[i, where + 4]):
data[i, where + 5] = data[i, where + 4]
elif (data[i - 1, where + 5] == data[i - 1, where + 4]) and (data[i, close] < data[i, where + 4]):
data[i, where + 5] = data[i, where + 3]
data = deleter(data, where, 5)
data = jump(data, 1)
return data
The above chart shows the hourly values of the EURUSD with a 10-period SuperTrend (represented by the ATR period) and a multiplier of 3. The way we should understand the indicator is that when it goes above the market price, we should be looking to short and when it goes below the market price, we should be looking to go long as we anticipate a bullish trend. Remember that the SuperTrend is a trend-following indicator. The aim here is to capture trends at the beginning and to close out when they are over.
If you are also interested by more technical indicators and strategies, then my book might interest you:
The Book of Trading Strategies
Amazon.com: The Book of Trading Strategies: 9798532885707: Kaabar, Sofien: Bookswww.amazon.com
Creating the Signals
As with any proper research method, the aim is to test the strategy and to be able to see for ourselves whether it is worth having as an add-on to our pre-existing trading framework or not. The below plot shows a 2-period ATR, 2x multiplier SuperTrend with a standard MACD(26, 12). We can omit the signal line so that we do not put a lot of filters into the strategy.
The first step is creating the trading rules. When will the system buy and when will it go short? In other words, when is the signal given that tells the system that the current market will go up or down? The trading conditions we can choose are:
Long (Buy) whenever MACD turns positive while the market price is above its SuperTrend Indicator.
Short (Sell) whenever MACD turns negative while the market price is below its SuperTrend Indicator.
The above chart shows the signals generated from the system. We have to keep in mind the frequency of the signals when we are developing a trading algorithm. The signal function used to generate the triggers based on the conditions mentioned above can be found in this snippet:
def signal(Data, close, macd_col, super_trend_col, buy, sell):
Data = adder(Data, 10)
for i in range(len(Data)):
if Data[i, macd_col] > 0 and Data[i, close] > Data[i, super_trend_col] and Data[i - 1, macd_col] < 0:
Data[i, buy] = 1
elif Data[i, macd_col] < 0 and Data[i, close] < Data[i, super_trend_col] and Data[i - 1, macd_col] > 0:
Data[i, sell] = -1
return Data
The above shows the same technique applied onto hourly values of the USDCHF. Without a doubt, lagging indicators will have a tough time detecting the trend at its beginning, which is why we sometimes see some bullish signals around the tops and bearish signals around the bottoms.
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 of 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.
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.