Order Types¶
Learning objectives
After reading this page you will understand:
- What each of the eight order types does and when to use it
- The difference between aggressive (market/IOC/FOK) and passive (limit/iceberg) orders
- How stop-family orders trigger and what happens after trigger
- How time-in-force controls order lifetime across session phases
- How combo and OCO orders manage multi-leg strategy risk
Prerequisite: The Order Book — you should understand price-time priority before diving into order type mechanics.
Key point: priority is not keyed on the order's timestamp
Queue priority ("time priority" in price-time priority) is determined by
the engine-assigned arrival_seq, not by the client-supplied
timestamp field sent with the order. See Priority Rules
in the Order Amendment section below for the full explanation.
EduMatcher supports eight order types plus two multi-leg constructs (COMBO and OCO), from the simplest market order to a COMBO that trades a strategy across several symbols all-or-none and an OCO that links two exits on one symbol.
Contents¶
| Order type | Category | Key characteristic |
|---|---|---|
| MARKET | Aggressive | Immediate execution at best price; no price guarantee |
| LIMIT | Passive | Rests on book at specified price or better |
| STOP | Conditional | Dormant until trigger; then becomes MARKET |
| STOP_LIMIT | Conditional | Dormant until trigger; then becomes LIMIT |
| FOK | Aggressive | All-or-nothing; fill completely or cancel |
| IOC | Aggressive | Fill what you can immediately; cancel rest |
| ICEBERG | Passive | Large order with hidden reserve; visible peak only |
| TRAILING_STOP | Conditional | Dynamic stop that follows price by fixed offset |
| COMBO | Multi-leg | Multiple linked orders across symbols |
| OCO | Multi-leg | Two orders where fill/cancel of one cancels the other |
Choosing an order type¶
Pick the type from what you are trying to achieve, then check the trade-off you are accepting. Each type has its own section below with worked examples and a How traders use it subsection.
| I want to… | Use | What I give up |
|---|---|---|
| Get in or out now, whatever the price | MARKET | Price certainty (slippage) |
| Get in or out now, but never worse than a price | IOC | Guaranteed completion — the rest is cancelled |
| Trade a whole size or nothing | FOK | Partial fills |
| Name my price and wait | LIMIT | Execution certainty and queue position |
| Work a large size without showing it | ICEBERG | Speed; the replenished peak joins the back of the queue |
| Exit automatically if the market falls (or rises) to a level | STOP | Price certainty after the trigger |
| Same, but with a worst acceptable price | STOP_LIMIT | Execution certainty after the trigger |
| Protect a profit that keeps growing | TRAILING_STOP | A reversal of TRAIL size is needed before it acts |
| Trade a relationship between instruments | COMBO | Each leg must be priced; fills only complete together |
| Have exactly one of two exits happen | OCO | Both legs share one symbol and one quantity |
Fields each type takes¶
The same field rule is applied by every entry point (pm-alf-console,
pm-alf-gwy, BALF, REST) and again by the engine, so a mistake gets the
same reject code however it arrives. A required field that is absent is
rejected with MISSING_FIELD; a field the type does not use is rejected
with UNSUPPORTED_FIELD, never silently dropped.
| Type | Required | Optional | Notes |
|---|---|---|---|
MARKET |
— | — | Any PRICE= / STOP= is refused |
LIMIT |
PRICE |
— | |
STOP |
STOP |
— | PRICE= is refused — use STOP_LIMIT for a limit |
STOP_LIMIT |
STOP, PRICE |
— | |
FOK |
PRICE |
— | |
IOC |
PRICE |
— | A priceless IOC is not supported; use MARKET |
ICEBERG |
PRICE, VISIBLE |
— | VISIBLE must be smaller than QTY |
TRAILING_STOP |
TRAIL |
STOP |
Without STOP= the engine starts it from the last trade |
TIF= (default DAY) is accepted on every type; see
Time-in-Force. SMP= and TAG= are likewise common to
all single orders.
Behaviour by session phase¶
| Type | Continuous trading | Auction / pre-open (no matching) | Symbol halted |
|---|---|---|---|
MARKET, IOC, FOK |
Match on arrival | Rejected SESSION_NOT_PERMITTED — they cannot rest |
Rejected INSTRUMENT_HALTED / CIRCUIT_BREAKER_ACTIVE |
LIMIT, ICEBERG |
Match, then rest | Rest and are collected for the uncross (an iceberg's hidden quantity takes part, ranked behind its visible slice) | Rest without matching |
STOP, STOP_LIMIT, TRAILING_STOP |
Armed, or fired at once if already breached | Stored; evaluated after the uncross print | Stored |
Everything is rejected with MARKET_CLOSED when the session is CLOSED.
Single-order lifecycle¶
Every order follows this state model from submission to final disposition:
stateDiagram-v2
[*] --> NEW : order accepted
NEW --> PARTIAL : first partial fill
NEW --> FILLED : fills completely immediately
NEW --> CANCELLED : cancel request or cascade
NEW --> EXPIRED : TIF expiry (DAY at close, ATO/ATC at phase end)
NEW --> REJECTED : validation failure
PARTIAL --> FILLED : remaining quantity fills
PARTIAL --> CANCELLED : cancel request or cascade
PARTIAL --> EXPIRED : TIF expiry
FILLED --> [*]
CANCELLED --> [*]
EXPIRED --> [*]
REJECTED --> [*]
MARKET¶
Executes immediately at the best available price.
Background¶
Market orders are the oldest order instruction in listed markets. Before electronic books, brokers executed "at market" instructions on exchange floors by taking the best immediately available quote.
In modern matching engines, MARKET remains the purest urgency-driven order: it prioritizes immediacy over price certainty.
Motivation¶
Use MARKET when:
- Execution speed is more important than exact price.
- You are entering/exiting quickly in a liquid symbol.
- You are reducing directional risk and need certainty of fill.
How it behaves¶
- Sweeps the opposite side of the book (consumes resting orders from best price outward) as far as needed
- Partial fills are accepted — the unfilled remainder is discarded (not rested)
- Takes no
PRICE=; supplying one is rejected (UNSUPPORTED_FIELD) - Trades at each resting order's price, so a sweep produces one fill per price level touched and an average price worse than the best quote
The discarded remainder is not silently dropped. The order is accepted, and
what could not be filled arrives as a cancellation carrying
CANCEL_REASON=INSUFFICIENT_LIQUIDITY (cancel_reason on REST). A MARKET
order into an empty book is therefore an accepted order that immediately
cancels in full — not a rejection, and worth distinguishing when you write
a client:
Not shown in pm-alf-console
pm-alf-console's terminal display prints CANCELLED <order-id> without
the reason, even though the underlying message carries it. To see
CANCEL_REASON for yourself, connect over the real ALF TCP gateway
(pm-alf-gwy) with a raw client, or use the REST API's cancel_reason
field — see ALF TCP Gateway — Unsolicited cancels.
| Situation | What you receive |
|---|---|
| Book runs out during continuous trading | ACK ACCEPTED=TRUE, any FILLs, then CANCELLED with INSUFFICIENT_LIQUIDITY |
| Symbol halted, or the session is not matching | ACK ACCEPTED=FALSE with INSTRUMENT_HALTED, CIRCUIT_BREAKER_ACTIVE or SESSION_NOT_PERMITTED — rejected before the book is touched |
IOC behaves identically for its own remainder. FOK differs: it is
all-or-nothing, so an unfillable FOK is rejected with
INSUFFICIENT_LIQUIDITY rather than accepted and cancelled.
Gateway Syntax¶
Example — a market buy walks the book¶
Asks: 50 @ 150.10, 30 @ 150.20, 100 @ 150.50. You send the order above.
| Fill | Price | Qty |
|---|---|---|
| 1 | 150.10 | 50 |
| 2 | 150.20 | 30 |
| 3 | 150.50 | 20 |
Average price = (50·150.10 + 30·150.20 + 20·150.50) / 100 = 150.21, i.e.
11 cents (the slippage) worse than the 150.10 you saw when you clicked.
Had the book held only 80 shares, you would receive three fills for 80 and a
CANCELLED for the other 20.
Educational note: Market orders guarantee execution but not price. In a thin book (few resting orders, so liquidity is low), a large market order can move the price significantly — this is called slippage (the difference between expected execution price and actual average fill price).
How traders use it¶
- Emergency exits and hedges. When the position is the problem (a news shock, a margin call, a hedge that must be on before the next print), the cost of waiting exceeds the cost of slippage.
- Small size in a deep book. If your quantity is well inside the top level, a market order costs one half-spread and nothing else.
- Don't send large market orders into a thin book or just after an announcement, when the spread is widest. Prefer an IOC with a limit a few ticks through the touch — a protected market order that takes what is there but refuses a runaway price.
- Market orders never rest, so they are the one type you cannot send during an
auction call phase; use a marketable
LIMITthere.
LIMIT¶
Executes at the specified price or better; any unfilled portion rests on the book (remains posted as a passive order, waiting for a future counterparty to trade against it).
Background¶
LIMIT orders are the foundation of central limit order books (CLOBs). They define displayed liquidity and form the visible bid/ask ladder used by all other participants to price risk and route flow.
Motivation¶
Use LIMIT when:
- Price control is more important than immediate execution.
- You want to provide liquidity and potentially earn spread capture.
- You are comfortable waiting in queue under price-time priority.
How it behaves¶
- Buy LIMIT: fills at
priceor lower - Sell LIMIT: fills at
priceor higher - Becomes a resting order if not immediately matchable
- A marketable limit (one that crosses the book) trades first, at the resting orders' prices, then rests whatever is left at its own limit
- Subject to the symbol's price collar and tick size: a price too far from
the reference, or off the tick grid, is rejected (
COLLAR_BREACH,TICK_VIOLATION) - Can be amended in place (the only type besides ICEBERG)
Gateway Syntax¶
NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.50
NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.50|TIF=GTC
Examples¶
Passive. Best bid 150.40, best ask 150.60. BUY LIMIT 100 @ 150.50 does
not cross, so it rests inside the spread as the new best bid. Everyone now
sees 150.50 as the price to hit.
Marketable. Asks are 40 @ 150.60 and 100 @ 150.70. BUY LIMIT 100 @ 150.65
buys 40 @ 150.60 (price improvement over your limit), cannot touch 150.70, and
rests the remaining 60 as a bid at 150.65.
Educational note: Limit orders guarantee price but not execution. In a thin book (few resting orders, so liquidity is low), a large limit order may not fill immediately or at all, especially if the limit price is far from the current market price.
How traders use it¶
- Providing liquidity / capturing the spread. Resting a bid and an offer around the mid earns the spread when both fill; this is what market makers do (see Market Making).
- Queue position matters. At one price, earlier
arrival_seqfills first. Joining the best bid early beats improving the price by one tick late — but reducing quantity keeps your place, while a price change or a quantity increase sends you to the back (see Amendment). - Price discipline. A buy limit under the market is a patient entry; a
sell limit above the market is a take-profit target. With
TIF=GTCit works across days until you cancel it. - Improving vs joining. Stepping one tick ahead of the best bid buys priority at the cost of a tick; sitting at the touch costs nothing but time.
STOP (Stop-Market)¶
A dormant order that activates when the market touches the stop price, then executes as a MARKET order.
Background¶
STOP orders became common as risk-management tools for both discretionary traders and portfolio overlays. They were designed to automate exits without requiring constant manual monitoring.
Motivation¶
Use STOP when:
- You need a hard, automated risk cutoff (stop-loss).
- You want to enter on momentum confirmation (breakout stop-entry).
-
Guaranteed execution after trigger matters more than trigger-to-fill price precision.
-
STOPprice is the trigger, not the execution price - Buy STOP: triggers when last trade price ≥ stop price (used to enter long on breakout)
- Sell STOP: triggers when last trade price \(\leq\) stop price (used to limit downside loss)
Gateway Syntax¶
Takes STOP= only; PRICE= is rejected. While dormant the order is in no
price level and appears in no depth, and it can be cancelled but not amended.
Full trigger rules are in Stop Trigger Logic.
Educational note: Once triggered, a STOP becomes a MARKET order — execution price is not guaranteed, especially in fast markets.
Example — protective sell stop¶
You bought 100 AAPL at 150.00 and will accept a 2-dollar loss. Last trade is 150.30.
Nothing happens while trades print above 148.00. When a trade prints at
148.00 or lower, the order becomes MARKET SELL 100 and hits the best bids.
If bids are 60 @ 147.90 and 200 @ 147.50, you sell 60 @ 147.90 and 40 @
147.50 — an average of 147.74, below the 148.00 you chose.
If you place a sell stop at or above the last trade price (say STOP=151.00
with the last trade at 150.30) it is already breached and fires
immediately — a stop is not a resting limit order at that price.
How traders use it¶
- Stop-loss. The standard use: cap the loss on a long (sell stop below the market) or a short (buy stop above it) without watching the screen.
- Breakout entry. A buy stop above resistance enters only if the market proves it can trade there; see Why does a BUY STOP trigger at a price higher than the stop?.
- Know the failure mode. A stop is a trigger, not a price. In a gap or a thin book it fills far through the stop level, and a cluster of stops at one level can push the price into the next cluster (a cascade). Where that risk is unacceptable, use STOP_LIMIT; where the level should follow a rising market, use TRAILING_STOP.
STOP_LIMIT¶
Like STOP, but once triggered converts to a LIMIT order at a specified limit price.
Background¶
STOP_LIMIT was introduced to address a classic STOP weakness: in fast or thin markets, trigger-to-fill slippage can be large. By adding a limit cap, traders can constrain worst-case execution price after trigger.
Motivation¶
Use STOP_LIMIT when:
- You need trigger automation but still require explicit price protection.
- You can tolerate non-fill risk after trigger.
-
You want to avoid executing through temporary price spikes/gaps.
-
Provides price protection after trigger, but may not fill if the market moves past the limit
- Requires both
STOP=(trigger) andPRICE=(limit after trigger)
Gateway Syntax¶
Educational note: The gap between stop price and limit price is the "insurance premium" against market impact. If the market falls through 147.50 before the order fills, it remains unexecuted as a resting limit.
Example — gap through the limit¶
Last trade 150.00. You hold 100 AAPL and place the sell above, STOP=148.00,
PRICE=147.50. Overnight news moves the market and the next trade prints at
146.00 against bids that are all at 146.00 or lower.
- Triggered: 146.00 ≤ 148.00, so the order becomes
LIMIT SELL 100 @ 147.50. - No bid is at 147.50 or higher, so nothing fills. The order rests as the best offer at 147.50, above the market, while the price keeps falling.
A plain STOP would have sold at about 146.00 and left you flat; the STOP_LIMIT protected the price and left you still long, in a falling market. If the market later rallies back to 147.50 the resting limit fills there.
How traders use it¶
- Controlled exits in thin or gapping markets. When a few ticks of slippage would defeat the point of the exit, the limit caps it.
- Sizing the gap. A tight gap (
STOP=148.00,PRICE=147.95) is nearly a stop-market that may miss; a wide gap behaves like a STOP in orderly markets and only bites in a gap. Choose it from how far the instrument typically moves between prints. - Entries. A buy stop-limit above resistance enters on the breakout but refuses to chase a spike beyond the limit.
- Remember the leftover. After the trigger it is an ordinary resting limit that must be watched, amended or cancelled. It stays at the back of the queue at that price (it receives a new queue position when it rests).
FOK (Fill-or-Kill)¶
Must be entirely filled immediately or the whole order is cancelled.
Background¶
FOK comes from block-trading workflows where partial execution can break a strategy's economics. It is common in institutional routing logic and cross-venue execution algos that need all-or-nothing completion.
Motivation¶
Use FOK when:
- Partial fills are unacceptable.
- You need immediate certainty of complete size.
-
The order is one leg of a tightly coupled strategy and partial execution introduces exposure.
-
Checks total available quantity at the given price before executing
- The pre-check includes hidden iceberg reserve quantity (not just the displayed peak) and excludes same-gateway resting liquidity that self-match prevention would skip — so a FOK can reject even when the book's displayed total looks sufficient, if most of it belongs to your own gateway
- If
available_qty < order_qty→ rejected entirely (no partial fills ever) - If sufficient → executes in full immediately
Gateway Syntax¶
Educational note: FOK is used when partial fills are unacceptable — e.g. algorithmic strategies needing an exact block to execute a multi-leg trade atomically.
How a failed FOK is reported depends on why it failed:
| Cause | Result |
|---|---|
Not enough quantity at or better than PRICE |
One ACK ACCEPTED=FALSE, INSUFFICIENT_LIQUIDITY; nothing traded |
The shortfall is only because your own orders are in the way, and SMP=CANCEL_AGGRESSOR or CANCEL_BOTH |
Accepted, then CANCELLED with SELF_MATCH_PREVENTED |
| Auction call phase | Rejected SESSION_NOT_PERMITTED |
With SMP=CANCEL_RESTING or CANCEL_BOTH the same-gateway resting orders in
the FOK's path are cancelled even when the FOK itself is then rejected for
lack of liquidity — a FOK is not a free way to test a book you share with
yourself.
Examples¶
Asks: 60 @ 150.00, 50 @ 150.10, 100 @ 150.30.
BUY FOK 100 @ 150.10— only 110 are available at or below 150.10, so the order fills as 60 @ 150.00 + 40 @ 150.10, in one go. FILLED.BUY FOK 200 @ 150.10— only 110 available. Rejected, no trade; the book is untouched.BUY FOK 100 @ 150.00— only 60 available at that price. Rejected, although more is offered one tick higher; widenPRICE=to include it.
How traders use it¶
- Atomic block trades. If 10,000 shares are needed for a hedge to work, eight thousand is not "most of the trade", it is a position you didn't want.
- Strict liquidity test. A FOK either gets the whole size at your price or tells you immediately, with no market footprint, that it isn't there — unlike an IOC, which takes whatever it can and signals your interest.
- A leg that must not be partial. When one leg's fill triggers another trade, partial execution leaves unhedged exposure.
- Price it deliberately. A FOK limited to the best ask only succeeds if
the top level alone covers you. Setting
PRICE=a few ticks through the touch lets it consume depth while still capping the price you will pay.
ICEBERG¶
A large LIMIT order that hides most of its size, showing only a visible_qty "peak"
at any one time.
Background¶
ICEBERG orders were designed for institutional execution where displaying full size can move the market against the trader. The displayed "tip" reveals only a small slice while preserving the same total parent quantity.
Motivation¶
Use ICEBERG when:
- You need to execute large notional size with reduced information leakage.
- You want to rest passively without broadcasting your full intent.
-
You accept slower completion in exchange for lower signaling risk.
-
Only
VISIBLE=quantity is published to the order book - Once the visible peak is fully consumed, a new peak is replenished from the hidden quantity
- Each new peak gets a new
arrival_seq→ goes to the back of the queue at that price level (because the book uses price-time priority: among orders at the same price, the order with the lowestarrival_seq— i.e. the one the engine admitted first — gets filled first; a fresharrival_seqmeans this peak waits behind all other orders already resting at that price) - The total hidden size is never visible to other market participants
- An incoming iceberg that crosses the book trades its full quantity
immediately, like a LIMIT — one execution per resting counterparty, not
one per peak. Only the remainder that rests is hidden: it rests with a full
peak of
min(VISIBLE, remaining)
Priority is keyed on arrival_seq, not the order's timestamp
arrival_seq is a monotonic counter the engine assigns when it
admits an order into the book — it is not the client-supplied
timestamp field on the order. See Priority Rules
below and Order Amendment — AMEND
for the full explanation of why this distinction matters.
Gateway Syntax¶
Educational note: This is how large institutional orders are worked into the market
without telegraphing their full size. Watch the book viewer as the iceberg refills — the
qty at that price level resets to 100 after each 100-lot fill. The order ID stays the
same, but its arrival_seq is refreshed (new arrival_seq = new queue position — the
order's original client timestamp does not change).
VISIBLE must be smaller than QTY (an iceberg showing everything is just a
LIMIT) and both PRICE= and VISIBLE= are required.
Example — working 1,000 shares with a 100-share peak¶
Bids: 300 @ 150.00 (other traders, ahead of you in the queue).
- Rests with a 100 peak. The book shows
400 @ 150.00(300 + your 100). The other 900 are known only to the engine. - Sellers arrive. A
SELL 350 @ 150.00fills the 300 ahead of you, then your 50 of the peak. Your peak now has 50 left showing. - Peak exhausted. Meanwhile another trader has bid 70 @ 150.00. A
SELL 50takes your last 50. The peak is replenished to 100 shown, at the back of the queue — behind that 70 — and the book now shows170 @ 150.00, with 900 of your 1,000 still to trade. - Repeat until all 1,000 have traded.
FILLmessages report each execution against the same order ID.
If the same order had instead arrived while 150 shares were offered at 150.00, it would first trade those 150 in full — as any limit order does — and rest only the remaining 850, behind a fresh 100 peak.
An iceberg can be amended. A price change or a quantity increase refills the peak and loses priority; a quantity decrease keeps priority and can only shrink the current peak.
How traders use it¶
- Reducing information leakage. A visible 1,000-share bid invites others to trade ahead of you or to lean on the price. A 100-share peak looks like ordinary flow.
- Choosing the peak. Too small and your queue position resets constantly (each refill goes to the back) and a pattern of identical 100-lot refills reveals the iceberg; too large and you lose the point. Peaks comparable to the typical visible size at the level blend in best.
- Cost of hiding. You trade slower than a displayed order of the same size would: the hidden part has lowest priority at the price, and other participants' displayed orders get filled first.
- In auctions the hidden quantity counts toward the uncross, so an iceberg is not penalised in size terms for being in an opening or closing call.
COMBO¶
A multi-leg order that bundles two or more child orders across different symbols into a single atomic instruction. The child orders are linked: they are submitted, tracked, and cancelled as a group.
Background¶
COMBO orders are the exchange-native answer to legging risk in spread, pairs, and relative-value strategies. Instead of managing legs manually, the matching system keeps lifecycle and cancellation coupling explicit.
Motivation¶
Use COMBO when:
- Strategy PnL depends on a relationship between instruments, not one symbol.
- You want coordinated child lifecycle and automatic cascade-cancel.
-
You need operational safety against partial strategy completion.
-
Each leg is posted to its own symbol's book as an ordinary order — a
LIMITunlessLEG<i>.TYPE=says otherwise (see below) - 2 to 10 legs, each on a different symbol
- All legs are tracked together under a parent combo ID
- If any leg is cancelled or expires, all remaining legs are automatically cascade-cancelled (unfilled quantities only — fills already executed are not reversed)
- The combo is considered MATCHED only when all legs are fully filled
Educational note: COMBO is not a synthetic construct built on top of regular orders — it is a first-class order type with native engine support for multi-leg lifecycle management and cascade cancellation.
Gateway Syntax¶
NEW|TYPE=COMBO|COMBO_ID=PAIR-001|COMBO_TYPE=AON|TIF=GTC|LEG_COUNT=2|LEG0.SYM=MSFT|LEG0.SIDE=BUY|LEG0.QTY=100|LEG0.PRICE=415.00|LEG1.SYM=AAPL|LEG1.SIDE=SELL|LEG1.QTY=100|LEG1.PRICE=210.00
| Field | Meaning |
|---|---|
TYPE=COMBO |
Identifies this as a multi-leg order |
COMBO_ID |
Your human-readable tracking label |
COMBO_TYPE=AON |
All-or-none: combo succeeds only when every leg fills completely |
LEG_COUNT |
Number of legs (2–10) |
LEG<i>.SYM |
Symbol for leg i |
LEG<i>.SIDE |
BUY or SELL for leg i |
LEG<i>.QTY |
Quantity for leg i |
LEG<i>.TYPE |
Optional; defaults to LIMIT. One of LIMIT, MARKET, STOP, STOP_LIMIT, FOK, IOC |
LEG<i>.PRICE |
Limit price for leg i (required for LIMIT, FOK, IOC, STOP_LIMIT) |
LEG<i>.STOP |
Trigger price for a STOP / STOP_LIMIT leg |
ICEBERG and TRAILING_STOP cannot be combo legs (a leg has no VISIBLE or
TRAIL); the combo is rejected with INVALID_VALUE. A combo's legs are
collared like ordinary orders, and the whole combo is rejected if two legs
share a symbol.
All-or-none in practice. Before posting any leg the engine checks that
every leg could fill in full. If one is short and nothing would trade right
now, all the legs rest as pending interest; if one is short but another leg
could already trade, the combo is rejected (INSUFFICIENT_LIQUIDITY) with
nothing posted, so you are never left holding half the structure.
Educational note: The COMBO_TYPE field is reserved for future expansion. Currently only AON (All-Or-Nothing) is supported, meaning the combo is successful only if every leg fills in full. In the future, we may add ANY (combo succeeds if at least one leg fills) or MIN (combo succeeds if a minimum number of legs fill).
Combo lifecycle¶
REJECTED
▲
(validation)
PENDING ──────► PARTIALLY_MATCHED ──────► MATCHED
│ │
│ ▼
└──────────────► FAILED (cascade-cancel: a child order cancelled or expired)
PENDING or PARTIALLY_MATCHED ──► CANCELLED (explicit CANCEL|COMBO_ID=)
Cancelling a combo¶
Cancels all unfilled legs. Fills already executed are irrevocable.
Educational note: Combos enable relative-value strategies (pairs trades, hedged entries, statistical arbitrage) where the profit depends on the relationship between instruments, not on any single instrument's direction. Without combos, submitting legs separately exposes you to leg risk — one side fills while the other does not, leaving you with unintended directional exposure. See the Combo Orders page for full details, examples, and the implied orders mechanism.
Example — a pairs trade¶
You think MSFT is rich relative to AAPL. You want to sell 100 MSFT at 415.00 and buy 200 AAPL at 210.00, and you want both or neither:
NEW|TYPE=COMBO|COMBO_ID=RV-1|COMBO_TYPE=AON|TIF=DAY|LEG_COUNT=2|LEG0.SYM=MSFT|LEG0.SIDE=SELL|LEG0.QTY=100|LEG0.PRICE=415.00|LEG1.SYM=AAPL|LEG1.SIDE=BUY|LEG1.QTY=200|LEG1.PRICE=210.00
Submitted separately, a MSFT fill with no AAPL fill leaves you short one
stock, outright, which is the risk you wanted to avoid. As a combo, the engine
first checks that both legs could fill in full; if they can, both trade at
once. If not, both legs rest. Either way, if one leg is cancelled or expires
the other is cascade-cancelled, and the combo is MATCHED only when both
have filled.
How traders use it¶
- Pairs / relative value. Profit comes from the spread between two names converging, not from either name's direction.
- Hedged entries. Buy the stock and sell the index or a related future in the same instruction, so you are never naked.
- Calendar spreads. Buy one expiry and sell another at a target difference.
- Baskets. Up to ten legs build a small basket in one order.
- Know the limits. When the legs cannot all fill at once they rest as
separate orders in their own books, and one can trade before the other
(status
PARTIALLY_MATCHED). Price each leg so that the relationship is still worth having if only that leg fills for a while, and cancel the combo (CANCEL|COMBO_ID=) to withdraw the unfilled legs.
Time-in-Force (TIF)¶
TIF (Time-in-Force) is not an order type; it is a set of instructions that specify how long an order remains active before it is executed or expires.
Time-in-force controls how long an order remains active before the engine automatically expires or cancels it.
Background¶
TIF instructions standardize order lifetime semantics across venues. They let participants express not only how to trade (order type) but for how long that instruction is valid under changing session states.
Motivation¶
Use TIF to align execution intent with horizon:
DAYfor session-local participation.GTCfor persistence across restarts/sessions.ATO/ATCfor auction-specific participation windows.FOKorder type when immediacy and completeness must be coupled (note:FOKis an order type, not aTIF=value).
| Value | Meaning |
|---|---|
DAY (default) |
Valid for the current trading session only. Not discarded at engine shutdown — it is persisted (alongside GTC orders) and restored on the next startup, dropped there only if the business day has since moved on. True expiry happens when the session transitions to CLOSED. |
GTC |
Good-Till-Cancelled. Persisted to data/gtc_orders.json at shutdown and reloaded next session. Survives across trading days. |
ATO |
At-The-Open. Only accepted during the OPENING_AUCTION phase. Automatically expired when the opening auction ends (transition to CONTINUOUS). |
ATC |
At-The-Close. Only accepted during the CLOSING_AUCTION phase. Automatically expired when the closing auction ends (transition to CLOSED). |
FOK is an order type, not a TIF
TIF=FOK is not valid and will be rejected by the gateway. FOK semantics
(fill entirely or cancel) are specified via TYPE=FOK, not TIF=FOK.
The ATO / ATC window checks apply whenever trading sessions are
configured; an order carrying one outside its window is rejected with
SESSION_NOT_PERMITTED. They are meant for LIMIT (and ICEBERG) orders: a
MARKET, IOC or FOK cannot rest in an auction at all.
Examples¶
# Participate in the opening price, then disappear
NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=500|PRICE=151.00|TIF=ATO
# Day-only resting bid
NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=149.50
# Standing protective stop that survives the close
NEW|SYM=AAPL|SIDE=SELL|TYPE=STOP|QTY=100|STOP=145.00|TIF=GTC
The first order is collected in the opening auction. If it fills at the
uncross price, fine; any unfilled quantity is EXPIRED the moment the auction
ends and never reaches continuous trading.
How traders use it¶
DAYfor tactical orders. Anything whose reason lapses at the close — a bid placed on today's news, a quote-chasing order — should expire with the session so it cannot trade tomorrow on stale information.GTCfor standing instructions. Take-profit targets, protective stops and trailing stops should outlive the session; they are saved at shutdown and restored on the next start. You must cancel them yourself, and review them after corporate events or gaps.ATOto trade the open. A limit order priced generously for its side (a high bid, a low offer) ranks first when the uncross allocates volume and trades at the single equilibrium price — never worse than your limit, and often better. If the auction is imbalanced on your side, you may still be filled only in part.ATCto hit the benchmark. Funds and index trackers are measured against the closing price; sending to the closing auction gets that price with no market-impact race against the close.
Stop Trigger Logic¶
Stop-family orders (STOP, STOP_LIMIT, and TRAILING_STOP) are
event-driven: they do not trigger from top-of-book quotes, midpoint,
or best bid/ask changes. They trigger from the symbol's last executed
trade price.
Why last trade price?¶
Using last trade price aligns trigger events with actual executions, which reduces false triggers that can occur from ephemeral quote flickers. It also makes replay and audit deterministic: every trigger can be traced to a trade event in the execution stream.
Trigger Conditions¶
Stops are evaluated after every trade event:
| Order Side | Trigger Condition |
|---|---|
| BUY STOP | last_trade_price >= stop_price |
| SELL STOP | last_trade_price <= stop_price |
Exact evaluation rules¶
The rules below pin down the cases the overview leaves open. They hold for
STOP, STOP_LIMIT and TRAILING_STOP alike.
- On entry. A stop whose condition is already met by the last trade price when it arrives triggers immediately instead of waiting for the next trade. (While the market is not matching — pre-open, an auction call, a halt — the entry check is skipped and the stop is simply stored; it is evaluated after the next trade, normally the uncross print.)
- Once per incoming order. Stops are evaluated after the incoming order has finished matching, against the price of its last execution — not after each individual print of a multi-level sweep. This matters for a trailing stop: it ratchets once, to the sweep's final price.
- Several at once. When one evaluation triggers several stops they execute one after another: triggered BUY stops first (lowest stop price first, then in arrival order), then SELL stops (highest stop price first, then in arrival order), then trailing stops in arrival order. Stops triggered by those executions queue behind them and run in turn. The same holds for the stops an auction's uncross print triggers.
Why does a BUY STOP trigger at a price higher than the stop?
It seems backwards — normally you want to buy low. The key is that a BUY STOP is not a value-buying tool; it is a momentum confirmation tool. The stop price acts as a breakout threshold: the trader is saying "I don't want to buy unless the market proves it can trade at or above this level."
Common use cases:
- Breakout entry: A stock has been consolidating below $150 (resistance). A trader places a BUY STOP at $150.01. If the price breaks through and prints at or above that level, it signals that sellers are exhausted and momentum is upward — the trader enters only after that confirmation.
- Short-squeeze protection: A trader is short and places a BUY STOP above the current price as an automated exit. If the market rises against their position past the stop, the buy triggers to close (cover) the short before losses grow larger.
- Trend-following systems: Algorithms that "buy strength" deliberately wait for a new high to be set before entering, treating the breakout level as the entry signal rather than a price they'd prefer to avoid.
The counterpart, SELL STOP (last_trade_price <= stop_price), is the more
intuitive stop-loss: trigger a sell when the market falls to or through a
floor price. BUY STOP is simply the mirror image applied to the upside.
Lifecycle After Trigger¶
- The stop leaves its dormant container (
_buy_stops,_sell_stops, or_trailing_stops). - The engine converts it:
STOP->MARKETSTOP_LIMIT->LIMIT(retains the originalPRICE=)TRAILING_STOP->MARKET(using current ratchetedstop_pricefor trigger only)- The converted order is re-processed through normal matching logic.
- It is treated as new from that moment: a
STOP_LIMITwhose limit rests gets a fresharrival_seq(queue position) when it does — it does not inherit any priority from when it was placed. A dormant stop has none, since it is not on the visible book.
This means a triggered stop behaves exactly like a newly submitted active order of its converted type.
Session-Phase Interaction¶
- In non-matching phases, stops can be accepted and stored.
- Trigger checks are tied to trade events, so without trades there are no triggers.
- On phase transitions that produce uncross trades, stop evaluations run after those trade events and can create cascades.
Trigger Cascades¶
A single aggressive trade can trigger multiple dormant stops:
- Trade updates
last_trade_price. - One or more stops become trigger-eligible.
- Converted orders are injected and may execute immediately.
- Those executions update
last_trade_priceagain. - Additional stops may now trigger.
This cascade is expected behavior in volatile moves and is why stop-heavy books can accelerate directional momentum.
Worked Scenarios¶
Scenario A — SELL STOP trigger and slippage¶
Gateway message:
- Dormant order:
SELL STOP qty=100 stop=148 - Last trade before event:
149.50 - Next execution prints at
147.20.
Trigger check: 147.20 <= 148 -> true, so order converts to MARKET SELL.
Fill can occur below 148 depending on available bids (slippage).
Scenario B — STOP_LIMIT trigger without fill¶
Gateway message:
- Dormant order:
SELL STOP_LIMIT qty=100 stop=148 limit=147.50 - Trade prints at
147.80(triggered) - Best bid drops to
146.90before order can match.
Order converts to LIMIT SELL at 147.50 and rests unfilled until a buyer meets that price.
Scenario C — Multiple stops fire on one print¶
Gateway messages (three separate dormant orders):
NEW|SYM=AAPL|SIDE=SELL|TYPE=STOP|QTY=100|STOP=149.00
NEW|SYM=AAPL|SIDE=SELL|TYPE=STOP|QTY=100|STOP=148.00
NEW|SYM=AAPL|SIDE=SELL|TYPE=STOP|QTY=100|STOP=147.00
Dormant sell stops: 149, 148, 147.
If a trade prints at 147.00, all three satisfy last_trade_price <= stop_price.
All convert in sequence and are processed as active orders.
Practical Notes¶
- Trigger is binary;
stop_priceis not an execution guarantee. - STOP prioritizes fill certainty over price certainty.
- STOP_LIMIT prioritizes price control over fill certainty.
- TRAILING_STOP adds adaptive trigger movement but still converts to MARKET on trigger.
IOC (Immediate-Or-Cancel)¶
Background¶
Immediate-Or-Cancel (IOC) is a time-in-force discipline that has been a fixture of electronic trading since the early days of ECNs (Electronic Communication Networks) in the 1990s. It was developed to give traders a way to "take what is available right now" without inadvertently posting a resting order that could fill much later at a stale price.
The Chicago Mercantile Exchange (CME), NYSE, and most modern exchanges support IOC natively. It is especially important for algorithmic and high-frequency traders who cannot afford the risk of an old order sitting on the book after a quote has moved.
Motivation¶
Use IOC when:
- You need to sweep available liquidity at or better than your limit price, but you are not willing to wait for the market to come to you.
- You want to avoid being "on the hook" for shares if the price moves away between submission and fill.
- You are executing a leg of a larger strategy and a partial fill is acceptable, but a resting order is not.
IOC vs LIMIT: A LIMIT order rests on the book if it does not fill
immediately. An IOC order never rests — the unfilled portion is
cancelled before control returns to the caller.
IOC vs FOK: FOK (Fill-Or-Kill) requires the entire quantity to fill
immediately or it is rejected. IOC accepts partial fills, cancelling only
what could not be filled.
How It Works¶
- The engine receives the
IOCorder. - If the session is in a non-matching phase (auction), the order is rejected immediately — IOC cannot rest on the book.
- During continuous trading, the engine sweeps the opposite side of the book, filling against resting orders at progressively worse prices up to the IOC limit price.
- After the sweep completes (or if no liquidity was available), any
remaining quantity is cancelled and a
CANCELLEDevent is published. - The IOC order is never added to the resting order book.
Educational note: IOC is a pure liquidity-taking instruction. It is designed to consume whatever is available at or better than the limit price, but it does not wait for liquidity to appear. If the book is empty or all orders are above (for BUY) or below (for SELL) the limit, the IOC will simply cancel itself without any fills.
Examples¶
Example 1 — Full fill¶
Gateway message:
Book state (asks):
Result: - Trade: 10 × 100.00 - IOC order: FILLED - No resting order added to the book
Example 2 — Partial fill, remainder cancelled¶
Gateway message:
Book state (asks):
Result: - Trade: 5 × 100.00 - IOC order: remaining 5 units CANCELLED - 5 asks at 101.00 remain on the book
Example 3 — No fill, entire order cancelled¶
Gateway message:
Book state (asks): empty (or all asks above limit)
Result: - No trade - IOC order: all 10 units CANCELLED immediately
Example 4 — Multi-level sweep¶
Gateway message:
Book state (asks):
Result: - Trade: 3 × 99.00, 3 × 100.00, 3 × 101.00 - IOC order: FILLED - 3 asks at 102.00 remain untouched
Gateway Syntax¶
An IOC never rests, so TIF=DAY and TIF=GTC make no difference to it — it
is always immediate-or-cancel by definition. TIF=ATO / TIF=ATC are not
ignored, though: the session-window check still applies, and an IOC that
carries one is rejected with SESSION_NOT_PERMITTED (in continuous trading
because the window is closed, in the auction itself because IOC cannot rest).
Leave TIF= off.
Edge Cases and Gotchas¶
| Scenario | Behaviour |
|---|---|
| Submitted during opening/closing auction | REJECTED SESSION_NOT_PERMITTED — IOC cannot rest |
| Symbol halted | REJECTED INSTRUMENT_HALTED or CIRCUIT_BREAKER_ACTIVE |
| Self-match (same gateway on both sides) | SMP rule applies to the IOC fill attempt |
| Price limit not specified | REJECTED MISSING_FIELD — IOC requires a PRICE= |
| Unfilled remainder | CANCELLED with CANCEL_REASON=INSUFFICIENT_LIQUIDITY (the order itself was accepted) |
How traders use it¶
- Protected market order.
BUY IOC 500 @ best_ask + 3 tickstakes everything up to three ticks through the touch and refuses worse prices — a market order with a seat belt. - Taking a visible quote without leaving a footprint. If a displayed offer is attractive but may be gone in a millisecond, an IOC at that price takes what is still there and leaves nothing for others to see or trade against.
- Sweeping to a price cap. To buy "up to 99.00" across several levels, one IOC at 99.00 is cleaner than repeated limit orders.
- Slicing algorithms. Execution algos fire an IOC per slice and size the next one from the fill; any residue is reported at once instead of lingering as stale interest.
- Retry loop. After a partial fill you decide what to do with the remainder — re-price an IOC, rest a LIMIT, or stop — rather than hoping a resting order catches up.
- IOC vs FOK. Choose IOC when a partial fill is useful and FOK when it is not.
TRAILING_STOP¶
Background¶
A trailing stop is a stop order whose trigger price automatically moves in the trader's favour as the market moves favourably — but never moves against the trader. It was popularised by retail brokerages in the 1990s and 2000s as a way to "let profits run while cutting losses".
Modern exchange systems (CME, NASDAQ, CBOE) all support native trailing stops. They are especially useful for momentum strategies and position management when a trader wants to stay long/short for as long as the trend continues, but exit automatically once a reversal of a given magnitude occurs.
Motivation¶
Use a trailing stop when:
- You want to protect a profit without capping the upside. A regular stop is static — once set at, say, $98, it stays at $98 even if the stock rallies to $130. A trailing stop at $2 behind would move up to $128 automatically.
- You are managing a position and cannot monitor it continuously.
- You want a systematic, emotion-free exit strategy.
Trailing stop vs regular STOP: A regular stop has a fixed trigger price. A trailing stop has a dynamic trigger price that ratchets in one direction (up for SELL, down for BUY) with the market.
Gateway Syntax¶
How It Works¶
The trailing stop maintains two values:
trail_offset(TRAIL=): a fixed price distance from the market.stop_price: the current trigger level, updated after each trade.
SELL trailing stop (protecting a long position)¶
- Goal: trigger only if the price falls by
trail_offsetfrom its peak. - After each trade at price P:
- New candidate stop: \(P - \text{trail\_offset}\)
- stop_price is updated only if the candidate is higher (ratchet upward)
- Trigger condition: \(P \leq \text{stop\_price}\)
BUY trailing stop (protecting a short position)¶
- Goal: trigger only if the price rises by
trail_offsetfrom its trough. - After each trade at price P:
- New candidate stop: \(P + \text{trail\_offset}\)
- stop_price is updated only if the candidate is lower (ratchet downward)
- Trigger condition: \(P \geq \text{stop\_price}\)
On trigger¶
The trailing stop is removed from the pending list and converted to a MARKET order, which is then processed immediately in the same trade cycle.
Initial stop price¶
If STOP= is supplied, that is the initial stop_price. If omitted, the
engine computes the initial stop from last_trade_price ± trail_offset.
If there is no last trade and no explicit STOP=, the order is rejected.
Examples¶
Example 1 — SELL trailing stop: price rallies then drops¶
Gateway message:
Setup: last trade price = 100. SELL trailing stop: trail=2, stop=98.
Trade at 105 → stop ratchets up → stop = max(98, 105−2) = 103
Trade at 110 → stop ratchets up → stop = max(103, 110−2) = 108
Trade at 102 → 102 $\leq$ 108? YES → TRIGGERED → MARKET SELL
The trailing stop locked in a floor of 108 as the stock rallied to 110. When the stock dropped back to 102, the stop fired.
Example 2 — SELL trailing stop: stop never moves down¶
Gateway message:
stop = 98, trail = 2, last_trade = 100
Trade at 106 → stop = max(98, 104) = 104
Trade at 95 → 95 $\leq$ 104? YES → TRIGGERED
Note that the trade at 95 did not move the stop down to 93: the candidate (95 − 2 = 93) is lower than the current stop, so the ratchet leaves it at 104, and 95 ≤ 104 triggers. The market gapped from 106 to 95 without printing near 104, so the resulting MARKET SELL goes out well below the stop level — slippage on the stop trigger.
Example 3 — BUY trailing stop: price falls then rises¶
Gateway message:
Setup: last trade = 50. BUY trailing stop: trail=3, stop=53.
Trade at 45 → stop ratchets down → stop = min(53, 45+3) = 48
Trade at 40 → stop ratchets down → stop = min(48, 40+3) = 43
Trade at 44 → 44 ≥ 43? YES → TRIGGERED → MARKET BUY
The stop followed the price down from 53 to 43. When the price started recovering and crossed 43, the buy was triggered.
Example 4 — Cancelling a trailing stop before trigger¶
A trailing stop can be cancelled at any time before trigger using its
order_id. The engine marks the order CANCELLED, publishes an
order.cancelled event, and drops it from the pending trailing-stop
list the next time the list is evaluated (lazy deletion). A trailing stop
cannot be amended.
Example 5 — Trailing stop that starts from the last trade¶
Last trade is 150.00 and no STOP= is given, so the engine sets the initial
stop at 150.00 − 2.00 = 148.00. Prints at 151, 153 and 152 move the stop
to 149, 151 and leave it at 151; a print at 151 (or below) then fires a
MARKET SELL. A trailing stop with no explicit STOP= and no prior trade in
the symbol is rejected (MISSING_FIELD).
If you supply a STOP= that is already breached by the last trade (for a
SELL, STOP= at or above it), the order fires on entry rather than waiting.
Gateway Syntax¶
# With explicit initial stop
NEW|SYM=AAPL|SIDE=SELL|TYPE=TRAILING_STOP|QTY=100|TRAIL=2.00|STOP=148.00
# Without explicit stop — engine computes from last trade price
NEW|SYM=AAPL|SIDE=SELL|TYPE=TRAILING_STOP|QTY=100|TRAIL=2.00
Edge Cases and Gotchas¶
| Scenario | Behaviour |
|---|---|
No TRAIL= supplied |
REJECTED |
No STOP= and no prior trade |
REJECTED (engine cannot compute initial stop) |
| Submitted during auction phase | Accepted — rests until the auction uncroses, then evaluated on next trade |
Large trail_offset relative to price |
Stop may never trigger if the market doesn't move that far |
| Triggered during uncross | The resulting MARKET order participates in the uncross matching round |
| Multiple trailing stops on the same symbol | All are evaluated after every trade in O(t) time (t = number of trailing stops) |
| Several prints in one sweep | Evaluated once per incoming order, against the sweep's last price: the stop ratchets once, to that price |
| Amend | Rejected — cancel and re-enter instead |
How traders use it¶
- Riding a trend. Hold a long while the move continues and exit
automatically once it reverses by
TRAIL. There is no profit target; the trade ends only on a pullback. - Calibrating
TRAIL. Too tight (a few ticks) and ordinary noise stops you out of a healthy trend; too wide and you give back much of the gain before it fires. A common rule of thumb is a multiple of the instrument's typical short-term range, so a volatile name gets a wider trail than a quiet one. - Buy-side mirror. A BUY trailing stop above the market covers a short and follows the price down, locking in the profit of a falling market.
- Combined with a target. Paired with a take-profit LIMIT in an OCO it gives a bracket whose stop side tightens as the trade works: whichever of target or reversal comes first ends the position and cancels the other.
- Limits of the tool. It reacts only to trades, converts to MARKET (so gaps still slip), and, being a stop, is dormant during auctions.
Order Amendment (AMEND)¶
Background¶
Order amendment (also called modify or cancel/replace in FIX protocol terminology) allows a trader to change the price or quantity of a resting order without explicitly cancelling and re-submitting it. This is a fundamental operation on every real exchange — CME, NYSE, Eurex, LSE, and NASDAQ all support in-place amendment.
In FIX Protocol, this is MsgType=G (Order Cancel/Replace Request). The key
semantic distinction from a plain cancel+new is that an amendment preserves
the order ID and, under certain conditions, preserves time priority.
Priority Rules¶
EduMatcher implements the same priority rules used by most lit exchanges:
| Amendment Type | Priority |
|---|---|
| Quantity decrease only (price unchanged) | Preserved — order keeps its original arrival_seq |
| Price change (any direction) | Lost — order is assigned a new arrival_seq and moves to the back of the queue at the new price |
| Quantity increase (price unchanged) | Lost — order is assigned a new arrival_seq and moves to the back of the queue |
| Price change + quantity change | Lost |
Rationale: A quantity decrease cannot disadvantage other participants at the same price level (there is less competition for incoming aggressor flow). Any other change either alters the priority ranking (price change) or grants the order more opportunity to fill without having competed for that position (quantity increase).
Priority is keyed on arrival_seq, not on the order's timestamp
It is tempting to think of price-time priority as being governed by the
order's timestamp — and colloquially that is a fine mental model. But the
field that actually determines queue position is the engine-assigned
arrival_seq, a monotonic counter incremented every time the engine
admits an order (or a re-priced/re-queued order) into the book.
The order's timestamp field, by contrast, is client-supplied at
submission time (see ALF Protocol and the
message reference) — it records when
the client says it built the order, and a back-dated or clock-skewed
value cannot be used to jump the queue. Only the engine's own
arrival_seq, assigned the moment the order (or amendment) is accepted,
decides fill order among orders resting at the same price.
In outbound order snapshots (order.orders.{GW_ID}) and in the AMENDED
event, watch arrival_seq — not timestamp — to see whether an order
kept or lost its place in the queue.
AMEND vs. Cancel+New¶
| Aspect | AMEND | Cancel + New |
|---|---|---|
| Order ID | Preserved | New ID assigned |
| Message count | 1 inbound + 1 response | 2 inbound + 2 responses |
| Priority on qty decrease | Preserved | Lost (new order gets current timestamp) |
| Atomicity | Guaranteed — no gap where the order is absent from the book | Non-atomic — brief window with no resting order |
| Fill risk during gap | None | Another order could fill the opposite side during the gap |
| Filled quantity tracking | Continuous — partial fills before amendment are preserved | Resets to zero on the new order |
| Gateway state | Single cache entry updated | Old entry cancelled + new entry created |
When to use Cancel+New instead: If you need to change the order's side, symbol, order type, or TIF, you must cancel and resubmit — amendment only modifies price and/or quantity of an existing resting order.
Supported Order Types¶
Only LIMIT and ICEBERG orders can be amended. Other order types either cannot rest on the book (MARKET, IOC, FOK) or have trigger semantics that make in-place amendment ambiguous (STOP, STOP_LIMIT, TRAILING_STOP).
Gateway Syntax¶
# Change price only (priority lost)
AMEND|ID=<order-id>|PRICE=151.00|RTAG=amd-price-001
# Decrease quantity only (priority preserved)
AMEND|ID=<order-id>|QTY=50|RTAG=amd-down-001
# Change both price and quantity (priority lost)
AMEND|ID=<order-id>|PRICE=151.00|QTY=200|RTAG=amd-both-001
Examples¶
# 1. Place a resting buy limit
NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.00
# → ACK: order_id = abc123...
# 2. Market moves — improve price (priority lost, back of queue at 151)
AMEND|ID=abc123...|PRICE=151.00|RTAG=amd-price-002
# → AMENDED: price=151.0 qty=100 remaining=100 (priority reset)
# 3. Reduce size to manage risk (priority preserved at 151)
AMEND|ID=abc123...|QTY=80|RTAG=amd-down-002
# → AMENDED: price=151.0 qty=80 remaining=80
# 4. After a partial fill of 30, reduce remaining exposure
# (original qty was 80, filled 30, so remaining = 50)
AMEND|ID=abc123...|QTY=60|RTAG=amd-partial-001
# → AMENDED: price=151.0 qty=60 remaining=30
# Note: new_qty (60) - filled (30) = 30 remaining
Amendment Events¶
| Topic | Fired When |
|---|---|
order.amended.{GW} |
Amendment accepted; contains new price, qty, remaining_qty, priority_reset |
order.ack.{GW} (accepted=false) |
Amendment rejected; contains stable reject_code plus human-readable reason |
priority_reset=true means the engine assigned the order a new arrival_seq
(the order fell to the back of the queue at its price level). It says nothing
about the order's timestamp field, which is client-supplied and unrelated to
queue position — see Priority Rules.
A price amend can trigger an immediate fill¶
If a price amend makes the order marketable — the new price crosses the
current best opposite price — the engine does not simply leave it resting at
the new price. It pulls the resting copy, re-processes it through the normal
matching path as if it were a fresh aggressive order, and publishes any
resulting order.fill.{GW}/trade.executed events before the order (or its
remainder) rests again. From the caller's point of view this looks like an
order.amended.{GW} event followed immediately by one or more
order.fill.{GW} events for the same order ID.
# Resting buy at 149.00; best ask is currently 150.00 (no cross)
AMEND|ID=<order-id>|PRICE=150.50|RTAG=amd-cross-001
# → AMENDED: price=150.5 ... (priority reset)
# → FILL: ... (the amend crossed the book and filled immediately)
Quantity-only amends never trigger this path — only a price change (with or without an accompanying quantity change) can make an order marketable. If the symbol is halted or the session phase is non-continuous, the amend still applies to the resting order but cannot cross, even if the new price would otherwise be marketable.
Edge Cases and Gotchas¶
| Scenario | Behaviour |
|---|---|
| Order not found (wrong ID) | REJECTED — "Order not found" |
| Order owned by another gateway | REJECTED — "Cannot amend an order owned by another gateway" |
| Order already filled | REJECTED — "Cannot amend FILLED order" |
| Order already cancelled | REJECTED — "Cannot amend CANCELLED order" |
| MARKET / STOP / FOK order | REJECTED — e.g. "Cannot amend STOP orders" (message names the actual order type) |
Session state is CLOSED |
REJECTED — "Market is closed" |
| New price violates the symbol's collar | REJECTED — same collar-rejection reason a NEW order at that price would get |
QTY= is not a valid integer |
REJECTED — "Amend quantity must be an integer" |
| New QTY \(\leq\) already filled qty | REJECTED — "New quantity must exceed already-filled quantity" |
| New QTY = 0 or negative | REJECTED — "Quantity must be positive" |
| New PRICE \(\leq\) 0 | REJECTED — "Price must be positive" |
| Neither PRICE nor QTY supplied | REJECTED — "Amend requires at least PRICE or QTY" |
| Iceberg order amended | Supported — a price change or qty increase refills the peak to visible_qty (priority lost); a qty decrease never enlarges the current peak, only shrinks it to the new remaining |
| Combo child order amended | Allowed — the child is a normal resting order |
| OCO leg amended | Allowed — does not affect the OCO linkage |
| Amend during auction phase | Allowed (any non-CLOSED state accepts amends) — see note above about crossing amends |
QTY means total quantity
QTY= in an AMEND command sets the new total order size, not the
additional quantity you want. For example, if 40 of 100 shares have already
filled and you want 80 total, send QTY=80 (not QTY=40).
OCO (One-Cancels-Other)¶
Background¶
One-Cancels-Other (OCO) is a paired order construct in which two orders
are linked such that when one reaches a terminal state (filled, cancelled,
or rejected), the other is automatically cancelled. OCO is a core primitive
in institutional trading and has been standardised by the FIX Protocol
(ContingencyType 1385 = One Cancels the Other) and supported by virtually every major exchange and brokerage
since the early 2000s.
The classic use case is a bracket order: a trader enters a long position and simultaneously places a take-profit limit above the entry price and a stop-loss below. Once one of them fills, the other is automatically cancelled, preventing the trader from accidentally doubling their position.
Motivation¶
Use OCO when:
- You want to protect a position with either a profit target or a stop-loss — whichever happens first — without needing to manually cancel the other leg.
- You want to implement a "bracket" strategy: entry fills, then simultaneously a take-profit limit and a stop-loss stop are live.
- You need the guarantee that you cannot be filled on both legs.
OCO vs two independent orders: Without OCO, if your take-profit fills first and you forget to cancel the stop, the stop might trigger later and re-open an unintended position. OCO prevents this by coupling the two orders at the engine level.
OCO vs COMBO: A COMBO (All-Or-Nothing) executes multiple legs simultaneously on a single triggering event. An OCO has two legs that compete — only one can survive.
Educational note: OCO is a powerful risk management tool that automates the lifecycle coupling of two related orders. It is essential for any strategy that relies on mutually exclusive outcomes, such as profit-taking vs stop-loss scenarios.
How traders use it¶
- Exit bracket (take-profit / stop-loss). The classic: one LIMIT to bank the gain, one STOP (or STOP_LIMIT) to cap the loss. See Example 1.
- Breakout straddle. A buy stop above a range and a sell stop below it enters whichever way the market leaves the range, and only one side. See Example 5.
- Two-sided entry. A buy limit below the market and a sell limit above it work as "buy the dip or sell the rip" — one position at most, never both.
- Exit with a ratchet. A LIMIT target plus a TRAILING_STOP lets a winner run while keeping a ceiling and a floor. See Example 6.
- Choose the stop type for the market. A STOP leg guarantees the exit but may slip; a STOP_LIMIT leg bounds the price but, if the market gaps past its limit, rests unfilled as a limit order. A triggered stop that is merely resting has not ended, so its sibling is not cancelled: your take-profit LIMIT can still be live next to it. Decide which failure is worse for the position, and cancel the pair yourself if that happens.
- Partial fills. A partially filled leg does not cancel its sibling;
only a leg reaching a terminal state does. After a partial fill you can be
working the remaining quantity on one leg and the full quantity on the other;
cancel the pair (
CANCEL|OCO_ID=) if you do not want that.
How It Works¶
- The gateway sends an OCO request with two leg definitions (side,
type, price/stop) plus a shared quantity and
oco_id. - The engine validates both legs (gateway auth, symbol config, price
fields), then creates two independent
Orderobjects, each tagged withoco_group_id. - Both orders are posted to the relevant order book and given
NEWstatus. - The engine publishes an
oco.ack.{GW}message containing bothorder_id_1andorder_id_2. - When either order transitions to a terminal state (FILLED, CANCELLED,
REJECTED), the engine calls
_check_oco_after_event(): - Looks up the sibling order ID.
- Sends a cancel request to the book for the sibling.
- Publishes
oco.cancelled.{GW}withcancelled_order_id(the sibling) andreasonexplaining why. - Removes the OCO group from the engine's tracking dict.
- The OCO group can also be cancelled explicitly via
CANCEL|OCO_ID=<id>, which cancels both legs immediately.
Examples¶
Example 1 — Classic take-profit + stop-loss bracket (SELL side)¶
You are long 100 AAPL at $150. You want to: - Sell at $160 (take profit) if the stock rallies, or - Sell at $140 (stop-loss) if the stock drops.
NEW|TYPE=OCO|OCO_ID=BRACKET1|SYM=AAPL|QTY=100|TIF=GTC|
LEG1_SIDE=SELL|LEG1_TYPE=LIMIT|LEG1_PRICE=160|
LEG2_SIDE=SELL|LEG2_TYPE=STOP|LEG2_STOP=140
Scenario A: Stock rallies to $162. A buyer crosses the LIMIT leg at $160.
Order book receives BUY at 162
LIMIT SELL at 160 → FILLED
Engine: OCO group BRACKET1 — sibling stop cancelled
oco.cancelled.TRADER01 published (cancelled_order_id = stop order)
Scenario B: Stock drops to $138. A seller triggers the STOP.
Trade at 138 → STOP SELL trigger → converts to MARKET SELL → FILLED
Engine: OCO group BRACKET1 — sibling limit cancelled
oco.cancelled.TRADER01 published (cancelled_order_id = limit order)
Example 2 — Two limit orders (buy-side bracket)¶
You are short 100 AAPL at $150. Cover at $140 (profit) or at $160 (stop).
NEW|TYPE=OCO|OCO_ID=SHORT_BRACKET|SYM=AAPL|QTY=100|TIF=GTC|
LEG1_SIDE=BUY|LEG1_TYPE=LIMIT|LEG1_PRICE=140|
LEG2_SIDE=BUY|LEG2_TYPE=LIMIT|LEG2_PRICE=160
Whichever price is touched first fills; the other is auto-cancelled.
Example 3 — Explicit cancellation of the entire OCO pair¶
Before either leg fills:
Both legs are cancelled atomically. The engine publishes order.cancelled
for each leg and removes the OCO group.
Example 4 — A malformed OCO is refused whole¶
A leg with a missing or unsupported field never reaches the book. Here leg 1
is a LIMIT with no price:
NEW|TYPE=OCO|OCO_ID=BAD1|SYM=AAPL|QTY=50|TIF=GTC|LEG1_SIDE=SELL|LEG1_TYPE=LIMIT|LEG2_SIDE=SELL|LEG2_TYPE=STOP|LEG2_STOP=145
The gateway answers with a MISSING_FIELD error (and a malformed number such
as LEG1_PRICE=INVALID is refused the same way, as INVALID_VALUE). Neither
leg is created. If the pair reaches the engine and fails there, you receive
oco.ack with accepted=false and a reason, again with neither leg posted.
Example 5 — Breakout bracket (range trading)¶
The stock is coiled between 148 and 152 and you have no view on direction, only that it will leave the range. You want to be in on whichever side it breaks, but not both:
NEW|TYPE=OCO|OCO_ID=BREAKOUT|SYM=AAPL|QTY=100|TIF=DAY|LEG1_SIDE=BUY|LEG1_TYPE=STOP|LEG1_STOP=152.00|LEG2_SIDE=SELL|LEG2_TYPE=STOP|LEG2_STOP=148.00
If a trade prints at 152.00 or higher the buy stop fires, you are long, and the sell stop is cancelled. If the stock instead breaks down through 148.00 the sell stop fires and the buy is cancelled. A stop that is already breached when the pair is placed fires on entry and cancels its sibling at once — place the legs on the correct side of the last trade price.
Example 6 — Trailing protection with a profit target¶
You are long 100 AAPL. Take profit at 160, otherwise let a trailing stop follow the price up:
NEW|TYPE=OCO|OCO_ID=RIDE|SYM=AAPL|QTY=100|TIF=GTC|LEG1_SIDE=SELL|LEG1_TYPE=LIMIT|LEG1_PRICE=160.00|LEG2_SIDE=SELL|LEG2_TYPE=TRAILING_STOP|LEG2_TRAIL=2.00
The limit takes the profit at 160 and cancels the trailing stop. If the stock reverses first, the trailing stop sells and the limit is cancelled.
Gateway Syntax¶
# Full OCO request syntax
NEW|TYPE=OCO|OCO_ID=<label>|SYM=<symbol>|QTY=<qty>|TIF=<tif>
|LEG1_SIDE=<BUY|SELL>|LEG1_TYPE=<order_type>|LEG1_PRICE=<price>
|LEG2_SIDE=<BUY|SELL>|LEG2_TYPE=<order_type>|LEG2_STOP=<stop_price>
# LIMIT + STOP (take-profit + stop-loss)
NEW|TYPE=OCO|OCO_ID=TPS|SYM=AAPL|QTY=50|TIF=GTC|LEG1_SIDE=SELL|LEG1_TYPE=LIMIT|LEG1_PRICE=155|LEG2_SIDE=SELL|LEG2_TYPE=STOP|LEG2_STOP=145
# Two LIMITs (buy bracket)
NEW|TYPE=OCO|OCO_ID=BB|SYM=AAPL|QTY=50|TIF=GTC|LEG1_SIDE=BUY|LEG1_TYPE=LIMIT|LEG1_PRICE=140|LEG2_SIDE=BUY|LEG2_TYPE=LIMIT|LEG2_PRICE=160
# LIMIT + TRAILING_STOP (take-profit with adaptive stop)
NEW|TYPE=OCO|OCO_ID=TS_BRACKET|SYM=AAPL|QTY=50|TIF=GTC|LEG1_SIDE=SELL|LEG1_TYPE=LIMIT|LEG1_PRICE=160|LEG2_SIDE=SELL|LEG2_TYPE=TRAILING_STOP|LEG2_TRAIL=2.00|LEG2_STOP=145
# Cancel the entire OCO pair
CANCEL|OCO_ID=TPS
TRAILING_STOP legs
TRAILING_STOP is a valid OCO leg type. Use LEG<n>_TRAIL= for the trail
offset and optionally LEG<n>_STOP= for the initial stop price (computed
from last_trade_price ± trail_offset if omitted). LEG<n>_TRAIL= is
required for any TRAILING_STOP leg or the OCO will be rejected.
OCO Events¶
| Topic | Fired When |
|---|---|
oco.ack.{GW} |
OCO pair accepted; contains order_id_1, order_id_2 |
oco.ack.{GW} (accepted=false) |
OCO pair rejected; contains reason |
oco.cancelled.{GW} |
Sibling auto-cancelled after the other leg settled |
order.fill.{GW} |
Each leg is an independent order; fill events are per-leg |
order.cancelled.{GW} |
Published for each leg on explicit CANCEL|OCO_ID= |
Edge Cases and Gotchas¶
OCO_ID must be unique among your live pairs
Submitting an OCO_ID that one of your own pairs still holds is refused
(oco.ack with accepted=false, OCO id … is already in use). The label
is free again once that pair has ended. Ids are scoped per gateway, so two
gateways may use the same label.
Which leg types are allowed. Each leg is LIMIT, STOP, STOP_LIMIT,
TRAILING_STOP, MARKET, IOC or FOK and takes the same fields as the
single order (LEG<n>_PRICE, LEG<n>_STOP, LEG<n>_TRAIL). ICEBERG is not
allowed (an OCO leg has no VISIBLE). Both legs are on the same symbol and
share the quantity and TIF. An OCO takes no SMP=; both legs use the
gateway's configured default. Only a resting or dormant leg can usefully wait
for its sibling — a MARKET, IOC or FOK leg trades or dies on entry and so
ends the pair at once.
| Scenario | Behaviour |
|---|---|
| OCO_ID already in use | REJECTED with oco.ack accepted=false — neither leg is created |
| Unknown symbol or gateway | REJECTED with oco.ack accepted=false |
LIMIT leg missing LEG1_PRICE= or LEG2_PRICE= |
REJECTED |
STOP leg missing LEG1_STOP= or LEG2_STOP= |
REJECTED |
| Partial fill of one leg | Does NOT cancel the sibling — only a terminal state (FILLED, CANCELLED, REJECTED) does |
| Both legs fill simultaneously | First fill to be processed cancels the second; race resolved by event order |
| A leg ends while an order is matching | Filled, cancelled by self-match prevention, an IOC leg's unfilled rest, or a stop leg short of liquidity: the sibling is pulled at once, before any stop that order triggers can trade with it — even a stop leg already triggered and waiting its turn. So oco.cancelled can arrive before the leg's own order.fill or order.cancelled |
A FOK meets both legs of a pair |
Only the first leg it meets counts as available: once that leg fills, the other is gone |
| Leg 1 fills, is cancelled or rejected on entry | Leg 2 is acknowledged, then cancelled with oco.cancelled; it never reaches the book |
| Symbol halted | Legs rest without matching, as ordinary limit orders do. A MARKET, IOC or FOK leg cannot rest and is rejected with INSTRUMENT_HALTED (or CIRCUIT_BREAKER_ACTIVE); outside continuous trading, with SESSION_NOT_PERMITTED |
| GTC OCO across sessions | Both legs persist with their TIF; if GTC they survive session reset |
See also¶
In this book:
- Combos — multi-symbol multi-leg strategies with cascade cancellation
- ALF Console — the full command syntax for submitting these order types
Operator's Guide:
- Auctions & Scheduling — which order types are accepted in each session phase
- Persistence — how GTC orders survive engine restarts
- Risk Controls — how collars and halts affect order admission
Protocols and Clients:
- ALF Protocol Reference — formal grammar for all order fields