All About Trading!

Share this post

Detecting Market Regime With the SuperTrend Indicator.

abouttrading.substack.com

Detecting Market Regime With the SuperTrend Indicator.

A Visual Way of Using Technical Indicators to Understand the Current Regime.

Sofien Kaabar, CFA
Oct 3, 2021
Share this post

Detecting Market Regime With the SuperTrend Indicator.

abouttrading.substack.com

Color-coding charts can be helpful to determine the market’s state when we want to take a quick glance at the charts. True we might be able to determine the trend visually or by using moving averages, but we can also use other indicators and transform the chart so that it reflects the indicator’s values. This article will discuss how to do that. The aim is to be able to produce multiple charts at the same time with a color code that shows our conditions.

I have just published a new book after the success of my previous one “New Technical Indicators in Python”. It features a more complete description and addition of structured 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

Fetching 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 libary 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.

The Market’s Regime

The market’s regime is its current state and can be divided into:

  • Bullish trend: The market has a tendency to make higher highs meaning that the aggregate direction is upwards.

  • Sideways: The market has a tendency to to range while remaining within established zones.

  • Bearish trend: The market has a tendency to make lower lows meaning that the aggregate direction is downwards.

Many tools attempt to detect the trend and most of them do give the answer but we can not really say we can predict the next state. The best way to solve this issue is to assume that the current state will continue and trade any reactions, preferably in the direction of the trend. For example, if the EURUSD is above its moving average and shaping higher highs, then it makes sense to wait for dips before buying and assuming that the bullish state will continue, also known as a trend-following strategy.

The aggregate direction from point 100 to point 400 was bullish.

The above chart shows the EURUSD’s hourly values where the market trended upwards for a while before reversing. Now, we will discuss a complex technical indicator called the SuperTrend and see how we can color code the trend for more objectivity.

The SuperTrend Indicator

The first concept we should understand before creating the SuperTrend indicator is volatility. 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 adder(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 deleter(Data, index, times):
    
    for i in range(1, times + 1):
    
        Data = np.delete(Data, index, axis = 1)    

    return Data
   
def jump(Data, jump):
    
    Data = Data[jump:, ]
    
    return Data

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 Data

def 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 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
EURUSD in the first panel with the 10-period Average True Range in the second panel.

Now that we have understood what the ATR is and how to calculate it, we can proceed further with the SuperTrend indicator. The indicator seeks to provide entry and exit levels for trend followers. You can think of it as a moving average or an MACD. Its uniqueness is its main advantage and although its calculation method is much more complicated than the other two indicators, it is intuitive in nature and not that hard to understand. Basically, we have two variables to choose from.

The ATR lookback and the multiplier’s value. The former is just the period used to calculated the ATR while the latter is generally an integer (usually 2 or 3).

The first thing to do is to average the high and low of the price bar, then we will either add or subtract the average with the selected multiplier multiplied by the ATR as shown in the above formulas. This will give us two arrays, the basic upper band and the basic lower band which form the first building blocks in the SuperTrend indicator. The next step is to calculate the final upper band and the final lower band using the below formulas.

It may seem complicated but most of these conditions are repetitive and in any case, I will provide the Python code below so that you can play with the function and optimize it to your trading preferences. Finally, using the previous two calculations, we can find the SuperTrend.

def supertrend(Data, multiplier, atr_col, close, high, low, where):
    
    Data = adder(Data, 6)
    
    for i in range(len(Data)):
        
            # Average Price
            Data[i, where] = (Data[i, high] + Data[i, low]) / 2
            # Basic Upper Band
            Data[i, where + 1] = Data[i, where] + (multiplier * Data[i, atr_col])
            # Lower Upper Band
            Data[i, where + 2] = Data[i, where] - (multiplier * Data[i, atr_col])
    
    # Final Upper Band
    for i in range(len(Data)):
        
        if i == 0:
            Data[i, where + 3] = 0
            
        else:  
            if (Data[i, where + 1] < Data[i - 1, where + 3]) or (Data[i - 1, close] > Data[i - 1, where + 3]):
                Data[i, where + 3] = Data[i, where + 1]
            
            else:
                Data[i, where + 3] = Data[i - 1, where + 3]
    
    # Final Lower Band
    for i in range(len(Data)):
        
        if i == 0:
            Data[i, where + 4] = 0
            
        else:  
            if (Data[i, where + 2] > Data[i - 1, where + 4]) or (Data[i - 1, close] < Data[i - 1, where + 4]):
                Data[i, where + 4] = Data[i, where + 2]
            
            else:
                Data[i, where + 4] = Data[i - 1, where + 4]
      
    # SuperTrend
    for i in range(len(Data)):
        
        if i == 0:
            Data[i, where + 5] = 0
        
        elif (Data[i - 1, where + 5] == Data[i - 1, where + 3]) and (Data[i, close] <= Data[i, where + 3]):
            Data[i, where + 5] = Data[i, where + 3]
        
        elif (Data[i - 1, where + 5] == Data[i - 1, where + 3]) and (Data[i, close] >  Data[i, where + 3]):
            Data[i, where + 5] = Data[i, where + 4]
        
        elif (Data[i - 1, where + 5] == Data[i - 1, where + 4]) and (Data[i, close] >= Data[i, where + 4]):
            Data[i, where + 5] = Data[i, where + 4]
        
        elif (Data[i - 1, where + 5] == Data[i - 1, where + 4]) and (Data[i, close] <  Data[i, where + 4]):
            Data[i, where + 5] = Data[i, where + 3]   
            
    # Cleaning columns
    Data = deleter(Data, where, 5)        
    
    return Data
EURUSD hourly values with the SuperTrend Indicator.

The above chart shows the hourly values of the EURUSD with a 10-period SuperTrend (represented by the ATR period) and a multiplier of 1.25.

The way we should understand the indicator is that when it goes above the market price, we should be looking to short and when it goes below the market price, we should be looking to go long as we anticipate a bullish trend. Remember that the SuperTrend is a trend-following indicator. The aim here is to capture trends at the beginning and to close out when they are over.

EURCHF hourly values with the SuperTrend Indicator.

Creating the Visual Interpreter

The assumption we will be making is that the SuperTrend is a trend-following indicator where values below the market price signify a bullish regime and values above the market price signify a bearish regime. Let us start with the default parameters 10 and 1.25

# Indicator Parameters
lookback = 10
multiplier = 1.25

my_data = atr(my_data, lookback, 1, 2, 3, 4, genre = 'Smoothed')
my_data = supertrend(my_data, multiplier, 4, 3, 1, 2, 5)
Signal chart on the EURUSD with a SuperTrend(10, 1.25).

The code to generate the above chart lies with this function below.

def ohlc_plot_candles(Data, window):
      
    Chosen = Data[-window:, ]
    
    for i in range(len(Chosen)):
        
        if Chosen[i, 5] < Chosen[i, 3]:
            
            plt.vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'green', linewidth = 1)
            color_chosen = 'green'
            plt.vlines(x = i, ymin = Chosen[i, 0], ymax = Chosen[i, 3], color = color_chosen, linewidth = 1) 
            
        if Chosen[i, 5] > Chosen[i, 3]:
            
            plt.vlines(x = i, ymin = Chosen[i, 2], ymax = Chosen[i, 1], color = 'red', linewidth = 1)
            color_chosen = 'red'
            plt.vlines(x = i, ymin = Chosen[i, 3], ymax = Chosen[i, 0], color = color_chosen, linewidth = 1)  
            
    plt.grid()

The chart seems a bit noisy. What we tweak a little bit the parameters of the indicator?

# Indicator Parameters
lookback = 20
multiplier = 2.50
Signal chart on the EURUSD with a SuperTrend(20, 2.5).

When we see a cluster of green values, we know that it is a bullish state as the SuperTrend is below the market price. Of course, there can be false signals in sideway moving markets.

Signal chart on the EURUSD with a SuperTrend(30, 3.0).

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

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 specific 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.

Share this post

Detecting Market Regime With the SuperTrend Indicator.

abouttrading.substack.com
Comments
TopNewCommunity

No posts

Ready for more?

© 2023 Sofien Kaabar
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing