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/

    How to add takeprofit / stoploss?

    General Code/Help
    8
    18
    13570
    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.
    • X
      xnox last edited by

      Hello,

      In my broker when sending an order, I can set stop-loss and take-profit levels on it. Such that e.g. I can send a limit buy order, with stoploss and takeprofit levels and forget about it. It will either remain open, or close with profit/loss. Simulating this with backtrader appears to result in errors.

      In order notification, if buy order is executed I execute two sell orders: one limit order with stop loss, and one stop order with take profit. This eventually fails and results in stategy executing both of the sell orders. Ideally if one of these sell orders is executed, i would like the other one to be instantly killed.

      How should I code takeprofit/stoploss on my positions?

      Should I extend the bbroker itself to support stoploss/takeprofit?

      Regards,

      Dimitri.

      1 Reply Last reply Reply Quote 1
      • B
        backtrader administrators last edited by

        "Simulating this with backtrader appears to result in errors."

        backtrader is only doing what you are telling it to do. That isn't an error.

        In order notification, if buy order is executed I execute two sell orders: one limit order with stop loss, and one stop order with take profit. This eventually fails and results in strategy executing both of the sell orders. Ideally if one of these sell orders is executed, i would like the other one to be instantly killed.

        It doesn't fail. It simply doesn't fulfill your own expectations (it is nowhere stated in the documentation that one order will be cancelled because the other is executed)

        You are looking for OCO (Order Cancels Order) functionality and this is not implemented in backtrader

        How should I code takeprofit/stoploss on my positions?

        In the absence of OCO, a dictionary can do the trick, by adding two entries. In one the stop-loss order is the key and the take-profit order is the value. In the second entry the roles are reversed.

        Upon being notified of the execution of one order, inspection of the dictionary allows cancelling the pending order

        Should I extend the bbroker itself to support stoploss/takeprofit?

        The platform is open.

        1 Reply Last reply Reply Quote 2
        • dimitar-petrov
          dimitar-petrov last edited by

          Hi Guys,

          I used dictionary with orders to implement stoploss/takeprofit in a strategy following the advice above and it works but I am facing an issue in a specific scenario.

          If both limit and stop orders execution prices are between high and low on the next bar, they both get executed and I am unable to cancel the other one.

          # excerpt from notify_order
          ...
                  elif order.status == order.Completed:
                      if 'name' in order.info:
                          self.broker.cancel(self.order_dict[order.ref])                           
                      else:
                          if order.isbuy():
                              stop_loss = order.executed.price*(1.0 - (self.p.stoploss)/100.0)
                              take_profit = order.executed.price*(1.0 + 3*(self.p.stoploss)/100.0)
          
                              sl_ord = self.sell(exectype=bt.Order.Stop,
                                                 price=stop_loss)
                              sl_ord.addinfo(name="Stop")
          
                              tkp_ord = self.sell(exectype=bt.Order.Limit,
                                                  price=take_profit)
                              tkp_ord.addinfo(name="Prof")
          
                              self.order_dict[sl_ord.ref] = tkp_ord
                              self.order_dict[tkp_ord.ref] = sl_ord
          ...
          

          Here is an execution log.

          Starting Portfolio Value: 10000.00
          SignalPrice: 1155.12 Buy: 1155.17, Stop: 1154.01, Prof: 1158.64
          Open: 1 2002-01-10 1155.17 1.00 0.00
          TRADE,OPEN,2002-01-10,1155.17,0.0,0.0
          Stop: 2 2002-01-10 1154.01 -1.00 0.00
          TRADE PROFIT, GROSS -1.16, NET -1.16
          CANCELL,Prof,3,2002-01-10,0.0,0,0.0
          SignalPrice: 1132.83 Buy: 1132.94, Stop: 1131.81, Prof: 1136.34
          Open: 4 2002-01-17 1132.94 1.00 0.00
          TRADE,OPEN,2002-01-17,1132.94,0.0,0.0
          Stop: 5 2002-01-17 1131.81 -1.00 0.00
          TRADE PROFIT, GROSS -1.13, NET -1.13
          CANCELL,Prof,6,2002-01-17,0.0,0,0.0
          SignalPrice: 1119.31 Buy: 1120.13, Stop: 1119.01, Prof: 1123.49
          Open: 7 2002-01-23 1120.13 1.00 0.00
          TRADE,OPEN,2002-01-23,1120.13,0.0,0.0
          Stop: 8 2002-01-23 1119.01 -1.00 0.00
          Prof: 9 2002-01-23 1123.49 -1.00 0.00
          TRADE PROFIT, GROSS -1.12, NET -1.12
          TRADE,OPEN,2002-01-23,-1123.49039,0.0,0.0
          

          And the corresponding data:
          On first bar is getting the signal, on second bar is buy with market order and on third bar is executing both stop and limit orders.

          2002-01-22 16:00:00,1119.34,1119.42,1119.3,1119.31,0,1119.31
          2002-01-23 09:00:00,1120.13,1123.39,1120.11,1120.55,0,1120.55
          2002-01-23 10:00:00,1120.62,1124.16,1117.43,1124.16,0,1124.16
          

          BR,
          dpetrov

          U 1 Reply Last reply Reply Quote 0
          • B
            backtrader administrators last edited by

            That case needs real OCO emulation (as real as you can get in an emulation), because both orders meet the condition during the same bar evaluation in the broker.

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

              To correctly backtest such orders you need to use lower data time frame.

              • 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
              • Maxim Korobov
                Maxim Korobov last edited by

                In my playground profit_percents calculates in Super of this strategy. Then strategy check:

                if 3 < profit_percents or profit_percents < -1:
                    self.close() # sell/buy according to open order sign
                

                Nice idea about dictionary in orders.

                dimitar-petrov 1 Reply Last reply Reply Quote 0
                • dimitar-petrov
                  dimitar-petrov @Maxim Korobov last edited by

                  @Maxim-Korobov

                  Good idea, but I think this apporach will have 1 bar delay.

                  According to the docs:

                  • Stop and Limit orders will execute as soon as the price touches the order price
                  • Close will execute using the close price of the next barwhen the next bar actually CLOSES

                  I am going to test it today and compare.

                  Otherwise I can use data with higher frequency as mentioned by @ab_trader.
                  Is this going to change the strategy decisions if it is based on technical indicators?

                  BR,
                  dpetrov

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

                    If you use higher frequency to simulate execution of stop loss and take profit orders, and use your regular timeframe for indicators, than your base line data will be higher frequency data and you will use data resampling to get new data set for indicator calculations.

                    Data resampling - https://www.backtrader.com/docu/data-resampling/data-resampling.html

                    Also I was thinking about another approach without going to lower timeframes - check execution of the stop loss first, and then check execution of the take profit. This will be conservative, and I believe some additional coding be required, it will not be simple order sending.

                    • 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 1
                    • U
                      Usct @dimitar-petrov last edited by

                      @dimitar-petrov said in How to add takeprofit / stoploss?:

                      excerpt from notify_order

                      ...
                      elif order.status == order.Completed:
                      if 'name' in order.info:
                      self.broker.cancel(self.order_dict[order.ref])
                      else:
                      if order.isbuy():
                      stop_loss = order.executed.price*(1.0 - (self.p.stoploss)/100.0)
                      take_profit = order.executed.price*(1.0 + 3*(self.p.stoploss)/100.0)

                                      sl_ord = self.sell(exectype=bt.Order.Stop,
                                                         price=stop_loss)
                                      sl_ord.addinfo(name="Stop")
                      
                                      tkp_ord = self.sell(exectype=bt.Order.Limit,
                                                          price=take_profit)
                                      tkp_ord.addinfo(name="Prof")
                      
                                      self.order_dict[sl_ord.ref] = tkp_ord
                                      self.order_dict[tkp_ord.ref] = sl_ord
                      

                      ...

                      Can you please share the complete code

                      dimitar-petrov 1 Reply Last reply Reply Quote 0
                      • dimitar-petrov
                        dimitar-petrov @Usct last edited by

                        @Usct here you go,

                        from __future__ import (absolute_import, division, print_function,
                                                unicode_literals)
                        
                        
                        import backtrader as bt
                        import backtrader.indicators as btind
                        
                        
                        class BBands(bt.Strategy):
                            params = (
                                ('stoploss', 0.001),
                                ('profit_mult', 2),
                                ('prdata', False),
                                ('prtrade', False),
                                ('prorder', False),
                            )
                        
                            def __init__(self):
                                # import ipdb; ipdb.set_trace()
                                self.bbands = bt.talib.BBANDS(self.data,
                                                              timeperiod=25,
                                                              plotname='TA_BBANDS')
                        
                                self.order_dict = {}
                                self.buy_sig = btind.CrossOver(self.data, self.bbands.line2)
                                self.sell_sig = btind.CrossOver(self.bbands.line0, self.data)
                                self.trades = 0
                                self.order = None
                        
                            def start(self):
                                self.trades = 0
                        
                            def next(self):
                                if self.position:
                                    return
                                else:
                                    if self.buy_sig > 0:
                                        # import ipdb; ipdb.set_trace()
                                        self.buy(exectype=bt.Order.Market)
                                        self.last_sig_price = self.data.close[0]
                                        self.trades += 1
                                    elif self.sell_sig > 0:
                                        self.sell(exextype=bt.Order.Market)
                                        self.trades += 1
                        
                                        if self.p.prdata:
                                            print(','.join(str(x) for x in
                                            ['DATA', 'OPEN',
                                                self.data.datetime.date().isoformat(),
                                                self.data.close[0],
                                                self.buy_sig[0]]))
                        
                        
                            def notify_order(self, order):
                                # import ipdb; ipdb.set_trace()
                                if order.status in [order.Margin, order.Rejected]:
                                    pass
                                elif order.status == order.Cancelled:
                                    if self.p.prorder:
                                        print(','.join(map(str, [
                                            'CANCELL', order.info['OCO'], order.ref,
                                            self.data.num2date(order.executed.dt).date().isoformat(),
                                            order.executed.price,
                                            order.executed.size,
                                            order.executed.comm,
                                        ]
                                        )))
                                elif order.status == order.Completed:
                                    if 'name' in order.info:
                                        self.broker.cancel(self.order_dict[order.ref])
                                        self.order = None
                                        if self.p.prorder:
                                            print("%s: %s %s %.2f %.2f %.2f" %
                                                (order.info['name'], order.ref,
                                                self.data.num2date(order.executed.dt).date().isoformat(),
                                                order.executed.price,
                                                order.executed.size,
                                                order.executed.comm))
                                    else:
                                        if order.isbuy():
                                            stop_loss = order.executed.price*(1.0 - (self.p.stoploss))
                                            take_profit = order.executed.price*(1.0 + self.p.profit_mult*(self.p.stoploss))
                        
                                            sl_ord = self.sell(exectype=bt.Order.Stop,
                                                               price=stop_loss)
                                            sl_ord.addinfo(name="Stop")
                        
                                            tkp_ord = self.sell(exectype=bt.Order.Limit,
                                                                price=take_profit)
                                            tkp_ord.addinfo(name="Prof")
                        
                                            self.order_dict[sl_ord.ref] = tkp_ord
                                            self.order_dict[tkp_ord.ref] = sl_ord
                        
                                            if self.p.prorder:
                                                print("SignalPrice: %.2f Buy: %.2f, Stop: %.2f, Prof: %.2f"
                                                      % (self.last_sig_price,
                                                         order.executed.price,
                                                         stop_loss,
                                                         take_profit))
                        
                                        elif order.issell():
                                            stop_loss = order.executed.price*(1.0 + (self.p.stoploss))
                                            take_profit = order.executed.price*(1.0 - 3*(self.p.stoploss))
                        
                                            sl_ord = self.buy(exectype=bt.Order.Stop,
                                                              price=stop_loss)
                                            sl_ord.addinfo(name="Stop")
                        
                                            tkp_ord = self.buy(exectype=bt.Order.Limit,
                                                                price=take_profit)
                                            tkp_ord.addinfo(name="Prof")
                        
                                            self.order_dict[sl_ord.ref] = tkp_ord
                                            self.order_dict[tkp_ord.ref] = sl_ord
                        
                                        if self.p.prorder:
                                            print("Open: %s %s %.2f %.2f %.2f" %
                                                (order.ref,
                                                 self.data.num2date(order.executed.dt).date().isoformat(),
                                                order.executed.price,
                                                order.executed.size,
                                                order.executed.comm))
                        
                            def notify_trade(self, trade):
                                # import ipdb; ipdb.set_trace()
                                if self.p.prtrade:
                                    if trade.isclosed:
                                        print('TRADE PROFIT, GROSS %.2f, NET %.2f' %
                                            (trade.pnl, trade.pnlcomm))
                                    elif trade.justopened:
                                                print(','.join(map(str, [
                                                    'TRADE', 'OPEN',
                                                    self.data.num2date(trade.dtopen).date().isoformat(),
                                                    trade.value,
                                                    trade.pnl,
                                                    trade.commission,
                                                ]
                                                )))
                        
                            def stop(self):
                                print('(Stop Loss Pct: %2f, S/P Multiplier: %2f) Ending Value %.2f (Num Trades: %d)' %
                                         (self.params.stoploss, self.params.profit_mult, self.broker.getvalue(), self.trades))
                        
                        1 Reply Last reply Reply Quote 0
                        • U
                          Usct last edited by

                          Thanks a lot!

                          T 1 Reply Last reply Reply Quote 0
                          • T
                            Taewoo Kim @Usct last edited by

                            CAn this be achieved with bracket orders?

                            1 Reply Last reply Reply Quote 0
                            • B
                              backtrader administrators last edited by

                              That's the point of bracket orders which exactly implement this concept. But beware: only Interactive Brokers has native support for it.

                              T 1 Reply Last reply Reply Quote 0
                              • T
                                Taewoo Kim @backtrader last edited by

                                @backtrader So in the case of Oanda, i would have to implement the bracket orders manually like so?

                                (From your code:)

                                mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False)
                                lowside  = self.sell(price=13.00, size=mainsize.size, exectype=bt.Order.Stop,
                                                     transmit=False, parent=mainside)
                                highside = self.sell(price=14.00, size=mainsize.size, exectype=bt.Order.Limit,
                                                     transmit=True, parent=mainside)
                                
                                Martin Karas 1 Reply Last reply Reply Quote 0
                                • B
                                  backtrader administrators last edited by

                                  No. It means (unless it is somewhere well hidden in the docs) that model is not supported with Oanda.

                                  There may be something similar by directly specifying the prices, but it will have to be looked into.

                                  1 Reply Last reply Reply Quote 0
                                  • B
                                    backtrader administrators last edited by

                                    You may play with this commit:

                                    • https://github.com/mementum/backtrader/commit/9c6d8214e11ac93b2d031b60a49ab0379262f495

                                    It uses the built-in stoploss and takeprofit price attributes of an order in Oanda to make a complete Bracket order simulation.

                                    • You can use buy_bracket / sell_bracket or the manual pattern from above (the former two would be recommended)

                                      The execution type for the stop-side and profit-side cannot be set. They are controlled by Oanda. Only the prices can be set.

                                      You can still set the execution type for the main-side (Market, Limit, ...)

                                    Even if Oanda manages everything as a single order, inside backtrader will be 3 orders and cancellation of any order will cancel the others. There will be individual notifications for each of the orders.

                                    The commit adds also

                                    • StopTrail support for Oanda
                                    1 Reply Last reply Reply Quote 0
                                    • T
                                      Taewoo Kim last edited by

                                      @backtrader Oh man, you're awsome. Thanks for adding that code so quickly.

                                      1 Reply Last reply Reply Quote 0
                                      • Martin Karas
                                        Martin Karas @Taewoo Kim last edited by

                                        @taewoo-kim could you please elaborate how does the transmit parameter work here? why its set to false to stop order but true to take profit?
                                        What if both orders fill at the same candle? (Huge high-low) range

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