nautechsystems/nautilus_trader · error

Python SimulationModule.process failed: {e}

Error message

Python SimulationModule.process failed: {e}

What it means

FFI boundary error in the Python SimulationModule wrapper: the module's Python process() callback raised an exception or returned a bad value; the Python error is embedded in the message and the simulation step fails.

Source

Thrown at crates/backtest/src/python/modules.rs:296

    fn process(
        &self,
        ts_now: nautilus_core::UnixNanos,
        ctx: &ExchangeContext,
    ) -> anyhow::Result<SimulationModuleResult> {
        Python::attach(|py| -> anyhow::Result<SimulationModuleResult> {
            let context = Py::new(py, PySimulationModuleContext::from_exchange(ctx))?;
            let adjustments = self
                .obj
                .bind(py)
                .call_method1("process", (ts_now.as_u64(), context))?
                .extract::<Option<Vec<Money>>>()?;
            Ok(adjustments.map_or(
                SimulationModuleResult::NotReady,
                SimulationModuleResult::Completed,
            ))
        })
        .map_err(|e| anyhow::anyhow!("Python SimulationModule.process failed: {e}"))
    }

    fn acknowledge(&self, outcomes: &[AccountAdjustmentOutcome]) -> anyhow::Result<()> {
        Python::attach(|py| -> anyhow::Result<()> {
            let outcomes = outcomes
                .iter()
                .map(PyAccountAdjustmentOutcome::from)
                .collect::<Vec<_>>();
            self.obj.bind(py).call_method1("acknowledge", (outcomes,))?;
            Ok(())
        })
        .map_err(|e| anyhow::anyhow!("Python SimulationModule.acknowledge failed: {e}"))
    }

    fn log_diagnostics(&self) -> anyhow::Result<()> {
        Python::attach(|py| -> anyhow::Result<()> {
            self.obj.bind(py).call_method0("log_diagnostics")?;
            Ok(())

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the embedded Python traceback and fix the exception in the module's process method.
  2. Handle edge cases: empty/None inputs, first-tick state, zero quantities.
  3. Reproduce with a minimal data slice (single tick/bar) to debug quickly.

Example fix

# before
def process(self, ts_now, data):
    return self.book[data.instrument_id].best_bid()  # KeyError
# after
def process(self, ts_now, data):
    book = self.book.get(data.instrument_id)
    return book.best_bid() if book else None
Defensive patterns

Strategy: try-catch

Validate before calling

module.process(0, sample_data, sample_ctx)  # smoke-test edge inputs before the run

Try / catch

class SafeModule(SimulationModule):
    def process(self, ts_now, data, ctx):
        try:
            return self._process(ts_now, data, ctx)
        except Exception:
            logging.exception("SimulationModule.process failed at ts=%s", ts_now)
            raise

Prevention

When it happens

Trigger: The Python object's process(ts_now, data, ...) raises during exchange processing (e.g. arithmetic on None, missing account state, exceptions in custom liquidity/fill logic).

Common situations: Custom fee/latency/fill modules hitting unhandled market states (first tick, empty book, halted instrument); division by zero in slippage calculations.

Related errors


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