nautechsystems/nautilus_trader · error

negative candle volume `{}`

Error message

negative candle volume `{}`

What it means

parse_candle_bar checks candle.volume_base with is_sign_positive and rejects negative (or NaN) base volume, since Bar volume is a non-negative Quantity. Negative volume indicates corrupt or mis-scaled upstream candle data. The offending value is embedded in the message.

Source

Thrown at crates/adapters/lighter/src/http/parse.rs:222

        "non-positive candle close `{}`",
        candle.close
    );

    let timestamp_ms =
        u64::try_from(candle.timestamp).context("negative Lighter candle timestamp")?;
    let ts_event = parse_millis_to_nanos(timestamp_ms)?;
    let price_precision = instrument.price_precision();
    let size_precision = instrument.size_precision();

    let open = Price::from_decimal_dp(candle.open, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle open: {e}"))?;
    let high = Price::from_decimal_dp(candle.high, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle high: {e}"))?;
    let low = Price::from_decimal_dp(candle.low, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle low: {e}"))?;
    let close = Price::from_decimal_dp(candle.close, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle close: {e}"))?;
    anyhow::ensure!(
        candle.volume_base.is_sign_positive(),
        "negative candle volume `{}`",
        candle.volume_base,
    );
    let volume = Quantity::from_decimal_dp(candle.volume_base, size_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle volume: {e}"))?;

    Bar::new_checked(bar_type, open, high, low, close, volume, ts_event, ts_init)
        .context("failed to construct Bar from Lighter candle")
}

/// Parses a Lighter historical funding row into a Nautilus [`FundingRateUpdate`].
///
/// Lighter returns `rate` as a magnitude and `direction` as the side paying
/// the funding. Nautilus uses the conventional signed rate: positive when
/// longs pay shorts and negative when shorts pay longs.
///
/// # Errors

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter candles with negative volume_base before parsing
  2. Inspect the raw API response for negative or NaN volume values
  3. Verify the adapter's volume field mapping matches the current Lighter API schema
  4. Correct test fixtures to use non-negative volumes

Example fix

// before
let bars = candles.iter().map(|c| parse_candle_bar(c, ...)).collect::<Result<Vec<_>>>()?;
// after
let bars = candles.iter().filter(|c| c.volume_base.is_sign_positive()).map(|c| parse_candle_bar(c, ...)).collect::<Result<Vec<_>>>()?;
Defensive patterns

Strategy: validation

Validate before calling

if !candle.volume_base.is_sign_positive() {
    return Err(anyhow::anyhow!("skip candle: negative volume {}", candle.volume_base));
}

Type guard

fn has_valid_volume(c: &LighterCandle) -> bool {
    c.volume_base.is_sign_positive() && c.volume_base.is_finite()
}

Try / catch

let bar = match parse_candle_bar(&candle, bar_type, instrument, ts_init) {
    Ok(b) => b,
    Err(e) if e.to_string().contains("negative candle volume") => continue,
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: parse_candle_bar receiving a LighterCandle with volume_base < 0 (or NaN, which fails is_sign_positive) when parsing the candles endpoint via parse_bars.

Common situations: Upstream API glitches or sign/scaling changes in the volume field; fixtures with negative volumes; data feeds during exchange incidents.

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/c8f02db4c251004a. Report an issue: GitHub.