VIZANIXTrading Software Development
Bybit APIExecution8 min read

Partial fills: the quiet way trading bots lose money

A limit order that fills halfway leaves you with a position nobody planned, a stop sized for a position you do not have, and an average price your code has not recomputed.

Vizanix engineering · about the author

ARTICLE
8 minreading time
SECTION
Bybit API
PUBLISHED
2026-08-28
CHAPTERS
6
READ NEXT
3
LANGUAGE
written in English
An engineering breakdown, not a rewrite of the docs.
PARTIAL FILLSAVG PRICEGTCFOKBRACKET

Ask a developer how their bot handles a partial fill and you learn most of what you need to know about it. The answers cluster into three groups: those who have never thought about it, those who wait for the order's time-to-live to expire, and those who have been burned.

What actually happens

You place a limit buy for 10 units. The market touches your price, takes 3, and moves away. You now hold 3 units and have a resting order for 7. Three things are true at once, and each one breaks a naive bot:

  • You have an unplanned position. The strategy sized risk for 10 units. You have 3. Your stop distance, computed from the intended size, is now wrong in the safe direction — but your take-profit legs, sized as percentages of 10, are wrong in a way that will try to sell units you never bought.
  • You have exposure to the remainder. The 7 can fill at any moment, including at a price where the setup is no longer valid.
  • Your average price is not your limit price. If the remainder fills later at a different level, the position's average moves, and every level computed from entry — stop, targets, break-even — has to move with it.

The wrong fixes

Waiting for the TTL. The most common approach: let the order live out its time-to-live and deal with whatever position exists at the end. This leaves the filled portion completely unprotected for the whole window. If the TTL is one candle, that is one candle of naked risk on every partial fill.

Cancelling and forgetting. Cancelling the remainder is right; forgetting to bracket the filled part is how a position ends up with no stop at all. This is the failure mode that shows up as “the bot had a position I did not know about”.

Sizing the bracket from the intended quantity. If the take-profit legs were computed as fixed quantities from the planned 10 units, submitting them against a 3-unit position produces reduce-only rejections at best and, on a venue that allows it, an unintended short at worst.

What we do

In our Bybit executor the rule is explicit: a partially filled entry cancels its remainder immediately, without waiting for the TTL. The filled portion is then handled by configuration — either closed at market, or bracketed.

Closing at market sounds wasteful and often is not. If your setup depended on getting size at a level and you got a third of it, the trade you are now in is not the trade the strategy chose. Paying a taker fee to exit a position you did not want is cheaper than carrying it with a stop that was designed for something else.

When the filled portion is kept, everything downstream is recomputed from the actual filled quantity and the actual average price — never from the intended ones. Take-profit legs are shares of what exists, not of what was planned.

python
def on_partial(order, position):
    # 1. Kill the remainder first. Nothing else is safe while it can still fill.
    exchange.cancel(order.id)

    filled, avg = order.cum_exec_qty, order.avg_price
    if filled <= 0:
        return

    if config.reject_partial_entry:
        exchange.close_market(position.symbol, filled)   # not the trade we chose
        return

    # 2. Bracket from what exists, never from what was intended.
    stop = level_from(avg, config.sl_k)
    legs = [(share * filled, level_from(avg, k)) for k, share in config.tp_legs]
    exchange.place_bracket(position.symbol, filled, stop, legs)

GTC or FOK

Fill-or-kill removes the problem by refusing anything less than the full quantity. That is a real option and it has a real cost: you pay taker fees, and you miss trades where the full size was not available at your price but most of it was.

GTC limitFOK
Partial fillsPossible — must be handledImpossible by construction
Fee sideMaker on the resting portionTaker on the whole order
Missed entriesFewerMore — all-or-nothing
Code complexityHigherLower
SuitsPatient entries, wider spreadsTight windows, small size

There is no universally right answer. There is a right answer per strategy, and it should be a configuration flag rather than an assumption baked into the execution layer.

Reconciliation catches what the stream missed

Fill notifications arrive over the private execution stream. If that stream dropped — see WebSocket and reconnect — you can hold a partially filled position and not know it.

This is why reconciliation after every reconnect is not optional. Compare the exchange's position and open orders against your own, and treat any difference as authoritative in the exchange's favour. The most expensive partial fill is the one your bot never heard about.

A short checklist

  1. Cancel the remainder immediately on partial fill — do not wait out the TTL.
  2. Recompute average price and quantity from execution reports, not from intent.
  3. Size every exit from the filled quantity.
  4. Decide explicitly whether a partial entry is kept or rejected, per strategy.
  5. Reconcile against the exchange after every disconnect.
  6. Log the partial-fill rate. A rising rate means your limits are sitting too far out.

This article describes engineering practice. It is not investment advice. Vizanix develops software and does not promise trading returns.

Blog

Read next

Want this running for you?

We write about what we build. If you need it built, get in touch — scoping is free.

Brief

Get a project estimate

Four questions and your contact. No deposit required to talk — if the job is not a fit, we say so straight away.

01What do you need
02Exchange
03Market
04Strategy
05Contacts

Prefer to write directly? Telegram @vx_ceo

Discuss a system