For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

Plotting text over bars



  • Hi @backtrader
    I discovered Backtrader only recently, and it is great! It already makes impact in my work! Thanks!
    Newbie question: how can one create indicator which plots indicator line as a text above bars?
    For illustration see Sequential indicator:
    https://www.mql5.com/en/market/product/25007
    Thank you for your time and help
    /Marek


  • administrators

    No it cannot be done. backtrader was not conceived as a charting/plotting platform and such things would require metadata and metaactions to indicate that it is text what you want to plot (text which obviously varies)



  • I am also missing this functionality from Trading View. Is there anything out there that allows you to do this? I suppose we would need to write a generic charter based on d3. The backtrader_plotting package shows how we could do this.



  • You can do it this way:

    # -*- coding: utf-8 -*-
    
    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    
    # Import the backtrader platform
    import backtrader as bt
    import backtrader.indicators as btind
    from backtrader.observer import Observer as btobserver
    
    import datetime
    import time
    
    '''
    TD Sequential-Style indicator
    
    // S = Sell Signal
    // B = Buy Signal
    // A = Sell (above bar) or Buy (below bar) Signal that is 'not ideal' - meaning the signal bar's or the previous bar's high or low is not higer or lower than one of the previous 2 bars.
    // - This could indicate that waiting another bar will deliver a 'true' signal. (Useful at the beginning of the trend reversal signal, not so much at the end)
    //
    // Indicator and Rules based on indicator as seen on CoinSheet.org
    
    
        Ein TD Sequential Buy Setup ist erfüllt, wenn 9 aufeinanderfolgende Bars tiefer Schliessen als 4 Bars zuvor. Für den Sell ist es umgekehrt, dort müssen 9 aufeinanderfolgende Bars höher Schliessen als 4 Bars zuvor.
    
        Das Ziel der Zählung ist es, mögliche Wendepunkte anzuzeigen und somit Low Risk Entrys zu finden. Falls die Bedingungen nicht bis zum 9er Count erfüllt wird, ist das Setup gecancelt. Sobald ein TD Setup erfüllt ist, würde normalerweise die Countdown resp. Intersection-Phase beginnen. Beim Countdown werden danach 13 Bars die einen tiefern Close haben, als das Low des zwei Bars zurückliegenden Stabes. Die Countdown Bars müssen jedoch nicht aufeinanderfolgen, die Zählung läuft weiter bis zu Bar 13, sofern nicht das Hoch des vorangehenden TD Setups (bei einem Buy Setup...sonst natürlich das Tief bei einem Sell) überschritten wird.
    
        Das ist jetzt wirklich nur ein ganz grober Überblick der Standartparameter.???
    
        Beim angehängten Indi handelt es sich übrigens um den TD Combo und nicht den TD Sequential. Der Unterschied zwischen den beiden liegt einfach gesagt darin, dass die Countdown Zählung beim erreichen eines kompletten TD Setups, die Countdownzählung zürückrechnet und somit schon beim ersten Bar des Setups beginnen kann, anstatt erst nach der Beendigung des Setups.:P
    
        Grob gesagt kann beim erfüllen eines Setups mit einem kleinen Wendepunkt gerechnet werden und beim erfüllen eines Countdowns ein grosser Wendepunkt.
        
        Entry:
        There are three methods of entry:
        (a) Enter on the close of the day on which the countdown is completed;
        (b) Long Trades: Enter on the close if Close[i] > Close[i - 4];
            Short Trades: Enter on the close if Close[i] < Close[i - 4];
        (c) Long Trades: Enter on the close if Close[i] > High[i - 2];
            Short Trades: Enter on the close if Close[i] < Low[i - 2]. Index: i ~ Current Bar. In this test we apply the 2nd method.
        
        Exit:
        Pattern Exit: The current position is liquidated once the new setup is complete and price fails to exceed the furthest price recorded by the recent inactive setup.
        Stop Loss Exit: 
        Long Trades: The difference between the close at the entry bar and the low of the lowest day of the pattern is subtracted from the low of the lowest day. Short Trades: The difference between the high of the highest day of the pattern and the close at the entry bar is added to the high of the highest day.                
        Note: In order to activate a stop loss, the close must exceed the calculated levels.
    
    '''
    
    class strategy(bt.Strategy):
        params = dict(
          candles_past_to_compare = 4,
          stoploss_dist = 0.0,
        )
    
        ''' Logging function for strategy '''
        def log(self, txt, dt=None):
            dt = dt or self.dataprimary.datetime.datetime(0)
            print('%s, %s' % (dt.isoformat(), txt))
        
        
        '''Initialization '''    
        def __init__(self):
            # Keep a reference to the primary data feed
            self.dataprimary = self.datas[0]
            # Keep a reference to the "close" line in the primary dataseries
            self.dataclose = self.dataprimary.close
            # Control vars
            self.live = True
            self.order = None
    
            self.bearishFlip = False
            self.bullishFlip = False
    
            self.tdsl = 0 # TD sequence long
            self.tdss = 0 # TD sequence short
            self.buySetup = False # TD buy setup 
            self.sellSetup = False # TD sell setup
            self.buyCountdown = 0 # TD buy countdown
            self.sellCountdown = 0 # TD sell countdown
            self.buyVal = 0 # buy value at intersection
            self.sellVal = 0 # sell value at intersection
            
            self.buySig = False
            self.idealBuySig = False
            self.sellSig = False
            self.idealSellSig = False
    
            print('-' * 80)
            print('Strategy Created')
            print('-' * 80)
    
    
        ''' Data notification '''
        def notify_data(self, data, status, *args, **kwargs):
            print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args)
            if status == data.LIVE:
                self.live = True
            elif status == data.DELAYED:
                self.live = False
    
    
        ''' Store notification '''
        def notify_store(self, msg, *args, **kwargs):
            print('*' * 5, 'STORE NOTIF:', msg)
            
    
        ''' Order notification '''
        def notify_order(self, order):
            if order.status in [order.Completed, order.Cancelled, order.Rejected]:
                self.order = None
            print('-' * 50, 'ORDER BEGIN', datetime.datetime.now())
            print(order)
            print('-' * 50, 'ORDER END')
    
    
        ''' Trade notification '''
        def notify_trade(self, trade):
            print('-' * 50, 'TRADE BEGIN', datetime.datetime.now())
            print(trade)
            print('-' * 50, 'TRADE END')
            
            if not trade.isclosed:
                return
    
            self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                (trade.pnl, trade.pnlcomm))
    
        ''' Next '''        
        def next(self):
            self.buySig = False
            self.sellSig = False
            self.idealBuySig = False
            self.idealSellSig = False
            
            # Calculate td sequential values if enough bars
            if len(self.dataclose) > self.p.candles_past_to_compare:
                
                # begin of sequence, bullish or bearish flip
                if self.dataclose[0] < self.dataclose[-self.p.candles_past_to_compare] and self.dataclose[-1] > self.dataclose[-(self.p.candles_past_to_compare + 1)]:
                    self.bearishFlip = True
                    self.bullishFlip = False
                    self.tdsl = 0
                    self.tdss = 0
                    self.sellSetup = False
                    self.sellCountdown = 0
                elif self.dataclose[0] > self.dataclose[-self.p.candles_past_to_compare] and self.dataclose[-1] < self.dataclose[-(self.p.candles_past_to_compare + 1)]:
                    self.bullishFlip = True
                    self.bearishFlip = False
                    self.tdss = 0
                    self.tdsl = 0
                    self.buySetup = False
                    self.buyCountdown = 0
    
                # sequence start
                if self.dataclose[0] < self.dataclose[-self.p.candles_past_to_compare] and self.bearishFlip:
                    self.tdsl += 1
                elif self.dataclose[0] > self.dataclose[-self.p.candles_past_to_compare] and self.bullishFlip:
                    self.tdss += 1
                
                # sequence intersection
                if self.tdsl == 9:
                    self.buySig = (self.dataprimary.low[0] < self.dataprimary.low[-3] and self.dataprimary.low[0] < self.dataprimary.low[-2]) or (self.dataprimary.low[-1] < self.dataprimary.low[-2] and self.dataprimary.low[-1] < self.dataprimary.low[-3])
                    self.bearishFlip = False
                    self.tdsl = 0
                    self.buySetup = True
                    self.buyCountdown = 0
                elif self.tdss == 9:
                    self.sellSig = (self.dataprimary.high[0] > self.dataprimary.high[-2] and self.dataprimary.high[0] > self.dataprimary.high[-3]) or (self.dataprimary.high[-1] > self.dataprimary.high[-3] and self.dataprimary.high[-1] > self.dataprimary.high[-2])
                    self.bullishFlip = False
                    self.tdss = 0
                    self.sellSetup = True
                    self.sellCountdown = 0
                    
                # sequence countdown
                if self.buySetup:
                    if self.dataprimary.close[0] <= self.dataprimary.low[-2]:
                        self.buyCountdown += 1
                    if self.buyCountdown == 8:
                        self.buyVal = self.dataclose[0]
                    elif self.buyCountdown == 13:
                        if self.dataprimary.low[0] <= self.buyVal:
                            self.idealBuySig = True
                        self.buySetup = False
                        self.buyCountdown = 0
                        
                elif self.sellSetup:
                    if self.dataprimary.close[0] >= self.dataprimary.high[-2]:
                        self.sellCountdown += 1
                    if self.sellCountdown == 8:
                        self.sellVal = self.dataclose[0]
                    elif self.sellCountdown == 13:
                        if self.dataprimary.high[0] >= self.sellVal:
                            self.idealSellSig = True
                        self.sellSetup = False
                        self.sellCountdown = 0
                        
            # Trade only if no pending order and live data
            if not self.order and self.live:
                if not self.position:
                    if self.buySig:
                        if self.p.stoploss_dist > 0:
                            stoplossPrice = self.dataprimary.close[0] - self.p.stoploss_dist
                            self.buy_bracket(stopprice=stoplossPrice, exectype=bt.Order.Market)
                        else:
                            self.buy()
                    elif self.sellSig:
                        if self.p.stoploss_dist > 0:
                            stoplossPrice = self.dataprimary.close[0] + self.p.stoploss_dist
                            self.sell_bracket(stopprice=stoplossPrice, exectype=bt.Order.Market)
                        else:
                            self.sell()
                else:
                    if self.position.size > 0 and (self.sellSig or self.idealSellSig):
                        self.close()
                    elif self.position.size < 0 and (self.buySig or self.idealBuySig):
                        self.close()
                        
            # Log the closing price of the series from the reference
            self.log("Close {} TD SeqShort {} TD SeqLong {} TD CDBuy {} TD CDSell {}".format(self.dataclose[0], self.tdss, self.tdsl, self.buyCountdown, self.sellCountdown))
    
    class observer(btobserver):
        plotinfo = dict(plot=True, subplot=False, plotlegend=False, 
                        plotlinelegend=False, plotlinevalues=False, 
                        plotabove=True, plotname="TD Sequential",)
        lines = ('buy', 'buy_ideal', 'sell', 'sell_ideal', 'seq_up_1', 
                'seq_up_2', 'seq_up_3', 'seq_up_4', 'seq_up_5', 
                'seq_up_6', 'seq_up_7', 'seq_up_8', 'seq_up_9', 
                'seq_up_1_b', 'seq_up_2_b', 'seq_up_3_b', 'seq_up_4_b', 
                'seq_up_5_b', 'seq_up_6_b', 'seq_up_7_b', 'seq_up_8_b', 
                'seq_up_9_b', 'seq_up_10_b', 'seq_up_11_b', 'seq_up_12_b', 
                'seq_down_1', 'seq_down_2', 'seq_down_3', 'seq_down_4', 
                'seq_down_5', 'seq_down_6', 'seq_down_7', 'seq_down_8', 
                'seq_down_9',
                'seq_down_1_s', 'seq_down_2_s', 'seq_down_3_s', 'seq_down_4_s', 
                'seq_down_5_s', 'seq_down_6_s', 'seq_down_7_s', 'seq_down_8_s', 
                'seq_down_9_s', 'seq_down_10_s','seq_down_11_s', 'seq_down_12_s',)
                
        plotlines = dict(
            buy_ideal = dict(marker='^', markersize=10.0, color='green',),
            buy = dict(marker='2', markersize=10.0, color='green',),
            sell_ideal = dict(marker='v', markersize=10.0, color='maroon',),
            sell = dict(marker='1', markersize=10.0, color='maroon',),
            seq_up_1 = dict(marker='$1$', markersize=6.0, color='black', ls='',),
            seq_up_2 = dict(marker='$2$', markersize=6.0, color='black', ls='',),
            seq_up_3 = dict(marker='$3$', markersize=6.0, color='black', ls='',),
            seq_up_4 = dict(marker='$4$', markersize=6.0, color='black', ls='',),
            seq_up_5 = dict(marker='$5$', markersize=6.0, color='black', ls='',),
            seq_up_6 = dict(marker='$6$', markersize=6.0, color='black', ls='',),
            seq_up_7 = dict(marker='$7$', markersize=6.0, color='black', ls='',),
            seq_up_8 = dict(marker='$8$', markersize=6.0, color='black', ls='',),
            seq_up_9 = dict(marker='$9$', markersize=6.0, color='black', ls='',),
            seq_up_1_b = dict(marker='$1$', markersize=6.0, color='blue', ls='',),
            seq_up_2_b = dict(marker='$2$', markersize=6.0, color='blue', ls='',),
            seq_up_3_b = dict(marker='$3$', markersize=6.0, color='blue', ls='',),
            seq_up_4_b = dict(marker='$4$', markersize=6.0, color='blue', ls='',),
            seq_up_5_b = dict(marker='$5$', markersize=6.0, color='blue', ls='',),
            seq_up_6_b = dict(marker='$6$', markersize=6.0, color='blue', ls='',),
            seq_up_7_b = dict(marker='$7$', markersize=6.0, color='blue', ls='',),
            seq_up_8_b = dict(marker='$8$', markersize=6.0, color='blue', ls='',),
            seq_up_9_b = dict(marker='$9$', markersize=6.0, color='blue', ls='',),
            seq_up_10_b = dict(marker='$10$', markersize=6.0, color='blue', ls='',),
            seq_up_11_b = dict(marker='$11$', markersize=6.0, color='blue', ls='',),
            seq_up_12_b = dict(marker='$12$', markersize=6.0, color='blue', ls='',),
            seq_down_1 = dict(marker='$1$', markersize=6.0, color='black', ls='',),
            seq_down_2 = dict(marker='$2$', markersize=6.0, color='black', ls='',),
            seq_down_3 = dict(marker='$3$', markersize=6.0, color='black', ls='',),
            seq_down_4 = dict(marker='$4$', markersize=6.0, color='black', ls='',),
            seq_down_5 = dict(marker='$5$', markersize=6.0, color='black', ls='',),
            seq_down_6 = dict(marker='$6$', markersize=6.0, color='black', ls='',),
            seq_down_7 = dict(marker='$7$', markersize=6.0, color='black', ls='',),
            seq_down_8 = dict(marker='$8$', markersize=6.0, color='black', ls='',),
            seq_down_9 = dict(marker='$9$', markersize=6.0, color='black', ls='',),
            seq_down_1_s = dict(marker='$1$', markersize=6.0, color='blue', ls='',),
            seq_down_2_s = dict(marker='$2$', markersize=6.0, color='blue', ls='',),
            seq_down_3_s = dict(marker='$3$', markersize=6.0, color='blue', ls='',),
            seq_down_4_s = dict(marker='$4$', markersize=6.0, color='blue', ls='',),
            seq_down_5_s = dict(marker='$5$', markersize=6.0, color='blue', ls='',),
            seq_down_6_s = dict(marker='$6$', markersize=6.0, color='blue', ls='',),
            seq_down_7_s = dict(marker='$7$', markersize=6.0, color='blue', ls='',),
            seq_down_8_s = dict(marker='$8$', markersize=6.0, color='blue', ls='',),
            seq_down_9_s = dict(marker='$9$', markersize=6.0, color='blue', ls='',),
            seq_down_10_s = dict(marker='$10$', markersize=6.0, color='blue', ls='',),
            seq_down_11_s = dict(marker='$11$', markersize=6.0, color='blue', ls='',),
            seq_down_12_s = dict(marker='$12$', markersize=6.0, color='blue', ls='',),
        )
        
        
        params = (
            ('bardist', 0.00005),  
        )
        
        def __init__(self):
            pass
            
        def next(self):
            # begin of sequence for long position
            if self._owner.tdsl > 0 and self._owner.tdsl < 10:
                name = 'seq_up_%d' % self._owner.tdsl
                attr = getattr(self.lines, name, False)
                attr[0] = self.data_low[0] - self.p.bardist
            
            # begin of sequence of short position
            elif self._owner.tdss > 0 and self._owner.tdss < 10:
                name = 'seq_down_%d' % self._owner.tdss
                attr = getattr(self.lines, name, False)
                attr[0] = self.data_high[0] + self.p.bardist
                
            if self._owner.buySetup:
                if self._owner.buyCountdown > 0 and self._owner.buyCountdown < 13:
                    name = 'seq_up_%d_b' % self._owner.buyCountdown
                    attr = getattr(self.lines, name, False)
                    attr[0] = self.data_low[0] - (2 * self.p.bardist)
            elif self._owner.sellSetup:
                if self._owner.sellCountdown > 0 and self._owner.sellCountdown < 13 and self._owner.sellSetup:
                    name = 'seq_down_%d_s' % self._owner.sellCountdown
                    attr = getattr(self.lines, name, False)
                    attr[0] = self.data_high[0] + (2 * self.p.bardist)
            
            if self._owner.buySig:
                self.lines.buy[0] = self.data_low[0] - (2 * self.p.bardist)
            elif self._owner.sellSig:
                self.lines.sell[0] = self.data_high[0] + (2 * self.p.bardist)
            elif self._owner.idealBuySig:
                self.lines.buy_ideal[0] = self.data_low[0] - (3 * self.p.bardist)
            elif self._owner.idealSellSig:
                self.lines.sell_ideal[0] = self.data_high[0] + (3 * self.p.bardist)
                
    
    

    To plot this, you need to add the observer


  • administrators

    Not automated, but it can for sure do the trick

    And good to see that things are getting more multilingual each day (at least I can read and understand this one)



  • Thanks @dasch & @backtrader
    Impressive community!