Edgoro
Trade server return code 10016 · MQL5

MT5 error 10016 Invalid stops: causes and fix

Why MetaTrader 5 refuses an order with retcode 10016 (invalid stops): stops level, Bid vs Ask, wrong side. With broken and fixed MQL5 code.

Trade server return code · MqlTradeResult.retcode
10016 TRADE_RETCODE_INVALID_STOPS
“Invalid stops in the request”
Experts tab · line printed by CTrade (example values)
CTrade::OrderSend: market buy 0.10 EURUSD sl: 1.08490 tp: 1.08560 [invalid stops]

What it means

The trade server refused the stop-loss or the take-profit attached to the request. Nothing was opened, or, for a modification, nothing changed. The levels are the problem: one of them is too close to the price it's checked against, or on the wrong side of it.

Usual causes

  • Too close to the price. Each symbol has a minimum distance, SYMBOL_TRADE_STOPS_LEVEL, in points. A stop or target inside it is refused. The broker sets it per symbol, so a distance that works on one account can fail on another.
  • Measured from the wrong price. A buy position closes at the Bid, so the server checks its stop and target against the Bid. A sell closes at the Ask, so its levels are checked against the Ask. A buy's stop measured from the Ask ends up one spread closer than you think.
  • On the wrong side. A buy's stop above the Bid, or its target below it, usually from a sign error, or from passing a distance (300) where a price (bid - 300 * _Point) is expected.
  • Computed from an old price. Levels built from a price saved earlier (on the last bar, or before a slow calculation) can fall inside the minimum distance by the time the request arrives.
  • Pending orders. For a pending order, the distance is measured from the order's own price, not from the market.

Round every level before sending: to _Digits, and to the tick size on symbols where one tick is larger than one point, such as many metals and indices.

How to detect it

  • OrderSend() returns false and result.retcode is 10016. With CTrade, trade.ResultRetcode() returns it and the Experts tab shows the request followed by [invalid stops].
  • GetLastError() returns 4756, which only says the request failed.
  • To see which level is wrong, log the Bid, the Ask, the stop, the target and SYMBOL_TRADE_STOPS_LEVEL at the moment of the failure. The distance that's too small is then obvious.

Broken code

This EA compiles. Every buy fails with 10016 when the stop ends up closer to the Bid than the symbol's stops level.

MQL5
#include <Trade/Trade.mqh>

input double InpLots       = 0.10; // Volume
input int    InpStopPoints = 30;   // Stop-loss distance, points
input int    InpTakePoints = 60;   // Take-profit distance, points

CTrade trade;

void OnTick()
  {
   if(PositionSelect(_Symbol))
      return;
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   // Stop and target measured from the Ask and never compared with the broker's minimum distance
   double sl = ask - InpStopPoints * _Point;
   double tp = ask + InpTakePoints * _Point;
   trade.Buy(InpLots, _Symbol, ask, sl, tp);
  }

Fixed code

The same program with the check that prevents the error. It compiles in MetaEditor with 0 errors and 0 warnings.

MQL5
#include <Trade/Trade.mqh>

input double InpLots       = 0.10; // Volume
input int    InpStopPoints = 30;   // Stop-loss distance, points
input int    InpTakePoints = 60;   // Take-profit distance, points

CTrade trade;

// Rounds a price to the symbol's tick size
double RoundToTick(const double price)
  {
   double tick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(tick <= 0.0)
      return NormalizeDouble(price, _Digits);
   return NormalizeDouble(MathRound(price / tick) * tick, _Digits);
  }

void OnTick()
  {
   if(PositionSelect(_Symbol))
      return;
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol, tick))
      return;
   // A buy closes at the Bid: its stop and target are checked against the Bid
   int    stopsLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   double minDist    = stopsLevel * _Point;
   double sl = RoundToTick(tick.bid - InpStopPoints * _Point);
   double tp = RoundToTick(tick.bid + InpTakePoints * _Point);
   if(tick.bid - sl < minDist || tp - tick.bid < minDist)
     {
      static bool warned = false;
      if(!warned)
         PrintFormat("Skipped: this symbol needs stops at least %d points from the Bid", stopsLevel);
      warned = true;
      return;
     }
   if(!trade.Buy(InpLots, _Symbol, tick.ask, sl, tp))
      PrintFormat("Buy failed: retcode %u (%s)", trade.ResultRetcode(), trade.ResultRetcodeDescription());
  }

Why the fix works

The levels are now measured from the Bid, the price a buy closes at, and rounded to the tick size. Before sending, the EA compares both distances with SYMBOL_TRADE_STOPS_LEVEL and skips the trade when one is too small, instead of letting the server refuse it.

It skips rather than widening the stop on purpose: a wider stop changes the risk of the trade. If you prefer to widen it, recompute the lot size from the new distance. For a sell, mirror everything: the levels are checked against the Ask, the stop goes above and the target below.

When modifying a position

PositionModify() returns the same 10016 when a trailing stop moves the stop inside the stops level. Modifications have a second limit: inside the freeze level (SYMBOL_TRADE_FREEZE_LEVEL) the server refuses any change, which is 10029, order or position frozen. A request whose stop equals the current one returns 10025, no changes.

Questions

What is the minimum stop-loss distance in MetaTrader 5?

It's the symbol's SYMBOL_TRADE_STOPS_LEVEL, in points, read with SymbolInfoInteger(). The broker sets it per symbol. A value of 0 means there is no fixed minimum, but the stop still has to be on the correct side of the price it's checked against.

Why does my EA get invalid stops on one account and not on another?

The stops level, the spread and the number of digits differ between brokers, account types and symbols. A distance hard-coded on one account can fall inside the minimum on another. Read SYMBOL_TRADE_STOPS_LEVEL and the live Bid and Ask before every order.

Is MT5 error 10016 the same as MT4 error 130?

Yes, they describe the same problem. MQL4's error 130 (ERR_INVALID_STOPS) is trade server return code 10016 in MQL5, read from the result structure rather than from GetLastError().

The programs on this page are minimal examples written to show the error and its fix, not trading systems. Some of them send orders when they run: try them on a demo account. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

Working code that avoids it

All MQL5 examples

Rather not write the error handling yourself?

Edgoro's MT5 EA generator writes complete MQL5 Expert Advisors from plain English and compiles each one in MetaEditor before you get it. A clean build proves the code compiles, not that a strategy works: test it on a demo account.