nautechsystems/nautilus_trader · error
Python on_stop failed: {e}
Error message
Python on_stop failed: {e} What it means
This error wraps any Python exception raised by the strategy's user-implemented `on_stop()` callback. The Rust `DataActor::on_stop` dispatches to the Python instance via `call_method0(py, "on_stop")` (crates/trading/src/python/strategy.rs:342-347) and re-wraps any `PyErr` as `anyhow::anyhow!("Python on_stop failed: {e}")`. It indicates cleanup code executed at strategy shutdown failed, which can leave orders, subscriptions, or timers in an unclean state.
Source
Thrown at crates/trading/src/python/strategy.rs:1066
fn on_position_changed(&mut self, event: PositionChanged) {
let _ = self.dispatch_on_position_changed(event);
}
fn on_position_closed(&mut self, event: PositionClosed) {
let _ = self.dispatch_on_position_closed(event);
}
}
impl DataActor for PyStrategyInner {
fn on_start(&mut self) -> anyhow::Result<()> {
Strategy::on_start(self)?;
self.dispatch_on_start()
.map_err(|e| anyhow::anyhow!("Python on_start failed: {e}"))
}
fn on_stop(&mut self) -> anyhow::Result<()> {
self.dispatch_on_stop()
.map_err(|e| anyhow::anyhow!("Python on_stop failed: {e}"))
}
fn on_resume(&mut self) -> anyhow::Result<()> {
self.dispatch_on_resume()
.map_err(|e| anyhow::anyhow!("Python on_resume failed: {e}"))
}
fn on_reset(&mut self) -> anyhow::Result<()> {
self.dispatch_on_reset()
.map_err(|e| anyhow::anyhow!("Python on_reset failed: {e}"))
}
fn on_dispose(&mut self) -> anyhow::Result<()> {
self.dispatch_on_dispose()
.map_err(|e| anyhow::anyhow!("Python on_dispose failed: {e}"))
}
fn on_degrade(&mut self) -> anyhow::Result<()> {View on GitHub (pinned to 18893faf8b)
Solutions
- Read the chained Python traceback to find the raising line in `on_stop`.
- Guard each cleanup step in `on_stop` with `try/except` so one failing step doesn't abort the rest of shutdown.
- Check client/adapter connectivity before calling cancel/close/unsubscribe operations in `on_stop`.
- Make `on_stop` idempotent so repeated stop calls or already-released resources don't raise.
Example fix
// before (strategy.py)
def on_stop(self):
self.cancel_all_orders(self.instrument.id) # raises if client disconnected
// after
def on_stop(self):
try:
self.cancel_all_orders(self.instrument.id)
except Exception as e:
self.log.error(f"Failed to cancel orders on stop: {e}") Defensive patterns
Strategy: try-catch
Try / catch
def on_stop(self):
for step in (self._cancel_open_orders, self._unsubscribe_all):
try:
step()
except Exception as e:
self.log.exception(f"on_stop step {step.__name__} failed: {e}") Prevention
- Make every step of `on_stop` independently guarded so shutdown always completes.
- Check connectivity before cancel/unsubscribe calls at shutdown time.
- Write `on_stop` to be idempotent; assume it may run after a fault or repeated stop.
- Test the full stop path (including Ctrl+C shutdown) in staging regularly.
When it happens
Trigger: Calling `strategy.stop()` (or stopping the trader node / Ctrl+C shutdown) when the Python subclass's `on_stop()` raises: e.g. canceling orders through a disconnected client, accessing actors/cache state already torn down, or any bug in user cleanup code.
Common situations: Shutdown-time races (venue/adapter already disconnected when cancel-orders runs), NameError/AttributeError in cleanup logic added after initial development, or double-stop flows where state was already reset.
Related errors
- Python on_stop failed: {e}
- Python on_start failed: {e}
- Python on_resume failed: {e}
- Python on_reset failed: {e}
- Python on_dispose failed: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/45d8cdbd751d0a0c.
Report an issue: GitHub.