nautechsystems/nautilus_trader · error

invalid {name}: must be non-zero

Error message

invalid {name}: must be non-zero

What it means

check_nonzero_denominator is a guard against division by zero in Bet arithmetic. It accepts a Decimal and its field name, and bails if the value is zero, so callers like as_bet_checked, decreased_state, flattening_bet_checked, and checked_div fail fast with a message naming the offending quantity instead of panicking or producing NaN/infinity.

Source

Thrown at crates/model/src/data/bet.rs:759

    probability: Decimal,
    volume: Decimal,
    side: OrderSide,
) -> anyhow::Result<Bet> {
    check_probability_invertible(probability)?;
    let inverse_probability = checked_sub(Decimal::ONE, probability)?;
    probability_to_bet(inverse_probability, volume, side.opposite())
}

fn check_odds_gt_one(price: Decimal) -> anyhow::Result<()> {
    if price <= Decimal::ONE {
        anyhow::bail!("Price must be greater than 1.0 for lay liability calculation, was {price}");
    }
    Ok(())
}

fn check_nonzero_denominator(value: Decimal, name: &str) -> anyhow::Result<()> {
    if value.is_zero() {
        anyhow::bail!("invalid {name}: must be non-zero")
    }
    Ok(())
}

fn checked_add(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_add(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow adding {lhs} and {rhs}"))
}

fn checked_sub(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_sub(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow subtracting {rhs} from {lhs}"))
}

fn checked_mul(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_mul(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow multiplying {lhs} by {rhs}"))
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the denominator with .is_zero() before the operation and handle the zero case explicitly (skip, treat result as zero, or error).
  2. Filter out zero-volume bets/orders before running bet state transitions or divisions.
  3. Verify the upstream data: a zero denominator often means the position is already flat and the operation is unnecessary.

Example fix

// before
let ratio = checked_div(pnl, remaining_volume)?;
// after
if remaining_volume.is_zero() {
    return Ok(Decimal::ZERO); // nothing remains to normalize against
}
let ratio = checked_div(pnl, remaining_volume)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust
use rust_decimal::Decimal;
fn safe_div(n: Decimal, d: Decimal) -> Option<Decimal> { if d.is_zero() { None } else { Some(n / d) } }

Try / catch

// Rust
match as_bet_checked(volume) {
    Ok(bet) => bet,
    Err(e) if e.to_string().contains("must be non-zero") => handle_flat_position(),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Any bet operation that divides by a user-supplied or computed denominator (volume, price, size) when that value is exactly Decimal::ZERO — e.g. checked_div with rhs = 0, as_bet_checked with zero size, decreased_state / flattening_bet_checked after the bet was fully filled or cancelled to zero volume.

Common situations: Operating on a bet whose remaining volume reached zero; passing an unfilled/zero Quantity placeholder; a data feed supplying 0 size for an unmatched order.

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