Following the Trend With the Adaptive Moving Average.
Creating a New Strategy on the Adaptive Moving Average.
Adaptive moving averages offer the opportunity to take into account multiple variables in order to reflect better the current conditions and to avoid whipsaws. This article discusses a certain type of strategies to be applied on adaptive moving averages.
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.
The Adaptive Moving Average
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:
def ma(Data, lookback, what, where): for i in range(len(Data)): try: Data[i, where] = (Data[i - lookback + 1:i + 1, what].mean()) except IndexError: pass return Data
To use it, we need to have an OHLC data array with an extra empty column. This can be done by using the following code:
def adder(Data, times): for i in range(1, times + 1): new = np.zeros((len(Data), 1), dtype = float) Data = np.append(Data, new, axis = 1) return Data def deleter(Data, index, times): for i in range(1, times + 1): Data = np.delete(Data, index, axis = 1) return Data def jump(Data, jump): Data = Data[jump:, ] return Data
The above 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.
The Kaufman’s Adaptive Moving Average — KAMA has been created to reduce the noise and whipsaw effects. It works the same as other moving averages do and follows the same intuition. The generation of false trading signals is one of the problems with moving averages and this is due to short-term sudden fluctuations that bias the calculation. The KAMA’s primary objective is to reduce as much noise as possible.
The first concept we should measure is the Efficiency Ratio which is the absolute change of the current close relative to the change in the past 10 periods divided by a type of volatility calculated in a special manner. We can say that the Efficiency Ratio is the change divided by volatility.
Then we calculate a smoothing constant based on the following formula:
Finally, to calculate the KAMA we use the below formula:
The calculation may seem complicated but it is easily automated and you do not have to think about it much. Let us see how to code it in Python and then proceed by seeing some examples.
def kama(Data, what, where, lookback): Data = adder(Data, 10) # lookback from previous period for i in range(len(Data)): Data[i, where] = abs(Data[i, what] - Data[i - 1, what]) Data[0, where] = 0 # Sum of lookbacks for i in range(len(Data)): Data[i, where + 1] = (Data[i - lookback + 1:i + 1, where].sum()) # Volatility for i in range(len(Data)): Data[i, where + 2] = abs(Data[i, what] - Data[i - lookback, what]) Data = Data[lookback + 1:, ] # Efficiency Ratio Data[:, where + 3] = Data[:, where + 2] / Data[:, where + 1] for i in range(len(Data)): Data[i, where + 4] = np.square(Data[i, where + 3] * 0.6666666666666666667) for i in range(len(Data)): Data[i, where + 5] = Data[i - 1, where + 5] + (Data[i, where + 4] * (Data[i, what] - Data[i - 1, where + 5])) Data[11, where + 5] = 0 Data = deleter(Data, where, 5) Data = jump(Data, lookback * 2) return Data
Creating the Strategy
The strategy in its basic form is a simple crossover where prices crossing above the moving average line signal a bullish bias while prices crossing below the moving average line signal a bearish bias. However, with the adaptive moving average, we can take advantage of an extra elements, the flat lines. Generally, flat lines hint to a possible sideways movement and thus, a false signal might occur whenever we cross the moving average. Taking this into account, we will have the following trading conditions:
A long (Buy) signal is generated whenever the market price surpasses the adaptive moving average on the condition that the current level of the moving average does not equal the previous one.
A short (Sell) signal is generated whenever the market price breaks the adaptive moving average on the condition that the current level of the moving average does not equal the previous one.
The above chart shows the benefit of imposing the condition of the flat line. Notice how the first time the market surpasses the moving average, no signal has been generated as it forecasted a false upside breakout which was true. However, a bearish signal was generated on the break of the moving average line because there was no flat line.
Similarly, the bullish signal that has been generated was of good quality because even though the market has re-integrated the moving average, it did so with a flat line so the bullish signal was still valid and the algorithm dismissed it as just noise before the rise which was the case.
def signal(Data, close, ma_column, buy, sell): Data = adder(Data, 10) for i in range(len(Data)): if Data[i, close] > Data[i, ma_column] and Data[i - 1, close] < Data[i - 1, ma_column] and \ Data[i, ma_column] != Data[i - 1, ma_column]: Data[i, buy] = 1 elif Data[i, close] < Data[i, ma_column] and Data[i - 1, close] > Data[i - 1, ma_column] and \ Data[i, ma_column] != Data[i - 1, ma_column]: Data[i, sell] = -1 return Data
Extensive back-tests are required in order to make the strategy work but the potential is clearly here.
If you are also interested by more technical indicators and strategies, then my book might interest you:
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.
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.