nautechsystems/nautilus_trader · error

BacktestEngine requires TestClock

Error message

BacktestEngine requires TestClock

What it means

BacktestEngine's time-advancement path downcasts the provided dyn Clock to a concrete TestClock because the engine's event scheduling is driven by the mutable time-setting API of TestClock. If the borrowed clock is not actually a TestClock (e.g. a LiveClock or custom Clock), the downcast returns None and this panic fires.

Source

Thrown at crates/backtest/src/engine.rs:2065

        }
    }

    fn init_command_senders() {
        replace_data_cmd_sender(Arc::new(SyncDataCommandSender));
        replace_exec_cmd_sender(Arc::new(SyncTradingCommandSender));
    }

    fn advance_clock_on_accumulator(
        accumulator: &mut TimeEventAccumulator,
        clock: &Rc<RefCell<dyn Clock>>,
        to_time_ns: UnixNanos,
        set_time: bool,
    ) {
        let mut clock_ref = clock.borrow_mut();
        let test_clock = clock_ref
            .as_any_mut()
            .downcast_mut::<TestClock>()
            .expect("BacktestEngine requires TestClock");
        accumulator.advance_clock(test_clock, to_time_ns, set_time);
    }

    fn set_all_clocks_time(clocks: &[Rc<RefCell<dyn Clock>>], ts: UnixNanos) {
        for clock in clocks {
            let mut clock_ref = clock.borrow_mut();
            let test_clock = clock_ref
                .as_any_mut()
                .downcast_mut::<TestClock>()
                .expect("BacktestEngine requires TestClock");
            test_clock.set_time(ts);
        }
    }

    #[rustfmt::skip]
    fn log_pre_run(&self) {
        log_info!("=================================================================", color = LogColor::Cyan);
        log_info!(" BACKTEST PRE-RUN", color = LogColor::Cyan);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Use TestClock::new() as the engine's clock (default when using BacktestEngine::new / the standard kernel builder)
  2. Ensure any custom clock wrapper used in backtests dereferences to a real TestClock or use TestClock directly
  3. Only call advance_time on engines built with the backtest (TestClock) kernel

Example fix

// before
let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(LiveClock::new()));
engine.add_client(...); // engine built with non-TestClock
engine.advance_time(ts, true); // panics
// after
let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(TestClock::new()));
// build engine with the TestClock, then advance_time works
Defensive patterns

Strategy: type-guard

Validate before calling

use std::any::Any;
fn is_test_clock(clock: &Rc<RefCell<dyn Clock>>) -> bool {
    clock.borrow().as_any().downcast_ref::<TestClock>().is_some()
}

Type guard

fn as_test_clock(clock: &mut Rc<RefCell<dyn Clock>>) -> Option<std::cell::RefMut<TestClock>> {
    RefMut::filter_map(clock.borrow_mut(), |c| c.as_any_mut().downcast_mut::<TestClock>()).ok()
}

Try / catch

// Downcast failure panics; pre-check instead:
if as_test_clock(&clock).is_none() {
    panic!("backtest engine must be built with TestClock");
}
engine.advance_time(ts, true);

Prevention

When it happens

Trigger: Calling advance_time (or the internal advance-clock helper) with a clock Rc<RefCell<dyn Clock>> that is not a TestClock — e.g. wiring a LiveClock, a mock Clock other than TestClock, or a wrapped clock into the backtest engine and then advancing time.

Common situations: Mixing live-trading clock setup code with the backtest engine; constructing the engine kernel with a custom Clock implementation; copy-pasted kernel builders shared between live and backtest code paths.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/a6cf7c400fe3c654. Report an issue: GitHub.