nautechsystems/nautilus_trader · error

Order quantity must be positive

Error message

Order quantity must be positive

What it means

quantity_to_raw_amount converts a base-denominated order Quantity into raw token units with exact integer scaling. It rejects a zero quantity outright: a zero-amount order cannot correspond to any valid on-chain swap amount and would produce a useless or reverting transaction.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:2446

        anyhow::bail!(
            "Input token {} balance {balance} is below the swap amount {}",
            plan.token_in,
            plan.amount_in
        );
    }

    Ok(())
}

/// Converts a base-denominated order quantity into raw token units with exact integer
/// scaling by the base token decimals.
///
/// `Quantity::raw` is scaled to the greater of its declared precision and `FIXED_PRECISION`;
/// token amounts are scaled to the token's decimals. Quantities not exactly representable in
/// token units are rejected rather than rounded.
fn quantity_to_raw_amount(quantity: Quantity, decimals: u8) -> anyhow::Result<U256> {
    if quantity.is_zero() {
        anyhow::bail!("Order quantity must be positive");
    }

    let raw = U256::from(quantity.raw);
    let raw_precision = quantity.precision.max(FIXED_PRECISION);
    if decimals >= raw_precision {
        let scale = U256::from(10u64)
            .checked_pow(U256::from(decimals - raw_precision))
            .ok_or_else(|| anyhow::anyhow!("Order amount scaling overflow"))?;
        raw.checked_mul(scale).ok_or_else(|| {
            anyhow::anyhow!("Order amount overflow scaling quantity to raw token units")
        })
    } else {
        let divisor = U256::from(10u64)
            .checked_pow(U256::from(raw_precision - decimals))
            .ok_or_else(|| anyhow::anyhow!("Order amount scaling overflow"))?;
        if !(raw % divisor).is_zero() {
            anyhow::bail!(
                "Order quantity {quantity} is not exactly representable in {decimals} base token decimals"

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Skip order submission entirely when the computed size is zero instead of forwarding it to the execution client
  2. Fix the sizing logic so signals with non-zero intent produce a positive size (guard upstream division/clamping)
  3. Validate user/config-provided sizes as strictly positive at the boundary where they enter the system

Example fix

// before
let qty = signal_strength * notional; // can be 0.0
submit_order(&instrument, Quantity::from(qty));

// after
let qty = signal_strength * notional;
if qty > 0.0 {
    submit_order(&instrument, Quantity::from(qty));
}
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(!qty.is_zero(), "refusing to submit zero-sized order for {instrument_id}");
let amount = quantity_to_raw_amount(qty, decimals)?;

Try / catch

match quantity_to_raw_amount(qty, decimals) {
    Err(e) if e.to_string().contains("must be positive") => {
        // sizing bug upstream: drop the signal and log; never coerce 0 -> 1
        log::warn!("zero size from sizer; skipping order");
        Ok(None)
    }
    other => other.map(Some),
}

Prevention

When it happens

Trigger: Passing a Quantity that is zero to the order/swap amount conversion: strategy sizing math that multiplies by a zero factor, a clamp that floors a computed size to zero, Quantity::default()/from('0') used as a placeholder, or a parsed size of 0 from user input or config.

Common situations: Sizing engines that emit zero when capital allocation, signal strength, or available balance computes to zero; order templates with unset size fields; edge-case tests or config presets that accidentally leave size at zero.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/7b51c0b66fe1346c. Report an issue: GitHub.