{"record":{"id":"7db90ff6ec65b7aa","repo":"nautechsystems/nautilus_trader","slug":"ax-final-settlement-fill-must-also-be-classified-a","errorCode":null,"errorMessage":"AX final-settlement fill must also be classified as a block trade","messagePattern":"AX final-settlement fill must also be classified as a block trade","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/http/parse.rs","lineNumber":659,"sourceCode":"/// # Errors\n///\n/// Returns an error if:\n/// - Price or quantity fields cannot be parsed.\n/// - Fee parsing fails.\n/// - Fill classification is inconsistent.\n/// - A fill is neither explicitly special nor linked to a valid order ID.\npub fn parse_fill_report(\n    fill: &AxFill,\n    account_id: AccountId,\n    instrument: &InstrumentAny,\n    ts_init: UnixNanos,\n) -> anyhow::Result<FillReport> {\n    let instrument_id = instrument.id();\n\n    let trade_id = TradeId::new_checked(&fill.trade_id).context(\"Invalid trade_id in Ax fill\")?;\n    let is_block_trade = fill.is_block_trade;\n    let is_final_settlement = fill.is_final_settlement;\n    anyhow::ensure!(\n        !(is_final_settlement == Some(true) && is_block_trade == Some(false)),\n        \"AX final-settlement fill must also be classified as a block trade\"\n    );\n\n    let is_special_fill = is_block_trade == Some(true) || is_final_settlement == Some(true);\n    let venue_order_id = if is_special_fill {\n        VenueOrderId::new_checked(format!(\"AX-FILL-{}\", fill.trade_id))\n            .context(\"Invalid synthetic venue order ID for AX fill\")?\n    } else {\n        let order_id = fill\n            .order_id\n            .as_deref()\n            .context(\"AX fill is missing order_id and explicit special-fill classification\")?;\n        anyhow::ensure!(\n            !order_id.is_empty(),\n            \"AX regular fill has an empty order_id\"\n        );\n        VenueOrderId::new_checked(order_id).context(\"Invalid order_id in AX fill\")?","sourceCodeStart":641,"sourceCodeEnd":677,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/http/parse.rs#L641-L677","documentation":"parse_fill_report models final settlements as synthetic block fills (venue order id \"AX-FILL-{trade_id}\"), so it requires any fill with is_final_settlement = true to also carry is_block_trade = true. A fill flagged as final settlement but explicitly not a block trade is ambiguous under that model and is rejected instead of mis-linked to an order.","triggerScenarios":"The /fills stream returns a fill with is_final_settlement: true and is_block_trade: false — e.g. the venue starts emitting settlement fills without the block flag, or a custom integration writes fills with incomplete classification flags.","commonSituations":"AX changes fill classification semantics between versions; a perp contract expires and its settlement fill arrives with flags the adapter's invariant does not allow; hand-crafted fills in test fixtures with only one flag set.","solutions":["Fetch the raw fill by the trade_id from /fills and inspect both flags to confirm the mismatch","If the fill really is a settlement, correct the classification upstream so is_block_trade is also true (venue config or the integration writing fills)","If AX semantics genuinely allow settlement-without-block, relax the invariant in the adapter via a PR with tests covering both flags","Until fixed, expect the fills request containing this fill to fail and reconcile the affected trades manually"],"exampleFix":"// before (AX fill payload)\n{\"trade_id\": \"T1\", \"is_final_settlement\": true, \"is_block_trade\": false}\n// after\n{\"trade_id\": \"T1\", \"is_final_settlement\": true, \"is_block_trade\": true}","handlingStrategy":"validation","validationCode":"// Pre-check fill classification flags before parsing (parse_fill_report is pub)\nfn fill_flags_consistent(fill: &AxFill) -> bool {\n    !(fill.is_final_settlement == Some(true) && fill.is_block_trade == Some(false))\n}\n\nfor fill in &fills {\n    if !fill_flags_consistent(fill) {\n        log::error!(\"fill {} has inconsistent settlement/block flags; quarantining\", fill.trade_id);\n        continue;\n    }\n    reports.push(parse_fill_report(fill, account_id, &instrument, ts_init)?);\n}","typeGuard":null,"tryCatchPattern":"match parse_fill_report(fill, account_id, &instrument, ts_init) {\n    Ok(report) => reports.push(report),\n    Err(e) if e.to_string().contains(\"must also be classified as a block trade\") => {\n        log::error!(\"quarantining fill {}: settlement/block flag mismatch: {e}\", fill.trade_id);\n        quarantined.push(fill.trade_id.clone()); // reconcile manually, do not double-count\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["When writing fills from custom integrations, always set is_block_trade alongside is_final_settlement","Contract-test fill classification flags after AX API upgrades","Track quarantined fills in a dead-letter list so settlements are not silently dropped from books"],"tags":["architect-ax","fills","classification","validation","settlement"],"backgroundTag":"invalid-fill-data","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}