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/

    Multiple TP Strategy with SL

    Indicators/Strategies/Analyzers
    3
    8
    702
    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.
    • B
      balibou last edited by

      Hello everyone !

      Just a reminder:
      TP for Take Profit
      SL for Stop Loss

      I'm currently coding a multiple TP strategy with dynamic SL size (when a TP is triggered, I remove the size of the TP from the SL in order that when the SL is triggered, only the size still in position will be sold).

      I just use a moving average cross up to enter in position then I set:

      • 4 TP: +3%, 6%, 12%, 20%
      • 1 SL: -3%

      I have tested it and it seems to work well apart a bug I don't understand how to solve:
      when a SL is triggered and a TP is triggered on the same bar, my algorithm will trigger both ...

      The strategy is on the repo. You just have to run main.py. There already are BTC and ETH data. If you want to run it with ETH, then in main.py change line 10 to dataname="dataset/ETHUSDT-1m.csv"

      If you run BTC, everything work.
      If you run ETH, you have this bug:

      2018-09-20 - Buy Order Completed - Size: 434.3725036443209 @Price: 212.56 Value: 92330.22 Comm: 92.33
      STOP LOSS @price: 206.1832
      TP1 size: 108.59312591108022, TP2 size: 108.59312591108022, TP3 size: 108.59312591108022, TP4 size: 108.59312591108022
      2018-09-20 210.40
      2018-09-20 210.40
      2018-09-20 - Sell Order Completed - Size: -434.3725036443209 @Price: 206.1832 Value: 92330.22 Comm: 89.56
      2018-09-20 - Sell Order Completed - Size: -108.59312591108022 @Price: 216.71200000000002 Value: -23533.43 Comm: 23.53
      
      File "multiple-tp-strategy/stopLoss.py", line 38, in notify_order
          if (order.ref == self.TakeProfitList[0].ref):
      IndexError: list index out of range
      

      PS: In my algorithm, this is normal that the list (of TP) is empty, because if a SL is triggered, then I cancel the TP orders in the list, then I empty the list.

      PS:

      • Maybe some part of the algorithm should be in def next(self) and not in def notify_order(self, order)
      • I plan to add break-even SL (when the first TP is triggered, the SL price is increased in order not to lose any money on the position)

      Here is the strategy, any help would be much appreciated:

      import backtrader as bt
      
      class BaseStrategy(bt.Strategy):
          params = dict(fast_ma=10, slow_ma=20,)
      
          def __init__(self):
              self.stopLossList = []
              self.TakeProfitList = []
              self.shouldUpdateSLSize = False
              self.SLSizeToUpdate = 0
      
              fast_ma = bt.ind.EMA(period=self.p.fast_ma)
              slow_ma = bt.ind.EMA(period=self.p.slow_ma)
              self.crossup = bt.ind.CrossUp(fast_ma, slow_ma)
      
      class MultipleTPWithSL(BaseStrategy):
          params = dict(
              stop_loss_percentage=3,
          )
      
          def notify_order(self, order):
              if not order.status == order.Completed: # Be sure that the broker set the order
                  return  # discard any other notification
      
              otypetxt = 'Buy' if order.isbuy() else 'Sell'
      
              if order.status == order.Completed:
                  print(
                      ('{} - {} Order Completed - '
                       'Size: {} @Price: {} '
                       'Value: {:.2f} Comm: {:.2f}').format(
                      self.date, otypetxt, order.executed.size, order.executed.price,
                      order.executed.value, order.executed.comm)
                  )
      
              if (otypetxt == 'Sell'):
                  # For each triggered TP, we update SL size
                  if (order.ref == self.TakeProfitList[0].ref):
                      print('Update size of the SL (TP 1)')
                      self.shouldUpdateSLSize = True
                      self.SLSizeToUpdate += order.executed.size
                  elif (order.ref == self.TakeProfitList[1].ref):
                      print('Update size of the SL (TP 2)')
                      self.shouldUpdateSLSize = True
                      self.SLSizeToUpdate += order.executed.size
                  elif (order.ref == self.TakeProfitList[2].ref):
                      print('Update size of the SL (TP 3)')
                      self.shouldUpdateSLSize = True
                      self.SLSizeToUpdate += order.executed.size
                  elif (order.ref == self.TakeProfitList[3].ref):
                      self.stopLossList[0].cancel()
                      self.stopLossList = []
                      self.shouldUpdateSLSize = False
                  elif (order.ref == self.stopLossList[0].ref): # if SL is triggered, we cancel all TP orders
                      self.TakeProfitList[0].cancel()
                      self.TakeProfitList[1].cancel()
                      self.TakeProfitList[2].cancel()
                      self.TakeProfitList[3].cancel()
                      self.TakeProfitList = []
                      self.shouldUpdateSLSize = False
                  return
              elif(otypetxt == 'Buy'):
                  # We have entered the market
                  self.buyPrice = order.executed.price
      
                  # ---- Create Stop Loss list ----
                  stop_price = order.executed.price * (1 - (self.p.stop_loss_percentage) / 100)
                  print("STOP LOSS @price: {}".format(stop_price))
                  self.stopLossList = [
                      self.sell(exectype=bt.Order.Stop, price=stop_price)
                  ]
                  # ---------------------------------
      
                  # ---- Create Take Profit list ----
                  firstTPSize = order.size * 0.25
                  secondTPSize = order.size * 0.25
                  thirdTPSize = order.size * 0.25
                  fourthTPSize = order.size - firstTPSize - secondTPSize - thirdTPSize
      
                  print('TP1 size: {}, TP2 size: {}, TP3 size: {}, TP4 size: {}'.format(
                      firstTPSize, secondTPSize, thirdTPSize, fourthTPSize
                  ))
      
                  self.TakeProfitList = [
                      self.sell(exectype=bt.Order.Limit, price=self.data.close * 1.03, size= firstTPSize),
                      self.sell(exectype=bt.Order.Limit, price=self.data.close * 1.06, size= secondTPSize),
                      self.sell(exectype=bt.Order.Limit, price=self.data.close * 1.12, size= thirdTPSize),
                      self.sell(exectype=bt.Order.Limit, price=self.data.close * 1.20, size= fourthTPSize)
                  ]
                  # ---------------------------------
      
          def next(self):
              self.date = self.data.datetime.date()
              print("{} {:.2f}".format(self.date, self.data[0]))
      
              if len(self.stopLossList) > 0 and self.shouldUpdateSLSize:
                  print('SL size: {}, Next SL Size position: {}'.format(self.stopLossList[0].created.size, self.position.size))
                  print("{} Stop size changed to: {:.20f}".format(self.date, self.position.size))
      
                  # delete previous SL and create new SL with updated size
                  self.stopLossList[0].cancel()
                  new_stop_price = self.buyPrice * (1 - 3 / 100)
                  self.stopLossList = [
                      self.sell(exectype=bt.Order.Stop, price=new_stop_price, size=self.position.size)
                  ]
      
                  self.shouldUpdateSLSize = False
                  self.SLSizeToUpdate = 0
      
              else:
                  pass
      
              if not self.position and self.crossup > 0:
                  # not in the market and signal triggered
                  o1 = self.buy()
      
      
      1 Reply Last reply Reply Quote 0
      • run-out
        run-out last edited by

        Have you considered using one cancels other orders? This might be better than trying to manually manage the cancellation process. I have had success doing it this way.

        Also, it might not be feasible to implement your method in a live setting.

        RunBacktest.com

        1 Reply Last reply Reply Quote 0
        • B
          balibou last edited by

          Thanks for your feedback @run-out !

          Actually I plan to reach all TPs in my strategy. OCO orders stop when one of the target is reached, so I don't think this method is relevant to me.
          Also, I'd like to have your feedback about the specific reason why you think it wouldn't be feasible in a live trading :)

          1 Reply Last reply Reply Quote 0
          • B
            balibou last edited by

            @run-out Oh I think I get it for the OCO orders. You mean:

            • Creating 4 OCO
            • Each OCO has 1 SL and 1 TP (All OCO SL will be the same price)
            • example:
              - oco 1: {SL: -3%, TP: +3%}
              - oco 2: {SL: -3%, TP: +6%}
              - oco 3: {SL: -3%, TP: +12%}
              - oco 4: {SL: -3%, TP: +20%}

            Right ?
            Will it be possible to update all oco2, oco3, oco4 when oco1 has triggered the TP and not the SL (in order to be at break even for oco2, oco3, oco4) ?

            1 Reply Last reply Reply Quote 0
            • B
              balibou last edited by

              Or maybe I can also use bracket orders for tackling this strategy, but according to @ab_trader in https://community.backtrader.com/topic/2488/updating-stop-loss-based-on-current-profit, it shouldn't be easy to implement break-even stop loss...

              Lot of possibilities 😅

              1 Reply Last reply Reply Quote 0
              • B
                balibou last edited by

                @run-out here is an update:

                I tried with a bracket order, but at some point it sounds that backtrader has an issue when dealing with order size.

                When all my orders are canceled I still have a position of 8.881784197001252e-16 (and impossible to create new order because of the line if not self.position, also backtrader is not working with if self.position.size < 1).

                You can still run the sample in this repo (main.py)

                PS: the sizer is not anymore 99 but 24 percents (in order to have 24% of the capital on each buy order)

                If you can help a bit :)

                The strategy is now:

                import backtrader as bt
                
                class BaseStrategy(bt.Strategy):
                    params = dict(fast_ma=10, slow_ma=20,)
                
                    def __init__(self):
                      self.orefs = list()
                      fast_ma = bt.ind.EMA(period=self.p.fast_ma)
                      slow_ma = bt.ind.EMA(period=self.p.slow_ma)
                      self.crossup = bt.ind.CrossUp(fast_ma, slow_ma)
                
                class MultipleTPWithSL(BaseStrategy):
                  def notify_order(self, order):
                      print('{}: Order ref: {} / Type {} / Status {}'.format(
                        self.data.datetime.date(0),
                        order.ref, 'Buy' * order.isbuy() or 'Sell',
                        order.getstatusname()
                      ))
                
                      if order.status == order.Completed:
                        self.holdstart = len(self)
                
                      if not order.alive() and order.ref in self.orefs:
                        self.orefs.remove(order.ref)
                
                
                  def next(self):
                    self.date = self.data.datetime.date()
                    print("{} {:.2f}".format(self.date, self.data[0]))
                
                    if self.orefs:
                      return  # pending orders do nothing
                    
                    print("Size is: {}".format(self.position.size))
                
                    if not self.position and self.crossup > 0:
                      # not in the market and signal triggered
                      close = self.data.close[0]
                      p1 = close
                      p2 = p1 - 0.03 * close
                      p3 = p1 + 0.03 * close
                      p4 = p1 + 0.06 * close
                      p5 = p1 + 0.12 * close
                      p6 = p1 + 0.24 * close
                
                      o1 = self.buy(
                        exectype=bt.Order.Limit,
                        price=p1,
                        transmit=False
                      )
                
                      print('{}: Oref {} / Buy at {}'.format(
                          self.datetime.date(), o1.ref, p1))
                
                      o2 = self.sell(
                        exectype=bt.Order.Stop,
                        price=p2,
                        parent=o1,
                        transmit=False
                      )
                
                      print('{}: Oref {} / Sell Stop at {}'.format(self.datetime.date(), o2.ref, p2))
                
                      o3 = self.sell(
                        exectype=bt.Order.Limit,
                        price=p3,
                        parent=o1,
                        transmit=True
                      )
                
                      print('{}: Oref {} / Sell Limit at {}'.format(self.datetime.date(), o3.ref, p3))
                
                      print('------------------------------------------------------------------------')
                      o4 = self.buy(
                        exectype=bt.Order.Limit,
                        price=p1,
                        transmit=False
                      )
                
                      print('{}: Oref {} / Buy at {}'.format(
                          self.datetime.date(), o4.ref, p1))
                
                      o5 = self.sell(
                        exectype=bt.Order.Stop,
                        price=p2,
                        parent=o4,
                        transmit=False
                      )
                
                      print('{}: Oref {} / Sell Stop at {}'.format(self.datetime.date(), o5.ref, p2))
                
                      o6 = self.sell(
                        exectype=bt.Order.Limit,
                        price=p4,
                        parent=o4,
                        transmit=True
                      )
                
                      print('{}: Oref {} / Sell Limit at {}'.format(self.datetime.date(), o6.ref, p4))
                
                      print('------------------------------------------------------------------------')
                      o7 = self.buy(
                        exectype=bt.Order.Limit,
                        price=p1,
                        transmit=False
                      )
                
                      print('{}: Oref {} / Buy at {}'.format(self.datetime.date(), o7.ref, p1))
                
                      o8 = self.sell(
                        exectype=bt.Order.Stop,
                        price=p2,
                        parent=o7,
                        transmit=False
                      )
                
                      print('{}: Oref {} / Sell Stop at {}'.format(self.datetime.date(), o8.ref, p2))
                
                      o9 = self.sell(
                        exectype=bt.Order.Limit,
                        price=p5,
                        parent=o7,
                        transmit=True
                      )
                
                      print('{}: Oref {} / Sell Limit at {}'.format(self.datetime.date(), o9.ref, p5))
                
                      self.orefs = [
                        o1.ref, o2.ref, o3.ref,
                        o4.ref, o5.ref, o6.ref,
                        o7.ref, o8.ref, o9.ref,
                      ]
                
                
                
                1 Reply Last reply Reply Quote 0
                • B
                  balibou last edited by

                  Another update, I have fixed the issue about the size:

                  I added this to my strategy:

                  def notify_cashvalue(self, cash, value):
                        if (not self.position):
                          self.Totalfragment = math.floor(cash / self.data.close[0])
                  

                  Then in my next funtion I set 4 sizes:

                  OrderSize1 = self.Totalfragment * 0.25
                  OrderSize2 = self.Totalfragment * 0.25
                  OrderSize3 = self.Totalfragment * 0.25
                  OrderSize4 = self.Totalfragment - f1 - f2 - f3
                  

                  and I set:

                  • brackets order 1 size with OrderSize1
                  • brackets order 2 size with OrderSize2
                  • brackets order 3 size with OrderSize3
                  • brackets order 4 size with OrderSize4

                  And everything work.

                  I don't think using notify_cashvalue for setting the order size is a good pattern ... Do you think there is a better implementation ?

                  • Using in strategy the function setsizer ?
                  • Using in sizer the function _getsizing ?

                  Thanks for your feebacks !

                  ? 1 Reply Last reply Reply Quote 1
                  • ?
                    A Former User @balibou last edited by

                    @balibou did you figure a solution for the above. I am fairly new to python and was working to implement the idea discussed by you.

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