One of my trades last month got roughly 15 times its intended risk allocation. Not a typo. Fifteen times. And the infuriating thing is that the backtest ran flawlessly. No warnings, no sizing errors, nothing.
This is Part 4 of the build-in-public series documenting my Binance futures bot. Part 3 covered the same-bar re-entry artifact that inflated backtest returns 30-50 times and is worth reading before this one if you're building your own backtester. This post is about the next layer of pain: the bugs that survive a clean, corrected backtest and only reveal themselves when real money is moving.
Three bugs. They hit in the same stretch. None of them are exotic or clever. They're the kind of thing that makes you feel dumb for a moment and then realize every bot builder probably hits them, because the design choices that lead to each one are completely intuitive when you're writing the code.
Bug 1: the shared-margin sizing footgun
The bot trades multiple symbols simultaneously, ETH, NEAR, SOL on a 4-hour cycle. Equal risk per symbol is the design intent: each gets its own wallet slice, and sizing is derived from that slice.
My original implementation did something simple: when it was time to size a position, it called the exchange for current free margin and calculated the position size from that number.
Sounds fine. Works perfectly in a backtest where trades happen sequentially. Falls apart badly live, where all three symbols can trigger in the same cycle.
Here's the sequence that caused the 15x mis-size:
The cycle starts. Symbol A triggers first, grabs a healthy chunk of free margin, and places its order. That order is now open, so the margin is committed and no longer "free." Symbol B triggers next. The bot asks the exchange for free margin. The number it gets back is the margin that Symbol A didn't use, basically the rounding leftover. Symbol B sizes off that tiny number and places a position that's a fraction of what it should be. Symbol C triggers. By now there's almost nothing left, and depending on timing, it either sizes to almost zero or the calculation produces a rounding error that lands a position ten or fifteen times the intended size.
The order of operations inside a cycle is arbitrary, it depends on which symbol's 4-hour candle closes first. So the "victim" of the mismatch rotates. I didn't notice the pattern immediately because any individual trade looked like it could be within normal variance for the leverage involved.
The fix is a one-time snapshot at cycle start. Before any orders go out, the bot reads total wallet equity once, divides by the number of symbols, and stores a per-symbol allocation. Every symbol sizes off its own slice, in isolation, regardless of what the other symbols do. No shared pool, no race condition. The live balance doesn't get re-queried during the cycle.
One live trade got roughly 15x its intended risk because of shared margin state. A cycle-start snapshot per symbol costs zero performance and eliminates the problem permanently.
The sizing footgun probably cost me around 6% of additional drawdown over the stretch it was running. Hard to pin exactly because some of those trades would have been losers anyway at any size, but the outsized positions on the bad ones definitely amplified the hit.
Bug 2: Binance -4120 and the algo order migration
Around December 2025, Binance migrated conditional order types to a separate endpoint. Specifically: STOP_MARKET, TAKE_PROFIT_MARKET, STOP, TAKE_PROFIT, and TRAILING_STOP_MARKET moved from POST /fapi/v1/order to a new POST /fapi/v1/algoOrder endpoint.
The old endpoint now rejects those order types and returns error code -4120. The error message is "Order type not supported for this endpoint. Please use the Algo Order API endpoints instead."
My bot enters a position first, then immediately places the bracket (stop and take-profit). After the migration, the bracket placement failed with -4120 every single time. The bot was written defensively enough that it detected the failure and closed the position to avoid being unprotected, so I didn't end up holding raw naked longs. But I also didn't hold any trades at all. The bot was technically running and technically correct about what it was doing. It just wasn't trading.
If your Binance futures bot started missing brackets with a -4120 error after late 2025, this is why. It's not your logic, it's a mandatory API migration that broke every bot that wasn't updated.
The fix is mechanical: rewrite the bracket placement to use POST /fapi/v1/algoOrder with algoType=CONDITIONAL and pass the same order parameters you were passing before. The payload structure is slightly different, the endpoint processes the same underlying logic. You can verify in the freqtrade/freqtrade issue #12610 on GitHub, which documented exactly this failure when it hit the broader bot community.
This bug had zero dollar cost because the defensive close-and-exit logic worked. But the opportunity cost was real: multiple setups triggered and were immediately exited before they could develop. I was running a market-regime bot with the ADX filter, and one of those missed setups was a clean trending move on ETH that the 4H system would have held for a meaningful return. It came and went while the bot was stuck in a loop of enter-fail-exit.
Bug 3: stale-candle stop rejection (-2021)
This one is subtler and, honestly, more interesting as an edge case.
The bot computes stop and take-profit prices from the signal candle. A 4-hour candle closes at some price, the UT Bot trailing level and the ATR bracket are calculated off that close, and the bot queues up an entry order.
Here's the timing problem. The candle closes, the signal is computed, but the bot doesn't place the order instantly. There's a brief gap, maybe 90 seconds to 2 minutes, between when the candle data is finalized and when the actual entry executes. In that window, the live price keeps moving.
For a long entry: say the close was at 100, and the stop is computed at 95 (5% below close). Two minutes later the live price has drifted to 94. Now the bot places the entry at 94 and sends a stop order at 95. The stop price is above the entry price on a long. That's not a pending stop, that's an order that would trigger immediately. Binance catches this and rejects it with error -2021: "Order would immediately trigger."
For a short entry the logic inverts, same failure mode. The stop lands on the wrong side of the live price because it was anchored to a price that no longer represents where the market is.
The fix: don't compute the stop from the closed candle. Anchor it to the live mark price at order placement time. The ATR bracket stays the same in dollar terms, but the reference price it offsets from is the mark price fetched seconds before placing the order, not the candle close from two minutes ago.
This is actually more correct behavior anyway. The candle close is the signal trigger, not the position entry price. Your risk bracket should be built off where you actually enter, and that's the live mark price when the order hits the exchange.
The -2021 error is well-documented in Binance's developer community. The Binance developer forum thread on "Order would immediately trigger" covers the underlying condition: a conditional order is rejected when the trigger price is already past the current market price, meaning the order would fill immediately, which conditional orders aren't designed to do.
What a clean backtest can't simulate
All three of these bugs are invisible in any backtest, corrected or otherwise. A backtest fills instantly at a clean price, sizes from an infinite ledger, uses a single endpoint abstraction, and doesn't model the gap between candle close and order placement. It has no concept of what happens when orders interleave within a cycle.
Part 3 of this series covered how a bad backtest can also lie to you with look-ahead artifacts. But this is the companion problem: even a good backtest has no way to catch multi-symbol margin contention, API endpoint migrations, or 90-second price drift between signal and execution.
The only way to find these bugs is to go live with a small account and watch carefully. Which is what I did. Expensively, on the sizing one. Cheaply but frustratingly, on the other two.
The bot is patched now. All three fixes are in. The next entry in this series will cover the ADX regime filter in detail, specifically why detecting sideways markets and doing nothing is the actual core of the edge here, not the entry logic. If you want the earlier parts of the story, the full series is on the journey page.
And if you're not building your own bot, that's probably the right call for most people. The bot-match quiz is worth running before you spend six months on Python. The Bybit built-in bot suite handles the API plumbing for you, which means none of these three bugs could have hit you there.
These bugs get found, documented, and fixed. That's the job.
