Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ gateway or whether the strategy should own that state itself.

### Add a Data Connector

Keep the core replay boundary venue-, asset-class-, and vendor-independent.
Databento and Binance are current reference integrations, not architectural
special cases.

1. Implement the public `DataSource` protocol.
2. Convert source data into the canonical MBO event schema.
3. Add a tiny fixture or generator that does not require private data.
Expand Down Expand Up @@ -157,8 +161,8 @@ Python engine before release:
- same order-intent log where the public API observes it.

Engines consume normalized `MBOEvent` rows. Do not make an engine responsible
for reading Databento, CSV, Parquet, or any other source format; that belongs in
a connector.
for reading vendor data, CSV, Parquet, or any other source format; that belongs
at the connector or reconstruction boundary.

Use `ordersim.testing.assert_equivalent_execution_engines` before trusting a new
engine implementation.
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ All notable public changes to `ordersim` are documented here.
trades aligned, every processed L2 endpoint reconciled, every joinable
book-ticker state matched, and an open challenge to compare fill models
against paired L3 or live execution evidence.
- Clarified the vendor- and asset-class-independent architecture: observed MBO
and reconstructed virtual MBO are two explicit fidelity paths, with
Databento and Binance documented as their current reference integrations.

## 0.1.3 - 2026-05-20

Expand Down
53 changes: 41 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,32 @@ interest include order-book replay, market replay, latency modeling,
queue-position effects, fill simulation, execution modeling, execution-aware
backtesting, and market microstructure research.

## Crypto L2, Reconstructed for MBO Replay
The replay core is independent of asset class, venue, and data vendor. It
operates on one canonical `MBOEvent` contract; integrations are responsible for
reaching that boundary without hiding the fidelity of their source data.

## Two Data Fidelity Paths

`ordersim` supports two explicit routes into the same queue-aware replay:

| Path | Source evidence | Current reference implementation | Replay input |
|---|---|---|---|
| Observed MBO | Order-level events with stable order IDs | Databento MBO | Observed `MBOEvent` rows |
| Reconstructed virtual MBO | Sequence-valid L2 depth plus individual trades | Binance USD-M | Modeled `MBOEvent` rows plus a reconstruction manifest |

Databento and Binance are the integrations available today, not boundaries of
the architecture. Futures, crypto, and equity datasets from additional venues
and vendors should join through one of these fidelity paths without changing
the replay or execution-engine APIs. Normalized CSV and Parquet remain
vendor-neutral interchange and storage formats.

### Observed MBO

When a source provides stable order IDs, its connector can normalize the
observed add, cancel, modify, and trade events directly into `MBOEvent`. This is
the highest-fidelity path. Databento is the current reference implementation.

### Reconstructed Virtual MBO for Crypto L2

Most crypto venues publish market-by-price depth, not the stable order IDs
needed for true market-by-order replay. `ordersim` takes a different path: it
Expand All @@ -28,7 +53,7 @@ price-level endpoint, and emits a deterministic **virtual MBO** stream under
explicit queue assumptions.

That means crypto data can use the same inspectable, queue-aware Python/C++
execution engines as native MBO data without pretending the inferred orders
execution engines as observed MBO data without pretending the inferred orders
were observed at the exchange.

The first full Binance USD-M study reconstructed:
Expand All @@ -43,7 +68,7 @@ and book consistency, not knowledge of Binance's hidden FIFO queue. The
recommended conservative policy and an optimistic sensitivity policy expose
that uncertainty instead of burying it inside one fill rule.

### The Crypto Realism Challenge
#### The Crypto Realism Challenge

Have a more realistic L2 execution model? Compare it with evidence. Use paired
L2/L3 data or live passive-order outcomes, disclose the queue and latency
Expand All @@ -65,8 +90,8 @@ join the [public validation challenge](https://github.com/tradingexpert/ordersim
strategy's orders, position, and portfolio state isolated.
- Exposes a small, regular Python API that is easy to read, debug, test, and
extend.
- Captures Binance L2 and individual-trade evidence and reconstructs modeled
MBO under explicit conservative and optimistic queue assumptions.
- Accepts observed MBO directly or reconstructed virtual MBO produced from
lower-fidelity evidence under an explicit, named model.

## What It Is Not

Expand Down Expand Up @@ -161,7 +186,8 @@ Install the current release from PyPI with:
pip install ordersim
```

Optional data integrations and file formats are installed separately as extras:
Current optional integrations and file formats are installed separately as
extras:

```bash
pip install "ordersim[databento]"
Expand All @@ -177,8 +203,9 @@ from ordersim import CsvSource
source = CsvSource("events.csv")
```

For repeated research runs, the recommended path is to normalize once,
materialize the canonical Parquet form, and replay from that thereafter:
For repeated research runs, the recommended path is to reach the canonical
`MBOEvent` boundary once, materialize Parquet, and replay from that thereafter.
For example, the current observed-MBO integration uses Databento:

```python
import databento as db
Expand All @@ -192,8 +219,8 @@ write_parquet(raw_source, "events.parquet")
source = ParquetSource("events.parquet")
```

Direct raw-source replay is still useful for one-off inspection and connector
development:
Direct source replay is still useful for one-off inspection and connector
development. With the current Databento integration:

```python
import databento as db
Expand Down Expand Up @@ -302,9 +329,11 @@ same input.
## Status

`0.1.x` is live on PyPI. The current public line includes the Python reference
engine, packaged C++ default, canonical connector -> Parquet -> replay workflow,
engine, packaged C++ default, canonical `MBOEvent` -> Parquet -> replay workflow,
latency models, economics, public execution-equivalence fixtures, and an
evidence-first Binance L2-to-virtual-MBO research path.
observed-MBO connector for Databento plus an evidence-first Binance USD-M
L2-to-virtual-MBO reference implementation. Both integrations feed the same
vendor- and asset-class-independent replay boundary.

Planned next milestones:

Expand Down
48 changes: 29 additions & 19 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ the rest.

```mermaid
flowchart LR
raw["Raw vendor data"]
connector["Connector<br/>DatabentoMboSource / CsvSource / ParquetSource"]
evidence["Source evidence<br/>observed MBO or L2 + trades"]
integration["Connector or<br/>named reconstruction model"]
canonical["Canonical MBOEvent stream"]
replay["Replay"]
recording["RecordingGateway"]
Expand All @@ -21,8 +21,8 @@ flowchart LR
valuation["Valuation marks"]
result["ReplayResult<br/>fills, order log, economics"]

raw --> connector
connector --> canonical
evidence --> integration
integration --> canonical
canonical --> replay
replay --> recording
recording <--> strategy
Expand All @@ -38,19 +38,26 @@ flowchart LR

The main boundaries are:

- connectors normalize external data into `MBOEvent`;
- observed-MBO connectors normalize order-level data directly into `MBOEvent`;
- lower-fidelity sources require a named reconstruction model before they
reach the canonical boundary;
- strategies depend on `OrderGateway`, not on storage or engine internals;
- replay normalizes inputs once, chooses an execution engine, and gathers
results;
- valuation marks are collected during replay and consumed by economics;
- the Python engine defines behavior; the C++ engine must match it.

## Recommended Data Flow
## Data Fidelity Paths

The replay core is independent of venue, asset class, and data vendor. Two data
paths converge on the canonical `MBOEvent` boundary.

Observed order-level data takes the direct path:

```mermaid
flowchart LR
raw["Raw vendor source"]
normalize["Vendor connector"]
raw["Observed order-level source"]
normalize["MBO connector"]
materialize["write_parquet(...)"]
parquet["Canonical Parquet"]
source["ParquetSource"]
Expand All @@ -61,37 +68,40 @@ flowchart LR

Direct connector replay is supported, but repeated research should normally
materialize canonical Parquet once and replay from it thereafter. See
`docs/data-guide.md`.
`docs/data-guide.md`. Databento is the current reference connector for this
path; CSV and Parquet support the normalized canonical schema.

Lower-fidelity venue data takes a longer, explicit path:
Price-level data takes a longer, explicit path:

```mermaid
flowchart LR
venue["Venue L2 + aggregate and individual trades"]
venue["Venue L2 + individual trades"]
capture["Raw capture"]
source["Typed L2 source"]
model["BinanceMBOReconstructor<br/>named queue policy"]
model["Named reconstruction model<br/>explicit queue policy"]
modeled["Modeled MBOEvent stream"]
parquet["Canonical Parquet + model manifest"]
replay["Replay"]

venue --> capture --> source --> model --> modeled --> parquet --> replay
```

This is the crypto-enabling path: aggregated venue data reaches the same
canonical event and execution-engine boundary as native MBO, but only after a
named model makes the missing order-level structure explicit. New
This is the path that currently enables Binance crypto data: aggregated venue
data reaches the same canonical event and execution-engine boundary as observed
MBO, but only after a named model makes the missing order-level structure
explicit. The architecture itself is not Binance- or crypto-specific. New
reconstruction policies should compete on paired-L3 or live-fill prediction,
not on undocumented claims of realism.

Capture code may live beside connectors because it owns venue I/O and source
schemas. Capture alone is not a `DataSource`: observed L2 rows must not be
presented as exchange-native MBO. The reconstruction model owns that
lower-fidelity assumption and preserves a study manifest describing how its
events were inferred. For Binance, `BinanceCaptureSource` is the typed,
sequence-validated L2 boundary and `BinanceMBOReconstructor` emits modeled
canonical events for one snapshot-anchored segment. The capture source itself
does not implement the canonical MBO `DataSource` protocol.
events were inferred. The current reference implementation uses
`BinanceCaptureSource` as the typed, sequence-validated L2 boundary and
`BinanceMBOReconstructor` to emit modeled canonical events for one
snapshot-anchored segment. The capture source itself does not implement the
canonical MBO `DataSource` protocol.

## One Replay Run

Expand Down
15 changes: 12 additions & 3 deletions docs/assumptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ replay under explicitly named assumptions.
The goal is not to claim perfect market realism. The goal is to make every
assumption visible, testable, and replaceable.

## Data Assumptions
## Data Fidelity Assumptions

The replay and execution engines do not distinguish asset classes or vendors.
They distinguish the evidence behind their canonical events: order-level
events may be observed directly, or they may be reconstructed from
lower-fidelity evidence under a named model.

### Observed MBO

The highest-fidelity path expects order-level data:

Expand All @@ -26,6 +33,8 @@ Public timestamps are normalized as UTC Unix-epoch nanoseconds. Connectors own
timezone-aware conversion from vendor or exchange-local source timestamps
before replay sees the data.

### Reconstructed Virtual MBO

Lower-fidelity data can be supported, but must be named honestly. A Level 2 or
MBP source provides aggregated size by price level, not individual queue
members. Queue-position behavior inferred from MBP is necessarily a model, not
Expand All @@ -43,8 +52,8 @@ must preserve the observed price-level updates and trades. Reconstruction must
name its cancellation-allocation, event-ordering, and randomness assumptions,
and modeled events must not be presented as exchange-native MBO.

The Binance virtual-MBO model uses the minimum-flow identity at each price
level and depth interval:
The current Binance reference model uses the minimum-flow identity at each
price level and depth interval:

```text
ending quantity = starting quantity + adds - cancels - traded quantity
Expand Down
33 changes: 27 additions & 6 deletions docs/connectors.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Connectors

Connectors translate vendor data into the public `MBOEvent` schema.
Data integrations enter `ordersim` through one of two fidelity boundaries:

- observed-MBO connectors normalize order-level source data directly into the
public `MBOEvent` schema;
- evidence connectors preserve lower-fidelity source records for a named
reconstruction model, which then emits modeled `MBOEvent` rows.

The distinction is deliberate. A source integration must not make L2 data look
like observed MBO simply to satisfy the replay interface.

The public contract is intentionally small:

Expand All @@ -9,8 +17,21 @@ class DataSource(Protocol):
def events(self) -> Iterable[MBOEvent]: ...
```

A connector should hide SDK details, file layout, network access, and vendor
column names. Replay code should receive only normalized `MBOEvent` rows.
At the canonical boundary, a connector should hide SDK details, file layout,
network access, and vendor column names. Replay code should receive only
normalized `MBOEvent` rows.

## Current Integrations

| Integration | Fidelity role | Output |
|---|---|---|
| `DatabentoMboSource` | Observed order-level reference | Canonical `MBOEvent` |
| `CsvSource` / `ParquetSource` | Vendor-neutral normalized interchange | Canonical `MBOEvent` |
| `BinanceCaptureSource` | L2 and trade evidence reference | Typed evidence for reconstruction |
| `BinanceMBOReconstructor` | Named virtual-MBO model | Modeled `MBOEvent` plus manifest |

These are the implementations available today, not permanent asset-class,
venue, or vendor boundaries.

Normalized `ts_ns` values must be UTC Unix-epoch nanoseconds. Connectors may
read exchange-local, vendor-local, or already-UTC source timestamps, but they
Expand All @@ -37,7 +58,7 @@ Every connector should include a tiny public fixture or generator and at least
one deterministic replay test. The test should prove the connector can produce
events that a strategy can replay without private data.

## Recommended Workflow
## Canonical Storage Workflow

For repeated research, normalize raw vendor data once, materialize the canonical
Parquet form, and replay from `ParquetSource` thereafter:
Expand All @@ -63,7 +84,7 @@ Direct connector replay remains useful for smoke tests, one-off inspection, and
connector development. CSV remains useful for tiny examples and reviewable
fixtures.

## Binance L2 Capture
## Reconstructed-MBO Reference: Binance USD-M

Binance USD-M futures publishes aggregated price-level depth rather than stable
market-by-order identifiers. The capture tool therefore records source evidence
Expand Down Expand Up @@ -360,7 +381,7 @@ from ordersim import write_parquet
write_parquet(source, "events.parquet")
```

## Databento MBO Sources
## Observed-MBO Reference: Databento

Use `DatabentoMboSource` with raw Databento MBO records, such as records yielded
by an iterable `DBNStore`:
Expand Down
Loading