What 958 Live Trades Taught Us About Stop-Losses in Illiquid Markets
We checked every live trade our agents closed over six months. In 80% of losing trades the realised loss was worse than the stop allowed. Here is why that happens, what we found when we instrumented it, and what we changed.
Most writing about automated trading shows you a backtest. This shows you what happened with real money.
Between 9 April and 29 September 2026, QWNT agents closed 958 live positions on-chain. Not paper. Not simulated. We pulled every one and looked at what the numbers say. The result was uncomfortable, and the thing we found underneath it was worth more than a clean equity curve.
The headline numbers
| Measure | Result |
|---|---|
| Live positions closed | 958 |
| Win rate | 46.9% |
| Average return per trade | -6.86% |
| Average winning trade | +25.07% |
| Average losing trade | -35.02% |
| Net realised | -$438.29 |
A 46.9% win rate is close to a coin flip, which is roughly what short-horizon momentum entries produce. That is not the problem. The problem is the two rows underneath it.
When these agents won they made 25%. When they lost they lost 35%. At a coin-flip win rate that arithmetic only resolves one way, and no amount of better entries fixes it.
The stops did not hold
Every one of these agents ran a stop-loss, most configured between 10% and 15%. If those stops had held, the average losing trade should have landed near -15%, not -35%.
Here is where the losses actually finished:
| Loss band | Share of losing trades |
|---|---|
| Within the stop (better than -15%) | 19.4% |
| Overshoot, -15% to -30% | 47.7% |
| Overshoot, -30% to -60% | 12.8% |
| Overshoot, -60% to -99% | 13.2% |
| Total loss (-100%) | 6.9% |
More than 80% of losing trades finished worse than the stop was set to allow. That is the real finding.
Why a stop-loss is a request, not a guarantee
A stop-loss says sell when price crosses a level. It assumes a buyer is there. On a major pair that assumption is safe. On a two-day-old token with $40,000 of liquidity it is not.
Three things break it:
- The book is thin. Your stop triggers at -15%, but the only bid deep enough to absorb your size sits at -28%. You get the fill that exists, not the one you asked for.
- Price gaps. Between one price poll and the next a token can fall 40%. There was never a tradeable price at -15%.
- Liquidity vanishes. In 6.9% of our losing trades the position went to -100%. When a pool is pulled there is no exit at any price, and no stop-loss in any system survives that.
The first two are execution problems and can be improved. The third is the nature of the asset.
What instrumenting it actually revealed
Here is the part we did not expect.
When we went to diagnose the overshoot we found that not one of those 958 closed positions recorded why it closed. We could measure that stops were being missed. We could not tell whether a stop had triggered and slipped, whether the sell had failed, or whether liquidity had simply gone. Three completely different problems, indistinguishable in the data.
So we recorded the exit reason on every close, along with the configured stop, the realised fill, and the distance between them. Then we looked again.
The answer was not slippage. It was that the sells were never being attempted. Over a two-day sample, 321 live stop-losses triggered and produced 12 sell attempts. The rest were silently abandoned.
The cause was a single line. The function that checks an agent's on-chain balance caught its own network errors and returned zero. The caller read that zero as "the wallet holds nothing to sell", hit a minimum-size guard, and returned without selling. Thirty seconds later the risk layer triggered again and did exactly the same thing. One position was skipped 314 consecutive times while it fell well past its stop.
An RPC failure and an empty wallet produced the same value, and both meant do nothing.
What we changed
- Record why every position closes — reason, configured stop, realised fill, and the gap between them. This went in first, because without it every other fix is guesswork.
- Never let a failure read as a zero. The balance lookup now returns "unknown" when it fails, and unknown is handled explicitly. If the balance cannot be read, the sell proceeds at the size we believe we hold and the swap rejects it if we are short, which is strictly better than abandoning a triggered exit.
- Stop guessing token decimals. A failed decimals lookup used to default to a value that mis-sizes the swap by a factor of a thousand. It now fails loudly instead.
- Escalate repeated skips. Those 314 identical warnings scrolled past unread for two days. Consecutive skips now escalate with a count attached. That was a monitoring failure as much as a code one.
What we cannot tell you yet
The fix is deployed but it has not proved itself. It only demonstrates its value the next time a network lookup fails during a triggered exit, and we have not run enough live volume since to show a before-and-after. We would rather say that plainly than show you a chart built on four days of data.
What to take from this if you run agents anywhere
- Check your realised losses against your configured stops. If the average loss is materially worse than the stop level, the risk control is not doing what you think. One query answers it, and almost nobody runs it.
- Win rate is the least interesting number. 46.9% is fine. The ratio of average win to average loss decides the outcome, and most dashboards bury it.
- Instrument your exits before you optimise your entries. Most automated strategies are heavily measured on the way in and nearly blind on the way out. Ours was.
- Treat a failed lookup as unknown, never as zero. In a risk path, silence is the most expensive default you can choose.
We publish this because a platform that only shows its good months is not giving you information you can use. If you are evaluating any automated trading system, ask for the distribution of its losing trades against its configured stops. If that answer is not available, that is itself the answer.