A partial fill is an order execution in which only some of the requested quantity trades.
That definition hides the hard part: the unfilled quantity may still be live, a cancellation may race with another fill, and a rejection may arrive after your system has timed out. Code that treats an order as a single request followed by a single answer can duplicate exposure or leave risk untracked.
Represent orders as changing state
An API acknowledgement usually means the broker or venue received the request. It does not mean the order has filled, or even that it has passed every validation check. Build a state machine around events and snapshots, not around the function call that sent the order.
A practical order lifecycle includes pending submission, accepted or working, partially filled, filled, pending cancellation, cancelled, rejected and expired. Some APIs use different labels. Map their statuses into your own small set, while retaining the original status and response for diagnosis. Keep an unknown state too. A timeout is not proof that the order failed.
Track the requested quantity, cumulative filled quantity, remaining quantity and average fill price. For each execution event, also store its execution ID, quantity, price, timestamp and fees where supplied. Check that cumulative filled quantity never exceeds the request, and calculate remaining quantity from confirmed fills rather than a local estimate.
Event order can be awkward. A snapshot may show a newer state than a delayed event, while duplicated events are common enough to plan for. Deduplicate using the broker's execution or event identifier.
If the API offers sequence numbers, record them and detect gaps. When events disagree or arrive out of order, query the broker's current order and execution history before changing exposure.
Keep order records separate from positions
An order describes intent. A position describes confirmed exposure. Update positions from executions, not from submission acknowledgements, and do not infer a fill from a cancelled status. A cancellation can confirm that the remaining quantity is no longer working while leaving earlier partial executions intact.
Persist each order with an internal identifier and a client-generated idempotency key if the API supports one. Store the broker's order ID as soon as it is available. If the connection drops after submission, look up the order by client ID or reconcile open orders before resubmitting. Sending the same request again without checking can create two live orders.
Use a ledger of execution events to build positions and cash balances. This makes corrections easier when an API reports a trade bust, fee adjustment or execution update.
Preserve the original event and append the correction rather than silently overwriting history. That gives the risk process a traceable view of what the system believed at each point.
Also keep strategy intent distinct from broker orders. One strategy instruction might result in several child orders, and one child order can produce several executions.
Link them. Then the system can answer three separate questions: what quantity did the strategy request, what quantity remains working, and what quantity actually traded?
Choose what to do with the remainder
A partial fill is not automatically a reason to send another order. First establish whether the original remainder is still working. Then apply a policy based on the strategy's urgency, risk limit and order type. A passive limit order may wait for a fill; a marketable order may complete quickly but pay the spread and take available liquidity.
Common policies include:
- Wait: Leave a resting remainder live until a defined deadline or price condition. This may reduce spread costs, but keeps exposure to a later fill.
- Cancel and replace: Cancel the old remainder, confirm its final status, then submit a new order for the verified residual quantity. This gives price control, but adds latency and a cancellation race.
- Complete immediately: Send a marketable order for the residual quantity when the strategy has a firm completion deadline. Set a maximum price deviation and maximum total quantity.
Order instructions matter. An immediate-or-cancel order may fill some quantity and cancel the rest. A fill-or-kill order should fill in full or not at all, subject to the venue's rules. A good-till-cancelled order can remain live beyond the strategy's intended holding window unless your system actively manages it.
For cancel-and-replace logic, assume the old order can fill while cancellation is pending. Do not submit the replacement using a stale residual. Wait for the cancel acknowledgement and reconcile executions that occurred during the race. If the API cannot give a reliable final status, query order history and keep the strategy's exposure cap active while state is uncertain.
Round quantities to the instrument's permitted increment and validate minimum order sizes before sending. For example, if a venue accepts increments of 0.01 units, a calculated remainder of 0.007 cannot be submitted as-is.
Decide whether to retain, round down or close through a separate policy. Never round up silently if that can breach the position limit.
Classify rejections before retrying
A rejection says the request was not accepted in its current form. The reason determines what to do. Invalid precision, unsupported order type, insufficient margin and a price outside venue limits usually need a corrected request or a stopped strategy. Repeating the same payload will not fix them.
Temporary failures need different handling. Rate limits, brief service errors and network timeouts may clear, but a timeout leaves the submission outcome unknown. Reconcile first.
For a response that explicitly identifies a temporary failure, use bounded exponential backoff with jitter, respect any retry-after instruction and cap the number of attempts. As a starting point, some systems use delays such as 250 ms, 500 ms and 1 s, then stop and alert; tune these to the API and strategy deadline.
Keep rejection handling explicit in code. Map raw broker codes to categories, but retain the raw response because meanings differ across brokers. A post-only order that would immediately trade may be rejected by design. A market-closed response may mean the strategy should wait for the next session, not retry every second. A margin rejection should trigger a risk check, not a smaller order chosen without a defined rule.
Useful controls include:
- Never retry a permanent validation or risk rejection unchanged.
- Use an idempotency key where supported, and reconcile uncertain submissions before retrying.
- Limit attempts per order and per instrument, with a circuit breaker for repeated API failures.
- Log the payload, response, retry decision and resulting order state with timestamps.
Measure the cost of incomplete execution
Partial execution has a cost beyond the commission. A residual order can miss a price move, cross a wider spread later or create extra fees through repeated submissions. The ranges below are typical market ranges for orientation, not fixed quotes. Costs vary by instrument, venue, account tier, order size, time of day and market conditions.
| Cost source | Typical market range | What code should record |
|---|---|---|
| Spread when crossing in liquid US equities | About 1 to 5 basis points; thin or volatile names can reach 10 to 100 or more | Decision price, execution price and filled quantity |
| Major FX pair spread | About 0.1 to 1.5 pips in liquid hours; 2 to 10 or more off-hours | Bid, ask, order time and fill time |
| Crypto spot trading fee | About 0 to 10 basis points maker; 5 to 20 basis points taker | Fee amount, currency and maker or taker flag |
| Slippage on marketable orders | About 0 to 5 basis points in liquid conditions; 5 to 50 or more in thin or stressed markets | Reference price at decision time and each execution price |
For each parent instruction, compare the decision price with the volume-weighted execution price, including fees and any later fills. Track fill ratio, time to first fill, time to completion, cancellation rate and rejection rate.
Segment these by instrument, order type, trading hour and API response category. An overall average can hide poor results in one session or one order type.
Do not treat a rejected order as a zero-cost outcome. It may leave the intended position unfilled, or lead to a later replacement at a worse price.
Record the rejected quantity and the eventual strategy outcome. A paper-trading environment such as agentic paper trading can help exercise event handling before live orders, but simulated fills do not reproduce every queue position or venue rejection.
Test failure paths before removing supervision
Test the order manager as a sequence processor, not just as a wrapper around an API. Feed it duplicate fills, delayed acknowledgements, partial execution followed by cancellation, rejection after a timeout, and a fill that arrives while cancellation is pending. Assert the resulting position and remaining order quantity after every event.
A useful staged test is to run deterministic event fixtures first, then paper trade against the broker interface, then use tightly limited live orders with an operator watching. Paper environments can validate your code's state transitions, but their rejection reasons, latency and fill allocation may differ from live markets. Treat each mismatch as a test case rather than assuming the simulation is a venue guarantee.
- Define the internal order states and map every broker status and rejection code to one.
- Persist order IDs, client IDs, execution IDs, timestamps, quantities, prices and raw responses.
- Test duplicate, delayed and out-of-order events, plus cancel-and-fill races.
- Verify that uncertain submissions trigger reconciliation instead of blind resubmission.
- Set maximum order quantity, position exposure, retry count and time-to-live limits.
- Alert on unknown states, repeated rejections, event gaps and differences between broker and internal positions.
This week, write and run one fixture where a partial fill arrives after a cancellation request, then verify that the position and residual order match the broker's final state.
Frequently Asked Questions
What should a trading bot do after a partial fill?
First confirm whether the unfilled remainder is still working. Then apply a defined wait, cancel-and-replace or completion policy, while keeping the position limit active. Do not resubmit the original quantity.
Can I retry an order after an API timeout?
Not until you establish whether the broker accepted it. Look up the order by client ID or reconcile open orders and executions. A timeout does not prove that submission failed.
How should code handle a fill received after a cancel request?
Treat cancellation as pending until the broker confirms its final status. Apply the execution to the position, recalculate the residual and only then decide whether another order is needed.
Which order rejections should a bot retry?
Retry only failures classified as temporary, and use bounded backoff while respecting rate limits. Correct validation errors or stop on risk rejections rather than resending the same request.
How do I test partial fills and rejections safely?
Use deterministic event fixtures for duplicate, delayed and conflicting events, then exercise the broker interface in paper trading. Compare simulated behaviour with tightly limited live orders before removing supervision.