I’ve backtested numerous trading strategies using a wide variety of technical indicators. Among these, a few indicators have delivered exceptional results, and I consider them to be “premium” indicators. In today’s analysis, we’ll focus on one such trend-following indicator that has earned its place in this exclusive group due to its accuracy and efficiency—the SuperTrend indicator.

We’ll begin by understanding what the SuperTrend indicator is and how its formula works, including its key parameters like volatility and settings for intraday trading. Afterward, we’ll demonstrate how to build the indicator from scratch using Python, step-by-step, and integrate it into a simple trading strategy. Next, we’ll backtest the strategy on Tesla’s stock and analyze its performance against the SPY ETF (an ETF designed to mimic the movements of the S&P 500 market index).

Finally, we’ll discuss the best indicators to use with SuperTrend and how to optimize the indicator for maximum accuracy rate in various market conditions. Let’s dive into the article and explore how you can harness the power of the SuperTrend indicator in your trading strategies.

Register & Get Data

Average True Range

Before moving on to discovering the SuperTrend indicator, it is essential to understand the parameters and role of the Average True Range (ATR), as it is crucial to the SuperTrend formula used in technical analysis and charting.

The Average True Range is a technical indicator that measures how much an asset moves on average, often used in intraday trading strategies. It is a lagging indicator, meaning it relies on historical data to calculate the current value but does not predict future price movements. However, this is not considered a drawback, as ATR is widely regarded as one of the best indicators to track volatility. Its ability to assess market volatility is why it pairs well with the SuperTrend indicator for strategies focused on dynamic market conditions.

Additionally, the ATR is a non-directional indicator, meaning its movement is not directly tied to the market’s trend. This makes it valuable for understanding overall volatility, regardless of whether the market is trending up or down. To calculate ATR, you must follow two key steps:

  • Calculate True Range (TR): A True Range of an asset is calculated by taking the greatest values of three price differences which are: market high minus marker low, market high minus previous market close, previous market close minus market low. It can be represented as follows:
MAX [ {HIGH - LOW}, {HIGH - P.CLOSE}, {P.CLOSE - LOW} ]

where,
MAX = Maximum values
HIGH = Market High
LOW = Market Low
P.CLOSE = Previous market close
  • Calculate ATR: The calculation for the Average True Range is simple. We just have to take a smoothed average of the previously calculated True Range values for a specified number of periods. The smoothed average is not just any SMA or EMA but an own type of smoothed average created by Wilder Wiles himself but there aren’t any restrictions in using other MAs too. In this article, we will be using the Exponential Moving Average (EMA) to calculate ATR rather than the custom moving average created by the founder of the indicator just to make things simple. The calculation of ATR with a traditional setting of 14 as the number of periods can be represented as follows:
ATR 14 = EMA 14 [ TR ]

where,
ATR 14 = 14 Period Average True Range
SMA 14 = 14 Period Simple Moving Average
TR = True Range

While using ATR as an indicator for trading purposes, traders must ensure that they are more cautious than ever, as the indicator is very lagging. Now that we have an understanding of what the Average True Range is all about. Let’s now dive into the main concept of this article, the SuperTrend Indicator.

SuperTrend Indicator

As the name suggests, the SuperTrend indicator tracks the direction of a trending market. This indicator is well-known for its precision in spotting efficient buy and sell signals for trades, making it a reliable tool for intraday trading as well. While the calculation of the SuperTrend might seem complex at first, I’ll break it down into simpler steps to help you understand it better.

There are two main parameters involved in the calculation of this indicator: the lookback period and the multiplier. The lookback period represents the number of data points considered, while the multiplier is used to adjust the volatilitycomponent, specifically by multiplying the ATR (Average True Range). The traditional setting for the SuperTrend indicator is a 10-period lookback and a multiplier of 3. Using these settings, let’s proceed to the calculation of the SuperTrend formula.

The first step is to determine the 10-day ATR, which we discussed earlier. Next, we calculate the basic upper and lower bands. To do this, we first find the High/Low average (HLA), calculated by adding the high and low values of the stock and dividing by 2. Using the HLA, the upper band is determined by multiplying the 10-day ATR by the multiplier (3 in this case) and then adding it to the HLA value. For the lower band, the same method is applied, but instead of adding, we subtract the ATR product from the HLA. These two bands form the basis of the SuperTrend chart used in market analysis.

The calculation of these two bands can be mathematically represented as follows:

BASIC UPPER BAND = HLA + [ MULTIPLIER * 10-DAY ATR ]
BASIC LOWER BAND = HLA - [ MULTIPLIER * 10-DAY ATR ]

where,
HLA = High Low Average
MULTIPLIER = 3

Then comes the calculation of the final upper and lower bands, which are the core components involved in the calculation of the SuperTrend indicator. There is no formula for the calculation of the final bands, but instead, conditions are passed and the values will be appended to the bands concerning which condition gets satisfied. The condition for the current final upper band goes as follows:

  • If the current basic upper band is lesser than the previous final upper band, or the previous closing price of the stock is greater than the previous final upper band, then the current final upper band’s value is the current basic upper band.
  • If the condition fails to get satisfied, then the current final upper band’s value is the previous final upper band. The condition of the final upper band can be represented as follows:
IF C.BUB < P.FUB OR P.CLOSE > P.FUB: C.FUB = C.BUB
IF THE CONDITION IS NOT SATISFIED: C.FUB = P.FUB

where,
C.BUB = Current Basic Upper Band
P.FUB = Previous Final Upper Band
P.CLOSE = Previous Closing Price of the Stock
C.FUB = Current Final Upper Band

The condition for the current lower band goes as follows:

  • If the current basic lower band is greater than the previous final lower band or the previous closing price of the stock is lesser than the previous final lower band, then the current final lower band’s value is the current basic lower band.
  • If this condition of the current final lower band fails to get satisfied, then the current final lower band is the previous final lower band. The condition can be represented as follows:
IF C.BLB > P.FLB OR P.CLOSE < P.FLB: C.FLB = C.BLB
IF THE CONDITION IS NOT SATISFIED: C.FLB = P.FLB

where,
C.BLB = Current Basic Lower Band
P.FLB = Previous Final Lower Band
P.CLOSE = Previous Closing Price of the Stock
C.FLB = Current Final Lower Band

Now we have all the essential components to determine the values of the SuperTrend indicator. The same method that we used conditions to calculate the final bands’ values applies to the calculation of the SuperTrend indicator too. While there is only one condition for determining the final bands’ values, there are four different conditions for the SuperTrend indicator. The conditions for the current SuperTrend value goes as follows:

  • If the previous SuperTrend indicator value is equal to the previous final upper band and the current closing price of the stock is lesser than the current final upper band, then the current SuperTrend indicator value is the current final upper band.
  • If the previous SuperTrend indicator value is equal to the previous final upper band and the current closing price of the stock is greater than the current final upper band, then the current SuperTrend indicator value is the current final lower band.
  • If the previous SuperTrend indicator value is equal to the previous final lower band and the current closing price of the stock is greater than the current final lower band, then the current SuperTrend indicator value is the current final lower band.
  • If the previous SuperTrend indicator value is equal to the previous final lower band and the current closing price of the stock is lesser than the current final lower band, then the current SuperTrend indicator value is the current final upper band.

When putting all these conditions together, the collective number of conditions can be represented as follows:

IF P.ST == P.FUB AND C.CLOSE < C.FUB: C.ST = C.FUB
IF P.ST == P.FUB AND C.CLOSE > C.FUB: C.ST = C.FLB
IF P.ST == P.FLB AND C.CLOSE > C.FLB: C.ST = C.FLB
IF P.ST == P.FLB AND C.CLOSE < C.FLB: C.ST = C.FUB

where,
P.ST = Previous SuperTrend indicator value
P.FUB = Previous Final Upper Band
P.FLB = Previous Final Lower Band
C.CLOSE = Current Closing Price of the Stock
C.ST = Current SuperTrend indicator value
C.FUB = Current Final Upper Band
C.FLB = Current Final Lower Band

That’s the whole process of calculating the SuperTrend indicator values. To build a stronger understanding of the indicator and how it works, let’s explore a chart where the closing price of a stock is plotted along with the SuperTrend indicator’s readings.

In the above chart, the blue line represents the closing price of the Tesla stock and the line with both red and green color represents the readings of the SuperTrend indicator. The line of the SuperTrend indicator turns green if the readings of the indicator are below the closing price and turns red if it’s above the closing price. As I said before, the SuperTrend indicator is a trend-following indicator and this can be observed in the chart that the indicator directly reveals the current trend of the market more accurately.

Traders use the color changes or trend changes observed in the SuperTrend indicator line to mark buy and sell signals for their trades. To be more elaborate, traders go long (buy the stock) if the indicator’s line crosses from above to below the closing price line, and similarly, they go short (sell the stock) if the indicator’s line crosses from below to above the closing price line. This SuperTrend strategy is called the crossover strategy. This strategy can be represented as follows:

IF PREV.ST > PREV.CLOSE AND CUR.ST < CUR.CLOSE ==> BUY SIGNAL
IF PREV.ST < PREV.CLOSE AND CUR.ST > CUR.CLOSE ==> SELL SIGNAL

This is the strategy we are going to implement in this article too. Many other strategies can also be implemented based on the SuperTrend indicator but just to make things simple to understand, we are going with the crossover strategy. This concludes our theory part on the SuperTrend indicator. Now, let’s move on to the coding part where we are first going to build the indicator from scratch, build the crossover strategy which we just discussed, then, compare our strategy’s performance with the SPY ETF’s returns in Python. Let’s do some coding! Before moving on, a note on disclaimer: This article’s sole purpose is to educate people and must be considered as an information piece but not as investment advice.

Implementation in Python

The coding part is classified into various steps as follows:

1. Importing Packages
2. API Key Activation
3. Extracting Historical Stock Data
4. SuperTrend Calculation
5. Creating the Crossover Trading Strategy
6. Plotting the Trading Lists
7. Creating our Position
8. Backtesting
9. SPY ETF Comparison

We will be following the order mentioned in the above list and buckle up your seat belts to follow every upcoming coding part.

Step-1: Importing Packages

Importing the required packages into the Python environment is a non-skippable step. The primary packages are going to be eodhd for extracting historical stock data, Pandas for data formatting and manipulations, NumPy to work with arrays and for complex functions, and Matplotlib for plotting purposes. The secondary packages are going to be Math for mathematical functions and Termcolor for font customization (optional).

Python Implementation:

# IMPORTING PACKAGES

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from termcolor import colored as cl
from math import floor
from eodhd import APIClient

plt.rcParams['figure.figsize'] = (20,10)
plt.style.use('fivethirtyeight')

With the required packages imported into Python, we can proceed to fetch historical data for Tesla using EODHD’s eodhd Python library. Also, if you haven’t installed any of the imported packages, make sure to do so using the pip command in your terminal.

Step-2: API Key Activation

It is essential to register the EODHD API key with the package in order to use its functions. If you don’t have an EODHD API key, firstly, head over to their website, then, finish the registration process to create an EODHD account, and finally, navigate to the ‘Settings’ page where you could find your secret EODHD API key. It is important to ensure that this secret API key is not revealed to anyone. You can activate the API key by following this code:

api_key = '<YOUR API KEY>'
client = APIClient(api_key)

The code is pretty simple. In the first line, we are storing the secret EODHD API key into the api_key and then in the second line, we are using the APIClient class provided by the eodhd package to activate the API key and stored the response in the client variable.

Note that you need to replace <YOUR API KEY> with your secret EODHD API key. Apart from directly storing the API key with text, there are other ways for better security such as utilizing environmental variables, and so on.

Register & Get Data

Step-3: Extracting Historical Data

Before heading into the extraction part, it is first essential to have some background about historical or end-of-day data. In a nutshell, historical data consists of information accumulated over a period of time. It helps in identifying patterns and trends in the data. It also assists in studying market behavior. Now, you can easily extract the historical data of any tradeable assets using the eod package by following this code:

# EXTRACTING HISTORICAL DATA

def extract_historical_data(ticker, start_date):
    json_resp = client.get_eod_historical_stock_market_data(symbol = ticker, period = 'd', from_date = start_date, order = 'a')
    df = pd.DataFrame(json_resp)
    df = df.set_index('date')
    df.index = pd.to_datetime(df.index)
    return df

tsla = extract_historical_data('TSLA', '2020-01-01')
tsla.tail()

In the above code, we are using the get_eod_historical_stock_market_data function provided by the eodhd package to extract the split-adjusted historical stock data of Tesla. The function consists of the following parameters:

  • the ticker parameter where the symbol of the stock we are interested in extracting the data should be mentioned
  • the period refers to the time interval between each data point (one-day interval in our case).
  • the from_date and to_date parameters which indicate the starting and ending date of the data respectively. The format of the input should be “YYYY-MM-DD”
  • the order parameter which is an optional parameter that can be used to order the dataframe either in ascending (a) or descending (d). It is ordered based on the dates.

After extracting the historical data, we are performing some data-wrangling processes to clean and format the data. The final dataframe looks like this:

Step-4: SuperTrend Calculation

In this step, we are going to calculate the values of the SuperTrend indicator by following the methods we discussed before.

Python Implementation:

def get_supertrend(high, low, close, lookback, multiplier):
    
    # ATR
    
    tr1 = pd.DataFrame(high - low)
    tr2 = pd.DataFrame(abs(high - close.shift(1)))
    tr3 = pd.DataFrame(abs(low - close.shift(1)))
    frames = [tr1, tr2, tr3]
    tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
    atr = tr.ewm(lookback).mean()
    
    # H/L AVG AND BASIC UPPER & LOWER BAND
    
    hl_avg = (high + low) / 2
    upper_band = (hl_avg + multiplier * atr).dropna()
    lower_band = (hl_avg - multiplier * atr).dropna()
    
    # FINAL UPPER BAND    final_bands = pd.DataFrame(columns = ['upper', 'lower'])
    final_bands.iloc[:,0] = [x for x in upper_band - upper_band]
    final_bands.iloc[:,1] = final_bands.iloc[:,0]    for i in range(len(final_bands)):
        if i == 0:
            final_bands.iloc[i,0] = 0
        else:
            if (upper_band[i] < final_bands.iloc[i-1,0]) | (close[i-1] > final_bands.iloc[i-1,0]):
                final_bands.iloc[i,0] = upper_band[i]
            else:
                final_bands.iloc[i,0] = final_bands.iloc[i-1,0]
    
    # FINAL LOWER BAND
    
    for i in range(len(final_bands)):
        if i == 0:
            final_bands.iloc[i, 1] = 0
        else:
            if (lower_band[i] > final_bands.iloc[i-1,1]) | (close[i-1] < final_bands.iloc[i-1,1]):
                final_bands.iloc[i,1] = lower_band[i]
            else:
                final_bands.iloc[i,1] = final_bands.iloc[i-1,1]
    
    # SUPERTREND
    
    supertrend = pd.DataFrame(columns = [f'supertrend_{lookback}'])
    supertrend.iloc[:,0] = [x for x in final_bands['upper'] - final_bands['upper']]
    
    for i in range(len(supertrend)):
        if i == 0:
            supertrend.iloc[i, 0] = 0
        elif supertrend.iloc[i-1, 0] == final_bands.iloc[i-1, 0] and close[i] < final_bands.iloc[i, 0]:
            supertrend.iloc[i, 0] = final_bands.iloc[i, 0]
        elif supertrend.iloc[i-1, 0] == final_bands.iloc[i-1, 0] and close[i] > final_bands.iloc[i, 0]:
            supertrend.iloc[i, 0] = final_bands.iloc[i, 1]
        elif supertrend.iloc[i-1, 0] == final_bands.iloc[i-1, 1] and close[i] > final_bands.iloc[i, 1]:
            supertrend.iloc[i, 0] = final_bands.iloc[i, 1]
        elif supertrend.iloc[i-1, 0] == final_bands.iloc[i-1, 1] and close[i] < final_bands.iloc[i, 1]:
            supertrend.iloc[i, 0] = final_bands.iloc[i, 0]
    
    supertrend = supertrend.set_index(upper_band.index)
    supertrend = supertrend.dropna()[1:]
    
    # ST UPTREND/DOWNTREND
    
    upt = []
    dt = []
    close = close.iloc[len(close) - len(supertrend):]

    for i in range(len(supertrend)):
        if close[i] > supertrend.iloc[i, 0]:
            upt.append(supertrend.iloc[i, 0])
            dt.append(np.nan)
        elif close[i] < supertrend.iloc[i, 0]:
            upt.append(np.nan)
            dt.append(supertrend.iloc[i, 0])
        else:
            upt.append(np.nan)
            dt.append(np.nan)
            
    st, upt, dt = pd.Series(supertrend.iloc[:, 0]), pd.Series(upt), pd.Series(dt)
    upt.index, dt.index = supertrend.index, supertrend.index
    
    return st, upt, dt

Output:

Code Explanation: We are first defining a function named ‘get_supertrend’ which takes a stock’s high (‘high’), low (‘low’), close (‘close’), the lookback period (‘lookback), and the multiplier (‘multiplier’) as parameters. The code inside the function can be divided into six parts: ATR calculation, HLA and basic bands calculation, final upper band calculation, final lower band calculation, SuperTrend indicator calculation, and determining the uptrend and downtrend of the indicator.

ATR calculation: To determine the readings of the Average True Range, we are first calculating the three differences and stored them into their respective variables. Then we are combining all three differences into one dataframe using the ‘concat’ function and took the maximum values out of the three collective differences to determine the True Range. Then, using the ‘ewm’ and ‘mean’ function, we are taking the Exponential Moving Average of True Range for a specified number of periods to get the ATR values. Many prefer using SMA for ATR calculation while determining the SuperTrend, but I used EMA for more accuracy.

HLA and Basic Bands calculation: Before calculating the basic upper and lower bands, we need to first determine the High Low Average. To calculate the values of HLA, we are first finding the total of the high and low values of a stock, then dividing the total by 2. Using the HLA values which are stored into the ‘hl_avg’ variable, we are determining both the upper and lower bands by following the formula we discussed and stored the values into the ‘upper_band’ and ‘lower_band’ respectively.

Final Upper Band calculation: Before directly moving into calculating the final upper band values, we are first creating a dataframe named ‘final_bands’ to store both the final upper and lower bands. Then just for the sake to fill the dataframe with values, we are subtracting the upper band by itself to store zeros matching the length of the upper band series so that it can be used for iterations. This step of creating the dataframe is optional but highly recommended since it reduces future works of data processing and all similar stuff. After creating the dataframe, we are passing a for-loop to create the conditions we discussed before for determining the values of the final upper band.

Final Lower Band calculation: The code structure for determining the final lower band is most similar to the final upper band calculation but only the conditions change. But now, let’s dive into exploring what’s happening inside the for-loop we are passing to get the values of the final lower band. First, we are passing a for-loop that iterates over the length of the ‘final_bands’ dataframe we created before. Inside the for-loop, we are first defining an if-statement that appends the final lower band’s value as 0 if the current iteration value is zero. This if-statement’s purpose is to fill the first value of the final lower band as zero. After the if-statement, we are defining nested else-statement which appends the final lower band’s value as the basic lower band value if the condition we discussed before gets satisfied, or else, it appends the previous final lower band value.

SuperTrend calculation: We are first creating a dataframe named ‘supertrend’ to store the values of the SuperTrend indicator. Like how we did before just to fill the dataframe with zeros to match the length of the basic bands series, we are doing the same here too to match the final bands series. Then comes the for-loop to determine the values of the SuperTrend indicator which appends the values concerning which condition gets satisfied out of the four. After the for-loop, we are doing some data processing to modify a bit and clean the dataframe.

Uptrend/Downtrend determination: This step is optional as I did only to make the visualization of the indicator a bit easy but you can try doing this too. The main aim of this step is to classify the SuperTrend indicator’s periods into two categories: the SuperTrend being below the closing price (uptrend), the SuperTrend being above the closing price (downtrend). With that being said, let’s dive into the methods involved. We are first defining two empty lists named ‘up’ and ‘down’ in which the values of the uptrend and the downtrend will be appended respectively. We are also reducing the length of the closing price data to match that of the SuperTrend data, only then, the iteration would be possible. Next, we are passing a for-loop to iterate over the length of the SuperTrend data to determine and append both uptrend and downtrend values into their respective variables. The values will be appended concerning which condition gets satisfied that are defined inside the for-loop. If either condition gets satisfied, the values of both uptrend and downtrend will be appended as ‘NaN’ (not defined). Then, we are converting the lists we created to store the uptrend and downtrend into Pandas series as it will be more convenient to work with.

After calculating all these values, we are finally returning the SuperTrend indicator values, and both the uptrend and downtrend readings. Then, we are calling the created function to store the SuperTrend indicator values of Tesla along with the uptrend and downtrend readings with 10 as the lookback period and 3 as the multiplier. From the output being shown, it is observable that whenever the closing price is greater than the SuperTrend indicator, the downtrend readings (‘st_dt’) represent ‘NaN’. Likewise, whenever the closing price is lesser than the SuperTrend indicator, the uptrend readings (‘st_upt’) represent ‘NaN’.

Step-5: Creating the trading strategy

In this step, we are going to implement the discussed SuperTrend indicator crossover trading strategy in python.

Python Implementation:

def implement_st_strategy(prices, st):
    buy_price = []
    sell_price = []
    st_signal = []
    signal = 0
    
    for i in range(len(st)):
        if st[i-1] > prices[i-1] and st[i] < prices[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                st_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                st_signal.append(0)
        elif st[i-1] < prices[i-1] and st[i] > prices[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                st_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                st_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            st_signal.append(0)
            
    return buy_price, sell_price, st_signal

buy_price, sell_price, st_signal = implement_st_strategy(tsla['close'], tsla['st'])

Code Explanation: First, we are defining a function named ‘implement_st_strategy’ which takes the stock prices (‘prices’), and the values of the SuperTrend indicator (‘st’) as parameters.

Inside the function, we are creating three empty lists (buy_price, sell_price, and st_signal) in which the values will be appended while creating the trading strategy.

After that, we are implementing the trading strategy through a for-loop. Inside the for-loop, we are passing certain conditions, and if the conditions are satisfied, the respective values will be appended to the empty lists. If the condition to buy the stock gets satisfied, the buying price will be appended to the ‘buy_price’ list, and the signal value will be appended as 1 representing to buy the stock. Similarly, if the condition to sell the stock gets satisfied, the selling price will be appended to the ‘sell_price’ list, and the signal value will be appended as -1 representing to sell the stock.

Finally, we are returning the lists appended with values. Then, we are calling the created function and stored the values into their respective variables. The list doesn’t make any sense unless we plot the values. So, let’s plot the values of the created trading lists.

Step-6: Plotting the trading signals

In this step, we are going to plot the created trading lists to make sense out of them.

Python Implementation:

plt.plot(tsla['close'], linewidth = 2)
plt.plot(tsla['st'], color = 'green', linewidth = 2, label = 'ST UPTREND')
plt.plot(tsla['st_dt'], color = 'r', linewidth = 2, label = 'ST DOWNTREND')
plt.plot(tsla.index, buy_price, marker = '^', color = 'green', markersize = 12, linewidth = 0, label = 'BUY SIGNAL')
plt.plot(tsla.index, sell_price, marker = 'v', color = 'r', markersize = 12, linewidth = 0, label = 'SELL SIGNAL')
plt.title('TSLA ST TRADING SIGNALS')
plt.legend(loc = 'upper left')
plt.show()

Output:

Code Explanation: We are plotting the readings of the SuperTrend indicator along with the buy and sell signals generated by the trading strategy. We can observe that whenever the SuperTrend indicator line crosses from above to below the closing price line, a green-colored buy signal is plotted in the chart. Similarly, whenever the SuperTrend indicator line crosses from below to above the closing price line, a red-colored sell signal is plotted in the chart.

Step-6: Creating our Position

In this step, we are going to create a list that indicates 1 if we hold the stock or 0 if we don’t own or hold the stock.

Python Implementation:

position = []
for i in range(len(st_signal)):
    if st_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(tsla['close'])):
    if st_signal[i] == 1:
        position[i] = 1
    elif st_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = tsla['close']
st = tsla['st']
st_signal = pd.DataFrame(st_signal).rename(columns = {0:'st_signal'}).set_index(tsla.index)
position = pd.DataFrame(position).rename(columns = {0:'st_position'}).set_index(tsla.index)

frames = [close_price, st, st_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

Output:

Code Explanation: First, we are creating an empty list named ‘position’. We are passing two for-loops, one is to generate values for the ‘position’ list to just match the length of the ‘signal’ list. The other for-loop is the one we are using to generate actual position values. Inside the second for-loop, we are iterating over the values of the ‘signal’ list, and the values of the ‘position’ list get appended concerning which condition gets satisfied. The value of the position remains 1 if we hold the stock or remains 0 if we sold or don’t own the stock. Finally, we are doing some data manipulations to combine all the created lists into one dataframe.

From the output being shown, we can see that in the first two rows our position in the stock has remained 1 (since there isn’t any change in the SuperTrend indicator signal) but our position suddenly turned to -1 as we sold the stock when the SuperTrend indicator trading signal represents a sell signal (-1). Our position will remain 0 until some changes in the trading signal occur. Now it’s time to implement some backtesting process!

Step-7: Backtesting

Before moving on, it is essential to know what backtesting is. Backtesting is the process of seeing how well our trading strategy has performed on the given stock data. In our case, we are going to implement a backtesting process for our SuperTrend indicator trading strategy over the Tesla stock data.

Python Implementation:

tsla_ret = pd.DataFrame(np.diff(tsla['close'])).rename(columns = {0:'returns'})
st_strategy_ret = []

for i in range(len(tsla_ret)):
    returns = tsla_ret['returns'][i]*strategy['st_position'][i]
    st_strategy_ret.append(returns)
    
st_strategy_ret_df = pd.DataFrame(st_strategy_ret).rename(columns = {0:'st_returns'})
investment_value = 100000
st_investment_ret = []

for i in range(len(st_strategy_ret_df['st_returns'])):
    number_of_stocks = floor(investment_value/tsla['close'][i])
    returns = number_of_stocks*st_strategy_ret_df['st_returns'][i]
    st_investment_ret.append(returns)

st_investment_ret_df = pd.DataFrame(st_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(st_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the ST strategy by investing $100k in TSLA : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the ST strategy : {}%'.format(profit_percentage), attrs = ['bold']))

Output:

Profit gained from the ST strategy by investing $100k in TSLA : 232677.17
Profit percentage of the ST strategy : 232%

Code Explanation: First, we are calculating the returns of the Tesla stock using the ‘diff’ function provided by the NumPy package and we have stored it as a dataframe into the ‘tsla_ret’ variable. Next, we are passing a for-loop to iterate over the values of the ‘tsla_ret’ variable to calculate the returns we gained from our SuperTrend indicator trading strategy, and these returns values are appended to the ‘st_strategy_ret’ list. Next, we are converting the ‘st_strategy_ret’ list into a dataframe and stored it into the ‘st_strategy_ret_df’ variable.

Next comes the backtesting process. We are going to backtest our strategy by investing a hundred thousand USD into our trading strategy. So first, we are storing the amount of investment into the ‘investment_value’ variable. After that, we are calculating the number of Tesla stocks we can buy using the investment amount. You can notice that I’ve used the ‘floor’ function provided by the Math package because, while dividing the investment amount by the closing price of Tesla stock, it spits out an output with decimal numbers. The number of stocks should be an integer but not a decimal number. Using the ‘floor’ function, we can cut out the decimals. Remember that the ‘floor’ function is way more complex than the ‘round’ function. Then, we are passing a for-loop to find the investment returns followed by some data manipulation tasks.

Finally, we are printing the total return we got by investing a hundred thousand into our trading strategy and it is revealed that we have made an approximate profit of two hundred and thirty-two thousand USD in one year. That’s wonderful! Now, let’s compare our returns with SPY ETF (an ETF designed to track the S&P 500 stock market index) returns.

Step-8: SPY ETF Comparison

This step is optional but it is highly recommended as we can get an idea of how well our trading strategy performs against a benchmark (SPY ETF). In this step, we are going to extract the data of the SPY ETF using the ‘get_historical_data’ function we created and compare the returns we get from the SPY ETF with our SuperTrend indicator trading strategy returns on Tesla.

Python Implementation:

def get_benchmark(start_date, investment_value):
    spy = get_historical_data('SPY', start_date)['close']
    benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})
    
    investment_value = investment_value
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        number_of_stocks = floor(investment_value/spy[i]) 
        returns = number_of_stocks*benchmark['benchmark_returns'][i]
        benchmark_investment_ret.append(returns)

    benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
    return benchmark_investment_ret_df

benchmark = get_benchmark('2020-01-01', 100000)
investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('ST Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Output:

Benchmark profit by investing $100k : 41144.52
Benchmark Profit percentage : 41%
ST Strategy profit is 191% higher than the Benchmark Profit

Code Explanation: The code used in this step is almost similar to the one used in the previous backtesting step but, instead of investing in Tesla, we are investing in SPY ETF by not implementing any trading strategies. From the output, we can see that our SuperTrend indicator trading strategy has outperformed the SPY ETF by 191%. That’s great!

Register & Get Data

Final Thoughts!

After a long process of crushing both theory and coding parts, we have successfully learned what the SuperTrend indicator is all about, its calculation, and how to build a simple crossover trading strategy based on it in python. One important thing we managed to accomplish in this article is understanding the complex math behind the indicator and this will help in increasing your understanding of the indicator significantly.

As the SuperTrend indicator has just started gaining momentum, there are a lot more spaces for improvement. One such important space is strategy optimization. I talk about this in almost every article of mine as it should be considered of paramount importance.

For those who don’t know what strategy optimization is, it is the process of tuning the trading algorithm to perform at its best. You can tune the SuperTrend indicator by experimenting with different settings. In this article, we built the indicator with the traditional setting of 14 as the lookback period and 3 as the multiplier but, you can try changing the values and run backtests for each and every change to acknowledge its performance. By doing this will help you reach the optimal setting for the indicator that can outperform the market itself.

We didn’t consider doing it in this article, as the sole purpose is not on building an optimistic trading strategy ,but on building a strong intuition on what the SuperTrend indicator is all about. But, it is highly recommended to get your hands dirty on experimenting with the indicator and introduce yourselves to a whole new level of possibilities. With that being said, you’ve reached the end of the article. Hope you learned something useful from this article. Happy programming!

Do you enjoy our articles?

We can send new ones right to your email box