Once a subscription is started, Handler builds and synchronizes books for each market for which a product definition has previously been received.
The OrderBook class exposes the book abstraction.
There are several types of books currently supported by the Handler:
The Handler can build and maintain books of any type for the subscribed markets.
Handler passes instances of OrderBook class to the listeners of book changes registered through the Handler's Handler::registerOrderBookChangeListener member.
OrderBook class exposes two major members to access market orders and price levels:
See the Product Definitions topic for more information concerning how to store product definitions. Use OrderBook::marketId to access the corresponding product definition.
An important aspect of book behavior is that not all its members are available for all types of books. In particular, OrderBook::bidOrders (OrderBook::offerOrders) members work only for full-order-depth books. Therefore, it will return an empty array for a price-level book. For this reason, when accessing members of the OrderBook instance, it's necessary to check its type using the OrderBook::depth member.
Also, since books change over time, do not store references to the orders and price levels obtained. Any instance exposed by the book through OrderBook::bidOrders (OrderBook::offerOrders) or OrderBook::bids and OrderBook::offers members is valid only within the bounds of the handling routine being called by the Handler to process changes in the book. It may be deleted right after handling is completed in response to a new message from the Multicast Price Feed. Therefore, instances can only be used to access the information they store.
See the Event Listeners topic for more information on how to be notified of changes to the market books.
The following sample demonstrates how book attributes can be processed by the appropriate listener:
The Handler constructs books for each security from the moment it receives a definition for that security. Books change over time, and their members are not thread-safe. The Handler uses its own synchronization strategies while updating the books. Therefore, do not access price-level information outside of book-related event handling, nor store references to any book instance or to the data returned by its members, such as ask and bid collections. This is because Handler may change internal book structures at the same time. If any data exposed by the OrderBook class instance must be used outside of event handling, it must be copied before later use.
| Container | Sorting Order | Prices Example |
|---|---|---|
| OrderBook::bids | Descending by price | 5, 4, 3, 2, 1 |
| OrderBook::offers | Ascending by price | 1, 2, 3, 4, 5 |
| OrderBook::bidOrders | Order of arrival | 1, 5, 2, 4, 3 |
| OrderBook::offerOrders | Order of arrival | 1, 5, 2, 4, 3 |
The Handler supports both implied and non-implied feeds. For more details about implied prices, please see ICE Futures Implied Prices.
The Handler provides the following order book-related events:
When the Handler has just started or needs to recover the current market state for a snapshot, it can trigger this event. For users, it indicates that the previous order book state is no longer valid and should be dropped. This event is usually used to update some local user's books if they hold copies.
In each datagram, we have one or more messages that can cause changes to the order book. After processing each of such messages, the Handler triggers this event. For example, if the Handler receives two messages in a single datagram, such as "Delete Price Level" and "Add Price Level", it will trigger two OrderBookChangeListener::onOrderBookChanged events.
If, for OrderBookChangeListener::onOrderBookChanged, the Handler triggers the event for each message in the datagram, OrderBookUpdateListener::onOrderBookUpdated is triggered after processing all the messages for the same market in the current datagram. Like in the previous example, if we have "Delete Price Level" and "Add Price Level" messages in the same datagram for the same market, the Handler will trigger OrderBookUpdateListener::onOrderBookUpdated only once after both messages have been processed.
ICE has a concept of a bundle, where a long sequence of messages can span two or more datagrams. In this case, ICE sends special messages called BundleMarker. If OrderBookBundleUpdateListener is registered, the Handler will analyze these bundle markers and trigger this event for all the affected markets only when it receives BundleMarker with BundleMarker::isTransactionEnd field set to true.
When only best bid/offer changes matter, the volume of OrderBookChangeListener::onOrderBookChanged events can be reduced by setting HandlerSettings::trackTopOfTheBookOnly to true. With this option enabled the Handler suppresses change notifications for updates that do not affect the top of the book, while leaving OrderBookUpdateListener::onOrderBookUpdated and OrderBookBundleUpdateListener::onOrderBookBundleUpdated unaffected - those listeners continue to fire for every processed message.
For Price Level subscriptions only position-1 (top of book) changes trigger [OrderBookChangeListener::onOrderBookChanged]:
| Operation | Position 1 (best) | Position 2+ |
|---|---|---|
| AddPriceLevel | fired | suppressed |
| ChangePriceLevel | fired | suppressed |
| DeletePriceLevel | fired | suppressed |
For Full Order Depth subscriptions OrderBookChangeListener::onOrderBookChanged fires when an order is added at a price equal to or better than the current best bid/offer, or when an order is removed from the current best price level (including the case where that removal shifts the best price to the next level). Note that snapshot replay is always silent - no change callbacks are emitted for orders received during initial book synchronization, regardless of their price. This differs from Price Level books, which do fire for position-1 entries during snapshot replay.
| Scenario | Notification |
|---|---|
| Order added at best bid/offer price | fired |
| Order added at a price improving best bid/offer | fired |
| Order added at a price worse than best bid/offer | suppressed |
| Order removed from the best bid/offer level | fired |
| Order removed from a non-best level | suppressed |
To enable the option, set HandlerSettings::trackTopOfTheBookOnly before starting the Handler:
The following walkthroughs show how trackTopOfTheBookOnly affects each event type for a concrete sequence of market updates. OrderBookUpdateListener::onOrderBookUpdated fires for every step in both modes and is omitted from the tables for brevity.
Initial bid book state:
| Position | Bid |
|---|---|
| 1 | 100 @ 78.15 |
| 2 | 200 @ 78.10 |
| Step | Market event | Best bid after | trackTopOfTheBookOnly=false | trackTopOfTheBookOnly=true |
|---|---|---|---|---|
| 1 | New bid: 150 @ 78.20 - inserted at pos 1 | 78.20 | onOrderBookChanged fired | onOrderBookChanged fired |
| 2 | New bid: 100 @ 78.05 - inserted at pos 4 | 78.20 | onOrderBookChanged fired | suppressed (not TOB) |
| 3 | Change pos 1 bid: 78.20, qty becomes 200 | 78.20 | onOrderBookChanged fired | onOrderBookChanged fired |
| 4 | Change pos 3 bid: 78.10, qty becomes 300 | 78.20 | onOrderBookChanged fired | suppressed (not TOB) |
| 5 | Delete pos 1 bid (78.20 withdrawn) | 78.15 | onOrderBookChanged fired | onOrderBookChanged fired |
| 6 | Delete pos 3 bid (78.05 withdrawn) | 78.15 | onOrderBookChanged fired | suppressed (not TOB) |
Empty side: When a side has no orders - either at startup or after all orders are cancelled - the first order added always fires OrderBookChangeListener::onOrderBookChanged regardless of price, because any price is the best price on an empty side.
Scenario A - Bid side (non-empty initial state):
One existing bid order at 99.16 is already in the book.
| Step | Market event | Best bid after | trackTopOfTheBookOnly=false | trackTopOfTheBookOnly=true |
|---|---|---|---|---|
| 1 | Bid 1 lot @ 99.10 added - below current best | 99.16 @ 1 order | onOrderBookChanged fired | suppressed (below TOB) |
| 2 | Bid 1 lot @ 99.16 added - joins best price level | 99.16 @ 2 orders | onOrderBookChanged fired | onOrderBookChanged fired |
| 3 | Cancel bid @ 99.10 - non-TOB order removed | 99.16 @ 2 orders | onOrderBookChanged fired | suppressed (not TOB) |
| 4 | Cancel one bid @ 99.16 - TOB quantity reduced | 99.16 @ 1 order | onOrderBookChanged fired | onOrderBookChanged fired |
Step 2 shows that joining an existing best price level also fires - the TOB quantity changed. Step 3 confirms that removing a non-best order is suppressed even when the TOB price itself is unchanged.
Scenario B - Offer side (starting empty):
The offer side is empty after snapshot recovery with no offer orders.
| Step | Market event | Best offer after | trackTopOfTheBookOnly=false | trackTopOfTheBookOnly=true |
|---|---|---|---|---|
| 1 | Sell 100 @ 5.00 (OrderId 1) added - first order | 5.00 | onOrderBookChanged fired | onOrderBookChanged fired |
| 2 | Sell 100 @ 4.00 (OrderId 2) added - improves TOB | 4.00 | onOrderBookChanged fired | onOrderBookChanged fired |
| 3 | Sell 50 @ 6.00 (OrderId 3) added - worse than TOB | 4.00 | onOrderBookChanged fired | suppressed (worse than TOB) |
| 4 | Cancel OrderId 3 @ 6.00 - non-TOB order removed | 4.00 | onOrderBookChanged fired | suppressed (not TOB) |
| 5 | Cancel OrderId 1 @ 5.00 - non-TOB order removed | 4.00 | onOrderBookChanged fired | suppressed (not TOB) |
| 6 | Cancel OrderId 2 @ 4.00 - last order, side becomes empty | none | onOrderBookChanged fired | onOrderBookChanged fired |
Step 1 demonstrates the empty-side rule: the first sell order fires unconditionally. Step 2 fires because the new price improves the current best. Step 3 is suppressed because the new price is worse. Step 6 fires when the last order is removed and the offer side becomes empty again - removing the only remaining order on a side is always a TOB change.
The Handler will enable order book building logic only if you subscribe to one of the following listeners:
If you don't register any of these listeners, the internal order-book-building logic will be completely disabled, and the Handler will only parse the messages.
We implemented the order book building logic on top of the most efficient data structures to make handling of order book updates as fast as possible. Let us take a closer look at the different types of order books and the complexity of their update operations.
| Message | Complexity |
|---|---|
| AddPriceLevel | Linear in the distance between the position of the new price level and end of the container |
| ChangePriceLevel | Constant |
| DeletePriceLevel | Linear in the number of elements after the price level deleted (moving) |
| Parameter | Complexity |
|---|---|
| AddModifyOrder | Constant (store an order) plus linear (add/update price level) |
| DeleteOrder | Constant (remove order) plus linear (remove price level) |
| Trade | Constant (update/remove order) plus linear (update price level) |