Mixing Up Powerful Indicators in a Trading Strategy
Can the RSI and the Fisher Transform Provide Good Signals?
The relative strength index (RSI) is a great contrarian indicator. Similarly, the Fisher transform also can hint at possible market inflections. What if we try to combine the two together to give us a timely signal of when a reaction should occur. This means that we will use the RSI and the Fisher transform as a fused indicator that gives out contrarian signals based on its values. The first two parts will introduce the RSI and the Fisher transform and the third part will present the strategy.
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
First introduced by J. Welles Wilder Jr., the RSI is one of the most popular and versatile technical indicators. Mainly used as a contrarian indicator where extreme values signal a reaction that can be exploited. Typically, we use the following steps to calculate the default RSI:
Calculate the change in the closing prices from the previous ones.
Separate the positive net changes from the negative net changes.
Calculate a smoothed moving average on the positive net changes and on the absolute values of the negative net changes.
Divide the smoothed positive changes by the smoothed negative changes. We will refer to this calculation as the Relative Strength — RS.
Apply the normalization formula shown below for every time step to get the RSI.
The above chart shows the hourly values of the GBPUSD in black with the 13-period RSI. We can generally note that the RSI tends to bounce close to 25 while it tends to pause around 75. To code the RSI in Python, we need an OHLC array composed of four columns that cover open, high, low, and close prices.
def add_column(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 delete_column(data, index, times):
for i in range(1, times + 1):
data = np.delete(data, index, axis = 1)
return data
def delete_row(data, number):
data = data[number:, ]
return data
def ma(data, lookback, close, position):
data = add_column(data, 1)
for i in range(len(data)):
try:
data[i, position] = (data[i - lookback + 1:i + 1, close].mean())
except IndexError:
pass
data = delete_row(data, lookback)
return data
def smoothed_ma(data, alpha, lookback, close, position):
lookback = (2 * lookback) - 1
alpha = alpha / (lookback + 1.0)
beta = 1 - alpha
data = ma(data, lookback, close, position)
data[lookback + 1, position] = (data[lookback + 1, close] * alpha) + (data[lookback, position] * beta)
for i in range(lookback + 2, len(data)):
try:
data[i, position] = (data[i, close] * alpha) + (data[i - 1, position] * beta)
except IndexError:
pass
return data
def rsi(data, lookback, close, position):
data = add_column(data, 5)
for i in range(len(data)):
data[i, position] = data[i, close] - data[i - 1, close]
for i in range(len(data)):
if data[i, position] > 0:
data[i, position + 1] = data[i, position]
elif data[i, position] < 0:
data[i, position + 2] = abs(data[i, position])
data = smoothed_ma(data, 2, lookback, position + 1, position + 3)
data = smoothed_ma(data, 2, lookback, position + 2, position + 4)
data[:, position + 5] = data[:, position + 3] / data[:, position + 4]
data[:, position + 6] = (100 - (100 / (1 + data[:, position + 5])))
data = delete_column(data, position, 6)
data = delete_row(data, lookback)
return data
Make sure to focus on the concepts and not the code. You can find the codes of most of my strategies in my books. The most important thing is to comprehend the techniques and strategies.
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 Fisher Transform
Created John F. Ehlers, the indicator seeks to transform the price into a normal Gaussian (normal) distribution. The steps to create the modified Fisher transformation are somewhat similar to the original Fisher Transformation. Here is how we do it:
Select a lookback period and calculate a normalized version of the OHLC data using the original Stochastic formula as seen in the formula below:
Trap the values from the first step between -1 and +1 using the following normalization formula:
Create a condition that eliminates the -1.00’s and +1.00’s and transforms them into -0.999’s and +0.999’s so that we do not get infinite values. This also serves to make the indicator bounded between two levels we will see later.
Apply the following formula to the results from the last step:
Now that we have the Modified Fisher Transformation Indicator, we can proceed by coding it in Python before starting the back-tests. The below plot shows a 5-period Modified Fisher Transformation with subjective boundaries at -2.00 and +2.00.
def stochastic_oscillator(data,
lookback,
high,
low,
close,
where,
slowing = False,
smoothing = False,
slowing_period = 1,
smoothing_period = 1):
data = adder(data, 1)
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
data[:, where] = data[:, where] * 100
if slowing == True and smoothing == False:
data = ma(data, slowing_period, where, where + 1)
if smoothing == True and slowing == False:
data = ma(data, smoothing_period, where, where + 1)
if smoothing == True and slowing == True:
data = ma(data, slowing_period, where, where + 1)
data = ma(data, smoothing_period, where, where + 2)
data = jump(data, lookback) return datadef fisher_transform(data, lookback, high, low, close, where):
data = adder(data, 1)
data = stochastic_oscillator(data, lookback, high, low, close, where)
data[:, where] = data[:, where] / 100
data[:, where] = (2 * data[:, where]) - 1
for i in range(len(data)):
if data[i, where] == 1:
data[i, where] = 0.999
if data[i, where] == -1:
data[i, where] = -0.999
for i in range(len(data)):
data[i, where + 1] = 0.5 * (np.log((1 + data[i, where]) / (1 - data[i, where])))
data = deleter(data, where, 1)
return data
Naturally with the correction I have added, the maximum values will be 3.80 and the minimum values will be -3.80. Therefore, we can say that the indicator is now bounded even though the original version was not shown to be bounded.
Creating the Strategy
The indicator or index we will create is formed by the two indicators above as already mentioned. We will below see how to create it.
Combining indicators and techniques is the first step towards a robust trading system as getting a confluence of signals around a time period reinforces the conviction. There are many ways to create such structured strategies namely, scorecards, indices, and simultaneous conditions. The strategy we will be discussing will revolve around a form of an index where the values from both indicators are transformed to be between 0 and 10. Hence, the RSI-Fisher will be an indicator that ranges between 0 and 20 composed of the RSI (on a scale of 0 to 10) and the modified Fisher transform (on a scale of 0 to 10).
The conditions we will be using for this strategy (as opposed to the above plots) are:
Long (Buy) whenever the 3-period RSI-Fisher combo reaches 4.
Short (Sell) whenever the 3-period RSI-Fisher combo reaches 14.
# Transformation process on the RSI
for i in range(len(my_data)):
if my_data[i, 4] > 0 and my_data[i, 4] <= 10:
my_data[i, 4] = 0
elif my_data[i, 4] > 10 and my_data[i, 4] <= 20:
my_data[i, 4] = 1
elif my_data[i, 4] > 20 and my_data[i, 4] <= 30:
my_data[i, 4] = 2
elif my_data[i, 4] > 30 and my_data[i, 4] <= 40:
my_data[i, 4] = 3
elif my_data[i, 4] > 40 and my_data[i, 4] <= 50:
my_data[i, 4] = 4
elif my_data[i, 4] > 50 and my_data[i, 4] <= 60:
my_data[i, 4] = 5
elif my_data[i, 4] > 60 and my_data[i, 4] <= 70:
my_data[i, 4] = 6
elif my_data[i, 4] > 70 and my_data[i, 4] <= 80:
my_data[i, 4] = 7
elif my_data[i, 4] > 80 and my_data[i, 4] <= 90:
my_data[i, 4] = 8
elif my_data[i, 4] > 90 and my_data[i, 4] <= 100:
my_data[i, 4] = 9
elif my_data[i, 4] == 100:
my_data[i, 4] = 10
# Transformation process on the Modified Fisher Transform
for i in range(len(my_data)):
if my_data[i, 5] > -4 and my_data[i, 5] <= -3.5:
my_data[i, 5] = 0
elif my_data[i, 5] > -3.5 and my_data[i, 5] <= -3:
my_data[i, 5] = 1
elif my_data[i, 5] > -3 and my_data[i, 5] <= -2:
my_data[i, 5] = 2
elif my_data[i, 5] > -2 and my_data[i, 5] <= -1:
my_data[i, 5] = 3
elif my_data[i, 5] > -1 and my_data[i, 5] <= 0:
my_data[i, 5] = 4
elif my_data[i, 5] > 0 and my_data[i, 5] <= 1:
my_data[i, 5] = 5
elif my_data[i, 5] > 1 and my_data[i, 5] <= 2:
my_data[i, 5] = 6
elif my_data[i, 5] > 2 and my_data[i, 5] <= 3:
my_data[i, 5] = 7
elif my_data[i, 5] > 3 and my_data[i, 5] <= 3.5:
my_data[i, 5] = 8
elif my_data[i, 5] > 3.5 and my_data[i, 5] <= 4:
my_data[i, 5] = 9
elif my_data[i, 5] > 4:
my_data[i, 5] = 10
Now, we are ready to write the signal function as mentioned above:
def signal(Data, what, buy, sell):
for i in range(len(Data)):
if Data[i, what] < lower_barrier and Data[i - 1, what] > lower_barrier and Data[i - 2, what] > lower_barrier :
Data[i, buy] = 1
if Data[i, what] > upper_barrier and Data[i - 1, what] < upper_barrier and Data[i - 2, what] < upper_barrier :
Data[i, sell] = -1
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.