How to Build an Order State Machine
Consider the following: our model says fair value is $100, and our quoter wants to bid for 100 shares at $99.95. We send the order. Before its acknowledgement comes back, another market update arrives and we re-evaluate orders.
If we only keep track of acknowledged orders, we will end up sending another order.
Suppose our maximum permitted long position is 150 shares. Each submission passed a risk check: buying 100 shares from a flat position looked acceptable. But the second check ignored the first order. Together, they can take us beyond our limit.
This is the first (of many) reasons to keep order state. Every trading decision must account for the orders we have already submitted, including those whose outcomes we do not yet know. Otherwise, we can send duplicate orders or exceed our risk limits while believing that we have done neither.
The previous article turned fair value into desired bids and offers. Now we need to connect those intentions to an exchange, keeping track of what we have asked it to do and what could still execute.
Worst Case Risk
The first 100-share bid should enter our local order records before it can be transmitted. We give it a client order identifier and mark it as pending acknowledgement, usually shortened to pending ACK.
The next quote cycle can then see that we have already requested the 100 shares. Crucially, the risk calculation also includes those shares, leaving only 50 shares of additional long capacity under our 150-share limit.
This reservation and the decision to submit must happen together. If two handlers can both read the same available capacity before either records its order, they can still overspend it. A single owner of order state, or an equivalent atomic reservation mechanism, prevents that local race.
Can we cancel before ACK?
Now change the example. We have sent our 100-share bid at $99.95, but before its acknowledgement arrives, fair value falls to $99.80. Our bid is now 15 cents above our estimate of value. We want to withdraw it immediately.
If the venue requires an exchange-assigned order ID that only comes back in the acknowledgement, we cannot yet address the cancellation. We must wait for that response, then send the cancel. The same problem arises if the venue or our adapter only permits cancellation after acceptance has been confirmed.
Meanwhile, the stale bid may be available for somebody else to hit throughout the delay. If we could send the cancellation immediately, it would already be travelling towards the exchange while the acknowledgement travelled towards us.
How can we achieve this? We might submit a new order with client ID A123, then send a cancellation referring to A123. We no longer need to wait for the venue to tell us its own order ID.
There can be problems on the exchange side, however: A cancel reaching one service before another service has registered the new order. The cancel returns “order not found”; a moment later, the new order becomes live. Unless the protocol promises otherwise, that failed lookup has not cancelled the incoming order.
Each state changes the next action
We can now motivate a small set of internal states.
Pending ACK: we have submitted the new order and are waiting for its outcome. Count it when deciding whether the desired quote already exists, and reserve its possible exposure.
Confirmed: the venue has accepted the order. Track the quantity that remains executable and the terms it actually accepted. The quoter can decide whether to leave it or cancel it.
Pending cancellation: we have requested removal, but the outcome remains unresolved. The quoter knows that a cancellation is already underway, while risk continues to count the quantity that could execute. Sending the request has not freed that capacity.
Now we just need to manage the state changes. With early cancellation, an order can await its initial acknowledgement while a cancel is also outstanding. We need to preserve both facts. The new-order ACK must not overwrite the cancellation request and turn the order back into an ordinary confirmed order.
Even with perfect support for cancel before ACK, we still need to track the unacknowledged submission. It prevents duplicate adds and reserves risk while the original request and its cancellation are being resolved.
Race Conditions
An order state machine becomes useful when the next message is something other than the one we expected. The following are illustrative sequences to test wherever the venue’s reporting paths permit them.
A fill arrives before the new-order response
We submit 100 shares. An execution update reports that 30 have traded. The original new-order response then arrives, describing the 100-share submission.
Our position is 30 shares and the unexecuted balance is 70. Applying the late response as a fresh 100-share balance would invent another 30 shares of potential execution. Applying it as a second order would be worse.
Cancellation confirmation arrives before an earlier fill’s notification
At the exchange, 30 shares trade and the remaining 70 are cancelled. Through separate reporting paths, the cancellation confirmation reaches us first.
If we delete the order, assume we are still flat and submit another 150 shares, those shares could fill and leave us long 180. The order really was cancelled; our mistake was assuming that none of it had traded beforehand.
If the cancellation report establishes a cumulative fill of 30, those 30 shares must already be represented in position or an equivalent risk reservation. When the detailed execution notification arrives, reconcile it with the quantity already accounted for. If we held those shares as a reservation, transfer them into booked inventory without counting the exposure twice. If the report does not establish the final executed quantity, we need to resolve that uncertainty before releasing the corresponding reservation.
A lost response looks like a failed order
We send order A, the venue accepts it, and the connection fails before the response reaches us. If we treat the connection error as rejection and send order B, both orders can be live.
This ambiguity appears explicitly in Binance’s API documentation: a timeout can leave execution status unknown, and a server error does not necessarily mean the operation failed. Binance Developer Docs
We retain A and reconcile its outcome using the available order-status and execution interfaces. Retrying under the same client ID is safe only where the venue’s documented duplicate-handling rules make it safe. A fresh ID certainly does not make the original order disappear.
The same discipline applies to rejection messages. A cancel rejection concerns the cancellation request. Depending on its reason, the order may still be resting, may already have filled, or may need further investigation. It is not a general instruction to delete the order or restore an earlier state.
Risk must include everything that could still fill
Return to our 150-share long limit. We have bought 30 shares, and the remaining 70 are awaiting cancellation. Available capacity for another bid is:
A replacement bid for 100 would permit a total position of 200 if the old balance and the new bid both filled. We may prefer the new price, but that preference does not remove the old order’s liability.
The partial fill should move 30 shares from outstanding-order exposure into inventory as one logical update. Before the fill, our possible long position was 100. Afterwards it is still 100: 30 owned plus 70 that could trade. The fill has changed the composition of the exposure, not created another 30 shares of capacity.
What about Market Data Races?
So far, we have just discussed races in order entry. When quoting strategies are sensitive to the book state, the same issues can occur when reconciling our own orders with the public book feed. To be discussed later…


