MT5 · code and request checks
MT5 error 10016 invalid stops
Check both SL and TP before moving a stop farther away. In the linked public SELL case, the question focused on the stop-loss distance, but the reported take-profit was above Ask. A distance check on SL alone would miss that wrong-side TP.
Arnold Holm · 2026-10-05 · Source-backed checks, no trading-result promise.
What the error means
10016 is TRADE_RETCODE_INVALID_STOPS: the server rejected stops in the request. It is a trade-server retcode, not the terminal's GetLastError number. An EA is a program that applies your rules in MetaTrader. Read the actual request and server comment before deciding which part failed.
Three code paths to check
1. One protective price is on the wrong side
For a BUY position, SL is below current Bid and TP is above it. For a SELL position, SL is above current Ask and TP is below it. Check both nonzero levels. A valid SL does not excuse a wrong-side TP. Pending entry prices have their own order-type constraints, so do not apply a market-position check blindly to every request.
2. The distance was checked against the wrong quote or unit
Read SYMBOL_TRADE_STOPS_LEVEL on the actual symbol and convert its points using SYMBOL_POINT, not a hard-coded pip multiplier. Compare protection on the correct closing side: Bid for BUY, Ask for SELL. Capture a fresh tick beside the request; a changing spread can invalidate a distance that looked sufficient on an earlier quote. Zero reported stop distance does not validate the other request fields.
3. Price grid or modification state was omitted
SYMBOL_DIGITS controls decimal display; SYMBOL_TRADE_TICK_SIZE controls the price increment. Decimal rounding alone need not put a stop on that grid. For a modification, also inspect SYMBOL_TRADE_FREEZE_LEVEL and reselect the intended position ticket. A frozen operation may have its own server retcode. Keep stop distance, freeze restrictions and position existence as separate checks rather than calling every rejection 10016.
Own pattern: inspect the complete request without placing a trade
Call this with the fully populated request from your authorised code. The function records quote time, request type, target ticket, both protection prices and the symbol's constraints. It calls OrderCheck but never OrderSend, widens a stop, removes protection or retries. Its true return means diagnostics were collected. For TRADE_ACTION_SLTP, inspect the selected position's direction; request.type by itself is not the position's direction.
bool InspectStopRequest(const MqlTradeRequest &request)
{
MqlTick quote = {};
long stops = 0, freeze = 0, digits = 0;
double point = 0.0, tick_size = 0.0;
if(request.symbol == "" ||
!SymbolInfoTick(request.symbol, quote) ||
!SymbolInfoInteger(request.symbol, SYMBOL_TRADE_STOPS_LEVEL, stops) ||
!SymbolInfoInteger(request.symbol, SYMBOL_TRADE_FREEZE_LEVEL, freeze) ||
!SymbolInfoInteger(request.symbol, SYMBOL_DIGITS, digits) ||
!SymbolInfoDouble(request.symbol, SYMBOL_POINT, point) ||
!SymbolInfoDouble(request.symbol, SYMBOL_TRADE_TICK_SIZE, tick_size))
return false;
PrintFormat("symbol=%s action=%d type=%d position=%I64u",
request.symbol, (int)request.action,
(int)request.type, request.position);
Print("quote_msc=", quote.time_msc,
" Bid=", DoubleToString(quote.bid, (int)digits),
" Ask=", DoubleToString(quote.ask, (int)digits),
" price=", DoubleToString(request.price, (int)digits),
" SL=", DoubleToString(request.sl, (int)digits),
" TP=", DoubleToString(request.tp, (int)digits));
Print("stops_points=", stops, " freeze_points=", freeze,
" point=", DoubleToString(point, 10),
" tick_size=", DoubleToString(tick_size, 10));
MqlTradeRequest candidate = request;
MqlTradeCheckResult check = {};
ResetLastError();
bool basic = OrderCheck(candidate, check);
int terminal_error = GetLastError();
PrintFormat("basic_check=%s terminal_error=%d check_retcode=%u comment=%s",
basic ? "true" : "false", terminal_error,
check.retcode, check.comment);
return true; // diagnostic collected, NOT permission to trade
}This is a source-level diagnostic pattern, not a compiled EA or a recorded broker execution. OrderCheck returning true does not guarantee later execution. If your approved demo workflow submits a request, retain its MqlTradeResult.retcode/comment and inspect the resulting position or order. Pending orders, stop-limit orders and broker-specific modification constraints need their own cases. Do not silently change the rule's risk distance to make a rejection disappear.
Three cases to agree before calling it fixed
Positive case: correct direction and permitted price grid
On the intended demo symbol, record a fresh quote and both nonzero protective prices on the correct sides, outside the stated stop distance and on the tick grid. Inspect OrderCheck fields. Any separately approved submission must also show the server retcode and the resulting position; a true function return alone is insufficient.
Negative case: SELL with TP above current Ask
Keep the wrong-side TP visible in the fixture even if SL is far enough away. The preflight rejects that case without sending, retrying or changing protection. This recreates the direction mistake in the linked public question, not a claim to have run that user's source.
Boundary case: quote changes or a modification is frozen
Refresh the tick and inspect stop distance, tick grid, freeze restrictions and the selected position again. Record the exact rejection code rather than assuming 10016. Missing symbol properties make the diagnostic return false. No automatic stop widening and no repeated order request are accepted as a repair.
Your own source
Find the next line to check
Start with the free EA-Check. Your code stays in your browser. It highlights patterns with line numbers; it does not prove the broker accepts an order.
Want one small change shown on your own case?
SOLVED-FIRST: one small fix or change to your own case, shown first as screenshots or a short screen video. 15 USD fixed (or the higher platform minimum), first five paid orders only during 4–17 October 2026. The file follows within 24 hours after funding. Close on Upwork first; marketplace cases stay on their marketplace.
Outside that sprint: R49: one authorised, reproducible MT4/MT5 defect, with three agreed cases. 49 USD fixed. Delivery within 48 hours after source review and funding. The exact case is agreed before funded work.
Send the expected and actual behaviourRelated checks
Common questions
Does 10016 mean my stop-loss is always too close?
No. Check direction and distance for both SL and TP, plus the pending entry price where applicable. The linked SELL question had a take-profit above Ask. Moving only the stop-loss farther away would not correct that request.
Will NormalizeDouble fix invalid stops?
It controls decimal precision, not every symbol's permitted price increment or stop distance. Read tick size, points and the current quote separately. Correct rounding still does not guarantee that the server accepts a complete request.
Can I remove stops and add them after entry?
That changes the protection path and creates an unprotected interval. It is not a general diagnostic fix. Preserve the agreed risk rule, record the rejected request and test an explicit permitted workflow on demo before any separately authorised use.
Sources and limits
- Official MQL5 trade-server return codes
- Official symbol stop distance, freeze distance and tick size
- Official OrderCheck result and execution limits
- Observed SELL question: valid-looking SL, wrong-side TP
The linked forum case belongs to its author. It is not my customer case or proof that your broker has the same cause. This article covers request behaviour only; it makes no performance or challenge-passing claim.