Statistical Overbought & Oversold Levels on Technical Indicators
Mean-Reversion Trading with Technical Indicators
We are constantly told about oversold and overbought levels as fixed numbers, but as markets move, so do their structure and statistical properties. We need to find a more objective and dynamic way to detect oversold and overbought levels on technical indicators.
I have released a new book called “Contrarian Trading Strategies in Python”. It features a lot of advanced contrarian indicators and strategies with a GitHub page dedicated to the continuously updated code. If you are interested, you could buy the PDF version directly through a PayPal payment of 9.99 EUR.
Please include your email in the note before paying so that you receive it on the right address. Also, once you receive it, make sure to download it through google drive.
Pay Kaabar using PayPal.Me
If you accept cookies, we’ll use them to improve and customize your experience and enable our partners to show you…www.paypal.com
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:
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
Check out my weekly market sentiment report to understand the current positioning and to estimate the future direction of several major markets through complex and simple models working side by side. Find out more about the report through this link:
Coalescence Report 1st May — 8th May 2022
This report covers the weekly market sentiment and positioning and any changes that have occurred which might present…coalescence.substack.com
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 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
Objective Overbought & Oversold Levels
The way we will do this is by assuming that the levels are dynamic and move according to the recent movements of the RSI. This means that we will not be saying 30/70 are oversold levels but rather, we will calculate them based on a long-term moving average on the RSI. In our case, it will be the 500-period moving average applied on the 21-period RSI.
A long-term moving average with its volatility bands provides dynamic oversold/overbought levels. The parameters and lookback periods can be adjusted accordingly as the trader wishes. Doing so implies that we will be applying the Bollinger bands on the values of the RSI
def bollinger_bands(data, boll_lookback, standard_distance, what, where):
# Adding a few columns
data = adder(data, 2)
# Calculating means
data = ma(data, boll_lookback, what, where)
data = volatility(data, boll_lookback, what, where + 1)
data[:, where + 2] = data[:, where] + (standard_distance * data[:, where + 1])
data[:, where + 3] = data[:, where] - (standard_distance * data[:, where + 1])
data = jump(data, boll_lookback)
data = deleter(data, where, 2)
return data
my_data = rsi(my_data, 21, 3, 4)
my_data = bollinger_bands(my_data, 500, 2, 4, 5)
We have a wide array of choices when using this technique, we can for example:
Use a 200-period RSI with a 50-period moving average and 2.0 standard deviations away.
Use a 5-period RSI with a 100-period moving average and 1.0 standard deviations away.
Therefore, to develop robust strategies, we need to back-test the different combinations and find the good ones.
Summary
To sum up, what I am trying to do is to simply contribute to the world of objective technical analysis which is promoting more transparent techniques and strategies that need to be back-tested before being implemented. This way, technical analysis will get rid of the bad reputation of being subjective and scientifically unfounded.
I recommend you always follow the the below steps whenever you come across a trading technique or strategy:
Have a critical mindset and get rid of any emotions.
Back-test it using real life simulation and conditions.
If you find potential, try optimizing it and running a forward test.
Always include transaction costs and any slippage simulation in your tests.
Always include risk management and position sizing in your tests.
Finally, even after making sure of the above, stay careful and monitor the strategy because market dynamics may shift and make the strategy unprofitable.
For the paperback link of the book, you may use the following link:
Contrarian Trading Strategies in Python
Amazon.com: Contrarian Trading Strategies in Python: 9798434008075: Kaabar, Sofien: Bookswww.amazon.com