Depth Metrics¶
Order book depth computation: price-level volume tracking, depth summary metrics in basis-point bins, spread extraction, and depth filtering.
PriceLevelBook is the book at L2: the size at each price level, kept one
depth row at a time. DepthMetricsEngine builds the depth summary on it, and
price_level_snapshots replays it to give the book at many instants, so a
snapshot's touch always equals the depth summary's at that instant.
PriceLevelBook ¶
The book at L2: the size resting at each price level, kept one row at a time.
Each side is a pair of parallel numpy arrays sorted ascending by price, so
the best ask is the first entry of its side and the best bid the last.
Lookups are a searchsorted, and evicting crossed levels is one slice.
:meth:update applies one row of the depth table: a positive volume sets
the size at that price, and zero removes the price level. A positive
update also evicts every opposing price level it strictly crosses. The
fresh quote is trusted over the older opposing level, whose delete is most
likely missing from the feed. Equal prices are left alone, so a locked
book is kept.
:class:DepthMetricsEngine builds the depth summary on this class, so a
book kept here holds the depth summary's touch after every row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
price_dtype
|
numpy dtype
|
The dtype prices are held in: |
int64
|
Attributes:
| Name | Type | Description |
|---|---|---|
bid_prices, bid_volumes |
ndarray
|
The bid side, ascending by price. |
ask_prices, ask_volumes |
ndarray
|
The ask side, ascending by price. |
update ¶
Apply one depth row and return whether it evicted opposing levels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
price
|
int or float
|
The price level, in the book's price units. |
required |
volume
|
float
|
The size now resting at price; |
required |
side
|
int
|
|
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
price_level_snapshots ¶
price_level_snapshots(
depth: DataFrame,
times: Iterable[datetime | Timestamp],
max_levels: int | None = None,
) -> list[OrderBookSnapshot]
Return the book at L2 at each of times, replayed from the depth table.
One pass over depth drives a :class:PriceLevelBook, the book
:class:DepthMetricsEngine builds the depth summary on, and copies it out
at each instant. So the touch of each snapshot equals the depth summary's
last row at or before that instant, and a stale level that a fresher
opposing quote crossed is evicted here as it is there. This is the L2
counterpart of :func:ob_analytics.analytics.order_book, which rebuilds
the per-order book at one instant from the events.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
DataFrame
|
The depth table: |
required |
times
|
iterable of datetime.datetime or pandas.Timestamp
|
The instants to take a snapshot at, in any order. A row counts at an instant when its timestamp is at or before it. |
required |
max_levels
|
int
|
Keep only this many price levels per side, nearest the touch first.
|
None
|
Returns:
| Type | Description |
|---|---|
list of OrderBookSnapshot
|
One per entry of times, in the same order. Each has the shape
:func: |
Raises:
| Type | Description |
|---|---|
TypeError
|
If an instant and the depth timestamps disagree about being time-zone aware. |
DepthMetricsEngine ¶
Incrementally compute order book depth metrics.
Replaces the monolithic :func:depth_metrics function with a
stateful, testable class. :meth:compute processes a whole depth
frame; internally each event is applied via :meth:update_side,
which writes one metrics row into a pre-allocated numpy buffer.
The book itself is a :class:PriceLevelBook, which holds each side as
parallel numpy arrays sorted ascending by integer price. BPS-bin sums
vectorize over the in-window slice of those arrays instead of iterating
every active level in Python (levels average ~1.8k per side on the
bundled sample, making that iteration the pipeline's former hot loop).
Output is written into a pre-allocated numpy matrix and converted to a
DataFrame once at the end; bin boundaries are @lru_cache-d.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
PipelineConfig
|
Pipeline configuration. |
None
|
compute ¶
Process an entire depth DataFrame and return metrics.
This is the main entry point, equivalent to the legacy
:func:depth_metrics function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
DataFrame
|
Price-level volume data with columns |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
Depth summary with |
update_side ¶
Process one depth event and write a metrics row into out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
price
|
int
|
Price as an integer tick count. |
required |
volume
|
float
|
Volume at this price level (0 means deletion). |
required |
side
|
int
|
0 = bid, 1 = ask. |
required |
out
|
ndarray
|
Pre-allocated 1-D array of length |
required |
depth_metrics ¶
Compute limit order book depth metrics.
This is a convenience wrapper around :class:DepthMetricsEngine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
DataFrame
|
DataFrame containing depth data. |
required |
bps
|
int
|
Basis points increment for volume bins. Default is 25. |
25
|
bins
|
int
|
Number of bins to use for volume aggregation. Default is 20. |
20
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame containing depth metrics over time. |
price_level_volume ¶
Calculate the cumulative volume for each price level over time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
DataFrame
|
A pandas DataFrame containing limit order events. Sizes are the
pipeline's integer lots, or floats such as the base-asset sizes of a
table converted for display. Float sizes on a decimal grid are summed
exactly on that grid, so a level that empties reads exactly |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
A pandas DataFrame with the cumulative volume for each price level:
one row per change to a level, holding the level's volume after the
event named by |
Warns:
| Type | Description |
|---|---|
UserWarning
|
When the events would take a level's size below zero. That means
they take more size off the level than they put on it, which a loader
that follows the schema's |
filter_depth ¶
Filter depth data within a specified time range.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
d
|
DataFrame
|
DataFrame containing depth data. |
required |
from_timestamp
|
Timestamp
|
Start of the time range. |
required |
to_timestamp
|
Timestamp
|
End of the time range. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
Filtered depth data within the specified time range. |
get_spread ¶
Extract the bid/ask spread from the depth summary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth_summary
|
DataFrame
|
A pandas DataFrame containing depth summary statistics. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
A pandas DataFrame with the bid/ask spread data. |
readable_quotes ¶
Return the rows of quotes whose book can be read as a price.
This is the package's one test of a usable quote. The measurements
that read a quote apply it: the mid a trade arrived into
(:func:~ob_analytics.trade_sign.mid_before, and so Lee-Ready and the
effective spread), the mid at an instant
(:func:~ob_analytics.trade_sign.prevailing_mid), the spread a hidden
trade printed inside (:func:~ob_analytics.hidden_liquidity.hidden_trades),
the book columns of the feature table
(:func:~ob_analytics.features.features) and the spread line on the depth
heatmap.
Two states get through a depth summary that are not books anything could have traded against, and both would otherwise reach a reader as ordinary numbers:
A side with nothing resting on it. The depth engine writes a price
and a volume of 0 for an empty side, which is a marker and not a
price. Taken at face value it makes a spread the width of the whole
instrument, a mid at half the other side, and a micro-price of zero:
three finite numbers, none of them true, and none of them marked.
A crossed book, where the best bid is above the best ask. A diff feed can hold genuinely crossed resting orders, so this is an expected state on such a feed rather than a fault, but its midpoint is not a price and its spread is negative. A locked book, bid equal to ask, is a real state at a spread of zero and is kept.
A reader that drops these rows reaches back to the last quote that could be
read, the way it reaches back over any other instant with no quote of its
own. The test reads the first bid/ask pair the frame carries:
best_bid_price / best_ask_price, best_bid / best_ask, or
bid / ask. A frame with none of them cannot be tested and is
returned unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quotes
|
DataFrame
|
Book snapshots: a pipeline |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
The readable rows, in their original order. |