nautechsystems/nautilus_trader · error

Failed to scale continuous-future adjustment to fixed precis

Error message

Failed to scale continuous-future adjustment to fixed precision

What it means

`BarBuilder::set_adjustment` converts a continuous-future adjustment `Price` from its own mantissa/scale into the builder's fixed `FIXED_PRECISION` via `mantissa_exponent_to_fixed_i128`. If the rescale would lose precision or overflow the i128 intermediate, the helper returns None and this `.expect` panics.

Source

Thrown at crates/data/src/aggregation.rs:228

    /// # Panics
    ///
    /// Panics if scaling the spread `adjustment` to the fixed-point representation overflows.
    pub fn set_adjustment(&mut self, adjustment: Decimal, mode: ContinuousFutureAdjustmentType) {
        if mode.is_ratio() {
            self.adjustment_is_ratio = true;
            self.adjustment_ratio = adjustment.to_f64().unwrap_or(1.0);
            self.adjustment_active = adjustment != Decimal::ONE;
            return;
        }

        // Spread mode: scale the Decimal offset to FIXED_PRECISION once so the hot path
        // can add it straight onto `price.raw`. Signed PriceRaw supports negatives, so
        // backward-spread offsets that push prices below zero remain representable.
        self.adjustment_is_ratio = false;
        let exponent = -(adjustment.scale() as i8);
        let raw_i128 =
            mantissa_exponent_to_fixed_i128(adjustment.mantissa(), exponent, FIXED_PRECISION)
                .expect("Failed to scale continuous-future adjustment to fixed precision");

        #[allow(
            clippy::useless_conversion,
            reason = "i128 to PriceRaw is real when not high-precision"
        )]
        let raw: PriceRaw = raw_i128
            .try_into()
            .expect("Continuous-future adjustment exceeds PriceRaw range");

        self.adjustment_raw = raw;
        self.adjustment_active = self.adjustment_raw != 0;
    }

    fn apply_adjustment_to_price(&self, price: Price) -> Price {
        if !self.adjustment_active {
            return price;
        }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Round/quantize the adjustment Price to FIXED_PRECISION before passing it to `set_adjustment`
  2. Verify the adjustment mantissa comes from the instrument's price precision, not raw float parsing
  3. Clamp or re-scale the adjustment value at the data-provider boundary
  4. If larger precision is genuinely needed, this is a platform limitation — file/track a change to the fixed precision constant

Example fix

// before
builder.set_adjustment(adjustment_price); // panics if scale > FIXED_PRECISION
// after
let adj = Price::new(
    adjustment_price.as_f64(),
    FIXED_PRECISION as u8,
); // quantize to fixed precision first
builder.set_adjustment(adj);
Defensive patterns

Strategy: validation

Validate before calling

// Rust: quantize the adjustment to the builder's fixed precision first
let adj = Price::new(adjustment_f64, FIXED_PRECISION as u8);
assert!(adjustment_f64.is_finite(), "adjustment must be finite");

Try / catch

let result = std::panic::catch_unwind(|| builder.set_adjustment(adj));

Prevention

When it happens

Trigger: Calling `set_adjustment` with a Price whose scale is finer than FIXED_PRECISION (e.g. an 11+ decimal-place price when fixed precision is 9) or whose mantissa is so large that scaling up overflows i128.

Common situations: Configuring spread adjustment factors parsed from CSV/JSON with unusual precision; instruments quoted with more decimals than the platform's fixed precision; hand-built test prices with extreme exponents.

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