Backtrader Community

    • 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/

    Trailing stop loss

    General Code/Help
    7
    12
    11761
    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.
    • D
      DesHartman last edited by

      Hi,

      I am very new to BackTrader. I have worked through the docs and I am specifically looking for a way to implement a trailing stop loss. There does not seem to be a way to do this or am I missing a subtlety here?

      What I want to do is place a long order, that has a constant trailing value. e.g. Buy at $1.75 with $0.10 trailing loss. If the stock goes to say $2.15, the exit stop loss will update to exit at $2.05. If the stock drop to $2.05 or below, the trade automatically exists with a sell and I am notified.

      If it exists, could you point me to the doc?

      Thanks
      Des

      A 1 Reply Last reply Reply Quote 0
      • A
        ab_trader @DesHartman last edited by

        @DesHartman I didn't find it as a built-in function, so it is one of my tasks. I plan to share my code as soon as it will be ready.

        Idea: send stop order after the main order is executed. Then cancel and send new stop order in the 'strategy.next ()'.

        • If my answer helped, hit reputation up arrow at lower right corner of the post.
        • Python Debugging With Pdb
        • New to python and bt - check this out
        1 Reply Last reply Reply Quote 0
        • S
          skytrading54 last edited by

          knowing that IB directly supports trailing order I am also interested in this.

          1 Reply Last reply Reply Quote 0
          • D
            DesHartman last edited by

            @ab_trader That would be fantastic

            Most on-line brokers allow you to place a stop loss at the time of the order, so they execute it and notify you. I guess to simulate this, your approach would then work by updating the exit in the 'strategy.next()' method on each tick.

            Looking forward to seeing the code ;-)

            1 Reply Last reply Reply Quote 0
            • C
              cwse last edited by

              @ab_trader any progress? :-)

              1 Reply Last reply Reply Quote 0
              • RandyT
                RandyT last edited by

                I spent some time looking at this recently as well. I am wondering if we should not consider adopting the IB Python API code as an option in Backtrader to do this. I recognize that anyone using it would have to use Python3, which is fine by me but may not be for everyone. Therefore, would probably need to be an option.

                Just a thought

                1 Reply Last reply Reply Quote 0
                • A
                  ab_trader last edited by

                  Here is the code that I came up with. I shown only pieces related to the stop loss and take profit orders processing. The code was developed for back testing only, I am not sure if it will be suitable for real-time trading.

                  Potentially this code can be improved, any suggestions are welcomed!

                  class MyStrategy(bt.Strategy):
                  
                      def __init__(self):
                  
                          # init stop loss and take profit order variables
                          self.sl_order, self.tp_order = None, None
                  
                      def notify_trade(self, trade):
                         
                          if trade.isclosed:
                              # clear stop loss and take profit order variables for no position state
                              if self.sl_order:
                                  self.broker.cancel(self.sl_order)
                                  self.sl_order = None
                  
                              if self.tp_order:
                                  self.broker.cancel(self.tp_order)
                                  self.tp_order = None
                  
                      def next(self):
                  			
                          # process stop loss and take profit signals
                          if self.position:
                  
                  	    # set stop loss and take profit prices
                              # in case of trailing stops stop loss prices can be assigned based on current indicator value
                              price_sl_long = self.position.price * 0.98
                              price_sl_short = self.position.price * 1.02
                              price_tp_long = self.position.price * 1.06
                              price_tp_short = self.position.price * 0.94
                  
                              # cancel existing stop loss and take profit orders
                              if self.sl_order:
                                  self.broker.cancel(self.sl_order)
                  
                              if self.tp_order:
                                  self.broker.cancel(self.tp_order)
                  
                              # check & update stop loss order
                              sl_price = 0.0
                              if self.position.size > 0 and price_sl_long !=0: sl_price = price_sl_long
                              if self.position.size < 0 and price_sl_short !=0: sl_price = price_sl_short
                  
                              if sl_price != 0.0:
                                  self.sl_order = self.order_target_value(target=0.0, exectype=bt.Order.Stop, price=sl_price)
                              
                              # check & update take profit order
                              tp_price = 0.0
                              if self.position.size > 0 and price_tp_long !=0: tp_price = price_tp_long
                              if self.position.size < 0 and price_tp_short !=0: tp_price = price_tp_short
                  
                              if tp_price != 0.0:
                                  self.tp_order = self.order_target_value(target=0.0, exectype=bt.Order.Limit, price=tp_price)
                  
                  • If my answer helped, hit reputation up arrow at lower right corner of the post.
                  • Python Debugging With Pdb
                  • New to python and bt - check this out
                  1 Reply Last reply Reply Quote 3
                  • A
                    ab_trader last edited by ab_trader

                    Code above can be slightly modified in order to plot stop loss and take profit levels on the price diagram. Stop loss and take profit prices need to be defined as class functions:
                    sl_price is changed to self.sl_price
                    tp_price is changed to self.tp_price

                    Then the following observer can be added:

                    class SLTPTracking(bt.Observer):
                    
                        lines = ('stop', 'take')
                    
                        plotinfo = dict(plot=True, subplot=False)
                    
                        plotlines = dict(stop=dict(ls=':', linewidth=1.5),
                                         take=dict(ls=':', linewidth=1.5))
                    
                        def next(self):
                    
                            if self._owner.sl_price != 0.0:
                                self.lines.stop[0] = self._owner.sl_price
                    
                            if self._owner.tp_price != 0.0:
                                self.lines.take[0] = self._owner.tp_price
                    

                    And then final plot will show stop loss and take profit levels: Picture

                    • If my answer helped, hit reputation up arrow at lower right corner of the post.
                    • Python Debugging With Pdb
                    • New to python and bt - check this out
                    yogi wahyu 1 Reply Last reply Reply Quote 1
                    • C
                      cwse last edited by

                      Cool thanks @ab_trader. But you think this wouldn't work in reality? How come?

                      Would be great to get a solution which works in RL.

                      PS: do you have the full code for which you ran this on?

                      Thanks!

                      1 Reply Last reply Reply Quote 0
                      • A
                        ab_trader last edited by

                        I am not familiar with real trading approach using bt. @RandyT probably can tell more on this.

                        Below is full code for sample trading system, implementing fixed stop loss and take profit orders.

                        from __future__ import (absolute_import, division, print_function, unicode_literals)
                        
                        import datetime as dt
                        import argparse
                        
                        import backtrader as bt
                        
                        def parse_args(pargs=None):
                        
                            parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
                                                             description=('Strategy auto-generation framework'))
                        
                            parser.add_argument('--data', required=False, default='SPY',
                                                metavar='TICKER', help='Yahoo ticker to download')
                        
                            parser.add_argument('--data_from', required=False, default='2015-01-01',
                                                metavar='YYYY-MM-DD[THH:MM:SS]',
                                                help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
                        
                            parser.add_argument('--data_to', required=False, default='2016-12-31',
                                                metavar='YYYY-MM-DD[THH:MM:SS]',
                                                help='Ending date[time]')
                        
                            parser.add_argument('--plot', required=False, default='',
                                                nargs='?', const='volume=True',
                                                help='kwargs in key=value format')
                        
                            return parser.parse_args(pargs)
                        
                        # script arguments parsing
                        args = parse_args(None)
                        
                        # quotes feed for one ticker
                        data = bt.feeds.YahooFinanceCSVData(dataname=args.data+'.csv',
                                                            fromdate=dt.datetime.strptime(args.data_from, '%Y-%m-%d'),
                                                            todate=dt.datetime.strptime(args.data_to, '%Y-%m-%d'),
                                                            reverse=True, adjclose=False, plot=True)
                        
                        # observer showing stop loss and take profit levels
                        class SLTPTracking(bt.Observer):
                        
                            lines = ('stop', 'take')
                        
                            plotinfo = dict(plot=True, subplot=False)
                        
                            plotlines = dict(stop=dict(ls=':', linewidth=1.5),
                                             take=dict(ls=':', linewidth=1.5))
                        
                            def next(self):
                        
                                if self._owner.sl_price != 0.0:
                                    self.lines.stop[0] = self._owner.sl_price
                        
                                if self._owner.tp_price != 0.0:
                                    self.lines.take[0] = self._owner.tp_price
                        
                        # master strategy
                        class MasterStrategy(bt.Strategy):
                        
                            params = (
                            ('ema_period', 200),
                            ('sl', 1.0), ('rr_ratio', 3), ('n', 5),
                                     )
                        
                            def __init__(self):
                        
                                # init order variables
                                self.order, self.sl_order, self.tp_order = None, None, None
                                self.sl_price, self.tp_price = 0.0, 0.0
                        
                                self.ema = bt.indicators.ExponentialMovingAverage(period=int(self.p.ema_period),
                                                                                  subplot=False)
                                
                                # entry signals
                                self.long = bt.indicators.CrossUp(self.data.close, self.ema)
                                self.short = bt.indicators.CrossDown(self.data.close, self.ema)
                        
                            def notify_trade(self, trade):
                        
                                if trade.isclosed:
                        
                                    # clear stop loss and take profit order variables for no position state
                                    if self.sl_order:
                                        self.broker.cancel(self.sl_order)
                                        self.sl_order = None
                                        self.sl_price = 0.0
                        
                                    if self.tp_order:
                                        self.broker.cancel(self.tp_order)
                                        self.tp_order = None
                                        self.tp_price = 0.0
                        
                            def notify_order(self, order):
                        
                                if order.status in [order.Completed, order.Margin]:
                                    
                                    # clear order variable
                                    self.order = None
                        
                            def next(self):
                        
                                # process entries at bar open if not in position
                                if not self.position:
                                    
                                   # check entry to long position
                                   if self.long[0] == True:
                                        self.order = self.order_target_percent(target=1.0)
                        
                                   # check entry to short position
                                   elif self.short[0] == True:
                                        self.order = self.order_target_percent(target=-1.0)
                                        
                                # process exits and position support orders if in position
                                if self.position:
                        
                                    price_sl_long = self.position.price * (1 - self.p.sl / 100)
                                    price_sl_short = self.position.price * (1 + self.p.sl / 100)
                        
                                    price_tp_long = self.position.price * (1 + self.p.sl / 100 * self.p.rr_ratio)
                                    price_tp_short = self.position.price * (1 - self.p.sl / 100 * self.p.rr_ratio)
                                    
                                    # cancel existing stop loss and take profit orders
                                    if self.sl_order:
                                        self.broker.cancel(self.sl_order)
                        
                                    if self.tp_order:
                                        self.broker.cancel(self.tp_order)
                        
                                    # check & update stop loss order
                                    self.sl_price = 0.0
                                    if self.position.size > 0 and price_sl_long !=0: self.sl_price = price_sl_long
                                    if self.position.size < 0 and price_sl_short !=0: self.sl_price = price_sl_short
                        
                                    if self.sl_price != 0.0:
                                        self.sl_order = self.order_target_value(target=0.0, exectype=bt.Order.Stop,
                                                                                price=self.sl_price)
                                    # check & update take profit order
                                    self.tp_price = 0.0
                                    if self.position.size > 0 and price_tp_long !=0: self.tp_price = price_tp_long
                                    if self.position.size < 0 and price_tp_short !=0: self.tp_price = price_tp_short
                        
                                    if self.tp_price != 0.0:
                                        self.tp_order = self.order_target_value(target=0.0, exectype=bt.Order.Limit,
                                                                                price=self.tp_price)
                        
                        # single backtest run
                        def single_backtest():
                        
                            cerebro = bt.Cerebro()
                            cerebro.addstrategy(MasterStrategy)
                            cerebro.addobserver(SLTPTracking)
                        
                            cerebro.adddata(data)
                            cerebro.broker.set_cash(1000000.0)
                            cerebro.broker.set_coc(True)
                            cerebro.run()
                        
                            print ('Broker value %0.2f' % cerebro.broker.getvalue())
                        
                            return cerebro
                        
                        if __name__ == '__main__':
                        
                            cerebro = single_backtest()
                        
                            if args.plot:
                                cerebro.plot(style='bar', numfigs=1,
                                                barup = 'black', bardown = 'black',
                                                barupfill = True, bardownfill = False,
                                                volup = 'green', voldown = 'red', voltrans = 50.0, voloverlay = False)
                        
                        • If my answer helped, hit reputation up arrow at lower right corner of the post.
                        • Python Debugging With Pdb
                        • New to python and bt - check this out
                        1 Reply Last reply Reply Quote 5
                        • B
                          backtrader administrators last edited by

                          The latest commit on the development branch contains support for StopTrail and StopTrailLimit orders.

                          It is still untested. At least nothing seems to be broken. Usage pattern:

                          # For a StopTrail going downwards
                          self.buy(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)  # last price will be used as reference
                          # or
                          self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)
                          
                          # For a StopTrail going upwards
                          self.sell(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)  # last price will be used as reference
                          # or
                          self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)
                          

                          One can also specify trailpercent instead of trailamount and the distance to the price will be calculated as a percentage of the price

                          # For a StopTrail going downwards with 2% distance
                          self.buy(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)  # last price will be used as reference
                          # or
                          self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.0.02)
                          
                          # For a StopTrail going upwards with 2% difference
                          self.sell(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)  # last price will be used as reference
                          # or
                          self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.02)
                          

                          This behaves like a Stop order when the stop price is reached. The order is executed as if it were a Market order.

                          The same concepts can be applied with StopTrailLimit as exectype. In this case one has to also specify plimit, just like with a StopLimit order. Once the stop price is reached, the execution takes place as a Limit order.

                          Commit: https://github.com/mementum/backtrader/commit/cb2dcecf2b055f4669531a059d2dc7a7a93b8430

                          1 Reply Last reply Reply Quote 0
                          • yogi wahyu
                            yogi wahyu @ab_trader last edited by

                            @ab_trader Thankyou for sharing, your information and code is verry help me 🙂

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