Quick Answer
Trade return code 10014 (TRADE_RETCODE_INVALID_VOLUME) means that the broker server rejected the volume (the lot size) in your order. The volume must follow three rules that the symbol defines: it cannot be below the minimum (SYMBOL_VOLUME_MIN), it cannot be above the maximum (SYMBOL_VOLUME_MAX), and it must be a whole multiple of the step (SYMBOL_VOLUME_STEP). A lot size of 0.123 fails on a symbol with a step of 0.01, even though it is above the minimum.
The fix is a small helper that reads the three values from the symbol, rounds the lot down to the step, clamps it between minimum and maximum, and returns 0 when nothing valid is left. You call it before every order, and you never send a hard-coded lot size to a symbol you have not checked.
From the author: I build and convert Expert Advisors, and 10014 is one of the first errors an EA meets on a new broker or a new symbol: the lot size that worked on EURUSD fails on gold, an index or a crypto symbol, because the minimum and the step are different there. This guide shows how to read the values and how to round safely.
Key Facts
| What you see | Usual cause | Fix |
|---|---|---|
| 10014 with a lot like 0.005 | Below SYMBOL_VOLUME_MIN | Use at least the minimum, or skip the trade |
| 10014 with a lot like 0.123 | Not a multiple of the step | Round down to the step |
| 10014 with a very large lot | Above SYMBOL_VOLUME_MAX | Clamp to the maximum, or split the order |
| 10014 after an int or double conversion | 0.29 / 0.01 gave 28.999... | Round with a small tolerance (see below) |
| Volume is 0 | A risk formula returned 0 | Check the stop distance and the tick value first |
| 10019 instead | Not enough free margin | The volume is valid, but you cannot afford it |
What the Error Looks Like
With CTrade, a rejected order gives a failed Buy or Sell call, and the details are in the result:
if(!trade.Buy(lots, _Symbol, 0.0, sl, tp, "bot"))
{
PrintFormat("Order failed. Retcode %u: %s",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
}
The Experts or Journal log can show a line such as invalid volume together with the return code. The list of all return codes is in MQL5 trade return codes, and the matching error for stops is in MQL5 invalid stops error 10016.
The Three Limits of a Symbol
Every symbol has its own volume rules, and they can be different between symbols and between brokers:
| Property | Meaning | Typical values (examples only) |
|---|---|---|
| SYMBOL_VOLUME_MIN | Smallest allowed volume | 0.01, 0.1, 1.0 |
| SYMBOL_VOLUME_MAX | Largest allowed volume per order | 50, 100, 500 |
| SYMBOL_VOLUME_STEP | Volume must change in this step | 0.01, 0.1, 1.0 |
The numbers in the last column are only examples. Read the real values from the symbol of your broker, in the Specification window of the Market Watch or with SymbolInfoDouble, because they can differ from the example.
There is also a total limit: SYMBOL_VOLUME_LIMIT is the maximum total volume of open positions and pending orders for the symbol, in one direction or in both, depending on the broker. A value of 0 means that there is no such limit. Orders can fail when this limit is reached.
Why a Normal Rounding Is Not Enough
A common line is:
double lots = NormalizeDouble(riskMoney / riskPerLot, 2);
This has three problems. First, NormalizeDouble(x, 2) rounds to the nearest hundredth, which is wrong when the step is 0.1 or 1.0. Second, it rounds up half of the time, so the risk can be higher than you planned. Third, it does not check the minimum and the maximum.
Floating point numbers add a fourth problem. 0.29 / 0.01 is not exactly 29, it is 28.999999999999996, so a plain MathFloor gives 28 steps and the lot drops by one step more than it should. The helper below adds a very small tolerance before it rounds down. The same effect is explained in MQL5 possible loss of data due to type conversion.
A Helper That Makes the Volume Valid
// Returns a volume that follows min, max and step, or 0.0 when no valid volume exists.
double NormalizeVolume(const string symbol, double volume)
{
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(minLot <= 0.0 || maxLot <= 0.0 || stepLot <= 0.0)
return 0.0; // symbol data not ready
if(volume <= 0.0)
return 0.0;
volume = MathMin(volume, maxLot);
// Round DOWN to the step; the 1e-9 absorbs floating point noise such as 28.999999999.
double steps = MathFloor(volume / stepLot + 1e-9);
volume = steps * stepLot;
if(volume < minLot)
return 0.0; // too small to trade
// Clean the number to the decimals of the step (0.01 -> 2, 0.1 -> 1, 1.0 -> 0).
int decimals = 0;
double scaled = stepLot;
while(decimals < 8 && MathAbs(scaled - MathRound(scaled)) > 1e-9)
{
scaled *= 10.0;
decimals++;
}
return NormalizeDouble(volume, decimals);
}
Two design choices matter here. The helper rounds down, so the risk never grows because of rounding. And it returns 0.0 when the result is below the minimum, and the caller must treat 0.0 as "do not trade". Opening the minimum lot instead would risk more money than the rule allows.
If the zero result comes from a risk formula and not from the rounding, look at the stop distance and the tick value first: MQL5 zero divide error and MQL5 lot size calculation.
Check the Margin Too
A valid volume can still be rejected when the account cannot afford it. The usual return code for that is 10019 (not enough money). You can check it before you send the order with OrderCalcMargin:
bool HasEnoughMargin(const string symbol, double volume, ENUM_ORDER_TYPE type, double price)
{
double margin = 0.0;
if(!OrderCalcMargin(type, symbol, volume, price, margin))
return false; // the calculation failed
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
return (margin > 0.0 && margin <= freeMargin);
}
Pass the ask price for a buy and the bid price for a sell. A margin check also protects the account when the volume comes from a formula that has a bug.
Using the Helper With CTrade
#include <Trade\Trade.mqh>
CTrade trade;
void TryBuy(double requestedLots, double sl, double tp)
{
double lots = NormalizeVolume(_Symbol, requestedLots);
if(lots <= 0.0)
{
PrintFormat("Skipping the trade: volume %.5f is not valid for %s", requestedLots, _Symbol);
return;
}
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(!HasEnoughMargin(_Symbol, lots, ORDER_TYPE_BUY, ask))
{
Print("Skipping the trade: not enough free margin");
return;
}
if(!trade.Buy(lots, _Symbol, 0.0, sl, tp, "bot"))
PrintFormat("Buy failed. Retcode %u: %s",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
}
The stops in this call must also follow the stops level, which is explained in MQL5 invalid stops error 10016.
A Full EA That Only Prints
This EA does not trade. It prints the volume limits of the current symbol and shows what the helper does with a few requested lot sizes, so you can see the effect on your own broker before you connect any trade logic:
input double RequestedLots1 = 0.005;
input double RequestedLots2 = 0.123;
input double RequestedLots3 = 7.77;
double NormalizeVolume(const string symbol, double volume)
{
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(minLot <= 0.0 || maxLot <= 0.0 || stepLot <= 0.0 || volume <= 0.0)
return 0.0;
volume = MathMin(volume, maxLot);
volume = MathFloor(volume / stepLot + 1e-9) * stepLot;
if(volume < minLot)
return 0.0;
int decimals = 0;
double scaled = stepLot;
while(decimals < 8 && MathAbs(scaled - MathRound(scaled)) > 1e-9)
{
scaled *= 10.0;
decimals++;
}
return NormalizeDouble(volume, decimals);
}
int OnInit()
{
PrintFormat("%s: min %.5f, max %.5f, step %.5f, total limit %.5f",
_Symbol,
SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN),
SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX),
SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP),
SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_LIMIT));
double requested[3];
requested[0] = RequestedLots1;
requested[1] = RequestedLots2;
requested[2] = RequestedLots3;
for(int i = 0; i < 3; i++)
PrintFormat("Requested %.5f -> valid volume %.5f (0 means do not trade)",
requested[i], NormalizeVolume(_Symbol, requested[i]));
return INIT_SUCCEEDED;
}
void OnTick()
{
}
Run it on a chart of each symbol you plan to trade. A symbol with a minimum of 1.0 returns 0 for the first two requests, and that is the information you need before the EA places a real order.
Common Causes Checklist
- Volume below the minimum. Check
SYMBOL_VOLUME_MIN. - Volume not on the step. Round down to
SYMBOL_VOLUME_STEP. - Volume above the maximum. Clamp, or split the order.
- A hard-coded lot size that was tested on another symbol or another broker.
- A rounding with
NormalizeDouble(x, 2)on a symbol with another step. - Floating point noise (
28.999999) that drops one step. - A total volume limit (
SYMBOL_VOLUME_LIMIT) that is already used. - The helper returned 0 and the caller sent the order anyway.
- Not enough free margin (10019), which looks similar in a log.
Common Mistakes
- Sending the minimum lot when the risk formula returns less. This risks more than the rule.
- Rounding up instead of down.
- Reading the volume limits once in
OnInitand using them for another symbol. - Ignoring the return value of
trade.Buyortrade.Sell. - Testing only on EURUSD, where the step is usually the smallest.
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: risk-based lots, step by step.
- MQL5 zero divide error: when the formula behind the lot size fails.
- MQL5 possible loss of data due to type conversion: the floating point effect behind
28.999.... - What to check in a trading bot before going live: a pre-live checklist for stops, lot size, spread and trade results.
- MQL5 not enough money error 10019: the volume is valid, but the free margin is not enough.
Fix Compile Errors Without Guessing
If an Expert Advisor does not compile, or you want a second look at its lot size and stop 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.