nautechsystems/nautilus_trader · error

Python on_stop failed: {e}

Error message

Python on_stop failed: {e}

What it means

This error is raised by the Rust `DataActor` bridge for Python actors (PyDataActorInner) when the user's Python `on_stop()` callback raises an exception. The Rust layer catches the Python error via `dispatch_on_stop()` and re-wraps it in an `anyhow::Error` with this message so the engine can surface it during component stop. The original Python traceback is embedded in `{e}`.

Source

Thrown at crates/common/src/python/actor.rs:1025

            if let Err(e) = algo.execute_exec_algorithm_command(command) {
                log::error!("Error executing command on Python algorithm {actor_id}: {e}");
            }
        } else {
            log::error!("Python execution algorithm {actor_id} not found in registry");
        }
    });
    msgbus::register_any(endpoint.into(), handler);
}

impl DataActor for PyDataActorInner {
    fn on_start(&mut self) -> anyhow::Result<()> {
        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

  1. Read the embedded Python traceback in `{e}` to find the exact line in your on_stop that raised.
  2. Make on_stop defensive: wrap fallible cleanup (network calls, order cancels) in try/except and log instead of raising.
  3. Verify resources used in on_stop (clients, cache, connections) are still valid at stop time and not already disposed.
  4. Run the strategy in isolation and call on_stop manually under a debugger to reproduce the failure.

Example fix

// before (Python)
def on_stop(self):
    self.client.close()
    self.logger.info(self.last_price)

// after
def on_stop(self):
    try:
        self.client.close()
    except Exception as e:
        self.log.error(f"cleanup failed: {e}")
    self.logger.info(getattr(self, "last_price", None))
Defensive patterns

Strategy: try-catch

Validate before calling

import inspect
assert inspect.ismethod(actor.on_stop), "on_stop must be a defined method"
# dry-run in a test harness: actor._log; call on_stop with stubbed clients before live stop

Type guard

def has_safe_on_stop(actor):
    return callable(getattr(actor, 'on_stop', None))

Try / catch

def on_stop(self):
    try:
        ...  # cleanup logic
    except Exception as e:
        self.log.error(f"on_stop cleanup failed: {e}")

Prevention

When it happens

Trigger: Any exception raised inside a custom Python Actor/Strategy's `on_stop` method: calling unavailable clients or resources being torn down, accessing attributes deleted earlier in the stop sequence, or a typo raising AttributeError, etc.

Common situations: Strategies that flush state, close network connections, or cancel orders in on_stop and hit an already-closed resource; referencing a `self.cache` or client object that was released; typos in method names inside cleanup code.

Related errors


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