nautechsystems/nautilus_trader · critical

Python on_start failed: {e}

Error message

Python on_start failed: {e}

What it means

This error wraps any Python exception raised by the strategy's user-implemented `on_start()` callback. The Rust `DataActor::on_start` first runs the native `Strategy::on_start` state transition, then dispatches to the Python instance via `py_self.call_method0(py, "on_start")` (crates/trading/src/python/strategy.rs:335-340). If that Python call returns a `PyErr`, it is re-wrapped with `anyhow::anyhow!("Python on_start failed: {e}")` and propagated, aborting the strategy's startup sequence.

Source

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

    fn on_position_event(&mut self, event: PositionEvent) {
        let _ = self.dispatch_on_position_event(event);
    }

    fn on_position_changed(&mut self, event: PositionChanged) {
        let _ = self.dispatch_on_position_changed(event);
    }

    fn on_position_closed(&mut self, event: PositionClosed) {
        let _ = self.dispatch_on_position_closed(event);
    }
}

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

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the chained Python traceback in the error message; it names the exact line in your strategy's `on_start` that raised.
  2. Wrap risky startup logic (subscriptions, config access) in `try/except` inside `on_start` and log/handle, or fail fast with a clear message.
  3. Validate strategy config fields (instrument IDs, client IDs, bar/quote types) before starting the trader node.
  4. Verify all adapters/clients the strategy subscribes through are connected before `trader.start()`.

Example fix

// before (strategy.py)
def on_start(self):
    self.subscribe_bars(self.config.bar_type)  # raises if bar_type is None

// after
def on_start(self):
    if self.config.bar_type is None:
        self.log.error("bar_type not configured; not subscribing")
        return
    self.subscribe_bars(BarType.from_str(self.config.bar_type))
Defensive patterns

Strategy: try-catch

Validate before calling

# before starting the trader
assert strategy.config.bar_type is not None, "bar_type missing"
assert strategy.config.instrument_id is not None, "instrument_id missing"
for cid in [strategy.config.client_id]:
    assert trader is not None, "trader not built"
# optionally dry-run the subscription targets through the cache
assert strategy.cache.instrument(strategy.config.instrument_id) is not None

Type guard

def ensure_bar_type(config) -> BarType:
    bt = getattr(config, "bar_type", None)
    if not isinstance(bt, (str, BarType)) or (isinstance(bt, str) and not bt):
        raise ValueError(f"config.bar_type invalid: {bt!r}")
    return BarType.from_str(bt) if isinstance(bt, str) else bt

Try / catch

def on_start(self):
    try:
        self.subscribe_bars(self.bar_type)
    except Exception as e:
        self.log.exception(f"on_start subscription failed: {e}")
        # decide: return (degrade) or re-raise to stop startup deliberately

Prevention

When it happens

Trigger: Calling `strategy.start()` (or the trader/engine's start path) when the Python subclass's `on_start()` raises: e.g. subscribing to an invalid instrument ID, `self.subscribe_...` on a disconnected client, accessing config fields that are None, an unregistered clock timer name, or any unhandled exception in user code inside `on_start`.

Common situations: Config mistakes (missing/typo'd instrument IDs or client IDs in strategy config), calling subscription or order APIs before connections are ready, referencing cache entries that don't exist yet, or a Python-side bug such as a NameError/AttributeError introduced in a strategy edit.

Related errors


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