A stop-loss at -7.5% sounds like a hard guarantee. It isn't, unless the price it's measured from is the price your order actually executed at — not the price the bot glanced at a few seconds, or a few minutes, earlier.
This gap — between the price a bot decides on and the price it actually gets — is one of the most common bugs I run into auditing live trading systems. It's not exotic. It doesn't look like a bug when you read the code casually. It only shows up when you trace one specific value across the full path from signal to fill.
The pattern
Almost every bot has a moment where it reads a price, makes a decision, and then waits — for an order to route, a swap to confirm on-chain, a limit order to fill. The wait can be 200ms or it can be five minutes. Whatever price the bot used to set its protective stop-loss/take-profit was captured before that wait, not after.
If nothing re-reads the price once the position is actually open, the "protection" is anchored to a number that may no longer describe reality.
Case 1: the swap that outran its own stop-loss
In a Solana DEX trading bot I reviewed, the stop-loss/take-profit levels were computed from a candle price captured right before a swap was submitted. The swap itself wasn't instant — it polled for on-chain confirmation inside a nested retry loop, worst case around five minutes between "price read" and "swap actually landed."
The bot's own swap function knew the real fill price — it computed and logged it — but
only logged it. The function returned a plain True/False, so the caller
never saw the number and fell back to the stale pre-swap price for the stop-loss reference.
Concretely: signal at $100 → intended stop at $92.50. While the swap sits in flight, the market drops to $90 and the swap fills there. The bot believes it entered at $100 with $7.50 of room. It actually entered at $90, already most of the way to a stop that was never recalculated from the real entry.
Case 2: an argument shift that silently swapped market-close for a slow limit order
A different bug, same root cause — the exit path wasn't tested end-to-end. In a futures bot, the function that closes a position on stop-loss/take-profit had grown new leading parameters over time. The call sites that trigger exits still passed the old, shorter argument list — every argument landed one slot to the right of where it was supposed to.
The practical effect: a parameter that defaults to 100 (truthy) ended up bound to
the internal flag that decides whether to close the position immediately at market, or place a
passive limit order and slowly chase the price instead. Every stop-loss and take-profit exit fired
the slow path — with no configuration change needed to trigger it — exactly in the scenario (a
fast move) where an immediate close was the entire point of having a stop-loss.
Nothing crashed. Nothing logged an error. The log line even printed "stop-loss triggered." The order just didn't behave the way the code around it assumed.
Case 3: it happened in my own bot too
I hold my own systems to the same standard I audit others against, so here's an honest one: in an equity swing-trading bot I run on Interactive Brokers, I found the identical bug class in a live trade. Stop-loss and take-profit were computed from the signal price, then sent as an atomic bracket order — but the entry used a limit order that could sit unfilled for up to the order timeout window while price kept moving.
One real trade: signal at $16.87 → SL at $16.77 (10¢ risk), TP at $17.06 (19¢ target). The limit order actually filled at $17.03. Measured from the real entry, risk was 26¢ and the target was 3¢ away — the risk/reward ratio was almost inverted, and the take-profit was nearly already hit at the moment the position opened.
I didn't catch this by reading the code and spotting it abstractly. I caught it by pulling one specific live trade from the audit log and checking the signal price against the actual fill. That's the only way this class of bug reliably surfaces — code review alone gets you 80% of the way; tracing a real execution gets you the rest.
Why this keeps happening
None of these are careless bugs. Every one sits in code that's otherwise well-structured, with retry logic, error handling, logging. The gap survives because most testing — and most code review — focuses on the entry/signal logic, where the interesting strategy decisions live. The exit path, where money actually gets protected, gets audited far less rigorously, even though it's the part doing the actual risk management.
What to check in your own bot
- Trace the exact variable your stop-loss/take-profit is computed from, from the moment it's read to the moment the protective order is placed. Is anything re-read after a fill, or is it the same value the whole way through?
- If an order function computes a real execution price internally, confirm that price actually gets returned to the caller — not just logged.
- Check every call site of your position-close function for argument order, especially after any signature change. A shifted keyword argument won't raise an exception; it'll just quietly change behavior.
- Pull one real trade from your logs and manually verify signal price → decision → fill price → protective order levels, end to end. Reading the code is not the same as checking what actually happened.
One of the bugs above — reported through the same review process, in a public repository — was confirmed and merged as a fix within 24 hours: github.com/0xfnzero/sol-trade-sdk/pull/112.