"Order sent" — but the broker never took it
Our bot announced that an order had been sent. The broker had rejected it. The bot was reporting what it tried to do, not what actually happened.
What went wrong
Our bot sent its usual alert: “order sent”. We relaxed. Later we found that the broker had rejected the order. The position the bot believed it had opened didn’t exist.
The bot was reporting what it had tried to do, not what had actually happened. It called the broker’s order API and announced success, without checking the broker’s reply.
What it cost, or could have cost
A missed trade at best. At worst, a strategy that believes it holds a position it doesn’t: it may skip the next entry, place an exit for a position that isn’t there, or leave you unhedged without knowing it. And a false “all good” message is worse than no message, because you stop looking.
Root cause
- “Sent” meant “we called the API”. It didn’t mean the broker accepted the order.
- The broker’s reply was ignored. A rejection comes back as a normal response with a status and a reason, not as a crash, so nothing in the bot noticed it.
- No follow-up check. Even an accepted order can still be rejected later, or sit unfilled. The bot never looked.
The fix
- Only the broker can say “sent”. The bot reports an order as placed only when the broker returns an order ID with a success status. Anything else is reported as a failure, with the broker’s reason.
- Two different alerts. “Order placed: ID …” when accepted; “Order attempted, REJECTED: reason” when not. A rejected order is the alert you most need to see, so it is never quiet.
resp = broker.place_order(order)
order_id = resp.get("order_id")
if resp.get("status") == "success" and order_id:
state.record_pending(order_id, order) # remember it before announcing it
alert(f"Order placed: {order.symbol} {order.side} {order.qty} id={order_id}")
else:
alert(f"Order attempted, REJECTED: {order.symbol} {order.side} {order.qty} reason={resp.get('message')}")
state.mark_failed(order) # the strategy must not assume it holds the position
- Confirm the final status. A few seconds after placing, the bot checks the order book for that order ID: filled, open or rejected. Only a fill updates the strategy’s position.
- Retry deliberately, not automatically. A blind retry after an unclear reply can create a duplicate order. The bot checks the order book first, and retries only if the order is definitely not there.
Checklist item
- Your bot treats an order as placed only when the broker returns an order ID with a success status, alerts every rejection with its reason, and confirms fills from the order book before updating its positions.
Was this useful?
More to say, or spotted a mistake? Write to [email protected]. We read everything.