{"record":{"id":"5efa6b4064d47102","repo":"nautechsystems/nautilus_trader","slug":"provider-order-side-does-not-match-cached-order","errorCode":null,"errorMessage":"provider order side {} does not match cached order side {}","messagePattern":"provider order side (.+?) does not match cached order side (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/polymarket/src/execution/reconciliation.rs","lineNumber":487,"sourceCode":"    provider_order: &PolymarketOpenOrder,\n    expected_quantity: Quantity,\n) -> anyhow::Result<()> {\n    anyhow::ensure!(\n        expected_quantity.as_decimal() == provider_order.original_size,\n        \"provider order quantity {} does not match cached order quantity {}\",\n        provider_order.original_size,\n        expected_quantity,\n    );\n    Ok(())\n}\n\nfn validate_client_bound_order_row(\n    provider_order: &PolymarketOpenOrder,\n    cached_order: &OrderAny,\n    expected_quantity: Quantity,\n    provider_expire_time: Option<UnixNanos>,\n) -> anyhow::Result<()> {\n    anyhow::ensure!(\n        cached_order.order_side() == provider_order.side.into(),\n        \"provider order side {} does not match cached order side {}\",\n        provider_order.side,\n        cached_order.order_side(),\n    );\n    anyhow::ensure!(\n        cached_order.time_in_force() == provider_order.order_type.into(),\n        \"provider order time in force {} does not match cached order time in force {}\",\n        provider_order.order_type,\n        cached_order.time_in_force(),\n    );\n    validate_client_bound_order_quantity(provider_order, expected_quantity)?;\n    let cached_price = cached_order\n        .price()\n        .context(\"cached Limit order is missing price\")?;\n    anyhow::ensure!(\n        cached_price.as_decimal() == provider_order.price,\n        \"provider order price {} does not match cached order price {cached_price}\",","sourceCodeStart":469,"sourceCodeEnd":505,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/polymarket/src/execution/reconciliation.rs#L469-L505","documentation":"validate_client_bound_order_row compares the cached OrderAny's order_side with the provider PolymarketOpenOrder's side (converted into the domain enum). A disagreement means the provider order being reconciled is not the same logical order as the cached one (or a side-mapping bug exists), so this anyhow error is raised before further checks.","triggerScenarios":"build_order_report_from_order -> validate_client_bound_order_row receives a provider order whose side (BUY/SELL) does not match cached_order.order_side() — typically because a venue_order_id collision matched two different orders, or the cached order was created with an inverted side.","commonSituations":"Venue order id reuse or off-by-one when matching provider order ids to cached orders; cache restored from a different account/strategy; side conversion bug after a provider API enum change (e.g. long vs buy); orders duplicated across strategies sharing one order id namespace.","solutions":["Verify the venue_order_id/provider order id mapping — the mismatch usually means the wrong provider order was matched to the cached order; correct the lookup key.","Re-sync the cache by fetching the order from the provider and rebuilding the cached OrderAny with the provider's authoritative side.","Check the side conversion (provider side -> OrderSide) for a mapping regression after an adapter/SDK update.","Purge stale cross-strategy cache entries so ids cannot collide between strategies."],"exampleFix":"// before\nlet cached = cache.get(&venue_order_id)?; // matches provider id X\n// but cached.order_side() == Sell, provider side == Buy\n// after\nlet cached = cache.get_by_both(&venue_order_id, &client_order_id)?; // disambiguate the lookup\nanyhow::ensure!(cached.order_side() == provider_order.side.into(), \"side mismatch\");","handlingStrategy":"validation","validationCode":"fn sides_match(provider: &PolymarketOpenOrder, cached: &OrderAny) -> bool {\n    cached.order_side() == provider.side.into()\n}\n// match on both ids to avoid collisions:\nlet cached = cache.find(|o| o.venue_order_id() == provider.id && o.order_side() == provider.side.into());","typeGuard":"fn same_logical_order(p: &PolymarketOpenOrder, c: &OrderAny) -> bool {\n    c.venue_order_id().map(|v| v.as_str() == p.id).unwrap_or(false)\n        && c.order_side() == p.side.into()\n}","tryCatchPattern":"match build_order_report_from_order(&provider_order, &cached) {\n    Err(e) if e.to_string().contains(\"does not match cached order side\") => {\n        log::error!(\"wrong provider order matched to cache: {}\", provider_order.id);\n        // re-key the lookup, do not silently flip the side\n    }\n    other => other?,\n}","preventionTips":["Look up cached orders by full (client_order_id, venue_order_id) keys, never side-agnostic id only","Separate order id namespaces per strategy/account to avoid cache collisions","Add a unit test for the provider side -> OrderSide conversion when upgrading the adapter SDK"],"tags":["polymarket","reconciliation","order-state","side-mismatch"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}