Structured indicators are the result of fusing two or more together to form a weighted or adjusted indicator that takes into account more variables. For example, we know that there is a Stochastic-RSI indicator which combines the two formulas together in an attempt to improve the signals, this article discusses the creation of an RSI-ATR indicator which adjusts the RSI for the average true range, a measure of volatility.
Start your Free Trial now at O’Reilly and start reading the Early Release of my upcoming book “Mastering Financial Pattern Recognition” which covers everything you need to know about candlestick patterns and how to code them in Python! (Bonus points: You will discover new never-seen-before patterns). You will also find technical indicators, risk management, and even behavioral finance covered in the book!
Mastering Financial Pattern Recognition
Candlesticks have become a key component of platforms and charting programs for financial trading. With these charts…www.oreilly.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:
# 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
# Cleaning
Data = jump(Data, lookback)
return Datadef 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
return Datadef 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
Knowledge must be accessible to everyone. This is why, from now on, a purchase of either one of my new books “Contrarian Trading Strategies in Python” or “Trend Following Strategies in Python” comes with free PDF copies of my first three books (Therefore, purchasing one of the new books gets you 4 books in total). The two new books listed above feature a lot of advanced indicators and strategies with a GitHub page. You can use the below link to purchase one of the two books (Please specify which one and make sure to include your e-mail in the note).
Pay Kaabar using PayPal.Me
Go to paypal.me/sofienkaabar and type in the amount. Since it’s PayPal, it’s easy and secure. Don’t have a PayPal…www.paypal.com
The Average True Range Indicator
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, genre = 'Smoothed'):
# Adding the required columns
Data = adder(Data, 1)
# True Range Calculation
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
if genre == 'Smoothed':
# Average True Range Calculation
Data = ema(Data, 2, lookback, where, where + 1)
if genre == 'Simple':
# Average True Range Calculation
Data = ma(Data, lookback, where, where + 1)
# Cleaning
Data = deleter(Data, where, 1)
Data = jump(Data, lookback)
return Data
The RSI/ATR Indicator
The idea is to divide the values of the RSI by the ATR so that we find a measure adjusted by the recent volatility. However, by doing so, we will find unbounded values, which is why we will apply the RSI formula on the values we find. Therefore, to calculate the RSI/ATR indicator, we follow these steps:
Calculate a 14-period RSI on the market price.
Calculate a 14-period ATR on the market price.
Divide the RSI by the ATR values.
Calculate a 14-period RSI on the results from the last step.
lookback = 14
upper_barrier = 70
lower_barrier = 30
# Calculating a 14-period RSI
my_data = rsi(my_data, lookback, 3, 4)
# Calculating a 14-period ATR
my_data = atr(my_data, lookback, 1, 2, 3, 5)
# Adding a few empty columns
my_data = adder(my_data, 10)
# Dividing the RSI by the ATR
my_data[:, 6] = my_data[:, 4] / my_data[:, 5]
# Calculating the RSI on the values from the last step
my_data = rsi(my_data, lookback, 6, 7)
# Cleaning
my_data = deleter(my_data, 4, 3)
The RSI/ATR resembles the regular RSI but takes into account some volatility measures. It is of course not a perfect indicator nor is it proven that it is better than the RSI but it is very optimizable as it has more variables and is an uncharted territory.
def signal(Data, rsi_col, buy, sell):
Data = adder(Data, 10)
for i in range(len(Data)):
if Data[i, rsi_col] <= lower_barrier and Data[i - 1, buy] == 0 and Data[i - 2, buy] == 0 and Data[i - 3, buy] == 0:
Data[i, buy] = 1
elif Data[i, rsi_col] >= upper_barrier and Data[i - 1, sell] == 0 and Data[i - 2, sell] == 0 and Data[i - 3, sell] == 0:
Data[i, sell] = -1
return Data
my_data = signal(my_data, 4, 6, 7)
The signal charts show the trades taken whenever the 14-period RSI/ATR reaches 30 (For a long position) and 70 (For a short position).
If you want to see how to create all sorts of algorithms yourself, feel free to check out Lumiwealth. From algorithmic trading to blockchain and machine learning, they have hands-on detailed courses that I highly recommend.
Learn Algorithmic Trading with Python Lumiwealth
Learn how to create your own trading algorithms for stocks, options, crypto and more from the experts at Lumiwealth. Click to learn more
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.