Handle partial fills

A limit order for 100 shares can fill as 30, then 45, then 25 across separate matches, potentially over several seconds or minutes, before reaching COMPLETE — or it might end the day CANCELLED with only 75 filled if you cancel or it expires with quantity remaining.

Detecting partial state

order = kite.orders()  # find the specific order
o = next(x for x in order if x["order_id"] == order_id)

print(o["quantity"], o["filled_quantity"], o["pending_quantity"], o["status"])
# e.g. 100, 75, 25, "OPEN"  -> partially filled, still working
# e.g. 100, 75, 25, "CANCELLED" -> partially filled, then stopped

Why your strategy logic must handle "75 of 100" as a real, valid outcome

A naive bot that only reacts to status == "COMPLETE" will never notice a CANCELLED order that actually acquired a real position of 75 shares — it'll proceed as if it holds zero, then mis-track P&L and risk from that point forward. Always compute position impact from filled_quantity, never from the originally requested quantity.

def net_position_delta(order: dict) -> int:
    sign = 1 if order["transaction_type"] == "BUY" else -1
    return sign * order["filled_quantity"]

Reconciling partial fills against your intended risk

If your position-sizing logic (chapter 71) computed a stop-loss and target based on an intended quantity of 100, but only 75 filled, your protective SL-M order (chapter 51) must be placed for 75, not 100 — sizing the exit order incorrectly either leaves shares unprotected or attempts to sell more than you hold.

def place_protective_orders_after_fill(kite, order_id, exchange, symbol, sl_trigger, product):
    filled = kite.order_history(order_id)[-1]["filled_quantity"]
    if filled == 0:
        return None
    return place_protective_stop(kite, exchange, symbol, filled, sl_trigger, product)

Multiple trades, one order — use the trade book for exact fill prices

Chapter 19 already covers this: order_trades(order_id) gives you every individual fill, useful when the size-weighted average price (not just the order-level average_price) matters for precise cost-basis tracking.

Next: 057 — Handle order rejections