Cancelling Bracket order following closing of the main side
-
For while I've been trying to solve following problem: When I place bracket order with stop and limit, and if later the trade is closed not by stop or limit orders, but by the strategy logic, they (the children orders - Stop and Limit) remain active and one of them gets executed later. To avoid this I need to cancel it by using the order reference and call self.cancel(sop_order) or similar.
My logic suggests that to call it I need to use something like if trade.isclosed: or maybe if self.position.size == 0: and then call the order by its id and this is where I get super confused.
I wrote about this previously but couldn't find a solution since.
Any ideas how to implement this part the code?
Thanks,
Rumen -
The fundamental problem is that a position open by a bracket should not have additional logic to close the position independently.
But if you insist in shooting yourself in the foot, the only thing you have to do is to keep a reference to any of the bracketing orders (stop-loss side or take-profit side) and cancel it whenever you close the position with the bracket-foreign logic.
if logic_orders_to_close_the_position: self.cancel(take_profit_order)
-
Hi @Rumen-Nenov...did you get this figured out? I am at a similar junction you were when you wrote this. Thanks!
-
@Dallascat Do you just have the one bracket order or multiple different orders?
-
@run-out I am still fairly new to BT and python. Basically there are 3 signals generated - long, short or out. I have just learned on how to put in brackets if I want to use a PT and SL. But there are times when none of those gets hit and a signal to get out of the position hits (goes flat) and that leaves the PT and SL open if I use the brackets. So I assume I need to do it another way (issue PT and SL separately, and have them cancelled when the position == 0) instead of the brackets, unless there was a way to cancel the brackets if I went flat. Make sense?
-
@Dallascat said in Cancelling Bracket order following closing of the main side:
@run-out I am still fairly new to BT and python. Basically there are 3 signals generated - long, short or out. I have just learned on how to put in brackets if I want to use a PT and SL. But there are times when none of those gets hit and a signal to get out of the position hits (goes flat) and that leaves the PT and SL open if I use the brackets. So I assume I need to do it another way (issue PT and SL separately, and have them cancelled when the position == 0) instead of the brackets, unless there was a way to cancel the brackets if I went flat. Make sense?
Thank you for that explanation. Could you please include your code? Thanks.
-
@run-out Sure, this is just a simple strategy for my learning purposes of BT and python:
`from future import (absolute_import, division, print_function,
unicode_literals)
from datetime import datetime
import backtrader as btCreate a Strategy
class TestStrategy(bt.Strategy):
params = (('order_pct', 0.95), ("buy_price_adjust", 0.0), ("buy_limit_adjust", 0.02), ("buy_stop_adjust", 0.02))def log(self, txt, dt=None): """ Logging function for this strategy""" dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): # Keep a reference to the "close" line in the data[0] dataseries self.bar_executed = len(self) self.dataclose = self.datas[0].close # Create an order list self.o_li = list() # To keep track of pending orders and buy price/commission self.order = None self.buyprice = None self.buycomm = None # Add EMA and TRIX self.ema = bt.indicators.ExponentialMovingAverage((self.datas[0].high + self.datas[0].low) / 2, period=50, plotname="EMAMid") self.ema2 = bt.indicators.ExponentialMovingAverage((self.datas[0].high + self.datas[0].low) / 2, period=15, plotname="EMAMid") self.atr = bt.indicators.ATR(period=50, movav=bt.indicators.MovAv.Exponential) # Indicators for plotting bt.indicators.ExponentialMovingAverage(self.datas[0], period=50) def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # Buy/Sell order submitted/accepted to/by broker - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enough cash if order.status in [order.Completed, order.Canceled, order.Margin]: if order.isbuy(): self.log( 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, Size %.2f' % (order.executed.price, order.executed.value, order.executed.comm, order.executed.size)) self.buyprice = order.executed.price self.buycomm = order.executed.comm else: # Sell self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, Size %.2f' % (order.executed.price, order.executed.value, order.executed.comm, order.executed.size)) self.bar_executed = len(self) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected') # Clean up the order list. if not order.alive() and order in self.o_li: self.o_li.remove(order) # Write down: no pending order self.order = None def notify_trade(self, trade): if not trade.isclosed: return self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) def next(self): # Check if an order is pending ... if yes, we cannot send a 2nd one if self.order: return # Check if we are in the market if not self.position: if self.ema[0] >= self.ema[-1] and (self.ema2[0] >= self.ema2[-1]): price = self.data.close[0] * (1.0 - self.p.buy_price_adjust) price_limit = price + self.atr[0] price_stop = price - self.atr[0] self.long_buy_order = self.buy_bracket( data=self.datas[0], size=None, exectype=bt.Order.Limit, plimit=price, stopprice=price_stop, stopexec=bt.Order.Stop, limitprice=price_limit, limitexec=bt.Order.Limit, ) if self.ema[0] <= self.ema[-1] and (self.ema2[0] <= self.ema2[-1]): price = self.data.close[0] * (1.0 - self.p.buy_price_adjust) price_limit = price - self.atr[0] price_stop = price + self.atr[0] self.short_sell_order = self.sell_bracket( data=self.datas[0], size=None, exectype=bt.Order.Limit, plimit=price, stopprice=price_stop, stopexec=bt.Order.Stop, limitprice=price_limit, limitexec=bt.Order.Limit, ) if self.position.size > 0 and (self.ema[0] <= self.ema[-1] or (self.ema2[0] <= self.ema2[-1])): self.close() if self.position.size < 0 and (self.ema[0] >= self.ema[-1] or (self.ema2[0] >= self.ema2[-1])): self.close()
if name == 'main':
# Variable for our starting cash
startcash = 100000# Create a cerebro entity cerebro = bt.Cerebro() # Add a strategy cerebro.addstrategy(TestStrategy) # Create a Data Feed data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate=datetime(2019, 6, 1), todate=datetime(2020, 11, 9), buffered=True) # Add the Data Feed to Cerebro cerebro.adddata(data) # Set our desired cash start cerebro.broker.setcash(startcash) # Add a FixedSize sizer according to the stake cerebro.addsizer(bt.sizers.FixedSize, stake=50) # Set the commission cerebro.broker.setcommission(commission=0.0) # Add Analyzers: cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="mySharpe", riskfreerate=0.001) cerebro.addanalyzer(bt.analyzers.Returns, _name='returns') cerebro.addanalyzer(bt.analyzers.SQN, _name='sqn') # Print out the starting conditions print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) # Run over everything cerebro.run() portvalue = cerebro.broker.getvalue() pnl = portvalue - startcash # Print out the final result print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) print('Final Portfolio Value: ${}'.format(portvalue)) print('P/L: ${}'.format(pnl)) # Plot the results cerebro.plot(style='bar', bardown='grey', barup="0.75", volume=False, grid=False)`
-
Sorry, I don't know why it didn't markdown the whole code.
-
@Dallascat said in Cancelling Bracket order following closing of the main side:
self.long_buy_order
Yes you have two possible exits from your position. The bracket orders and your custom code:
if self.position.size > 0 and (self.ema[0] <= self.ema[-1] or (self.ema2[0] <= self.ema2[-1])): self.close() if self.position.size < 0 and (self.ema[0] >= self.ema[-1] or (self.ema2[0] >= self.ema2[-1])): self.close()
I would recommend you change your order lists from
self.long_buy_order
andself.short_sell_order
toself.order
which you have already established ininit
. You may wish to initialize as a list.self.order = []
By making these buy/sell lists into one list, you can now cancel out any orders in this list if you close the position in your custom code at the bottom
for o in self.order: self.cancel(o)
So it would look like:
if self.position.size > 0 and (self.ema[0] <= self.ema[-1] or (self.ema2[0] <= self.ema2[-1])): self.close() for o in self.order: self.cancel(o) if self.position.size < 0 and (self.ema[0] >= self.ema[-1] or (self.ema2[0] >= self.ema2[-1])): self.close() for o in self.order: self.cancel(o)
-
@run-out Awesome thanks! I will make those changes this weekend and get back!
-
@run-out I tried to implement your suggestions, I and I know I am doing something wrong with the list. I get an error :
"line 110, in next for o in self.order:
TypeError: 'NoneType' object is not iterable"Code:
from __future__ import (absolute_import, division, print_function, unicode_literals) from datetime import datetime import backtrader as bt # Create a Strategy class TestStrategy(bt.Strategy): def log(self, txt, dt=None): """ Logging function for this strategy""" dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): # Keep a reference to the "close" line in the data[0] dataseries self.bar_executed = len(self) self.dataclose = self.datas[0].close # Create an order list self.o_li = list() # To keep track of pending orders and buy price/commission self.order = [] self.buyprice = None self.buycomm = None # Add EMAs self.ema = bt.indicators.ExponentialMovingAverage((self.datas[0].high + self.datas[0].low) / 2, period=50) self.ema2 = bt.indicators.ExponentialMovingAverage((self.datas[0].high + self.datas[0].low) / 2, period=15) self.atr = bt.indicators.ATR(period=50, movav=bt.indicators.MovAv.Exponential) def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # Buy/Sell order submitted/accepted to/by broker - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enough cash if order.status in [order.Completed, order.Canceled, order.Margin]: if order.isbuy(): self.log( 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, Size %.2f' % (order.executed.price, order.executed.value, order.executed.comm, order.executed.size)) self.buyprice = order.executed.price self.buycomm = order.executed.comm else: # Sell self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, Size %.2f' % (order.executed.price, order.executed.value, order.executed.comm, order.executed.size)) self.bar_executed = len(self) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected') # Clean up the order list. if not order.alive() and order in self.o_li: self.o_li.remove(order) # Write down: no pending order self.order = None def notify_trade(self, trade): if not trade.isclosed: return self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) def next(self): # Check if an order is pending ... if yes, we cannot send a 2nd one if self.order: return # Check if we are in the market if not self.position: if self.ema[0] >= self.ema[-1] and (self.ema2[0] >= self.ema2[-1]): price = self.data.close[0] price_limit = price + self.atr[0] price_stop = price - self.atr[0] self.order = self.buy_bracket( data=self.datas[0], size=None, exectype=bt.Order.Limit, plimit=price, stopprice=price_stop, stopexec=bt.Order.Stop, limitprice=price_limit, limitexec=bt.Order.Limit, ) if self.ema[0] <= self.ema[-1] and (self.ema2[0] <= self.ema2[-1]): price = self.data.close[0] price_limit = price - self.atr[0] price_stop = price + self.atr[0] self.order = self.sell_bracket( data=self.datas[0], size=None, exectype=bt.Order.Limit, plimit=price, stopprice=price_stop, stopexec=bt.Order.Stop, limitprice=price_limit, limitexec=bt.Order.Limit, ) if self.position.size > 0 and (self.ema[0] <= self.ema[-1] or (self.ema2[0] <= self.ema2[-1])): self.close() for o in self.order: self.cancel(o) if self.position.size < 0 and (self.ema[0] >= self.ema[-1] or (self.ema2[0] >= self.ema2[-1])): self.close() for o in self.order: self.cancel(o) if __name__ == '__main__': # Variable for our starting cash startcash = 100000 # Create a cerebro entity cerebro = bt.Cerebro() # Add a strategy cerebro.addstrategy(TestStrategy) # Create a Data Feed data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate=datetime(2019, 6, 1), todate=datetime(2020, 11, 9), buffered=True) # Add the Data Feed to Cerebro cerebro.adddata(data) # Set our desired cash start cerebro.broker.setcash(startcash) # Add a FixedSize sizer according to the stake cerebro.addsizer(bt.sizers.FixedSize, stake=50) # Set the commission cerebro.broker.setcommission(commission=0.0) # Add Analyzers: cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="mySharpe", riskfreerate=0.001) cerebro.addanalyzer(bt.analyzers.Returns, _name='returns') cerebro.addanalyzer(bt.analyzers.SQN, _name='sqn') # Print out the starting conditions print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) # Run over everything cerebro.run() portvalue = cerebro.broker.getvalue() pnl = portvalue - startcash # Print out the final result print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) print('Final Portfolio Value: ${}'.format(portvalue)) print('P/L: ${}'.format(pnl)) # Plot the results cerebro.plot(style='bar', bardown='grey', barup="0.75", volume=False, grid=False)
-
@Dallascat said in Cancelling Bracket order following closing of the main side:
for o in self.order:
self.cancel(o)Just debug in here. The list
self.order
containsNone
and this is throwing an error. -
@run-out Thanks so much for your guidance! I'm still racking my brain against the well....is the error in
# To keep track of pending orders and buy price/commission self.order = [] self.buyprice = None self.buycomm = None
or
# Clean up the order list. if not order.alive() and order in self.o_li: self.o_li.remove(order) # Write down: no pending order self.order = None
or none of the above . LOL! Thanks again!!
-
@Dallascat This is getting too fragmented for me. Could you put the entire code in one block for me? I will then copy it to my ide and look at it.
Also, right after that, put in the latest error code you are getting.
Thanks.
-
@run-out Sure, I did get rid of the error, but not sure if I am doing the list right or not (I assume NOT, lol) because it runs, but only triggers two trades at the beginning then nothing. Thanks Again
import backtrader as bt from datetime import datetime class Strategy(bt.Strategy): def log(self, txt, dt=None): """ Logging function fot this strategy""" dt = dt or self.data.datetime[0] if isinstance(dt, float): dt = bt.num2date(dt) print("%s, %s" % (dt.date(), txt)) def __init__(self): # Keep a reference to the "close" line in the data[0] dataseries self.bar_executed = len(self) self.dataclose = self.datas[0].close # Create an order list self.o_li = list() # To keep track of pending orders and buy price/commission self.order = [] self.buyprice = None self.buycomm = None # Add EMA and TRIX self.ema = bt.indicators.ExponentialMovingAverage((self.datas[0].high + self.datas[0].low) / 2, period=100, plotname="EMAMid") self.trix = bt.indicators.TRIX(self.datas[0], period=5, plot=True) self.atr = bt.indicators.ATR(period=100, movav=bt.indicators.MovAv.Exponential) def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # Buy/Sell order submitted/accepted to/by broker - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enough cash if order.status in [order.Canceled, order.Margin]: if order.isbuy(): self.log("BUY FAILED, Cancelled or Margin") self.log if order.status in [order.Completed, order.Canceled, order.Margin]: if order.isbuy(): self.log( "BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f" % (order.executed.price, order.executed.value, order.executed.comm) ) self.buyprice = order.executed.price self.buycomm = order.executed.comm else: # Sell self.log( "SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f" % (order.executed.price, order.executed.value, order.executed.comm) ) self.bar_executed = len(self) # Cleans up the order list. if not order.alive() and order in self.o_li: self.o_li.remove(order) def notify_trade(self, trade): if not trade.isclosed: return self.log("OPERATION PROFIT, GROSS %.2f, NET %.2f" % (trade.pnl, trade.pnlcomm)) def next(self): # Simply log the closing price of the series from the reference global price, price_limit, price_stop self.log('ATR: %.2f' % (self.atr[0])) if self.position.size == 0 and len(self.o_li) == 0: if self.ema[0] >= self.ema[-1] and (self.trix[0] >= self.trix[-1]): price = self.data.close[0] price_limit = price + self.atr[0] price_stop = price - self.atr[0] self.order = self.buy_bracket( data=self.datas[0], size=None, exectype=bt.Order.Limit, plimit=price, stopprice=price_stop, stopexec=bt.Order.Stop, limitprice=price_limit, limitexec=bt.Order.Limit, ) if self.position.size == 0 and len(self.o_li) == 0: if self.ema[0] <= self.ema[-1] and (self.trix[0] <= self.trix[-1]): price = self.data.close[0] price_limit = price - self.atr[0] price_stop = price + self.atr[0] self.order = self.sell_bracket( data=self.datas[0], size=None, exectype=bt.Order.Limit, plimit=price, stopprice=price_stop, stopexec=bt.Order.Stop, limitprice=price_limit, limitexec=bt.Order.Limit, ) # Store orders in a list self.o_li = [o for o in self.order] self.log( "LONG BUY limit Targets: Buy {:8.2f}, Target {:8.2f}, Stop {:8.2f}".format( price, price_limit, price_stop ) ) if self.position.size > 0: if self.ema[0] <= self.ema[-1] or (self.trix[0] <= self.trix[-1]): print("Sell shares at {}".format(self.data.close[0])) self.close() self.o_li = list() if self.position.size < 0: if self.ema[0] >= self.ema[-1] or (self.trix[0] >= self.trix[-1]): print("Sell shares at {}".format(self.data.close[0])) self.close() self.o_li = list() if __name__ == '__main__': # Variable for our starting cash startcash = 100000 # Create a cerebro entity cerebro = bt.Cerebro() # Add a strategy cerebro.addstrategy(Strategy) # Create a Data Feed data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate=datetime(2019, 6, 1), todate=datetime(2020, 11, 9), buffered=True) # Add the Data Feed to Cerebro cerebro.adddata(data) # Set our desired cash start cerebro.broker.setcash(startcash) # Add a FixedSize sizer according to the stake cerebro.addsizer(bt.sizers.FixedSize, stake=50) # Set the commission cerebro.broker.setcommission(commission=0.0) # Add Analyzers: cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="mySharpe", riskfreerate=0.001) cerebro.addanalyzer(bt.analyzers.Returns, _name='returns') cerebro.addanalyzer(bt.analyzers.SQN, _name='sqn') # Print out the starting conditions print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) # Run over everything cerebro.run() portvalue = cerebro.broker.getvalue() pnl = portvalue - startcash # Print out the final result print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) print('Final Portfolio Value: ${}'.format(portvalue)) print('P/L: ${}'.format(pnl)) # Plot the results cerebro.plot(style='bar', bardown='grey', barup="0.75", volume=False, grid=False)
-
@run-out If not too busy, have you had a chance to look at my code? TY!
-
@run-out said in Cancelling Bracket order following closing of the main side:
Normally when using a list or dictionary to track orders, if you remove the orders from the list at the right spots, there should be no issues with the list.
However, I tend to get a bit cautious (lazy?) and insert a clause at the beginning of next() that checks if:
-
Are there any open positions, and...
-
Are there any live orders.
If there are no open positions and no live orders, really the list should be empty. The code looks something like:
def next(self): if self.position.size == 0 and len([o for o in self.o_li if o.status < 4]) == 0: self.o_li = list()
Order status of 4 is completed, and higher are all dead orders.
Then another thing I notice in your code is that you have bracket orders with purchase price which is fine. But if the stock trends permanantly away from your purchase price, you will be left with a set of orders that will never fill, blocking future intended orders, since there are open orders.
Since in your bracket orders you entering the market at last close, probably best to leave exectype as Market and no plimit, or alternatively insert a valid period so the bracket expires after so many bars.
-