nautechsystems/nautilus_trader · error
Cumulative spread fill went backwards: acc_fill_sz='{}' < pr
Error message
Cumulative spread fill went backwards: acc_fill_sz='{}' < previous_filled_qty={} (possible stale data after reconnect) What it means
OKX spread order updates report cumulative filled size (acc_fill_sz). The parser keeps the previously known filled quantity and rejects updates where the new cumulative value is less than the previous one, since that indicates stale/duplicate data (often after a reconnect) rather than a real fill regression.
Source
Thrown at crates/adapters/okx/src/websocket/parse.rs:2019
previous_filled_qty: Option<Quantity>,
_ts_init: UnixNanos,
) -> anyhow::Result<Option<FillReport>> {
let size_precision = instrument.size_precision();
if !msg.fill_sz.is_empty() && msg.fill_sz != "0" {
parse_quantity(&msg.fill_sz, size_precision).map_err(|e| {
anyhow::anyhow!("Failed to parse spread fill_sz='{}': {e}", msg.fill_sz)
})?;
} else if !msg.acc_fill_sz.is_empty() && msg.acc_fill_sz != "0" {
let current_filled = parse_quantity(&msg.acc_fill_sz, size_precision).map_err(|e| {
anyhow::anyhow!(
"Failed to parse spread acc_fill_sz='{}': {e}",
msg.acc_fill_sz
)
})?;
if let Some(prev_qty) = previous_filled_qty {
if current_filled < prev_qty {
anyhow::bail!(
"Cumulative spread fill went backwards: acc_fill_sz='{}' < previous_filled_qty={} \
(possible stale data after reconnect)",
msg.acc_fill_sz,
prev_qty
);
}
if (current_filled - prev_qty).is_zero() {
log::debug!(
"Skipping duplicate spread fill: acc_fill_sz='{}' unchanged from previous={}",
msg.acc_fill_sz,
prev_qty
);
return Ok(None);
}
}
} else {
anyhow::bail!(View on GitHub (pinned to 18893faf8b)
Solutions
- Verify only one spread-orders subscription is active and reconnect logic doesn't replay old messages
- Discard/regenerate order state from a fresh REST snapshot after reconnect instead of trusting interleaved WS updates
- If genuinely out-of-order data is expected, catch this error and drop the stale update rather than failing the stream
Example fix
// before
let report = parse_spread_order_fill_report(msg, prev_qty)?; // bails on regression
// after
match parse_spread_order_fill_report(msg, prev_qty) {
Ok(r) => handle(r),
Err(e) if e.to_string().contains("went backwards") => tracing::warn!("stale spread update dropped"),
Err(e) => return Err(e),
} Defensive patterns
Strategy: try-catch
Validate before calling
if let Some(prev) = previous_filled_qty {
if let Some(acc) = &msg.acc_fill_sz {
if acc.parse::<f64>().map_or(true, |v| (v as u64) < prev.raw()) {
// stale/out-of-order update; drop before parsing
}
}
} Try / catch
match parse_spread_order_fill_report(&msg, prev_qty) {
Ok(Some(r)) => handle(r),
Ok(None) => {}
Err(e) if e.to_string().contains("went backwards") => {
tracing::warn!("dropping stale spread update: {e}");
}
Err(e) => return Err(e),
} Prevention
- After reconnect, rebuild order state from a REST snapshot before accepting WS updates
- Ensure a single subscription per spread order stream
- Stamp updates with timestamps and ignore older-than-last messages
When it happens
Trigger: A sprd-orders update arrives with acc_fill_sz lower than the previously tracked filled quantity for the same spread order, typically after a WebSocket reconnect replaying older messages.
Common situations: WS reconnect delivering a delayed/older update out of order; subscribing to the same channel twice; replayed snapshots mixed with live updates.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Cumulative fill went backwards: acc_fill_sz='{acc_fill_sz}'
- Previous WebSocket handler failed: {e}
- Invalid `BarSpecification` for channel, was {bar_spec}
- errors.join("; ")
- Timeout waiting for account {account_id} to be registered af
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/f410df1ef6513a7a.
Report an issue: GitHub.