nautechsystems/nautilus_trader · error

invalid candle bar

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.

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.

Appendix: 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)