Trading From A to Z #5: The Stochastic Oscillator.
Article #5 on the Stochastic Oscillator and How to Use it Optimally.
The Stochastic Oscillator is one of the most common indicators in Technical Analysis. It allows us to have a quick glance as to whether the market is overbought or oversold. As controversial as this oscillator is, its utility is well known and many strategies can be formed around. A good number of such strategies are stable and profitable ones. In this article, we will build the indicator from scratch and discuss some of its simple strategies.
I have just published a new book after the success of my previous one “New Technical Indicators in Python”. It features a more complete description and addition of structured 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.
The Concept of Normalization
This great technique allows us to trap the values between 0 and 1 (or 0 and 100 if we wish to multiply by 100). The concept revolves around subtracting the minimum value in a certain lookback period from the current value and dividing by the maximum value in the same lookback period minus the minimum value (the same in the nominator).
The above chart shows the normalized values of the EURUSD in blue. Whenever the market makes a close higher than all 14 previous closing prices, the normalized indicator will show a reading of 100 and will continue showing it as long as the market keeps making higher closes. Similarly, Whenever the market makes a close lower than all 14 previous closing prices, the normalized indicator will show a reading of 0 and will continue showing it as long as the market keeps making lower closes.
Creating the Stochastic Oscillator Step-By-Step
The Stochastic Oscillator seeks to find oversold and overbought zones by incorporating the highs and lows using the normalization formula as shown below:
An overbought level is an area where the market is perceived to be extremely bullish and is bound to consolidate. An oversold level is an area where market is perceived to be extremely bearish and is bound to bounce. Hence, the Stochastic Oscillator is a contrarian indicator that seeks to signal reactions of extreme movements. We will create the below function that calculates the Stochastic on an OHLC data:
def stochastic(Data, lookback, high, low, close, where, genre = 'High-Low'): # Adding a column Data = adder(Data, 1) if genre == 'High-Low': for i in range(len(Data)): try: Data[i, where] = (Data[i, close] - min(Data[i - lookback + 1:i + 1, low])) / (max(Data[i - lookback + 1:i + 1, high]) - min(Data[i - lookback + 1:i + 1, low])) except ValueError: pass if genre == 'Normalization': for i in range(len(Data)): try: Data[i, where] = (Data[i, close] - min(Data[i - lookback + 1:i + 1, close])) / (max(Data[i - lookback + 1:i + 1, close]) - min(Data[i - lookback + 1:i + 1, close])) except ValueError: pass Data[:, where] = Data[:, where] * 100 Data = jump(Data, lookback) return Data # The Data variable refers to the OHLC array # The lookback variable refers to the period (5, 14, 21, etc.) # The what variable refers to the closing price # The high variable refers to the high price # The low variable refers to the low price # The where variable refers to where to put the Oscillator
The above plot shows the EURUSD values plotted with a 14-period Stochastic Oscillator. Notice that the indicator will always be bounded between 0 and 100 due to the nature of its normalization function that traps values between the minimum and the maximum. The above blue line is called the fast Stochastic %K. Generally, charting systems show the fast Stochastic and a slow Stochastic which is a 3-period moving average applied to it, also referred to as %D.
The default parameters that are generally found are of a 14-period Stochastic. It generally has overbought levels at 20 and oversold levels at 80. We should be careful from taking trades based solely on its signals as it can remain overbought or oversold for a long time. To use the function on an OHLC array, we need to define these functions first.
# 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
Other Variations: The Stochastic Smoothing Oscillator
Now, to get the Stochastic Smoothing Oscillator, we can follow the below steps:
Calculate a 14-period exponential moving average on the highs.
Calculate a 14-period exponential moving average on the lows.
Calculate a 14-period exponential moving average on the close.
Apply the Stochastic formula on the above results.
But how do we calculate an exponential moving average? Well, we have to keep in mind that we have seen the simple moving average in previous articles and now the only difference would be that the exponential version gives more weight to the recent values. Here is the code of the functions.
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 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
And now, to calculate the Stochastic Smoothing Oscillator, we can use the following syntax.
def stochastic_smoothing_oscillator(Data, high, low, close, lookback, where): # Adding columns Data = adder(Data, 4) Data = ema(Data, 2, 2, high, where) Data = ema(Data, 2, 2, low, where + 1) Data = ema(Data, 2, 2, close, where + 2) for i in range(len(Data)): try: Data[i, where + 3] = (Data[i, where + 2] - min(Data[i - lookback + 1:i + 1, where + 1])) / (max(Data[i - lookback + 1:i + 1, where]) - min(Data[i - lookback + 1:i + 1, where + 1])) except ValueError: pass Data[:, where + 3] = Data[:, where + 3] * 100 Data = deleter(Data, where, 3) Data = jump(Data, lookback) return Data
Other Variations: The Stochastic-RSI
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 rsi(Data, lookback, close, where, width = 1, genre = 'Smoothed'): # Adding a few columns Data = adder(Data, 7) # Calculating Differences for i in range(len(Data)): Data[i, where] = Data[i, close] - Data[i - width, 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 if genre == 'Smoothed': 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) if genre == 'Simple': Data = ma(Data, lookback, where + 1, where + 3) Data = ma(Data, 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
We have defined and understood both indicators. Keeping in mind that we will be using the normalization function, let us now code what is necessary to get us to the last step of creating the Stochastic-RSI Indicator based on the below .
# Choosing a lookback period for the RSI lookback = 5 # Calling the 5-period RSI my_data = rsi(my_data, lookback, 3, 4) # Choosing a 100-period for the Stochastic-RSI normalization_lookback = 100 # Creating the Stochastic-RSI Indicator my_data = stochastic(my_data, normalization_lookback, 1, 2, 4, 5, genre = 'Normalization')
It is now clear that the Stochastic-RSI is but a normalized RSI using the primal function of the Stochastic Oscillator. This will give us a form of bounded RSI.
The chart above shows the hourly values of the USDCHF with the Stochastic-RSI in the second panel. It is based on a 5-period RSI and a 100-period normalized values. This means that whenever the new value of the RSI is bigger than the last 100 values, the Stochastic-RSI indicator will show a value of 100.
We can create simple trading rules:
A buy (Long) signal is generated when the Stochastic-RSI is at 0.
A sell (Short) signal is generated when the Stochastic-RSI is at 100.
def signal(Data, stoch_rsi, buy, sell): Data = adder(Data, 10) for i in range(len(Data)): if Data[i, stoch_rsi] == 0 and Data[i - 1, stoch_rsi] > 0 and Data[i - 2, stoch_rsi] > 0: Data[i, buy] = 1elif Data[i, stoch_rsi] == 100 and Data[i - 1, stoch_rsi] < 100 and Data[i - 2, stoch_rsi] < 100: Data[i, sell] = -1 return Data
And here is another example on the hourly values of the EURCHF pair. The signals tend to be early but we can always try to optimize the lookback periods, the conditions, and even combine the indicator with other indicators. Keep in mind that we do not need to complicate things much.
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:
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. That way you can share with me your better strategy and we will get rich together.
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.