nautechsystems/nautilus_trader · error
provider order expiration seconds {provider_expire_seconds:?
Error message
provider order expiration seconds {provider_expire_seconds:?} do not match cached order expiration seconds {cached_expire_seconds:?} What it means
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.
Source
Thrown at crates/adapters/polymarket/src/execution/reconciliation.rs:515
cached_order.time_in_force(),
);
validate_client_bound_order_quantity(provider_order, expected_quantity)?;
let cached_price = cached_order
.price()
.context("cached Limit order is missing price")?;
anyhow::ensure!(
cached_price.as_decimal() == provider_order.price,
"provider order price {} does not match cached order price {cached_price}",
provider_order.price,
);
let provider_expire_seconds = provider_expire_time.map(|value| value.as_seconds());
let cached_expire_seconds = cached_order
.expire_time()
.filter(|value| !value.is_zero())
.map(|value| value.as_seconds());
if cached_order.time_in_force() == TimeInForce::Gtd {
anyhow::ensure!(
cached_expire_seconds == provider_expire_seconds,
"provider order expiration seconds {provider_expire_seconds:?} do not match cached order expiration seconds {cached_expire_seconds:?}",
);
}
Ok(())
}
struct OrderRowResult {
report: Option<OrderStatusReport>,
counted_filtered: bool,
}
#[derive(Clone, Copy)]
pub(crate) struct TargetOrderReportScope<'a> {
instrument_id: InstrumentId,
venue_order_id: VenueOrderId,
client_order_id: Option<ClientOrderId>,View on GitHub (pinned to 18893faf8b)
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.
Example fix
// before: reusing a cached GTD row with a stale expire_time
let cached = cache.order(&client_order_id)?; // expire 1700000000
// after: drop stale GTD rows before reconciliation
if cached.time_in_force() == TimeInForce::Gtd
&& cached.expire_time().as_seconds() < now_seconds()
{
cache.rebuild_from_venue(&venue_order_id).await?;
} Defensive patterns
Strategy: validation
Validate before calling
if cached.time_in_force() == TimeInForce::Gtd {
let venue = client.get_order(&venue_order_id).await?;
let cached_secs = cached.expire_time().as_seconds();
let venue_secs = venue.expire_time.map(|t| t.as_seconds());
if cached_secs != venue_secs {
return Err(anyhow!("GTD expire drift for {venue_order_id}: {cached_secs} vs {venue_secs:?}"));
}
} Type guard
fn gtd_expiry_matches(cached: &OrderAny, provider_secs: Option<i64>) -> bool {
cached.time_in_force() != TimeInForce::Gtd
|| cached.expire_time().filter(|t| !t.is_zero()).map(|t| t.as_seconds()) == provider_secs
} Try / catch
match result {
Err(e) if e.to_string().contains("expiration seconds") => {
log::warn!("GTD expiry drift; refreshing cache from venue");
rebuild_order_from_venue(&venue_order_id).await?;
}
other => other?,
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- provider GTD order {} requires a valid positive expiration
- unmapped_in_scope_message("open order", instrument_id, Some(
- unmapped_in_scope_message("position", instrument_id, None, c
- expected Polymarket BinaryOption instrument, found {instrume
- provider venue order {} is not owned by the account
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/3e3d94795c1b43b2.
Report an issue: GitHub.