{"record":{"id":"7b51c0b66fe1346c","repo":"nautechsystems/nautilus_trader","slug":"order-quantity-must-be-positive","errorCode":null,"errorMessage":"Order quantity must be positive","messagePattern":"Order quantity must be positive","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":2446,"sourceCode":"        anyhow::bail!(\n            \"Input token {} balance {balance} is below the swap amount {}\",\n            plan.token_in,\n            plan.amount_in\n        );\n    }\n\n    Ok(())\n}\n\n/// Converts a base-denominated order quantity into raw token units with exact integer\n/// scaling by the base token decimals.\n///\n/// `Quantity::raw` is scaled to the greater of its declared precision and `FIXED_PRECISION`;\n/// token amounts are scaled to the token's decimals. Quantities not exactly representable in\n/// token units are rejected rather than rounded.\nfn quantity_to_raw_amount(quantity: Quantity, decimals: u8) -> anyhow::Result<U256> {\n    if quantity.is_zero() {\n        anyhow::bail!(\"Order quantity must be positive\");\n    }\n\n    let raw = U256::from(quantity.raw);\n    let raw_precision = quantity.precision.max(FIXED_PRECISION);\n    if decimals >= raw_precision {\n        let scale = U256::from(10u64)\n            .checked_pow(U256::from(decimals - raw_precision))\n            .ok_or_else(|| anyhow::anyhow!(\"Order amount scaling overflow\"))?;\n        raw.checked_mul(scale).ok_or_else(|| {\n            anyhow::anyhow!(\"Order amount overflow scaling quantity to raw token units\")\n        })\n    } else {\n        let divisor = U256::from(10u64)\n            .checked_pow(U256::from(raw_precision - decimals))\n            .ok_or_else(|| anyhow::anyhow!(\"Order amount scaling overflow\"))?;\n        if !(raw % divisor).is_zero() {\n            anyhow::bail!(\n                \"Order quantity {quantity} is not exactly representable in {decimals} base token decimals\"","sourceCodeStart":2428,"sourceCodeEnd":2464,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/execution/client.rs#L2428-L2464","documentation":"quantity_to_raw_amount converts a base-denominated order Quantity into raw token units with exact integer scaling. It rejects a zero quantity outright: a zero-amount order cannot correspond to any valid on-chain swap amount and would produce a useless or reverting transaction.","triggerScenarios":"Passing a Quantity that is zero to the order/swap amount conversion: strategy sizing math that multiplies by a zero factor, a clamp that floors a computed size to zero, Quantity::default()/from('0') used as a placeholder, or a parsed size of 0 from user input or config.","commonSituations":"Sizing engines that emit zero when capital allocation, signal strength, or available balance computes to zero; order templates with unset size fields; edge-case tests or config presets that accidentally leave size at zero.","solutions":["Skip order submission entirely when the computed size is zero instead of forwarding it to the execution client","Fix the sizing logic so signals with non-zero intent produce a positive size (guard upstream division/clamping)","Validate user/config-provided sizes as strictly positive at the boundary where they enter the system"],"exampleFix":"// before\nlet qty = signal_strength * notional; // can be 0.0\nsubmit_order(&instrument, Quantity::from(qty));\n\n// after\nlet qty = signal_strength * notional;\nif qty > 0.0 {\n    submit_order(&instrument, Quantity::from(qty));\n}","handlingStrategy":"validation","validationCode":"anyhow::ensure!(!qty.is_zero(), \"refusing to submit zero-sized order for {instrument_id}\");\nlet amount = quantity_to_raw_amount(qty, decimals)?;","typeGuard":null,"tryCatchPattern":"match quantity_to_raw_amount(qty, decimals) {\n    Err(e) if e.to_string().contains(\"must be positive\") => {\n        // sizing bug upstream: drop the signal and log; never coerce 0 -> 1\n        log::warn!(\"zero size from sizer; skipping order\");\n        Ok(None)\n    }\n    other => other.map(Some),\n}","preventionTips":["Assert positive size in the strategy's submit path, not just in the executor","Unit-test sizers for boundary inputs (zero signal, zero capital) expecting no submission","Treat a zero-size order as a smell: log it with the inputs that produced it"],"tags":["order","quantity","validation","swap","zero-size"],"backgroundTag":"zero-order-quantity-rejected","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}