Pierre Cilliers
Pierre Cilliers

Reputation: 11

Sellstop order not executing if a buy trade is already open

I have run two tests which clearly explain the scenario. Please see code block below of notify_order and notify_trade.

**Test 1 (execute only 1 sellstop order): **

When I solely execute a sellstop order, I get what is expected. I get logs in my notify_order that my sell order gets SUBMITTED and ACCEPTED (not COMPLETED yet as the price has not reached the price of the sellstop yet). When the price reaches the trigger price for the sellstop order, I get logs in my notify_order that my sell order is COMPLETED and immediately thereafter, I get logs in my notify_trade that a sell trade has been executed (I use trade.justopened to track this).

**Test 2 (execute 1 buy trade and 1 sellstop order): **

When I open a buy trade (which executes immediately) and a sellstop order, I run into issues. I get logs in my notify_order that my buy trade is SUBMITTED, ACCEPTED and COMPLETED and I get logs in my notify_trade that the buy trade get successfully executed. Similar to above in Test 1, I get logs in my notify_order at the same time as the buy trade that my sell order gets SUBMITTED and ACCEPTED (but not COMPLETED yet as the price has not reached the price of the sellstop yet). When the price reaches the trigger price for the sellstop order, I still get logs in my notify_order that my sell order is COMPLETED but now I get no logs in my notify_trade. There is not code change expect for adding a buy trade in Test 2.

NOTE: When doing exactly the same with limit orders, everything works fine (limit orders are also pending orders like stop orders). It is strange that it only occurs with sell stop orders.

Has Backtrader published any Q&A regarding these order issues? Or does anybody have a solution?


def notify_order(self, order):
    """
    ORDERS are actions that are unfulfilled/pending
    (means they are waiting to enter or be triggered into the market)
    Args:
        order: order.status can be any of {order.Created, order.Submitted,
        order.Accepted, order.Partial,
        order.Completed, order.Canceled, order.Expired, order.Margin, order.Rejected}
        #todo type hint

    Returns: None
    """

    if order.status in [order.Submitted]:
        log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- Order {order.ref} Submitted at price {order.price}, commission {order.executed.comm}\n{order}')

        if order.p.exectype == 0 : # Market order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of BUY type Ref ID {order.ref} SUBMITTED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of SELL type Ref ID {order.ref} SUBMITTED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass

        elif order.p.exectype == 2 : # Limit order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of BUY type Ref ID {order.ref} SUBMITTED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of SELL type Ref ID {order.ref} SUBMITTED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass

        elif order.p.exectype == 3 : # Stop order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of BUY type Ref ID {order.ref} SUBMITTED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of SELL type Ref ID {order.ref} SUBMITTED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass
        else:
            pass
        return

    if order.status in [order.Accepted]:
        log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- Order {order.ref} Accepted at price {order.price}, commission {order.executed.comm}\n{order}')
        
        if order.p.exectype == 0 : # Market order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of BUY type Ref ID {order.ref} ACCEPTED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of SELL type Ref ID {order.ref} ACCEPTED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass

        elif order.p.exectype == 2 : # Limit order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of BUY type Ref ID {order.ref} ACCEPTED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of SELL type Ref ID {order.ref} ACCEPTED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass

        elif order.p.exectype == 3 : # Stop order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of BUY type Ref ID {order.ref} ACCEPTED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of SELL type Ref ID {order.ref} ACCEPTED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass
        else:
            pass
        return

    if order.status in [order.Completed]:
        log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- Order {order.ref} Completed at price {order.price}, commission {order.executed.comm}\n{order}')
                    
        if order.p.exectype == 0 : # Market order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of BUY type Ref ID {order.ref} COMPLETED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of SELL type Ref ID {order.ref} COMPLETED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass

        elif order.p.exectype == 2 : # Limit order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of BUY type Ref ID {order.ref} COMPLETED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of SELL type Ref ID {order.ref} COMPLETED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass

        elif order.p.exectype == 3 : # Stop order
            if order.isbuy():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of BUY type Ref ID {order.ref} COMPLETED at Price: {order.price}, commission {order.executed.comm}')
            elif order.issell():
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of SELL type Ref ID {order.ref} COMPLETED at Price: {order.price}, commission {order.executed.comm}')
            else:
                pass
        else:
            pass
        return

    if order.status in [order.Canceled]:
        log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- Order {order.ref} Canceled. Specs of order:\n{order}')

        if order.isbuy():
            if order.p.exectype == 0:
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of BUY type ID {order.info.ref} & Ref ID {order.ref} deleted')
            elif order.p.exectype == 2:
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of BUY type ID {order.info.ref} & Ref ID {order.ref} deleted')
            elif order.p.exectype == 3:
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of BUY type ID {order.info.ref} & Ref ID {order.ref} deleted')
            else:
                pass

        elif order.issell():
            if order.p.exectype == 0:
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- MARKET order of SELL type ID {order.info.ref} & Ref ID {order.ref} deleted')
            elif order.p.exectype == 2:
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- LIMIT order of SELL type ID {order.info.ref} & Ref ID {order.ref} deleted')
            elif order.p.exectype == 3:
                log.debug(f'{self.datetime.datetime(ago=0).isoformat()} --- STOP order of SELL type ID {order.info.ref} & Ref ID {order.ref} deleted')
            else:
                pass
        
    if order.status in [order.Expired]:
        log.info(f'{self.datetime.datetime(ago=0).isoformat()}  --- Order {order.ref} Expired and could not be executed')
        self.remove_open_order(order.p.tradeid)
        return

    if order.status in [order.Margin]:
        log.error(f'{self.datetime.datetime(ago=0).isoformat()}  --- Insufficient cash to execute order - increase starting cash or improve strategy: Order {order.ref} Margin')
        # log.error(f'{self.datetime.datetime(ago=0).isoformat()}  --- Current balance {self.broker.getcash()} and equity {self.broker.getvalue()} - trying to open order {order.ref} with size {}')
        self.cerebro.runstop()
        return

    if order.status in [order.Rejected]:
        log.info(f'{self.datetime.datetime(ago=0).isoformat()}  --- Order {order.ref} Rejected by broker and could not be executed')
        self.remove_open_order(order.p.tradeid)
        return

def notify_trade(self, trade):
    """
    TRADES are actions that are fulfilled
    (means they are active - in a trade and making/losing money)
    """
    if trade.justopened:

        # commission added when trade is opened (entry commission) and closed (exit commission)
        self.strategy.total_commission += trade.commission # calculating total commission paid
        # self.open_orders[trade.tradeid]['commission'] = trade.commission # assign commission of orders in open_orders dict
        
        try:
            if self.open_orders[trade.tradeid]['action'] == 'buy':
                log.debug(f"{self.datetime.datetime(ago=0).isoformat()} --- BUY trade ID {trade.tradeid} executed at price {trade.price}")
                
                # assign the actual executed entry price to the backtrader buy object
                self.buy_trade[trade.tradeid][0].price = trade.price

                # assign the actual executed entry price to initially opened buy trades; note that modification trades need to be tracked seperately
                if trade.tradeid not in self.modification_list:
                    self.strategy.total_long_orders += 1
                    self.open_orders[trade.tradeid]['price'] = trade.price
            
            elif self.open_orders[trade.tradeid]['action'] == 'sell':
                log.debug(f"{self.datetime.datetime(ago=0).isoformat()} --- SELL trade ID {trade.tradeid} executed at price {trade.price}")
                
                # assign the actual executed entry price to the backtrader sell object
                self.sell_trade[trade.tradeid][0].price = trade.price

                # assign the actual executed entry price to initially opened sell trades; note that modification trades need to be tracked seperately
                if trade.tradeid not in self.modification_list:
                    self.strategy.total_short_orders += 1
                    self.open_orders[trade.tradeid]['price'] = trade.price
        
            elif self.open_orders[trade.tradeid]['action'] == 'buylimit':
                log.debug(f"{self.datetime.datetime(ago=0).isoformat()} --- BUYLIMIT {self.buy_limit[trade.tradeid][0].ref} -> BUY TRADE ID {trade.tradeid}")
                self.open_orders[trade.tradeid]['action'] = 'buy'
                # assign the actual executed entry price to the backtrader buy object when a buylimit becomes a buy
                self.open_orders[trade.tradeid]['price'] = trade.price
                self.buy_trade[trade.tradeid] = self.buy_limit[trade.tradeid] # move buylimit objects to buy objects
                del self.buy_limit[trade.tradeid] # removing the buylimit objects as it became a buy trade
            
            elif self.open_orders[trade.tradeid]['action'] == 'buystop':
                log.debug(f"{self.datetime.datetime(ago=0).isoformat()} --- BUYSTOP {self.buy_stop[trade.tradeid][0].ref} -> BUY TRADE ID {trade.tradeid}")
                self.open_orders[trade.tradeid]['action'] = 'buy'
                # assign the actual executed entry price to the backtrader buy object when a buystop becomes a buy
                self.open_orders[trade.tradeid]['price'] = trade.price
                self.buy_trade[trade.tradeid] = self.buy_stop[trade.tradeid] # move buystop objects to buy objects
                del self.buy_stop[trade.tradeid] # removing the buystop objects as it became a buy trade
            
            elif self.open_orders[trade.tradeid]['action'] == 'selllimit':
                log.debug(f"{self.datetime.datetime(ago=0).isoformat()} --- SELLLIMIT {self.sell_limit[trade.tradeid][0].ref} -> SELL TRADE ID {trade.tradeid}")
                self.open_orders[trade.tradeid]['action'] = 'sell'
                # assign the actual executed entry price to the backtrader buy object when a selllimit becomes a sell
                self.open_orders[trade.tradeid]['price'] = trade.price
                self.sell_trade[trade.tradeid] = self.sell_limit[trade.tradeid] # move selllimit objects to sell objects
                del self.sell_limit[trade.tradeid] # removing the selllimit objects as it became a sell trade
            
            elif self.open_orders[trade.tradeid]['action'] == 'sellstop':
                log.debug(f"{self.datetime.datetime(ago=0).isoformat()} --- SELLSTOP {self.sell_stop[trade.tradeid][0].ref} -> SELL TRADE ID {trade.tradeid}")
                self.open_orders[trade.tradeid]['action'] = 'sell'
                # assign the actual executed entry price to the backtrader buy object when a sellstop becomes a sell
                self.open_orders[trade.tradeid]['price'] = trade.price
                self.sell_trade[trade.tradeid] = self.sell_stop[trade.tradeid] # move sellstop objects to sell objects
                del self.sell_stop[trade.tradeid] # removing the sellstop objects as it became a sell trade
            else:
                pass
        except KeyError:
            pass
        else:
            pass
        log.debug(f"{self.datetime.date(ago=0).isoformat()} {self.datetime.time(ago=0).isoformat()} --- TRADE ID {trade.tradeid} with ENTRY PRICE {trade.price} CLOSED, final PNL {trade.pnl} & NET PROFIT {trade.pnlcomm}")
    

I have run multiple tests and have concluded that Test 1 and Test 2 define the problem well.

In Test 1, I prove that sell orders are correctly executed by Backtrader. In Test 2, I prove that adding a single buy trade to the situation causes sell orders to not be executed correctly by Backtrader

Upvotes: 1

Views: 39

Answers (0)

Related Questions