{"record":{"id":"8a55bcbfa42e4644","repo":"nautechsystems/nautilus_trader","slug":"ax-requires-whole-contract-quantities-was","errorCode":null,"errorMessage":"AX requires whole contract quantities, was {}","messagePattern":"AX requires whole contract quantities, was (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/common/parse.rs","lineNumber":196,"sourceCode":"}\n\n/// Converts a [`Quantity`] to an i64 contract count for AX orders.\n///\n/// AX uses integer contracts only. Uses integer arithmetic to avoid\n/// floating-point precision issues.\n///\n/// # Errors\n///\n/// Returns an error if:\n/// - The quantity represents a fractional number of contracts.\n/// - The quantity is zero.\npub fn quantity_to_contracts(quantity: Quantity) -> anyhow::Result<u64> {\n    let raw = quantity.raw;\n    let scale = 10_u64.pow(FIXED_PRECISION as u32) as QuantityRaw;\n\n    // AX requires whole contract quantities\n    if !raw.is_multiple_of(scale) {\n        anyhow::bail!(\n            \"AX requires whole contract quantities, was {}\",\n            quantity.as_f64()\n        );\n    }\n\n    // QuantityRaw is u128 under the `high-precision` feature and u64 otherwise,\n    // so the narrowing cast is conditional on the active feature set.\n    #[allow(clippy::unnecessary_cast)]\n    let contracts = (raw / scale) as u64;\n    if contracts == 0 {\n        anyhow::bail!(\"Order quantity must be at least 1 contract\");\n    }\n    Ok(contracts)\n}\n\n/// Converts a [`ClientOrderId`] to a deterministic AX `cid` in the non-negative `int64` range.\n///\n/// Inbound WebSocket `cid` values remain `u64` because venue messages can exceed `int64`.","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/common/parse.rs#L178-L214","documentation":"quantity_to_contracts converts a NautilusTrader fixed-precision Quantity to AX's whole-contract count. ArchitectX deals in integral contracts only, so the raw fixed-point value must be an exact multiple of the FIXED_PRECISION scale (i.e. a whole number); fractional sizes such as 0.5 or 1.25 are rejected with the offending decimal shown. Note this fires even for values < 1 (e.g. 0.5 is fractional, not zero-contract).","triggerScenarios":"Submitting an order to the architect_ax adapter with a fractional quantity — e.g. Quantity.from_str('0.5') on a futures contract — when the adapter converts the size for the venue; commonly a strategy tuned for crypto venues running against AX.","commonSituations":"Porting crypto strategies (where fractional BTC/ETH sizes are normal) to Architect futures; position sizing math (e.g. risk-based sizing) producing 2.7 contracts; symbol metadata missing so no lot-size rounding occurs upstream.","solutions":["Round your order size to a whole number of contracts before submitting: qty = float(qty).__floor__() or an explicit round strategy","Apply risk sizing in contract units rather than fractional notional","Validate size against the instrument's lot_size before order creation"],"exampleFix":"# before (Python strategy)\norder = self.factory.market(instrument_id, quantity=Quantity.from_str('2.7'))\n# -> AX requires whole contract quantities, was 2.7\n\n# after\norder = self.factory.market(instrument_id, quantity=Quantity.from_int(2))","handlingStrategy":"validation","validationCode":"qty = self._compute_position_size(...)  # float contracts\nwhole = int(qty)  # or use your rounding policy\nif whole < 1:\n    self.log.info(f'Skipping order: size {qty} < 1 contract')\n    return\norder = self.factory.market(instrument_id, quantity=Quantity.from_int(whole))","typeGuard":"from nautilus_trader.model.objects import Quantity\n\ndef is_whole_contracts(qty: Quantity) -> bool:\n    return qty.as_double() == int(qty.as_double())","tryCatchPattern":"# Prefer pre-submission validation; adapter errors arrive async via order rejected events.\n# Python strategy guard:\nif qty.as_double() % 1 != 0:\n    qty = Quantity.from_int(int(qty.as_double()))","preventionTips":["Round sizes to the instrument lot size (or whole contracts for AX) before order creation","Unit-test sizing code with fractional inputs to prove flooring behavior","Remember AX is whole-contract only when porting crypto strategies"],"tags":["rust","architect","adapter","quantity","order-validation","fractional-quantity"],"backgroundTag":"invalid-order-quantity","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}