|
| 1 | +# ADR 0001: Bracket order type |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Proposed - 2026-07-09 |
| 6 | + |
| 7 | +## Purpose |
| 8 | + |
| 9 | +Implement a new `OrderType.Bracket` in Lean core, plus the brokerage-side mapping in the plugins whose API supports it. This file is the working document for that implementation: it holds the broker research and the reference PRs to follow. |
| 10 | + |
| 11 | +## Context |
| 12 | + |
| 13 | +A bracket order is an entry (parent) order with two attached exit orders: a take-profit limit and a stop-loss. The two exits are OCO (one cancels the other). Lean users ask for this feature in live trading, but each brokerage API supports it differently: some have a native bracket/OTOCO order type, some only allow one attached exit, some only support standalone TP/SL orders, and some do not support it at all. |
| 14 | + |
| 15 | +Before designing the Lean-side API, we researched every brokerage that has a `Lean.Brokerages.*` plugin and checked whether the broker's own trading API natively supports bracket orders. Sources are official broker/exchange documentation pages, fetched and verified. |
| 16 | + |
| 17 | +Classification used in the table: |
| 18 | + |
| 19 | +- **yes** - native API support for parent + take-profit + stop-loss |
| 20 | +- **no** - no bracket support in the API |
| 21 | + |
| 22 | +## Broker API support matrix |
| 23 | + |
| 24 | +| brokerage | bracket | source (official doc link) | notes | |
| 25 | +| --------- | ------- | -------------------------- | ----- | |
| 26 | +| InteractiveBrokers | yes | https://interactivebrokers.github.io/tws-api/bracket_order.html | Parent + TP limit + SL via `parentId` | |
| 27 | +| Alpaca | yes | https://docs.alpaca.markets/us/docs/orders-at-alpaca#bracket-orders | `order_class=bracket`, exits are OCO; no extended hours, TIF must be day/gtc | |
| 28 | +| Tradier | yes | https://docs.tradier.com/reference/advanced-orders | Native OTOCO endpoint; equities and options; legs sent in one request | |
| 29 | +| Tastytrade | yes | https://developer.tastytrade.com/open-api-spec/orders/, https://developer.tastytrade.com/order-management/#submit-complex-order | `POST /accounts/{n}/complex-orders`, type OTOCO; docs call it a bracket | |
| 30 | +| TradeStation | yes | https://api.tradestation.com/docs/specification#tag/Order-Execution/operation/PlaceGroupOrder | OSO + OCO order types; stocks, options, futures; endpoint schema is deeper in the API spec | |
| 31 | +| WeBull | yes | https://developer.webull.com/apis/docs/trade-api/stock/ | `combo_type` enum includes OTO, OCO, OTOCO and MASTER (entry + TP/SL legs) | |
| 32 | +| Coinbase | yes | https://docs.cdp.coinbase.com/coinbase-app/advanced-trade-apis/guides/orders | `attached_order_configuration` with `trigger_bracket_gtc/gtd`; one exit fills, the other cancels | |
| 33 | +| Binance | yes (spot) | https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints | Spot OTOCO order list `POST /api/v3/orderList/otoco`; entry must be LIMIT/LIMIT_MAKER (no market-entry bracket). Futures API not verified yet - TP/SL there are separate conditional orders | |
| 34 | +| TradingTechnologies | yes | https://library.tradingtechnologies.com/trade/tto-bracket-order.html | TT Bracket: parent limit/stop + OCO child pair with fill-size matching; page is platform help - TT REST/FIX reference still to confirm API-level | |
| 35 | +| CharlesSchwab | yes (docs gated) | https://developer.schwab.com/ | Order schema (`orderStrategyType` TRIGGER/OCO, i.e. 1st-triggers-OCO) is behind developer login; public portal does not show it | |
| 36 | +| Bybit | no | https://bybit-exchange.github.io/docs/v5/order/create-order | [Need deeply read doc] `takeProfit`/`stopLoss` attached on create (spot + derivatives); `tpslMode` Full/Partial; options get market-only exits; docs never say "OCO" explicitly | |
| 37 | +| Kraken | no | https://docs.kraken.com/api/docs/rest-api/add-order/ | Conditional close attaches only ONE exit (`close[ordertype]`) - cannot attach both TP and SL, no OCO link | |
| 38 | +| Bitfinex | no | https://docs.bitfinex.com/reference/ws-auth-input-order-new | `price_oco_stop` gives a limit+stop OCO pair, but no parent entry that triggers exits (no OTOCO) | |
| 39 | +| dYdX | no | https://docs.dydx.xyz/concepts/trading/orders | Standalone TP/SL conditional orders for closing positions; no OCO/bracket linkage documented | |
| 40 | +| Public.com | no | https://public.com/api/docs | Any mentions in doc | |
| 41 | +| Zerodha | no | https://support.zerodha.com/category/trading-and-markets/trading-faqs/general/articles/why-bo-stopped | Bracket orders discontinued March 2020, confirmed still gone by Kite staff (2021); no API bracket | |
| 42 | +| RBI (Raiffeisen) | no | - | Institutional FIX connection; nothing publicly documented to cite | |
| 43 | +| Wolverine (WEX) | no | - | Institutional FIX; only an algo help page found so far | |
| 44 | +| OANDA | no | https://developer.oanda.com/rest-live-v20/order-df/ | `takeProfitOnFill` + `stopLossOnFill`; exits are bound to the trade, so OCO effect comes from trade close | |
| 45 | +| Eze (SS&C) | no | https://emsportal.ezesoft.com/pdf/EMS%20xAPI%20Technical%20Documentation.pdf (candidate) | EMS xAPI PDF found by search, not verified yet | |
| 46 | +| TerminalLink (Bloomberg EMSX) | no | https://emsx-api-doc.readthedocs.io/ (candidate) | EMSX API docs found, not verified yet | |
| 47 | +| Samco | no | - | Likely same SEBI-driven removal as Zerodha; needs confirmation | |
| 48 | +| Rithmic | no | https://www.rithmic.com/apis | Server-side brackets and OCOs in R\|API+ and R\|Protocol; exits fire even if the client disconnects; field-level docs are gated | |
| 49 | + |
| 50 | +## OCO / OTO primitive support matrix |
| 51 | + |
| 52 | +A bracket is not a broker primitive on its own; it decomposes into two generic mechanisms that most brokers already expose separately or in combination: |
| 53 | + |
| 54 | +- **OTO** (One-Triggers-Other / conditional): an order that, once filled (or once a price triggers), submits/activates another order. |
| 55 | +- **OCO** (One-Cancels-Other): a linked pair/group where filling one leg cancels the rest. |
| 56 | +- A **bracket** is the composition **OTO → OCO** (entry triggers an OCO exit pair); brokers usually call this **OTOCO** / **1st-Trigger-OCO** / **Bracket**. |
| 57 | + |
| 58 | +This table records, per brokerage, whether OCO and OTO/conditional are available **standalone** (their own order class) and whether a **combined** OTOCO/bracket exists. Legend: **yes** / **no** / **partial** (only in a limited or attached form) / **—** (unknown / not publicly documented). |
| 59 | + |
| 60 | +| brokerage | OCO standalone | OTO / conditional standalone | combined (OTOCO / bracket) | native name(s) | source (official doc link) | |
| 61 | +| --------- | -------------- | ---------------------------- | -------------------------- | -------------- | -------------------------- | |
| 62 | +| InteractiveBrokers | yes | yes | yes | OCA group, `parentId`, Conditional Orders, Bracket | https://interactivebrokers.github.io/tws-api/oca.html | |
| 63 | +| Alpaca | yes | yes | yes | `order_class` = `oco` / `oto` / `bracket` (equities only) | https://alpaca.markets/blog/oco-oto/ | |
| 64 | +| Tradier | yes | yes | yes | `class` = `oco` / `oto` / `otoco` | https://documentation.tradier.com/brokerage-api/trading/place-otoco-order | |
| 65 | +| Tastytrade | yes | no (only inside OTOCO) | yes | complex-order type `OCO` / `OTOCO` | https://developer.tastytrade.com/order-management/#submit-complex-order | |
| 66 | +| TradeStation | yes | yes (OSO) | yes (BRK) | group type `OCO` / `OSO` / `BRK` | https://help.tradestation.com/09_01/tradestationhelp/ob/about_oco_oso_orders.htm | |
| 67 | +| WeBull | yes | yes | yes | `combo_type` = `OTO` / `OCO` / `OTOCO` / `MASTER` (US equities) | https://developer.webull.com/apis/docs/trade-api/stock/ | |
| 68 | +| Coinbase | no | no | yes (attached only) | `attached_order_configuration` `trigger_bracket_gtc/gtd` | https://docs.cdp.coinbase.com/coinbase-app/advanced-trade-apis/guides/orders | |
| 69 | +| Binance (spot) | yes | yes | yes | order list `oco` / `oto` / `otoco` (spot only) | https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints | |
| 70 | +| TradingTechnologies | yes | yes (OSO) | yes | TT OCO / OSO / Bracket (platform; REST/FIX exposure TBC) | https://library.tradingtechnologies.com/trade/tto-oco-order.html | |
| 71 | +| CharlesSchwab | yes | yes (TRIGGER) | yes (TRIGGER→OCO) | `orderStrategyType` = `OCO` / `TRIGGER` (docs gated) | https://developer.schwab.com/ | |
| 72 | +| Bybit | partial (attached TP/SL on position) | partial (conditional/trigger orders) | yes (attached TP/SL) | attached `takeProfit`/`stopLoss` (`tpslMode`), conditional order | https://bybit-exchange.github.io/docs/v5/order/create-order | |
| 73 | +| Kraken | derivatives only | yes (Conditional Close, spot) | derivatives TP/SL bracket | Conditional Close (OTO); futures TP/SL (OCO) | https://support.kraken.com/articles/360038640052-conditional-close | |
| 74 | +| Bitfinex | yes | no | no | OCO (limit + stop pair) | https://support.bitfinex.com/hc/en-us/articles/115003507305-What-is-a-One-Cancels-Other-OCO-order-option-on-Bitfinex | |
| 75 | +| dYdX | no | partial (price-triggered conditional only, no order→order link) | no | conditional stop / take-profit orders | https://help.dydx.trade/en/articles/166981-perpetual-order-types-on-dydx-chain | |
| 76 | +| Public.com | no | no | no | — | https://public.com/api/docs | |
| 77 | +| Zerodha | yes (GTT two-leg) | partial (GTT single-leg conditional; intraday BO discontinued 2020) | no | GTT OCO (`two-leg`) | https://kite.trade/docs/connect/v3/gtt/ | |
| 78 | +| RBI (Raiffeisen) | — | — | — | institutional FIX (undocumented) | - | |
| 79 | +| Wolverine (WEX) | — | — | — | institutional FIX (undocumented) | - | |
| 80 | +| OANDA | trade-level only | yes (`takeProfitOnFill` / `stopLossOnFill`) | yes (entry + TP/SL on fill) | `takeProfitOnFill` / `stopLossOnFill` | https://developer.oanda.com/rest-live-v20/order-df/ | |
| 81 | +| Eze (SS&C) | — | — | — | EMS xAPI (unverified) | - | |
| 82 | +| TerminalLink (Bloomberg EMSX) | — | — | — | EMSX API (unverified) | - | |
| 83 | +| Samco | — | — | — | SEBI-driven removal like Zerodha BO; unverified | - | |
| 84 | +| Rithmic | yes (server-side) | via bracket | yes (server-side bracket) | server-side OCO / Bracket (R\|API+) | https://www.rithmic.com/apis | |
| 85 | + |
| 86 | +**Takeaway:** the composable **OCO + OTO** model is the norm, not the exception. Seven brokers expose OCO and OTO/OSO/TRIGGER as distinct order classes with OTOCO/bracket as the explicit combination (InteractiveBrokers, Alpaca, Tradier, Binance spot, WeBull, TradeStation, CharlesSchwab). Standalone **OCO** is broadly available beyond those (Bitfinex, Zerodha GTT, TradingTechnologies, Rithmic, Tastytrade). Only a minority are bracket-/attach-only (Coinbase, OANDA, Bybit) or single-conditional-only (dYdX, Kraken spot). This supports implementing **OCO** and **conditional (OTO)** as independent Lean primitives, with **bracket** as a thin OTOCO composition on top. |
| 87 | + |
| 88 | +## Reference implementations |
| 89 | + |
| 90 | +Earlier PRs that added a new order type to Lean. They show every place a new order type must touch: `OrderType` enum, order class in `Common/Orders`, serialization, brokerage models, fill models, Python bindings, and tests. |
| 91 | + |
| 92 | +- TrailingStop - https://github.com/QuantConnect/Lean/pull/7402 |
| 93 | +- ComboMarketOrder, ComboLimitOrder, ComboLegLimitOrder - https://github.com/QuantConnect/Lean/pull/6813 - introduced `GroupOrderManager` for orders that belong to one group; closest pattern for a parent order with attached exits |
| 94 | +- LimitIfTouched - https://github.com/QuantConnect/Lean/pull/5164 |
| 95 | + |
0 commit comments