Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

    Custom Dynamic trailstop indicator stops after updating tradeopen parameter

    Indicators/Strategies/Analyzers
    2
    10
    517
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • S
      stevenm100 last edited by

      Hi,
      I had originally written this for a multidata strategy, and it didnt work as expected, so i wrote a single data strategy and it still didnt work, so wondering if anyone else has run into this...

      I have wanted an ATR trailing stop for a while, and can make it work using the strategy 'next', but wanted a custom indicator because then I can make it print the line on the plot. I notice that nobody has shared a custom trailingATR indicator anywhere, so had a go myself.

      I added some print() statements to help me debug:

      class TrailStop(bt.Indicator):
          lines = ('trailatr','trailstop',)
          params = ( ('tradeopen',False),('atr_period', 10),('trail_mult', 4),)
          plotinfo = dict(subplot=False)
          plotlines = dict(trailstop=dict(color='red', ls='--',_plotskip=False,),  
                              trailatr=dict(color='red', ls='-', _plotskip=True),
                          )         
      
          def init(self):
              self.l.trailatr = bt.ind.AverageTrueRange(period=self.p.atr_period)#self.p.atr_period)
      
          def next(self):
              print('from inside indicator next, tradeopen param = {}'.format(self.p.tradeopen)) # unable to access next when changing the param????
              if self.p.tradeopen == True:
                  self.lines.trailstop[0] = max(self.data.close[0], self.lines.trailstop[-1])
                  print('inside indicator if statement')
                  print(self.l.trailstop[0])
              else:
                  self.lines.trailstop[0] = 0
                  print('in indicator else statement')
                  print(self.l.trailstop[0])
      
      

      I instatiate the indicator in the strategy 'init':

      self.trailstop = TrailStop()
      self.l.trailatr = self.trailstop.trailstop
      

      note that the max statement is just a dummy to help me understand what was going on, the value is zero (i know from printing that too). The story continues....
      When I change the indicators parameter from the strategy 'next' like this:

      self.trailstop.p.tradeopen = True
      

      The printing stops and i get nothing from the indicator 'next' or from the strategy 'next' but the strategy runs through the data, reaches 'stop' and plots the chart at the end.

      In particular, I am puzzled why the indicator seems not to be accessing the IF statement or the ELSE statement after the parameter update.

      A snippet of the many lines of output looks like this:

      from inside indicator next, tradeopen param = False
      in indicator else statement
      from inside indicator next, tradeopen param = False
      in indicator else statement
      Trailstop parameter set to True (inside strategy next statement)
      15:15:00, NIO, BUY EXECUTED, Price: 43.12, Size: 383.00, Cost: 16514.96

      and that is where the printing stops.

      Any ideas what is going on, or has anyone else seen this? This is my first go at a dynamic indicator, and if i cant get it to work i'll have to live without seeing the trailstop on the chart.
      Failing that, has anyone got a trailingATR custom indicator to work?

      run-out 1 Reply Last reply Reply Quote 0
      • run-out
        run-out @stevenm100 last edited by

        @stevenm100 Please include your entire code.

        RunBacktest.com

        S 1 Reply Last reply Reply Quote 0
        • S
          stevenm100 @run-out last edited by

          @run-out

          sorry for not posting the whole thing.....Ive cut it down to something easier to understand and follow.

          Ive added some debugging print statements to follow along.

          The code attempts to use the custom indicator (copying the idea in the dynamic highest example, setting the param to True/False, 0 otherwise), and also calculating the stop price in the strategy next to show that it works outside of the custom indicator.
          note that if you copy/paste it directly after one week, you'll need to update the dates in the yfinance statement as it only goes back 7 days.

          I learned that the indicator runs through all of the data before the strategy does (printing len shows 388 from next in the indicator before the strategy next begins printing), and so the title is probably now incorrect

          but the thing I was trying to achieve still remains...how do I set the indicator parameter to True from the strategy? it doesnt seem to work for me as it does in the Dynamic Indicator example.

          I understand that I cant see the self.l.trailstop line on the chart because it is zero throughout (according to strategy print, it is nan according to indicator print), and so outside of the plotted area. I had wanted to see the trailstop appear in the visible range during the period that the param is set to True.

          Any thoughts or suggestions on why the custom indicator isnt working?

          representative output:

          ***INDICATOR NEXT self.l.trailatr[0] = nan, tradeopen param = False, at len: 385***
          ***INDICATOR NEXT self.l.trailatr[0] = nan, tradeopen param = False, at len: 386***
          ***INDICATOR NEXT self.l.trailatr[0] = nan, tradeopen param = False, at len: 387***
          ***INDICATOR NEXT self.l.trailatr[0] = nan, tradeopen param = False, at len: 388***
          ***STRAT NEXT self.TrailStop.trailstop[0] = 0.0, at len: 22***
          ******BUYING, SET PARAM TRUE*****
          stop price = 142.62
          ***STRAT NEXT self.TrailStop.trailstop[0] = 0.0, at len: 23***
          stop price updated = 142.78
          ***STRAT NEXT self.TrailStop.trailstop[0] = 0.0, at len: 24***
          stop price updated = 142.89
          ***STRAT NEXT self.TrailStop.trailstop[0] = 0.0, at len: 25***
          ***STRAT NEXT self.TrailStop.trailstop[0] = 0.0, at len: 26***
          
          import backtrader.feeds as btfeed
          import pandas as pd
          import backtrader as bt
          from pandas_datareader import data as pdr
          import yfinance as yf
          
          yf.pdr_override()
          
          class TrailStop(bt.Indicator):
              lines = ('trailatr','trailstop',)
              params = ( ('tradeopen',False),('atr_period', 10),('trail_mult', 4),)
              plotinfo = dict(subplot=False)
              plotlines = dict(trailstop=dict(color='blue', ls='--',_plotskip=False,),  
                                  trailatr=dict(color='black', ls='-', _plotskip=False),
                              )         
          
              def init(self):
                  self.l.trailatr = bt.indicators.AverageTrueRange(period=self.p.atr_period)
          
              def next(self):
                  print('***INDICATOR NEXT self.l.trailatr[0] = {}, tradeopen param = {}, at len: {}***'.format(self.l.trailatr[0], self.p.tradeopen, len(self.l.trailatr)))
                  
                  if self.p.tradeopen == True: # using "if True:" this gets accessed and result is self.l.trailstop[0] = 1. suggests uim using the wrong way to access param?
                      self.l.trailstop[0] = max(self.dataclose[0] - self.p.trail_mult * self.atr[0], self.l.trailstop[-1])
                      #print('inside indicator if statement')
          
                  else:
                      self.l.trailstop[0] = min(0,1)
                      #print('in indicator else statement')
          
           
          # Create a Stratey
          class TestStrategy(bt.Strategy):
              params = (
                  ('fast_ma',20),
                  ('trail_mult', 4),
              )
          
              def log(self, txt, dt=None): 
                  ''' Logging function fot this strategy'''
                  dt = dt or self.datas[0].datetime.date(0)
                  #print('%s, %s' % (dt.isoformat(), txt)) # #-out to turn logging off
          
              def __init__(self):
                  # Keep a reference to the "close" line in the data[0] dataseries
                  self.dataclose = self.datas[0].close
          
                  self.atr = bt.indicators.AverageTrueRange(self.datas[0])    # using for manually calculating exit in strategy next
                  self.stopprice = 0                                          # for manually working out the stop in strategy next
          
                  self.TrailStop = TrailStop(self.datas[0])                   # instantiate the TrailStop Class
          
                  # To keep track of pending orders and buy price/commission
                  self.order = None
                  self.buyprice = None
                  self.buycomm = None
          
                  # Add a MovingAverageSimple indicator
                  self.sma = bt.indicators.ExponentialMovingAverage(self.datas[0].close, period=self.p.fast_ma)
          
                  self.buysig = bt.indicators.AllN(self.datas[0].low > self.sma, period=3)
                  
          
              def notify_order(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 enough cash
                  if order.status in [order.Completed]:
                      if order.isbuy():
                          self.log(
                              'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                              (order.executed.price,
                               order.executed.value,
                               order.executed.comm))
          
                          self.buyprice = order.executed.price
                          self.buycomm = order.executed.comm
                      else:  # Sell
                          self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                                   (order.executed.price,
                                    order.executed.value,
                                    order.executed.comm))
          
                      # Keep track of which bar execution took place
                      self.bar_executed = len(self)
          
                  elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                      self.log('Order Canceled/Margin/Rejected')
          
                  # Write down: no pending order
                  self.order = None
          
              def notify_trade(self, trade):
                  if not trade.isclosed:
                      return
          
                  self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                           (trade.pnl, trade.pnlcomm))
          
              def next(self):
                  # Simply log the closing price of the series from the reference
                  self.log('Close, %.2f' % self.dataclose[0])
          
                  print('***STRAT NEXT self.TrailStop.trailstop[0] = {}, at len: {}***'.format(self.TrailStop.trailstop[0], len(self.datas[0])))
          
                  # Check if an order is pending ... if yes, we cannot send a 2nd one
                  if self.order:
                      return
          
                  # Check if we are in the market
                  if not self.position:
          
                      # Not yet ... we MIGHT BUY if ...
                      if self.buysig:
                          # BUY, BUY, BUY!!! (with default parameters)
                          self.log('BUY CREATE, %.2f' % self.dataclose[0])
          
                          # Keep track of the created order to avoid a 2nd order
                          self.order = self.buy()
                          self.TrailStop.p.tradeopen = True
                          print('******BUYING, SET PARAM TRUE*****')
                          self.stopprice = self.data.close[0] - self.p.trail_mult * self.atr[0]
                          print('stop price = {:.2f}'.format(self.stopprice))
          
                  elif self.data.close[0] <  self.stopprice :
                      self.close()
                      self.stopprice = 0
                      self.TrailStop.p.tradeopen = False
                      print('******SELLING, SET PARAM FALSE*****')
          
          
                  if self.stopprice < self.dataclose[0] - self.p.trail_mult * self.atr[0]:    # if old price < new price
                      self.stopprice = self.dataclose[0] - self.p.trail_mult * self.atr[0]    # assign new price
                      print('stop price updated = {:.2f}'.format(self.stopprice))
          
          
          starting_balance=50000
          
          
          if __name__ == '__main__':
              
              ticker_id       =   'AAPL'
              
              dataframe = pdr.get_data_yahoo(ticker_id, period='1d', interval='1m', start='2021-07-20',end='2021-07-21',prepost=False)
          
              dataframe = dataframe[dataframe.index.strftime('%H:%M') < '20:00']
              data = bt.feeds.PandasData(dataname=dataframe)
          
              # Create a cerebro entity, add strategy
              cerebro = bt.Cerebro()
              cerebro.addstrategy(TestStrategy)
          
              # Add the Data Feed to Cerebro, set desired cash, set desired commission
              cerebro.adddata(data)
              cerebro.broker.setcash(starting_balance)
              cerebro.broker.setcommission(commission=0.0)
          
              # Add a FixedSize sizer according to the stake
              cerebro.addsizer(bt.sizers.PercentSizer, percents=10)
          
              # Run over everything
              cerebro.run()
              cerebro.plot(style='candlestick')
          
          
          run-out 1 Reply Last reply Reply Quote 0
          • run-out
            run-out @stevenm100 last edited by

            @stevenm100 In order to match the dynamic indicator article, you will need to make a number of changes.

            1. Change your TrailStop init to include dunders:
            def __init__(self):
            
            1. Indicator next: Modify your atr and data close to the following:
            self.l.trailstop[0] = max(
                self.datas[0].close[0] - self.p.trail_mult * self.l.trailatr[0],
                self.l.trailstop[-1],
            )
            

            If you wish to add in the parameter manually, add tradeopen to your params in strategy and you can then feed this into the indicator TradeStop.

            class TestStrategy(bt.Strategy):
                params = (
                    ("fast_ma", 20),
                    ("trail_mult", 4),
                    ("tradeopen", True),
                )
            ...
            self.TrailStop = TrailStop(
                self.datas[0], tradeopen=self.p.tradeopen
                )
            

            To dynamically update, you can add the trade open boolean in notify_trade:

            def notify_trade(self, trade):
                self.TrailStop.p.tradeopen = trade.isopen
             
            

            Finally, I believe you need to set runonce=False when launching cerebro, This will evaluate the indicator with the strategy.

            cerebro.run(runonce=False)
            

            This seems to generate some results like you are looking for I believe.

            ***INDICATOR NEXT self.l.trailatr[0] = 0.10698710357525403, tradeopen param = True, at len: 214 tailstop = 146.34227850992914***
            ***STRAT NEXT self.TrailStop.trailstop[0] = 146.34227850992914, at len: 214***
            inside indicator if statement
            ***INDICATOR NEXT self.l.trailatr[0] = 0.10328759976069737, tradeopen param = True, at len: 215 tailstop = 146.34227850992914***
            ***STRAT NEXT self.TrailStop.trailstop[0] = 146.34227850992914, at len: 215***
            inside indicator if statement
            ***INDICATOR NEXT self.l.trailatr[0] = 0.10495835150337765, tradeopen param = True, at len: 216 tailstop = 146.34227850992914***
            ***STRAT NEXT self.TrailStop.trailstop[0] = 146.34227850992914, at len: 216***
            ******SELLING, SET PARAM FALSE*****
            stop price updated = 145.93
            in indicator else statement
            ***INDICATOR NEXT self.l.trailatr[0] = 0.10146324877491489, tradeopen param = False, at len: 217 tailstop = 0.0***
            ***STRAT NEXT self.TrailStop.trailstop[0] = 0.0, at len: 217***
            stop price updated = 145.94
            in indicator else statement
            ***INDICATOR NEXT self.l.trailatr[0] = 0.09918740729586091, tradeopen param = False, at len: 218 tailstop = 0.0***
            ***STRAT NEXT self.TrailStop.trailstop[0] = 0.0, at len: 218***
            stop price updated = 145.95
            

            The total code is here:

            import backtrader.feeds as btfeed
            import pandas as pd
            import backtrader as bt
            from pandas_datareader import data as pdr
            import yfinance as yf
            
            yf.pdr_override()
            
            
            class TrailStop(bt.Indicator):
                lines = (
                    "trailatr",
                    "trailstop",
                )
                params = (
                    ("tradeopen", False),
                    ("atr_period", 10),
                    ("trail_mult", 4),
                )
                plotinfo = dict(subplot=False)
                plotlines = dict(
                    trailstop=dict(
                        color="blue",
                        ls="--",
                        _plotskip=False,
                    ),
                    trailatr=dict(color="black", ls="-", _plotskip=False),
                )
            
                def __init__(self):
                    self.l.trailatr = bt.indicators.AverageTrueRange(period=self.p.atr_period)
            
                def next(self):
            
                    # self.p.tradeopen = True
                    if (
                        self.p.tradeopen
                    ):  # using "if True:" this gets accessed and result is self.l.trailstop[0] = 1. suggests uim using the wrong way to access param?
                        self.l.trailstop[0] = max(
                            self.datas[0].close[0] - self.p.trail_mult * self.l.trailatr[0],
                            self.l.trailstop[-1],
                        )
                        print("inside indicator if statement")
            
                    else:
                        self.l.trailstop[0] = min(0, 1)
                        print("in indicator else statement")
            
                    print(
                        "***INDICATOR NEXT self.l.trailatr[0] = {}, tradeopen param = {}, at len: {} tailstop = {}***".format(
                            self.l.trailatr[0],
                            self.p.tradeopen,
                            len(self.l.trailatr),
                            self.l.trailstop[0],
                        )
                    )
            
            
            # Create a Stratey
            class TestStrategy(bt.Strategy):
                params = (
                    ("fast_ma", 20),
                    ("trail_mult", 4),
                    ("tradeopen", True),
                )
            
                def log(self, txt, dt=None):
                    """ Logging function fot this strategy"""
                    dt = dt or self.datas[0].datetime.date(0)
                    # print('%s, %s' % (dt.isoformat(), txt)) # #-out to turn logging off
            
                def __init__(self):
                    # Keep a reference to the "close" line in the data[0] dataseries
                    self.dataclose = self.datas[0].close
                    atestst = self.p.tradeopen
                    self.atr = bt.indicators.AverageTrueRange(
                        self.datas[0]
                    )  # using for manually calculating exit in strategy next
                    self.stopprice = 0  # for manually working out the stop in strategy next
            
                    self.TrailStop = TrailStop(
                        self.datas[0], tradeopen=self.p.tradeopen
                    )  # instantiate the
                    # TrailStop Class
            
                    # To keep track of pending orders and buy price/commission
                    self.order = None
                    self.buyprice = None
                    self.buycomm = None
            
                    # Add a MovingAverageSimple indicator
                    self.sma = bt.indicators.ExponentialMovingAverage(
                        self.datas[0].close, period=self.p.fast_ma
                    )
            
                    self.buysig = bt.indicators.AllN(self.datas[0].low > self.sma, period=3)
            
                def notify_order(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 enough cash
                    if order.status in [order.Completed]:
                        if order.isbuy():
                            self.log(
                                "BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
                                % (order.executed.price, order.executed.value, order.executed.comm)
                            )
            
                            self.buyprice = order.executed.price
                            self.buycomm = order.executed.comm
                        else:  # Sell
                            self.log(
                                "SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
                                % (order.executed.price, order.executed.value, order.executed.comm)
                            )
            
                        # Keep track of which bar execution took place
                        self.bar_executed = len(self)
            
                    elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                        self.log("Order Canceled/Margin/Rejected")
            
                    # Write down: no pending order
                    self.order = None
            
                def notify_trade(self, trade):
                    self.TrailStop.p.tradeopen = trade.isopen
                    if not trade.isclosed:
                        return
            
                    self.log("OPERATION PROFIT, GROSS %.2f, NET %.2f" % (trade.pnl, trade.pnlcomm))
            
                def next(self):
                    # Simply log the closing price of the series from the reference
                    self.log("Close, %.2f" % self.dataclose[0])
            
                    print(
                        "***STRAT NEXT self.TrailStop.trailstop[0] = {}, at len: {}***".format(
                            self.TrailStop.trailstop[0], len(self.datas[0])
                        )
                    )
            
                    # Check if an order is pending ... if yes, we cannot send a 2nd one
                    if self.order:
                        return
            
                    # Check if we are in the market
                    if not self.position:
            
                        # Not yet ... we MIGHT BUY if ...
                        if self.buysig:
                            # BUY, BUY, BUY!!! (with default parameters)
                            self.log("BUY CREATE, %.2f" % self.dataclose[0])
            
                            # Keep track of the created order to avoid a 2nd order
                            self.order = self.buy()
                            self.TrailStop.p.tradeopen = True
                            print("******BUYING, SET PARAM TRUE*****")
                            self.stopprice = self.data.close[0] - self.p.trail_mult * self.atr[0]
                            print("stop price = {:.2f}".format(self.stopprice))
            
                    elif self.data.close[0] < self.stopprice:
                        self.close()
                        self.stopprice = 0
                        self.TrailStop.p.tradeopen = False
                        print("******SELLING, SET PARAM FALSE*****")
            
                    if (
                        self.stopprice < self.dataclose[0] - self.p.trail_mult * self.atr[0]
                    ):  # if old price < new price
                        self.stopprice = (
                            self.dataclose[0] - self.p.trail_mult * self.atr[0]
                        )  # assign new price
                        print("stop price updated = {:.2f}".format(self.stopprice))
            
            
            starting_balance = 50000
            
            if __name__ == "__main__":
                ticker_id = "AAPL"
            
                dataframe = pdr.get_data_yahoo(
                    ticker_id,
                    period="1d",
                    interval="1m",
                    start="2021-07-20",
                    end="2021-07-21",
                    prepost=False,
                )
            
                dataframe = dataframe[dataframe.index.strftime("%H:%M") < "20:00"]
                data = bt.feeds.PandasData(dataname=dataframe)
            
                # Create a cerebro entity, add strategy
                cerebro = bt.Cerebro()
                cerebro.addstrategy(TestStrategy)
            
                # Add the Data Feed to Cerebro, set desired cash, set desired commission
                cerebro.adddata(data)
                cerebro.broker.setcash(starting_balance)
                cerebro.broker.setcommission(commission=0.0)
            
                # Add a FixedSize sizer according to the stake
                cerebro.addsizer(bt.sizers.PercentSizer, percents=10)
            
                # Run over everything
                cerebro.run(runonce=False)
                cerebro.plot(style="candlestick")

            RunBacktest.com

            S 2 Replies Last reply Reply Quote 3
            • S
              stevenm100 @run-out last edited by

              @run-out
              Yes, that works. Thank you very much for your input, very much appreciated.

              1 Reply Last reply Reply Quote 1
              • S
                stevenm100 @run-out last edited by

                @run-out
                Ive been testing out lots of different things with the trailing ATR on a single dataset without issue, thank you for setting me straight!
                The issue of the day is when attempting the same on a multi-data strategy, I try to instantiate the indicator using a dictionary reference in place of the parameter self.p.tradeopen.

                This doesnt work. From some simple tests (instantiating the indicator with the dict value set as true or false in line 98) gives the expected result, but the strategy has lost the ability to dynamically set it when instantiating from a dict versus a param.
                When it is True, it is expected to print a blue line for the trailing stop that at first will be different to the other trailstop line.

                Is there something you can recommend to make the dynamic behaviour work in a multi data strategy? Im thinking of several work arounds, but again prefer to keep it simple.

                code below with latest trailing ATR TrailStop indicator in a multidata example.

                from pandas_datareader import data as pdr
                import yfinance as yf
                import backtrader as bt
                from datetime import datetime
                import pytz
                from pandas.tseries.offsets import BDay
                import pandas as pd
                
                yf.pdr_override()
                
                class TrailStop(bt.Indicator):
                    lines = (   'trailatr',
                                'trailstop',
                                'dyntrailstop',
                                'trailstoplong',
                                'trailstopshort',
                                )
                    
                    params = (  ('direction', 'Short'),        # Long is starting point
                                ('tradeopen', False),         # For dynamic behaviour from the strategy
                                ('atr_period', 8),
                                ('trail_mult', 3),
                                )
                
                    plotinfo = dict(subplot=False)
                    plotlines = dict(   trailstop       =   dict( ls='',    color='green',  marker='_', _plotskip=True,),
                                        dyntrailstop    =   dict( ls='',    color='blue',   marker='_', _plotskip=False,),
                                        trailatr        =   dict( ls='-',   color='black',              _plotskip=True),
                                        trailstoplong   =   dict( ls='',    color='green',  marker='_', _plotskip=False,),
                                        trailstopshort  =   dict( ls='',    color='red',    marker='_', _plotskip=False,),
                                        )
                
                    def __init__(self):
                        self.l.trailatr = bt.indicators.AverageTrueRange(period=self.p.atr_period)
                
                    def prenext(self):
                        self.l.trailstop[0] = 0
                        self.l.trailstoplong[0] = 0
                        self.l.trailstopshort[0] = 0
                        
                    def next(self):
                
                        if len(self.datas[0]) == 1:                 # first datapoint
                            
                            self.l.trailstopshort[0]    =   100000  # Very large number
                            self.l.trailstoplong[0]     =   0       # smallest number
                        
                        else:
                
                            if self.p.direction == 'Long':
                                
                                self.l.trailstopshort[0]    =   100000
                                self.l.trailstoplong[0]     =   max(self.datas[0].close[0] - self.p.trail_mult * self.l.trailatr[0], self.l.trailstoplong[-1],)
                                self.l.trailstop[0]         =   self.l.trailstoplong[0]
                                
                                if self.datas[0].close[0] < self.l.trailstoplong[0]:
                                    self.p.direction = 'Short'
                
                            else:
                
                                self.l.trailstoplong[0]     =   0              
                                self.l.trailstopshort[0]    =   min(self.datas[0].close[0] + self.p.trail_mult * self.l.trailatr[0], self.l.trailstopshort[-1],)
                                self.l.trailstop[0]         =   self.trailstopshort[0]
                                
                                if self.datas[0].close[0] > self.l.trailstopshort[0]:
                                    self.p.direction = 'Long' 
                
                        if self.p.tradeopen == True:  # Dynamic stop long direction only
                            self.l.dyntrailstop[0]  =   max(self.datas[0].close[0] - self.p.trail_mult * self.l.trailatr[0], self.l.dyntrailstop[-1],)
                        else:
                            self.l.dyntrailstop[0]  =   0
                
                class teststrat(bt.Strategy):
                    '''
                    Borrowed the multi data code snippet from here, but modified the strat:
                    https://www.backtrader.com/blog/posts/2017-04-09-multi-example/multi-example.html
                    oneplot = Force all datas to plot on the same master.
                    '''
                    params = (
                    #('tradeopen', False),  # believe a strategy level param is not required in multidata strat
                    ('atr_trail', 4),
                    ('pct_size', 0.05),
                    ('oneplot', False),
                    )
                
                    def __init__(self):
                        '''
                        Create an dictionary of indicators so that we can dynamically add the
                        indicators to the strategy using a loop. This mean the strategy will
                        work with any number of data feeds. 
                        '''
                        self.inds = dict()
                        
                        # setup tradestate
                        self.tradestate = dict()
                        for i, d in enumerate(self.datas):
                            self.tradestate[d]                  =   dict()
                            self.tradestate[d]['tradeopen']     =   False       # setup indicator switch for plotting trail atr
                             
                        for i, d in enumerate(self.datas):
                            self.inds[d] = dict()
                            # Trailstop
                            self.TrailStop                  =   TrailStop(d, tradeopen=self.tradestate[d]['tradeopen'])    #need to check if this param is unique to each data or is global?
                            self.inds[d]['trailstop']       =   self.TrailStop.trailstop
                            self.inds[d]['dyntrailstop']    =   self.TrailStop.dyntrailstop
                            self.inds[d]['sma']             =   bt.indicators.MovingAverageSimple(d.close, period=30)
                
                            # SIGNALS
                            self.inds[d]['buysig']          =   bt.indicators.CrossUp(d.close, self.inds[d]['sma'])
                
                            if i > 0: #Check we are not on the first loop of data feed:
                                if self.p.oneplot == True:
                                    d.plotinfo.plotmaster = self.datas[0]
                
                            # setup lot size
                            self.maxsize                            =   self.p.pct_size*self.broker.getcash()  # max position size as % of total bankroll
                
                            # startcash
                            self.startcash = self.broker.getvalue()
                
                    def next(self):
                        
                        for i, d in enumerate(self.datas):
                            dt, tm, dn = self.datetime.date(tz=pytz.timezone('US/Eastern')), self.datetime.time(tz=pytz.timezone('US/Eastern')), d._name
                            pos = self.getposition(d).size
                            
                            if not pos and self.inds[d]['buysig'] :                     # no position and buysig
                                
                                print('{} {} BUYSIG going long on ticker: {} @ {}'.format(dt, tm, dn, round(d.close[0],2)))
                                self.buy(data = d, size = (round(self.maxsize/d.close[0],0)) )
                                self.tradestate[d]['tradeopen'] = True
                                
                                
                            elif pos and d.close[0] < self.inds[d]['dyntrailstop']:             # exit using dynamic trailstop value
                                self.close(data = d)
                                print('{} {} EXIT trailingstop {} @ {}'.format(dt,tm, dn,round(d.close[0],2)))
                                self.tradestate[d]['tradeopen'] = False
                                
                
                    def notify_trade(self, trade):
                        dt = self.data.datetime.date()
                        
                        if trade.isclosed:
                            pass
                
                    def log(self, txt, dt=None): 
                        ''' Logging function fot this strategy'''
                        dt = dt or self.datas[0].datetime.time(0)
                        print('%s, %s' % (dt.isoformat(),  txt)) # #-out to turn logging off
                    
                    def notify_order(self, order):
                        
                        dt = self.datas[0].datetime.time(0)
                        
                        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 enough cash
                        if order.status in [order.Completed]:
                            if order.isbuy():
                                self.log('BUY EXECUTED, %s, Price: %.2f, Size: %.2f, Cost: %.2f' %
                                    (order.data._name,
                                    order.executed.price,
                                    order.executed.size,
                                    order.executed.value,
                                    
                                    ))
                
                                self.buyprice = order.executed.price
                                self.buycomm = order.executed.comm
                            else:  # Sell
                                self.log('SELL EXECUTED, %s, Price: %.2f, Size: %.2f, Cost: %.2f' %
                                        (order.data._name,
                                        order.executed.price,
                                        order.executed.size,
                                        order.executed.value,
                                        ))
                
                        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                            self.log('%s, Order Canceled/Margin/Rejected' %
                            (order.data._name,
                            ))
                
                    def stop(self):
                        pass
                
                #Variable for our starting cash
                startcash = 100000
                
                #Create an instance of cerebro
                cerebro = bt.Cerebro()
                
                #Add our strategy
                cerebro.addstrategy(teststrat, atr_trail=4, pct_size=0.1, oneplot=False)
                
                plot = True
                
                #create our data list
                watchlist = ['AMC','DIDI',]#	'AAPL',	'SPCE',	'MRNA',	'NIO',	'GE',	'BAC',	'AMD',] # add more as required
                
                for ticker in watchlist:
                    dataframe = pdr.get_data_yahoo(ticker, period='1d', interval='1m', prepost=False)
                    data = bt.feeds.PandasData(dataname=dataframe, tz=pytz.timezone('US/Eastern'))
                    cerebro.adddata(data, name=ticker)
                
                # Set our desired cash start
                cerebro.broker.setcash(startcash)
                
                # Run over everything
                cerebro.run(maxcpus=1, runonce=True)
                
                #Get final portfolio Value
                portvalue = cerebro.broker.getvalue()
                pnl = portvalue - startcash
                
                #Print out the final result
                print('Final Portfolio Value: ${}'.format(portvalue))
                print('P/L: ${}'.format(pnl))
                
                #Finally plot the end results
                if plot == True:
                    cerebro.plot(style='candlestick')
                
                run-out 1 Reply Last reply Reply Quote 0
                • run-out
                  run-out @stevenm100 last edited by

                  @stevenm100 Have you tried calling the dictionary from notify_trade?

                  def notify_trade(self, trade):
                      # self.TrailStop.p.tradeopen = trade.isopen
                      self.tradestate[trade.data]['tradeopen']     =   True
                  

                  RunBacktest.com

                  S 1 Reply Last reply Reply Quote 0
                  • S
                    stevenm100 @run-out last edited by

                    @run-out said in Custom Dynamic trailstop indicator stops after updating tradeopen parameter:

                    self.tradestate[trade.data]['tradeopen'] = True

                    I have, and no joy. I thought I could be missing something basic in the way the dict is set up in init in a multidata strategy (i.e. the data type is wrong, and so cannot be used in the instantiation of the indicator):

                    __init__
                    self.tradestate = dict()
                    for i, d in enumerate(self.datas):
                        self.tradestate[d]                  =   dict()
                        self.tradestate[d]['tradeopen']     =   False
                    

                    versus a parameter in a single data strategy class such as

                    class teststrat(bt.strategy):
                    params = (('tradeopen', True),).
                    

                    I added a print line to debug and it appears that the self.tradestate[d]['tradeopen'] value is updating, but the indicator is ignoring:

                    in next:

                    print(str(d._name) + " " + str(self.tradestate[d]['tradeopen']))
                    

                    produces output like this, where you can see the state is changed False --> True after the first buysig and happens on one data before the other, all looking good to here:

                    AMC False
                    DIDI False
                    AMC False
                    DIDI False
                    AMC False
                    2021-08-06 10:02:00 BUYSIG going long on ticker: AMC @ 32.73
                    DIDI False
                    10:03:00, BUY EXECUTED, AMC, Price: 32.73, Size: 306.00, Cost: 10015.07
                    AMC True
                    DIDI False
                    AMC True
                    DIDI False
                    AMC True
                    DIDI False
                    AMC True
                    DIDI False
                    2021-08-06 10:06:00 BUYSIG going long on ticker: DIDI @ 9.64
                    10:07:00, BUY EXECUTED, DIDI, Price: 9.64, Size: 1038.00, Cost: 10006.32
                    AMC True
                    DIDI True
                    AMC True
                    DIDI True
                    

                    But the indicator isnt using this information anymore.
                    If you have any other thoughts or suggestions i'd be grateful.

                    run-out 1 Reply Last reply Reply Quote 0
                    • run-out
                      run-out @stevenm100 last edited by

                      @stevenm100
                      I notice now that this structure seems a little odd.

                      for i, d in enumerate(self.datas):
                                  self.inds[d] = dict()
                                  # Trailstop
                                  self.TrailStop                  =   TrailStop(d, tradeopen=self.tradestate[d]['tradeopen'])    #need to check if this param is unique to each data or is global?
                                  self.inds[d]['trailstop']       =   self.TrailStop.trailstop
                                  self.inds[d]['dyntrailstop']    =   self.TrailStop.dyntrailstop
                                  self.inds[d]['sma']             =   bt.indicators.MovingAverageSimple(d.close, period=30)
                      
                      

                      I would be inclined to drop the two lines (trailstop and dyntrailstop) and just have the indicator part of the inds dictionary. The indicator needs to be dynamically updated but you are not saving the indicator itself, just the lines.

                      for i, d in enumerate(self.datas):
                                  self.inds[d] = dict()
                                  # Trailstop
                                  self.inds[d]['trailstop'] = TrailStop(d, tradeopen=self.tradestate[d]['tradeopen'])    
                                  self.inds[d]['sma'] = bt.indicators.MovingAverageSimple(d.close, period=30)
                      
                      

                      Then add the trailstop and dyntrailstop lines in next as example:

                      elif pos and d.close[0] < self.inds[d]['trailstop'].dyntrailstop[0]:
                      

                      Don't forget to add [0] in next to indicate the current bar. It's more explicit.

                      Now you should be able to set the parameter directly on the indicator instead of relying on global setting.

                      The parameter is attached to the indicator in strategy. We now have an indicator for each symbol. If you wish to change the indicator parameter directly try:

                      def notify_trade(self, trade):
                          # self.TrailStop.p.tradeopen = trade.isopen
                          self.ind[trade.data].p.tradeopen = True
                      

                      RunBacktest.com

                      S 1 Reply Last reply Reply Quote 1
                      • S
                        stevenm100 @run-out last edited by

                        @run-out
                        Thanks for the detailed reply. Agree there was some clumsiness about the setup, but I had mirrored what I had in a single data example which was working. I have streamlined it now though per your suggestion.
                        Anyway, after some banging of my head on the desk, I found what was wrong with my multi-data version and it was simple.....the magical runonce=False, which i hadnt copied over from my single-data example.

                        # Run over everything
                        cerebro.run(runonce=False, maxcpus=1) # runonce = False is required
                        

                        now it all works as expected. Thanks for helping me though it. Hopefully the Dynamic Trailstop indicator is useful to other folks too now that its shared. Cheers!

                        1 Reply Last reply Reply Quote 1
                        • 1 / 1
                        • First post
                          Last post
                        Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors