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



  • 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()
    
    


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



  • 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 :)



  • @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) ?



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



  • @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,
          ]
    
    
    


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


Log in to reply
 

});