Bracket Orders: Entry + TP + SL
Bracket orders, OCO child exits, percent levels from actual fill price and trailing stop-loss legs.
A bracket order wraps an entry with two linked exits: take-profit and stop-loss. When one exit fills, the other is cancelled through OCO sibling logic.
Source
backtester/execution/order_types.py + backtester/execution/fill_engine.pyLayerOrder execution / entry with managed exits
ModeOrder mode
Input
OrderType.BRACKET plus BracketSpecOutputentry order plus OCO-linked TP and SL children
Primary API
Order(..., order_type=OrderType.BRACKET, bracket=BracketSpec(...))Main caveatChildren are parked until the entry fills; daily bars still have same-bar ambiguity.
Execution Semantics
- Strategy submits a parent
OrderType.BRACKET. FillEngine._submit_bracket()creates an entry order.- The engine creates two parked child exits: TP and SL.
- TP and SL share an
oco_pair_id. - After the entry fills, child prices are resolved from actual fill if percent levels are used.
- Children become pending.
- When one child fills completely, the sibling is cancelled.
Fixed Price Bracket
Explicit TP and SL prices
python
self.fill_engine.submit(Order(
instrument=inst,
side=OrderSide.BUY,
quantity=shares,
order_type=OrderType.BRACKET,
bracket=BracketSpec(
take_profit_price=round(bar.close * 1.10, 2),
stop_loss_price=round(bar.close * 0.95, 2),
),
))Percent Levels From Actual Entry Fill
take_profit_pct and stop_loss_pct are resolved after the entry fills, using the actual average fill price, not the signal bar close.
Bracket sized as percent of NAV
python
self.bracket_percent("AAPL", weight=0.20, tp=0.10, sl=0.05)Stop-Limit Stop-Loss Leg
Bracket with stop-limit risk leg
python
self.fill_engine.submit(Order(
instrument=inst,
side=OrderSide.BUY,
quantity=shares,
order_type=OrderType.BRACKET,
bracket=BracketSpec(
take_profit_pct=0.10,
stop_loss_pct=0.05,
stop_loss_type=OrderType.STOP_LIMIT,
stop_limit_offset=0.25,
),
))Trailing Stop-Loss Leg
Bracket with trailing stop-loss
python
self.fill_engine.submit(Order(
instrument=inst,
side=OrderSide.BUY,
quantity=shares,
order_type=OrderType.BRACKET,
bracket=BracketSpec(
take_profit_price=round(bar.close * 1.10, 2),
stop_loss_type=OrderType.STOP_TRAIL,
trail_amount=5.0,
),
))Signal Exit Cancels Children
If the strategy signal invalidates before TP or SL, cancel resting children and exit manually.
Cancel bracket children before manual exit
python
if signal == 0 and pos > 0:
self.fill_engine.cancel_all(instrument=inst)
self.close_position(inst)Daily-Bar Boundary
If TP and SL are both touched inside the same daily candle, the engine cannot know which came first. The result follows documented fill priority. For path-dependent strategies, use intraday bars.
Failure Modes
- Assuming child exits are active before the entry fill.
- Assuming percent TP/SL levels are based on signal close instead of actual fill.
- Forgetting to cancel child orders on signal-based exit.
- Using stop-limit when a guaranteed protective stop was intended.
- Assuming daily bars prove whether TP or SL happened first.