In this post, we are going to take a look at bracket orders. Bracket orders are a special type of order which have not been covered on the blog before. These type of order can greatly simplify entering positions when a stop loss and take profit are desired.
In previous versions of Backtrader, we had to simulate the bracket order with multiple separate orders. However, since version
If price never made a new low, we would be safe as no orders would be triggered. However, in our example, we can see that our stop order is triggered and we eventually are stopped out.
Bracket Orders
The bracket order allows Backtrader to emulate a broker order where we specify a stop loss and take profit at the same time we enter. This is quite a common way to enter a position with most brokers and is quite special because:- We send 3 orders at the same time. 1 order to enter a position and 2 orders to exit a position
- The exit orders are accepted by the broker but are not active until the entry order is filled. This stops your exit orders from triggering a trade in the wrong direction!
- Once we are in a position and one of the exit orders is hit, the other is automatically canceled.

1.9.37.116
we have a specific bracket
order which will manage all of this for us.
The official docs do a great job of describing how a bracket order works:
Source: https://www.backtrader.com/docu/order-creation-execution/bracket/bracket.html?#bracket-orders Note: Relating this to our image above, the main side order would be our market entry order. The low side, would be our stop loss and the high side would be our take profit. If you are new to programming, you may not understand how parents and children relate to our order. It is just a convenient way of indicating hierarchy. You can think of this like folders on your hard drive. You will have a folder named
- The 3 orders are submitted together to avoid having any of them triggered independently
- The low/high side orders are marked as children of the main side
- The children are not active until the main side is executed
- The cancellation of the main side cancels both the low and high side
- The execution of the main side activates both the low and high side
- Upon being active
- The execution or cancellation of any of low/high side orders automatically cancels the other
documents
and in that documents
folder you might have another folder named work
. In this scenario, the work
folder is the child folder of the documents
folder.
Get The Test Data
To run the examples in this post, download the following test data: TestBrackets.csv As mentioned in other tutorials, using this artificial data allows us to know exactly what is happening and when. We are able to control price movements in a predictable pattern to make testing and calculations easier. The test data in this example contains only close data and simply pulsates between $100 and $300.Scope
Once you know the basic syntax, bracket orders are a breeze to use. The official documentation is solid but only covers a simple usage example using a limit order for entry. As such, we will attempt to add some value by covering more types of order and briefly discussing when you might want to use them.Market Entry, Stop Loss and Take Profit
A bracket order which uses a market order to enter a position (i.e the mainside) is probably the most common bracket order that readers will have used on other platforms. It is also the same bracket order as shown in the order ticket image above. We can execute this order with the line:self.buy_bracket(limitprice=long_tp, stopprice=long_stop, exectype=bt.Order.Market)
Generally, we use this type of order when we have a signal to enter and we do not want to wait to enter the position. We place a market order and we are filled against the best order on the order book.

Limit Entry, Stop Loss and Take Profit
If we do not wish to enter immediately, we could decide to use a limit order to try and get a better price. In this case, we enter using the following line:self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Limit)
In our imaginary example below, we are confident that price will bounce a bit before continuing a downward trend. Therefore we place a limit order just above the peak of a recent bounce to try and catch another high before the fall.

Stop Entry, Stop Loss and Take Profit
Sometimes we may want to enter a position at a price which is worse than what we can have right now. We might choose to do this when looking for confirmation that price will continue moving in a certain direction. Another reason might be to try and catch a breakout. To enter with a stop order we just change theexectype
like so:
self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Limit)
In the example below, at current bar we can see price has bounced back up. We have a bearish view but would like to see price move below previous support to confirm that price will continue to move lower. As such, we set a stop entry order just a bit lower than the low before the bounce.

The Code
''' Author: www.backtest-rookies.com MIT License Copyright (c) 2018 backtest-rookies.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import backtrader as bt from datetime import datetime class TestStrategy(bt.Strategy): params = (('percents', 0.9),) # Float: 1 == 100% def __init__(self): print('-'*32,' STRATEGY INIT ','-'*32) self.rsi = bt.indicators.RSI_SMA(self.data.close, period=21) def next(self): date = self.data.datetime.date() close = self.data.close[0] print('{}: Close: ${}, Position Size: {}'.format(date, close, self.position.size)) if not self.position: long_tp = close + 50 long_stop = close - 50 if date == datetime(2018,1,11).date(): # Enter with a market order # Place TP at 50 dollars above buy_ord = self.buy_bracket(limitprice=long_tp, stopprice=long_stop, exectype=bt.Order.Market) elif date == datetime(2018,1,21).date(): # Enter with a limit order to go short to try and catch price # near the top entry = 200 short_tp = entry - 50 short_stop = entry + 50 buy_ord = self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Stop) elif date == datetime(2018,2,11).date(): # Enter with a stop order to go short and catch price on the # way down entry = 290 short_tp = entry - 50 short_stop = entry + 50 buy_ord = self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Limit) elif date == datetime(2018,3,23).date(): # Invalid Order Test # Attempt to set a limit order again # 1. Incorrect Exetype # 2. Stop Value below price (incorrect for short) # 3. Limit Order above price (incorrect for short) entry = 290 short_tp = 300 short_stop = 90 buy_ord = self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Stop) def notify_order(self, order): date = self.data.datetime.datetime().date() if order.status == order.Accepted: print('-'*32,' NOTIFY ORDER ','-'*32) print('Order Accepted') print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) if order.status == order.Completed: print('-'*32,' NOTIFY ORDER ','-'*32) print('Order Completed') print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) print('Created: {} Price: {} Size: {}'.format(bt.num2date(order.created.dt), order.created.price,order.created.size)) print('-'*80) if order.status == order.Canceled: print('-'*32,' NOTIFY ORDER ','-'*32) print('Order Canceled') print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) if order.status == order.Rejected: print('-'*32,' NOTIFY ORDER ','-'*32) print('WARNING! Order Rejected') print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) print('-'*80) 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) startcash = 10000 #Create an instance of cerebro cerebro = bt.Cerebro() #Add our strategy cerebro.addstrategy(TestStrategy) # Create a Data Feed data = bt.feeds.GenericCSVData( timeframe=bt.TimeFrame.Days, compression=1, dataname='data/TestBrackets.csv', dtformat=('%m/%d/%Y'), datetime=0, time=-1, high=1, low=-1, open=1, close=1, volume=-1, openinterest=-1 #-1 means not used ) # Add the data cerebro.adddata(data) # Set our desired cash start cerebro.broker.setcash(startcash) # Run over everything cerebro.run() #Get final portfolio Value portvalue = cerebro.broker.getvalue() pnl = portvalue - startcash #Print out the final result print('Final Portfolio Value: ${}'.format(portvalue)) print('P/L: ${}'.format(pnl))
Code Commentary
The code above will create a bracket order for each of the types described above. Further, since we are testing it will do this on a fixed date. There is no intelligence for setting our target prices and stop loss levels. Instead, we just provide some fixed figures based on what we know the data will do. The intention is to use the bracket orders in their simplest form rather than bloat the code with complex entry signals and stop loss calculations. In addition to the order types described above, we also created one invalid order to see how the engine handles such a scenario and help you easily spot when you have setup the order incorrectly.Running the Code
The data used is assumed to be in a subfolder calleddata
which is in the same directory as the script. If you place the data file somewhere else or give it a different name, be sure to modify the line:
dataname='data/TestBrackets.csv',
Let’s now take a look at some of the output and how it relates to our trade scenarios.
Market Entry, Stop Loss and Take Profit
The first trade completed produces the following output:-------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-01-12, Status 2: Ref: 1, Size: 1, Price: NA -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-01-12, Status 2: Ref: 2, Size: -1, Price: 150.0 -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-01-12, Status 2: Ref: 3, Size: -1, Price: 250.0 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-01-12, Status 4: Ref: 1, Size: 1, Price: NA Created: 2018-01-11 23:59:59.999989 Price: 200.0 Size: 1 -------------------------------------------------------------------------------- 2018-01-12: Close: $210.0, Position Size: 1 2018-01-13: Close: $220.0, Position Size: 1 2018-01-14: Close: $230.0, Position Size: 1 2018-01-15: Close: $240.0, Position Size: 1 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-01-16, Status 4: Ref: 3, Size: -1, Price: 250.0 Created: 2018-01-11 23:59:59.999989 Price: 250.0 Size: -1 -------------------------------------------------------------------------------- -------------------------------- NOTIFY ORDER -------------------------------- Order Canceled 2018-01-16, Status 5: Ref: 2, Size: -1, Price: 150.0 -------------------------------- NOTIFY TRADE -------------------------------- 2018-01-16 23:59:59.999989, Close Price: 210.0, Profit, Gross 40.0, Net 40.0 --------------------------------------------------------------------------------We can see that all 3 orders are accepted at the same time. The first order
Ref: 1
has a Price: NA
. This is our Market Order. You will also see that this order also is the only order which has Completed
on the same bar that the orders are Accepted
.
Following this, our position is open for a few bars until the price rises to $250 and our take profit is triggered. You can see that Ref: 3
is Completed
and Ref: 2
is Canceled
automatically at the same time. Lovely!
Stop Entry, Stop Loss and Take Profit
Our second example uses stop order for entry and produces the following output:-------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-01-22, Status 2: Ref: 4, Size: -1, Price: 200 -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-01-22, Status 2: Ref: 5, Size: 1, Price: 250 -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-01-22, Status 2: Ref: 6, Size: 1, Price: 150 2018-01-22: Close: $290.0, Position Size: 0 2018-01-23: Close: $280.0, Position Size: 0 2018-01-24: Close: $270.0, Position Size: 0 2018-01-25: Close: $260.0, Position Size: 0 2018-01-26: Close: $250.0, Position Size: 0 2018-01-27: Close: $240.0, Position Size: 0 2018-01-28: Close: $230.0, Position Size: 0 2018-01-29: Close: $220.0, Position Size: 0 2018-01-30: Close: $210.0, Position Size: 0 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-01-31, Status 4: Ref: 4, Size: -1, Price: 200 Created: 2018-01-21 23:59:59.999989 Price: 200 Size: -1 -------------------------------------------------------------------------------- 2018-01-31: Close: $200.0, Position Size: -1 2018-02-01: Close: $190.0, Position Size: -1 2018-02-02: Close: $180.0, Position Size: -1 2018-02-03: Close: $170.0, Position Size: -1 2018-02-04: Close: $160.0, Position Size: -1 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-02-05, Status 4: Ref: 6, Size: 1, Price: 150 Created: 2018-01-21 23:59:59.999989 Price: 150 Size: 1 -------------------------------------------------------------------------------- -------------------------------- NOTIFY ORDER -------------------------------- Order Canceled 2018-02-05, Status 5: Ref: 5, Size: 1, Price: 250 -------------------------------- NOTIFY TRADE -------------------------------- 2018-02-05 23:59:59.999989, Close Price: 200.0, Profit, Gross 50.0, Net 50.0 --------------------------------------------------------------------------------The key difference here is that all 3 orders are
Accepted
but none of them are Completed
. We create the order, the price is quite high. Following this, the price gradually drops until the stop entry order is triggered at $200. Notice how the stopprice
was set at $250 but did not trigger when the price was above $250 or passing through it. This is because the order was not valid until the mainside
(the stop entry) order was completed. That is the beauty of bracket orders.
Limit Entry, Stop Loss and Take Profit
Our third example uses a limit order for entry and produces the following output:-------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-02-12, Status 2: Ref: 7, Size: -1, Price: 290 -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-02-12, Status 2: Ref: 8, Size: 1, Price: 340 -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-02-12, Status 2: Ref: 9, Size: 1, Price: 240 2018-02-12: Close: $120.0, Position Size: 0 2018-02-13: Close: $130.0, Position Size: 0 2018-02-14: Close: $140.0, Position Size: 0 2018-02-15: Close: $150.0, Position Size: 0 2018-02-16: Close: $160.0, Position Size: 0 2018-02-17: Close: $170.0, Position Size: 0 2018-02-18: Close: $180.0, Position Size: 0 2018-02-19: Close: $190.0, Position Size: 0 2018-02-20: Close: $200.0, Position Size: 0 2018-02-21: Close: $210.0, Position Size: 0 2018-02-22: Close: $220.0, Position Size: 0 2018-02-23: Close: $230.0, Position Size: 0 2018-02-24: Close: $240.0, Position Size: 0 2018-02-25: Close: $250.0, Position Size: 0 2018-02-26: Close: $260.0, Position Size: 0 2018-02-27: Close: $270.0, Position Size: 0 2018-02-28: Close: $280.0, Position Size: 0 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-03-01, Status 4: Ref: 7, Size: -1, Price: 290 Created: 2018-02-11 23:59:59.999989 Price: 290 Size: -1 -------------------------------------------------------------------------------- 2018-03-01: Close: $290.0, Position Size: -1 2018-03-02: Close: $300.0, Position Size: -1 2018-03-03: Close: $290.0, Position Size: -1 2018-03-04: Close: $280.0, Position Size: -1 2018-03-05: Close: $270.0, Position Size: -1 2018-03-06: Close: $260.0, Position Size: -1 2018-03-07: Close: $250.0, Position Size: -1 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-03-08, Status 4: Ref: 9, Size: 1, Price: 240 Created: 2018-02-11 23:59:59.999989 Price: 240 Size: 1 -------------------------------------------------------------------------------- -------------------------------- NOTIFY ORDER -------------------------------- Order Canceled 2018-03-08, Status 5: Ref: 8, Size: 1, Price: 340 -------------------------------- NOTIFY TRADE -------------------------------- 2018-03-08 23:59:59.999989, Close Price: 290.0, Profit, Gross 50.0, Net 50.0 --------------------------------------------------------------------------------The sequence of events when using a
limit
order for entry does not change at all from the stop
order. The only difference is that the price must move up to the target entry price (because we want to go short) instead of down through it. In other words, the price we enter at, must be better than what the price was when the order was created.
Invalid Settings
Our final order uses invalid settings. Specially:- We provide an incorrect
exectype
. It should beLimit
but we enteredStop
- Our
stopprice
value is set below our entry price (incorrect for short) - The
limitprice
is set above our entry price (incorrect for short)
-------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-03-24, Status 2: Ref: 10, Size: -1, Price: 290 -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-03-24, Status 2: Ref: 11, Size: 1, Price: 90 -------------------------------- NOTIFY ORDER -------------------------------- Order Accepted 2018-03-24, Status 2: Ref: 12, Size: 1, Price: 300 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-03-24, Status 4: Ref: 10, Size: -1, Price: 290 Created: 2018-03-23 23:59:59.999989 Price: 290 Size: -1 -------------------------------------------------------------------------------- 2018-03-24: Close: $120.0, Position Size: -1 -------------------------------- NOTIFY ORDER -------------------------------- Order Completed 2018-03-25, Status 4: Ref: 11, Size: 1, Price: 90 Created: 2018-03-23 23:59:59.999989 Price: 90 Size: 1 -------------------------------------------------------------------------------- -------------------------------- NOTIFY ORDER -------------------------------- Order Canceled 2018-03-25, Status 5: Ref: 12, Size: 1, Price: 300 -------------------------------- NOTIFY TRADE -------------------------------- 2018-03-25 23:59:59.999989, Close Price: 120.0, Profit, Gross -10.0, Net -10.0 --------------------------------------------------------------------------------Finishing off the examples, we can see here that our entry order is
Completed
immediately. This is because the price of the asset is already below our stop
entry level. After this order is Completed
, our stop loss is immedaitely Completed
on the next bar. The order became active when our entry order Completed
and is then also filled immediately because the current price is above the stopprice
that we set. (Note: Remember this is a short position). Find This Post Useful?
If this post saved you time and effort, please consider support the site! There are many ways to support us and some won’t even cost you a penny.
Brave
Backtest Rookies is a registered with Brave publisher!
Brave users can drop us a tip.
Alternatively, support us by switching to Brave using this referral link and we will receive some BAT!
Tradingview
Referral Link
Enjoying the content and thinking of subscribing to Tradingview? Support this site by clicking the referral link before you sign up!
PayPal
BTC
3HxNVyh5729ieTfPnTybrtK7J7QxJD9gjD
ETH
0x9a2f88198224d59e5749bacfc23d79507da3d431
LTC
M8iUBnb8KamHR18gpgi2sSG7jopddFJi8S
Very interesting post! Direct and to the point… thanks
Is it possible that the position size is always different? So assume, I have a strategy where the stop level and the target is always different, from trade to trade. But I want to loose always the same amount in maximum. So for example if my max loss is 200 the position size is 200 / (limit – stop). how can I do this?
I noticed that if the position is closed not by one of the OCO orders in the bracket but by the logic of the strategy, they remain active and get executed later.
Thank you for the insightful post! Under the “Limit Entry, Stop Loss and Take Profit” paragraph you mean (…,exectype=self.Order.Stop) and not(…, exectype=self.Order.Stop .Limit) right?