You are on page 1of 28

A Step-By-Step Guide to Implementing the

SuperTrend Indicator in Python


Learn to build a powerful trading strategy with the SuperTrend
indicator in python

Photo by Oskar Yildiz on Unsplash

I’ve backtested a lot of trading strategies based on an extensive amount of


technical indicators. Among those, some indicators’ results are off the charts and I
term these as premium indicators. In today’s article, we are going to discuss a
trend-following indicator that joins this exclusive list of premium indicators
concerning its performance and efficiency. It’s none other than the SuperTrend
indicator.

We will first explore what this indicator is all about and its complex calculation.
Then, we will move on to building the indicator from scratch in Python and
construct a simple trading strategy based on it. Finally, we will backtest it on the
stock of Tesla and compare the strategy’s performance with the returns of SPY ETF
(an ETF specifically designed to track the movements of the S&P 500 market
index). Without further ado, let’s hop into the article.

Before moving on, if you want to backtest your trading strategies without any
coding, there is a solution for it. It is BacktestZone. It is a platform to backtest any
number of trading strategies on different types of tradeable assets for free without
coding. You can use the tool right away using the link
here: https://www.backtestzone.com/

Average True Range


Before moving on to discovering the SuperTrend indicator, it is essential to know
what the Average True Range (ATR) is as it is involved in the calculation of the
SuperTrend indicator.

The Average True Range is a technical indicator that measures how much an asset
moves on an average. It is a lagging indicator meaning that it takes into account
the historical data of an asset to measure the current value but it’s not capable of
predicting the future data points. This is not considered as a drawback while using
ATR as it’s one of the indicators to track the volatility of a market more accurately.
Along with being a lagging indicator, ATR is also a non-directional indicator
meaning that the movement of ATR is inversely proportional to the actual
movement of the market. To calculate ATR, it is requisite to follow two 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 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. I’ve got to be honest that the calculation of the SuperTrend
is kinda a complex one but I’ll breakdown into pieces to make you understand
better.

There are two main components involved in the calculation of this indicator which
are the lookback period and the multiplier. The lookback period is nothing but the
number of data points to take into account for the calculation and the multiplier is
the value used to multiply the ATR. The traditional setting of the SuperTrend
indicator is 10 as the lookback period and 3 as the multiplier. By taking these
settings into consideration, let’s proceed to the steps involved in the calculation of
the SuperTrend indicator.

The first step involved in the calculation is to determine the 10-day ATR using the
formula we discussed before. The second step is to determine the basic upper and
lower band. To calculate these two bands, we need to first find the High Low
average (let’s now call it HLA) which is calculated by adding the high and low
values of the stock and dividing it by 2. Using these HLA values, the upper band is
calculated by first multiplying the 10-day ATR values with the multiplier (which is
3) and adding the product with the HLA values. The same procedure applies to the
calculation of the basic lower band too but instead of adding, we need to subtract
the product with the HLA values. The calculation of the 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. Like how we used conditions to calculate the final bands’
values, the same 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.
Image by Author

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 or so.

Implementation in Python
The coding part is classified into various steps as follows:

1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. SuperTrend Calculation
4. Creating the Crossover Trading Strategy
5. Plotting the Trading Lists
6. Creating our Position
7. Backtesting
8. 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 Pandas to work with data, NumPy to
work with arrays and for complex functions, Matplotlib for plotting purposes, and
Requests to make API calls. The secondary packages are going to be Math for
mathematical functions and Termcolor for font customization (optional).

Python Implementation:

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

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20,10)
Now that we have imported all the required packages into our python. Let’s pull
the historical data of Tesla with Twelve Data’s API endpoint.

Step-2: Extracting data from Twelve Data


In this step, we are going to pull the historical stock data of Tesla using an API
endpoint provided by twelvedata.com. Before that, a note on twelvedata.com:
Twelve Data is one of the leading market data providers having an enormous
amount of API endpoints for all types of market data. It is very easy to interact
with the APIs provided by Twelve Data and has one of the best documentation
ever. Also, ensure that you have an account on twelvedata.com, only then, you will
be able to access your API key (vital element to extract data with an API).

Python Implementation:

def get_historical_data(symbol, start_date):


api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol=
{symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df =
pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').as
type(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

tsla = get_historical_data('TSLA', '2020-01-01')


tsla

Output:
Image by Author

Code Explanation: The first thing we did is to define a function named


‘get_historical_data’ that takes the stock’s symbol (‘symbol’) and the starting date
of the historical data (‘start_date’) as parameters. Inside the function, we are
defining the API key and the URL and stored them into their respective variable.
Next, we are extracting the historical data in JSON format using the ‘get’ function
and stored it into the ‘raw_df’ variable. After doing some processes to clean and
format the raw JSON data, we are returning it in the form of a clean Pandas
dataframe. Finally, we are calling the created function to pull the historic data of
Tesla from the starting of 2020 and stored it into the ‘tsla’ variable.

Step-3: 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:
Image by Author

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-4: 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-5: 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:

Image by Author

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:
Image by Author

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 do
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
number_of_stocks = floor(investment_value/tsla['close'][-1])
st_investment_ret = []

for i in range(len(st_strategy_ret_df['st_returns'])):
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 :


55764.2
Profit percentage of the ST strategy : 55%

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 fifty-five thousand USD in one year. That’s not bad! 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
number_of_stocks = floor(investment_value/spy[-1])
benchmark_investment_ret = []

for i in range(len(benchmark['benchmark_returns'])):
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 : 22445.78


Benchmark Profit percentage : 22%
ST Strategy profit is 33% 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 33%. That’s great!

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. If
you forgot to follow any of the coding parts, don’t worry. I’ve provided the full
source code at the end of the article. Hope you learned something useful from this
article. Happy programming!

Full code:

# IMPORTING PACKAGES

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

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

# EXTRACTING DATA

def get_historical_data(symbol, start_date):


api_key = YOUR API KEY
api_url = f'https://api.twelvedata.com/time_series?symbol=
{symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df =
pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').as
type(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

tsla = get_historical_data('TSLA', '2020-01-01')


print(tsla)
# SUPERTREND CALCULATION
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

tsla['st'], tsla['s_upt'], tsla['st_dt'] =


get_supertrend(tsla['high'], tsla['low'], tsla['close'], 10, 3)
tsla = tsla[1:]
print(tsla.head())

# SUPERTREND PLOT

plt.plot(tsla['close'], linewidth = 2, label = 'CLOSING PRICE')


plt.plot(tsla['st'], color = 'green', linewidth = 2, label = 'ST
UPTREND 10,3')
plt.plot(tsla['st_dt'], color = 'r', linewidth = 2, label = 'ST
DOWNTREND 10,3')
plt.legend(loc = 'upper left')
plt.show()

# SUPERTREND STRATEGY

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'])

# SUPERTREND SIGNALS

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()
# GENERATING STOCK POSITION
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.head()
print(strategy[20:25])
# BACKTESTING

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
number_of_stocks = floor(investment_value/tsla['close'][-1])
st_investment_ret = []

for i in range(len(st_strategy_ret_df['st_returns'])):
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']))

# SPY ETF COMPARISON


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
number_of_stocks = floor(investment_value/spy[-1])
benchmark_investment_ret = []

for i in range(len(benchmark['benchmark_returns'])):
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']))

Finance Investing Education Programming Python

You might also like