← Back to the rules overviewDownload (EN) ↓
Original candidate statement (English)

Order lifecycle tracker

The original candidate statement is reproduced below in English. The detailed rule reference is also available in your selected language.

Detailed rule reference ↓

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.

RequestAllowed whenEffect
newOrderid never used; quantity > 0; price > 0PendingNew
cancelRequestnew id never used; origClOrdId is the current id; order is New or PartiallyFilledPendingCancel
replaceRequestsame as cancel; price > 0; new quantity > filled quantityPendingReplace

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

ReportValid inResult
NewPendingNewNew
RejectedPendingNewRejected
FillNew, PartiallyFilled, PendingCancel, PendingReplacesee below
Cancelledsame four statesCancelled (requested by us or not)
Expiredsame four statesExpired
ReplacedPendingReplacenew quantity and price apply; the request id becomes the current id; see below for the resulting state
CancelRejectedPendingCancel, PendingReplacethe 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 execId was already received is a duplicate: ignore it.
  • Otherwise, record a Violation and 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).
  • 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.

Detailed rule reference

Requests we send

A refused request returns false and changes nothing. A successful request returns true and permanently consumes its new ID. Only one request may be pending per order.

RequestAllowed whenResult
newOrderUnused ID; quantity > 0; price > 0.PendingNew
cancelRequestUnused new ID; origClOrdId is the current ID; state is New or PartiallyFilled.PendingCancel
replaceRequestSame rules as cancel, plus price > 0 and new total quantity > filled quantity.PendingReplace
Reports we receive
ReportValid statesResult
NewPendingNewBecomes New.
RejectedPendingNewBecomes Rejected; finished.
FillNew, PartiallyFilled, PendingCancel, PendingReplacePositive lastQty, at most confirmed quantity − filled. Add to filled and filledNotional. At full quantity: Filled. Otherwise preserve pending state, or become PartiallyFilled.
Cancelled / ExpiredNew, PartiallyFilled, PendingCancel, PendingReplaceFinished, including cancellation initiated by the exchange. Preserve earlier fills.
ReplacedPendingReplaceApply the proposed terms and make the request ID current. If filled ≥ new quantity: Filled. Otherwise PartiallyFilled when filled > 0, or New.
CancelRejectedPendingCancel, PendingReplaceDrop the pending request. Return to PartiallyFilled if anything filled, otherwise New. For an already finished order, ignore the late report.
Identity, repeated reports and invalid input
  • clOrdId identifies a request. origClOrdId must be the current ID: initially the order ID, then the ID of its last confirmed replacement. Every used ID continues to identify that same order, even after rejection.
  • A repeated execId is a duplicate: ignore it. This includes IDs from invalid or ignored reports, across the whole tracker.
  • For a new report, record UnknownOrder if its order is unknown; otherwise InvalidForState if the report is not allowed; otherwise InvalidQuantity for an invalid fill quantity. Leave the order unchanged and continue processing.
Queries and exercise boundaries
  • find must work with any ID ever used for the order. remaining is quantity − filled for an active order, and zero for Filled, Cancelled, Rejected or Expired. violations are returned in arrival order.
  • lastPriceTicks is positive. Arithmetic for valid operations fits in std::int64_t, including filledNotional. Validate quantities; overflow handling for valid operations is outside this exercise.
  • This is an in-memory, sequential event-processing exercise. You may add private members and helpers. Networking, a matching engine, persistent storage and multithreading are not required.