Agentic Trading Index

Partial fills and rejections: safer controls for trading code

Trading terminal showing order updates, relevant to handling partial fills and rejections in code
Photo by Jan van der Wolf on Pexels

A successful order response does not mean the trade is complete.

Model the order as a lifecycle

Broker APIs return acknowledgements, executions, cancellations and rejections as separate events. Treating an acknowledgement as a fill can make a strategy believe it owns shares or contracts that it does not. Treating a timeout as a rejection can be worse: the venue may have accepted the order even though the client received no response.

Represent each order with a durable state record. At minimum, track the client order ID, broker order ID, instrument, side, original quantity, cumulative filled quantity, remaining quantity, average fill price and current status.

Derive remaining quantity from confirmed events, not from the last request sent. Keep raw broker messages as well as your normalised fields, since normalisation can hide a venue-specific meaning.

Events may arrive late, out of order or more than once. Deduplicate by the broker's execution ID where available. Apply cumulative quantities monotonically, but retain corrections and trade busts as new events rather than rewriting history. A stream reconnect should trigger a reconciliation against the broker's open orders and executions before the strategy resumes trading.

Price the risk in the remaining quantity

A partial fill is an actual position plus a live obligation. If an order for 1,000 shares fills 350, the system now has 350 shares of exposure and may still acquire another 650. Risk checks should account for both. A practical conservative calculation reserves capacity for the filled position and the full remaining quantity until cancellation is confirmed.

This matters for limits on gross exposure, sector concentration, use and intraday loss. It also matters when multiple strategies share an account. Two processes can each see spare buying power and submit orders that exceed the combined limit unless reservations are centralised. Release a reservation only after a confirmed fill, cancellation or rejection updates the state.

Choose an explicit policy for residual quantity. It might remain live until a deadline, be cancelled after a price move, or be replaced at a new limit. Each policy trades fill probability against price control. A market order may complete faster but surrender price certainty. A passive limit can reduce spread cost but leave inventory unhedged. Record the policy and its trigger in the order decision.

  • Set maximum order size and maximum live leaves quantity per instrument.
  • Include open orders in pre-trade exposure checks, not just positions already reported as filled.
  • Define a maximum age and price deviation for every working order.

Classify rejection before acting

A rejection is not a single condition. Some responses indicate a permanent problem, such as an invalid symbol, unsupported order type or permission failure.

Others may be temporary, including rate limits, session interruptions or a price outside a venue's current band. Broker codes and wording differ, so map each response into a small internal classification while preserving its original code.

Permanent rejections should normally stop that order path and raise an alert. A temporary rejection may permit a retry, but only after checking whether the original request could have been accepted. For an ambiguous timeout, query by client order ID or reconcile open orders first. Sending a fresh order with a new ID can create a duplicate position.

Do not translate every rejection into a new price or order type automatically. That can bypass a pre-trade control or turn an intended limit order into a marketable one.

Define which fields may change, who or what authorises the change, and the maximum number of attempts. A rejection caused by a compliance restriction must not be retried with altered parameters to reach the same economic result.

Make retries bounded and race-safe

Use a stable client order ID for a single logical order and follow the broker's rules for idempotency. A retry with the same ID can be safe on some interfaces, but other APIs reject duplicate IDs or interpret them differently.

Confirm the contract for the specific endpoint. If the interface offers no reliable idempotency, treat uncertain submission as an unresolved state and reconcile before resubmitting.

Cancel and replace operations have their own race. A fill can occur after a cancel request is sent but before cancellation is confirmed. Do not assume the remaining quantity is zero until the cancel acknowledgement and any executions are reconciled.

If a replacement is submitted too early, the original and replacement may both trade. Use a state transition that prevents new exposure during this interval, or reserve for both orders until the broker confirms which is live.

Apply a retry budget, backoff and circuit breaker. For example, a system might allow two retries for a transient transport failure, with delays of 250 milliseconds and one second, then pause that instrument and alert an operator. These are engineering starting points, not universal values. Exchange throttles, order urgency and strategy horizon should determine the actual limits. A repeated rejection rate, stale order stream or unexplained position difference should stop new orders, not prompt faster retries.

Put execution costs beside control decisions

Retries and residual handling have costs beyond API load. The table gives typical market ranges for liquid instruments in ordinary conditions.

They are illustrative ranges, not audited results or guarantees. Volatility, order size, venue, instrument and time of day can move costs materially, and less liquid products can exceed them.

EventTypical cost rangeCommon sourceControl to measure
Crossing the spread on residual quantity1 to 5 basis points of residual notionalSpread paid on a marketable replacementCompare execution price with decision-time midpoint
Cancel and replace after a partial fill1 to 10 basis points of replaced notionalSpread, slippage and lost queue priorityTrack filled quantity and price before and after replacement
Re-entry after a rejected marketable order2 to 15 basis points of intended notionalPrice movement while diagnosing or retryingMeasure decision-to-fill shortfall and retry delay
Unhedged residual during a fast market5 to 30 basis points of residual notionalAdverse price movement before hedge or cancellationMeasure exposure duration and worst move while live

These measurements help distinguish a useful retry from a costly habit. Track rates by rejection class, instrument and venue, and compare them with fill probability and realised slippage. A paper environment that models acknowledgements, delayed fills and rejection paths can test state handling before live deployment. For example, agentic paper trading can be part of a test programme, but it cannot establish that a broker's live API behaves identically.

Keep evidence and a recovery path

Execution controls also need an audit trail. Store timestamps in UTC, preserve the broker's event time and your receipt time, and use a monotonic sequence for local processing.

Record the strategy and software version, inputs to the risk check, limits applied, order changes, rejection codes and the reason for each retry. Retain the link between a decision, its client order ID and every resulting execution.

Before resuming after a process crash or connection loss, compare local state with broker state. Reconcile positions, open orders, executions and cash or buying power. Resolve discrepancies explicitly. Do not silently overwrite local records to match a snapshot, because that removes evidence of what the system believed when it acted. Procedures should also cover broker outages, duplicate events and execution corrections.

Use this checklist before enabling unattended order placement:

  1. Persist an order record before sending the request, with a unique client order ID.
  2. Represent acknowledgement, partial fill, full fill, pending cancel, cancelled and rejected as distinct states.
  3. Reserve risk for filled exposure and all potentially live remaining quantity.
  4. Classify rejection codes, and allow retries only for defined transient classes.
  5. On timeout or reconnect, query and reconcile before creating another order.
  6. Set retry, order-age, slippage and rejection-rate limits that stop new orders when breached.
  7. Test duplicate, delayed, out-of-order and corrected execution events, then verify the audit record.

The mistake most readers will still make is treating a timeout as proof that the order never reached the broker.

Frequently Asked Questions

What should trading code do after a partial fill?

Update the confirmed position and keep the remaining quantity as live exposure until the order is filled or cancellation is confirmed. Recalculate risk limits before submitting or replacing any further quantity.

Should an automated trader retry a rejected order?

Only after classifying the rejection and checking whether the original request could have been accepted. Retry defined transient failures within a fixed budget, and do not retry compliance or permission failures with altered parameters.

How can I avoid duplicate orders after an API timeout?

Keep a stable client order ID and query broker state or reconcile open orders before resubmitting. If the API does not guarantee idempotency, treat the request as unresolved until its status is known.