nautechsystems/nautilus_trader · error

OpenPositions: failed to build Quantity for {inst_id}: {e:?}

Error message

OpenPositions: failed to build Quantity for {inst_id}: {e:?}

What it means

After resolving the instrument, the adapter converts the position volume into a Nautilus `Quantity` using the instrument's `size_precision`. `Quantity::from_decimal_dp` fails when the parsed value cannot be represented at that precision, and the adapter wraps the debug error in this anyhow message. It means Kraken-reported volume is incompatible with the instrument's size precision.

Source

Thrown at crates/adapters/kraken/src/http/spot/client.rs:2446

        }

        let mut reports = Vec::new();

        for (_, (signed_qty, inst_id)) in agg {
            let instrument = self
                .get_cached_instrument(&inst_id.symbol.inner())
                .ok_or_else(|| InstrumentLookupError::not_found(inst_id))?;

            let side = if signed_qty.is_sign_positive() && !signed_qty.is_zero() {
                PositionSide::Long
            } else if signed_qty.is_sign_negative() && !signed_qty.is_zero() {
                PositionSide::Short
            } else {
                PositionSide::Flat
            };
            let quantity = Quantity::from_decimal_dp(signed_qty.abs(), instrument.size_precision())
                .map_err(|e| {
                    anyhow::anyhow!("OpenPositions: failed to build Quantity for {inst_id}: {e:?}")
                })?;
            let report = PositionStatusReport::new(
                account_id, inst_id, side, quantity, ts_init, ts_init, None, None, None,
            );
            reports.push(report);
        }

        // If a specific instrument was requested but no open position exists for it, emit
        // a FLAT report so the engine can reconcile a previously-open position to closed.
        // (Kraken omits fully-closed positions from OpenPositions entirely.)
        // Only emit for instruments known to this spot client; a missing cache entry means
        // the target belongs to a different product type (e.g. futures) and must not receive
        // a spurious FLAT from the spot reconciliation path.
        if let Some(target_id) = instrument_id {
            let already_reported = reports.iter().any(|r| r.instrument_id == target_id);

            if !already_reported
                && let Some(instrument) = self.get_cached_instrument(&target_id.symbol.inner())

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Update the instrument definition so `size_precision` matches Kraken's `lot_decimals` for the pair.
  2. Inspect the inner `{e:?}` for the exact precision/parse problem and the offending `vol` value.
  3. Re-fetch instruments to refresh precision after Kraken announces pair changes.
  4. If the value is genuinely over-precise, round to the instrument precision at the strategy layer before it reaches the venue volume, or report the venue data as unexpected.
Defensive patterns

Strategy: validation

Validate before calling

fn validate_volume(vol: &str, size_precision: u8) -> Result<(), String> {
    let d = rust_decimal::Decimal::from_str_exact(vol)
        .map_err(|e| format!("bad vol {vol}: {e}?"))?;
    let scale = d.scale();
    if scale as u8 > size_precision {
        Err(format!("vol {vol} has {} dp, instrument allows {}
        ", scale, size_precision))
    } else { Ok(()) }
}

Try / catch

match client.request_open_positions().await {
    Ok(r) => r,
    Err(e) if e.to_string().contains("failed to build Quantity") => {
        log::warn!("venue volume incompatible with instrument precision: {e}");
        Vec::new() // skip bad position, alert operator
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: OpenPositions parsing where `pos.vol` (parsed via `Decimal::from_str_exact`) has more decimal places than `instrument.size_precision()`, is negative in an unexpected way, or otherwise cannot be rounded/represented as a Quantity at the instrument precision.

Common situations: Stale or wrong instrument definition whose size_precision does not match the venue's current precision; Kraken changes pair volume precision; corrupted/unexpected `vol` string in the API response (e.g. scientific notation the exact parser rejects).

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/9af263f49f5aa602. Report an issue: GitHub.