Learn / Exchange APIs

Order lifecycle and partial fills

Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs before acting on a number.

Code written as though an order is either filled or unfilled works until the first partial fill, and then produces a position that does not match anything it believes.

Partial fills are normal. Treating them as an edge case is the bug.

The states

StateMeaning
SubmittedSent, outcome unknown
Accepted / openResting on the book
Partially filledSome quantity executed, remainder resting
FilledFully executed
CancelledWithdrawn; may have been partially filled first
RejectedRefused, nothing executed
ExpiredTime in force elapsed; may have been partially filled first

Note that cancelled and expired do not mean nothing happened. An order can fill 40%, rest, and then be cancelled. Your position is 40% of what you intended and your code, if it reads “cancelled”, may believe it is zero.

CCXT surfaces the distinction as OrderNotFound, OrderNotCached, OrderNotFillable and OrderImmediatelyFillable under InvalidOrder — see CCXT’s common errors.

Why partial fills happen

Insufficient depth at your price. Your size exceeds what rests there — see spread, depth and what liquidity actually costs you.

IOC by design. Immediate-or-cancel fills what it can and cancels the rest. Partial is the expected outcome, not an anomaly.

A resting limit order partially taken, with the remainder still resting.

Large orders in thin markets, which is the case that also produces the worst slippage.

The three things that break

1. Position tracking. Code assuming full fills carries a wrong position size from that point forward. Every subsequent calculation — exposure, PnL, risk — is wrong, and nothing errors.

2. Protective orders. You entered 1.0 and got 0.6, then placed a stop for 1.0. Depending on the venue that is rejected, reduces oddly, or opens a short for the excess. reduce-only on protective orders prevents the worst version — see order types explained.

3. Average entry price. Fills at different prices mean your entry is a weighted average, not the price you asked for. Your R multiple is computed from the wrong number, and the error propagates into every statistic.

Handling it

Read filled quantity, never assume it. After any order operation, the question is “how much filled” rather than “did it fill”.

Size protective orders from actual position, not intended position. Query the position, place the stop against that. This single habit removes most of the damage.

Decide a partial-fill policy in advance:

  • Accept and adjust — keep what filled, size the stop to it. Simple, usually right.
  • Complete the fill — chase the remainder. Costs more and may not fill.
  • Unwind — close the partial, start over. Two rounds of fees.

Choose per strategy, and write it down. Deciding while holding an unexpected position is not a good time.

Treat entry-plus-stop as one unit. Either both exist or the position is unwound. A filled entry with no stop is the failure that monitoring exists to catch.

Where it compounds

With cancellation. Cancelling a partially-filled order leaves the filled part. Code that treats cancel as “nothing happened” now holds a silent position.

With retries. After an ambiguous failure, a partial fill may exist. Retrying adds to it rather than replacing it — which is why reconciliation means querying fills and position, not just checking whether an order exists.

With scaling out. Partial exits on top of partial fills make the position history genuinely complicated — see what partial exits do to your expectancy.

With minimum sizes. A partial fill can leave a remainder below the venue’s minimum notional, which you then cannot close as a separate order — symbol filters and minimum order sizes. That is an annoying position to be stuck in and it is avoidable by not sizing close to the minimum.

For AI-assisted setups

Partial fills are an unusually good example of why a model needs current state rather than conversation history.

A model that “knows” you entered 1.0 because that is what the plan said will reason about a position you do not have. It cannot detect the discrepancy, because from its side nothing went wrong — see how LLM tool calls go wrong on orders.

The fix is architectural: re-fetch position state before any reasoning that depends on it, rather than carrying it forward in context.

FAQ

What is a partial fill?

An order that executed for less than its full quantity, with the remainder either still resting or cancelled. It is normal rather than exceptional — a consequence of insufficient depth at your price, or of an immediate-or-cancel order behaving exactly as specified.

How do I handle partial fills in a trading bot?

Read the actual filled quantity after every order operation rather than assuming a full fill, size protective orders from the position you actually hold, and decide a policy in advance for the remainder — accept, chase, or unwind. Treating entry and stop as a single unit that either completes or unwinds prevents the worst outcome.

Can a cancelled order still have filled?

Yes, and this is the case that breaks naive code. An order can execute part of its quantity, rest, and then be cancelled — so “cancelled” means the remainder was withdrawn, not that nothing happened. Always check filled quantity rather than inferring it from status.

Why is my stop-loss rejected after a partial fill?

Usually because it was sized to the intended position rather than the actual one, so it exceeds what you hold. Depending on the venue it is rejected, reduces oddly, or opens an opposing position for the excess. Sizing stops from the queried position and setting reduce-only prevents all three outcomes.