DEV Community

Omais Ali
Omais Ali

Posted on

Why your MQL5 lot size gets rejected, and how to normalize volume and prices properly

A risk based lot calculation almost never produces a valid number. You compute 0.4372 lots, send it, and the trade server answers with TRADE_RETCODE_INVALID_VOLUME. Prices have the same problem: a stop at 1.083467 on a symbol that moves in steps of 0.00005 gets rejected as an invalid price.

NormalizeDouble(x, 2) looks like the fix, but it only rounds to a number of decimals. It knows nothing about the symbol's real step size, and brokers set those differently.

Normalizing volume

Every symbol has a minimum, a maximum and a step. Round down to the step so you never risk more than you calculated, then clamp to the limits.

double NormalizeVolume(const string symbol, double lots)
{
   double minLot  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);

   if(stepLot <= 0.0)
      return 0.0;

   lots = MathFloor(lots / stepLot + 1e-9) * stepLot;

   if(lots < minLot)
      return 0.0;   // too small to trade: skip the signal instead of forcing the minimum
   if(lots > maxLot)
      lots = maxLot;

   int digits = (int)MathMax(0, MathCeil(-MathLog10(stepLot)));
   return NormalizeDouble(lots, digits);
}
Enter fullscreen mode Exit fullscreen mode

Two details matter here. The small 1e-9 stops floating point noise from turning 0.30 into 0.29. And returning 0 when the result is below the minimum is deliberate: silently bumping a 0.004 lot up to 0.01 can more than double the risk you planned.

Normalizing prices

Prices must be multiples of the tick size, which is not always the same as the 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)
      return NormalizeDouble(price, digits);

   return NormalizeDouble(MathRound(price / tickSize) * tickSize, digits);
}
Enter fullscreen mode Exit fullscreen mode

On most forex symbols tick size equals the point, so this changes nothing. On indices, metals and some crypto CFDs the tick size can be 0.25, 0.5 or 5 points, and this is exactly where EAs that worked fine on EURUSD start failing.

Check margin before sending

A valid volume can still be one the account cannot afford. Ask the terminal first:

double margin;
if(!OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lots,
                    SymbolInfoDouble(_Symbol, SYMBOL_ASK), margin))
   return;
if(margin > AccountInfoDouble(ACCOUNT_MARGIN_FREE))
{
   Print("Not enough free margin for ", lots, " lots");
   return;
}
Enter fullscreen mode Exit fullscreen mode

A quick test

Run the EA in the Strategy Tester on three symbols with different specs, for example EURUSD, XAUUSD and an index. Print the raw lot, the normalized lot and the volume step for the first few trades. If any trade is rejected, the log shows you which value was off before you ever go live.

These three functions are small, but they remove one of the most common reasons an EA works for one broker and fails for the next.

Top comments (0)