nautechsystems/nautilus_trader · error

Python on_instrument failed: {e}

Error message

Python on_instrument failed: {e}

What it means

This error wraps a Python exception raised inside a strategy's `on_instrument` handler. The Rust core converted the `InstrumentAny` object to a Python object successfully, then called `dispatch_on_instrument`, which invokes the user-overridden Python method; any exception propagating out of that Python code is captured and re-thrown as this anyhow error. The library throws it so Rust-side infrastructure can uniformly handle (usually log or stop) strategy callback failures.

Source

Thrown at crates/trading/src/python/strategy.rs:1139

            .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}"))
    }

    fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
        Python::attach(|py| {
            let py_instrument = instrument_any_to_pyobject(py, instrument.clone())
                .map_err(|e| anyhow::anyhow!("Failed to convert InstrumentAny to Python: {e}"))?;
            self.dispatch_on_instrument(py_instrument)
                .map_err(|e| anyhow::anyhow!("Python on_instrument failed: {e}"))
        })
    }

    fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
        self.dispatch_on_quote(*quote)
            .map_err(|e| anyhow::anyhow!("Python on_quote failed: {e}"))
    }

    fn on_trade(&mut self, tick: &TradeTick) -> anyhow::Result<()> {
        self.dispatch_on_trade(*tick)
            .map_err(|e| anyhow::anyhow!("Python on_trade failed: {e}"))
    }

    fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
        self.dispatch_on_bar(*bar)
            .map_err(|e| anyhow::anyhow!("Python on_bar failed: {e}"))
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the chained `{e}` text in the log for the underlying Python traceback (file/line of the user handler) and fix the exception there.
  2. Wrap the body of the Python `on_instrument` override in try/except and log/handle expected per-instrument-type mismatches.
  3. Check the instrument's variant before using variant-specific attributes (e.g. `isinstance(instrument, CryptoPerpetual)`).
  4. Verify the strategy code against the installed nautilus_trader version for renamed instrument properties.

Example fix

# before
def on_instrument(self, instrument):
    lot_size = instrument.option_details.lot_size  # AttributeError for non-options

# after
def on_instrument(self, instrument):
    if isinstance(instrument, Option):
        lot_size = instrument.option_details.lot_size
    else:
        lot_size = instrument.size_increment
Defensive patterns

Strategy: try-catch

Validate before calling

# in Python, before relying on instrument attributes
def validate_instrument(instrument):
    assert instrument is not None and instrument.id.value
    assert instrument.size_increment and instrument.price_increment

Type guard

def is_option(instrument):
    return hasattr(instrument, 'option_details') and instrument.option_details is not None

Try / catch

def on_instrument(self, instrument):
    try:
        self._handle_instrument(instrument)
    except Exception as e:
        self.log.error(f"on_instrument failed for {instrument.id}: {e}", exc_info=True)

Prevention

When it happens

Trigger: Any unhandled Python exception in a strategy's `on_instrument(self, instrument)` override: e.g. calling an instrument attribute that is None for that asset class, dividing by an instrument's size_increment of 0, type errors from assuming a specific InstrumentAny variant, or an explicit `raise` in user code.

Common situations: Subscribing to instruments across multiple asset classes and assuming `instrument.option_details` or similar exists; copy-pasted handler logic from `on_bar` that indexes the instrument like a bar; library version change where an instrument property was renamed or made optional.

Related errors


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