Skip to content

Commit af93931

Browse files
committed
feat: add ADR of OrderType = Bracket
1 parent b9f616b commit af93931

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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

Comments
 (0)