Quick Answer
Trade return code 10015 (TRADE_RETCODE_INVALID_PRICE) means that the price field of your request is not acceptable. The usual reasons are a price that is 0 or stale, a price that is not rounded to the digits or the tick size of the symbol, the wrong side (for example the Bid for a buy), and a pending order price that is on the wrong side of the market or too close to it.
It is the price counterpart of invalid stops, 10016 and invalid volume, 10014. An EA avoids it by reading a fresh tick right before the order, using the correct side, rounding the price with the tick size and the digits, and, for pending orders, keeping a minimum distance from the market. The helpers below do this, and a small EA prints the numbers so you can see the limits of your symbol.
From the author: I build and convert Expert Advisors. Price errors appear mostly with pending orders and with symbols that have an unusual tick size, such as indexes and some metals. A price that is correct on EURUSD is rejected on another symbol, because the EA rounds to 5 digits and the symbol uses a larger step.
Key Facts
| What you see | Usual cause | Fix |
|---|---|---|
| 10015 on a market order | The price is 0, stale or from the wrong side | Read a fresh tick: Ask for buy, Bid for sell |
| 10015 on a pending order | The price is on the wrong side of the market | A buy limit below Ask, a buy stop above Ask, and the reverse for sell |
| 10015 on one symbol only | The price is not a multiple of the tick size | Round to SYMBOL_TRADE_TICK_SIZE, then to the digits |
| 10015 only on volatile moves | The price was read some time ago | Read the tick right before the request |
| 10016 instead | The stops are the problem, not the price | See the invalid stops guide |
| The price looks right on the chart | The EA used a different number than it prints | Print the price that goes into the request |
What the Error Looks Like
With CTrade, a rejected order makes the call return false, and the code is in the result:
if(!trade.BuyLimit(lots, price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "bot"))
{
PrintFormat("Order failed. Retcode %u: %s",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
}
The full list of return codes is in MQL5 trade return codes. When the whole request is wrong, you get 10013 (invalid request) instead, and a rounding problem in the volume gives 10014.
Cause 1: A Price That Is 0 or Stale
A market order through CTrade can take the current price by itself when you pass 0. A request that you build by hand needs a price, and a variable that was set once and never updated is stale:
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
{
Print("No tick for ", _Symbol);
return;
}
double askNow = tick.ask;
double bidNow = tick.bid;
Read the tick right before you build the request, and use these numbers. A price that was read 30 seconds ago can already be outside the allowed deviation on a fast market. A price of 0 is invalid on its own, and it happens when the symbol is not in the Market Watch or has no quotes yet.
Cause 2: The Wrong Side of the Market
A buy is filled at the Ask, a sell at the Bid. A buy order with the Bid price is a few points off, and on some symbols that is enough to be rejected or to be filled badly:
double price = (type == ORDER_TYPE_BUY) ? tick.ask : tick.bid;
For pending orders the rule is about the side of the market:
| Order | Price must be | |---|---| | Buy limit | below the current Ask | | Buy stop | above the current Ask | | Sell limit | above the current Bid | | Sell stop | below the current Bid |
An order with a price on the wrong side is not a limit or a stop of that type, and the server rejects it.
Cause 3: A Price That Is Not Rounded
A price must use the digits of the symbol, and on some symbols it must also be a multiple of the tick size. NormalizeDouble(price, _Digits) is not always enough, because the tick size can be larger than one point:
double NormalizePrice(const string symbol, double price)
{
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
if(tickSize > 0.0)
price = MathRound(price / tickSize) * tickSize;
return NormalizeDouble(price, digits);
}
First round to a multiple of the tick size, then normalize to the digits to remove floating point noise. This is the same idea as the volume step in the lot size guide: never send the raw result of a calculation.
Cause 4: A Pending Order Too Close to the Market
Brokers set a minimum distance between the market price and a pending order. It is the stops level, in points:
long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double minDist = stopsLevel * point;
A buy limit must be at least minDist below the Ask, and a buy stop at least minDist above it. A pending order closer than that is rejected. Depending on the server, the code can be 10015 or 10016, so check both when you debug. A stops level of 0 does not mean "no limit" on every server, it can mean that the level is floating: keep a margin of a few points anyway.
A related setting is the freeze level (SYMBOL_TRADE_FREEZE_LEVEL): near the market you cannot change or delete a pending order.
Cause 5: A Different Number Than You Think
The most common bug is a price that is correct in the EA's logic and wrong in the request, because a variable was overwritten or a stop level was added to the wrong side. Print the value that goes into the request, with the Ask, the Bid and the minimum distance next to it:
PrintFormat("Pending %s: price %.5f, ask %.5f, bid %.5f, min distance %.5f",
EnumToString(type), price, tick.ask, tick.bid, minDist);
When the price in the log is on the wrong side, the code is wrong, not the broker.
A Safe Check for Pending Orders
This function returns true when a pending price is on the right side and far enough from the market. It does not know the rules of every server, so keep a margin:
bool IsPendingPriceValid(const string symbol, ENUM_ORDER_TYPE type, double price)
{
MqlTick tick;
if(!SymbolInfoTick(symbol, tick))
return false;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
long level = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
double minDist = level * point;
switch(type)
{
case ORDER_TYPE_BUY_LIMIT: return price <= tick.ask - minDist;
case ORDER_TYPE_BUY_STOP: return price >= tick.ask + minDist;
case ORDER_TYPE_SELL_LIMIT: return price >= tick.bid + minDist;
case ORDER_TYPE_SELL_STOP: return price <= tick.bid - minDist;
}
return false;
}
Use it after NormalizePrice, and skip the order (with a printed reason) when it returns false. When you can, add the stop distance rule for the stop loss and take profit too, as in the invalid stops guide.
A Full EA That Prints the Limits
This EA does not place trades. It prints the digits, the tick size, the stops level and the freeze level of the chart symbol, and checks four example pending prices:
#property strict
int OnInit()
{
string sym = _Symbol;
int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
double tickSize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
double point = SymbolInfoDouble(sym, SYMBOL_POINT);
long stops = SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL);
long freeze = SymbolInfoInteger(sym, SYMBOL_TRADE_FREEZE_LEVEL);
PrintFormat("%s: digits %d, point %.8f, tick size %.8f, stops level %d, freeze level %d",
sym, digits, point, tickSize, (int)stops, (int)freeze);
MqlTick tick;
if(!SymbolInfoTick(sym, tick))
{
Print("No tick yet, attach the EA to a symbol with quotes.");
return INIT_SUCCEEDED;
}
double minDist = stops * point;
double step = 10 * point + minDist;
double buyLimit = NormalizePrice(sym, tick.ask - step);
double buyStop = NormalizePrice(sym, tick.ask + step);
double sellLimit = NormalizePrice(sym, tick.bid + step);
double sellStop = NormalizePrice(sym, tick.bid - step);
PrintFormat("Ask %.5f Bid %.5f", tick.ask, tick.bid);
PrintFormat("Buy limit %.5f valid=%d", buyLimit, IsPendingPriceValid(sym, ORDER_TYPE_BUY_LIMIT, buyLimit));
PrintFormat("Buy stop %.5f valid=%d", buyStop, IsPendingPriceValid(sym, ORDER_TYPE_BUY_STOP, buyStop));
PrintFormat("Sell limit %.5f valid=%d", sellLimit, IsPendingPriceValid(sym, ORDER_TYPE_SELL_LIMIT, sellLimit));
PrintFormat("Sell stop %.5f valid=%d", sellStop, IsPendingPriceValid(sym, ORDER_TYPE_SELL_STOP, sellStop));
return INIT_SUCCEEDED;
}
double NormalizePrice(const string symbol, double price)
{
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
if(tickSize > 0.0)
price = MathRound(price / tickSize) * tickSize;
return NormalizeDouble(price, digits);
}
bool IsPendingPriceValid(const string symbol, ENUM_ORDER_TYPE type, double price)
{
MqlTick tick;
if(!SymbolInfoTick(symbol, tick))
return false;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
long level = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
double minDist = level * point;
switch(type)
{
case ORDER_TYPE_BUY_LIMIT: return price <= tick.ask - minDist;
case ORDER_TYPE_BUY_STOP: return price >= tick.ask + minDist;
case ORDER_TYPE_SELL_LIMIT: return price >= tick.bid + minDist;
case ORDER_TYPE_SELL_STOP: return price <= tick.bid - minDist;
}
return false;
}
Run it on a forex pair, gold and an index on a demo account and compare the output with the symbol specification. All four lines should say valid=1.
Checklist: What to Check When You See 10015
- Print the price that goes into the request, with the Ask, the Bid and the stops level.
- Read a fresh tick right before the order.
- Use the right side: Ask for a buy, Bid for a sell.
- Check the side of a pending order: limit and stop go in opposite directions.
- Round the price to the tick size, then to the digits.
- Keep the minimum distance (stops level) from the market, with a small margin.
- Compare with 10016 and 10013, because a server can use a neighbouring code.
- Test on a demo account before the EA trades with real money.
Common Mistakes
- Using a price from an earlier tick.
- Mixing up Ask and Bid.
- Placing a buy limit above the market, or a stop on the wrong side.
- Only calling
NormalizeDoubleon a symbol with a larger tick size. - Ignoring the stops level for pending orders.
- Not printing the price that is sent.
FAQ
Related Guides
- MQL5 trade return codes: what each return code means and how to react.
- MQL5 invalid stops error 10016: the matching error for stop loss and take profit.
- MQL5 unsupported filling mode error: another order rejection with a clear fix.
- MQL5 lot size calculation: a volume that matches the symbol rules.
- MQL5 zero divide error: guard a division before you use it.
- Pine Script strategy.exit to MQL5: stop loss and take profit with correct distances.
- MQL5 trailing stop EA: move a stop without breaking the minimum distance.
- What to check in a trading bot before going live: a pre-live checklist for stops, lot size, spread and trade results.
- MQL5 invalid volume error 10014: a lot size that does not match the symbol rules.
- MQL5 not enough money error 10019: not enough free margin for the order.
- MQL5 market closed error 10018: an order sent outside the trading session.
Fix Compile Errors Without Guessing
If an Expert Advisor does not compile, or you want a second look at its order and risk logic, paste the code into the CodeFlowOS Error Fixer. It suggests a minimal fix, explains each error and shows what changed. A fix uses 1 credit, with up to 3 attempts included, and the compiler check is optional. Outputs are not guaranteed, and code that compiles can still have wrong trading logic, so always review the result. The Code Review is free during the early-bird period and checks an EA for common mistakes such as a fixed lot size or a missing stop loss. CodeFlowOS also converts strategies between Pine Script, MQL5 and cTrader C#: try 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.