NautilusTrader
Developer Guide

Data Testing Spec

This section defines a rigorous test matrix for validating adapter data functionality using the DataTester actor. Both Python (nautilus_trader.test_kit.strategies.tester_data) and Rust (nautilus_testkit::testers) provide the DataTester. 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 (e.g. is_demo=True), 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 (reference: examples/live/{adapter}/{adapter}_data_tester.py):

from nautilus_trader.live.node import TradingNode
from nautilus_trader.test_kit.strategies.tester_data import DataTester, DataTesterConfig

node = TradingNode(config=config_node)
tester = DataTester(config=config_tester)
node.trader.add_actor(tester)
# Register adapter factories, build, and run

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

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

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

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::new(client_id, vec![instrument_id])
    .with_request_instruments(true)

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::new(client_id, vec![instrument_id])
    .with_subscribe_instrument(true)

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 depthOrderBookDepth10 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.
TC-D15Request historical book deltasHistorical book deltas request.No historical deltas.

TC-D10: Subscribe book deltas

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to order book deltas.
Event sequenceOrderBookDeltas events received in on_order_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,
    book_type=BookType.L2_MBP,
)

Rust config:

DataTesterConfig::new(client_id, vec![instrument_id])
    .with_subscribe_book_deltas(true)
    .with_book_type(BookType::L2_MBP)

TC-D11: Subscribe book at interval

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to periodic order book snapshots.
Event sequenceOrderBook events received in on_order_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_type=BookType.L2_MBP,
    book_depth=10,
    book_interval_ms=1000,
)

Rust config:

DataTesterConfig::new(client_id, vec![instrument_id])
    .with_subscribe_book_at_interval(true)
    .with_book_type(BookType::L2_MBP)
    .with_book_depth(Some(NonZeroUsize::new(10).unwrap()))
    .with_book_interval_ms(NonZeroUsize::new(1000).unwrap())

TC-D12: Subscribe book depth

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester subscribes to OrderBookDepth10 snapshots.
Event sequenceOrderBookDepth10 events received in on_order_book_depth.
Pass criteriaDepth snapshots received with up to 10 bid/ask levels; prices are correctly ordered.
Skip whenAdapter does not support book depth subscriptions.

Python config:

DataTesterConfig(
    instrument_ids=[instrument_id],
    subscribe_book_depth=True,
    book_type=BookType.L2_MBP,
    book_depth=10,
)

Rust config: Not yet supported. Book depth subscription is TODO in the Rust DataTester.

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::new(client_id, vec![instrument_id])
    .with_request_book_snapshot(true)
    .with_book_depth(Some(NonZeroUsize::new(10).unwrap()))

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_type=BookType.L2_MBP,
    book_levels_to_print=10,
)

Rust config:

DataTesterConfig::new(client_id, vec![instrument_id])
    .with_subscribe_book_deltas(true)
    .with_manage_book(true)
    .with_book_type(BookType::L2_MBP)

TC-D15: Request historical book deltas

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester requests historical order book deltas.
Event sequenceHistorical deltas received via callback.
Pass criteriaDeltas received with valid timestamps and book actions.
Skip whenAdapter does not support historical book delta requests.

Python config:

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

Rust config: Not yet supported. Historical book delta requests are TODO in the Rust DataTester.


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_tick.
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::new(client_id, vec![instrument_id])
    .with_subscribe_quotes(true)

TC-D21: Request historical quotes

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester requests historical quote ticks.
Event sequenceHistorical quotes received via on_historical_data callback.
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,
    requests_start_delta=pd.Timedelta(hours=1),
)

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_tick.
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::new(client_id, vec![instrument_id])
    .with_subscribe_trades(true)

TC-D31: Request historical trades

FieldValue
PrerequisiteAdapter connected, instrument loaded.
ActionDataTester requests historical trade ticks.
Event sequenceHistorical trades received via on_historical_data callback.
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,
    requests_start_delta=pd.Timedelta(hours=1),
)

Rust config:

DataTesterConfig::new(client_id, vec![instrument_id])
    .with_request_trades(true)

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::new(client_id, vec![instrument_id])
    .with_bar_types(vec![bar_type])
    .with_subscribe_bars(true)

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,
    requests_start_delta=pd.Timedelta(hours=1),
)

Rust config:

DataTesterConfig::new(client_id, vec![instrument_id])
    .with_bar_types(vec![bar_type])
    .with_request_bars(true)

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::new(client_id, vec![instrument_id])
    .with_subscribe_mark_prices(true)

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::new(client_id, vec![instrument_id])
    .with_subscribe_index_prices(true)

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::new(client_id, vec![instrument_id])
    .with_subscribe_funding_rates(true)

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::new(client_id, vec![instrument_id])
    .with_request_funding_rates(true)

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::new(client_id, vec![instrument_id])
    .with_subscribe_instrument_status(true)

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::new(client_id, vec![instrument_id])
    .with_subscribe_instrument_close(true)

Group 8: Lifecycle

Test actor lifecycle behavior: unsubscribe handling 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-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::new(client_id, vec![instrument_id])
    .with_subscribe_quotes(true)
    .with_subscribe_trades(true)
    .with_can_unsubscribe(true)

TC-D71: Custom subscribe params

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

Considerations:

  • The subscribe_params dict is opaque to the DataTester and passed through to the adapter.
  • Consult the adapter's guide for supported parameters.

TC-D72: Custom request params

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

Considerations:

  • The request_params dict is opaque to the DataTester and passed through to the adapter.
  • Consult the adapter's guide for supported parameters.

DataTester configuration reference

Quick reference for all DataTesterConfig parameters. Defaults shown are for the Python config. Note: Rust DataTesterConfig::new sets manage_book=true, while Python defaults it to False.

ParameterTypeDefaultAffects groups
instrument_idslist[InstrumentId]requiredAll
client_idClientId?NoneAll
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_paramsdict?None8
can_unsubscribeboolTrue8
request_instrumentsboolFalse1
request_book_snapshotboolFalse2
request_book_deltasboolFalse2
request_quotesboolFalse3
request_tradesboolFalse4
request_barsboolFalse5
request_funding_ratesboolFalse6
request_paramsdict?None8
requests_start_deltaTimedelta?1 hour3, 4, 5
book_typeBookTypeL2_MBP2
book_depthPositiveInt?None2
book_interval_msPositiveInt10002
book_levels_to_printPositiveInt102
manage_bookboolFalse2
use_pyo3_bookboolFalse2
log_databoolTrueAll

On this page