Agentic Trading Index

Partial fills and rejections: a checklist for trading code

Trading terminal showing order updates, relevant to handling partial fills and rejections in code
Photo by Rafael Minguet Delgado on Pexels

At 09:31, Operator A has filled 2,000 shares of a 5,000-share buy and cancels the 3,000-share remainder. Operator B sees the same partial fill, submits a replacement for 3,000 shares before confirming the original cancel, then receives another 1,000-share fill on the original order. A has 2,000 shares; B has 6,000.

The difference is not the strategy. It is how each system treats an order that is still changing.

Partial fills and rejections need explicit state, quantity and recovery rules. This week's task is to make those rules testable, rather than leaving them to a retry loop.

Two operators, opposite calls

There is no universal rule to cancel or replace a partial order. A passive order may be worth leaving in the book if its price is still valid. A marketable order whose remainder is no longer within the risk limit may need cancelling. In either case, a cancel request is not a cancel confirmation. The original order can still fill while the request is in flight.

Start by separating three quantities:

  • Order quantity: the amount originally submitted.
  • Cumulative filled quantity: the sum of unique executions received so far.
  • Leaves quantity: the amount still working, normally order quantity minus cumulative fills, adjusted for broker-specific amendments.

Do not infer that the leaves quantity is zero from a cancel request, a timeout, or a local status label. Keep exposure reserved until an authoritative update or reconciliation establishes what remains live.

Model fills as durable events

Represent executions separately from the order's current status. A broker may report a partial fill, then a cancel, while a late execution report arrives after the cancel acknowledgement.

Each execution should be recorded once, keyed by a stable execution identifier where the API provides one. Applying the same event twice must not increase position twice.

Keep a durable order record with at least these fields:

  • Your client order ID and the broker's order ID;
  • Instrument, side, submitted quantity, limit or stop price, and time in force;
  • Cumulative filled quantity, average fill price, and the IDs of executions applied;
  • Current broker status, last update time, and any pending cancel or replace request.

Expect duplicate and out-of-order updates. A websocket event can arrive before a REST response, and a reconnect can replay events.

Process incoming updates idempotently, retain raw messages for diagnosis, and use broker sequence numbers or timestamps where available. Do not let a later-arriving status overwrite a higher cumulative filled quantity with an older value.

For a timed-out submission, the result is unknown, not failed. Look up the client order ID or query open orders before submitting again. A second order with a new ID can double the intended exposure. A useful agentic paper trading environment should let a developer inject duplicate reports, delayed fills and lost acknowledgements to test this path.

Reconcile the live order state

Maintain a state machine that distinguishes pending submission, working, partially filled, pending cancel, pending replace, filled, cancelled, expired and rejected. Map the broker's statuses into these states without discarding the original status.

Some APIs report a partially filled order as cancelled when only its unfilled remainder was cancelled. The executions still count.

When a cancel is pending, keep the remaining quantity in your worst-case exposure calculation. When a cancel confirmation arrives, compare its cumulative fill quantity with the executions already applied.

If they differ, fetch order and execution history before allowing a replacement. Use a scheduled reconciliation as well as event-driven updates: for example, poll open orders every 2 to 10 seconds during active trading, then compare positions and executions at session end. Tune the interval to the broker's rate limits and the strategy's risk.

Reserve risk for both filled exposure and orders that could still fill. For a buy order, a simple conservative bound is current position plus the leaves quantity of every live buy, with sell orders handled according to the portfolio's netting and shorting rules.

Apply the same logic while a cancel is pending. Release the reservation only when the order is confirmed terminal and its final fills have been reconciled.

Classify rejections before retrying

A rejection is a reason to inspect the order, not an instruction to repeat it. Preserve the broker's code and text, then map them into a small internal taxonomy. Common categories include:

  • Permanent request errors: invalid symbol, unsupported order type, malformed price increment or missing required field. Fix the request; do not retry unchanged.
  • Risk or account controls: buying power, position limit, restricted instrument or account permission. Recalculate or escalate to a defined fail-closed path.
  • Transient service errors: rate limit, unavailable gateway or temporary connection fault. Retry only after checking whether the order was accepted despite the error.
  • Market-state errors: closed session, auction restriction or price band. Reassess eligibility and timing before creating a new order.

Use bounded retries with a backoff, such as 250ms, 500ms and 1 second, only for errors documented as transient. Set a maximum attempt count and a time limit. Never retry a rejection simply because its message contains the word "temporary".

If the response is ambiguous, query by client order ID first. Keep cancel rejects distinct from new-order rejects: a failed cancel can leave the original order live.

Price the recovery path

Replacement decisions have execution costs as well as operational risk. The ranges below are typical market ranges for liquid US listed equities, expressed in basis points of traded notional. They vary with spread, volatility, order size, venue, account schedule and time of day. They are planning assumptions, not audited results for any named firm.

Cost itemTypical rangeWhat to measure
Crossing the spread on a residual quantity1 to 5 bps in highly liquid names; 5 to 25 bps in thinner namesExecution price against the mid-price when the replacement decision was made
Adverse selection after a passive fill1 to 10 bps over the next 1 to 5 minutesMid-price movement after each fill, signed by order side
Explicit broker and venue charges0 to 3 bps equivalent for many retail or low-touch schedulesActual account charges per fill, converted to notional basis points
Market impact on a replacement0.5 to 10 bps for modest residual orders; potentially higher for larger participationImplementation shortfall against the decision price, grouped by order size and participation

Calculate costs on the residual, not the original order, and record the decision price before cancel or replace. Otherwise a strategy can appear to improve execution by excluding the unfilled quantity. Track reject rate, partial-fill rate, time to terminal state, duplicate-order incidents and implementation shortfall. Segment by instrument, order type and session; a blended average can hide a problem concentrated in opening minutes or less liquid names.

Run this week's checklist

Use one trading week to make the behaviour observable and repeatable. Run the checklist in a sandbox or paper environment before enabling any new recovery path in production.

  1. Write down the broker's documented order states and map each one to an internal state.
  2. Confirm which fields identify an order across reconnects and how the API identifies individual executions.
  3. Store raw order, execution, cancel and rejection messages with timestamps and correlation IDs.
  4. Test a partial fill followed by a cancel request, then deliver another fill before the cancel confirmation.
  5. Replay duplicate and out-of-order execution events. Assert that position and filled quantity change only once.
  6. Simulate a submission timeout after broker acceptance. Verify that the system queries by client order ID before resubmitting.
  7. Test each known rejection class. Confirm permanent errors do not loop and transient retries stop at the configured limit.
  8. Check that pending cancels and replaces retain worst-case exposure in risk calculations.
  9. Reconcile open orders, executions and positions after a forced disconnect and again at session end.
  10. Report fill, reject, retry and shortfall metrics by symbol, order type and session. Review anomalies before changing retry timing.

Keep the recovery policy conservative until these tests pass and the live data supports a change. This advice does not apply unchanged to multi-leg orders, such as options combinations, where one leg can fill without the other; those require explicit legging, hedge and unwind controls rather than a single-order state machine.

Frequently Asked Questions

What should a trading bot do after a partial fill?

Update position from the unique execution and calculate the remaining live quantity. Keep the unfilled amount reserved for risk until the broker confirms the order is terminal or a reconciliation establishes its state. Then decide whether to leave, cancel or replace it under the strategy's price and exposure limits.

Can a broker fill an order after a cancel request?

Yes. A cancel request can be pending while the original order remains executable, so further fills may arrive before the cancel confirmation. Treat the order as live for exposure calculations until its final state and fills are reconciled.

Should code retry a rejected order automatically?

Only for a rejection classified as transient, and only after checking that the broker did not accept the original order. Permanent request errors need correction, while account or risk rejections should follow a defined fail-closed path. Bound retries by both attempt count and elapsed time.

How do I prevent duplicate fills in an order handler?

Store each execution using the broker's stable execution identifier and apply it idempotently. Retain raw reports so duplicate and out-of-order events can be replayed in tests. If an API does not provide a reliable execution ID, define and test a broker-specific deduplication key.

How often should a bot reconcile open orders?

A typical starting point during active trading is every 2 to 10 seconds, subject to broker rate limits and the strategy's risk. Reconcile immediately after reconnects or ambiguous submission results, and compare orders, executions and positions again at session end. Faster strategies may need a shorter interval or a different broker-supported mechanism.