A partial fill is an order that has executed some, but not all, of its requested quantity.
That definition is simple; the state is not. A working order can fill while a cancel is in flight, a rejection can arrive after a timeout, and a broker's order status can lag its execution reports. Code that treats each message as a clean, final answer will misstate exposure.
Model executions, not status labels
Most trading systems start with a status enum: new, partially filled, filled, cancelled, rejected. That is useful for display, but unsafe as the source of truth. Order status describes the broker's view of an order. Executions describe trades that have occurred. Track both.
For every order, maintain at least:
- The requested quantity and the latest accepted order quantity
- Cumulative executed quantity, built from unique fills
- Remaining quantity, computed as accepted quantity minus cumulative fills
- The order's current broker status and the strategy's intended status
- The broker order ID, client order ID, and execution IDs already processed
Use execution reports to update filled quantity. Many APIs provide a last-fill quantity and a cumulative quantity.
Treat the cumulative value as a consistency check, not an extra fill to add. If a message is repeated, deduplicate it using a stable execution ID. If the same execution ID arrives with changed details, flag it for reconciliation rather than silently overwriting the record.
Do not infer that an order is fully done because a message says partially filled. The remaining quantity can still be live. Nor does a cancel acknowledgement prove that the remainder is zero: a fill may have occurred before the venue processed the cancel and be reported later. Preserve the event history so the position can be reconstructed.
Separate order intent from broker state
A strategy expresses intent: buy 500 shares, or sell 2 contracts. The broker reports what it accepted and what happened next.
These are related, but they are not the same object. Store an intent record separately from each broker order created to fulfil it. A replacement order should not erase the prior order's identity or fills.
This distinction matters in cancel-replace flows. A request to replace an order may be implemented by a venue as an atomic modification, or as a cancel followed by a new order.
The API's wording does not guarantee the venue's sequencing. Until the response is confirmed, both the old order's remaining quantity and the new request may need to be considered as possible exposure.
Set an invariant for each intent. For example, total live leaves quantity plus cumulative fills must not exceed the strategy's permitted quantity, except during a documented replace race where the venue can briefly hold both versions. If the broker supports atomic amendments, verify that behaviour per order type. Otherwise, use a conservative sequence and reserve risk for the maximum plausible live quantity.
Keep the strategy's goal explicit too. After a partial fill, it may want to leave the residual working, cancel it, or reprice it.
Those are different policies. A handler that automatically resubmits the unfilled amount can create duplicate exposure if the original order remains active. Make the decision in strategy logic, then have the execution layer enforce quantity and risk limits.
Classify rejections before retrying
A rejection is an outcome, not a retry instruction. Parse the broker's reason code and classify it.
Exact codes vary by API, and the same human-readable message can cover different causes. Retain the raw response alongside your normalised classification.
- Permanent request errors: invalid symbol, unsupported order type, malformed price, or quantity below the minimum. Fix or abandon the intent. Repeating the same request will not help.
- Account or risk constraints: insufficient buying power, position limits, restricted instrument, or a trading halt. Refresh relevant account state, then require an explicit policy decision before trying again.
- Transient service errors: throttling, temporary unavailability, or a gateway timeout. Retry only when the request's outcome is known or the API provides a safe idempotency mechanism.
- Market-state errors: price outside a band, closed session, or an invalid time-in-force for the current phase. Re-evaluate the order against current market and session data.
Be especially cautious with timeouts. A client timeout says the client did not receive a timely answer. It does not say the broker failed to accept the order. Query by client order ID or use the broker's order stream before sending another request. Where the API guarantees idempotency, reuse the same key for the same logical request. A fresh key can turn a retry into a second order.
Bound retries by count and elapsed time. Use jittered backoff for service throttles, but do not let a backoff schedule become a blind order-placement schedule.
A useful policy can be one or two attempts for a clearly transient error, followed by a halted intent and reconciliation. Permanent validation failures should have no automatic retry.
Price the residual and the recovery path
Partial fills create an inventory decision. Suppose an agent wants 1,000 shares and receives a fill for 350. It now owns 350, has 650 potentially working, and must decide whether that residual still belongs at the same price. The answer depends on urgency, volatility, spread, participation limits, and the strategy's remaining risk budget.
Blindly crossing the spread to complete the target can cost more than leaving the order alone. Cancelling to reprice can lose queue priority. Leaving a stale limit order can expose the strategy to adverse selection. These trade-offs should be measured on the residual, not hidden inside an average fill price for the whole intent.
The figures below are typical planning ranges for liquid listed instruments, not audited outcomes or venue quotes. Actual costs vary materially by instrument, session, order size, and volatility. Basis points are measured against the residual order's notional.
| Failure mode | Typical planning range | Main cost driver |
|---|---|---|
| Residual left at a stale price | 1-10 bps adverse move over 100 ms to 5 seconds | Volatility, queue position, and time exposed |
| Marketable cleanup of residual | Spread cost of roughly 1-10 bps, plus 0-5 bps impact | Quoted spread, order size, and available depth |
| Cancel and re-enter | 1-5 ticks of lost price priority, with re-entry delay commonly 10-500 ms | Venue queue rules and API round-trip time |
| Duplicate live order after uncertain retry | Up to 1x the intended residual quantity in additional exposure | Whether the first request was accepted before timeout |
These estimates are inputs for scenario tests, not universal thresholds. Measure fill probability, slippage, time to completion, and residual exposure by instrument and order type. For testing, an agentic paper-trading environment can help exercise the code path, but synthetic fills cannot establish real queue priority or venue latency.
Make recovery a normal code path
Websocket events can be duplicated, delayed, or missed during a disconnect. REST snapshots can arrive while the event stream is still catching up. Recovery logic should therefore be part of the order lifecycle, not a support script reserved for an incident.
On reconnect, pause new orders for affected instruments. Request open orders, recent executions, and positions from the broker. Merge these with the local event log using broker order IDs and execution IDs. Do not assume that a snapshot is a later point in time than every stream event already buffered by the client. Apply deduplication and ordering rules, then verify invariants such as cumulative fills not exceeding accepted quantity.
If the broker cannot provide a sequence number, compare executions over an overlapping time window and deduplicate by execution ID. Record discrepancies rather than choosing whichever source seems newer.
When state cannot be reconciled, disable new risk in the affected instrument and raise an operational fault. Continuing with a guessed position is usually worse than missing a trade.
Keep an audit trail with request payload, response, broker timestamps, local receive timestamps, and every state transition. This allows a developer to distinguish a venue delay from a client bug. It also makes replay possible: feed the recorded events back through the same reducer and check that they produce the same order and position state.
Checklist for production handlers
Before unattended order placement, test the failure cases as deliberately as the normal fill path. The following checklist is a useful minimum:
- Represent intent, broker orders, and executions as separate records.
- Deduplicate fills by execution ID and reconcile cumulative quantity against individual events.
- Handle fills that arrive after cancel requests, cancel acknowledgements, or replacement requests.
- Classify rejection codes into permanent, account, transient, and market-state causes.
- On timeout, query the original client order ID before considering a new placement.
- Cap retries by count and time, and prohibit retries for permanent request errors.
- Enforce a maximum live quantity across original and replacement orders.
- Reconcile open orders, executions, and positions after every stream gap or process restart.
- Test duplicate, late, missing, and out-of-order events, including partial fill followed by reject or cancel.
- Log enough data to replay each state transition and explain the resulting exposure.
Run these tests with deterministic event fixtures, then with injected delays, duplicated messages, and dropped connections. Include awkward sequences, such as a partial fill followed by a timeout on cancel, then a late execution report. The expected result should specify both the position and whether new risk is permitted. Passing a happy-path sandbox test says little about this behaviour.
The industry still calls these cases edge conditions, despite their being ordinary execution.
Frequently Asked Questions
How should code calculate the remaining order quantity after a partial fill?
Use the broker-accepted order quantity minus cumulative executed quantity, with executions deduplicated by execution ID. Check that this agrees with any leaves quantity reported by the broker. Treat a mismatch as a reconciliation issue, not as a reason to place more quantity.
Can an order fill after a cancel request?
Yes. A fill can occur before the venue processes the cancel and may arrive after the cancel acknowledgement. Keep processing execution reports until the order is reconciled, and update position from executions rather than assuming the acknowledgement removed all remaining risk.
Should an automated trading system retry a rejected order?
Only after classifying the rejection. Permanent request errors need correction, while transient errors may justify a bounded retry. For a timeout with an unknown outcome, query the original client order ID before placing anything else.
How can a system avoid duplicate orders after an API timeout?
Use a stable client order ID or idempotency key for the same logical request when the broker supports it. Otherwise, query order state by the original identifier and reconcile executions before deciding whether a new request is safe. Do not use a fresh identifier as a blind retry.
What should an order handler do after missing broker events?
Pause new risk in the affected instrument and reconcile open orders, executions, and positions from the broker. Deduplicate overlapping records, record discrepancies, and resume only when local exposure is consistent with broker state. If the available data cannot establish that state, keep the instrument halted.