Reputation: 11
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