Idempotent order placement

Chapter 20 flagged this: never blindly retry a place_order() call that timed out — you can't tell if the order reached the exchange before the connection dropped. This chapter builds the actual pattern.

The problem

try:
    order_id = kite.place_order(**params)   # times out — did it place or not?
except NetworkException:
    order_id = kite.place_order(**params)   # DANGER: may create a duplicate order

Solution: use tag, and check-before-resend

import uuid

def place_order_idempotent(kite, max_attempts=2, **order_params) -> str:
    client_ref = order_params.pop("tag", None) or str(uuid.uuid4())[:20]
    order_params["tag"] = client_ref

    for attempt in range(max_attempts):
        try:
            return kite.place_order(**order_params)
        except NetworkException:
            # Before retrying, check if an order with this tag already exists
            existing = [o for o in kite.orders() if o.get("tag") == client_ref]
            if existing:
                logging.info(f"Order with tag {client_ref} already placed, not resending")
                return existing[0]["order_id"]
            if attempt == max_attempts - 1:
                raise
            time.sleep(1)

Why tag, not order parameters, as the dedup key

Two legitimately different orders can have identical exchange/symbol/quantity/price/type (e.g. two separate signals both wanting to buy 10 INFY at market, minutes apart) — you can't dedup on order content alone without also risking blocking a valid second order. A client-generated unique tag, set *before* the network call and checked after a failure, is the correct idempotency key because you control its uniqueness independent of order content.

Kite Connect specifics

tag is limited to alphanumeric characters and a max length (check current docs, historically 20 chars) — a full UUID may need truncation; ensure your truncation still preserves enough entropy to avoid collisions within a single trading day.

This matters more than it seems

A duplicated order isn't just an annoyance — it's an unplanned position at 2x (or more) your intended size, with its own margin consumption and risk exposure, created by a network hiccup rather than a strategy decision. This is exactly the kind of bug that's rare enough to not show up in early testing and severe enough to matter a lot when it does.

Next: 059 — Place a GTT order