An Expert Advisor that trades happily in the Strategy Tester and then refuses to open a single position on a demo or live account almost always says why. The reason is in the Experts tab of the terminal's Toolbox, as a number: 10016, 10030, 10019. This guide explains what those numbers mean, which ones you'll actually see, and how to prevent the common ones in code.
The MQL5 code below comes as excerpts: functions you add to your own EA, not complete programs.
Two kinds of numbers
When an order fails, MQL5 gives you two different codes, and mixing them up sends people in the wrong direction:
- Trade server return codes ("retcodes"), from 10004 to about 10046. They're in the
retcodefield ofMqlTradeResult, ortrade.ResultRetcode()if you use theCTradeclass. This is the reason the order failed. - Runtime errors from
GetLastError(), in the 4000s and 5000s. The one you'll see most is 4756, "trade request sending failed". It only says thatOrderSend()returnedfalse; the retcode says why.
Success has codes too: 10009 (request completed) for market orders, 10008 (order placed) for pending orders, and 10010 when only part of the volume was filled.
Reading both after a raw OrderSend():
// Excerpt: send a market buy and report both codes if it fails.
bool SendBuy(const double volume)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = volume;
req.type = ORDER_TYPE_BUY;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
req.deviation = 10;
req.magic = 4505;
if(!OrderSend(req, res))
{
PrintFormat("OrderSend failed: error %d, retcode %u (%s)", GetLastError(), res.retcode, res.comment);
return false;
}
return res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED;
}
Quick reference
| Code | Constant | Meaning | Usual cause |
|---|---|---|---|
| 10004 | TRADE_RETCODE_REQUOTE | Requote | Price moved before the order was accepted |
| 10006 | TRADE_RETCODE_REJECT | Request rejected | Broker-side rejection; check the journal for detail |
| 10013 | TRADE_RETCODE_INVALID | Invalid request | A field that doesn't fit the order type, or a missing one |
| 10014 | TRADE_RETCODE_INVALID_VOLUME | Invalid volume | Below the minimum, above the maximum, or off the volume step |
| 10015 | TRADE_RETCODE_INVALID_PRICE | Invalid price | Stale price, or a pending order on the wrong side of the market |
| 10016 | TRADE_RETCODE_INVALID_STOPS | Invalid stops | Stop-loss or take-profit too close, or on the wrong side |
| 10017 | TRADE_RETCODE_TRADE_DISABLED | Trade disabled | Trading switched off for the symbol or account |
| 10018 | TRADE_RETCODE_MARKET_CLOSED | Market closed | Outside the symbol's trading session |
| 10019 | TRADE_RETCODE_NO_MONEY | Not enough money | Free margin too low for the volume |
| 10020 | TRADE_RETCODE_PRICE_CHANGED | Prices changed | Fast market; the requested price is gone |
| 10021 | TRADE_RETCODE_PRICE_OFF | No quotes | No price available to process the request |
| 10024 | TRADE_RETCODE_TOO_MANY_REQUESTS | Too frequent requests | Order calls in a tight loop |
| 10027 | TRADE_RETCODE_CLIENT_DISABLES_AT | Autotrading disabled by client terminal | The Algo Trading button or the EA's permission is off |
| 10030 | TRADE_RETCODE_INVALID_FILL | Unsupported filling type | The order's filling mode isn't allowed for this symbol |
| 10034 | TRADE_RETCODE_LIMIT_VOLUME | Volume limit reached | Total volume on the symbol would exceed its limit |
| 10044 | TRADE_RETCODE_CLOSE_ONLY | Close only | The symbol only allows closing existing positions |
| 10045 | TRADE_RETCODE_FIFO_CLOSE | FIFO rule | The account must close the oldest position first |
| 10046 | TRADE_RETCODE_HEDGE_PROHIBITED | Hedging prohibited | Opposite positions on one symbol aren't allowed |
The full list is in the MQL5 reference linked at the end.
10016: invalid stops
The most common one. The server rejects a stop-loss or take-profit when it's:
- Too close to the current price. Each symbol has a minimum distance in points,
SYMBOL_TRADE_STOPS_LEVEL. A value of 0 means no fixed minimum, but on floating spreads the server can still reject stops inside the spread. - Measured from the wrong price. A buy position closes at the bid, so its stop and target are checked against the bid. A sell closes at the ask, so its levels are checked against the ask. Using the ask for a buy's stop leaves you a spread short of the minimum.
- On the wrong side, such as a buy's stop above the price, usually from a sign error.
- Not on a valid price step. Gold, indices and some crypto CFDs move in steps larger than one point.
A check to run before sending, and a rounding helper:
// Excerpt: true if SL/TP respect the broker's minimum distance for a new market order.
bool StopsAreValid(const ENUM_ORDER_TYPE type, const double sl, const double tp)
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
return false;
double minDist = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
if(type == ORDER_TYPE_BUY)
return (sl == 0.0 || tick.bid - sl >= minDist) && (tp == 0.0 || tp - tick.bid >= minDist);
if(type == ORDER_TYPE_SELL)
return (sl == 0.0 || sl - tick.ask >= minDist) && (tp == 0.0 || tick.ask - tp >= minDist);
return false;
}
// Excerpt: round a price to the symbol's tick size.
double NormalizeToTick(const 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);
}
Modifying a position's stops has one more constraint: inside the freeze level (SYMBOL_TRADE_FREEZE_LEVEL) near the current price, a position can't be modified at all. Trailing-stop code should check it; our MQL5 trailing stop guide does.
10030: unsupported filling type
Every symbol allows certain filling modes: Fill or Kill (FOK), Immediate or Cancel (IOC), and Return, depending on the broker and execution type. MqlTradeRequest and CTrade default to FOK. If the broker doesn't allow FOK on that symbol, every order fails with 10030, which is why EAs that work on one broker can fail on another.
With CTrade, one line when the EA starts (in its OnInit handler) fixes it:
// Excerpt: pick a filling mode the symbol allows; call it once at startup.
void ConfigureTrade(CTrade &t, const ulong magic)
{
t.SetExpertMagicNumber(magic);
t.SetDeviationInPoints(10);
t.SetTypeFillingBySymbol(_Symbol);
}
If you build MqlTradeRequest yourself, read SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE), check its flags (SYMBOL_FILLING_FOK, SYMBOL_FILLING_IOC) and set type_filling to an allowed value.
10019: not enough money
Free margin can't cover the new position. Causes, roughly in order of frequency: a lot size computed too large, lower leverage than expected (common on prop-firm and EU retail accounts, and for gold and indices), and margin already used by other positions. Check before sending:
// Excerpt: true if free margin covers a new position of this volume.
bool HasMarginFor(const ENUM_ORDER_TYPE type, const double volume)
{
double price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
double margin = 0.0;
if(!OrderCalcMargin(type, _Symbol, volume, price, margin))
return false;
return margin < AccountInfoDouble(ACCOUNT_MARGIN_FREE);
}
Margin isn't risk, though. A trade can pass this check and still risk far more than you intended. Size from the stop distance first, as in MQL5 lot size from risk percent, then check margin.
10014: invalid volume
Volume must be between SYMBOL_VOLUME_MIN and SYMBOL_VOLUME_MAX and a multiple of SYMBOL_VOLUME_STEP. A lot size from a risk formula, such as 0.137, fails on a symbol with a 0.01 step unless you round it:
// Excerpt: round a volume down to the symbol's step; 0 means "too small to trade".
double NormalizeVolume(const double volume)
{
double minVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(step <= 0.0)
return 0.0;
double v = MathFloor(volume / step + 1e-9) * step;
if(v < minVol)
return 0.0; // skip the trade rather than round the risk up
return MathMin(v, maxVol);
}
Rounding down keeps the risk at or below your target. Returning 0 when the result is under the minimum is deliberate: rounding up to the minimum lot would risk more than you asked for.
10004, 10015, 10020 and 10021: price problems
These mean the price in the request no longer matches the market: a requote, a price that changed, an invalid price or no quotes at all. Read the price immediately before sending, never from a variable saved earlier in the tick or on the previous bar, and set a sensible deviation (SetDeviationInPoints()). For pending orders, 10015 often means the order is on the wrong side: a buy limit must be below the ask, a buy stop above it, and both must respect the stops level. After a requote, wait for the next tick before retrying, and limit the retries.
10018, 10017 and 10044: the symbol isn't tradable right now
The market is closed (weekends, holidays, the daily break on some CFDs), trading is disabled for the symbol, or it only allows closing. A quick check before sending:
// Excerpt: can this EA open a new position on the chart's symbol right now?
bool CanOpenPositions()
{
long mode = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE);
if(mode == SYMBOL_TRADE_MODE_DISABLED || mode == SYMBOL_TRADE_MODE_CLOSEONLY)
return false;
return TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && MQLInfoInteger(MQL_TRADE_ALLOWED);
}
SYMBOL_TRADE_MODE_LONGONLY and SYMBOL_TRADE_MODE_SHORTONLY exist too; check them if your EA trades both directions. For session times, SymbolInfoSessionTrade() returns the trading sessions per weekday. The market hours tool is a quick visual check, but the broker's sessions in server time are what count.
10027 and error 4752: autotrading is off
The terminal or the EA isn't allowed to trade. Check that the Algo Trading button in the toolbar is on, and that "Allow Algo Trading" is ticked in the EA's properties when you attach it. The CanOpenPositions() check above covers both. Logging in with the account's investor (read-only) password also blocks trading. Step-by-step setup is in how to install an Expert Advisor on MT5.
10045 and 10046: FIFO and hedging rules
Some accounts, notably under US rules, forbid holding opposite positions on one symbol (10046) and require positions to be closed in the order they were opened (10045). An EA written for a hedging account needs a different design on these accounts: one position per symbol, closed or reduced rather than hedged.
Error 4806: requested data not found
Not a trade error, but a common neighbour. CopyBuffer() fails with 4806 when an indicator hasn't finished calculating, typically on the first ticks after the EA starts or after a timeframe change. Return and try again on the next tick, or check BarsCalculated(handle) first. Never trade on values from a copy that failed.
A logging helper
When an order fails, log everything you'd need to diagnose it later, in one line:
#include <Trade/Trade.mqh>
// Excerpt: one-line diagnosis after a failed CTrade call.
void LogTradeFailure(CTrade &t, const string what, const double sl, const double tp)
{
PrintFormat("%s failed: retcode %u (%s), error %d, bid %s, ask %s, sl %s, tp %s, stops level %d pts, volume step %s",
what, t.ResultRetcode(), t.ResultRetcodeDescription(), GetLastError(),
DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits),
DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits),
DoubleToString(sl, _Digits), DoubleToString(tp, _Digits),
(int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL),
DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP), 2));
}
Coming from MT4?
MQL4 used different numbers for the same problems:
| MQL4 error | MQL5 retcode |
|---|---|
| 129 Invalid price | 10015 |
| 130 Invalid stops | 10016 |
| 131 Invalid trade volume | 10014 |
| 132 Market is closed | 10018 |
| 133 Trade is disabled | 10017 |
| 134 Not enough money | 10019 |
| 135 Price changed | 10020 |
| 136 Off quotes | 10021 |
| 138 Requote | 10004 |
| 4109 Trade is not allowed | 10027, or runtime error 4752 |
The rest of an MT4-to-MT5 port is covered in how to convert MQL4 to MQL5.
Test where you'll trade
The Strategy Tester doesn't reproduce everything a live server checks: requotes, broker-specific filling rules, session breaks and account permissions can all differ. Before going live, run the EA on a demo account with the same broker and account type you'll use, and read the Experts tab daily for the first weeks.
Sources and further reading
- MQL5 Reference: Trade server return codes — the complete list of retcodes.
- MQL5 Reference: Runtime errors — codes returned by
GetLastError(), including 4756 and 4806. - MQL5 Reference: Symbol properties — stops level, freeze level, volume limits and filling modes.
- The checks a trading robot must pass before publication in the Market — MetaQuotes' article on validating volume, stops and margin before sending orders.
Try it with Edgoro
The Expert Advisors Edgoro's MT5 EA generator writes already normalise prices and volumes, keep stops beyond the broker's stop level and print the return code when an order fails, and every file is compile-checked in MetaEditor; it's part of the trading bots option. Broker conditions still vary, so run any EA on a demo account with your broker before trusting it with real money.
Error handling makes an EA safer to run, not profitable. Trading involves substantial risk of loss; test on a demo account first.