nautechsystems/nautilus_trader · error · anyhow::Error

Invalid contract size {contract_size}

Error message

Invalid contract size {contract_size}

What it means

derive_contract_decimal_and_increment parses the instrument's contract size (computed from the raw multiplier or defaulting to 1.0) into a Decimal and fails if the conversion is not representable. This guards instrument parsing so contract sizes stay exact Decimal values before scale normalization/rounding to max_scale.

Source

Thrown at crates/adapters/bitmex/src/common/parse.rs:163

/// The returned decimal retains BitMEX precision (clamped to `max_scale`) so downstream
/// quantity conversions stay lossless.
///
/// # Errors
///
/// Returns an error when the multiplier cannot be represented with the configured precision.
pub fn derive_contract_decimal_and_increment(
    multiplier: Option<f64>,
    max_scale: u32,
) -> anyhow::Result<(Decimal, Quantity)> {
    let raw_multiplier = multiplier.unwrap_or(1.0);
    let contract_size = if raw_multiplier > 0.0 {
        1.0 / raw_multiplier
    } else {
        1.0
    };

    let mut contract_decimal = Decimal::from_str(&contract_size.to_string())
        .map_err(|_| anyhow::anyhow!("Invalid contract size {contract_size}"))?;

    if contract_decimal.scale() > max_scale {
        contract_decimal = contract_decimal
            .round_dp_with_strategy(max_scale, RoundingStrategy::MidpointAwayFromZero);
    }
    contract_decimal = contract_decimal.normalize();
    let contract_precision = contract_decimal.scale() as u8;
    let size_increment = Quantity::from_decimal_dp(contract_decimal, contract_precision)?;

    Ok((contract_decimal, size_increment))
}

/// Converts an optional contract-count field (e.g. `lotSize`, `maxOrderQty`) into a Nautilus
/// quantity using the previously derived contract size.
///
/// # Errors
///
/// Returns an error when the raw value cannot be represented with the available precision.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the instrument payload's multiplier fields that feed raw_multiplier; correct or filter the malformed instrument
  2. Guard the caller against raw_multiplier == 0 before deriving the contract decimal
  3. Confirm the BitMEX instrument definition is current; reload the instrument list after an exchange listing update

Example fix

// before
let raw_multiplier = 1.0 / init_quantity; // NaN/inf if init_quantity == 0
// after
if init_quantity == 0.0 {
    return Err(anyhow::anyhow!("Instrument {symbol} has zero initQuantity"));
}
Defensive patterns

Strategy: validation

Validate before calling

fn contract_size_parsable(raw_multiplier: f64) -> bool {
    raw_multiplier.is_finite() && raw_multiplier != 0.0 && !(1.0 / raw_multiplier).is_finite()
}

Type guard

fn finite_f64(x: f64) -> bool { x.is_finite() }

Try / catch

match parse_instrument(&row) {
    Ok(inst) => inst,
    Err(e) if e.to_string().contains("Invalid contract size") => {
        log::warn!("skipping instrument with bad contract size: {e}");
        return None;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Parsing a spot/perpetual/futures/crypto-futures-spread instrument whose derived contract_size.to_string() cannot be parsed by Decimal::from_str (e.g. NaN, infinity, or a non-finite float produced by 1.0/raw_multiplier when raw_multiplier is 0).

Common situations: A BitMEX instrument row with an unexpected or zero initQuantity/lotSize multiplier yielding inf or NaN contract size; malformed instrument metadata from the exchange or test fixtures.

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