nautechsystems/nautilus_trader · error

invalid candle bar: {e}

Error message

invalid candle bar: {e}

What it means

parse_ws_bar builds a NautilusTrader Bar from a Lighter candlestick websocket message and validates the OHLCV combination via Bar::new_checked. When any component (open/high/low/close price, volume, or timestamps) violates Bar invariants — e.g. a price with excess precision for the instrument, a negative/NaN value, or zero-precision mismatch — the check fails and the parse is aborted with this anyhow error so the bad candle is not emitted downstream.

Source

Thrown at crates/adapters/lighter/src/websocket/parse.rs:421

    let low = Price::from_decimal_dp(candle.l, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle low: {e}"))?;
    let close = Price::from_decimal_dp(candle.c, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle close: {e}"))?;
    let volume = Quantity::from_decimal_dp(candle.v, size_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle volume: {e}"))?;

    let t_ms = u64::try_from(candle.t)
        .map_err(|_| anyhow::anyhow!("negative candle timestamp: {}", candle.t))?;
    let ts_event = parse_millis_to_nanos(t_ms)?;

    let bar_type = BarType::new(
        instrument.id(),
        resolution.to_bar_spec(),
        AggregationSource::External,
    );

    Bar::new_checked(bar_type, open, high, low, close, volume, ts_event, ts_init)
        .map_err(|e| anyhow::anyhow!("invalid candle bar: {e}"))
}

fn parse_book_level_delta(
    level: &LighterPriceLevel,
    instrument: &InstrumentAny,
    side: OrderSide,
    sequence: u64,
    ts_event: UnixNanos,
    ts_init: UnixNanos,
    flags: u8,
) -> anyhow::Result<OrderBookDelta> {
    let price = price_from_decimal(level.price, instrument.price_precision())?;
    let size = quantity_from_decimal(level.size, instrument.size_precision())?;
    let action = if flags & RecordFlag::F_SNAPSHOT as u8 != 0 {
        BookAction::Add
    } else if size.is_zero() {
        BookAction::Delete
    } else {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the instrument definition in the cache matches the exchange's current price/increment precision for that market (instrument_id must come from the same catalog used at subscribe time).
  2. Log the raw candle payload (open/high/low/close/volume/ts) before Bar::new_checked to identify which field violates the invariants.
  3. If the exchange changed tick size, refresh the instrument (re-request instrument definitions) and rebuild any catalog entry with stale precision.
  4. If the bad candle is a transient exchange artifact (e.g. a partial snapshot candle), treat it as a skipped update and continue consuming subsequent bars instead of failing the stream.

Example fix

// before
Bar::new_checked(bar_type, open, high, low, close, volume, ts_event, ts_init)
    .map_err(|e| anyhow::anyhow!("invalid candle bar: {e}"))

// after
let open = Price::new(open_raw.round_dp(instrument.price_increment_precision).as_f64(), instrument.price_precision)?;
// ensure each price/quantity is built at the instrument's exact precision before construction
Bar::new_checked(bar_type, open, high, low, close, volume, ts_event, ts_init)
    .map_err(|e| anyhow::anyhow!("invalid candle bar: {e}"))
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_candle(open: f64, high: f64, low: f64, close: f64, volume: f64) -> bool {
    [open, high, low, close, volume].iter().all(|v| v.is_finite() && *v >= 0.0)
        && high >= low
        && high >= open && high >= close
        && low <= open && low <= close
}

Try / catch

match parse_ws_bar(...) {
    Ok(bar) => process(bar),
    Err(e) => { log::warn!("skipping malformed candle: {e}"); continue; }
}

Prevention

When it happens

Trigger: A candle update arrives over the Lighter websocket whose price or volume fields, after parsing into Price/Quantity at the instrument's precision, do not satisfy Bar::new_checked invariants (e.g. value rounds out of range at instrument precision, negative volume, or inconsistent price precision).

Common situations: Exchange sends a candle with more decimal places than the instrument's configured price precision; instrument was registered with wrong precision/size in the cache; a degenerate or malformed candle (empty or zeroed fields) arrives during reconnect/snapshot transitions; instrument precision changed on the exchange but the local definition is stale.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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