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:
Microprice=sb+sasbpa+sapbwhere
- pb and pa are the best bid and ask prices
- sb and sa are the sizes at the best bid and ask price levels
To calculate book imbalance over N levels, we will use the following formula:
ImbN=∑i=1Nsb,i+∑i=1Nsa,i∑i=1Nsb,i−∑i=1Nsa,iwhere
- sb,i is the size at the i-th best bid price level
- sa,i is the size at the i-th best ask price level
- N is the number of levels to aggregate (e.g., N=5 for top 5 levels)
- ImbN 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.
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.