nautechsystems/nautilus_trader · error

Python on_time_event failed: {e}

Error message

Python on_time_event failed: {e}

What it means

Raised by the Rust `ExecutionAlgorithm` wrapper when the Python callback `on_time_event` throws an exception. The wrapper first runs the native Rust `ExecutionAlgorithm::on_time_event`, then dispatches the `TimeEvent` into the Python subclass via `dispatch_time_event`; a `PyErr` from the user's handler is wrapped in `anyhow!("Python on_time_event failed: {e}")`.

Source

Thrown at crates/trading/src/python/algorithm.rs:586

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

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

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

    fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
        ExecutionAlgorithm::on_time_event(self, event)?;
        self.dispatch_time_event(event)
            .map_err(|e| anyhow::anyhow!("Python on_time_event failed: {e}"))
    }

    fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
        self.dispatch_on_signal(signal)
            .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}"))
    }
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the embedded Python traceback in the error to locate the failing line in your `on_time_event` override
  2. Handle the incoming `TimeEvent` generically (check `event.name`/`event.tags`) instead of assuming a specific timer
  3. Add try/except inside `on_time_event` for expected data conditions and log the failure
  4. Confirm the override signature is `def on_time_event(self, event) -> None:`
  5. Re-register or cancel stale timers in `on_stop`/`on_fault` so no handler fires against torn-down state

Example fix

# before
def on_time_event(self, event):
    price = self.cache.price(self.instrument_id)  # raises if no price yet

# after
def on_time_event(self, event):
    price = self.cache.price(self.instrument_id)
    if price is None:
        self.log.warning(f"No price yet for {event.name}")
        return
Defensive patterns

Strategy: try-catch

Validate before calling

sig = inspect.signature(MyAlgo.on_time_event)
assert list(sig.parameters) == ['self', 'event']

Type guard

def is_time_event(ev):
    return hasattr(ev, 'ts_event') and hasattr(ev, 'name')  # TimeEvent surface

Try / catch

try:
    algo.on_time_event(event)
except Exception as e:
    log.error(f"on_time_event failed for timer '{event.name}': {e}")

Prevention

When it happens

Trigger: A registered timer fires and the trader calls `on_time_event(event)`; the user's Python subclass overrides `on_time_event` and raises (e.g. mishandles the event type, wrong event class assumed, or an exception inside timed logic). Also triggered by `algo.on_time_event(event)` called directly with a `TimeEvent`.

Common situations: Timers set with `clock.set_timer` whose handlers unpack event fields that don't exist after a version change; logic assuming a specific `TimeEvent` name/tag; unhandled exceptions inside backtest loops at timer expiry.

Related errors


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