Bug Report
Confirmation
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
- Implement a strategy that opens positions during
on_bar
- Call
close_all_positions() inside on_stop()
- Run a backtest
- Ensure there is an open position before the backtest ends
- 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
Bug Report
Confirmation
devdevelop oranightly) and can still reproduce it.Expected Behavior
After
BacktestEngine::end()is called, all trading commands emitted by strategies — including those triggered inon_stop()(such asclose_all_positions()) — should be fully processed.Specifically:
on_stop()should be executedActual Behavior
Commands emitted in
on_stop()(e.g.,close_all_positions()) are not fully processed before the engines are stopped.As a result:
Steps to Reproduce the Problem
on_barclose_all_positions()insideon_stop()Code Snippets
Strategy Example
Root Cause
In
engine.rs, insideBacktestEngine::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 traderand before// Stop engines:This ensures all pending commands are executed and reflected in the final state.
Specifications
nautilus_traderversion: 1.227.0