Rounding to SYMBOL_VOLUME_STEP so orders stop failing with 10014
Posted: Fri Oct 23, 2026 12:59 am
Rounding to SYMBOL_VOLUME_STEP so orders stop failing with 10014
If your MQL5 EA logs TRADE_RETCODE_INVALID_VOLUME, which is retcode 10014, the usual cause is a lot size that doesn't fit the symbol's volume settings. Three values matter:
SYMBOL_VOLUME_MIN, the smallest allowed size, often 0.01.
SYMBOL_VOLUME_MAX, the largest allowed size per order.
SYMBOL_VOLUME_STEP, the increment, often 0.01 but sometimes 0.1 or 1 on metals, indices or certain accounts.
A computed size like 0.6249999 from a risk formula won't pass. Neither will 0.625 if the step is 0.01.
Here's the logic I use:
step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP)
lots = MathFloor(raw / step) * step
lots = NormalizeDouble(lots, digits for the step)
if lots < min, skip the trade
if lots > max, cap at max or split into several orders
The digits for the step come from the step itself: 0.01 needs 2, 0.1 needs 1, 1 needs 0. Floating point means MathFloor(0.62 / 0.01) can occasionally come out as 61 instead of 62, so I add a tiny epsilon, like 1e-9, before flooring.
Worked case: raw size 0.6249, step 0.01. 0.6249 divided by 0.01 is 62.49, floored to 62, times 0.01 is 0.62. Normalized to two digits it's 0.62, and the order passes.
The max check matters more than people think. A large account can produce a size above the symbol max on a tight stop, and the whole order fails.
If your MQL5 EA logs TRADE_RETCODE_INVALID_VOLUME, which is retcode 10014, the usual cause is a lot size that doesn't fit the symbol's volume settings. Three values matter:
SYMBOL_VOLUME_MIN, the smallest allowed size, often 0.01.
SYMBOL_VOLUME_MAX, the largest allowed size per order.
SYMBOL_VOLUME_STEP, the increment, often 0.01 but sometimes 0.1 or 1 on metals, indices or certain accounts.
A computed size like 0.6249999 from a risk formula won't pass. Neither will 0.625 if the step is 0.01.
Here's the logic I use:
step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP)
lots = MathFloor(raw / step) * step
lots = NormalizeDouble(lots, digits for the step)
if lots < min, skip the trade
if lots > max, cap at max or split into several orders
The digits for the step come from the step itself: 0.01 needs 2, 0.1 needs 1, 1 needs 0. Floating point means MathFloor(0.62 / 0.01) can occasionally come out as 61 instead of 62, so I add a tiny epsilon, like 1e-9, before flooring.
Worked case: raw size 0.6249, step 0.01. 0.6249 divided by 0.01 is 62.49, floored to 62, times 0.01 is 0.62. Normalized to two digits it's 0.62, and the order passes.
The max check matters more than people think. A large account can produce a size above the symbol max on a tight stop, and the whole order fails.