nautilus-backtest 0.56.0

Core backtesting machinery for the Nautilus trading engine
Documentation
// -------------------------------------------------------------------------------------------------
//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
//  https://nautechsystems.io
//
//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
//  You may not use this file except in compliance with the License.
//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// -------------------------------------------------------------------------------------------------

//! Provides a `BacktestDataClient` implementation for backtesting.

use std::{cell::RefCell, rc::Rc};

use nautilus_common::{
    cache::Cache,
    clients::DataClient,
    messages::data::{
        RequestBars, RequestBookSnapshot, RequestCustomData, RequestForwardPrices,
        RequestInstrument, RequestInstruments, RequestQuotes, RequestTrades, SubscribeBars,
        SubscribeBookDeltas, SubscribeBookDepth10, SubscribeCustomData, SubscribeIndexPrices,
        SubscribeInstrument, SubscribeInstrumentClose, SubscribeInstrumentStatus,
        SubscribeInstruments, SubscribeMarkPrices, SubscribeQuotes, SubscribeTrades,
        UnsubscribeBars, UnsubscribeBookDeltas, UnsubscribeBookDepth10, UnsubscribeCustomData,
        UnsubscribeIndexPrices, UnsubscribeInstrument, UnsubscribeInstrumentClose,
        UnsubscribeInstrumentStatus, UnsubscribeInstruments, UnsubscribeMarkPrices,
        UnsubscribeQuotes, UnsubscribeTrades,
    },
};
use nautilus_model::identifiers::{ClientId, Venue};

/// Data client implementation for backtesting market data operations.
///
/// The `BacktestDataClient` provides a data client interface specifically designed
/// for backtesting environments. It handles market data subscriptions and requests
/// during backtesting, coordinating with the backtesting engine to provide
/// historical data replay functionality.
#[derive(Debug)]
pub struct BacktestDataClient {
    pub client_id: ClientId,
    pub venue: Venue,
    _cache: Rc<RefCell<Cache>>,
}

impl BacktestDataClient {
    /// Creates a new [`BacktestDataClient`] instance.
    #[must_use]
    pub const fn new(client_id: ClientId, venue: Venue, cache: Rc<RefCell<Cache>>) -> Self {
        Self {
            client_id,
            venue,
            _cache: cache,
        }
    }
}

#[async_trait::async_trait(?Send)]
impl DataClient for BacktestDataClient {
    fn client_id(&self) -> ClientId {
        self.client_id
    }

    fn venue(&self) -> Option<Venue> {
        Some(self.venue)
    }

    fn start(&mut self) -> anyhow::Result<()> {
        Ok(())
    }

    fn stop(&mut self) -> anyhow::Result<()> {
        Ok(())
    }

    fn reset(&mut self) -> anyhow::Result<()> {
        Ok(())
    }

    fn dispose(&mut self) -> anyhow::Result<()> {
        Ok(())
    }

    fn is_connected(&self) -> bool {
        true
    }

    fn is_disconnected(&self) -> bool {
        false
    }

    fn subscribe(&mut self, _cmd: SubscribeCustomData) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_instruments(&mut self, _cmd: SubscribeInstruments) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_instrument(&mut self, _cmd: SubscribeInstrument) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_book_deltas(&mut self, _cmd: SubscribeBookDeltas) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_book_depth10(&mut self, _cmd: SubscribeBookDepth10) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_quotes(&mut self, _cmd: SubscribeQuotes) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_trades(&mut self, _cmd: SubscribeTrades) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_bars(&mut self, _cmd: SubscribeBars) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_mark_prices(&mut self, _cmd: SubscribeMarkPrices) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_index_prices(&mut self, _cmd: SubscribeIndexPrices) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_instrument_status(
        &mut self,
        _cmd: SubscribeInstrumentStatus,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    fn subscribe_instrument_close(&mut self, _cmd: SubscribeInstrumentClose) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe(&mut self, _cmd: &UnsubscribeCustomData) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_instruments(&mut self, _cmd: &UnsubscribeInstruments) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_instrument(&mut self, _cmd: &UnsubscribeInstrument) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_book_deltas(&mut self, _cmd: &UnsubscribeBookDeltas) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_book_depth10(&mut self, _cmd: &UnsubscribeBookDepth10) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_quotes(&mut self, _cmd: &UnsubscribeQuotes) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_trades(&mut self, _cmd: &UnsubscribeTrades) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_bars(&mut self, _cmd: &UnsubscribeBars) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_mark_prices(&mut self, _cmd: &UnsubscribeMarkPrices) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_index_prices(&mut self, _cmd: &UnsubscribeIndexPrices) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_instrument_status(
        &mut self,
        _cmd: &UnsubscribeInstrumentStatus,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    fn unsubscribe_instrument_close(
        &mut self,
        _cmd: &UnsubscribeInstrumentClose,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    fn request_data(&self, _request: RequestCustomData) -> anyhow::Result<()> {
        // No-op in backtest: data is replayed by the engine
        Ok(())
    }

    fn request_instruments(&self, _request: RequestInstruments) -> anyhow::Result<()> {
        // No-op in backtest: instruments are pre-loaded
        Ok(())
    }

    fn request_instrument(&self, _request: RequestInstrument) -> anyhow::Result<()> {
        // No-op in backtest: instruments are pre-loaded
        Ok(())
    }

    fn request_book_snapshot(&self, _request: RequestBookSnapshot) -> anyhow::Result<()> {
        // No-op in backtest
        Ok(())
    }

    fn request_quotes(&self, _request: RequestQuotes) -> anyhow::Result<()> {
        // No-op in backtest: quotes are replayed by the engine
        Ok(())
    }

    fn request_trades(&self, _request: RequestTrades) -> anyhow::Result<()> {
        // No-op in backtest: trades are replayed by the engine
        Ok(())
    }

    fn request_bars(&self, _request: RequestBars) -> anyhow::Result<()> {
        // No-op in backtest: bars are replayed by the engine
        Ok(())
    }

    fn request_forward_prices(&self, _request: RequestForwardPrices) -> anyhow::Result<()> {
        // No live ATM source in backtest; return Err so the engine fallback
        // creates the option-chain manager without an initial ATM price.
        anyhow::bail!("backtest data client cannot fetch forward prices")
    }
}

#[cfg(test)]
mod tests {
    use nautilus_core::{UUID4, UnixNanos};
    use rstest::rstest;
    use ustr::Ustr;

    use super::*;

    #[rstest]
    fn test_request_forward_prices_returns_err_for_engine_fallback() {
        let client_id = ClientId::new("BACKTEST");
        let venue = Venue::new("BACKTEST");
        let cache = Rc::new(RefCell::new(Cache::default()));
        let client = BacktestDataClient::new(client_id, venue, cache);

        let request = RequestForwardPrices::new(
            venue,
            Ustr::from("BTC"),
            None,
            Some(client_id),
            UUID4::new(),
            UnixNanos::default(),
            None,
        );

        let result = client.request_forward_prices(request);
        assert!(result.is_err());
        let msg = result.unwrap_err().to_string();
        assert!(msg.contains("backtest data client"));
    }
}