Trade never closed with position = 0
-
I am having difficulty understanding why this error is occurring. Even though the position size goes to 0 with the following logic, seen in
print(self.position.size)
and the fact that
if not self.position:
is triggered, why is notify_trade() not called?
Does there have to be a rest period, i.e more than 1 tick whilst position = 0 for it to trigger? The order output also states that the initial buy or sell order from within if not self.position: is accepted twice with the second one being cancelled once the first is completed. If I remove the stop_loss, take_profit logic then this problem does not occur.
Any help on where i've gone wrong would be appreciated.
Code:
from __future__ import (absolute_import, division, print_function, unicode_literals) import backtrader as bt import backtrader.feeds as btfeeds import backtrader.indicators as btind __all__ = ['dema'] class dema(bt.Strategy): params = dict( # Template parameters: stop_loss = 0, stop_trail = 0, take_profit = 0, sizer_fixed = 1, sizer_perc = 0.01, # Strategy dependant parameters period = 30, ) def __init__(self): # Add indicators here: self.dema = btind.DEMA(self.data1, period=self.p.period) self.cross = btind.CrossOver(self.data1.close, self.dema.dema) self.o = [] def start(self): self.lendata1_enter = 0 def next(self): # Sizing logic pos_size = self.sizer() if not self.position: # =============== Enter order logic whilst out of a position =============== # if self.cross == 1: self.lendata1_enter = len(self.data1) buy = self.buy(size=pos_size) buy.addinfo(Trigger="CrossUp Out of Position.") # Enter predifined exit points (This logic requires only one of stop_trail or take_profit can be used) # Submit stop loss order # Calculate price to set stop treating stop_loss as a percentage stop_loss_price = self.data.close[0] * (1 - self.p.stop_loss) stop_loss = self.sell(size=buy.size, price=stop_loss_price, exectype=bt.Order.Stop,\ parent=buy, transmit=False) stop_loss.addinfo(Trigger="Stop loss.") self.o.append(stop_loss) # Submit take profit order take_profit_price = self.data.close[0] * (1 + self.p.take_profit) take_profit = self.sell(size=buy.size, price=take_profit_price, exectype=bt.Order.Limit,\ parent=buy, transmit=True) take_profit.addinfo(Trigger="Take profit.") self.o.append(take_profit) if self.cross == -1: self.lendata1_enter = len(self.data1) sell = self.sell(size=pos_size) sell.addinfo(Trigger="CrossDown Out of Position.") # Enter predifined exit points (This logic requires only one of stop_trail or take_profit can be used) # Submit stop loss order # Calculate price to set stop treating stop_loss as a percentage stop_loss_price = self.data.close[0] * (1 - self.p.stop_loss) stop_loss = self.buy(size=sell.size, price=stop_loss_price, exectype=bt.Order.Stop,\ parent=sell, transmit=False) stop_loss.addinfo(Trigger="Stop loss.") self.o.append(stop_loss) # Submit take profit order take_profit_price = self.data.close[0] * (1 + self.p.take_profit) take_profit = self.buy(size=sell.size, price=take_profit_price, exectype=bt.Order.Limit,\ parent=sell, transmit=True) take_profit.addinfo(Trigger="Take profit.") self.o.append(take_profit) else: if len(self.data1) > self.lendata1_enter: # =============== Enter order logic whilst in a position =============== # cur_size = self.position.size if self.cross == 1: # Close out current order, then reverse direction buy = self.buy(size=cur_size) buy.addinfo(Trigger="CrossUp In a Position.") if self.cross == -1: # Close out current order, then reverse direction sell = self.sell(size=cur_size) sell.addinfo(Trigger="CrossDown In a Position.") def notify_trade(self, trade): date = self.data.datetime.datetime() if trade.isclosed: print('='*32 + ' NOTIFY TRADE ' + '='*32) print('{}, Close Price: {}, Profit, Gross {}, Net {}'.format( date, trade.price, round(trade.pnl,2), round(trade.pnlcomm,2))) print('='*80) # Cancel and clear all remaining orders if a trade is closed. if trade.isclosed: for order in self.o: self.cancel(order) self.o.clear() def notify_order(self, order): date = self.data.datetime.datetime() if order.status == order.Accepted: print('-'*32 + ' NOTIFY ORDER ' + '-'*32) print('Order Accepted\n') print('{}, Status {}: Ref: {}, Size: {}, Price: {}, Info: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5), order.info )) if order.status == order.Completed: print('-'*32 + ' NOTIFY ORDER ' + '-'*32) print('Order Completed') print('{}, Status {}: Ref: {}, Size: {}, Price: {}, Info: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5), order.info )) print('Created: {} Price: {} Size: {}'.format(bt.num2date(order.created.dt), order.created.price,order.created.size)) print('-'*80) print(self.position.size) print('-'*80) if order.status == order.Canceled: print('-'*32 + ' NOTIFY ORDER ' + '-'*32) print('Order Canceled\n') print('{}, Status {}: Ref: {}, Size: {}, Price: {}, Info: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5), order.info )) if order.status == order.Rejected: print('-'*32 + ' NOTIFY ORDER ' + '-'*32) print('WARNING! Order Rejected') print('{}, Status {}: Ref: {}, Size: {}, Price: {}, Info: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5), order.info )) print('-'*80) def sizer(self): if self.p.sizer_fixed == 1: pos_price = self.broker.startingcash * self.p.sizer_perc pos_size = pos_price / self.data0.close[0] else: pos_price = self.broker.get_cash() * self.p.sizer_perc pos_size = pos_price / self.data0.close[0] return pos_size if __name__ == '__main__': pass
Output sample:
-------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2016-01-03 23:01:00, Status 2: Ref: 1, Size: 0.46464083263637207, Price: NA, Info: AutoOrderedDict([('Trigger', 'CrossUp Out of Position.')]) -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2016-01-03 23:01:00, Status 2: Ref: 1, Size: 0.46464083263637207, Price: NA, Info: AutoOrderedDict([('Trigger', 'CrossUp Out of Position.')]) -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2016-01-03 23:01:00, Status 2: Ref: 2, Size: -0.46464083263637207, Price: 426.1356, Info: AutoOrderedDict([('Trigger', 'Stop loss.')]) -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2016-01-03 23:01:00, Status 2: Ref: 3, Size: -0.46464083263637207, Price: 434.7444, Info: AutoOrderedDict([('Trigger', 'Take profit.')]) -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2016-01-03 23:01:00, Status 4: Ref: 1, Size: 0.46464083263637207, Price: NA, Info: AutoOrderedDict([('Trigger', 'CrossUp Out of Position.')]) Created: 2016-01-03 23:00:00 Price: 430.44 Size: 0.46464083263637207 -------------------------------------------------------------------------------- 0.46464083263637207 -------------------------------------------------------------------------------- -------------------------------- NOTIFY ORDER -------------------------------- Order Canceled 2016-01-03 23:01:00, Status 5: Ref: 1, Size: 0.46464083263637207, Price: NA, Info: AutoOrderedDict([('Trigger', 'CrossUp Out of Position.')]) -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2016-01-04 14:01:00, Status 2: Ref: 4, Size: -0.46464083263637207, Price: NA, Info: AutoOrderedDict([('Trigger', 'CrossDown In a Position.')]) -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2016-01-04 14:01:00, Status 4: Ref: 4, Size: -0.46464083263637207, Price: NA, Info: AutoOrderedDict([('Trigger', 'CrossDown In a Position.')]) Created: 2016-01-04 14:00:00 Price: 432.29 Size: -0.46464083263637207 -------------------------------------------------------------------------------- 0.0 --------------------------------------------------------------------------------
Sample of plot:
-
notify_trade
(with a closed trade) would in any case be called after this has been printed out:@Benjamin-Kesby said in Trade never closed with position = 0:
0.0
Because your log ends there ... it is impossible to actually judge if
notify_trade
is called or not.Then ... you fail to take into account that the cross runs on the higher timeframe and you operate on the lower timeframe. The lower timeframe will tick SEVERAL times, whilst the higher timeframe remains at the same point. On all these ticks the cross will evaluate to be true.
That's why you have order duplication and probably the source of your problems. You can only check the cross when it actually happens and have to ignore all other ticks which only belong to the lower timeframe.
See: https://www.backtrader.com/docu/concepts/ and read about the
len