Simultaneous bracket order - Cancel one when another has been submitted & accepted



  • I'm trying to enter both a long and a short bracket order with target price, take profit, and stop loss... simultaneously

    For long: Limit order +1 pip from current price. Stop loss -1 pip from current price. And take profit of +5 pip of from current price.

    For short, exact reverse.

    Here's the picture
    for illustration

    When one of the two bracket orders get filled (at target price), i'd like to cancel the other one.

    Here's the logic for entering the 2 simultaneous orders:

    def next(self):
    
    
    	current_tick = {}
    	current_tick["close"]   = self.datas[0].close[0]
    	current_tick["volume"]  = self.datas[0].volume[0]
    	current_tick["date"]    = self.datas[0].datetime.date()
    	current_tick["time"]    = self.datas[0].datetime.time()
    
    	if(self.entrysig):
    
    		order_size =get_size(
    			currency_to_trade=self.p.currency,
    			leverage=self.p.leverage,
    			max_loss_per_trade=0.01,
    			total_cash=self.broker.get_cash(),
    			at_datetime =self.datas[0].datetime.datetime(0),
    			default_value = self.datas[0].close[0]
    		)
    
    		long_order = {
    		    "direction"       	: "long",
    		    "order_placed_at" 	: len(self),
    		    "order_placed_at_datetime" : current_tick["date"],
    		    "target_price"    	: self.datas[0].close + self.distance_entry,
    		    "take_profit"     	: self.datas[0].close + self.distance_tp,
    		    "stop_loss"       	: self.datas[0].close - self.distance_sl
    		    #"valid"			  	: terminate_order
    		}
    
    		long_bracket = {
    			"limitprice": float('%.4f' % long_order["take_profit"]),
    			"price"     : float('%.4f' % long_order["target_price"]), 
    			"stopprice" : float('%.4f' % long_order["stop_loss"]),
    			"addinfo"   : "long",
    			"size"      : order_size,
    			#"exectype"	: bt.Order.Market
    			#"valid"     : terminate_order
    		}
    
    		self.order_pairs["long"] = self.buy_bracket(**long_bracket)
    
    
    
    		short_order = {
    		    "direction"       	: "short",
    		    "order_placed_at" 	: len(self),
    		    "order_placed_at_datetime" : current_tick["date"],
    		    "target_price"    	: self.datas[0].close - self.distance_entry,
    		    "take_profit"     	: self.datas[0].close - self.distance_tp,
    		    "stop_loss"       	: self.datas[0].close + self.distance_sl
    		    #"valid"			  	: terminate_order
    		}
    
    		short_bracket = {
    			"limitprice": float('%.4f' % short_order["take_profit"]),
    			"price"     : float('%.4f' % short_order["target_price"]),
    			"stopprice" : float('%.4f' % short_order["stop_loss"]),
    			"addinfo"   : "short",
    			"size"      : order_size,
    			#"exectype"	: bt.Order.Market
    			#"valid"     : terminate_order
    		}
    
    		self.order_pairs["short"] = self.sell_bracket(**short_bracket)
    

    Here's the logic for cancelling the other

    def notify_order(self, order):
    
    
    	logging.info('[NOTIFY_ORDER {}] ORDER {} - {}'.format(len(self), order.Status[order.status], order.tradeid))
    
    	if order.status in [order.Submitted]:
    		# Buy/Sell order submitted/accepted to/by broker - Nothing to do
    		return
    
    
    	if order.status in [order.Accepted]:
    		# Buy/Sell order submitted/accepted to/by broker - Nothing to do			
    		self.total_played+=1
    		if(order.isbuy()):
    			self.went_long+=1
    		if(order.issell()):
    			self.went_short+=1
    
    		order_addinfo = dict(order.info)
    
    		if(order_addinfo["addinfo"] == "long"):
    			
    			if(self.order_pairs["short"] is not None):
    				for order in self.order_pairs["short"]:
    					self.cancel(order)
    				self.order_pairs["short"] = None
    
    		if(order_addinfo["addinfo"] == "short"):
    
    			if(self.order_pairs["long"] is not None):
    				for order in self.order_pairs["long"]:
    					self.cancel(order)
    				self.order_pairs["long"] = None
    
    
    		return
    
    	if order.status in [order.Expired, order.Cancelled, order.Rejected]:
    		
    		pass
    
    	if order.status in [order.Completed]:
    
    		order_type  = "buy" if( order.isbuy() ) else "sell"
    
    		logging.info('[NOTIFY_ORDER {}] {} ORDER EXECUTED [{}], Size: {}, Price: {}, Cost: {}, Comm {}'.format(
    			len(self),
    			order_type.upper(), 
    			order.ref,
    			order.executed.size,
    			order.executed.price,
    			order.executed.value,
    			order.executed.comm))
    

    For some reason, all the orders seem to be getting in but also getting out at exact same bar

    alt text

    Where am I going wrong?


  • administrators

    Your distance_entry is unknown. But it would seem if is possibly small enough so that both orders get executed within the range of the same bar.

    If you activate quicknotify in Cerebro you would get a chance to act in between orders, be it the case.



  • Oh oops, sorry about that

    distance_sl = 0.0003
    distance_entry=0.0001
    distance_tp=0.0006
    

    I tried widening the values just to see if the volatility was triggering both... even up to:

    distance_sl = 0.00010
    distance_entry=0.0005
    distance_tp=0.0020
    

    But it seems that all the orders are getting bought/sold at exact same time

    [INFO] 14:21:32 [NOTIFY_ORDE] BUY ORDER EXECUTED [151], Size: 8814.339166956806, Price: 1.142125, Cost: 10067.077121060544, Comm 0.0
    [INFO] 14:21:32 [NOTIFY_ORDE] ORDER Completed - 0
    [INFO] 14:21:32 [NOTIFY_ORDER] SELL ORDER EXECUTED [154], Size: -8814.339166956806, Price: 1.142125, Cost: 10067.077121060544, Comm 0.0
    [INFO] 14:21:32 [NOTIFY_TRADE] OPERATION PROFIT, GROSS 0.00, NET 0.00
    [INFO] 14:21:32 [NOTIFY_TRADE] OPERATION PROFIT, GROSS 0.00, NET 0.00


  • Why cancel? you can totally profit from both of those brackets if something fluctuates up and down intraday =)


  • administrators

    @Taewoo-Kim said in Simultaneous bracket order - Cancel one when another has been submitted & accepted:

    logging.info('[NOTIFY_ORDER {}] ORDER {} - {}'.format(len(self), order.Status[order.status], order.tradeid))

    It would seem that you are executing with Market and both orders take the closing price the. Since the code posted is not the code executing, it may be that your execution run did so.

    See for example. Code

    logging.info('[NOTIFY_ORDER {}] ORDER {} - {}'.format(len(self), order.Status[order.status], order.tradeid))
    

    Output

    [INFO] 14:21:32 [NOTIFY_ORDE] ORDER Completed - 0
    

    The printout doesn't match the logging.info statement. It seems the code may have correct to account for the typo and not printing the len. With that in mind the following could have also not been commented during execution:

    #"exectype"	: bt.Order.Market
    

    In any case the cancelling is donde at the wrong place. It's been done here

    if order.status in [order.Accepted]:
    

    Which only means the broker has accepted the order because there is enough cash to execute it.

    Additionally: in the case of a bracket order, there is no need to cancel the 3 orders. Cancelling just the main order will already cancel the bracket.



  • In any case the cancelling is donde at the wrong place. It's been done here

    if order.status in [order.Accepted]:
    

    Which only means the broker has accepted the order because there is enough cash to execute it.

    So where does this need to go? Which status?


  • administrators

    You said you want to cancel if the order has been filled. Accepted means that the broker has taken it (as opposed for example to Margin when you don't have enough cash)

    During backtesting and unless you are playing with volume filling strategies the notification of an order being filled will happen with Order.Completed



  • im having trouble having BT cancel one of the bracket orders when the other bracket order is converted into a position... here's what i've tried

    1. putting the order.cancel in order. accepted / submitted
    2. set exectype as bt.Order.StopLimit / bt.Order.Limit

    Still, all the orders are cancelling each other out simultaneously.

    Questions:

    1. When the "main" order of a bracket order is converted into a position, how is BT notified? via notify_order or notify_trade?
    2. Is there a sample code that achieves what I'm trying to do? I am trying to
    • place simultaeous long and short bracket order, both of which are X pips away
    • close one set of bracket orders when the other set of bracket orders (i.e. the main order of that other bracket order set) has been filled

  • administrators

    @Taewoo-Kim said in Simultaneous bracket order - Cancel one when another has been submitted & accepted:

    1. putting the order.cancel in order. accepted / submitted

    As stated in the answer above. The right place is Completed. Using any of the ones mentioned above guarantees that you will be cancelling the other bracket immediately, yes or yes (unless there isn't cash enough and you receive a Margin)

    @Taewoo-Kim said in Simultaneous bracket order - Cancel one when another has been submitted & accepted:

    Still, all the orders are cancelling each other out simultaneously.

    Probably due to the fact that you are cancelling them immediately upon receiving Accepted or Submitted as stated above.

    @Taewoo-Kim said in Simultaneous bracket order - Cancel one when another has been submitted & accepted:

    1. When the "main" order of a bracket order is converted into a position, how is BT notified? via notify_order or notify_trade?

    A position is your current status in an asset. An order is not converted to a position. The results of the execution are applied to the current position.

    Orders are always notified via notify_order, hence the name. Docs - Strategy / notify_order

    @Taewoo-Kim said in Simultaneous bracket order - Cancel one when another has been submitted & accepted:

    1. Is there a sample code that achieves what I'm trying to do? I am trying to

    Not in the samples.


Log in to reply
 

Looks like your connection to Backtrader Community was lost, please wait while we try to reconnect.