Blog
MQL5 Invalid Stops (10016): Stops Level Explained
Quick Answer
TRADE_RETCODE_INVALID_STOPS (10016) means the broker rejected your stop loss or take profit. The usual causes are a stop that is too close to the market price, a stop on the wrong side of the price, or a price that is not rounded to the symbol's tick size.
Read SYMBOL_TRADE_STOPS_LEVEL, keep SL and TP at least that many points away from the correct reference price (Bid for a buy, Ask for a sell), normalize both prices, and check the result of the order call instead of assuming it succeeded.
Key Facts
| Item | Detail |
|---|---|
| Return code | TRADE_RETCODE_INVALID_STOPS = 10016 |
| Minimum distance | SYMBOL_TRADE_STOPS_LEVEL, measured in points |
| Unit of a point | SYMBOL_POINT (not the same as a pip on 5-digit symbols) |
| Reference price for a buy | Bid |
| Reference price for a sell | Ask |
| Zero stops level | No fixed limit, or the broker controls it dynamically |
| Related limit | SYMBOL_TRADE_FREEZE_LEVEL (blocks changes near the price) |
What Error 10016 Means
When you call CTrade::Buy() or CTrade::Sell() with a stop loss or take profit, the server checks those levels against the current market price. If they break the symbol's rules, the order is rejected and trade.ResultRetcode() returns 10016. Our MQL5 trade retcodes guide lists the other codes and how to handle each one. This post goes deeper on this single code.
Stops Level: The Minimum Distance
SYMBOL_TRADE_STOPS_LEVEL is the minimum distance, in points, between the market price and your stop levels. Read it like this:
long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double minDist = (double)stopsLevel * point;
PrintFormat("Stops level: %d points = %.5f in price", (int)stopsLevel, minDist);
The distance rules for a market position are:
| Direction | Stop loss rule | Take profit rule | |---|---|---| | Buy | Bid minus SL must be at least the stops level | TP minus Bid must be at least the stops level | | Sell | SL minus Ask must be at least the stops level | Ask minus TP must be at least the stops level |
A value of 0 does not always mean "no limit". The broker may apply its own control outside the terminal, so you should still handle a rejection.
The Spread Trap
Many EAs calculate a buy stop from the Ask price, but the server checks it against the Bid. The difference is the spread.
Suppose the stops level is 300 points and the spread is 20 points. If you place a buy SL exactly 300 points below Ask, the distance from Bid is only 280 points. That is below the minimum, so the order is rejected with 10016, even though your arithmetic looked correct.
The fix is to measure from the right price, or to add a small buffer on top of the stops level.
Normalize Prices to the Tick Size
A stop price must be a valid price for the symbol. Rounding to _Digits is a good start, but some symbols trade in tick sizes larger than one point, so round to the tick size first:
double NormalizePrice(double price)
{
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickSize <= 0.0)
return NormalizeDouble(price, _Digits);
return NormalizeDouble(MathRound(price / tickSize) * tickSize, _Digits);
}
A Helper That Validates Stops Before Sending
This function checks SL and TP against the current Bid and Ask before you call CTrade. A value of 0 means "no SL" or "no TP" and is skipped.
bool StopsAreValid(bool isBuy, double sl, double tp)
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
return false;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
long level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double minDist = (double)level * point;
double eps = point * 0.5; // tolerance for floating-point rounding
if(isBuy)
{
if(sl > 0.0 && (tick.bid - sl) < (minDist - eps)) return false;
if(tp > 0.0 && (tp - tick.bid) < (minDist - eps)) return false;
}
else
{
if(sl > 0.0 && (sl - tick.ask) < (minDist - eps)) return false;
if(tp > 0.0 && (tick.ask - tp) < (minDist - eps)) return false;
}
return true;
}
Using the Helper With CTrade
#include <Trade\Trade.mqh>
CTrade trade;
input int SlPoints = 300;
input int TpPoints = 600;
void OpenBuy(double lots)
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = NormalizePrice(ask - SlPoints * point);
double tp = NormalizePrice(ask + TpPoints * point);
if(!StopsAreValid(true, sl, tp))
{
Print("Stops too close to the market. Order not sent.");
return;
}
if(!trade.Buy(lots, _Symbol, 0.0, sl, tp))
{
PrintFormat("Buy failed. Retcode: %u, %s",
trade.ResultRetcode(),
trade.ResultRetcodeDescription());
}
}
If the check fails, you can log it, widen the stops, or skip the trade, depending on your strategy. To size the position correctly once the stop distance is known, see the MQL5 lot size calculation guide.
Freeze Level: A Different Limit
SYMBOL_TRADE_FREEZE_LEVEL is related but separate. It blocks modifying or cancelling an order or position when the price is within that distance of it. A rejection from this limit shows up as a different return code (TRADE_RETCODE_FROZEN, 10029), not 10016. Read it the same way:
long freezeLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
If your EA trails stops, check the freeze level before each modification.
Pending Orders
For pending orders, the stops level applies in two places: the distance of the pending price from the market, and the distance of SL and TP from the pending price. Check both before you send a buy limit, sell limit, buy stop or sell stop.
Common Causes Checklist
- SL or TP is closer to the price than
SYMBOL_TRADE_STOPS_LEVEL. - The distance was measured from Ask, but the server checks Bid for a buy (the spread trap above).
- SL or TP is on the wrong side of the price, for example a buy with SL above the entry.
- The price is not rounded to the tick size or digits.
- A fixed pip value was hard-coded, which breaks on symbols with a different point size.
- The stops level changed because of market conditions or the broker's settings.
- You modified a position that was inside the freeze level.
FAQ
What does invalid stops mean in MQL5?
It means the server rejected your stop loss or take profit because it breaks the symbol's rules. The return code is TRADE_RETCODE_INVALID_STOPS (10016).
How do I find the minimum stop distance for a symbol?
Call SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL). The result is in points. Multiply it by SYMBOL_POINT to get the distance in price.
Why does 10016 happen even when my SL is far from the price?
Check the side of the price, the rounding, and whether you measured from Bid or Ask. Also confirm that you are not passing a distance as a price.
Can stops level be different on a live account and in the Strategy Tester?
It can. The tester uses the symbol settings available to it, and a broker can change the live value. Read it at run time instead of hard-coding a number.
Handle Retcodes Without Rewriting Your EA
Stop validation, tick-size rounding and retcode handling are easy to miss when you convert a strategy from another platform. CodeFlowOS generates MQL5 code with order handling you can review before you test it in the MT5 Strategy Tester. You can try it on the converter page.
This article is educational. It does not describe trading performance and is not financial advice. Always compile and test code in MetaEditor and the Strategy Tester before using it on a live account.