Skip to content

Rust: Closing orders in on_stop() are not executed in BacktestEngine #4062

@zhanghaoda

Description

@zhanghaoda

Bug Report

Confirmation

  • I’ve re-read the relevant sections of the documentation.
  • I’ve searched existing issues and discussions to avoid duplicates.
  • I’ve reviewed or skimmed the source code (or examples) to confirm the behavior is not by design.
  • I’ve tested this issue using a recent development wheel (dev develop or a nightly) and can still reproduce it.

Expected Behavior

After BacktestEngine::end() is called, all trading commands emitted by strategies — including those triggered in on_stop() (such as close_all_positions()) — should be fully processed.

Specifically:

  • Command queues should be drained
  • Orders submitted in on_stop() should be executed
  • Venue modules should process those commands
  • Final fills, positions, and account balances should reflect these executions

Actual Behavior

Commands emitted in on_stop() (e.g., close_all_positions()) are not fully processed before the engines are stopped.

As a result:

  • Closing orders may not be executed
  • Final fills are missing
  • Positions may remain non-zero after backtest completion
  • Account state (PnL, balance) does not reflect expected final trades

Steps to Reproduce the Problem

  1. Implement a strategy that opens positions during on_bar
  2. Call close_all_positions() inside on_stop()
  3. Run a backtest
  4. Ensure there is an open position before the backtest ends
  5. Observe:
    • The position is not closed
    • No corresponding fill is generated
    • Final account state is inconsistent

Code Snippets

Strategy Example

fn on_stop(&mut self) -> anyhow::Result<()> {
    self.cancel_all_orders(self.instrument_id, None, None, None)?;
    self.close_all_positions(self.instrument_id, None, None, None, None, None, None)?;
    self.unsubscribe_bars(self.bar_type, None, None);
    Ok(())
}

Root Cause

In engine.rs, inside BacktestEngine::end():

After stopping the trader, residual commands emitted by strategies (e.g., from on_stop()) are not processed before stopping the engines.


Suggested Fix

Insert the following code after // Stop trader and before // Stop engines:

// Stop trader
self.kernel.stop_trader();


let ts_now = self.kernel.clock.borrow().timestamp_ns();
self.drain_command_queues();
self.settle_venues(ts_now);
self.run_venue_modules(ts_now);


// Stop engines
 self.kernel.data_engine.borrow_mut().stop();
self.kernel.risk_engine.borrow_mut().stop();
self.kernel.exec_engine.borrow_mut().stop();

This ensures all pending commands are executed and reflected in the final state.


Specifications

  • OS platform: Windows
  • Rust version: rustc 1.95.0 (59807616e 2026-04-14)
  • Rust toolchain: rustup 1.28.2
  • nautilus_trader version: 1.227.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions