nautechsystems/nautilus_trader · error
negative tick count {ticks} for Quantity
Error message
negative tick count {ticks} for Quantity What it means
parse_quantity_from_ticks converts a signed i64 tick count (a mantissa in units of 10^-decimals base-asset units) into a Nautilus Quantity, which is non-negative. The library rejects any negative tick count because Quantity cannot represent signed values; callers that deal with signed amounts (e.g. position/delta parsers) are expected to extract the sign before calling this parser. This guards against silently wrapping or panicking in the fixed-precision constructor.
Source
Thrown at crates/adapters/lighter/src/common/parse.rs:78
/// Order sizes on the wire are signed `i64` multiples of `10^-decimals`
/// base-asset units. Nautilus [`Quantity`] is non-negative, so a negative
/// `ticks` value is rejected: callers (e.g. position parsers) extract the
/// sign separately before invoking this parser.
///
/// Conversion routes through [`Decimal`] and [`Quantity::from_decimal_dp`]
/// so out-of-range tick counts return an error rather than panicking inside
/// the unchecked mantissa-exponent constructor.
///
/// # Errors
///
/// Returns an error if `decimals` exceeds [`MAX_DECIMALS`], if `ticks` is
/// negative, or if the resulting value exceeds the [`Quantity`] range.
pub fn parse_quantity_from_ticks(ticks: i64, decimals: u8) -> anyhow::Result<Quantity> {
anyhow::ensure!(
decimals <= MAX_DECIMALS,
"size decimals {decimals} exceeds maximum {MAX_DECIMALS}",
);
anyhow::ensure!(ticks >= 0, "negative tick count {ticks} for Quantity");
let decimal = Decimal::new(ticks, u32::from(decimals));
Quantity::from_decimal_dp(decimal, decimals).map_err(|e| {
anyhow::anyhow!("Quantity overflow for ticks={ticks}, decimals={decimals}: {e}")
})
}
/// Converts a decimal string into a Nautilus [`Price`] at the requested precision.
///
/// # Errors
///
/// Returns an error if the string is not a decimal, if `precision` exceeds
/// [`MAX_DECIMALS`], or if the resulting value is out of range.
pub fn parse_price(value: &str, precision: u8) -> anyhow::Result<Price> {
anyhow::ensure!(
precision <= MAX_DECIMALS,
"price precision {precision} exceeds maximum {MAX_DECIMALS}",
);
let decimal =View on GitHub (pinned to 18893faf8b)
Solutions
- Extract the sign before parsing: call parse_quantity_from_ticks(ticks.abs(), decimals) and handle the sign separately (e.g. as an order side or position direction).
- Check the sign at the call site and route negative values to whatever decrement/removal logic the caller has, mirroring how position parsers in this adapter do it.
- If a negative value is truly unexpected for your payload type, log the raw ticks value and treat the message as malformed rather than coercing it.
- Audit which wire fields are signed (i64) vs unsigned (u32) in the Lighter API docs and use parse_price_from_ticks (u32) for unsigned fields so the type system prevents this.
Example fix
// before
let qty = parse_quantity_from_ticks(base_amount, size_decimals)?;
// after
let qty = parse_quantity_from_ticks(base_amount.abs(), size_decimals)?;
let side = if base_amount < 0 { OrderSide::Sell } else { OrderSide::Buy }; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_non_negative_ticks(ticks: i64) -> anyhow::Result<()> {
anyhow::ensure!(ticks >= 0, "negative tick count {ticks}; extract sign before parsing");
Ok(())
} Type guard
fn is_non_negative(ticks: i64) -> bool { ticks >= 0 } Try / catch
match parse_quantity_from_ticks(ticks, decimals) {
Ok(q) => /* ... */,
Err(e) if e.to_string().contains("negative tick count") => {
tracing::warn!("signed amount {ticks}; handle sign separately");
}
Err(e) => return Err(e),
} Prevention
- Take .abs() of signed i64 wire fields before size parsing and carry the sign as side/direction metadata
- Use u32-based parse_price_from_ticks for unsigned wire fields so signedness errors surface at compile time
- Match Lighter's field conventions: base_amount on deltas/fills is signed, sizes on orders are not
- Add a unit test with a negative input for every signed-field parser
When it happens
Trigger: Calling parse_quantity_from_ticks with a negative i64 ticks value — e.g. passing a signed Lighter base-amount field (order delta of -5, negative position change) directly without taking its absolute value first.
Common situations: Parsing WebSocket order-fill or position-update payloads where Lighter sends signed base_amount values; applying a decrease/delta directly instead of its magnitude; copy-pasting a signed field into a size parser.
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
- size precision {precision} exceeds maximum {MAX_DECIMALS}
- negative quantity `{value}`
- AX requires whole contract quantities, was {}
- Order quantity must be at least 1 contract
- quantity size={} cannot be represented with precision={}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/d47b5ee8484f8d91.
Report an issue: GitHub.