NautilusTrader
Developer Guide

Nightly docs

These docs track unreleased changes and may change without notice. Their code examples can use APIs that the latest release lacks, so run them with a development wheel or switch to the latest release docs.

Data Testing Spec

This section defines a rigorous test matrix for validating adapter data functionality using the Rust DataTester actor. Python exposes it as a built-in actor configured through nautilus_trader.testkit.DataTesterConfig; Rust code imports it from nautilus_testkit::testers. Each test case is identified by a prefixed ID (e.g. TC-D01) and grouped by functionality.

Each adapter must pass the subset of tests matching its supported data types.

Test groups are ordered from least derived to most derived data: instruments and raw book data first, then quotes, trades, bars, and derivatives data. An adapter that passes groups 1-4 is considered baseline data compliant.

Document adapter-specific data behavior (custom channels, throttling, snapshot semantics, etc.) in the adapter's own guide, not here.

Prerequisites

Before running data tests:

  • Target instrument available and loadable via the instrument provider.
  • API credentials set via environment variables ({VENUE}_API_KEY, {VENUE}_API_SECRET) when the venue requires authentication for the data being tested.
  • If the venue offers a demo/testnet mode, use credentials created for that environment. Demo and production API keys are typically separate and not interchangeable; using the wrong credentials produces authentication errors (e.g. HTTP 401).

Python node setup:

Use nautilus_trader.live.LiveNode. Call LiveNode.builder(...) when you need to register adapter client factories before the node is built.

from nautilus_trader.common import Environment
from nautilus_trader.config import LiveDataEngineConfig
from nautilus_trader.live import LiveNode
from nautilus_trader.model import TraderId
from nautilus_trader.testkit import DataTesterConfig

node = (
    LiveNode.builder("TESTER-001", TraderId("TESTER-001"), Environment.SANDBOX)
    .with_data_engine_config(LiveDataEngineConfig(time_bars_build_with_no_updates=False))
    .add_data_client(None, adapter_data_client_factory, data_client_config)
    .build()
)

tester_config = DataTesterConfig(
    client_id=client_id,
    instrument_ids=[instrument_id],
    subscribe_quotes=True,
)
node.add_builtin_actor("DataTester", tester_config)
# Register remaining components, then start or run

Rust node setup (reference: crates/adapters/{adapter}/examples/node_data_tester.rs):

use nautilus_testkit::testers::{DataTester, DataTesterConfig};

let tester_config = DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_quotes(true)
    .build()?;
let tester = DataTester::new(tester_config);
node.add_actor(tester)?;
node.run().await?;

Timestamp scale

Nautilus stores ts_event and ts_init as Unix nanoseconds (UnixNanos). Every data message that carries those fields must use that scale, not leftover seconds, milliseconds, or microseconds.

  • A value below 10^16 is not a plausible Unix-nanosecond timestamp (10^16 ns is about 116 days after 1970-01-01) and usually means the adapter left the venue scale unconverted.
  • Second-precision venue times that were converted correctly end in 000000000 and still pass: that is coarse precision, not a scale error.
  • Live stream ts_event should be near wall-clock time for the session. Historical request results may be older and still valid if the scale is nanoseconds.
  • ts_init is the local clock when Nautilus created the object. Small ts_event > ts_init skew is possible when the venue clock is ahead.

DataTester warns when ts_event or ts_init fails the scale check on instruments, quotes, trades, bars, book deltas, book depth, mark and index prices, funding rates, instrument status and close, option greeks, and historical batches of those types. It does not check reconstructed books in on_book. Treat a warning as a failure for the case that produced the message.


Order book sync conformance

Adapters that maintain order books from a venue stream must keep their output valid through venue and network faults. Unit and integration suites cannot reproduce venue timing, so changes to book sync and recovery machinery need a deterministic model check and live validation against a real venue. Live validation here means market-data-only observation: subscribe, request, and fault-inject, never place orders. A dark book is a subscribed book that never receives data.

Book stream contract

BookStreamChecker in nautilus_live::book::conformance, enabled by the nautilus-live test-support feature, applies the contract to every emitted OrderBookDeltas batch:

  • A batch ends with F_LAST. Each F_LAST closes an event group, and the book must pass its integrity check after every group because consumers observe it at those boundaries.
  • A snapshot group is a Clear followed by Add deltas, all flagged F_SNAPSHOT. A lone Clear is an empty snapshot.
  • An incremental group carries neither F_SNAPSHOT nor Clear, and follows a snapshot.
  • Each incremental group's sequence exceeds the previous one when the venue sequence is monotonic within a snapshot episode. OKX seqId can reset, and Polymarket, Hyperliquid, Betfair, and AX Exchange books carry no venue sequence, so their checkers skip this rule and rely on the oracle.
  • A book emits nothing after its unsubscribe settles.

Validation levels

Match the level to the riskiest aspect of the change; higher levels include the bars of every level below.

LevelTriggerMethodAcceptance
L0 ModelAny change to BookSync, BookRecovery, or an adapter's use of themProperty test of the per-book state machine against a reference model, plus planted regressionsThe property test passes, and reverting a known fix, such as the gap ownership rule, makes it fail
L1 ConformanceAny change to previously validated sync/recovery codeRerun the venue's stress harness or established oraclePASS at the documented bar, zero checker violations, zero dark books, zero unexplained errors
L2 Edge probeBoundary behavior changes (timeouts, disabled paths, budget exhaustion, the retry ceiling)Targeted boundary scenarios, including each new tuning extremeEvery scenario passes; disabled paths stay quiet; an exhausted budget reaches the ceiling and a late snapshot still restores the book
L3 Race probeConcurrency or ordering changes (gates, epochs, reconnect interplay), or any live-found race fixFault injection plus subscribe churn under an independent oracleDozens of forced recoveries complete with zero dark books; the reported race scenario passes with no recurrence
L4 Full validationNew sync/recovery implementationL1-L3 plus a sustained churn and reconnect-fault soakAll lower bars hold for the full soak; recovery latencies stay bounded

The L0 property test is schedule_keeps_sync_contract in crates/live/src/book/sync.rs. Record the level, venue, oracle, and result with the change. A fix that live validation finds restarts at the level that found it: the rerun must clear the same bar, not a lighter one.

Fault catalog

Every adapter must produce these outcomes, whichever recovery family it belongs to:

FaultRequired outcome
Sequence gapOutput stops at the gap and resumes only after a fresh snapshot replaces the book.
Missing or late snapshotThe snapshot deadline starts or retries recovery; a snapshot accepted between attempts ends it.
Recovery cannot startThe book stays unowned and requests recovery again on its next frame; a refused task cancels its claimed episode.
Rejected replacementRecovery retries; an error the classifier marks permanent skips the budget and retries at the ceiling.
Retry budget exhaustedOne error log, then retries at the ceiling until a snapshot is accepted.
Reconnect mid-recoveryThe running recovery keeps its budget, ownership, and in-flight write, and its next ceiling wait ends at once; other books resync from fresh snapshots.
Unsubscribe during recoveryRecovery and its pending writes stop, and the book emits nothing further.

Forcing techniques

Prefer distinct orderings over raw volume: a probe earns its place by forcing an ordering the suite cannot produce (reconnect mid-recovery, a snapshot racing gate-open, an unsubscribe racing an in-flight subscribe), not by message count.

TechniqueStressesFigures that proved effectiveCaught in practice
Subscribe churn (rotating unsubscribe/resubscribe with periodic full flaps)Recovery initiation, gate/epoch rollover, in-flight cancel races20 s ticks over a 10-15 min run; dozens of forced recoveries (40+) with zero dark booksDuplicate-subscribe flaw that could not recover (forced a design revisit)
Traffic freeze (STOP the tunnel ~40 s)Dead-connection detection, reconnect replay, post-reconnect recovery2-3 freezes per run, spaced minutes apartProves reconnect recovery under total packet loss; no defect caught yet
Proxy fault injection (drop/hold/cut frames by rule)Gap handling, held-frame release, oracle conformanceThousands of oracle batches per run (6k+), per-round gap countsTimeout-scaled harness race (fixed observe window vs new default)
Tuning extremes (0 plus a short non-default value)Disabled-deadline branches, param threading end to endOne short run per extreme (4-5 min) with churn activeConfirmed the review-found zero-timeout fix live; proves threading
Client-issued reconnect (public reconnect command, then exercise)Reconnect recovery without touching host networking5+ consecutive reconnect/reconcile passesProves recovery without host faults; no defect caught yet
Serial repetition of the race scenarioScheduler sensitivity5+ consecutive live passes; 100x repetition for deterministic harnessesFlakes that pass once and fail rarely

Route each venue through a network location it serves: Polymarket restricts access by region, while OKX, Lighter, Binance, Hyperliquid, and AX Exchange validate direct. Confirm the route delivers venue data before a long run: sockets can connect while the venue stays silent. Branches the venue never produces live belong in a captured-wire deterministic harness, not in the live run.

Oracles

An oracle is an independent reconstruction of venue truth, compared with the emitted book through BookStreamChecker::verify:

  • Build it from a separate connection or a REST snapshot, never from the adapter's own state.
  • Compare at an aligned venue sequence. Skip a sample that cannot be aligned; it does not count as a pass.
  • Count a snapshot episode verified once a comparison after its snapshot succeeds. A harness with Coverage::Episodes, such as OKX, verifies each batch as it arrives and fails a session unless every episode is verified. A harness with Coverage::Samples, such as Binance, matches oracle samples by update ID after the fact, so it reports oracle checks and unmatched samples instead of episode coverage.

Stress harnesses

Stress harnesses are development tools for changes to book sync and recovery code. They are not part of the published crates and do not run in CI. The BookStreamChecker they use ships with nautilus-live under the test-support feature, so other tests can apply the same contract.

An adapter that uses the shared book machinery keeps its live harness at crates/adapters/<venue>/tests/stress/book_stress.rs, registered as a test target named <venue>-book-stress:

[[test]]
name = "okx-book-stress"
path = "tests/stress/book_stress.rs"
harness = false
test = false
required-features = ["examples"]

harness = false lets the target own its runtime and arguments, and test = false keeps it out of default cargo test and nextest runs. Add nautilus-live with the test-support feature to the crate's dev-dependencies.

The shared machinery is test source at crates/live/tests/book/stress/, which each harness compiles in with a path include:

#[path = "../../../../live/tests/book/stress/mod.rs"]
mod stress;

The shared module runs the harness, and the venue supplies only its own pieces by implementing StressVenue:

  • A WireCodec that classifies each venue frame as a book snapshot, a book update, or an unsubscribe acknowledgement, recording it in the oracle before any fault applies. It can also rewrite a frame to plant a sequence gap or an in-band mismatch, and answer an adapter subscribe with a venue rejection.
  • The proxy routes, the data client configuration, and any extra proxy routes, such as a REST snapshot proxy.
  • The oracle comparison for each emitted batch, the condition for a healthy book, and a startup self-check.
  • The scenarios, written against Session.

FaultProxy relays the adapter's WebSocket traffic to the venue, or CRLF-delimited lines over raw TCP for a route whose upstream URL is not a WebSocket URL. A line route serves the proxy address alone, and the venue's WireCodec::connect opens its upstream connection, for example over TLS. It applies per-book Fault rules (drop snapshots or updates, corrupt, hold, silence, cut on unsubscribe, reject subscribes) and connection-wide cuts and freezes. Session passes every emitted batch through BookStreamChecker and the oracle, waits for books to heal, and checks at shutdown that every socket and reconnect handle is released.

Every harness accepts the same flags, and venues add their own; --help lists them:

FlagMeaningDefault
--scenario NAMEScenario to run.churn
--timeout SECSbook_snapshot_timeout_secs; 0 disables snapshot deadlines.10
--rounds NStress rounds.Venue default

Run the harness explicitly, with adapter environment variables stripped:

CARGO_BUILD_JOBS=16 bash scripts/strip-adapter-env.bash \
  cargo test -p nautilus-okx --features examples --test okx-book-stress -- --timeout 10 --rounds 18

Betfair streams market data only to logged-in accounts, so its harness runs with the Betfair credentials set; see Live recovery validation. AX Exchange market data also requires authentication, so its harness reads sandbox credentials from the environment and runs without the wrapper.

The harness writes one line per event to stderr, each led by a fixed word:

LineMeaning
STARTThe venue and arguments.
CHECKThe startup self-check or a scenario probe passed.
ROUNDA stress round finished, with its counters.
SHUTDOWNA session stopped cleanly, with its counters and oracle coverage.
PASSThe run finished; always the last line of a passing run.
FAILA check failed or any thread panicked; the process exits with status 1.

A deadline failure reports the venue frames each proxy route received, which separates a silent route from an adapter failure. A harness = false target cannot run #[test] functions, so the venue proves its wire parsing and oracle in StressVenue::self_check, which runs before any venue traffic. The shared proxy, argument parsing, and wire book carry unit tests in the nautilus-live book test target, run with cargo nextest run -p nautilus-live --features test-support --test book. Document the harness in the adapter's integration guide under a Live recovery validation heading that covers what it checks, the faults it injects, the run command, its scenarios and flags, and the endpoints it requires. OKX, Binance, Lighter, Polymarket, Hyperliquid, Bybit, Betfair, and AX Exchange provide harnesses.

In-band verification

When a venue publishes a book checksum or hash, validate it and treat a mismatch as a gap. It catches corruption in the data it covers that sequence checks miss, without an external oracle. Kraken validates the CRC32 checksum on each L3 update when validate_l3_checksum is enabled, which is the default. Polymarket validates the hash on each book snapshot that carries a hash and its full preimage (see book snapshot validation). OKX books frames carry a zero checksum, so OKX relies on its oracle instead.


Each group below begins with a summary table, followed by detailed test cards. Test IDs use spaced numbering to allow insertion without renumbering.


Group 1: Instruments

Verify instrument loading and subscription before testing market data streams.

TCNameDescriptionSkip when
TC-D01Request instrumentsLoad all instruments for a venue.Never.
TC-D02Subscribe instrumentSubscribe to instrument updates.No instrument sub.
TC-D03Load specific instrumentLoad a single instrument by ID.Never.

TC-D01: Request instruments

FieldValue
PrerequisiteAdapter connected.
ActionDataTester requests all instruments for the venue on start.
Event sequenceon_instruments callback receives instrument list.
Pass criteriaAt least one instrument received; each has valid symbol, price precision, and size increment.
Skip whenNever.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    request_instruments=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .request_instruments(true)
    .build()?

TC-D02: Subscribe instrument

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to instrument updates.
Event sequenceon_instrument callback receives instrument.
Pass criteriaInstrument received with correct instrument_id, valid fields.
Skip whenAdapter does not support instrument subscriptions.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_instrument=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_instrument(true)
    .build()?

TC-D03: Load specific instrument

FieldValue
PrerequisiteAdapter connected.
ActionLoad a specific instrument by InstrumentId via the instrument provider.
Event sequenceInstrument available in cache after load.
Pass criteriaInstrument loaded with correct ID, price precision, size increment, and trading rules.
Skip whenNever.

Considerations:

  • This tests the instrument provider's load / load_async method directly.
  • Verify the instrument is cached and available via self.cache.instrument(instrument_id).

Group 2: Order book

Test order book subscription modes and snapshot requests.

TCNameDescriptionSkip when
TC-D10Subscribe book deltasStream OrderBookDeltas updates.No book support.
TC-D11Subscribe book at intervalPeriodic OrderBook snapshots.No book support.
TC-D12Subscribe book depthOrderBookDepth snapshots.No book depth.
TC-D13Request book snapshotOne-time book snapshot request.No book snapshot.
TC-D14Managed book from deltasBuild local book from delta stream.No book support.

Python uses BookType.L2_MBP for these scenarios. The Rust builder can override book_type when an adapter requires a different book representation.

TC-D10: Subscribe book deltas

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to order book deltas.
Event sequenceOrderBookDeltas events received in on_book_deltas.
Pass criteriaDeltas received with valid instrument ID; at least one delta contains bid/ask updates.
Skip whenAdapter does not support order book data.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_book_deltas=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_book_deltas(true)
    .book_type(BookType::L2_MBP)
    .build()?

TC-D11: Subscribe book at interval

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to periodic order book snapshots.
Event sequenceOrderBook events received in on_book at configured interval.
Pass criteriaBook snapshots received with bid/ask levels; updates arrive at approximately the configured interval.
Skip whenAdapter does not support order book data.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_book_at_interval=True,
    book_depth=10,
    book_interval_ms=1000,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_book_at_interval(true)
    .book_type(BookType::L2_MBP)
    .book_depth(10)
    .book_interval_ms(1000)
    .build()?

TC-D12: Subscribe book depth

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to OrderBookDepth snapshots.
Event sequenceOrderBookDepth events received in on_book_depth.
Pass criteriaDepth snapshots respect the requested level limit; prices are correctly ordered.
Skip whenAdapter does not support book depth subscriptions.

Choose a depth supported by the venue; see the adapter guide for its limit. book_depth applies to all enabled book subscriptions and the book snapshot request. Omitting it uses the adapter default. When depth runs alongside deltas or interval books, DataTester subscribes to depth with managed=False so it cannot overwrite the delta-managed book. When only depth is enabled, its managed setting follows manage_book.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_book_depth=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_book_depth(true)
    .book_type(BookType::L2_MBP)
    .build()?

TC-D13: Request book snapshot

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester requests a one-time order book snapshot.
Event sequenceBook snapshot received via historical data callback.
Pass criteriaSnapshot contains bid/ask levels with valid prices and sizes.
Skip whenAdapter does not support book snapshot requests.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    request_book_snapshot=True,
    book_depth=10,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .request_book_snapshot(true)
    .book_depth(10)
    .build()?

TC-D14: Managed book from deltas

FieldValue
PrerequisiteAdapter connected, instrument loaded, book deltas streaming.
ActionDataTester subscribes to deltas with manage_book=True; builds local order book from the delta stream.
Event sequenceOrderBookDeltas applied to local OrderBook; book logged with configured depth.
Pass criteriaLocal book builds correctly from deltas; bid levels descend, ask levels ascend; book is not empty after initial snapshot.
Skip whenAdapter does not support order book data.

Considerations:

  • The managed book applies each delta to an OrderBook instance maintained by the actor.
  • Use book_levels_to_print to control logging verbosity.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_book_deltas=True,
    manage_book=True,
    book_levels_to_print=10,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_book_deltas(true)
    .manage_book(true)
    .book_type(BookType::L2_MBP)
    .build()?

DataTesterConfig exposes request_book_deltas, but DataTester does not issue that historical request. Test an adapter's historical book delta support through a custom actor until the tester implements the request path.


Group 3: Quotes

Test quote tick subscriptions and historical requests.

TCNameDescriptionSkip when
TC-D20Subscribe quotesVerify QuoteTick events flow after start.Never.
TC-D21Request historical quotesRequest historical quote ticks.No historical quotes.

TC-D20: Subscribe quotes

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to quotes on start.
Event sequenceQuoteTick events received in on_quote.
Pass criteriaAt least one QuoteTick received with valid bid/ask prices and sizes; bid < ask.
Skip whenNever.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_quotes=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_quotes(true)
    .build()?

TC-D21: Request historical quotes

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester requests historical quote ticks.
Event sequenceHistorical quote batches received via on_historical_quotes.
Pass criteriaQuotes received with valid timestamps, bid/ask prices, and sizes.
Skip whenAdapter does not support historical quote requests.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    request_quotes=True,
)

Group 4: Trades

Test trade tick subscriptions and historical requests.

TCNameDescriptionSkip when
TC-D30Subscribe tradesVerify TradeTick events flow after start.Never.
TC-D31Request historical tradesRequest historical trade ticks.No historical trades.

TC-D30: Subscribe trades

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to trades on start.
Event sequenceTradeTick events received in on_trade.
Pass criteriaAt least one TradeTick received with valid price, size, and aggressor side.
Skip whenNever.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_trades=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_trades(true)
    .build()?

TC-D31: Request historical trades

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester requests historical trade ticks.
Event sequenceHistorical trade batches received via on_historical_trades.
Pass criteriaTrades received with valid timestamps, prices, sizes, and trade IDs.
Skip whenAdapter does not support historical trade requests.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    request_trades=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .request_trades(true)
    .build()?

Group 5: Bars

Test bar subscriptions and historical requests.

TCNameDescriptionSkip when
TC-D40Subscribe barsVerify Bar events flow after start.No bar support.
TC-D41Request historical barsRequest historical OHLCV bars.No historical bars.

TC-D40: Subscribe bars

FieldValue
PrerequisiteAdapter connected, instrument loaded, bar type configured.
ActionDataTester subscribes to bars for a configured BarType.
Event sequenceBar events received in on_bar.
Pass criteriaAt least one Bar received with valid OHLCV values; high >= low, high >= open, high >= close.
Skip whenAdapter does not support bar subscriptions.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    bar_types=[BarType.from_str("BTCUSDT-PERP.VENUE-1-MINUTE-LAST-EXTERNAL")],
    subscribe_bars=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .bar_types(vec![bar_type])
    .subscribe_bars(true)
    .build()?

TC-D41: Request historical bars

FieldValue
PrerequisiteAdapter connected, instrument loaded, bar type configured.
ActionDataTester requests historical bars for a configured BarType.
Event sequenceHistorical bars received via callback.
Pass criteriaBars received with valid OHLCV values and ascending timestamps.
Skip whenAdapter does not support historical bar requests.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    bar_types=[BarType.from_str("BTCUSDT-PERP.VENUE-1-MINUTE-LAST-EXTERNAL")],
    request_bars=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .bar_types(vec![bar_type])
    .request_bars(true)
    .build()?

Group 6: Derivatives data

Test derivatives-specific data streams: mark prices, index prices, and funding rates.

TCNameDescriptionSkip when
TC-D50Subscribe mark pricesMarkPriceUpdate events.Not a derivative.
TC-D51Subscribe index pricesIndexPriceUpdate events.Not a derivative.
TC-D52Subscribe funding ratesFundingRateUpdate events.Not a perpetual.
TC-D53Request historical funding ratesHistorical funding rate data.Not a perpetual.

TC-D50: Subscribe mark prices

FieldValue
PrerequisiteAdapter connected, derivative instrument loaded.
ActionDataTester subscribes to mark price updates.
Event sequenceMarkPriceUpdate events received in on_mark_price.
Pass criteriaAt least one MarkPriceUpdate received with valid instrument ID and mark price.
Skip whenInstrument is not a derivative, or adapter does not provide mark prices.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_mark_prices=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_mark_prices(true)
    .build()?

TC-D51: Subscribe index prices

FieldValue
PrerequisiteAdapter connected, derivative instrument loaded.
ActionDataTester subscribes to index price updates.
Event sequenceIndexPriceUpdate events received in on_index_price.
Pass criteriaAt least one IndexPriceUpdate received with valid instrument ID and index price.
Skip whenInstrument is not a derivative, or adapter does not provide index prices.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_index_prices=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_index_prices(true)
    .build()?

TC-D52: Subscribe funding rates

FieldValue
PrerequisiteAdapter connected, perpetual instrument loaded.
ActionDataTester subscribes to funding rate updates.
Event sequenceFundingRateUpdate events received in on_funding_rate.
Pass criteriaAt least one FundingRateUpdate received with valid instrument ID and rate.
Skip whenInstrument is not a perpetual, or adapter does not provide funding rates.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_funding_rates=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_funding_rates(true)
    .build()?

TC-D53: Request historical funding rates

FieldValue
PrerequisiteAdapter connected, perpetual instrument loaded.
ActionDataTester requests historical funding rates (default 7-day lookback).
Event sequenceHistorical funding rates received via callback.
Pass criteriaFunding rates received with valid timestamps and rate values.
Skip whenInstrument is not a perpetual, or adapter does not support historical funding rate requests.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    request_funding_rates=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .request_funding_rates(true)
    .build()?

Group 7: Instrument status

Test instrument status and close event subscriptions.

TCNameDescriptionSkip when
TC-D60Subscribe instrument statusInstrumentStatus events.No status support.
TC-D61Subscribe instrument closeInstrumentClose events.No close support.

TC-D60: Subscribe instrument status

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to instrument status updates.
Event sequenceInstrumentStatus events received in on_instrument_status.
Pass criteriaStatus events received with valid MarketStatusAction (e.g. Trading).
Skip whenAdapter does not support instrument status subscriptions.

Considerations:

  • Status events may only fire on state changes (e.g. trading halt -> resume).
  • During normal trading hours, a Trading status may be received on subscribe.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_instrument_status=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_instrument_status(true)
    .build()?

TC-D61: Subscribe instrument close

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to instrument close events.
Event sequenceInstrumentClose events received in on_instrument_close.
Pass criteriaClose event received with valid close price and close type.
Skip whenAdapter does not support instrument close subscriptions.

Considerations:

  • Close events typically fire at end-of-session for traditional markets.
  • May not fire for 24/7 crypto venues unless the adapter synthesizes a daily close.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_instrument_close=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_instrument_close(true)
    .build()?

Group 8: Option greeks

Test option greeks and option chain subscriptions.

TCNameDescriptionSkip when
TC-D62Subscribe option greeksOptionGreeks data for a single instrument.No greeks support.
TC-D63Subscribe option chainOptionChainSlice snapshots for a series.No chain support.

TC-D62: Subscribe option greeks

FieldValue
PrerequisiteAdapter connected, option instrument loaded.
ActionDataTester subscribes to option greeks updates.
Event sequenceOptionGreeks events received in on_option_greeks.
Pass criteriaGreeks received with valid delta, gamma, vega, theta values.
Skip whenAdapter does not support option greeks subscriptions.

Considerations:

  • Greeks are only available for option instruments.
  • Values depend on the venue's pricing model and may update on every quote change.
  • Some venues (Bybit, Deribit) subscribe per instrument; OKX subscribes per instrument family and filters to the requested instruments.
  • rho may be zero when the venue does not provide it (Bybit, OKX).
  • underlying_price and open_interest may be None depending on the venue channel.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_option_greeks=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_option_greeks(true)
    .build()?

TC-D63: Subscribe option chain

FieldValue
PrerequisiteAdapter connected, option instruments loaded for the series.
ActionDataTester subscribes to option chain snapshots for a series.
Event sequenceOptionChainSlice snapshots received in on_option_chain.
Pass criteriaChain snapshot contains greeks for instruments matching the series.
Skip whenAdapter does not support option chain subscriptions.

Considerations:

  • Option chain subscriptions are managed by the DataEngine, which creates per-instrument quote and greeks subscriptions internally.
  • Dynamic strike ranges require an ATM price before instrument subscriptions begin. The DataEngine requests an initial reference price and otherwise waits for live option Greeks.
  • Not yet configurable via DataTesterConfig; requires manual actor setup with subscribe_option_chain and an OptionSeriesId.

Group 9: Lifecycle

Test actor lifecycle behavior: unsubscribe handling, retirement cleanup, and custom parameters.

TCNameDescriptionSkip when
TC-D70Unsubscribe on stopUnsubscribe from data feeds on actor stop.No unsub support.
TC-D71Custom subscribe paramsAdapter-specific subscription parameters.N/A.
TC-D72Custom request paramsAdapter-specific request parameters.N/A.
TC-D73Retirement cleanupRelease an actor's retained data subscriptions.N/A.
TC-D74DeFi shared pool demandKeep shared pool feeds until the final owner.No DeFi support.
TC-D75DeFi bootstrap cancelDiscard snapshots for canceled pool bootstraps.No DeFi support.

TC-D70: Unsubscribe on stop

FieldValue
PrerequisiteActive data subscriptions (quotes, trades, book).
ActionStop the actor with can_unsubscribe=True (default).
Event sequenceData subscriptions removed; no further data events received.
Pass criteriaClean unsubscribe; no errors in logs; no data events after stop.
Skip whenAdapter does not support unsubscribe.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_quotes=True,
    subscribe_trades=True,
    can_unsubscribe=True,
)

Rust config:

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_quotes(true)
    .subscribe_trades(true)
    .can_unsubscribe(true)
    .build()?

TC-D71: Custom subscribe params

FieldValue
PrerequisiteAdapter connected, adapter accepts additional subscription parameters.
ActionSubscribe with adapter-specific subscribe_params.
Event sequenceSubscription established with custom parameters applied.
Pass criteriaData flows with adapter-specific parameters in effect.
Skip whenN/A (adapter-specific).

Rust config:

use nautilus_core::Params;
use serde_json::json;

let mut subscribe_params = Params::new();
subscribe_params.insert("key".to_string(), json!("value"));

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .subscribe_quotes(true)
    .subscribe_params(subscribe_params)
    .build()?

Considerations:

  • subscribe_params is opaque to the DataTester and passed through to the adapter.
  • The Python DataTesterConfig constructor does not expose this Rust-only field.
  • Consult the adapter's guide for supported parameters.

TC-D72: Custom request params

FieldValue
PrerequisiteAdapter connected, adapter accepts additional request parameters.
ActionRequest data with adapter-specific request_params.
Event sequenceRequest fulfilled with custom parameters applied.
Pass criteriaHistorical data received with adapter-specific parameters in effect.
Skip whenN/A (adapter-specific).

Rust config:

use nautilus_core::Params;
use serde_json::json;

let mut request_params = Params::new();
request_params.insert("key".to_string(), json!("value"));

DataTesterConfig::builder()
    .client_id(client_id)
    .instrument_ids(vec![instrument_id])
    .request_quotes(true)
    .request_params(request_params)
    .build()?

Considerations:

  • request_params is opaque to the DataTester and passed through to the adapter.
  • The Python DataTesterConfig constructor does not expose this Rust-only field.
  • Consult the adapter's guide for supported parameters.

TC-D73: Retirement cleanup

FieldValue
PrerequisiteAn actor has venue-backed subscriptions; two actors share an internally aggregated bar.
ActionRetire the first actor, then retire the second actor through the trader.
Event sequenceon_dispose completes; unsubscribe commands are sent; the actor is deregistered.
Pass criteriaThe first retirement keeps shared data active; the final retirement releases the retained route and leaves no retired actor handlers.
Skip whenN/A.

The shared bar must remain active after the first actor retires and stop after the final actor retires.

Considerations:

  • DataTesterConfig does not cover multi-actor retirement. Create two actors manually, then remove them through Python Controller.remove_actor or Rust Trader::remove_actor.
  • If on_dispose fails, the actor must remain registered with its subscriptions intact so a later retirement can release them without invoking the failed hook again.
  • A failed on_stop or on_fault must not block retirement: disposal and deregistration must still complete from the corresponding transitional state.

TC-D74: DeFi shared pool demand

FieldValue
PrerequisiteA DeFi data client; two actors subscribe to the same pool, through the same or overlapping subscription types.
ActionRetire or unsubscribe one actor, then the other, in each order.
Event sequenceThe first release sends no client unsubscribe for shared types; the final release unsubscribes and stops the pool updater.
Pass criteriaPool events keep reaching the remaining actor and the profiler until the final release; none flow afterwards.
Skip whenAdapter does not provide DeFi pool subscriptions.

Cover these overlaps:

  • Two actors with the same subscription type, retired in either order.
  • SubscribePool with each narrower type (swaps, liquidity updates, fee collects, flash events), unsubscribed in both orders. The narrower event filters must stay active while either subscription remains.
  • The same pool on two data clients. Each client keeps its own demand, and the pool updater stays active until both release.

Considerations:

  • DataTesterConfig does not cover DeFi pool subscriptions. Create the actors manually.
  • A duplicate unsubscribe from one actor must not release another actor's demand.

TC-D75: DeFi bootstrap cancel

FieldValue
PrerequisiteA DeFi data client; the pool is absent from the cache, so a subscription requests a pool snapshot.
ActionSubscribe, release the final owner before the pool definition arrives, then subscribe again.
Event sequenceTwo snapshot requests are sent; the response to the first arrives after the second subscription.
Pass criteriaThe engine discards the first response; only the response to the current request installs a profiler.
Skip whenAdapter does not provide pool snapshots.

Considerations:

  • The second subscription requests a new snapshot only while the pool is absent from the cache. Once the first request's pool definition arrives, a later subscription builds the profiler from the cached pool instead, so the case needs a delayed response.
  • An engine reset or disconnect also cancels pending bootstraps. A response that arrives afterwards must not install a profiler.

DataTester configuration reference

The Python constructor accepts the parameters below. Defaults are resolved values after construction. Historical quote, trade, and bar requests use a one-hour lookback; funding rate requests use seven days. The lookback is not configurable through DataTesterConfig.

ParameterTypeDefaultAffects groups
actor_idActorId?NoneAll
client_idClientId?NoneAll
instrument_idslist[InstrumentId][]All
bar_typeslist[BarType]?None5
subscribe_book_deltasboolFalse2
subscribe_book_depthboolFalse2
subscribe_book_at_intervalboolFalse2
subscribe_quotesboolFalse3
subscribe_tradesboolFalse4
subscribe_mark_pricesboolFalse6
subscribe_index_pricesboolFalse6
subscribe_funding_ratesboolFalse6
subscribe_barsboolFalse5
subscribe_instrumentboolFalse1
subscribe_instrument_statusboolFalse7
subscribe_instrument_closeboolFalse7
subscribe_option_greeksboolFalse8
can_unsubscribeboolTrue9
request_instrumentsboolFalse1
request_book_snapshotboolFalse2
request_book_deltasboolFalseNot implemented
request_quotesboolFalse3
request_tradesboolFalse4
request_barsboolFalse5
request_funding_ratesboolFalse6
book_depthPositiveInt?None2
book_interval_msPositiveInt10002
book_levels_to_printPositiveInt102
manage_bookboolTrue2
log_databoolTrueAll
stats_interval_secsint5All
log_eventsboolTrueAll
log_commandsboolTrueAll

The Rust builder also exposes these parameters:

ParameterTypeDefaultAffects groups
book_typeBookTypeL2_MBP2
subscribe_paramsParams?None9
request_paramsParams?None9

On this page