Three lot size bugs that survive every backtest

Three lot size bugs that survive every backtest

20 July 2026, 21:47
Boris Armenteros
0
37

The client who ran double risk for six weeks

Last year I reviewed a client's XAUUSD Expert Advisor. The risk input read 2% per trade. The actual exposure per position was 4.1%. Every trade for six weeks had been roughly double the intended size. The formula compiled, passed backtest, and ran live without a single error.

This is not unusual. In about 15 out of the last 40 code rescue projects I have worked on, the lot size formula was the primary defect. Three specific errors account for nearly all of them.

Point is not a pip

The most common failure. On a 5-digit EURUSD broker, Point  returns 0.00001. A pip is 0.0001. That is a 10x difference. On XAUUSD, depending on how the broker configures the symbol, the error can be 10x or 100x.

The broken version:

lots = AccountBalance * RiskPercent / (SL_pips * Point * TickValue);

The fix: use SYMBOL_TRADE_TICK_SIZE  instead of Point . Ticks are unambiguous — they represent the broker's actual minimum price movement.

double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);

Manual pip value vs TickValue

MQL provides SYMBOL_TRADE_TICK_VALUE  — the monetary value of one tick for 1.0 lot, already converted to account currency and updated every tick. Many EAs ignore it:

double pipValue = 10.0;  // "works for EURUSD on USD account"

This breaks on EUR-denominated accounts, cross pairs like GBPJPY, and any instrument where the contract size differs from the developer's test setup.

One additional trap: TickValue  returns 0 when the market is closed or when the broker's conversion feed is unavailable. An EA that reads this value inside OnInit()  on a Sunday gets zero, and the lot size formula divides by zero or returns infinity. The function should read TickValue  immediately before OrderSend()  and treat zero as a hard stop — do not place the trade.

Lot step normalization

Brokers enforce minimum lot, maximum lot, and lot step. If the formula outputs 0.087 and the step is 0.01, the broker rounds — and different brokers round differently.

The EA should handle this before sending the order:

double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double lots = MathFloor(rawLots / step) * step;

double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
lots = MathMax(minLot, MathMin(maxLot, lots));

MathFloor  always rounds down. The trader may risk slightly less than the target — never more.

Why these bugs persist

Other errors produce feedback. Error 130 for stop level violations. Error 134 for margin. Error 138 for invalid price. Lot size adjustment produces nothing — OrderSend()  returns success, the position fills, the ticket is valid. Nothing in standard error handling catches it.

The only way to detect the mismatch: compare the calculated lot with the actual deal volume via HistoryDealGetDouble(dealTicket, DEAL_VOLUME) . Most EAs never make this comparison.

Complete function

double CalculateLotSize(const string symbol, const double riskPercent, const double slPoints)
{
   double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskMoney  = balance * riskPercent / 100.0;

   double tickSize   = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue  = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);

   if(tickSize == 0 || tickValue == 0)
      return 0;

   double ticks      = slPoints / tickSize;
   double rawLots    = riskMoney / (ticks * tickValue);

   double step       = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   double lots       = MathFloor(rawLots / step) * step;

   double minLot     = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maxLot     = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   lots              = MathMax(minLot, MathMin(maxLot, lots));

   return lots;
}

Pass slPoints  as price distance (e.g., Ask - stopLossPrice ), not pips. Call this immediately before OrderSend() , not at initialization.

Quick diagnostic

Pull up your last five trades. Compare the lot size your EA calculated (or logged) with the lot size in your account history. If they match exactly, your normalization is correct. If they differ — even by 0.01 — one of these three failures is active.