nautechsystems/nautilus_trader · error · anyhow::Error

Python on_historical_quotes failed: {e}

Error message

Python on_historical_quotes failed: {e}

What it means

Raised when the strategy's Python `on_historical_quotes` handler throws while processing historical QuoteTicks. The Rust bridge clones the quote slice, dispatches via PyO3, and wraps any error with this anyhow message. The underlying failure is an unhandled exception in the user's Python handler.

Source

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

            };
            self.dispatch_on_historical_data(py_data)
                .map_err(|e| anyhow::anyhow!("Python on_historical_data failed: {e}"))
        })
    }

    fn on_historical_book_deltas(&mut self, deltas: &[OrderBookDelta]) -> anyhow::Result<()> {
        self.dispatch_on_historical_book_deltas(deltas.to_vec())
            .map_err(|e| anyhow::anyhow!("Python on_historical_book_deltas failed: {e}"))
    }

    fn on_historical_book_depth(&mut self, depths: &[OrderBookDepth10]) -> anyhow::Result<()> {
        self.dispatch_on_historical_book_depth(depths.to_vec())
            .map_err(|e| anyhow::anyhow!("Python on_historical_book_depth failed: {e}"))
    }

    fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
        self.dispatch_on_historical_quotes(quotes.to_vec())
            .map_err(|e| anyhow::anyhow!("Python on_historical_quotes failed: {e}"))
    }

    fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
        self.dispatch_on_historical_trades(trades.to_vec())
            .map_err(|e| anyhow::anyhow!("Python on_historical_trades failed: {e}"))
    }

    fn on_historical_funding_rates(
        &mut self,
        funding_rates: &[FundingRateUpdate],
    ) -> anyhow::Result<()> {
        self.dispatch_on_historical_funding_rates(funding_rates.to_vec())
            .map_err(|e| anyhow::anyhow!("Python on_historical_funding_rates failed: {e}"))
    }

    fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
        self.dispatch_on_historical_bars(bars.to_vec())
            .map_err(|e| anyhow::anyhow!("Python on_historical_bars failed: {e}"))

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the Python traceback in `e` and fix `on_historical_quotes`
  2. Guard against empty quote lists and zero/None bid-ask values before computing
  3. Verify signature `on_historical_quotes(self, quotes: list[QuoteTick])`
  4. Test warm-up with an empty-quote scenario

Example fix

// before (Python)
def on_historical_quotes(self, quotes):
    spread = quotes[-1].ask_price - quotes[-1].bid_price  # IndexError when empty
// after
def on_historical_quotes(self, quotes):
    if not quotes:
        return
    spread = quotes[-1].ask_price - quotes[-1].bid_price
Defensive patterns

Strategy: validation

Validate before calling

def quotes_ok(quotes):
    return bool(quotes) and all(q.ask_price > 0 and q.bid_price > 0 for q in quotes)

Type guard

def quotes_usable(quotes):
    return quotes is not None and len(quotes) > 0 and quotes[-1].ask_price is not None and quotes[-1].bid_price is not None

Try / catch

try:
    strategy.on_historical_quotes(quotes)
except Exception as e:
    strategy.log.error(f"on_historical_quotes failed: {e}", exc_info=True)

Prevention

When it happens

Trigger: Historical quote data loads (backtest warm-up or data request) and `on_historical_quotes(self, quotes)` raises — e.g. computing spreads on empty lists, dividing by zero bid/ask, or wrong attribute usage.

Common situations: Handlers computing rolling stats during warm-up with empty windows; division by zero when ask is 0/None; handlers written for bars mistakenly wired to quotes.

Related errors


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