Support

State management of resting orders

Overview

This example shows how to track orders and update them according to the different MBO actions. Order tracking is simpler to implement than a full limit order book: it keeps track of all resting orders, but not the book levels. The book levels such as the top of book (BBO) can be constructed from a map of resting orders, but it requires inefficiently iterating over all the orders.

Info
Info

This example works for a single instrument. To use it with multiple instruments, will need to extend it to keep separate books for each instrument.

Order events

Name Value Action
Add A Insert a new order into the book.
Modify M Change an order's price and/or size.
Cancel C Fully or partially cancel an order from the book.
Clear R Remove all resting orders for the instrument.
Trade T An aggressing order traded. Does not affect the book.
Fill F A resting order was filled. Does not affect the book.
None N No action: does not affect the book, but may carry flags or other information.

Trade and Fill actions do not affect the book because all fills will be accompanied by cancel actions that do update the book.

Handling F_LAST

A single event from a publisher can be normalized into a multiple records. Databento sets the flag F_LAST to denote the last record for an instrument in an event. The book state should only be examined after a record with F_LAST set.

Example

from dataclasses import dataclass, field
import databento as db

OrderId = int
UnixTimestamp = int

@dataclass
class Order:
    side: str
    price: int
    size: int
    ts_event: UnixTimestamp

@dataclass
class PriceLevel:
    price: int | None = None
    size: int = 0
    count: int = 0

@dataclass
class Book:
    orders: dict[OrderId, Order] = field(default_factory=dict)

    def bbo(self) -> tuple[PriceLevel, PriceLevel]:
        best_ask = PriceLevel()
        best_bid = PriceLevel()
        for order in self.orders.values():
            if order.side == "A":
                if best_ask.price is None or best_ask.price > order.price:
                    best_ask = PriceLevel(
                        price=order.price,
                        size=order.size,
                        count=1,
                    )
                elif best_ask.price == order.price:
                    best_ask.size += order.size
                    best_ask.count += 1
            elif order.side == "B":
                if best_bid.price is None or best_bid.price < order.price:
                    best_bid = PriceLevel(
                        price=order.price,
                        size=order.size,
                        count=1,
                    )
                elif best_bid.price == order.price:
                    best_bid.size += order.size
                    best_bid.count += 1
        return best_bid, best_ask

    def apply(
        self,
        ts_event: UnixTimestamp,
        action: str,
        side: str,
        order_id: int,
        price: int,
        size: int,
        flags: db.RecordFlags,
    ) -> None:
        # Trade, Fill, or None: no change
        if action in ("T", "F", "N"):
            return

        # Clear book: remove all resting orders
        if action == "R":
            self.orders.clear()

        # Add: insert a new order
        elif action == "A":
            # For top-of-book publishers, remove previous order associated with this side
            if flags & db.RecordFlags.F_TOB:
                self.orders = {i: o for i, o in self.orders.items() if o.side != side}
                # UNDEF_PRICE indicates the price level was removed. There's no new level to add
                if price == db.UNDEF_PRICE:
                    return
            self.orders[order_id] = Order(side, price, size, ts_event)

        # Cancel: partially or fully cancel some size from a resting order
        elif action == "C":
            existing_order = self.orders[order_id]
            assert existing_order.size >= size
            existing_order.size -= size
            # If the full size is cancelled, remove the order from the book
            if existing_order.size == 0:
                self.orders.pop(order_id)

        # Modify: change the price and/or size of a resting order
        elif action == "M":
            existing_order = self.orders[order_id]
            # The order loses its priority if the price changes or the size increases
            if existing_order.price != price or existing_order.size < size:
                existing_order.ts_event = ts_event
            existing_order.size = size
            existing_order.price = price

# First, create a historical client
client = db.Historical("YOUR_API_KEY")

# Next, we will request MBO data starting from the beginning of pre-market trading hours
data = client.timeseries.get_range(
    dataset="XNAS.ITCH",
    start="2022-08-26T08:00:00",
    end="2022-08-26T14:30:00",
    symbols="MSFT",
    schema="mbo",
)

# Then, we replay the data, updating the book with each record
book = Book()
book_is_ready = False
for mbo in data:
    book.apply(mbo.ts_event, mbo.action, mbo.side, mbo.order_id, mbo.price, mbo.size, mbo.flags)
    book_is_ready = mbo.flags & db.RecordFlags.F_LAST

# Finally we inspect the final BBO
if book_is_ready:
    best_bid, best_ask = book.bbo()
    print(f"Best ask  {float(best_ask.price) / db.FIXED_PRICE_SCALE} × {best_ask.size}")
    print(f"Best bid  {float(best_bid.price) / db.FIXED_PRICE_SCALE} × {best_bid.size}")

Result

Best ask  276.07 × 62
Best bid  276.01 × 19