The Countdown Indicator in Python — Modern Technical Analysis.
Creating the Countdown Indicator for Contrarian Trading in Python.
Research always has its fruits and through the huge number of trading tools, we have to be efficient at filtering the good from the bad. Contrarian Indicators exist in many forms and in this article, we will discuss a personal indicator that I have developed a while ago. It is based on the concept of pressure and is used to detect reversals and corrections.
I have just published a new book after the success of New Technical Indicators in Python. It features a more complete description and addition of complex trading strategies with a Github page dedicated to the continuously updated code. If you feel that this interests you, feel free to visit the below link, or if you prefer to buy the PDF version, you could contact me on Linkedin.
Creating the Countdown Indicator Step-by-Step
The Countdown Indicator has been created in order to include the totality of the OHLC data inside a framework that calculates when we are around an extreme and when we are bound to reverse. I have made some optimizations and finally arrived at the following ways to construct the indicator. All we need, is OHLC data. The Countdown Indicator should be used in conjunction with other technical indicators to get a better confirmation. The below are the steps used to create it with the function in Python later given so that it can be copied and pasted.
Calculate the Upside Pressure. The calculation is simply a summation of two conditions:
If the current close is greater than the opening price, then we have 1 point in the Upside Pressure calculation. Next, if the current high is greater than the high from the last period, we have another point. At its maximum, the Upside Pressure can only have 2 points while at its minimum, it can have zero points. If the current close is greater than the open while the current high is lower than the previous high, the Upside Pressure should have 1 point.
Calculate the Downside Pressure. The calculation is simply a summation of two conditions:
If the current close is lower than the opening price, then we have 1 point in the Downside Pressure calculation. Next, if the current low is lower than the low from the last period, we have another point. At its maximum, the Downside Pressure can only have 2 points while at its minimum, it can have zero points. If the current close is lower than the open while the current low is higher than the previous low, the Downside Pressure should have 1 point.
Calculate the 8-period Cumulative Upside and Downside pressures. This is a simple summation of the last 8 pressures including the current one:
Next, the Countdown Indicator (Raw form) is simply the difference between the Cumulative Upside Pressure and the Cumulative Downside Pressure which then we can simply calculate a 3-period Exponential Moving Average on:
To code the Indicator, we need to have an OHLC array (not a data frame) and define the following three small manipulation functions:
# The function to add a certain number of columns def adder(Data, times): for i in range(1, times + 1): z = np.zeros((len(Data), 1), dtype = float) Data = np.append(Data, z, axis = 1) return Data # The function to deleter a certain number of columns 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 certain number of rows from the beginning def jump(Data, jump): Data = Data[jump:, ] return Data
Now we are ready to code the Countdown Indicator:
def countdown_indicator(Data, lookback, ma_lookback, opening, high, low, close, where): # Adding columns Data = adder(Data, 20) # Calculating Upside Pressure for i in range(len(Data)): if Data[i, close] > Data[i, opening]: Data[i, where] = 1if Data[i, high] > Data[i - 1, high]: Data[i, where + 1] = 1 Data[:, where + 2] = Data[:, where] + Data[:, where + 1] Data = deleter(Data, where, 2) # Calculating Downside Pressure for i in range(len(Data)): if Data[i, close] < Data[i, opening]: Data[i, where + 1] = 1if Data[i, low] < Data[i - 1, low]: Data[i, where + 2] = 1 Data[:, where + 3] = Data[:, where + 1] + Data[:, where + 2] Data = deleter(Data, where + 1, 2) # Calculate Cumulative Upside Pressure for i in range(len(Data)): Data[i, where + 2] = Data[i - lookback + 1:i + 1, where].sum() # Calculate Cumulative Downside Pressure for i in range(len(Data)): Data[i, where + 3] = Data[i - lookback + 1:i + 1, where + 1].sum() # Calculate the Countdown Indicator Data[:, where + 4] = Data[:, where + 2] - Data[:, where + 3] Data = ema(Data, 2, ma_lookback, where + 4, where + 5) Data = deleter(Data, where, 5) Data = jump(Data, lookback) return Data # Using the Function my_data = countdown_indicator(my_data, lookback, ma_lookback, 0, 1, 2, 3, 4)
If you are also interested by more technical indicators and using Python to create strategies, then my best-selling book on Technical Indicators may interest you:
Creating & Evaluating the Strategy
Obviously, the indicator is counter-cyclical and therefore, we can derive these simple trading rules:
A bullish bias can be generated when the indicator gets close to its bottom at -10 / -12 area.
A bearish bias can be generated when the indicator gets close to its top at 10 / 12 area.
The barriers are subjective of course and the indicator can only be used in confirmation of other more sophisticated signals. The below plot shows the Countdown Indicator with periods (13, 13) versus the EURUSD.
Having had the signals, we now know when the algorithm would have placed its buy and sell orders, meaning, that we have an approximate replica of the past where can can control our decisions with no hindsight bias. We have to simulate how the strategy would have done given our conditions. This means that we need to calculate the returns and analyze the performance metrics. Let us see a neutral metric that can give us somewhat a clue on the predictability of the indicator or the strategy. For this study, we will use the Signal Quality metric.
The signal quality is a metric that resembles a fixed holding period strategy. It is simply the reaction of the market after a specified time period following the signal. Generally, when trading, we tend to use a variable period where we open the positions and close out when we get a signal on the other direction or when we get stopped out (either positively or negatively).
Sometimes, we close out at random time periods. Therefore, the signal quality is a very simple measure that assumes a fixed holding period and then checks the market level at that time point to compare it with the entry level. In other words, it measures market timing by checking the reaction of the market after a specified time period.
# Choosing a Holding Period for a trend-following strategy period = 13 def signal_quality(Data, closing, buy, sell, period, where): Data = adder(Data, 1) for i in range(len(Data)): try: if Data[i, buy] == 1: Data[i + period, where] = Data[i + period, closing] - Data[i, closing] if Data[i, sell] == -1: Data[i + period, where] = Data[i, closing] - Data[i + period, closing] except IndexError: pass return Data # Applying the Signal Quality Function my_data = signal_quality(my_data, 3, 6, 7, period, 8) positives = my_data[my_data[:, 8] > 0] negatives = my_data[my_data[:, 8] < 0] # Calculating Signal Quality signal_quality = len(positives) / (len(negatives) + len(positives)) print('Signal Quality = ', round(signal_quality * 100, 2), '%') # Output Signal Quality EURUSD = 52.86%
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 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.