nautechsystems/nautilus_trader · error

Cannot determine fill quantity: fill_sz is empty/zero and ac

Error message

Cannot determine fill quantity: fill_sz is empty/zero and acc_fill_sz is empty/zero

What it means

A fill quantity can only be derived from fill_sz (incremental) or acc_fill_sz (cumulative). When both are empty or zero the parser cannot produce a meaningful fill and bails, since emitting a zero fill would be ambiguous.

Source

Thrown at crates/adapters/okx/src/websocket/parse.rs:2136

                    anyhow::bail!(
                        "Cumulative fill went backwards: acc_fill_sz='{acc_fill_sz}' < previous_filled_qty={prev_qty} \
                         (possible stale data after reconnect)"
                    );
                }
                let incremental = current_filled - prev_qty;
                if incremental.is_zero() {
                    log::debug!(
                        "Skipping duplicate fill: acc_fill_sz='{acc_fill_sz}' unchanged from previous={prev_qty}"
                    );
                    return Ok(None);
                }
                incremental
            } else {
                // First fill, use accumulated as incremental
                current_filled
            }
        } else {
            anyhow::bail!(
                "Cannot determine fill quantity: fill_sz is empty/zero and acc_fill_sz is empty/zero"
            );
        }
    } else {
        anyhow::bail!(
            "Cannot determine fill quantity: fill_sz='{}' and acc_fill_sz is None",
            msg.fill_sz
        );
    };

    let fee_str = msg
        .fee
        .as_deref()
        .filter(|fee| !fee.trim().is_empty())
        .ok_or_else(|| anyhow::anyhow!("missing fee for fill report inst_id={}", msg.inst_id))?;
    let fee_dec = Decimal::from_str(fee_str)
        .map_err(|e| anyhow::anyhow!("Failed to parse fee '{fee_str}': {e}"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Only route updates with status 'live'/'partially_filled' and a nonzero fill into parse_fill_report; treat others as status events
  2. Return None instead of an error for zero-size updates if your flow expects them
  3. Log the raw message and confirm which OKX channel it came from

Example fix

// before
let fill = parse_fill_report(&msg, instrument, prev, ...)?; // bails on zero sizes
// after
if msg.fill_sz.is_none() && msg.acc_fill_sz.is_none() {
    return Ok(None); // status-only update
}
Defensive patterns

Strategy: type-guard

Validate before calling

fn is_zero_fill(msg: &OKXOrderMsg) -> bool {
    let zero = |s: Option<&String>| s.map_or(true, |v| v.is_empty() || v == "0");
    zero(msg.fill_sz.as_ref()) && zero(msg.acc_fill_sz.as_ref())
}

Type guard

fn has_fill_data(msg: &OKXOrderMsg) -> bool { !is_zero_fill(msg) }

Try / catch

if is_zero_fill(&msg) { return Ok(None); } // status-only update, not a fill

Prevention

When it happens

Trigger: An orders-channel update where fill_sz is empty or '0' and acc_fill_sz is also empty or zero — e.g. a status-only update (canceled/amended) routed through parse_fill_report, or a first snapshot with no fills yet.

Common situations: Order state changes (cancel/modify) delivered on the same channel and mistaken for fills; malformed fixtures; OKX field semantics differences between channels.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/e299f15529774599. Report an issue: GitHub.