QuantJourney Backtester

QuantJourney Backtester

Share product feedback

✓

Thank you.

Your note is now in the QuantJourney inbox.

docs/engine/bracket-orders.mdx

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.

Sourcebacktester/execution/order_types.py + backtester/execution/fill_engine.py
LayerOrder execution / entry with managed exits
ModeOrder mode
InputOrderType.BRACKET plus BracketSpec
Outputentry order plus OCO-linked TP and SL children
Primary APIOrder(..., order_type=OrderType.BRACKET, bracket=BracketSpec(...))
Main caveatChildren are parked until the entry fills; daily bars still have same-bar ambiguity.

Execution Semantics

  1. Strategy submits a parent OrderType.BRACKET.
  2. FillEngine._submit_bracket() creates an entry order.
  3. The engine creates two parked child exits: TP and SL.
  4. TP and SL share an oco_pair_id.
  5. After the entry fills, child prices are resolved from actual fill if percent levels are used.
  6. Children become pending.
  7. 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.