Support

Microprice and book imbalance

In this example, we'll explore different ways to estimate the fair price of a financial instrument using market data. Specifically, we'll examine microprice and book imbalance to better under order book dynamics.

Overview

We'll use the MBP-10 schema ("L2" data) which shows all trades, as well as all updates to the top 10 levels of the order book. We'll plot various ways you can use microprice or book imbalance when analyzing price action for an instrument.

The standard formula for microprice, which only uses the top-of-book, is shown below:

where

  • and are the best bid and ask prices
  • and are the sizes at the best bid and ask price levels

To calculate book imbalance over N levels, we will use the following formula:

where

  • is the size at the i-th best bid price level
  • is the size at the i-th best ask price level
  • is the number of levels to aggregate (e.g., N=5 for top 5 levels)
  • ranges from -1 to 1

Example

import databento as db
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

def calc_microprice(
    df_mbp10: pd.DataFrame,
) -> pd.DataFrame:
    """
    Calculate top-of-book microprice.
    """
    numerator = (
        (df_mbp10["bid_sz_00"] * df_mbp10["ask_px_00"]) +
        (df_mbp10["ask_sz_00"] * df_mbp10["bid_px_00"])
    )
    denominator = df_mbp10["bid_sz_00"] + df_mbp10["ask_sz_00"]

    df_mbp10["Microprice"] = numerator / denominator

    return df_mbp10


def calc_book_imbalance(
    df_mbp10: pd.DataFrame,
    n: int,
) -> pd.DataFrame:
    """
    Calculate book imbalance over N levels of the order book.

    If a positive value, there is more size on the bid than the offer.
    If a negative value, there is more size on the offer than the bid.
    If the size is equal, or there is no size on either the bid or the ask, the value will be 0.
    """
    total_bid_size = df_mbp10[[f"bid_sz_{i:02}" for i in range(n)]].sum(axis=1).astype("int64")
    total_ask_size = df_mbp10[[f"ask_sz_{i:02}" for i in range(n)]].sum(axis=1).astype("int64")

    s = ((total_bid_size - total_ask_size) / (total_bid_size + total_ask_size)).fillna(0)
    df_mbp10[f"Book imbalance ({n})"] = s

    return df_mbp10

def plot_microprice(
    df_mbp10: pd.DataFrame,
    symbol: str,
) -> None:
    """
    Plot microprice against midprice and trades.
    """
    df_trades = df_mbp10[df_mbp10["action"] == db.Action.TRADE]

    df_mbp10["Midprice"] = (df_mbp10["bid_px_00"] + df_mbp10["ask_px_00"]) / 2
    df_mbp10 = calc_microprice(df_mbp10)

    y_tick_range = np.arange(
        min(df_mbp10["bid_px_00"].min(), df_trades["price"].min()),
        max(df_mbp10["ask_px_00"].max() + tick_size, df_trades["price"].max() + tick_size),
        tick_size,
    )

    # Plot
    df_mbp10.plot(
        y=["Midprice", "Microprice"],
        title=f"{symbol} Midprice vs. Microprice",
        xlabel="Time (UTC)",
        ylabel="Price",
        drawstyle="steps-post",
    )

    # Plot trades
    plt.scatter(
        df_trades.index,
        df_trades["price"],
        color="C2",
        marker=".",
        s=100,
        label="Trades",
    )

    plt.yticks(y_tick_range)
    plt.legend()
    plt.show()

def plot_book_imbalance(
    df_mbp10: pd.DataFrame,
    symbol: str,
    n: int = 10,
) -> None:
    """
    Plot book imbalance over N levels of the book, along with trades.
    """
    df_trades = df_mbp10[df_mbp10["action"] == db.Action.TRADE]

    df_mbp10 = calc_book_imbalance(df_mbp10, n)

    # Plot
    ax1 = df_mbp10[f"Book imbalance ({n})"].plot(
        drawstyle="steps-post",
        title=f"{symbol} Book Imbalance ({n} {'level' if n == 1 else 'levels'})",
        xlabel="Time (UTC)",
        ylabel="Imbalance",
    )
    ax1.set_ylim(-1, 1)

    # Plot trades
    df_trades["is_buy"] = np.where(df_trades["side"] == "B", 1, -1)
    ax2 = ax1.twinx()
    ax2.scatter(
        df_trades.index,
        df_trades["is_buy"],
        color="C2",
        marker=".",
        s=150,
        label="Trades",
    )

    lines1, labels1 = ax1.get_legend_handles_labels()
    lines2, labels2 = ax2.get_legend_handles_labels()
    ax2.legend(lines1 + lines2, labels1 + labels2)
    ax2.grid(False)
    ax2.set_yticks([-1, 1])
    ax2.set_yticklabels(["Sell", "Buy"])
    ax2.set_ylabel("Trade Side")
    plt.show()

if __name__ == "__main__":
    # Set parameters
    dataset = "XNAS.ITCH"
    symbol = "TSLA"
    tick_size = 0.01
    start = "2025-09-10T14:00:00"
    end = "2025-09-10T14:00:05"

    # Create historical client
    client = db.Historical("$YOUR_API_KEY")

    # Download MBP-10 data and convert to a DataFrame
    mbp10_df = client.timeseries.get_range(
        dataset=dataset,
        symbols=symbol,
        start=start,
        end=end,
        schema="mbp-10",
    ).to_df()

    # Plot top-of-book microprice
    plot_microprice(mbp10_df, symbol)

    # Plot book imbalance with 10 levels of the orderbook
    plot_book_imbalance(mbp10_df, symbol, 10)

Results

First, we'll take a look at the microprice and compare it to the midprice. Microprice will take into account the size on the bid and offer.

Microprice

Since microprice is sensitive to changes in the orderbook, it may offer some predictive power for future price movements.

Below, we'll take a look at book imbalance, which is a calculated indicator between -1 and 1. Book imbalance can look at multiple levels of the order book. Positive values mean there is more cumulative size on the bid than the offer, negative values mean there is more cumulative size on the offer, and a zero value means the bid and offer have the same total size.

Book imbalance