Skip to content
Course contents
The order and the order manager

From click to fill

An order is not filled the instant you send it. It moves through states, sent, acknowledged, open, partially filled, filled, rejected, or cancelled, and a safe system tracks that state rather than assuming success. This chapter draws the order state machine and explains why every serious bug in an order manager is really a confusion about state.

9 min readChapter 10 of 28
What you will learn
  • Describe the lifecycle states of an order and the transitions between them
  • Explain why a program must wait for an acknowledgement rather than assume a fill
  • Identify the failure states (rejected, cancelled, unknown) that code must handle

When you place an order by hand and see "completed" flash on the screen, you rarely think about the small journey the order just took. A program has to think about nothing else. Between sending an order and having a position, the order passes through a series of states, and the single most common source of bugs, and losses, in automated trading is a program that assumed an order had reached one state when it was really in another. So learn the states, and learn to always check which one you are in.

The states an order passes through

An order is not filled the instant you send it: it moves through states, and a safe system tracks that state rather than assuming success.
An order is not filled the instant you send it: it moves through states, and a safe system tracks that state rather than assuming success.

An order lives in one of a handful of states, and it moves between them in a limited set of ways. Naming them precisely is half the battle.

  • Pending, or sent: your program has sent the order, but the broker has not yet confirmed it. You do not yet know if it was accepted.
  • Open: the broker has accepted the order and it is working in the market, waiting to fill. A resting limit order sits here.
  • Partially filled: some of the quantity has traded, but not all. A 100-share order that has filled 40 is here, with 60 still working.
  • Filled: the whole order has traded. Only now do you truly have the position you wanted.
  • Rejected: the broker refused the order, for a reason such as insufficient funds or a bad price. Nothing traded.
  • Cancelled: you, or the system, called off an order that had not fully filled. Nothing more will trade.

Here is one limit order travelling through these states as prices arrive, followed by the two terminal states every program must handle.

ExampleOne order from resting to partly filled to filled, plus rejected and cancelledch10/order_lifecycle.py
# An order is not filled the instant you send it. It moves through states, and a
# safe program tracks that state rather than assuming success. Here we watch one
# limit order travel from resting to partly filled to filled, then show the two
# terminal states a program must always handle: rejected and cancelled.
from paper_broker import PaperBroker

# liquidity=40 means a resting limit order fills at most 40 shares per price event.
broker = PaperBroker(cash=2_000_000, prices={"RELIANCE": 1400}, liquidity=40)

print("A limit order's journey:")
order = broker.place_order("RELIANCE", "BUY", 100, "LIMIT", price=1395)
print(f"  placed:      {order.status:<17} {order.filled_quantity}/{order.quantity}")

for tick in [1396, 1394, 1393, 1392]:
    broker.feed_price("RELIANCE", tick)
    print(f"  after {tick}:  {order.status:<17} {order.filled_quantity}/{order.quantity}")

print("\nTerminal states you must handle:")
rejected = broker.place_order("RELIANCE", "BUY", 1_000_000, "MARKET")   # far too big
print(f"  rejected:    {rejected.status:<17} reason: {rejected.message}")

to_cancel = broker.place_order("RELIANCE", "SELL", 10, "LIMIT", price=1500)
broker.cancel_order(to_cancel.order_id)
print(f"  cancelled:   {to_cancel.status:<17} (never traded)")
Output
A limit order's journey:
  placed:      OPEN              0/100
  after 1396:  OPEN              0/100
  after 1394:  PARTIALLY_FILLED  40/100
  after 1393:  PARTIALLY_FILLED  80/100
  after 1392:  FILLED            100/100

Terminal states you must handle:
  rejected:    REJECTED          reason: insufficient funds
  cancelled:   CANCELLED         (never traded)

Read the journey. The order is placed and rests, OPEN, because the market has not reached its price. A tick at 1,396 passes and it is still OPEN. When prices reach the limit, it fills in pieces, 40, then 80, sitting in PARTIALLY_FILLED, before reaching FILLED at 100. Only at that last line do you actually hold 100 shares. Below it are the two endings that are not fills: an order rejected for insufficient funds, which never traded, and an order cancelled before it could. A program that treats "I sent it" as "I own it" is wrong in every one of these cases except the clean fill.

Why you must wait, not assume

Here is the discipline the states demand. After sending an order, your program does not know what happened until the broker tells it. It might be filled, it might be resting, it might be rejected. Assuming the happy case is how automated accounts drift from reality. A program that fires a buy and immediately acts as though it is long, without confirming, will base its next decision on a fiction if that buy was rejected or only partly filled. The correct habit is to send, then wait for the broker's acknowledgement, then read the actual state, and only then decide what to do next. Those confirmations arrive on the same kind of stream you met in the last part, and every one updates an order's state.

Every state must be handled

The states that hurt are the ones beginners forget. It is easy to write code for "the order filled." The orders that cause losses are the ones that did something else: the reject you did not notice, so you think you have a hedge you do not; the partial fill you treated as complete, so your size is wrong; the cancel that did not go through, leaving an order live you thought was dead. Dependable order handling is mostly the discipline of asking, for every order, "what state is it actually in," and having an answer prepared for each. The next chapter takes the messiest of these, the partial fill, and the one after builds the order manager that keeps this state straight for you.

What to carry forward

An order passes through states, pending, open, partially filled, filled, rejected, cancelled, and it is a position only when filled. After you send an order you do not know its state until the broker confirms, so the correct habit is send, wait for the acknowledgement, read the real state, then decide, never assume success. The endings that cause losses are the forgotten ones: the reject, the partial, the failed cancel. You saw one order travel the full journey with the two terminal states beside it. Next, the messiest state of all, the partial fill, and why a big order is really many.