nautechsystems/nautilus_trader · error · anyhow::Error
Order quantity must be at least 1 contract
Error message
Order quantity must be at least 1 contract
What it means
The second stage of quantity_to_contracts: a size that is a valid whole multiple (so it passes the fractional check) but rounds down to zero contracts — which in practice means Quantity 0. ArchitectX minimum order size is 1 contract, and the adapter refuses to emit a zero-size order rather than letting the venue reject it opaquely.
Source
Thrown at crates/adapters/architect_ax/src/common/parse.rs:207
/// - The quantity is zero.
pub fn quantity_to_contracts(quantity: Quantity) -> anyhow::Result<u64> {
let raw = quantity.raw;
let scale = 10_u64.pow(FIXED_PRECISION as u32) as QuantityRaw;
// AX requires whole contract quantities
if !raw.is_multiple_of(scale) {
anyhow::bail!(
"AX requires whole contract quantities, was {}",
quantity.as_f64()
);
}
// QuantityRaw is u128 under the `high-precision` feature and u64 otherwise,
// so the narrowing cast is conditional on the active feature set.
#[allow(clippy::unnecessary_cast)]
let contracts = (raw / scale) as u64;
if contracts == 0 {
anyhow::bail!("Order quantity must be at least 1 contract");
}
Ok(contracts)
}
/// Converts a [`ClientOrderId`] to a deterministic AX `cid` in the non-negative `int64` range.
///
/// Inbound WebSocket `cid` values remain `u64` because venue messages can exceed `int64`.
#[must_use]
pub fn client_order_id_to_cid(client_order_id: &ClientOrderId) -> u64 {
CID_HASHER.hash_one(client_order_id.inner()) & i64::MAX as u64
}
/// Creates a [`ClientOrderId`] from a cid value.
///
/// Used when we receive an order with a cid but cannot resolve it to the
/// original ClientOrderId (e.g., after restart when in-memory mapping is lost).
#[must_use]
pub fn cid_to_client_order_id(cid: u64) -> ClientOrderId {View on GitHub (pinned to a4b06ed870)
Solutions
- Skip order submission when the computed size is zero: if qty == 0: return / log and continue
- Clamp sizing to at least 1 contract when the signal should trade: qty = max(qty, instrument.lot_size or 1)
- Assert qty > 0 in strategy code before order creation as a fast local guard
Example fix
# before
qty = int(risk_budget // price) # floors to 0 for small budgets
order = self.factory.market(instrument_id, quantity=Quantity.from_int(qty))
# -> Order quantity must be at least 1 contract
# after
qty = int(risk_budget // price)
if qty < 1:
self.log.info('Skipping: size rounds to 0 contracts')
return Defensive patterns
Strategy: validation
Validate before calling
qty = self._compute_position_size(...)
if qty < 1:
self.log.info('Skipping: allocated size rounds to zero contracts')
return
order = self.factory.market(instrument_id, quantity=Quantity.from_int(int(qty))) Type guard
def is_orderable_contracts(qty: float) -> bool:
return qty >= 1 Try / catch
# Validate before submit; a zero-quantity order surfaces as a rejected order event, # so guard at sizing time instead of relying on venue/adapter rejection.
Prevention
- Always branch on qty < 1 after flooring risk-based sizes
- Add an assert quantity > 0 in strategy order helpers during development
- Treat zero-sized signals as 'no trade', never as a submit-0 order
When it happens
Trigger: Submitting an order with quantity 0 (or 0.0) through the architect_ax adapter: raw == 0 is a multiple of the scale, divides to 0 contracts, and trips this guard.
Common situations: Risk-based sizing that floors an already-tiny allocation to 0 and still submits; signal handlers firing with a default/zero size; test orders created with placeholder zero quantities.
Related errors
- AX requires whole contract quantities, was {}
- Unsupported bar specification for AX: {step}-{:?}
- AX timestamp must be non-negative, was {seconds}
- AX timestamp_ns must be non-negative, was {nanos}
- Unsupported order type: {:?}, the Architect AX adapter accep
AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16).
Data as JSON: /api/errors/99b5c8951a61e339.
Report an issue: GitHub.