nautechsystems/nautilus_trader · error

Python on_signal failed: {e}

Error message

Python on_signal failed: {e}

What it means

Raised by the Rust `ExecutionAlgorithm` wrapper when the Python callback `on_signal` throws an exception. Signals routed through the trader are passed to the Python subclass via `dispatch_on_signal`; any `PyErr` raised inside the user's handler is wrapped as `anyhow!("Python on_signal failed: {e}")`.

Source

Thrown at crates/trading/src/python/algorithm.rs:591

    fn on_degrade(&mut self) -> anyhow::Result<()> {
        self.dispatch_no_args("on_degrade")
            .map_err(|e| anyhow::anyhow!("Python on_degrade failed: {e}"))
    }

    fn on_fault(&mut self) -> anyhow::Result<()> {
        self.dispatch_no_args("on_fault")
            .map_err(|e| anyhow::anyhow!("Python on_fault failed: {e}"))
    }

    fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
        ExecutionAlgorithm::on_time_event(self, event)?;
        self.dispatch_time_event(event)
            .map_err(|e| anyhow::anyhow!("Python on_time_event failed: {e}"))
    }

    fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
        self.dispatch_on_signal(signal)
            .map_err(|e| anyhow::anyhow!("Python on_signal failed: {e}"))
    }

    fn on_queue_state(&mut self, event: &QueueStateChanged) -> anyhow::Result<()> {
        self.dispatch_on_queue_state(event)
            .map_err(|e| anyhow::anyhow!("Python on_queue_state failed: {e}"))
    }

    fn on_socket_state(&mut self, event: &SocketStateChanged) -> anyhow::Result<()> {
        self.dispatch_on_socket_state(event)
            .map_err(|e| anyhow::anyhow!("Python on_socket_state failed: {e}"))
    }
}

#[pyo3::pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
#[allow(
    clippy::large_types_passed_by_value,
    reason = "PyO3 callbacks accept Python-owned event values"

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the chained Python traceback in the message to find the real exception inside your `on_signal` override
  2. Validate/parse `signal.data` defensively before using it (check type, decode with fallback)
  3. Add try/except around signal parsing and log-and-drop invalid signals instead of raising
  4. Ensure the published signal's data type matches what `on_signal` unpacks
  5. Confirm the override signature is `def on_signal(self, signal) -> None:`

Example fix

# before
def on_signal(self, signal):
    payload = json.loads(signal.data.value)["target"]  # KeyError on malformed signal

# after
def on_signal(self, signal):
    try:
        payload = json.loads(signal.data.value).get("target")
    except (ValueError, AttributeError) as e:
        self.log.error(f"Bad signal {signal.topic}: {e}")
        return
    if payload is None:
        return
Defensive patterns

Strategy: validation

Validate before calling

def safe_on_signal(signal):
    raw = getattr(signal, 'data', None)
    return raw is not None  # validate before parsing in on_signal

Type guard

def is_parseable_signal(signal):
    data = getattr(signal, 'data', None)
    return data is not None and hasattr(data, 'value')

Try / catch

try:
    algo.on_signal(signal)
except Exception as e:
    log.error(f"on_signal failed for topic '{getattr(signal, 'topic', '?')}': {e}")

Prevention

When it happens

Trigger: Publishing/receiving a `Signal` that is dispatched to a registered execution algorithm whose Python subclass overrides `on_signal` and raises — e.g. parsing `signal.data` with the wrong type, assuming a custom data type that doesn't match, or errors in signal-driven order logic.

Common situations: Signal payloads parsed with `json.loads` failing on malformed data; custom `DataType` mismatches between the publisher and the algorithm; missing signal topic handlers; errors after strategy config changes.

Related errors


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