Skip to content

Add contingent orders: OCO, OTO, OUO and brackets - #9828

Open
Martin-Molinero wants to merge 5 commits into
masterfrom
feature-contingent-orders
Open

Martin-Molinero wants to merge 5 commits into
masterfrom
feature-contingent-orders

Conversation

@Martin-Molinero

@Martin-Molinero Martin-Molinero commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Description

Adds contingent orders: sets of independent orders whose lifecycle is related.

  • One Cancels Other (OCO/OCA): the first fill of a member, even partial, cancels the rest.
  • One Triggers Other (OTO): children are held until their parent, all its legs for a combo, completely fills. They're canceled if the parent is canceled or invalid.
  • One Updates Other (OUO): a partial fill reduces the remaining quantity of the siblings proportionally.
  • Any composition of them, like brackets (OTOCO). Combo orders can be members.

Algorithm API. Existing order methods are unchanged and go through the same submit path.

  • OrderFactory (self.order_factory) creates SubmitOrderRequests. They're composed with Triggers, Bracket, OneCancelsOther and OneUpdatesOther, then submitted with Order(...).
  • BracketOrder, OneCancelsOtherOrder, OneUpdatesOtherOrder and OneTriggersOtherOrder are helpers for the common shapes.
entry = self.order_factory.limit_order(spy, 100, 500)
entry.triggers(self.order_factory.one_cancels_other(
    self.order_factory.limit_order(spy, -100, 510),
    self.order_factory.stop_market_order(spy, -100, 495)))
tickets = self.order(entry)
# or
tickets = self.bracket_order(spy, 100, take_profit_price=510, stop_loss_price=495, limit_price=500)

Order model.

  • Order.Contingency (OrderContingency) holds the set shared by its orders, with per-order links: contingency id, type, and role (parent, child, or none for siblings).
  • OrderContingency owns the composition (Trigger/Relate). The brokerages also use it to rebuild the contingencies of their open orders.
  • OrderContingency.IsWaitingForTrigger tells whether a child is still held.
  • JSON serialization is backwards compatible.

Transaction handler.

  • A set is submitted atomically once all its orders arrive.
  • Held children skip the buying power check; they're validated when released. This matches the brokerages; see the live testing below.
  • OCO/OUO siblings count once in GetOpenOrdersRemainingQuantity and in the cash buying power model.
  • Brokerage updates for triggered or resized orders are applied. A fill of a held order also marks it triggered, as a safety net.

Backtesting.

  • ContingentOrderProcessor drives the lifecycle: holding children, triggering on a complete fill, cancelling siblings, resizing OUO siblings and cascading cancels.
  • A triggered order needs new data before it can fill.
  • On same-bar ties the stop loss wins (pessimistic).
  • A trailing stop child starts trailing when triggered.

Brokerage models.

  • IB, Charles Schwab, TradeStation, Alpaca and Binance validate what each brokerage supports. The rest reject contingent orders.
  • The base Brokerage exposes ContingentOrderCache and OnContingentOrdersTriggered for the plugins.

Related Issue

Brokerage plugin support, to be merged after this one:

Motivation and Context

Brackets, OCO and OTO are standard order types the supported brokerages offer natively. Until now algorithms had to emulate them with order events, which isn't atomic and leaves naked positions when a fill and a cancel race.

Requires Documentation Change

Yes: the new order types and APIs (OrderFactory, Order, BracketOrder, OneCancelsOtherOrder, OneUpdatesOtherOrder, OneTriggersOtherOrder) and what each brokerage supports.

How Has This Been Tested?

  • Unit tests:
    • AlgorithmOrderFactoryTests
    • ContingentOrdersTransactionHandlerTests, including the remaining open quantity aggregation
    • ContingentOrderTests, ContingentOrderProcessorTests
    • ContingentOrdersBrokerageModelTests
  • Regression algorithms, C# and Python:
    • BracketOrder, BracketOrderLimitEntry
    • OneCancelsOtherOrder, OneCancelsOtherOrderCashAccount, OneTriggersOtherOrder, OneUpdatesOtherOrder
    • ContingentOrderCancel, ContingentOrderUpdate, ContingentComboOrder, ContingentTrailingStopOrder
  • The full regression suite passes. The broad unit test sweep passes except LiveOptionChainProviderReturnsFutureOptionData, which also fails on master (CME endpoint error).
  • Live end to end on an Interactive Brokers paper account:
    • brackets with the take profit or the stop loss filling
    • OTO with a cancelled parent
    • OCO/OUO exits
    • chains
    • updates of held and working orders
    • forex, futures and multi-symbol OCO
    • option combo OTO
    • held children beyond the account buying power, which IB accepts and holds

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

🤖 Generated with Claude Code

Introduces contingent orders, sets of independent orders whose lifecycle
is related: one cancels other (OCO/OCA), one triggers other (OTO), one
updates other (OUO) and any composition of them like brackets (OTOCO),
including combo orders as members.

- Order model: Order.Contingency (OrderContingency) with the set shared
  by its orders and per order links (contingency id, type, nullable role
  parent/child, triggered state), JSON serialization (backwards
  compatible). OrderContingency owns the composition: Trigger/Relate
  join orders (single orders or all the legs of a combo order) into a
  set, used by the algorithm and by the brokerages to rebuild the
  contingencies of their open orders
- Algorithm API: OrderFactory on the algorithm creates SubmitOrderRequests
  composed through Triggers, Bracket, OneCancelsOther and OneUpdatesOther,
  submitted with Order(); BracketOrder, OneCancelsOtherOrder,
  OneUpdatesOtherOrder and OneTriggersOtherOrder helpers. Every existing
  order method goes through the same submit path, no API changes
- Transaction handler: atomic submission once all the orders arrive,
  buying power skipped for held children and shared by OCO siblings,
  per set request routing, order update events for triggered children
  and brokerage side resizing
- Backtesting/paper brokerage: ContingentOrderProcessor drives the
  lifecycle (hold, trigger on full fill, cancel siblings, reduce OUO,
  cascade cancels), triggered orders require new data, stop loss wins
  same bar ties, trailing stops anchor on trigger
- Brokerage models: IB, Charles Schwab, TradeStation, Alpaca and Binance
  validate what each brokerage supports, the rest reject
- Brokerages: ContingentOrderCache and GroupOrderCacheManager on the base
  brokerage, OnContingentOrdersTriggered and shared helpers for plugins
- Regression algorithms (C# and Python) and unit tests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Martin-Molinero
Martin-Molinero force-pushed the feature-contingent-orders branch from a99b51b to 7c732a0 Compare September 24, 2026 14:21
Martin-Molinero and others added 4 commits September 24, 2026 13:01
ContingentOrderTestParameters builds OCO, OUO, OTO and bracket sets out of the existing order test parameters, or any custom set.
BrokerageTests gets ContingentOrdersCancel, ContingentOrdersUpdate and ContingentOrdersTrigger, which brokerages opt into like the rest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gent brokerage tests

TradeStation rejects a bracket group (BRK) without a stop order. The contingent cancel test no longer assumes the rest of a group
is canceled with its canceled member, and the trigger test takes updated orders as working.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The brokerage open orders are rebuilt with the same contingencies as the placed orders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…test

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant