nautechsystems/nautilus_trader · error

Python on_resume failed: {e}

Error message

Python on_resume failed: {e}

What it means

This error wraps any Python exception raised by the strategy's user-implemented `on_resume()` callback. The Rust `DataActor::on_resume` dispatches to the Python instance via `call_method0(py, "on_resume")` (crates/trading/src/python/strategy.rs:349-354) and re-wraps any `PyErr` as `anyhow::anyhow!("Python on_resume failed: {e}")`. The strategy's transition from a degraded/paused state back to running is aborted.

Source

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

        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<()> {
        self.dispatch_on_dispose()
            .map_err(|e| anyhow::anyhow!("Python on_dispose failed: {e}"))
    }

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

    fn on_fault(&mut self) -> anyhow::Result<()> {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the chained Python traceback to locate the raising line in `on_resume`.
  2. Validate that resources needed on resume (subscriptions, instruments, clients) still exist before using them.
  3. Make `on_resume` re-establish state rather than assume it, e.g. re-derive subscriptions from current cache contents.
  4. Add a unit test covering the degrade -> resume cycle for your strategy.

Example fix

// before (strategy.py)
def on_resume(self):
    self.subscribe_quote_ticks(self._instrument.id)  # _instrument may be stale/None

// after
def on_resume(self):
    instrument = self.cache.instrument(self._instrument_id)
    if instrument is None:
        self.log.error(f"Instrument {self._instrument_id} unavailable; cannot resume")
        return
    self.subscribe_quote_ticks(instrument.id)
Defensive patterns

Strategy: try-catch

Validate before calling

# before calling resume
instrument = strategy.cache.instrument(strategy._instrument_id)
assert instrument is not None, "instrument missing from cache; cannot resume"

Type guard

def resumable(strategy) -> bool:
    return (
        strategy._instrument_id is not None
        and strategy.cache.instrument(strategy._instrument_id) is not None
    )

Try / catch

def on_resume(self):
    try:
        self._resubscribe()
    except Exception as e:
        self.log.exception(f"on_resume failed: {e}")
        self.degrade()  # fall back to degraded state instead of crashing

Prevention

When it happens

Trigger: Calling `strategy.resume()` (typically after `on_degrade` or a fault-recovery flow) when the Python subclass's `on_resume()` raises: e.g. re-subscribing with a stale/invalid bar type, touching resources released during degradation, or an unhandled exception in user resume logic.

Common situations: Recovery flows where resume logic assumes state that degradation tore down, resubscribing to instruments that were delisted or renamed, or resume handlers written but never exercised in tests.

Related errors


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