nautechsystems/nautilus_trader · error

Python on_pool_swap failed: {e}

Error message

Python on_pool_swap failed: {e}

What it means

This error is raised by the Rust `DataActor::on_pool_swap` wrapper (crates/common/src/python/actor.rs:1181) when dispatching a `PoolSwap` DeFi event to the Python actor's `on_pool_swap` callback fails. The underlying `dispatch_on_pool_swap` calls `py_self.call_method1(py, "on_pool_swap", ...)` via pyo3, so any Python exception raised inside the user's handler, a missing/incorrectly-named handler method, or a failure converting the `PoolSwap` to a Python object (`.into_py_any`) surfaces here wrapped as `Python on_pool_swap failed: {e}`. It is the library's way of propagating a Python-side callback failure back into the Rust actor pipeline.

Source

Thrown at crates/common/src/python/actor.rs:1181

            .map_err(|e| anyhow::anyhow!("Python on_option_chain failed: {e}"))
    }

    #[cfg(feature = "defi")]
    fn on_block(&mut self, block: &Block) -> anyhow::Result<()> {
        self.dispatch_on_block(block.clone())
            .map_err(|e| anyhow::anyhow!("Python on_block failed: {e}"))
    }

    #[cfg(feature = "defi")]
    fn on_pool(&mut self, pool: &Pool) -> anyhow::Result<()> {
        self.dispatch_on_pool(pool.clone())
            .map_err(|e| anyhow::anyhow!("Python on_pool failed: {e}"))
    }

    #[cfg(feature = "defi")]
    fn on_pool_swap(&mut self, swap: &PoolSwap) -> anyhow::Result<()> {
        self.dispatch_on_pool_swap(swap.clone())
            .map_err(|e| anyhow::anyhow!("Python on_pool_swap failed: {e}"))
    }

    #[cfg(feature = "defi")]
    fn on_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate) -> anyhow::Result<()> {
        self.dispatch_on_pool_liquidity_update(update.clone())
            .map_err(|e| anyhow::anyhow!("Python on_pool_liquidity_update failed: {e}"))
    }

    #[cfg(feature = "defi")]
    fn on_pool_fee_collect(&mut self, collect: &PoolFeeCollect) -> anyhow::Result<()> {
        self.dispatch_on_pool_fee_collect(collect.clone())
            .map_err(|e| anyhow::anyhow!("Python on_pool_fee_collect failed: {e}"))
    }

    #[cfg(feature = "defi")]
    fn on_pool_flash(&mut self, flash: &PoolFlash) -> anyhow::Result<()> {
        self.dispatch_on_pool_flash(flash.clone())
            .map_err(|e| anyhow::anyhow!("Python on_pool_flash failed: {e}"))

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the chained `{e}` message in the log; it contains the original Python traceback/exception raised inside your `on_pool_swap` handler - fix that root cause first.
  2. Verify your Python actor class defines a callable `on_pool_swap(self, swap)` method with the correct signature and no typos in the name.
  3. Add defensive error handling/logging inside the Python handler and test it directly with a synthetic PoolSwap before running the live/backtest pipeline.
  4. Confirm your nautilus version's PoolSwap fields match what the handler accesses; update handler code after upgrading the crate.

Example fix

# before: handler raises on unexpected None
def on_pool_swap(self, swap):
    amount = int(swap.amount)  # raises if amount is None/unexpected

# after: validate inputs defensively
def on_pool_swap(self, swap):
    if swap.amount is None:
        self.log.warning("PoolSwap missing amount, skipping")
        return
    amount = int(swap.amount)
Defensive patterns

Strategy: try-catch

Validate before calling

assert callable(getattr(actor, 'on_pool_swap', None)), "actor must implement on_pool_swap"

Type guard

def has_pool_swap_handler(obj):
    return callable(getattr(obj, 'on_pool_swap', None))

Try / catch

try:
    actor.on_pool_swap(swap)
except Exception as e:
    log.error(f"on_pool_swap handler failed: {e}", exc_info=True)  # inspect chained cause

Prevention

When it happens

Trigger: Occurs when `on_pool_swap(&mut self, swap: &PoolSwap)` is invoked and (1) the bound Python actor instance raises an exception inside its `on_pool_swap` method, (2) the Python object has no callable `on_pool_swap` attribute (pyo3 call_method1 fails), or (3) `swap.into_py_any(py)` fails to convert the PoolSwap into a Python object.

Common situations: A Python strategy subclasses the actor but overrides or renames `on_pool_swap` incorrectly, or the handler code itself throws (e.g. unpacking swap fields, math on amounts, calling a DEX/contract helper that fails). Also common when the defi feature data types changed between nautilus versions and user handler signatures no longer match, or when custom __init__ code leaves the actor partially initialized so the callback errors.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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