What is an orphan order in crypto trading?
An orphan order is a stop loss (or take profit) order that gets left behind on the exchange after the position it was protecting has already been closed.
It sits there, invisible, waiting. When the price hits its trigger, it opens a new position you never intended.
This happened to me. Here’s the full story.
How did the orphan order happen?
The sequence:
- Bot opens a SHORT position on ENJ
- Places a STOP_LIMIT order as stop loss (algoId =
1000001302050842) - Binance algo API has a hiccup — the bot doesn’t get a valid response
- Bot thinks the SL wasn’t placed, so it places a second SL (new algoId)
- Now there are two SL orders on the exchange for one position
- Bot closes the position normally via trailing stop
- Bot cancels the SL — but only the second one (the last ID it saved)
- First SL (algoId
1000001302050842) is still live on the exchange - Price later hits the orphaned SL trigger
- Unintended position opens
Why didn’t the cleanup catch it?
My bot had a _cancel_all_open_orders function that was supposed to clean up all orders for a symbol after closing a position. It calls fapiPrivateGetOpenAlgoOrders to find any remaining algo orders.
The problem: the algo order query also failed, and the error handler was:
|
|
Silent failure. The query failed, the orphan survived, and nobody knew until it triggered.
How did I fix it?
Three changes:
1. Retry algo order queries
|
|
2. Log every cancel failure
No more except: pass. Every failed cancel gets logged so I can investigate.
3. Simplified symbol matching
The algo order API returns raw symbols (ENJUSDT) while ccxt uses formatted symbols (ENJ/USDT). My matching logic was overcomplicated and sometimes missed matches. Simplified to direct raw symbol comparison.
What are the warning signs of orphan orders?
Watch for these in your logs:
- Unexpected positions appearing without entry signals
- ReduceOnly rejections — an orphaned order triggering on a closed position
- SL placement count > position count — more stops than positions means duplicates exist
- Algo API errors followed by silence — the dangerous
except: passpattern
How do you prevent orphan orders in trading bots?
-
Never silently swallow API errors.
except: passon exchange operations is a guaranteed future disaster. -
Retry critical operations. Algo order queries and cancels deserve at least 2 attempts with a short delay.
-
Cancel ALL orders for a symbol, not just the last ID. Query
fapiPrivateGetOpenAlgoOrders, filter by symbol, cancel everything. -
Periodic audit. Every few minutes, compare your state file against actual exchange positions and open orders. Flag any mismatch.
-
Use
reduceOnlyon all SL/TP orders. This limits the damage — an orphanedreduceOnlyorder on a closed position gets rejected instead of opening a new trade. But don’t rely on this alone; some edge cases bypass it.
What did this bug cost?
In my case, the orphan was caught before it caused significant damage. But the AGT incident the day before — where a market close failed and left a position unmanaged — cost -121U in unrealized losses before I manually intervened.
These aren’t strategy failures. They’re infrastructure failures. The strategy was right. The execution had gaps.
The scariest bugs in trading bots aren’t the ones that lose money on bad trades. They’re the ones that make trades you never asked for.
Related:
- Binance API Gotchas — More exchange API traps
- What Happens When Your Bot Crashes at 3 AM — Crash recovery systems
- Stop Loss Implementation — STOP_LIMIT deep dive