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
- 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.
- Verify your Python actor class defines a callable `on_pool_swap(self, swap)` method with the correct signature and no typos in the name.
- Add defensive error handling/logging inside the Python handler and test it directly with a synthetic PoolSwap before running the live/backtest pipeline.
- 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
- Always implement on_pool_swap on any actor subscribing to pool swap data
- Validate swap fields (amount, amounts in/out) at the top of the handler
- Unit-test the handler with synthetic PoolSwap events
- Re-check handler field access after every nautilus upgrade
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
- Python on_pool_liquidity_update failed: {e}
- Python on_pool_fee_collect failed: {e}
- Python on_pool_flash failed: {e}
- Failed to convert historical data to Python: unsupported typ
- Failed to convert instrument to Python: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/fa8c7be6d65a3d49.
Report an issue: GitHub.