Multiple Time Frames Trading Signal in Python.
Creating a Multiple Time-Frame Technical Scanner in Python.
Multiple time frame analysis unlocks more potential to a greater analysis quality and this is due to overseeing more signals and patterns. Technical indicators are calculated on a specific time frame, like the hourly time frame. But what do they say when we apply them on 30-minute charts of 2-hour charts? Do they confirm the same bias? This is the idea of the article. We will create a scanner of a technical indicator on 8 different time frames.
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.
The Book of Trading Strategies
Amazon.com: The Book of Trading Strategies: 9798532885707: Kaabar, Sofien: Bookswww.amazon.com
Downloading Historical OHLC Data
One of the most famous trading platforms in the retail community is the MetaTrader5 software. It is a powerful tool that comes with its own programming language and its huge online community support. It also offers the possibility to export its historical short-term and long-term FX data.
The first thing we need to do is to simply download the platform from the official website. Then, after creating the demo account, we are ready to import the library in Python that allows to import the OHLC data from MetaTrader5.
A library is a group of structured functions that can be imported into our Python interpreter from where we can call and use the ones we want.
The easiest way to install the MetaTrader5 library is to go to the Python prompt on our computer and type:
pip install MetaTrader5
This should install the library in our local Python. Now, we want to import it to the Python interpreter (such as PyCharm or SPYDER) so that we can use it. Let us actually import all the libraries we will be using for this:
import datetime # Date acquiring
import pytz # Time zone management
import pandas as pd # Mostly for Data frame manipulation
import MetaTrader5 as mt5 # Importing OHLC data
import matplotlib.pyplot as plt # Plotting charts
import numpy as np # Mostly for array manipulation
Anything that comes after “as” is a shortcut. The plt shortcut is there so that each time we want to call a function from that library we do not have to type the full matplotlib.pyplot statement.
The official documentation for the Metatrader5 library can be found here.
The first thing we can do is to select which time frame we want to import. Let us suppose that there are only two time frames, the 30-minute and the hourly bars. We can therefore create variables that hold the statement to tell the MetaTrader5 library which time frame we want.
# Choosing the 30-minute time frame
frame_M30 = mt5.TIMEFRAME_M30
# Choosing the hourly time frame
frame_H1 = mt5.TIMEFRAME_H1
Then, by staying in the spirit of importing variables, we can define the variable that states what date is it now. This helps the algorithm know the stopping date of the import. We can do this by the simple line of code below.
# Defining the variable now to give out the current date
now = datetime.datetime.now()
Note that these code snippets are better used chronologically, hence, I encourage you to copy them in order and then execute them one by one so that you understand the evolution of what you are doing. The below is a function that holds which assets we want. Generally, I use 10 or more but for simplicity, let us consider that there are only two currency pairs: EURUSD and USDCHF.
def asset_list(asset_set):
if asset_set == 'FX':
assets = ['EURUSD', 'USDCHF']
return assets
Now, with the key function that gets us the OHLC data. The below establishes a connection to MetaTrader5, applies the current date, and extracts the needed data. Notice the arguments year, month, and day. These will be filled by us to select from when do we want the data to start. Note, I have inputed Europe/Paris as my time zone, you should use your time zone to get more accurate data.
def get_quotes(time_frame, year = 2005, month = 1, day = 1, asset = "EURUSD"):
# Establish connection to MetaTrader 5
if not mt5.initialize():
print("initialize() failed, error code =", mt5.last_error())
quit()
timezone = pytz.timezone("Europe/Paris")
utc_from = datetime.datetime(year, month, day, tzinfo = timezone)
utc_to = datetime.datetime(now.year, now.month, now.day + 1, tzinfo = timezone)
rates = mt5.copy_rates_range(asset, time_frame, utc_from, utc_to)
rates_frame = pd.DataFrame(rates)
return rates_frame
And finally, the last function we will use is the one that uses the below get_quotes function and then cleans the results so that we have a nice array. We have selected data since January 2019 as shown below.
def mass_import(asset, horizon):
if horizon == 'M30':
data = get_quotes(frame_M30, 2019, 1, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
return data
Finally, we are done building the blocks necessary to import the data. To import EURUSD OHLC historical data, we simply use the below code line:
# Choosing the horizon
horizon = 'M30'
# Creating an array called EURUSD having M30 data since 2019
EURUSD = mass_import(0, horizon)
And voila, now we have the EURUSD OHLC data from 2019.
Importing Multiple Time Frame Data
The next step is to choose which time frames we want to view in our scanner. Let us keep it short-term and select the 1-minute, 5-minute, 10-minute, 15-minute, 20-minute, 30-minute, hourly, and 2-hour time frames. This gives us 8 charts where we will see the values of the technical indicator which we will define later. The code to import the necessary charts with the selected time frames can be as follows.
def mass_import(asset, horizon):
if horizon == 'MN1':
data = get_quotes(frame_MIN1, 2021, 7, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
if horizon == 'M5':
data = get_quotes(frame_M5, 2021, 6, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)if horizon == 'M10':
data = get_quotes(frame_M10, 2021, 1, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
if horizon == 'M15':
data = get_quotes(frame_M15, 2021, 1, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
if horizon == 'M30':
data = get_quotes(frame_M30, 2016, 8, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
if horizon == 'M20':
data = get_quotes(frame_M20, 2018, 8, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
if horizon == 'H1':
data = get_quotes(frame_H1, 2011, 1, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
if horizon == 'H2':
data = get_quotes(frame_H2, 2010, 1, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
return data
pair = 0
# Mass imports
my_data_MN1 = mass_import(pair, 'MN1')
my_data_M5 = mass_import(pair, 'M5')
my_data_M10 = mass_import(pair, 'M10')
my_data_M15 = mass_import(pair, 'M15')
my_data_M20 = mass_import(pair, 'M20')
my_data_M30 = mass_import(pair, 'M30')
my_data_H1 = mass_import(pair, 'H1')
my_data_H2 = mass_import(pair, 'H2')
Next, we do some formatting by adding a few columns to be populated later.
# 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 my_data_MN1 = adder(my_data_MN1, 10)
my_data_M5 = adder(my_data_M5, 10)
my_data_M10 = adder(my_data_M10, 10)
my_data_M15 = adder(my_data_M15, 10)
my_data_M20 = adder(my_data_M20, 10)
my_data_M30 = adder(my_data_M30, 10)
my_data_H1 = adder(my_data_H1, 10)
my_data_H2 = adder(my_data_H2, 10)
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, 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 need to define the primal manipulation functions first in order to use the RSI’s function on OHLC data arrays.
# 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
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:
New Technical Indicators in Python
Amazon.com: New Technical Indicators in Python: 9798711128861: Kaabar, Mr Sofien: Bookswww.amazon.com
Creating the Multiple Time Frames Scanner
The next step is to calculate the Relative Strength Index on the different arrays we have imported. Up to now, we have 8 arrays each harboring the EURUSD OHLC data with each having a different time frame. We are really interested in the last value to see if the Relative Strength Index is oversold or overbought.
lookback = 13
my_data_MN1 = rsi(my_data_MN1, lookback, 3, 4)
my_data_M5 = rsi(my_data_M5, lookback, 3, 4)
my_data_M10 = rsi(my_data_M10, lookback, 3, 4)
my_data_M15 = rsi(my_data_M15, lookback, 3, 4)
my_data_M20 = rsi(my_data_M20, lookback, 3, 4)
my_data_M30 = rsi(my_data_M30, lookback, 3, 4)
my_data_H1 = rsi(my_data_H1, lookback, 3, 4)
my_data_H2 = rsi(my_data_H2, lookback, 3, 4)
After calculating the indicator on each array, we can create the following conditions that will output the following:
For each array, if the 13-period RSI is lower than 25, a message will appear stating so.
For each array, if the 13-period RSI is greater than 75, a message will appear stating so.
For each array, if the 13-period RSI is between 25 and 75, a message will appear stating so.
upper_barrier = 75
lower_barrier = 25
if my_data_MN1[-1, 4] < lower_barrier:
print('RSI 1-Minute Chart Oversold')
elif my_data_MN1[-1, 4] > upper_barrier:
print('RSI 1-Minute Chart Overbought')
else:
print('RSI 1-Minute Chart Normal')
if my_data_M5[-1, 4] < lower_barrier:
print('RSI 5-Minute Chart Oversold')
elif my_data_M5[-1, 4] > upper_barrier:
print('RSI 5-Minute Chart Overbought')
else:
print('RSI 5-Minute Chart Normal')
if my_data_M10[-1, 4] < lower_barrier:
print('RSI 10-Minute Chart Oversold')
elif my_data_M10[-1, 4] > upper_barrier:
print('RSI 10-Minute Chart Overbought')
else:
print('RSI 10-Minute Chart Normal')
if my_data_M15[-1, 4] < lower_barrier:
print('RSI 15-Minute Chart Oversold')
elif my_data_M15[-1, 4] > upper_barrier:
print('RSI 15-Minute Chart Overbought')
else:
print('RSI 15-Minute Chart Normal')
if my_data_M20[-1, 4] < lower_barrier:
print('RSI 20-Minute Chart Oversold')
elif my_data_M20[-1, 4] > upper_barrier:
print('RSI 20-Minute Chart Overbought')
else:
print('RSI 20-Minute Chart Normal')
if my_data_M30[-1, 4] < lower_barrier:
print('RSI 30-Minute Chart Oversold')
elif my_data_M30[-1, 4] > upper_barrier:
print('RSI 30-Minute Chart Overbought')
else:
print('RSI 30-Minute Chart Normal')
if my_data_H1[-1, 4] < lower_barrier:
print('RSI Hourly Chart Oversold')
elif my_data_H1[-1, 4] > upper_barrier:
print('RSI Hourly Chart Overbought')
else:
print('RSI Hourly Chart Normal')
if my_data_H2[-1, 4] < lower_barrier:
print('RSI 2-Hour Chart Oversold')
elif my_data_H2[-1, 4] > upper_barrier:
print('RSI 2-Hour Chart Overbought')
else:
print('RSI 2-Hour Chart Normal')
# Output RSI 1-Minute Chart Normal
# Output RSI 5-Minute Chart Normal
# Output RSI 10-Minute Chart Normal
# Output RSI 15-Minute Chart Normal
# Output RSI 20-Minute Chart Normal
# Output RSI 30-Minute Chart Normal
# Output RSI Hourly Chart Normal
# Output RSI 2-Hour Chart Normal
The above is the output we should see in case we run the below code. It is a multiple chart view on the different RSI’s where we see the oversold and overbought levels in black. Alongside the printed output from the above code, we should be able to know quickly, where the market stands RSI-wise.
fig, ax = plt.subplots(2, 2, figsize = (10, 7))
ax[0, 0].plot(my_data_MN1[-100:, 4])
ax[0, 0].axhline(y = upper_barrier, color = 'black')
ax[0, 0].axhline(y = lower_barrier, color = 'black')
ax[0, 0].set_title('1-Minute', loc = 'left')
ax[0, 0].grid()ax[1, 0].plot(my_data_M5[-100:, 4])
ax[1, 0].axhline(y = upper_barrier, color = 'black')
ax[1, 0].axhline(y = lower_barrier, color = 'black')
ax[1, 0].set_title('5-Minute', loc = 'left')
ax[1, 0].grid()ax[0, 1].plot(my_data_M10[-100:, 4])
ax[0, 1].axhline(y = upper_barrier, color = 'black')
ax[0, 1].axhline(y = lower_barrier, color = 'black')
ax[0, 1].set_title('10-Minute', loc = 'right')
ax[0, 1].grid()ax[1, 1].plot(my_data_M15[-100:, 4])
ax[1, 1].axhline(y = upper_barrier, color = 'black')
ax[1, 1].axhline(y = lower_barrier, color = 'black')
ax[1, 1].set_title('15-Minute', loc = 'right')
ax[1, 1].grid()
fig, ax = plt.subplots(2, 2, figsize = (10, 7))
ax[0, 0].plot(my_data_M20[-100:, 4])
ax[0, 0].axhline(y = upper_barrier, color = 'black')
ax[0, 0].axhline(y = lower_barrier, color = 'black')
ax[0, 0].set_title('20-Minute', loc = 'left')
ax[0, 0].grid()ax[1, 0].plot(my_data_M30[-100:, 4])
ax[1, 0].axhline(y = upper_barrier, color = 'black')
ax[1, 0].axhline(y = lower_barrier, color = 'black')
ax[1, 0].set_title('30-Minute', loc = 'left')
ax[1, 0].grid()ax[0, 1].plot(my_data_H1[-100:, 4])
ax[0, 1].axhline(y = upper_barrier, color = 'black')
ax[0, 1].axhline(y = lower_barrier, color = 'black')
ax[0, 1].set_title('Hourly', loc = 'right')
ax[0, 1].grid()ax[1, 1].plot(my_data_H2[-100:, 4])
ax[1, 1].axhline(y = upper_barrier, color = 'black')
ax[1, 1].axhline(y = lower_barrier, color = 'black')
ax[1, 1].set_title('2-Hour', loc = 'right')
ax[1, 1].grid()
Conclusion
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.