nautechsystems/nautilus_trader · error

Python on_stop failed

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.

Solutions

  1. Read the chained Python traceback to find the raising line in `on_stop`.
  2. Guard each cleanup step in `on_stop` with `try/except` so one failing step doesn't abort the rest of shutdown.
  3. Check client/adapter connectivity before calling cancel/close/unsubscribe operations in `on_stop`.
  4. 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

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


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

Appendix: 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)