Skip to content

Watch orderbook

Depending on your trading strategy, keeping track of the current orderbook can be essential. The orderbook is a list of all the unmatched orders, divided into the bids (buy orders) and the asks (sell orders).

We'll use the Indexer WebSockets data streams for this.

Subscribe to the Orders channel

Lets take as reference the previous section. Subscribe to the Orders channel.

Python
def handler(ws: IndexerSocket, message: dict):
  if message["type"] == "connected":
      # Subscribe.
      ws.orders.subscribe(ETH_USD) 
  print(message)

Parse the update messages

Grab the bids and asks lists from the incoming messages. Each incoming bid and ask entry is the updated level in the orderbook. Each level is associated with a certain price and a total size. The total size is the current aggregated orders size for that price.

Python
def handler(ws: IndexerSocket, message: dict):
    if message["type"] == "connected":
        ws.order_book.subscribe(ETH_USD, False)
    elif message["channel"] == "v4_orderbook" and "contents" in message: 
        # Bids levels. 
        if "bids" in contents: 
            for bid in contents["bids"]: 
                price = bid["price"] 
                size = bid["size"] 
        # Asks levels. 
        if "asks" in contents: 
            for ask in contents["asks"]: 
                price = ask["price"] 
                size = ask["size"] 

Keeping track

On a continuous loop, keep recording all the incoming bids and asks and update your local orderbook.

Python
def handler(ws: IndexerSocket, message: dict):
    if message["type"] == "connected":
        ws.order_book.subscribe(ETH_USD, False)
    elif message["channel"] == "v4_orderbook" and "contents" in message:
        # Modify the above snippet. 
        # For full snapshot (initial subscribed message), reset the orderbook. 
        if message["type"] == "subscribed": 
            orderbook["bids"] = {} 
            orderbook["asks"] = {} 
        # Process bids levels. 
        if "bids" in contents:
            for bid in contents["bids"]:
                process_price_level(bid, "bids") 
 
        # Process asks levels. 
        if "asks" in contents:
            for ask in contents["asks"]:
                process_price_level(ask, "asks") 
 
# Orderbook state. Levels are stored as [price, size, offset]. 
orderbook = { 
    "bids": {}, 
    "asks": {} 
} 
 
def process_price_level(level, side): 
    """Process a single price level (bid or ask)"""
    if isinstance(level, dict): 
        # Full snapshot format 
        price = level["price"] 
        size = level["size"] 
        offset = level.get("offset", "0") 
    else: 
        # Incremental update format 
        price = level[0] 
        size = level[1] 
        offset = level[2] if len(level) > 2 else "0"
 
    # Update local orderbook. 
    if float(size) > 0: 
        orderbook[side][price] = [price, size, offset] 
    elif price in orderbook[side]: 
        del orderbook[side][price] 

Uncrossing the orderbook

Given the decentralized nature of dYdX, sometimes, some of the bids will be higher than some of the asks.

If trader needs the orderbook uncrossed, then one way is to use the order of messages as a logical timestamp. That is, when a message is received, update a global locally-held offset. Each WebSockets update has a message-id which is a logical offset to use. Using a timestamp is also an option.

Python
# In the handler() function
# ...
        # Process asks levels.
        if "asks" in contents:
            for ask in contents["asks"]:
                process_price_level(ask, "asks")
        
        # Uncross the orderbook.
        uncross_orderbook()
 
def get_sorted_book():
    """Get sorted lists of bids and asks"""
    bids_list = list(orderbook["bids"].values())
    asks_list = list(orderbook["asks"].values())
 
    bids_list.sort(key=lambda x: float(x[0]), reverse=True)
    asks_list.sort(key=lambda x: float(x[0]))
 
    return bids_list, asks_list
 
def uncross_orderbook():
    """Remove crossed orders from the orderbook"""
    bids_list, asks_list = get_sorted_book()
 
    if not bids_list or not asks_list:
        return
 
    top_bid = float(bids_list[0][0])
    top_ask = float(asks_list[0][0])
 
    while bids_list and asks_list and top_bid >= top_ask:
        bid = bids_list[0]
        ask = asks_list[0]
 
        bid_price = float(bid[0])
        ask_price = float(ask[0])
        bid_size = float(bid[1])
        ask_size = float(ask[1])
        bid_offset = int(bid[2]) if len(bid) > 2 else 0
        ask_offset = int(ask[2]) if len(ask) > 2 else 0
 
        if bid_price >= ask_price:
            # Remove older entry.
            if bid_offset < ask_offset:
                bids_list.pop(0)
            elif bid_offset > ask_offset:
                asks_list.pop(0)
            else:
                # Same offset, handle based on size.
                if bid_size > ask_size:
                    asks_list.pop(0)
                    bid[1] = str(bid_size - ask_size)
                elif bid_size < ask_size:
                    ask[1] = str(ask_size - bid_size)
                    bids_list.pop(0)
                else:
                    # Both filled exactly.
                    asks_list.pop(0)
                    bids_list.pop(0)
        else:
            # No crossing.
            break
 
        if bids_list and asks_list:
            top_bid = float(bids_list[0][0])
            top_ask = float(asks_list[0][0])
 
    # Update the orderbook with uncrossed data.
    orderbook["bids"] = {bid[0]: bid for bid in bids_list}
    orderbook["asks"] = {ask[0]: ask for ask in asks_list}

Additional logic

Now with an always up-to-date orderbook, implement your trading strategy based on this data. For simplicity here, we'll just print the current state.

Python
def print_orderbook():
    """Print n levels"""
    bids_list, asks_list = get_sorted_book()
 
    print(f"\n--- Orderbook for {ETH_USD} ---")
 
    # Print asks. 
    for price, size in reversed(asks_list): 
        print(f"ASK: {price:<12} | {size:<16}") 
 
    print("----------------") 
 
    # Print bids. 
    for price, size in bids_list: 
        print(f"BID: {price:<12} | {size:<16}") 
 
    print("")
# ... 
print_orderbook()