{"record":{"id":"3e3d94795c1b43b2","repo":"nautechsystems/nautilus_trader","slug":"provider-order-expiration-seconds-provider-expire","errorCode":null,"errorMessage":"provider order expiration seconds {provider_expire_seconds:?} do not match cached order expiration seconds {cached_expire_seconds:?}","messagePattern":"provider order expiration seconds (.+?) do not match cached order expiration seconds (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/polymarket/src/execution/reconciliation.rs","lineNumber":515,"sourceCode":"        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}\",\n        provider_order.price,\n    );\n\n    let provider_expire_seconds = provider_expire_time.map(|value| value.as_seconds());\n    let cached_expire_seconds = cached_order\n        .expire_time()\n        .filter(|value| !value.is_zero())\n        .map(|value| value.as_seconds());\n    if cached_order.time_in_force() == TimeInForce::Gtd {\n        anyhow::ensure!(\n            cached_expire_seconds == provider_expire_seconds,\n            \"provider order expiration seconds {provider_expire_seconds:?} do not match cached order expiration seconds {cached_expire_seconds:?}\",\n        );\n    }\n\n    Ok(())\n}\n\nstruct OrderRowResult {\n    report: Option<OrderStatusReport>,\n    counted_filtered: bool,\n}\n\n#[derive(Clone, Copy)]\npub(crate) struct TargetOrderReportScope<'a> {\n    instrument_id: InstrumentId,\n    venue_order_id: VenueOrderId,\n    client_order_id: Option<ClientOrderId>,","sourceCodeStart":497,"sourceCodeEnd":533,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/polymarket/src/execution/reconciliation.rs#L497-L533","documentation":"For GTD (good-tilled-date) orders, reconciliation compares the provider-reported expiration seconds with the cached order's expire_time. This error means they differ, so the venue row cannot be safely matched to the cached GTD order. The adapter only enforces this for TimeInForce::Gtd because other TIFs have no meaningful expiration.","triggerScenarios":"Reconciling a cached GTD order where `provider_expire_seconds` (from the venue's open-order data, zero/none values filtered out of the cached side) is not exactly equal to the cached `expire_time().as_seconds()`. Occurs when the cached GTD order expired/was replaced, when expire_time was serialized with different units or truncated (nanos->seconds), or when the venue row belongs to a different order generation.","commonSituations":"Order placed with a GTD timeout that was later canceled and re-placed with a new expiry while the cache kept the old row; timezone/epoch-unit mismatch when persisting expire_time; adapter version change altering how expiration is parsed from the Polymarket API; clock skew at order creation.","solutions":["Check the cached order's expire_time against the venue's expiration and refresh/rebuild the cache entry for that venue_order_id.","Verify expire_time is stored as Unix seconds consistently (not millis/nanos) across persistence and reconstruction.","If the order expired and was re-placed, reconcile the new venue order id rather than reusing the old one.","Cancel the GTD order and resubmit with the intended expiry, then re-run reconciliation."],"exampleFix":"// before: reusing a cached GTD row with a stale expire_time\nlet cached = cache.order(&client_order_id)?; // expire 1700000000\n// after: drop stale GTD rows before reconciliation\nif cached.time_in_force() == TimeInForce::Gtd\n    && cached.expire_time().as_seconds() < now_seconds()\n{\n    cache.rebuild_from_venue(&venue_order_id).await?;\n}","handlingStrategy":"validation","validationCode":"if cached.time_in_force() == TimeInForce::Gtd {\n    let venue = client.get_order(&venue_order_id).await?;\n    let cached_secs = cached.expire_time().as_seconds();\n    let venue_secs = venue.expire_time.map(|t| t.as_seconds());\n    if cached_secs != venue_secs {\n        return Err(anyhow!(\"GTD expire drift for {venue_order_id}: {cached_secs} vs {venue_secs:?}\"));\n    }\n}","typeGuard":"fn gtd_expiry_matches(cached: &OrderAny, provider_secs: Option<i64>) -> bool {\n    cached.time_in_force() != TimeInForce::Gtd\n        || cached.expire_time().filter(|t| !t.is_zero()).map(|t| t.as_seconds()) == provider_secs\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"expiration seconds\") => {\n        log::warn!(\"GTD expiry drift; refreshing cache from venue\");\n        rebuild_order_from_venue(&venue_order_id).await?;\n    }\n    other => other?,\n}","preventionTips":["Store expire_time in Unix seconds consistently across persistence formats.","Re-place (cancel + submit) GTD orders instead of reusing stale cache rows when the expiry changes.","Keep the adapter version and cache serialization format in sync across upgrades."],"tags":["reconciliation","gtd","expiration-mismatch","polymarket"],"backgroundTag":"invalid-argument-value","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"}