nautechsystems/nautilus_trader · error

non-positive candle low `{}`

Error message

non-positive candle low `{}`

What it means

parse_candle_bar enforces that a candle's low price is strictly positive. Since OHLC values must be representable as positive Prices, a non-positive low flags invalid upstream candle data. The offending value is included in the message.

Source

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

///
/// Returns an error if any price, volume, or timestamp field cannot be converted.
pub fn parse_candle_bar(
    candle: &LighterCandle,
    bar_type: BarType,
    instrument: &InstrumentAny,
    ts_init: UnixNanos,
) -> anyhow::Result<Bar> {
    anyhow::ensure!(
        candle.open > Decimal::ZERO,
        "non-positive candle open `{}`",
        candle.open
    );
    anyhow::ensure!(
        candle.high > Decimal::ZERO,
        "non-positive candle high `{}`",
        candle.high
    );
    anyhow::ensure!(
        candle.low > Decimal::ZERO,
        "non-positive candle low `{}`",
        candle.low
    );
    anyhow::ensure!(
        candle.close > Decimal::ZERO,
        "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}"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Drop candles with low <= 0 before calling parse_candle_bar
  2. Query a different time range or market where real trades exist
  3. Inspect the raw API payload for corrupted candle entries
  4. Fix test fixtures to use positive low values

Example fix

// before
let bar = parse_candle_bar(&candle, bar_type, &inst, ts_init)?;
// after
anyhow::ensure!(candle.low > Decimal::ZERO, "skip invalid candle");
let bar = parse_candle_bar(&candle, bar_type, &inst, ts_init)?;
Defensive patterns

Strategy: validation

Validate before calling

if candle.low <= Decimal::ZERO {
    return Err(anyhow::anyhow!("skip candle: non-positive low {}", candle.low));
}

Type guard

fn has_positive_ohlc(c: &LighterCandle) -> bool {
    c.open > Decimal::ZERO && c.high > Decimal::ZERO && c.low > Decimal::ZERO && c.close > Decimal::ZERO
}

Try / catch

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

Prevention

When it happens

Trigger: parse_candle_bar receiving a LighterCandle with low <= 0 via parse_bars when parsing the Lighter candles endpoint response.

Common situations: Zero-filled candles for markets with no trades in the window; broken upstream data; synthetic fixtures with placeholder zeros.

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