A partial fill is an execution of less than the order's requested quantity.
That definition hides the hard part: a broker's order status is not the same thing as the sequence of executions. A cancel request can be pending while another fill arrives. A stream event can arrive after a REST response, or arrive twice after reconnect. Treating each status as a final answer can leave local position, cash and working quantity wrong.
Model executions, not status labels
Keep an append-only record of order events and execution events. An accepted acknowledgement means the broker accepted the request for processing, not that any quantity traded.
Each execution should update filled quantity and position immediately, even if the order remains open. Store the broker's execution identifier, order identifier, quantity, price, fees, event time and receive time.
Calculate remaining quantity from the order's original quantity minus the sum of unique execution quantities. Do not subtract a partial fill from the latest reported cumulative quantity and also add the individual execution. Brokers may send both forms. Pick one authoritative update path, or reconcile the two, and deduplicate executions by their stable identifier.
Use explicit states such as pending submission, working, partially filled, cancel pending, cancelled, filled and rejected. A state transition should not erase an execution. For example, a cancel acknowledgement can close the residual quantity while a late-arriving fill event still changes the final filled quantity. Apply the execution, then recompute the order and position from the event ledger.
- Keep order intent, broker order and individual executions as separate records.
- Track both event time and local receive time to identify delayed or out-of-order messages.
- After reconnect, fetch open orders and recent executions, then reconcile by broker IDs before resuming new submissions.
This design costs storage and reconciliation work. It avoids the more expensive alternative: silently trading from a local position that never incorporated a real fill.
Choose metrics that preserve the denominator
One fill ratio cannot explain execution quality. For each logical order intent, record requested quantity, executed quantity, terminal reason and time in force.
Report quantity-weighted fill ratio as total executed quantity divided by total requested quantity. Also report the distribution by order, because a few large orders can conceal many small orders that never filled.
Keep attempts separate from intents. If an order is rejected and then resubmitted, counting both as independent trading decisions inflates the denominator. Report rejection rate per submission attempt for API health, and rejected-intent rate for strategy impact. Label each rate. Include only comparable order types, instruments, sessions and time-in-force windows in a benchmark.
- Measure time to first fill and time to terminal state, using monotonic local timestamps for elapsed time.
- Measure residual quantity at cancellation, plus residual exposure in currency or risk units.
- Measure cancel latency from request send to broker acknowledgement, and count fills received after that acknowledgement.
- Measure slippage and short-horizon markout against the same reference price and timestamp for filled quantity only.
- Break rejection counts out by validation, buying power, risk limit, venue, rate limit and expiry.
Report median and 95th percentile latency, not only an average. Include sample counts. A 500 millisecond median from twelve orders is weak evidence beside a 95th percentile calculated from thousands.
Compare against typical operating ranges
The figures below are typical working ranges for liquid instruments, not audited results for a particular broker or venue. Order type, volatility, session, routing and the definition of each event can move them substantially. Treat them as prompts for investigation, not service guarantees.
| Metric | Typical range | Scope | What to inspect |
|---|---|---|---|
| Time to first fill | 50 ms to 2 s | Marketable orders in liquid markets | Market conditions, routing and timestamp source |
| Quantity filled within 60 seconds | 10% to 80% | Passive limit orders; some receive no fill | Queue position, price distance and volatility |
| Rejected submissions | 0.1% to 2% | Controlled order flow across mixed causes | Reject reason, instrument and strategy version |
| Cancel requests followed by a fill | 0.1% to 5% | Cancel attempts during live trading | Cancel latency and venue event ordering |
These ranges are broad by design. A rejection rate of 0.1% can still hide a serious buying-power error if the affected orders are large. A high passive fill ratio can mean the limit is too aggressive, not that execution is efficient. Pair each rate with quantity, notional and loss or risk impact.
Make rejection handling safe to retry
A rejection is an event with a cause, not a generic failure signal. Parse broker codes into stable internal categories while retaining the original code and message. Validation errors usually need a corrected request. Insufficient buying power needs a fresh account check and a sizing decision. A rate-limit response may permit a delayed retry. A risk-limit rejection should normally stop that intent and raise an alert rather than trigger a smaller order automatically.
Before retrying, establish whether the first request created an order. A timeout after submission is ambiguous: the broker may have accepted it even though the client received no acknowledgement.
Query by client order ID or reconcile recent orders before sending another request. Use a unique client order ID per intent and attempt, and preserve the mapping between them.
For partial fills, make the retry quantity equal to the confirmed residual, not the original quantity. Do not submit that residual while a cancel is still pending unless the broker provides an atomic replace operation with documented semantics. Otherwise, both the old and new orders can execute. Set a maximum number of attempts and a deadline; retries without either can turn a transient fault into repeated exposure.
- Retry only errors classified as transient, with capped exponential backoff and jitter.
- Do not retry malformed orders, permission failures or risk-limit rejections unchanged.
- On an ambiguous submission, reconcile first. On an execution event, update position before deciding whether any residual should be replaced.
Log the decision as well as the broker response: retry reason, prior order ID, residual quantity, delay and resulting exposure. This makes it possible to distinguish a broker rejection from a policy that repeatedly generated invalid requests.
Test the races before unattended deployment
Build tests around event sequences, not just isolated responses. Feed the same execution twice, deliver a fill before an acknowledgement, deliver a fill after a cancel acknowledgement, and disconnect the stream between a partial fill and its update. After every sequence, assert that unique executed quantity, open quantity, cash and position agree with the event ledger. The same test should pass after process restart and replay.
Exercise different failure policies. A transient rate-limit response should wait and retry within a deadline.
A buying-power rejection should refresh account state before any new sizing decision. An ambiguous timeout should trigger lookup, not blind resubmission. A cancel timeout should keep the order in a pending state until reconciliation resolves it.
Paper environments help reproduce these flows, but their event timing and queue behaviour may differ from live venues. An agentic paper-trading environment can support repeated tests of order logic before capital is at risk. If you use a paper-trading subscription, including plans from $9.99 a month, compare its event model with the broker interface rather than assuming matching execution behaviour.
Set alerts on changes from your own baseline, split by instrument and order type. Watch rejection causes, duplicate executions, cancel-to-fill races, residual exposure and reconciliation breaks.
A single daily percentage can conceal a sudden failure in one strategy or session. Check your own last 30 days of logs for fills arriving after cancel acknowledgement, and write down the count, even if it is 0.
Frequently Asked Questions
How should a trading system calculate partial fill quantity?
Sum unique execution quantities for the broker order, using stable execution IDs to deduplicate repeated events. Calculate remaining quantity as original quantity minus confirmed executed quantity, then reconcile that result against broker-reported cumulative quantity.
Should an automated strategy retry a rejected order?
Only retry causes classified as transient, such as a rate limit, and use a capped delay and attempt limit. For an ambiguous timeout, look up the order before resubmitting. Do not repeat a risk-limit or malformed-order rejection unchanged.
What is a cancel-to-fill race?
It occurs when an execution reaches the system while a cancel request is in flight, or after the client has received a cancel acknowledgement. Record both events and update position from the execution; do not assume the cancel removed quantity that had already traded.