See the NYSE Arca auction documentation for more information.
Analyze auction imbalance dynamics
The closing auction for US Equities is one of the most important events of the trading day. With nearly 10% of average daily volume transacting on the close, the dynamics around price discovery are critical, as these closing prices are used in various use cases, such as benchmark pricing for index funds. While different exchanges share many common behaviors during the closing auction, there are some microstructure differences, such as accepted order types, that can affect the price discovery mechanism.
Overview
We'll use the historical client to request data for the closing auction. The imbalance schema provides auction imbalance data such as reference price, paired quantity, and auction status. We'll also use the OHLCV-1m schema to look at the price and volume transacting on the limit order book.
We'll compare the closing auction process for SPY on NYSE Arca and QQQ on Nasdaq. Due to differences in the data disseminated by the exchange, we'll use different fields for Nasdaq and NYSE.
For both venues, the ref_price field represents the price at which imbalance shares are being calculated.
This price is generally within the current BBO on the limit order book.
For Nasdaq, we'll look at the cont_book_clr_price field, which represents the price at which auction and continuous orders would transact.
For NYSE Arca, we'll look at the ind_match_price field instead.
This field represents the price at which the highest number of shares would trade, subject to auction collars.
Example
import datetime as dt
from zoneinfo import ZoneInfo
import databento as db
import pandas as pd
from matplotlib import dates as mdates
from matplotlib import pyplot as plt
def get_imbalance_df(
client: db.Historical,
dataset: str,
symbol: str,
start: dt.datetime,
end: dt.datetime,
) -> pd.DataFrame:
"""Get imbalance schema data"""
imbalance_df = client.timeseries.get_range(
dataset=dataset,
symbols=symbol,
schema="imbalance",
start=start,
end=end,
).to_df(tz=start.tzinfo)
return imbalance_df
def get_ohlcv_df(
client: db.Historical,
dataset: str,
symbol: str,
start: dt.datetime,
end: dt.datetime,
) -> pd.DataFrame:
"""Get OHLCV schema data"""
ohlcv_df = client.timeseries.get_range(
dataset=dataset,
symbols=symbol,
schema="ohlcv-1s",
start=start,
end=end,
).to_df(tz=start.tzinfo)
return ohlcv_df
def plot_closing_auction(
venue: str,
symbol: str,
imbalance_df: pd.DataFrame,
ohlcv_df: pd.DataFrame,
start_time: dt.time,
end_time: dt.time,
) -> None:
imbalance_df = imbalance_df.between_time(start_time, end_time)
ohlcv_df = ohlcv_df.between_time(start_time, end_time)
ohlcv_df["cumulative_volume"] = ohlcv_df["volume"].cumsum()
# Create the subplots
_, (ax1, ax2) = plt.subplots(
2, 1,
figsize=(14, 9),
sharex=True,
gridspec_kw={"height_ratios": [3, 1]},
)
# First subplot will display prices
ax1.plot(
imbalance_df.index,
imbalance_df["ref_price"],
label="Reference Price",
drawstyle="steps-pre",
)
ax1.plot(
ohlcv_df.index,
ohlcv_df["close"],
label="Last Price",
drawstyle="steps-post",
)
ax1.plot(
imbalance_df.index,
imbalance_df["uncrossing_price"],
label="Theoretical Uncrossing Price",
drawstyle="steps-post",
)
ax1.set_ylabel("Price")
ax1.grid(True)
ax1.legend(loc="best")
ax1.set_title(f"{venue} Closing Auction - {symbol}")
# Second subplot will display traded volume and auction paired quantity
ax2.plot(
ohlcv_df.index,
ohlcv_df["cumulative_volume"],
label="Cumulative Volume",
drawstyle="steps-post",
)
ax2.plot(
imbalance_df.index,
imbalance_df["paired_qty"],
label="Paired Quantity",
drawstyle="steps-post",
)
ax2.set_xlabel("Time (ET)")
ax2.set_ylabel("Volume")
ax2.grid(True)
ax2.legend(loc="best")
ax2.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S", tz=start_time.tzinfo))
plt.xticks(rotation=45)
plt.show()
# Set parameters
tz = ZoneInfo("America/New_York")
start = dt.datetime(2025, 11, 5, 15, 58, 0, tzinfo=tz)
end = dt.datetime(2025, 11, 5, 16, 0, 1, tzinfo=tz)
client = db.Historical("YOUR_API_KEY")
# First, we'll look at SPY data on NYSE Arca (ARCX.PILLAR)
arcx_symbol = "SPY"
arcx_dataset = "ARCX.PILLAR"
arcx_ohlcv_df = get_ohlcv_df(client, arcx_dataset, arcx_symbol, start, end)
arcx_imbalance_df = get_imbalance_df(client, arcx_dataset, arcx_symbol, start, end)
arcx_imbalance_df = arcx_imbalance_df.rename(columns={"ind_match_price": "uncrossing_price"})
arcx_imbalance_df = arcx_imbalance_df[["ref_price", "uncrossing_price", "paired_qty", "symbol"]]
print(arcx_imbalance_df)
plot_closing_auction(
"ARCX",
arcx_symbol,
arcx_imbalance_df,
arcx_ohlcv_df,
dt.time(15, 58, 0, tzinfo=tz),
dt.time(16, 0, 0, tzinfo=tz),
)
# Next, we'll look at QQQ data on Nasdaq (XNAS.ITCH)
xnas_symbol = "QQQ"
xnas_dataset = "XNAS.ITCH"
xnas_ohlcv_df = get_ohlcv_df(client, xnas_dataset, xnas_symbol, start, end)
xnas_imbalance_df = get_imbalance_df(client, xnas_dataset, xnas_symbol, start, end)
xnas_imbalance_df = xnas_imbalance_df.rename(columns={"cont_book_clr_price": "uncrossing_price"})
xnas_imbalance_df = xnas_imbalance_df[["ref_price", "uncrossing_price", "paired_qty", "symbol"]]
print(xnas_imbalance_df)
plot_closing_auction(
"XNAS",
xnas_symbol,
xnas_imbalance_df,
xnas_ohlcv_df,
dt.time(15, 58, 0, tzinfo=tz),
dt.time(16, 0, 0, tzinfo=tz),
)
Results
First, we'll take a look at the closing auction for SPY on NYSE Arca.
See also
ref_price uncrossing_price paired_qty symbol
ts_recv
2025-11-05 15:58:00.061423353-05:00 677.715 684.49 951885 SPY
2025-11-05 15:58:01.055768239-05:00 677.760 676.22 1193528 SPY
2025-11-05 15:58:02.054678442-05:00 677.760 676.50 1194940 SPY
2025-11-05 15:58:03.056152198-05:00 677.760 676.50 1195106 SPY
2025-11-05 15:58:04.054268311-05:00 677.720 676.18 1200714 SPY
... ... ... ... ...
2025-11-05 15:59:55.052843213-05:00 677.670 677.81 1401805 SPY
2025-11-05 15:59:56.050469422-05:00 677.600 677.76 1402565 SPY
2025-11-05 15:59:57.051653917-05:00 677.590 677.72 1402565 SPY
2025-11-05 15:59:58.052726733-05:00 677.530 677.68 1403065 SPY
2025-11-05 15:59:59.050924091-05:00 677.470 677.60 1403218 SPY
Next, we'll look at the closing auction for QQQ on Nasdaq. You'll notice that in both examples, these theoretical uncrossing prices will converge towards the last traded price as we approach the close.
See alsoSee the Nasdaq Closing Cross documentation for more information.
ref_price uncrossing_price paired_qty symbol
ts_recv
2025-11-05 15:58:00.060035531-05:00 623.49 621.42 677169 QQQ
2025-11-05 15:58:01.019052477-05:00 623.52 622.10 677696 QQQ
2025-11-05 15:58:02.055023604-05:00 623.52 622.06 678796 QQQ
2025-11-05 15:58:03.010276615-05:00 623.54 622.20 678836 QQQ
2025-11-05 15:58:04.040411490-05:00 623.47 622.00 699336 QQQ
... ... ... ... ...
2025-11-05 15:59:55.008542818-05:00 623.43 623.43 827157 QQQ
2025-11-05 15:59:56.099171134-05:00 623.37 623.37 826774 QQQ
2025-11-05 15:59:57.059011900-05:00 623.36 623.36 826774 QQQ
2025-11-05 15:59:58.025345576-05:00 623.27 623.27 826393 QQQ
2025-11-05 15:59:59.032786745-05:00 623.31 623.31 826393 QQQ