A partial fill is not just a slower full fill. It changes the quantity still exposed to the market, and a rejection may arrive after your code has already made another decision.
Unattended execution needs to treat order acknowledgements, fills, cancellations and rejections as separate events. The job is not to make every order succeed. It is to know what happened, cap the actions taken in response and reconcile the resulting position before sending more risk.
Represent each order as a state machine
Keep the order lifecycle in durable state, rather than inferring it from the most recent API response. A submitted request may be pending, accepted, partially filled, filled, cancelled, expired or rejected. Some venues expose additional states, such as pending cancel or pending replace. Map venue-specific labels into your own small set of states, but retain the original status and reason for diagnosis.
Track requested quantity, cumulative filled quantity and leaves quantity separately. For an order to buy 800 shares with 310 filled, leaves quantity is 490.
If a later report says cumulative filled quantity is 350, the new fill is 40, not 350. Use cumulative quantities or unique execution identifiers to avoid counting a duplicated event twice.
Do not assume events arrive in order. A fill can reach the application before the acknowledgement, and a cancel request can cross with a fill. Keep the order in a pending or uncertain state until the venue confirms what happened. A cancel acknowledgement means the remainder is cancelled. It does not undo fills that occurred before cancellation took effect.
Persist the client order ID before sending a request. On a timeout, the request may have reached the venue even if the response did not reach your process. Query by that ID or reconcile open orders before deciding whether to submit again. Blindly retrying a timed-out placement can create a second live order.
Useful invariants include:
- Cumulative filled quantity never decreases and never exceeds the accepted order quantity.
- Leaves quantity equals accepted quantity minus cumulative fills, unless the venue reports a different quantity definition.
- A terminal state cannot be reopened by a delayed status event. Reconcile the event sequence or query current venue state.
- Position accounting uses executions, not order acknowledgements or intended quantities.
Store raw events alongside the normalised state. That makes it possible to rebuild the order lifecycle after a process restart and investigate whether a venue, network path or local handler produced the mismatch.
Choose how to observe and manage the remainder
There is no single best response to a partial fill. Leaving the remainder working preserves queue position and avoids extra order traffic. Cancelling and replacing gives the strategy more control over price, but introduces a race between fills and cancellation.
Polling is straightforward, though it adds latency and API load. The right choice depends on urgency, liquidity, venue rules and the strategy's tolerance for an incomplete position.
The figures below are typical ranges for planning under ordinary API conditions, not guarantees. Venue, instrument, network path and market activity can move them substantially.
| Approach | Typical range | Strength | Main trade-off |
|---|---|---|---|
| Streaming execution reports | 20-250 ms event delivery | Low-latency updates without repeated status queries | Requires reconnect handling, sequence checks and reconciliation after gaps |
| REST status polling | 250-2,000 ms interval, or 30-240 polls per minute per order | Simple recovery path when event streams disconnect | More stale decisions and request load as active order count grows |
| Cancel, confirm, then replace | 50 ms-1 s for a typical acknowledgement round trip | Limits exposure at an old price before choosing a new one | Can miss queue priority and must account for fills during the cancel race |
| Leave remainder working | Review every 1-5 s, or on defined market events | Avoids unnecessary order churn and preserves existing priority | Price or inventory exposure can drift while the order rests |
A stream-first design is usually a sensible default, with polling or a direct query as a recovery mechanism. A stream is not a complete source of truth: connections drop, consumers restart and messages can be delayed.
Record sequence numbers where available. After a gap, query the order and recent executions, then reconcile before processing new strategy instructions.
For a cancel and replace, do not send the replacement merely because the cancel request was accepted by your local client. Wait for a terminal response or query the order. If the old order has filled more while cancellation was pending, reduce the replacement quantity accordingly. If the status remains unknown, pause that order's replacement path rather than guessing.
Leaving a remainder in the market is often appropriate for passive execution with a price limit. A marketable order or a short-lived instruction may need a tighter deadline.
Make that decision explicit in strategy configuration: a maximum resting time, a price band and a maximum number of replacements. Avoid a generic rule that reprices every partial fill, since it can create order churn in a thin book.
Classify rejections and bound recovery
A rejection is a venue response with a reason, not a generic signal to retry. Separate deterministic errors from transient failures.
Invalid symbols, precision errors, unavailable order types, insufficient buying power and breached risk limits usually require a corrected instruction or a human-defined halt. Repeating the same request will not fix them.
Rate limits, temporary service errors and some connectivity failures may be recoverable, but only after checking whether the original order exists. A timeout is not a rejection.
If the placement response is unknown, reconcile by client order ID before another submission. Use a stable idempotency key where the API supports it, and keep your own duplicate protection even when it does not.
A recovery sequence should make the next action depend on confirmed state:
- Persist intent. Store the client order ID, instrument, side, quantity, limit, time in force and strategy decision before sending the request.
- Submit once. Record the request and response. If the response times out, mark the order unknown rather than rejected.
- Reconcile uncertainty. Query by client order ID and inspect open orders and executions. Do not place a replacement until the original status is known.
- Apply fills first. Update cumulative fills and position from execution reports, deduplicating by execution ID. Recalculate the remaining quantity from confirmed state.
- Classify the outcome. Treat permanent validation or risk rejections as stop conditions. Retry only documented transient errors or an explicitly recoverable state.
- Enforce a retry budget. Use capped attempts, a deadline and backoff, such as 100-500 ms initially and 1-2 s between later attempts, subject to venue limits and strategy urgency.
- Check exposure limits. Before any replacement, verify that confirmed position plus all live and uncertain orders remains within the strategy's limits.
- Escalate unresolved state. If status cannot be reconciled within the configured window, stop new orders for that instrument or strategy and raise an alert.
Retrying a rejected order with a changed price is not the same as retrying the same request. Treat it as a new decision with a new client order ID, a recorded reason and a fresh risk check. Keep a maximum replacement count, often one to three attempts for a tightly bounded execution task, rather than allowing a loop to run until the market moves or the process stops.
Do not silently convert a rejected limit order into a market order. That changes execution risk. Likewise, reducing quantity to fit buying power can alter the strategy's intended exposure. If the strategy permits either adjustment, define its conditions and audit the chosen quantity and price.
Test recovery paths, not just successful placement
Most test code exercises the clean sequence: submit, acknowledge, fill. A live handler also needs to survive duplicate reports, delayed acknowledgements, partial fills during cancel, rejected replacements, stream gaps and restarts between an execution and its database write. Build these cases into deterministic tests and replay recorded event sequences where available.
Include scenarios such as:
- A 500-unit order fills 120 units, receives a duplicate execution report, then fills another 80 before cancellation.
- A placement times out, but a query reveals that the order is live with no fill.
- A cancel request times out while a late fill arrives, leaving only part of the original remainder open.
- A replacement is rejected for price precision after the old order has already been cancelled.
- A websocket reconnect produces a sequence gap while several orders are partially filled.
For each case, assert the final order state, position, remaining live quantity and number of requests sent. Assert that the system never exceeds its configured exposure limit, even when an event is duplicated or arrives out of order. Test restart recovery too: reconstruct state from durable intent, venue queries and executions, not from memory left over in the previous process.
Track operational measures by venue and instrument. Useful signals include time from venue event to local state update, unknown-order duration, duplicate event count, cancel acknowledgement latency, rejection reason frequency, retry count and difference between internal and venue-reported position. Alert on a persistent position mismatch or an order that remains unknown beyond its deadline, rather than alerting only on API errors.
Paper trading can exercise these handlers, but only if it generates partial fills, rejects and cancel races instead of treating every request as fully filled. A configurable test environment for agentic paper trading can help teams validate order-handling logic before live deployment. Keep the test assumptions visible: a simulated fill model cannot reproduce every queue, venue outage or market impact.
Set a clear fallback for each failure class. Some systems pause only the affected order; others stop new orders for the instrument or strategy.
The choice should reflect how much exposure can remain live and how quickly the process can reconcile it. What is the longest an order can remain in an unknown state before your code stops adding exposure?
Frequently Asked Questions
What should trading code do after a partial fill?
Update position from confirmed executions and calculate the remaining quantity from accepted quantity minus cumulative fills. Then follow a defined policy: leave the remainder working, cancel it or replace it after confirming the current order state.
Should an order rejection be retried automatically?
Only if the rejection reason is documented as transient and the original order state is known. Validation errors, risk rejections and insufficient buying power need a corrected decision or a stop condition, not repeated identical requests.
How can code avoid duplicate orders after an API timeout?
Treat a timeout as an unknown outcome, not proof that the venue rejected the request. Query using a stable client order ID or reconcile open orders and executions before submitting again.
Is websocket order tracking enough for live trading?
No. Streaming reports reduce observation latency, but connections can drop and events can be delayed or duplicated. Use sequence checks where available and reconcile with venue queries after reconnects or detected gaps.
When should code cancel a partially filled order?
Cancel when the remaining quantity no longer fits the strategy's price, time or exposure limits. Confirm the cancellation or reconcile the order before placing a replacement, because fills can occur while the cancel request is pending.