Freeze quantity and order splitting
Exchanges cap the maximum quantity allowed in a single order for F&O contracts — the "freeze quantity" (e.g. NIFTY options might have a freeze limit of 1800 units — this changes periodically, always check current values, never hardcode). An order above this limit is rejected outright, not partially accepted.
def get_freeze_qty(nfo_df: pd.DataFrame, tradingsymbol: str) -> int:
row = nfo_df[nfo_df.tradingsymbol == tradingsymbol].iloc[0]
# freeze quantity isn't always in the instrument master directly;
# NSE publishes it separately per contract — fetch/maintain this
# from NSE's published freeze-quantity list, updated periodically.
return FREEZE_QTY_LOOKUP.get(row["name"], 1800)
Splitting a large order into freeze-compliant chunks
def split_order_by_freeze_limit(total_qty: int, lot_size: int, freeze_qty: int) -> list[int]:
max_lots_per_order = freeze_qty // lot_size
chunk_qty = max_lots_per_order * lot_size
chunks = []
remaining = total_qty
while remaining > 0:
take = min(chunk_qty, remaining)
chunks.append(take)
remaining -= take
return chunks
def place_large_fno_order(kite, exchange, symbol, total_qty, lot_size, freeze_qty, **order_kwargs):
chunks = split_order_by_freeze_limit(total_qty, lot_size, freeze_qty)
order_ids = []
for qty in chunks:
oid = kite.place_order(
exchange=exchange, tradingsymbol=symbol, quantity=qty, **order_kwargs
)
order_ids.append(oid)
time.sleep(0.3) # avoid rate-limit bursts, chapter 20
return order_ids
Why this matters even for "small" retail accounts
Freeze quantity limits apply per order regardless of account size — a trader running an index-option strategy with even a moderate lot count (not necessarily huge capital) can exceed the freeze limit on liquid near-the-money strikes during high-conviction trades. Not handling this means the order is rejected in full, and a naive bot might interpret that as "no liquidity" or some other wrong diagnosis instead of the actual cause.
Track chunk results independently
Each chunk is a separate order with its own order_id, fill status, and potential partial fill (chapter 56) — reconcile total filled quantity across all chunks, don't assume they all filled identically.