Our gateway sends orders to an exchange and receives execution reports back. Implement OrderTracker, which keeps every order's state and quantities correct, including when our requests and the exchange's reports cross each other on the wire. The names follow FIX, simplified; no FIX knowledge is needed beyond this page.
Ids
Every request carries a new clOrdId. A cancel or replace also names the order's current id in origClOrdId: the order's first id, or the id of its last confirmed replace.
An id is used as soon as the request method returns true, even if the exchange later rejects that request, and it can never be used again. Every id an order has used identifies that order, both in reports and in find.
Requests we send
Each returns false and changes nothing if the request must not be sent.
| Request | Allowed when | Effect |
|---|---|---|
newOrder | id never used; quantity > 0; price > 0 | PendingNew |
cancelRequest | new id never used; origClOrdId is the current id; order is New or PartiallyFilled | PendingCancel |
replaceRequest | same as cancel; price > 0; new quantity > filled quantity | PendingReplace |
Only one request can be pending at a time. While a replace is pending the order keeps its old quantity and price.
Reports we receive
| Report | Valid in | Result |
|---|---|---|
| New | PendingNew | New |
| Rejected | PendingNew | Rejected |
| Fill | New, PartiallyFilled, PendingCancel, PendingReplace | see below |
| Cancelled | same four states | Cancelled (requested by us or not) |
| Expired | same four states | Expired |
| Replaced | PendingReplace | new quantity and price apply; the request id becomes the current id; see below for the resulting state |
| CancelRejected | PendingCancel, PendingReplace | the request is dropped; PartiallyFilled if anything is filled, otherwise New |
Fill: lastQty must be positive and at most quantity − filled (the old quantity while a replace is pending). It adds to filled and to filledNotional (lastQty × lastPriceTicks). When filled reaches the quantity the order is Filled, even if a request was pending. Otherwise a New order becomes PartiallyFilled and a pending order stays pending.
Replaced: fills may have arrived while the replace was pending. If filled is at or above the new quantity, the order is Filled: quantity takes the new value, filled keeps its real value (it may be larger than quantity) and remaining is 0. Otherwise the order is PartiallyFilled if anything is filled, or New.
A CancelRejected for an order that is already finished is the exchange answering a request that arrived too late: ignore it.
Bad and repeated reports
- A report whose
execIdwas already received is a duplicate: ignore it. - Otherwise, record a
Violationand leave the order unchanged if:- no order uses the
clOrdId(UnknownOrder), - the report is not valid in the order's state (InvalidForState),
- a valid fill has a bad
lastQty(InvalidQuantity).
- no order uses the
- Keep processing after a violation.
Queries
find returns the order for any of its ids. remaining is quantity − filled, and 0 once the order is finished (Filled, Cancelled, Rejected, Expired).
Assumptions
In Fill reports, lastPriceTicks is always positive. For valid operations, all arithmetic, including lastQty × lastPriceTicks and the accumulated filledNotional, fits in std::int64_t, so no overflow handling is required. The quantity rules above still apply and must be checked.
You may add private members and helpers. Do not change the public interface, the types in the header or the tests. Passing all unit tests is required.