nautechsystems/nautilus_trader · error

Simulation module {module_index} {method} failed: {error:#}

Error message

Simulation module {module_index} {method} failed: {error:#}

What it means

When a simulation module (aAct/act component attached to the exchange) raises an error during pre-processing or per-data processing, store_module_error wraps it with the module index and method name, stores it (the next module callback surfaces it via check_module_error), and returns the enriched error. It tells you exactly which module and phase failed.

Source

Thrown at crates/backtest/src/exchange.rs:349

            anyhow::bail!("Simulation module failure requires exchange reset: {error}");
        }
        Ok(())
    }

    #[must_use]
    pub(crate) const fn has_module_error(&self) -> bool {
        self.module_error.is_some()
    }

    fn store_module_error(
        &mut self,
        module_index: usize,
        method: &str,
        error: &anyhow::Error,
    ) -> anyhow::Error {
        let error = format!("Simulation module {module_index} {method} failed: {error:#}");
        self.module_error = Some(error.clone());
        anyhow::anyhow!(error)
    }

    fn pre_process_modules(&mut self, data: &Data) -> anyhow::Result<()> {
        self.check_module_error()?;

        for module_index in 0..self.modules.len() {
            if let Err(e) = self.modules[module_index].pre_process(data) {
                return Err(self.store_module_error(module_index, "pre_process", &e));
            }
        }
        Ok(())
    }

    /// Returns the configured book type for this venue.
    #[must_use]
    pub const fn book_type(&self) -> BookType {
        self.book_type
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the full chained message (the suffix after 'failed:') for the module's root cause.
  2. Inspect the module at the reported module_index (order of modules added to the exchange) and the reported method (pre_process vs process).
  3. Harden your custom module's process/pre_process against missing instruments/prices by returning controlled errors or skipping.
  4. Check the data stream for anomalies (gaps, zero prices) fed to the exchange before the module runs.

Example fix

// before
fn process(&mut self, data: &Data) -> anyhow::Result<()> {
    let price = self.last_price(data.instrument_id()).unwrap(); // panics -> module error
    Ok(())
}
// after
fn process(&mut self, data: &Data) -> anyhow::Result<()> {
    let Some(price) = self.last_price(data.instrument_id()) else {
        tracing::warn!("no price yet for {}; skipping", data.instrument_id());
        return Ok(());
    };
    Ok(())
}
Defensive patterns

Strategy: try-catch

Validate before calling

// keep custom module processing defensive
fn process(&mut self, data: &Data) -> anyhow::Result<()> {
    self.state.required_context().ok_or_else(|| anyhow::anyhow!("module not initialized"))?;
    Ok(())
}

Try / catch

match result {
    Err(e) if format!("{e:#}").starts_with("Simulation module") => {
        // parse module_index and method from the message, log, then disable that module and retry
        eprintln!("module failure: {e:#}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: pre_process_modules or process_modules invoking module.pre_process(data)/module.process(data) (or similar) which returns Err for module at module_index; the wrapped message includes the original error chain via {error:#}.

Common situations: A custom user module panicking/erroring on unexpected data (None prices, missing instruments); a built-in module encountering data it cannot handle at the exchange level; module state corrupted after a prior failure.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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