Strategy testing and trading software · Germanycontact@stratcorealpha.com · DE
All trading software errors

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. 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. 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. 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.

Check my EA locally

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 behaviour

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

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.

Trading risk disclosure

Futures and forex trading contains substantial risk and is not for every investor. An investor could potentially lose all or more than the initial investment. Risk capital is money that can be lost without jeopardizing one's financial security or lifestyle. Only risk capital should be used for trading, and only those with sufficient risk capital should consider trading. Past performance is not necessarily indicative of future results.

Hypothetical performance disclosure

Hypothetical performance results have many inherent limitations. No representation is being made that any account will or is likely to achieve profits or losses similar to those shown. There are frequently sharp differences between hypothetical performance results and the actual results subsequently achieved by any particular trading program. Hypothetical results are generally prepared with the benefit of hindsight, do not involve financial risk, and cannot completely account for the impact of financial risk in actual trading. Market conditions, the ability to withstand losses, and adherence to a trading program can all materially affect actual results.

NinjaTrader trademark disclosure

NinjaTrader® is a registered trademark of NinjaTrader Group, LLC. No NinjaTrader company has any affiliation with the owner, developer, or provider of the products or services described herein, or any interest, ownership or otherwise, in any such product or service, or endorses, recommends or approves any such product or service.

Testimonials, when shown, may not represent the experience of other clients and are not a guarantee of future performance or success. Virtual-currency trading carries additional risks. See the CFTC customer advisories. Read the full financial risk disclaimer.