This is How I Implemented SUPER TREND Indicator in Backtrader (Python)

I am a huge fan of open source backtesting projects and Backtrader is one of the most popular backtesting open-source software available on internet. It is based on python and very customizable.

I really appreciate work of the backtrader developer Daniel Rodriguez for this amazing project and also for actively helping traders on the community portal.

Super Trend is very popular indicator which is used to trade trend and also for stop-loss. As a freelancer my clients usually ask for super-trend which is unfortunately not available in talib(Most popular python indicator library) and also is not available in backtrader default indicators list.

So here is my contribution to the community :

class SuperTrend(bt.Indicator):
    """
    SuperTrend Algorithm :
    
        BASIC UPPERBAND = (high + low) / 2 + Multiplier * ATR
        BASIC lowERBAND = (high + low) / 2 - Multiplier * ATR
        
        FINAL UPPERBAND = IF( (Current BASICUPPERBAND < Previous FINAL UPPERBAND) or (Previous close > Previous FINAL UPPERBAND))
                            THEN (Current BASIC UPPERBAND) ELSE Previous FINALUPPERBAND)
        FINAL lowERBAND = IF( (Current BASIC lowERBAND > Previous FINAL lowERBAND) or (Previous close < Previous FINAL lowERBAND)) 
                            THEN (Current BASIC lowERBAND) ELSE Previous FINAL lowERBAND)
        SUPERTREND = IF((Previous SUPERTREND = Previous FINAL UPPERBAND) and (Current close <= Current FINAL UPPERBAND)) THEN
                        Current FINAL UPPERBAND
                    ELSE
                        IF((Previous SUPERTREND = Previous FINAL UPPERBAND) and (Current close > Current FINAL UPPERBAND)) THEN
                            Current FINAL lowERBAND
                        ELSE
                            IF((Previous SUPERTREND = Previous FINAL lowERBAND) and (Current close >= Current FINAL lowERBAND)) THEN
                                Current FINAL lowERBAND
                            ELSE
                                IF((Previous SUPERTREND = Previous FINAL lowERBAND) and (Current close < Current FINAL lowERBAND)) THEN
                                    Current FINAL UPPERBAND
        
    """
        
    lines = ('super_trend',)
    params = (('period', 7),
              ('multiplier', 3),
             )
    plotlines = dict(
                    super_trend=dict(
                    _name='ST',
                    color='blue',
                    alpha=1
                    ))
                    
    plotinfo = dict(subplot=False)
    
    def __init__(self):
        self.st = [0]
        self.finalupband = [0]
        self.finallowband = [0]
        self.addminperiod(self.p.period)
        atr = bt.ind.ATR(self.data, period=self.p.period)

        self.upperband = (self.data.high + self.data.low)/2 + self.p.multiplier * atr
        self.lowerband = (self.data.high + self.data.low)/2 - self.p.multiplier * atr
        
 

    def next(self):
        
        pre_upband=self.finalupband[0]
        pre_lowband=self.finallowband[0]
        
        if self.upperband[0] < self.finalupband[-1] or self.data.close[-1] > self.finalupband[-1]:

            self.finalupband[0] = self.upperband[0]
            
        else:
            self.finalupband[0] = self.finalupband[-1]
            
        if self.lowerband[0] > self.finallowband[-1] or self.data.close[-1] < self.finallowband[-1]:
            
            self.finallowband[0] = self.lowerband[0]
            
        else:
            self.finallowband[0] = self.finallowband[-1]
          
        if  self.data.close[0] <= self.finalupband[0] and ( (self.st[-1] == pre_upband)):
             
            self.st[0] = self.finalupband[0]
            self.lines.super_trend[0] = self.finalupband[0]
            
        elif (self.st[-1] == pre_upband) and (self.data.close[0] > self.finalupband[0]) :
            
            self.st[0] = self.finallowband[0]
            self.lines.super_trend[0] = self.finallowband[0]
            
        elif (self.st[-1] == pre_lowband) and ( self.data.close[0] >= self.finallowband[0]) :
                
            self.st[0] = self.finallowband[0]
            self.lines.super_trend[0] = self.finallowband[0]
                   
        elif (self.st[-1] == pre_lowband) and ( self.data.close[0] < self.finallowband[0]): 
            
            self.st[0] = self.finalupband[0]
            self.lines.super_trend[0] = self.st[0]
        

To implement new indicators in backtrader you have to implement a new class and inherit the base class bt.Indicator.

There is a well documented article on how to develop indicators. Refer to this link https://www.backtrader.com/docu/inddev/

Using the indicator in your code is pretty easy:-

Here is a sample code :

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import backtrader as bt
import pandas as pd
from report import Cerebro


class SuperTrend(bt.Indicator):
    """
    SuperTrend Algorithm :

    BASIC UPPERBAND = (high + low) / 2 + Multiplier * ATR
    BASIC lowERBAND = (high + low) / 2 - Multiplier * ATR

    FINAL UPPERBAND = IF( (Current BASICUPPERBAND < Previous FINAL UPPERBAND) or (Previous close > Previous FINAL UPPERBAND))
                        THEN (Current BASIC UPPERBAND) ELSE Previous FINALUPPERBAND)
    FINAL lowERBAND = IF( (Current BASIC lowERBAND > Previous FINAL lowERBAND) or (Previous close < Previous FINAL lowERBAND))
                        THEN (Current BASIC lowERBAND) ELSE Previous FINAL lowERBAND)
    SUPERTREND = IF((Previous SUPERTREND = Previous FINAL UPPERBAND) and (Current close <= Current FINAL UPPERBAND)) THEN
                    Current FINAL UPPERBAND
                ELSE
                    IF((Previous SUPERTREND = Previous FINAL UPPERBAND) and (Current close > Current FINAL UPPERBAND)) THEN
                        Current FINAL lowERBAND
                    ELSE
                        IF((Previous SUPERTREND = Previous FINAL lowERBAND) and (Current close >= Current FINAL lowERBAND)) THEN
                            Current FINAL lowERBAND
                        ELSE
                            IF((Previous SUPERTREND = Previous FINAL lowERBAND) and (Current close < Current FINAL lowERBAND)) THEN
                                Current FINAL UPPERBAND

    """


    lines = ('super_trend',)
    params = (('period', 7),
              ('multiplier', 3),
              )
    plotlines = dict(
        super_trend=dict(
            _name='ST',
            color='blue',
            alpha=1
        )
    )
    plotinfo = dict(subplot=False)


    def __init__(self):
        self.st = [0]
        self.finalupband = [0]
        self.finallowband = [0]
        self.addminperiod(self.p.period)
        atr = bt.ind.ATR(self.data, period=self.p.period)

        self.upperband = (self.data.high + self.data.low) / 2 + self.p.multiplier * atr
        self.lowerband = (self.data.high + self.data.low) / 2 - self.p.multiplier * atr


    def next(self):
        pre_upband = self.finalupband[0]
        pre_lowband = self.finallowband[0]

        if self.upperband[0] < self.finalupband[-1] or self.data.close[-1] > self.finalupband[-1]:

            self.finalupband[0] = self.upperband[0]

        else:
            self.finalupband[0] = self.finalupband[-1]

        if self.lowerband[0] > self.finallowband[-1] or self.data.close[-1] < self.finallowband[-1]:

            self.finallowband[0] = self.lowerband[0]

        else:
            self.finallowband[0] = self.finallowband[-1]

        if self.data.close[0] <= self.finalupband[0] and ((self.st[-1] == pre_upband)):

            self.st[0] = self.finalupband[0]
            self.lines.super_trend[0] = self.finalupband[0]

        elif (self.st[-1] == pre_upband) and (self.data.close[0] > self.finalupband[0]):

            self.st[0] = self.finallowband[0]
            self.lines.super_trend[0] = self.finallowband[0]

        elif (self.st[-1] == pre_lowband) and (self.data.close[0] >= self.finallowband[0]):

            self.st[0] = self.finallowband[0]
            self.lines.super_trend[0] = self.finallowband[0]

        elif (self.st[-1] == pre_lowband) and (self.data.close[0] < self.finallowband[0]):

            self.st[0] = self.finalupband[0]
            self.lines.super_trend[0] = self.st[0]


class testStrategy(bt.Strategy):
    def log(self, txt, dt=None):
        if  True:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s - %s' % (dt.isoformat(), txt))
 
    def __init__(self):
        self.x = SuperTrend(self.data)
        self.dclose = self.datas[0].close
        self.cross = bt.ind.CrossOver(self.dclose, self.x)
    def notify(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enougth cash
        if order.status in [order.Completed, order.Canceled, order.Margin]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED: %s, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.data._name,
                     order.executed.price,
                     order.executed.value,
                     order.executed.comm))

                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
                self.opsize = order.executed.size
            else:  # Sell
                self.log('SELL EXECUTED: %s, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.data._name,
                          order.executed.price,
                          order.executed.value,
                          order.executed.comm))
                
    def notify_trade(self, trade):
        if trade.isclosed:
            self.log('TRADE PROFIT: EQ %s, GROSS %.2f, NET %.2f' %
                     ('Closed'  , trade.pnl, trade.pnlcomm))

        elif trade.justopened:
            self.log('TRADE OPENED: EQ %s, SIZE %2d' % (  'Opened'  , trade.size))
                

    def next(self):
        pos = self.getposition(self.data)
        dpos = pos.size
        if self.cross[0]==1 and dpos <= 0:
            self.order_target_percent(data=self.data, target=1)
        elif self.cross[0]==-1 and dpos >= 0:
            self.order_target_percent(data=self.data, target=-1)
        
 #Create an instance of cerebro
if __name__ == "__main__":

    cerebro = bt.Cerebro()
    x = pd.read_csv('DATAFILENAME.csv', index_col=0, parse_dates=True)

    z = bt.feeds.PandasData(dataname=x)
    cerebro.adddata(z)

    #Add our strategy
    cerebro.addstrategy(testStrategy)
    result = cerebro.run()
    cerebro.plot(style='candlesticks' ,volume=False)

Output:-

Backtest Result