QuantJourney Backtester

QuantJourney Backtester

Share product feedback

✓

Thank you.

Your note is now in the QuantJourney inbox.

docs/engine/oco-orders.mdx

OCO Orders

How one-cancels-other pairs link two orders so the first fill cancels the sibling.

OCO means one-cancels-other. It links two orders through a shared pair id so the first filled leg cancels the remaining sibling.

Sourcebacktester/execution/fill_engine.py
LayerOrder execution / sibling cancellation
ModeOrder mode
Inputtwo orders with shared oco_pair_id
Outputone filled order and one cancelled sibling
Primary APIOrder(..., order_type=OrderType.OCO, oco_pair_id=...)
Main caveatUse unique OCO ids per pair to avoid accidental cross-cancellation.

Level 1: Manual TP/SL Pair

Use bracket orders for normal TP/SL entry plans. Use OCO directly when you need manual control over pair construction.

Manual exit OCO
python
oco_id = f"exit_{inst}_{date}"

self.fill_engine.submit(Order(
    instrument=inst,
    side=OrderSide.SELL,
    quantity=pos,
    order_type=OrderType.OCO,
    limit_price=round(entry_price * 1.08, 2),
    oco_pair_id=oco_id,
))

self.fill_engine.submit(Order(
    instrument=inst,
    side=OrderSide.SELL,
    quantity=pos,
    order_type=OrderType.OCO,
    stop_price=round(entry_price * 0.95, 2),
    oco_pair_id=oco_id,
))

Level 2: Dip Or Breakout Entry

OCO can also express competing entry hypotheses: buy the dip or buy the breakout, but not both.

Competing entry OCO
python
oco_id = f"entry_{inst}_{date}"

self.fill_engine.submit(Order(
    instrument=inst,
    side=OrderSide.BUY,
    quantity=shares,
    order_type=OrderType.OCO,
    limit_price=round(bar.close * 0.98, 2),
    oco_pair_id=oco_id,
))

self.fill_engine.submit(Order(
    instrument=inst,
    side=OrderSide.BUY,
    quantity=shares,
    order_type=OrderType.OCO,
    stop_price=round(bar.close * 1.02, 2),
    oco_pair_id=oco_id,
))

Level 3: Clear Stale OCO Orders

If neither branch remains valid, cancel the pair.

Cancel stale OCO pair
python
if pos == 0 and not entry_window_is_open:
    self.fill_engine.cancel_all(instrument=inst)
    self._oco_live[inst] = False

Implementation Rule

  • Orders with the same oco_pair_id are registered as siblings.
  • When one sibling fills, _cancel_oco_pair() cancels every other active sibling.
  • OrderType.OCO behaves as limit-style when limit_price is set and stop-style when stop_price is set.

Failure Modes

  • Reusing the same OCO id across instruments or dates.
  • Forgetting to cancel OCO orders when the entry window expires.
  • Using OCO where bracket is simpler and less error-prone.
  • Assuming OCO models exchange-native order queue semantics.