Skip to content

Add contingent orders support: attached orders and OCA groups - #265

Merged
Martin-Molinero merged 4 commits into
masterfrom
feature-contingent-orders
Sep 25, 2026
Merged

Martin-Molinero merged 4 commits into
masterfrom
feature-contingent-orders

Conversation

@Martin-Molinero

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

Copy link
Copy Markdown
Member

Description

Places contingent order sets (OCO, OTO, OUO and brackets, including combo orders) atomically at IB:

  • Orders triggered by another are attached through parentId. An order is held back, not transmitted, only when a later order of the set will carry it: its child or a sibling under the same parent. Anything else, like plain OCA members, is transmitted on its own.
  • Each IB order of the set goes through the regular IBPlaceOrder path, with the contingent attributes on top.
  • Members of an OCO/OUO are placed in an OCA group: type 1 cancels, type 2 reduces proportionally.
  • When a member is updated, the parent id and OCA group IB reports for it are sent back unchanged, so IB doesn't reject the update (10326/10327). They're captured from IB's open order callbacks, which also cover the orders restored on restart.
  • A 201 OCA group is already filled rejection of a member is reported as Canceled, which is the one cancels other semantic.
  • A 10148 cancel reply for an order IB already canceled, like the children of a canceled parent, isn't reported as Invalid.
  • A held trailing stop is placed without a trail stop price: IB starts trailing once it's activated.
  • If IB rejects an order of a set, the orders of the set held back waiting for it to be transmitted are canceled.
  • Held children are reported as triggered once their parent fills, through the Lean OnContingentOrdersTriggered helper.
  • The contingencies of open orders are rebuilt, best effort, from their parent ids and OCA groups, through OrderContingency.Trigger/Relate.

Related Issue

Requires QuantConnect/Lean#9828, which adds contingent orders to Lean. This PR builds against it, so CI will pass once it's merged and released.

Motivation and Context

Support for Lean contingent orders (OCO, OTO, OUO, brackets) using the brokerage's native functionality, so the sets are placed atomically and managed on the brokerage side.

Requires Documentation Change

The supported contingent order shapes of this brokerage, see its brokerage model in Lean.

How Has This Been Tested?

  • Unit tests: contingent orders tests plus the error handling tests.
  • Live on an IB paper account, from a Python Lean algorithm through the Lean launcher, on this revision. SPY/QQQ, 1-2 shares. "Updated" means the update was confirmed by IB.

Existing order types (plain API and order_factory)

Order Result
Market ✅ filled
Limit, resting ✅ price + quantity updated, canceled
Limit, marketable ✅ filled
Stop market / stop limit ✅ updated, canceled
Trailing stop, amount / percentage ✅ updated, canceled
Limit if touched ✅ updated, canceled
Market on open ✅ rejected by Lean while the market is open (expected)
Market on close ⚠️ IB sends no status within a minute, stays New; same on master
Factory market / limit ✅ filled / updated, canceled
Combo market (SPY call spread) ✅ both legs filled
Combo limit ✅ updated, canceled
Combo leg limit ✅ submitted, canceled
Factory combo limit ✅ submitted, canceled
Option exercise ✅ accepted by IB (the paper account processes it later in the day)

Contingent orders

Case Result
OCO resting: price and quantity updates, cancel one ✅ both canceled
OTO: held children + parent updated, parent canceled ✅ children canceled
Bracket, market entry: stop loss updated, take profit fills ✅ stop loss canceled
Bracket: stop loss moved through the market fills ✅ take profit canceled
Bracket, limit entry, OUO exits: held exit updated, entry and take profit fill ✅
Bracket with a stop limit stop loss: take profit canceled ✅ stop loss canceled
Limit if touched + stop exits: updated, one canceled ✅
OCO / OUO exits, marketable member fills ✅ sibling canceled
Chain of 3 ✅ released in order
OCO of 2 entries, one with an attached exit ✅ both transmitted, exit held then canceled
Multi-symbol OCO (SPY, QQQ) ✅
Market on close exit ✅
Trailing stop exit under a limit parent ✅ starts trailing when triggered
Trailing stop under a market parent ✅ rejected by the brokerage model, IB doesn't support it
Whole bracket canceled at once ✅ canceled, no spurious Invalid
Restart with a resting bracket ✅ rebuilt, held exit updated, canceled
IB rejects the transmitting order (321) ✅ runtime error, the algorithm stops; same as a plain order on master

Contingent orders on derivatives: SPY option, SPXW index option, MES future, MES future option

Case Option Index option Future Future option
Bracket, market entry: take profit made marketable fills ✅ ✅ ✅ ✅
OCO resting: one updated, cancel one cancels both ✅ ✅ ✅ ✅
OTO: held child updated, parent canceled cancels it ✅ ✅ ✅ ✅
  • Combo OTO (SPY call spreads): IB rejected the held combo exit (201 Guaranteed-to-Lose combination), and the combo parent held back with it was canceled. This is the rejected-transmitting-order handling.

Lean BrokerageTests contingent orders suite (InteractiveBrokersContingentOrderTests, SPY)

Case OCO OUO OTO Bracket Combo OCO Combo OTO
Resting: cancel the first order ✅ ✅ ✅ ✅ ✅ ✅
Resting: every order updated, held ones included ✅ ✅ ✅ ✅ ✅ ✅
Resting: open orders rebuilt with the same contingencies ✅ ✅ ✅ ✅ ✅ ✅
Market parent fills, children released ✅ ✅ ✅

The combos are SPY call spreads (combo limit / combo market orders), the combo OTO exit is the combo sell order of the spread.

  • Earlier runs on a previous revision: forex and futures brackets, option combo OTO, held children beyond the account buying power (IB accepts and holds them).

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

@Martin-Molinero
Martin-Molinero force-pushed the feature-contingent-orders branch 2 times, most recently from 225b1dd to 86b6019 Compare September 24, 2026 13:23
Places OCO, OTO, OUO and bracket order sets atomically through parent
ids and OCA groups, transmitting only the last order. Triggered children
are reported once their parent fills and open orders rebuild their
contingencies best effort from parent ids and OCA groups.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Martin-Molinero
Martin-Molinero force-pushed the feature-contingent-orders branch from 86b6019 to a680b68 Compare September 24, 2026 14:21
Martin-Molinero and others added 3 commits September 24, 2026 13:01
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Martin-Molinero
Martin-Molinero merged commit 25fa440 into master Sep 25, 2026
1 check failed
@Martin-Molinero
Martin-Molinero deleted the feature-contingent-orders branch September 25, 2026 16:17
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