broker-order-type-capability-matrix · v3.0.0 · 2026-09-03 · sha256 53a5be19c7c69669
broker-order-type-capability-matrix v3.0.0A
Immutable. This exact content is served forever at /api/v1/blob/53a5be19c7c69669.
---
name: broker-order-type-capability-matrix
description: >-
Use when one strategy runs across brokers with different native order types (bracket,
OCO, trailing stop, iceberg, peg, TWAP) and must decide whether to send a native type
or emulate it locally while conserving the parent quantity.
license: Apache-2.0
metadata:
domain: algorithmic-trading
subdomain: broker-integration
tags: broker-integration, order-types, capability-matrix, bracket-orders, oco-orders, synthetic-orders, execution-algorithms
brokers_frameworks: "Interactive Brokers TWS API; Alpaca Trading API; Zerodha Kite Connect; Binance Spot API"
version: "3.0.0"
author: algo-trading-skills-contributors
---
## When to Use
Invoke this skill when deploying algorithmic strategies across broker APIs whose
native order-type support differs. IBKR exposes bracket orders, OCA groups, iceberg
via `displaySize` and the TWAP/VWAP IBALGOs; Alpaca exposes bracket and OCO order
classes but no iceberg and no execution algos; Zerodha withdrew bracket orders but
offers a native iceberg variety; Binance Spot covers OCO, OTOCO and `icebergQty` but
reaches TWAP only through a separate Algo API. Submitting an unsupported type gets it
rejected. This skill checks native support first and, where it is missing,
decomposes the order into a primary leg plus explicitly typed legs for a local
Execution Management System.
The two things it must get right, because everything downstream trusts them: the
**quantity conservation** (does the plan execute exactly the requested size?) and the
**price geometry** (are the protective legs on the side of the market that protects?).
## When NOT to Use
- **As an EMS.** `scripts/capability_matrix.py` plans an order. It performs no network
I/O, submits nothing, watches no quotes and holds no state. The trigger watching,
the timers, the sibling cancellation and the persistence are all yours.
- **As a live source of broker truth.** `DEFAULT_CAPABILITIES` is a dated, sourced
template. Support changes and varies by asset class, product and entitlement —
re-verify against `references/standards.md`'s links before trading it.
- **For VWAP, pegged, trailing-stop or auction orders the broker lacks.** These are
refused, not approximated. Emulating VWAP as evenly spaced slices yields a TWAP
benchmarked against the wrong number. See the Related Skills.
- **As a substitute for idempotency.** The plan tells you to fire one primary order;
making sure a retry does not fire it twice is `order-placement-idempotency`.
- **Where a native path exists.** An emulated OCO is strictly worse than the venue's
own: local triggers add latency, a failure domain, and real double-execution risk.
## Prerequisites
- Each target broker's **documented** order-type surface — the API reference, not the
four types you happened to test. `references/standards.md` lists them with sources.
- A local EMS that can watch Level 1 quotes, run interval timers, cancel a sibling leg
on trigger, and **persist emulated legs across a restart**.
- The venue's minimum order size and quantity step per instrument, for `min_slice_qty`
and for rounding the slice schedule before dispatch.
## Workflow
1. **Register capability profiles, and make them self-consistent.** The `supports_*`
booleans are a view of `native_order_types`, not a second switch — a profile where
they disagree raises at construction rather than reaching order time claiming
native OCO while silently taking the emulation path.
2. **Validate before deciding how to route.** `plan_order_execution` checks quantity,
prices, leg completeness and price geometry *ahead of* the native/emulated branch.
A bracket with no exit legs, or with the stop on the profitable side, is malformed
regardless of whether the broker would have accepted it — the native path is not a
validation bypass. Arguments the requested type does not consume are refused by
name rather than discarded: a `stop_loss_price` silently dropped from a MARKET
order leaves the caller believing a position is protected that nothing is watching.
3. **Read `action` per order type — they are not the same.** For `BRACKET` it is the
**entry** side and the exits invert it. For `OCO` it is the side of **both** exit
legs, matching the same-side constraint Binance and Alpaca document for their
native OCOs. `BRACKET`+`BUY` therefore wants the target *above* the stop, and
`OCO`+`BUY` (closing a short) wants it *below*.
4. **Execute the plan exactly as written.** If `has_primary_order`, fire
`primary_quantity` of `primary_order_type` at `primary_price` once; then register
`emulated_legs`. `primary_quantity` plus every scheduling leg's quantity already
equals the requested quantity — do not re-slice the parent. When
`primary_order_type` is `None` (emulated OCO), fire nothing now.
5. **Persist before you submit.** `plan.to_dict()` serializes the plan losslessly.
Write it down before the primary order goes out, not after.
> Full procedure, EMS contract and the price-geometry table: see `references/workflows.md`.
> Per-broker capability evidence with sources: see `references/standards.md`.
> Printable pre-flight checklist: see `assets/checklist.md`.
## Common Pitfalls
- **Firing the primary slice *and* a feeder sized for the whole parent order.** If the
feeder's quantity is the full parent quantity while the primary is already the first
slice, an EMS following the contract executes `quantity + one slice` — a 100-unit
iceberg in 3 slices sends 133. The feeder here carries only what the primary did not.
- **Slicing in binary float.** Ten thousand units in seven slices, summed, is not ten
thousand units. The residue is tiny and the consequences are not: the final slice
can be rejected for exceeding the parent's remaining quantity, or leave dust.
- **Assuming emulated OCO atomicity.** One leg can fill on the exchange while the
local cancel of the sibling is still in flight — both execute. Even IBKR's *native*
OCA only removes this risk with an `ocaType` "with block", which routes one order at
a time; `ocaType=3` carries the same exposure a local emulation does.
- **Naming the unsupported type as the primary order.** An emulated OCO whose plan
reports `primary_order_type=OCO` sends the caller back to the exact endpoint that
just failed the native-support check.
- **Exit legs on the wrong side of the market.** A "protective" stop above a long
entry is already through its trigger when the EMS registers it — it does not protect
the position, it closes it instantly at a loss.
- **Falsy-checking a price.** `if not stop_loss_price` reads `0` as "not supplied" and
plans a bracket with a silently missing leg instead of rejecting a bad price.
- **Iceberg slices below the venue floor.** Zerodha's iceberg takes 2–50 legs; Binance
enforces `LOT_SIZE` and a minimum notional. A slice below the floor is a guaranteed
rejection — pass `min_slice_qty` so the plan fails at planning time.
- **Slicing a market order and calling it an iceberg.** An iceberg is a resting limit
order with a restricted display size; market slices sweep the book instead. This is
why the venues offering it natively attach it to limit orders.
- **Treating "native TWAP" as unconditional.** Binance's TWAP lives only on the Algo
endpoints, bounded by duration and notional; IBKR's TWAP/VWAP IBALGOs are documented
for US equities. Both are "native" and neither is universal.
- **Losing emulated state.** If the EMS restarts without its legs, the emulated stops
never fire and nothing raises an error — the position is simply unprotected.
## Verification
- Run the unit suite and confirm every test passes:
`python -m unittest discover -s skills/broker-order-type-capability-matrix/scripts`
- Assert quantity conservation for an indivisible size: plan a 1000-unit TWAP in 7
slices and check `primary_quantity + sum(metadata["slice_schedule"])` equals exactly
1000. This is the highest-value single assertion in the suite.
- Assert an emulated OCO reports `primary_order_type is None` and
`has_primary_order is False`.
- Assert an inverted bracket (stop above target on a long) raises on the **native**
path too, not just the emulated one.
- Assert `BRACKET`+`BUY` and `OCO`+`BUY` accept opposite price orderings.
- Assert a `0` price is rejected rather than treated as absent.
- Assert `min_slice_qty` rejects a schedule whose slices fall below the venue floor.
- Assert `BrokerOrderCapabilityMatrix(custom_matrix={})` resolves no brokers at all.
- Spot-check each profile in `references/standards.md` against the broker's live
documentation before promoting to live trading.
## Related Skills
- `broker-agnostic-adapter-interface`
- `order-placement-idempotency`
- `execution-algo-twap-vwap-slicing`
- `iceberg-order-native-broker-support-vs-simulation`
- `minimum-fill-size-and-lot-rounding-logic`
- `paper-to-live-promotion-checklist`