Creating an Automatic Support & Resistance Scanner in Python
How to Create a Scanner of Support/Resistance Levels Using Pivot Points
Each morning when we want to start looking at the markets, we need multiple scanners and quick interpreters that give us our basic needs so that we quickly get ready. Some developers have full signal scanners and trading bots while others prefer to rely on simpler measures such as technical scanners. This article discusses a scanner that loops around the market and charts support & resistance lines using a famous and objective method.
I have released a new book after the success of my previous one “Trend Following Strategies in Python”. It features advanced contrarian indicators and strategies with a GitHub page dedicated to the continuously updated code. If you feel that this interests you, feel free to visit the below Amazon link (which contains a sample), or if you prefer to buy the PDF version, you could check the link at the end of the article.
Contrarian Trading Strategies in Python
Amazon.com: Contrarian Trading Strategies in Python: 9798434008075: Kaabar, Sofien: Bookswww.amazon.com
The Concept of Support & Resistance Levels
At its purest form, a support level is an area supposed to keep prices from going down further. Naturally, when the market price approaches a support area, the right decision is to have a bullish bias.
A resistance level is an area supposed to keep prices from going up further. Naturally, when the market price approaches a resistance area, the right decision is to have a bearish bias. The above chart shows the support line in green and the resistance line in blue drawn subjectively based on empirical observation. It is known that finding support and resistance levels can be done in many ways:
Charting: This technique is mostly subjective and uses the past reactions as pin points for the future. We can draw ascending or descending trend lines as well as horizontal support and resistance levels. The flaw of this technique is that it can be very subjective and can differ from one trader to another.
Fibonacci: This technique is less subjective and uses the Fibonacci sequence (discussed in previous articles) as retracements to find reactionary levels.
Moving Averages: This technique is objective and uses moving averages as dynamic support and resistance levels to follow market reactions. The flaw of this technique is that there is a huge number of periods and types of moving averages to choose from where the reliability differs.
Pivot Points: This technique is objective and uses an old method of calculating the future implied support and resistance lines. It has stood the test of time and continues to be an efficient and quick way of analyzing the market.
Pivot Points
Price action trading is based purely on price rather than its derivatives such as technical indicators. It enjoys a huge advantage in that it is not lagging. Price is what is happening right now and is either leading or coinciding but never lagging. Price action is all about detecting patterns and finding support and resistance levels. One way to do this is through simple mathematical calculations referred to as Pivot Points.
Pivot points are calculations used to find implied support and resistance levels. They are very simple and very easy to use. Some intraday traders calculate them every day and project levels that last for the duration of the trading day. Other traders calculate the levels on a monthly and on a yearly basis. For example, on the first day of January, a trader might calculate the projected support and resistance levels using last year’s values. The formulas and types of Pivot Points will be discussed below.
Note: For this article, we will be calculating daily pivot points from midnight to midnight Paris time. This is just an example and may not be the best way to do it but time zones remain a huge topic in the FX market.
What is a Pivot Point and how is it used? Basically, it can be used as a magnet where the market should revert back to it in case it deviates away. Another method is to use it as the profit level where after initiating a reversal position, we can target the Pivot Point as an exit after having opened a contrarian position off the resistance or the support level.
The plot above shows the USDCAD hourly values where the calculation period is the previous day. We take the high, low, and close of the previous day, apply the above formulas and get the support and resistance levels for today (assuming we start very early in the morning). The zone in light grey above shows the trading zone where we already have the defined support at 1.2030 and the defined resistance at 1.2100. Basically, when the market closed the previous day, we had the following HLC data:
High = 1.20950
Low = 1.20275
Close = 1.20650
Following the above formulas, our Pivot Point should be 1.2062 and the first support should be 1.2030. The first resistance is 1.2100. As the trading zone commences, we monitor the levels and try to trade them whenever the market approaches them. Now, we will see how to develop the simple scanner.
The first question to ask ourselves is what do we expect from the scanner? In our case, we want a code that when executed will give us a few charts with the recent candlestick data and the implied support & resistance levels together to give us an idea on where we stand with regards to the Pivot Points technique.
Let us develop more the asset_list function seen earlier so that it contains more currency pairs.
def asset_list(asset_set):
if asset_set == 'FX':
# A selected bunch of currency pairs
assets = ['EURUSD', 'USDCHF', 'GBPUSD', 'AUDUSD', 'NZDUSD',
'USDCAD', 'EURCAD', 'EURGBP', 'EURCHF', 'AUDCAD']
elif asset_set == 'CRYPTO':
# Example of cryptocurrencies
assets = ['BTCUSD', 'ETHUSD', 'XRPUSD', 'LTCUSD','DSHUSD',
'XMRUSD', 'ETHBTC']
elif asset_set == 'COMMODITIES':
# Example of commodities
assets = ['XAUUSD', 'XAGUSD', 'XPTUSD', 'XPDUSD']
return assets
Now we select the needed type, which is FX in our case.
assets = asset_list('FX')
Now, we define the get_quotes function again using today’s date.
def get_quotes(time_frame, year = 2021, month = 8, day = 10, 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, tzinfo = timezone)
rates = mt5.copy_rates_range(asset, time_frame, utc_from, utc_to)
rates_frame = pd.DataFrame(rates)
return rates_frame
The next step is to download the Daily data and not the short-term data for now so that the calculation becomes easier.
def mass_import(asset, horizon):
if horizon == 'D1':
data = get_quotes(frame_D1, 2021, 5, 25, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
return data
horizon = 'D1'
# Mass imports
Asset1 = mass_import(0, horizon) # EURUSD
Asset2 = mass_import(1, horizon) # USDCHF
Asset3 = mass_import(2, horizon) # GBPUSD
Asset4 = mass_import(3, horizon) # AUDUSD
Asset5 = mass_import(4, horizon) # NZDUSD
Asset6 = mass_import(5, horizon) # USDCAD
Asset7 = mass_import(6, horizon) # EURCAD
Asset8 = mass_import(7, horizon) # EURGBP
Asset9 = mass_import(8, horizon) # EURCHF
Asset10 = mass_import(9, horizon) # AUDCAD
We should now see 10 arrays containinf the daily historical data. The next important step is defining the Pivot Points function so that the algorithm does the calculation of the support & resistance levels:
def pivot_point_calculator(Data, n, type = 'Classic'):
levels = []
pivot = (Data[-1, 1] + Data[-1, 2] + Data[-1, 3]) / 3
pivot = round(pivot, 4)
levels = np.append(levels, pivot)
first_support = ((pivot * 2) - Data[-1, 1])
first_support = round(first_support, 4)
levels = np.append(levels, first_support)
first_resistance = ((pivot * 2) - Data[-1, 2])
first_resistance = round(first_resistance, 4)
levels = np.append(levels, first_resistance)
print(assets[n])
print('Pivot = ', pivot)
print('First Support = ', first_support)
print('First Resistance = ', first_resistance)
print('----')
return levels
# Selecting only 6 of the currency pairs
levels1 = pivot_point_calculator(Asset1, 0)
levels2 = pivot_point_calculator(Asset2, 1)
levels3 = pivot_point_calculator(Asset3, 2)
levels6 = pivot_point_calculator(Asset6, 5)
levels7 = pivot_point_calculator(Asset7, 6)
levels8 = pivot_point_calculator(Asset8, 7)
The reason we have selected only 6 of the 10 currency pairs we have imported is just in case we are not interested in trading all of them. However, we can select any currency pair and replace its name inside the function.
Now, we need to re-import everything as we need OHLC hourly data as opposed to daily data. Remember, we are calculating on a daily basis but charting on an hourly basis.
def get_quotes(time_frame, year = 2021, month = 8, day = 10, 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_framedef mass_import(asset, horizon):
if horizon == 'H1':
data = get_quotes(frame_H1, 2021, 6, 1, asset = assets[asset])
data = data.iloc[:, 1:5].values
data = data.round(decimals = 5)
return data
horizon = 'H1'
# Mass imports
Asset1 = mass_import(0, horizon)
Asset2 = mass_import(1, horizon)
Asset3 = mass_import(2, horizon)
Asset6 = mass_import(5, horizon)
Asset7 = mass_import(6, horizon)
Asset8 = mass_import(7, horizon)
The final touches are in for the plotting function which should give us the below plot.
# In case it is around 9 AM and we want the past data since midnight to be charted
past = 9
Asset1 = Asset1[-past:, ]
Asset2 = Asset2[-past:, ]
Asset3 = Asset3[-past:, ]
Asset6 = Asset6[-past:, ]
Asset7 = Asset7[-past:, ]
Asset8 = Asset8[-past:, ]
fig, axs = plt.subplots(3, 2)Chosen = Asset1[-past:, ]for i in range(len(Chosen)):
axs[0, 0].vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'black', linewidth = 1)
if Chosen[i, 3] > Chosen[i, 0]:
color_chosen = 'teal'
axs[0, 0].vlines(x = i, ymin = Chosen[i, 0], ymax = Chosen[i, 3], color = color_chosen, linewidth = 2)
if Chosen[i, 3] < Chosen[i, 0]:
color_chosen = 'purple'
axs[0, 0].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
if Chosen[i, 3] == Chosen[i, 0]:
color_chosen = 'black'
axs[0, 0].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
axs[0, 0].grid()
axs[0, 0].set_title('EURUSD')
axs[0, 0].axhline(y = levels1[1])
axs[0, 0].axhline(y = levels1[2])Chosen = Asset2[-past:, ]
for i in range(len(Chosen)):
axs[0, 1].vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'black', linewidth = 1)
if Chosen[i, 3] > Chosen[i, 0]:
color_chosen = 'teal'
axs[0, 1].vlines(x = i, ymin = Chosen[i, 0], ymax = Chosen[i, 3], color = color_chosen, linewidth = 2)
if Chosen[i, 3] < Chosen[i, 0]:
color_chosen = 'purple'
axs[0, 1].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
if Chosen[i, 3] == Chosen[i, 0]:
color_chosen = 'black'
axs[0, 1].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
axs[0, 1].grid()
axs[0, 1].set_title(assets[1])
axs[0, 1].axhline(y = levels2[1])
axs[0, 1].axhline(y = levels2[2])
Chosen = Asset3[-past:, ]
for i in range(len(Chosen)):
axs[1, 0].vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'black', linewidth = 1)
if Chosen[i, 3] > Chosen[i, 0]:
color_chosen = 'teal'
axs[1, 0].vlines(x = i, ymin = Chosen[i, 0], ymax = Chosen[i, 3], color = color_chosen, linewidth = 2)
if Chosen[i, 3] < Chosen[i, 0]:
color_chosen = 'purple'
axs[1, 0].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
if Chosen[i, 3] == Chosen[i, 0]:
color_chosen = 'black'
axs[1, 0].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
axs[1, 0].grid()
axs[1, 0].set_title(assets[2])
axs[1, 0].axhline(y = levels3[1])
axs[1, 0].axhline(y = levels3[2])
Chosen = Asset6[-past:, ]
for i in range(len(Chosen)):
axs[1, 1].vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'black', linewidth = 1)
if Chosen[i, 3] > Chosen[i, 0]:
color_chosen = 'teal'
axs[1, 1].vlines(x = i, ymin = Chosen[i, 0], ymax = Chosen[i, 3], color = color_chosen, linewidth = 2)
if Chosen[i, 3] < Chosen[i, 0]:
color_chosen = 'purple'
axs[1, 1].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
if Chosen[i, 3] == Chosen[i, 0]:
color_chosen = 'black'
axs[1, 1].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
axs[1, 1].grid()
axs[1, 1].set_title(assets[5])
axs[1, 1].axhline(y = levels6[1])
axs[1, 1].axhline(y = levels6[2])Chosen = Asset7[-past:, ]for i in range(len(Chosen)):
axs[2, 1].vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'black', linewidth = 1)
if Chosen[i, 3] > Chosen[i, 0]:
color_chosen = 'teal'
axs[2, 1].vlines(x = i, ymin = Chosen[i, 0], ymax = Chosen[i, 3], color = color_chosen, linewidth = 2)
if Chosen[i, 3] < Chosen[i, 0]:
color_chosen = 'purple'
axs[2, 1].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
if Chosen[i, 3] == Chosen[i, 0]:
color_chosen = 'black'
axs[2, 1].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
axs[2, 1].grid()
axs[2, 1].set_title(assets[6])
axs[2, 1].axhline(y = levels7[1])
axs[2, 1].axhline(y = levels7[2])
Chosen = Asset8[-past:, ]
for i in range(len(Chosen)):
axs[2, 0].vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'black', linewidth = 1)
if Chosen[i, 3] > Chosen[i, 0]:
color_chosen = 'teal'
axs[2, 0].vlines(x = i, ymin = Chosen[i, 0], ymax = Chosen[i, 3], color = color_chosen, linewidth = 2)
if Chosen[i, 3] < Chosen[i, 0]:
color_chosen = 'purple'
axs[2, 0].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
if Chosen[i, 3] == Chosen[i, 0]:
color_chosen = 'black'
axs[2, 0].vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 2)
axs[2, 0].grid()
axs[2, 0].set_title(assets[7])
axs[2, 0].axhline(y = levels8[1])
axs[2, 0].axhline(y = levels8[2])
The result is self-explanatory. Take for example, the EURUSD plot, we have been hovering around the support level at 1.1725 where we should have a bullish bias. Similarly, we can see the EURCAD already forming a mini reaction from its support level at 1.4736. USDCHF is close to its resistance at 0.9225. We can run the algorithm each time we want an update.
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.