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
- 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).
- Log the raw candle payload (open/high/low/close/volume/ts) before Bar::new_checked to identify which field violates the invariants.
- If the exchange changed tick size, refresh the instrument (re-request instrument definitions) and rebuild any catalog entry with stale precision.
- 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
- Keep instrument definitions (price precision, size increment) in sync with the exchange's current tick rules.
- Validate OHLC relationships (high >= max(open,close), low <= min(open,close)) on raw payloads before parsing.
- Log the raw candle message before conversion so failures are diagnosable.
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
- Unsupported Deribit resolution: {resolution}
- Unknown order direction: {}
- Unknown trade direction: {}
- conflicting RTDS TWAP observation topic={} symbol={} timesta
- Derive {label} cannot be represented as f64
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/3f4a3f82c4fbcc86.
Report an issue: GitHub.