Skip to content

Analytics

Format-agnostic post-processing analytics. These functions work with the output of any format's pipeline run (Bitstamp, LOBSTER, or custom).

Trade Analysis

order_aggressiveness

order_aggressiveness(
    events: DataFrame, depth_summary: DataFrame
) -> pd.DataFrame

Calculate order aggressiveness with respect to the best bid or ask in BPS.

Each new limit order is measured against the best price on its own side of the book as it stood just before the order arrived: the last depth_summary row strictly earlier in the canonical event order (:func:~ob_analytics.schemas.time_order_keys -- timestamp, then event_id between rows at the same instant), found by :func:~ob_analytics.trade_sign.quote_before. A positive value means the order improved on that price, a negative one sat behind it.

Parameters:

Name Type Description Default
events DataFrame

The events DataFrame (must contain direction, action, type, timestamp, event_id, price columns).

required
depth_summary DataFrame

The order book summary statistics DataFrame (must contain timestamp and the best_bid_price / best_ask_price columns; an event_id column orders rows that share a timestamp).

required

Returns:

Type Description
DataFrame

The events DataFrame with an added aggressiveness_bps column.

trade_impacts

trade_impacts(trades: DataFrame) -> pd.DataFrame

Generate a DataFrame containing order book impact summaries.

Aggregates trade records by taker order ID to summarise how each aggressive order swept through the book (price range, number of fills, total volume, VWAP, duration).

Parameters:

Name Type Description Default
trades DataFrame

The trades DataFrame (must contain taker, price, volume, timestamp, direction columns).

required

Returns:

Type Description
DataFrame

A DataFrame summarising market order impacts with columns: id, min_price, max_price, vwap, hits, vol, start_time, end_time, dir. The price-valued columns (min_price, max_price, vwap) are in the quote currency and vol in the instrument's own units, whether the trades hold integer ticks and lots (with tick_size and lot_size on the frame's attrs, as on a pipeline result) or display prices and sizes.

Raises:

Type Description
ConfigError

If a required column is missing, or the prices or sizes are integers and the frame carries no tick_size or lot_size. Also if trades is empty.

Order Type Classification

set_order_types

set_order_types(
    events: DataFrame, trades: DataFrame
) -> pd.DataFrame

Determine limit order types.

Classifies each order as one of: market, resting-limit, flashed-limit, or market-limit, based on how the order interacts with the book over its lifetime.

market and market-limit mark takers, so they need the trades' taker ids. A feed that shows resting orders only (see :class:~ob_analytics.protocols.TradeAttribution) has none, and no order is labelled either; that is the feed, not a session without market orders. LOBSTER fills its taker ids with a guess, so its labels are only as good as the guess.

Parameters:

Name Type Description Default
events DataFrame

The limit order events DataFrame.

required
trades DataFrame

The executions DataFrame.

required

Returns:

Type Description
DataFrame

The events DataFrame with an updated 'type' column indicating order types.

Order Book Reconstruction

The reconstructions themselves live in the order-book engine; these are their frame-level faces.

OrderBookSnapshot

Bases: TypedDict

The order book at one instant, as :func:order_book returns it.

A plain dictionary at run time; the type names its three fixed keys so that callers indexing it get a DataFrame rather than a wide union.

Attributes:

Name Type Description
timestamp datetime or Timestamp

The instant the book was evaluated at.

bids DataFrame

Active bid orders, best first.

asks DataFrame

Active ask orders, best last.

order_book

order_book(
    events: DataFrame,
    tp: datetime | None = None,
    max_levels: int | None = None,
    bps_range: int = 0,
    uncross: bool = False,
) -> OrderBookSnapshot

Reconstruct the order book at a specific point in time.

The reconstruction itself is :func:ob_analytics.engine.book_state; this function is its frame adapter, and owns the display window (max_levels, bps_range) the engine has no opinion about.

Parameters:

Name Type Description Default
events DataFrame

DataFrame containing order events.

required
tp datetime or Timestamp

The point in time at which to evaluate the order book. If None, uses the latest event timestamp in the data.

None
max_levels int

The maximum number of price levels to include for bids and asks.

None
bps_range int

Basis points range to filter the bids and asks. Default is 0.

0
uncross bool

When True, evict crossed resting orders so the snapshot satisfies best_bid < best_ask — a display convenience, by the rule the depth summary uncrosses its touch with. At the crossed touch, the order whose latest event is older goes first, and orders at one price are walked in that order. This is the library's only way to uncross a per-order book: to draw an uncrossed book_snapshot or depth_chart, pass the book it returns. The default is False: the reconstruction stays faithful to the feed, so a diff feed's genuinely crossed resting orders (see :class:~ob_analytics.protocols.FeedType) are replayed as-is rather than silently uncrossed. Has no effect on a matched-book feed, which is never crossed.

False

Returns:

Type Description
OrderBookSnapshot

A dictionary containing: - 'timestamp': The evaluation timestamp. - 'asks': DataFrame of active ask orders. - 'bids': DataFrame of active bid orders.

Each order's price is the price it rests at: the price of its latest created row, or of a later changed row with no fill. A row that reports a fill does not move the order, so price can differ from the price on the order's latest row. The depth table and the queue tables place orders at the same price.

order_lifecycles

order_lifecycles(events: DataFrame) -> pd.DataFrame

Collapse events into one row per order: placement → outcome.

The canonical lifecycle table (one derivation, shared by the L3 faces and the order-book reconstruction). A thin frame wrapper over :func:ob_analytics.engine.order_lifecycles, which relies on the schemas.py volume contract: volume is the outstanding size after each event and fill the executed delta, so an order is terminated when a deleted row arrives or its outstanding size reaches zero — the latter is how fully-executed LOBSTER orders end, which never emit a deleted event.

Parameters:

Name Type Description Default
events DataFrame

Events satisfying the schemas.py contract. Orders without a created row (pre-existing book, hidden executions) are excluded — their placement is unknown.

required

Returns:

Type Description
DataFrame

One row per order id:

  • id, direction, price — placement identity.
  • type — classifier label (when the column is present).
  • placed_ts, placed_vol — from the created row.
  • filled_vol — total executed quantity (Σ fill).
  • end_ts — termination time (NaT while still resting; callers clip to their window end for display).
  • outcome — filled / partial / cancelled / resting. Flashed orders are the cancelled subset whose type is flashed-limit.
  • aggressiveness_bps — placement distance (when present).

Data Quality

See Data quality: matched book, diff feed and price levels for the concepts and the audit how-to for the CLI.

data_quality_summary

data_quality_summary(
    events: DataFrame,
    trades: DataFrame,
    *,
    feed_type: FeedType = FeedType.UNKNOWN,
    depth: DataFrame | None = None,
    tick_size: float = 1.0,
    sequence_kind: SequenceKind = SequenceKind.CONTIGUOUS,
    sequence_restarts: int = 0,
    trade_attribution: TradeAttribution = TradeAttribution.BOTH,
    clocks: Clocks = Clocks.BOTH,
    venue_book: BookLevels | None = None,
) -> DataQualitySummary

Summarise the data quality of one reconstructed session.

Surfaces the health signals that matter before trusting a feed — most importantly how crossed the resting book is, which distinguishes a matched book from a diff feed (see :class:~ob_analytics.protocols.FeedType).

A source whose feed flags records that come after a gap it could not repair (Databento) puts their count on events.attrs under :data:~ob_analytics.schemas.MAYBE_BAD_BOOK_ATTR; the summary reports it as maybe_bad_book_records.

For a pipeline run, call :meth:~ob_analytics.pipeline.PipelineResult.audit instead: it passes the run's tables, tick size and declarations (:class:~ob_analytics.protocols.FeedDeclarations) for you. Pass the declarations here yourself only for tables that are not a run's.

Like :func:detect_stale_orders, this takes either the pipeline's own tables or the tables :func:~ob_analytics.visualization.display_result converts for display, and measures the same crossing and the same stale orders on both.

Parameters:

Name Type Description Default
events DataFrame

Classified events (must carry the canonical columns and the type column from :func:set_order_types). For a price-level (L2) run this is the empty, schema-valid frame from :func:~ob_analytics._utils.empty_events: the per-order metrics then report zero and crossing is read from depth (pass PipelineResult.depth).

required
trades DataFrame

The trades frame, with maker_event_id / taker_event_id.

required
feed_type FeedType

The source's declared feed type, recorded on the summary and used to interpret crossed_pct.

UNKNOWN
depth DataFrame

A faithful price-level-volume frame (e.g. PipelineResult.depth). When None it is computed from events via :func:~ob_analytics.depth.price_level_volume. Do not pass depth_summary — that is already uncrossed and would report ~0%.

None
tick_size float

Quote-currency size of one price tick (PipelineResult.config.tick_size), so the prices in stale_orders read in the quote currency. Leave at 1.0 to report them in the units of events["price"]: ticks for the pipeline's tables, the quote currency for display tables.

1.0
sequence_kind SequenceKind

What the venue sequence promises, passed to :func:detect_sequence_gaps. A capture records it in meta.json (read it with :func:~ob_analytics.capture_record.read_record).

CONTIGUOUS
sequence_restarts int

How many of the sequence's steps back the source counted as the count starting again at a new opening book. That many are left out of sequence_out_of_order. A capture records it in meta.json (read it with :func:~ob_analytics.capture_record.read_record).

0
trade_attribution TradeAttribution

Which orders of a trade the feed can name, so the unmatched-trades check looks only for those. Read it off the source with :func:~ob_analytics.protocols.trade_attribution_of; a capture records it in meta.json.

BOTH
clocks Clocks

Which clocks the data carries, so the clock checks run only when there are two to compare. With two, the checks still leave out rows from a capture's opening book (origin snapshot). Read it off the source with :func:~ob_analytics.protocols.clocks_of; a live capture records it in meta.json (read it with :func:~ob_analytics.capture_record.read_record). Data with no venue time at all (a price-level file without exchange_timestamp) is read as :attr:~ob_analytics.protocols.Clocks.RECEIVE_ONLY whatever is declared; an empty frame keeps the declaration.

BOTH
venue_book BookLevels

The venue's own book after every message, when the source supplies one: for LOBSTER, the orderbook file read by :func:~ob_analytics.lobster.lobster_venue_book. Row i is the book after the message whose original_number is i + 1, so events must carry original_number and be the pipeline's own tables (integer ticks and lots). The book rebuilt from events is then compared with it on every price and size of the levels it states. Without it, the venue_book check is skipped.

None

Returns:

Type Description
DataQualitySummary

Raises:

Type Description
ConfigError

If a venue_book is passed with events that lack original_number, are display tables, or name a message row the venue book does not have.

DataQualitySummary dataclass

DataQualitySummary(
    feed_type: FeedType,
    n_events: int,
    n_orders: int,
    n_trades: int,
    crossed_pct: float,
    crossed_episodes: int,
    unmatched_trades_pct: float,
    duplicate_event_ids: int,
    duplicate_created_ids: int,
    pre_existing_orders: int,
    events_with_sequence: int = 0,
    sequence_gaps: int = 0,
    sequence_out_of_order: int = 0,
    sequence_restarts: int = 0,
    sequence_kind: SequenceKind = SequenceKind.CONTIGUOUS,
    orphan_orders: int = 0,
    orphan_events: int = 0,
    nonpositive_price_rows: int = 0,
    negative_volume_rows: int = 0,
    negative_level_rows: int = 0,
    exchange_time_after_receive: int = 0,
    exchange_time_reordered: int = 0,
    clocks: Clocks = Clocks.BOTH,
    stale_orders: tuple[StaleOrder, ...] = (),
    trade_attribution: TradeAttribution = TradeAttribution.BOTH,
    trades_matched_by_order: int = 0,
    per_order_crossed_pct: float = 0.0,
    per_order_crossed_episodes: int = 0,
    venue_book_price_levels: int = 0,
    venue_book_rows: int = 0,
    venue_book_differing_rows: int = 0,
    venue_book_first_difference: BookDifference
    | None = None,
    maybe_bad_book_records: int | None = None,
)

Per-run data-quality metrics for a reconstructed session.

Built by :func:data_quality_summary. All percentages are 0–100 floats.

The fields are the measurements; :attr:checks turns them into pass/fail verdicts with a :class:Severity each, and :attr:ok is the one-line answer to "is this feed trustworthy?" that ob-analytics audit exits on.

Attributes:

Name Type Description
feed_type FeedType

The source's declared crossing invariant (see :class:~ob_analytics.protocols.FeedType); sets expectations for crossed_pct.

n_events, n_orders, n_trades int

Row / distinct-order / trade counts.

crossed_pct float

Percentage of session time the faithful book is crossed (best_bid > best_ask). Expected ~0 for a matched book or a price-level book; a genuine, faithfully-replayed property of a diff feed.

crossed_episodes int

Number of distinct crossed intervals.

per_order_crossed_pct float

Percentage of session time the book rebuilt from the per-order events is crossed. It differs from crossed_pct only when the depth table comes from somewhere else: a LOBSTER run reads its depth from the orderbook file, and its message file holds only the events inside the requested price levels, so an order cancelled outside them stays in the per-order book. The per-order views (order book snapshots, queue positions, order lifecycles) read that book. 0 on a price-level (L2) run, which has no per-order events.

per_order_crossed_episodes int

Number of distinct intervals the per-order book is crossed.

venue_book_price_levels int

How many price levels per side the venue's own book states (the venue_book passed to :func:data_quality_summary; for LOBSTER, the orderbook file). 0 when there is no venue book, or no book rebuilt from per-order events to compare with it: the venue_book check is then skipped.

venue_book_rows int

Book states compared: one per event, each after its own message.

venue_book_differing_rows int

Of those, the states where the book rebuilt from the per-order events differs from the venue's in any price or size of the best venue_book_price_levels levels of either side.

venue_book_first_difference BookDifference or None

Where the first of those states differs, or None when none does.

unmatched_trades_pct float

Percentage of trades with an order the feed can name left unresolved: a missing maker_event_id or taker_event_id when the feed names both, a missing maker_event_id when it names the maker only, and 0 when it names neither (see trade_attribution).

duplicate_event_ids int

Count of event_id values occurring more than once (event_id should be globally unique — any non-zero value is suspect).

duplicate_created_ids int

Count of order ids with more than one created event.

pre_existing_orders int

Distinct orders resting before the capture window (classifier label pre-existing — structurally unclassifiable, not failures).

events_with_sequence int

Rows carrying a non-null venue sequence (see :func:detect_sequence_gaps). 0 when the source has no sequence or track_sequence was off at load — the sequence metrics below are then trivially zero.

sequence_gaps int

Dropped-message count: skipped venue sequence numbers. Always 0 when sequence_kind is MONOTONIC, where a skip is normal.

sequence_out_of_order int

Reordered or duplicated messages: sequence steps that did not advance, leaving out sequence_restarts.

sequence_restarts int

Steps back where the source started again from a new opening book and the venue's count started again with it. The source reports them (sequence_restarts in meta.json); they are not faults of the sequence, and the resync is reported by the capture's own checks.

sequence_kind SequenceKind

What the venue sequence promises, and so whether sequence_gaps was checked (see :class:~ob_analytics.protocols.SequenceKind).

orphan_orders int

Distinct order ids with a changed or deleted event but no created one. Every order resting before the capture began is an orphan, so a capture that starts mid-stream reports a small, stable count; this is also the signal a live capture's stream drifting from its opening snapshot shows up as.

orphan_events int

Rows belonging to those orphan orders.

nonpositive_price_rows int

Rows priced at or below zero. Legal in the schema (prices are signed integer ticks) but not a tradeable level.

negative_volume_rows int

Rows with a negative volume (or negative fill): impossible size.

negative_level_rows int

Depth rows where the events would take a price level below zero: they took more size off the level than they put on it. The depth table holds such a level at zero, so it reads less than the orders the events show resting there. Counted from the events. Always 0 on a price-level (L2) run, whose depth rows negative_volume_rows already counts.

exchange_time_after_receive int

Rows whose venue clock (exchange_timestamp) is later than the local receive clock (timestamp) — an event received before it happened. Always 0 when clocks is not BOTH. Rows from a capture's opening book (origin snapshot) are not counted: the capture may not have measured their clocks.

exchange_time_reordered int

Steps where the venue clock goes backwards while the receive clock moves forward: messages that reached the capture out of order. Always 0 when clocks is not BOTH; the opening book's rows are left out, as for exchange_time_after_receive.

clocks Clocks

Which clocks the data carries (see :class:~ob_analytics.protocols.Clocks). The two clock checks run only when it is BOTH; with one clock there is nothing to compare, and the report says so.

stale_orders tuple of StaleOrder

Resting orders a trade printed through that the venue did not report again within :data:STALE_GRACE, worst first (see :func:detect_stale_orders). Each one stays in the rebuilt book after it has gone, so a diff feed's crossing is then partly this defect rather than the feed's normal lag.

trade_attribution TradeAttribution

Which orders of a trade the feed can name (see :class:~ob_analytics.protocols.TradeAttribution); sets which sides unmatched_trades_pct and trades_matched_by_order count.

trades_matched_by_order int

Trades whose taker is an order created with size 0. Bitstamp creates an instant order bought by quote amount that way and never reports what it executed, so a trade with no row of its size is matched to the order by its id (see :class:~ob_analytics.bitstamp.BitstampTradeReader). The order and its type are right; its rows show less fill than its trades. Always 0 when the feed does not name takers.

maybe_bad_book_records int or None

Records the venue flagged as coming after a gap in its feed that it could not repair, so the book may be wrong from the first of them on (Databento's F_MAYBE_BAD_BOOK). Read from the events attrs (:data:~ob_analytics.schemas.MAYBE_BAD_BOOK_ATTR). None when the events carry no count, as from a source that does not flag such records, and the check does not run.

venue_book_differing_pct property

venue_book_differing_pct: float

Percentage of the compared book states that differ from the venue's.

checks property

checks: tuple[QualityCheck, ...]

Every check this run was scored against, errors first.

The crossing check reads its severity off :attr:feed_type: a crossed resting book is a defect in a matched book or a price-level book and a faithful property of a diff feed, so the same number means opposite things and only the declared feed type can tell them apart.

errors property

errors: tuple[QualityCheck, ...]

Failed checks whose severity is :attr:Severity.ERROR.

warnings property

warnings: tuple[QualityCheck, ...]

Failed checks whose severity is :attr:Severity.WARNING.

ok property

ok: bool

True when no error-severity check failed (warnings may still stand).

to_dict

to_dict() -> dict[str, Any]

Return the summary as a plain, JSON-serialisable dict.

render

render() -> str

Return a fixed-width, human-readable report block.

QualityCheck dataclass

QualityCheck(
    name: str,
    passed: bool,
    severity: Severity,
    detail: str,
    skipped: bool = False,
)

One named data-quality check and how it read on this run.

Attributes:

Name Type Description
name str

Stable identifier, matching the summary field it reads (e.g. "duplicate_event_ids").

passed bool

Whether the data satisfied the check. False for a skipped check, which did not pass.

severity Severity

What a failure means (see :class:Severity).

detail str

One line saying what was found and how to read it.

skipped bool

Whether the check did not run, because the data cannot answer it. A skipped check is :attr:Severity.INFO and never fails a run; detail says why it did not run.

skip classmethod

skip(name: str, why: str) -> QualityCheck

A check that did not run, and why.

to_dict

to_dict() -> dict[str, Any]

Return the check as a plain, JSON-serialisable dict.

Severity

Bases: str, Enum

How much a failed data-quality check matters.

A check carries its severity so the policy — what fails a run — lives with the measurement rather than in each caller. The enum mixes in str (Severity.ERROR == "error"), which keeps CLI and JSON output plain.

Attributes:

Name Type Description
ERROR

The data contradicts something that must hold (a duplicate event_id, a dropped venue message, a negative volume). Any failing error check fails the run.

WARNING

A signal worth reading before trusting the feed, but one a sound capture can legitimately show (orders resting before the capture began, zero-priced levels, messages reordered in transit). Fails the run only under ob-analytics audit --strict.

INFO

Reported for context; never fails a run.

detect_sequence_gaps

detect_sequence_gaps(
    frame: DataFrame,
    *,
    sequence_col: str = SEQUENCE_COLUMN,
    order_col: str = INGEST_SEQ_COLUMN,
    group_cols: Sequence[str] = _SEQUENCE_GROUP_COLUMNS,
    kind: SequenceKind = SequenceKind.CONTIGUOUS,
) -> SequenceGapReport

Report missing or out-of-order venue sequence numbers in frame.

Parameters:

Name Type Description Default
frame DataFrame

Events or depth rows. A missing sequence_col (a source with no venue sequence) yields an empty, clean report — the column is optional.

required
sequence_col str

Column holding the venue's per-event sequence (nullable integer).

SEQUENCE_COLUMN
order_col str

Column defining ingest order; when absent, the frame's current row order is used instead.

INGEST_SEQ_COLUMN
group_cols sequence of str

Columns that identify one channel (instrument / venue). Only those present in frame are used; with none present the whole frame is one channel. Sequences from different channels are not comparable, so each group is scored on its own.

_SEQUENCE_GROUP_COLUMNS
kind SequenceKind

What the sequence promises. With :attr:~ob_analytics.protocols.SequenceKind.MONOTONIC a skipped number is normal, so nothing is counted as missing and only steps that do not rise are reported.

CONTIGUOUS

Returns:

Type Description
SequenceGapReport
Notes

Consecutive equal sequence values are collapsed before comparison, so a single venue update that emits several rows (e.g. every changed level of one book diff) counts once. A non-consecutive repeat still shows as out-of-order.

SequenceGapReport dataclass

SequenceGapReport(
    n_sequenced: int,
    n_updates: int,
    n_missing: int,
    n_out_of_order: int,
    max_gap: int,
    first_break_seq: int | None,
)

Result of scanning a frame's venue sequence column for breaks.

Built by :func:detect_sequence_gaps. A feed numbers each message on a channel with a sequence that should rise by exactly one; a skip is a dropped message and a step that does not rise is a reordered (or repeated) one. Rows are read in ingest order (ingest_seq when present, else the frame's row order) and grouped per instrument / venue when those columns exist, so interleaved channels are scored independently.

Attributes:

Name Type Description
n_sequenced int

Rows carrying a non-null venue sequence. 0 means the frame has nothing to check (the report is then trivially clean).

n_updates int

Distinct consecutive sequence values seen — one per venue message (a book update that emits several rows shares one sequence, counted once).

n_missing int

Total count of skipped sequence numbers: the dropped-message count.

n_out_of_order int

Steps where the sequence did not advance (a repeat or a decrease) — a reordered or duplicated message.

max_gap int

Largest single run of consecutive missing numbers (0 when none).

first_break_seq int or None

The last in-order sequence value before the first break, a hint for where to resync; None when the frame is clean.

has_sequence property

has_sequence: bool

Whether any row carried a venue sequence to check.

clean property

clean: bool

True when no missing and no out-of-order sequence values were found.

to_dict

to_dict() -> dict[str, Any]

Return the report as a plain, JSON-serialisable dict.

detect_stale_orders

detect_stale_orders(
    events: DataFrame,
    trades: DataFrame,
    *,
    grace: Timedelta = STALE_GRACE,
    depth: DataFrame | None = None,
    tick_size: float = 1.0,
) -> tuple[StaleOrder, ...]

Find resting orders a trade printed through that the venue left in place.

A trade above a resting ask, or below a resting bid, shows that the order had already left the book: a matching engine fills the better price first. The venue normally reports the order within milliseconds. An order it does not report again within grace stays in the rebuilt book although it is no longer at the venue, and distorts the spread, the depth and the queue from then on.

This only reports. It does not change what :func:order_book returns: the test needs to know what the feed said after the trade, which a live capture cannot know in time.

The tables can be the pipeline's own (prices in integer ticks, sizes in integer lots; pass tick_size) or the tables :func:~ob_analytics.visualization.display_result converts for display (prices in the quote currency, sizes in the base asset; leave tick_size at 1.0). Both give the same orders and the same times.

Parameters:

Name Type Description Default
events DataFrame

Order events with id, timestamp, price, volume, direction and action. Rows typed market (see :func:set_order_types) are skipped when a type column is present.

required
trades DataFrame

Trades with timestamp and price, in the same price units as events.

required
grace Timedelta

How long after the trade the venue may take to report the order. Defaults to :data:STALE_GRACE (one second).

STALE_GRACE
depth DataFrame

The faithful price-level-volume frame, used to measure how long each stale order held the touch. Computed from events with :func:~ob_analytics.depth.price_level_volume when None and an order is found.

None
tick_size float

Quote-currency size of one price tick, so the reported prices read in the quote currency. Leave at 1.0 to report them in the units of events["price"]: ticks for the pipeline's tables, the quote currency for display tables.

1.0

Returns:

Type Description
tuple of StaleOrder

Worst first: the longest time at the touch, then the longest time stale. Empty when the venue reported every order in time.

StaleOrder dataclass

StaleOrder(
    id: int | str,
    direction: str,
    price: float,
    disproved_at: Timestamp,
    stale_seconds: float,
    touch_seconds: float,
)

A resting order a trade printed through, which the venue did not report.

A matching engine cannot print a trade at a price worse than a resting order on the other side of it. So a trade above a resting ask, or below a resting bid, shows that the order had already left the book. When the venue then does not report the order again within a grace period, the rebuilt book goes on holding an order that is not there.

Attributes:

Name Type Description
id int or str

The order id, as the feed writes it: an integer on most venues, a UUID string on Independent Reserve.

direction str

"bid" or "ask".

price float

The price the order rests at, in the quote currency (ticks * tick_size).

disproved_at Timestamp

The time of the first trade that printed through it.

stale_seconds float

Seconds from that trade to the order's next event row, or to the end of the capture when it has none.

touch_seconds float

Seconds of that span in which the order's price was the best price on its side of the faithful book: how long it held the touch after the trade showed it was gone.

to_dict

to_dict() -> dict[str, Any]

Return the order as a plain, JSON-serialisable dict.

BookDifference dataclass

BookDifference(
    message_row: int,
    direction: str,
    price_level: int,
    price: float | None,
    volume: int,
    venue_price: float | None,
    venue_volume: int,
)

The first place the book rebuilt from the events differs from the venue's.

Attributes:

Name Type Description
message_row int

The original_number of the event after which the two books differ: the row of the venue's file that states the venue's book.

direction str

"bid" or "ask".

price_level int

The price level, counted from the touch (1 is the best price).

price, venue_price float or None

The price of that level in the rebuilt book and in the venue's, in the quote currency (ticks * tick_size); None where that book has no level there.

volume, venue_volume int

The size at that level in the rebuilt book and in the venue's, in integer lots; 0 where that book has no level there.

to_dict

to_dict() -> dict[str, Any]

Return the difference as a plain, JSON-serialisable dict.

describe

describe() -> str

The difference in words, for the report.