Your EA says it’s risking 2% per trade. Your account statement says otherwise. On a client’s XAUUSD setup last year, a “2% risk” EA was actually opening positions at 4.1% — doubling the intended exposure on every trade for weeks before anyone noticed. The lot size calculation had three bugs. All three compiled clean, passed every backtest, and survived demo testing.
Most EAs calculate position size incorrectly because they confuse `Point` with tick size, bypass platform-provided tick value with manual formulas, and skip lot step rounding. The broker silently adjusts the lot without returning an error, so the trader never knows their actual risk diverges from their intended risk.
The Risk You Think You’re Taking vs. the Risk You Actually Are
When lot size is wrong, the dollar risk per trade silently drifts from the trader’s target. A 2% risk setting might execute as 0.8% or 4.5% — the trader has no way to tell from the terminal alone, because the order fills successfully either way.
I see this pattern in roughly a third of code rescue projects at barmenteros FX — about 15 out of the last 40 over the past two years. The XAUUSD client above had been live for six weeks. The EA calculated 0.15 lots per trade. Correct was 0.08 lots. Every single trade risked double the intended amount. The trader only discovered the problem when a losing streak hit harder than his risk model predicted — because his risk model was based on a number his EA never actually used.
The root cause is always one of three formula errors. Sometimes all three at once.

Failure #1 — Point vs. Tick Size in Lot Size Calculation
MQL’s `Point` returns the smallest price increment for the current symbol. On a 5-digit EURUSD broker, `Point` = 0.00001. A pip is 0.0001. That is a factor-of-10 difference.
Here is the formula I find in most broken EAs:
lots = AccountBalance * RiskPercent / (SL_pips * Point * TickValue)The bug is `Point`. If the developer means “pip value” but writes `Point`, the result is 10x too large on 5-digit forex pairs. On XAUUSD, depending on broker configuration, the error can be 10x or 100x — because `Point` on gold is often 0.01 while the trader thinks in dollar-per-ounce moves.
The fix is straightforward: stop using `Point` as a pip proxy. Either define pip size explicitly per instrument category, or — better — work in ticks throughout the calculation and use `SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)` instead of `Point`. Ticks are unambiguous. They represent the actual minimum price movement the broker recognizes, with no interpretation needed.
This is the most common of the three failures. It is also the one that produces the largest errors — off by a full order of magnitude on standard instruments. If your EA works on EURUSD but breaks on everything else, this is often the first place to look.
Failure #2 — Manual Pip Value Instead of TickValue
MQL provides `SYMBOL_TRADE_TICK_VALUE` — the monetary value of one tick for a 1.0 lot position, already converted to the account currency at the current exchange rate. It handles USD accounts, EUR accounts, cross pairs, gold, indices — everything. It updates every tick.
Many EAs do not use it. Instead, they hardcode pip value or calculate it manually:
double pipValue = 10.0; // "works for EURUSD on a USD account"Or the more ambitious version:
double pipValue = lotSize * Point / MarketInfo(Symbol(), MODE_POINT);These manual formulas encode assumptions about the account currency, the instrument category, and the broker’s contract size. They work on the developer’s test account — typically a USD-denominated EURUSD setup — and break everywhere else. A EUR-denominated account gets a pip value that is off by the current EUR/USD exchange rate. A cross pair like GBPJPY on a USD account requires a conversion through a third currency that the manual formula does not perform.
The other trap: `TickValue` returns zero when the market is closed (weekends, holidays) or when the broker’s conversion pair feed is unavailable. An EA that reads `TickValue` at the wrong moment — inside `OnInit()` on a Sunday, for example — gets zero, and the lot size formula divides by zero or returns infinity. The EA should read `TickValue` immediately before `OrderSend()` during active trading hours, and treat a zero value as a hard stop: do not place the trade.
Failure #3 — Lot Step and Lot Normalization
Brokers enforce three constraints on lot size: minimum lot, maximum lot, and lot step. The lot step is the smallest allowed increment — typically 0.01 for retail accounts, sometimes 0.1 or 1.0 for certain instruments.
If the formula produces 0.087 lots and the lot step is 0.01, the broker must round. The problem: different brokers round differently. Some floor (0.087 becomes 0.08). Some round to nearest (0.087 becomes 0.09). The EA has no control over which rounding the broker applies unless it normalizes the value itself before sending the order.
The EA should do this:
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double lots = MathFloor(rawLots / step) * step;`MathFloor` always rounds down, guaranteeing the trader never risks more than intended. Then clamp to the broker’s min/max:
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
lots = MathMax(minLot, MathMin(maxLot, lots));Without this normalization, the order still executes — the broker adjusts silently. The trader sees a filled position and assumes everything matched the EA’s calculation. It did not.
The Silent Acceptance Problem
This is what makes lot size bugs uniquely persistent. Other EA errors produce feedback. Place a stop loss too close — the broker returns error 130. Insufficient margin — error 134. Invalid price — error 138. The EA can catch these, log them, retry with corrected values.
Lot size adjustment produces no error. `OrderSend()` returns success. The trade ticket is valid. The position appears in the terminal. Nothing in the EA’s error handling detects the discrepancy, because there is no discrepancy from the broker’s perspective — it received a valid order and filled it.
The only way to detect the mismatch: compare the lot size the EA calculated with the lot size on the actual order. In MQL5, that means checking `HistoryDealGetDouble(dealTicket, DEAL_VOLUME)` against the value the EA intended to send. Most EAs never make this comparison. The bug runs indefinitely.
This is why lot size errors survive every testing stage. They compile. They pass backtest (Strategy Tester uses the same silent adjustment). They pass demo testing. They run live without errors. The trader’s risk is wrong on every trade, and the only symptom is a P&L that does not match the expected risk profile — a signal that takes weeks or months to notice.
The Correct Lot Size Calculation
Here is a lot size function that handles all three failures:
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;
}Three things to note:
- `slPoints`is in price distance, not pips. Pass `Ask – stopLossPrice` (or equivalent) directly. No pip conversion needed — the function handles tick conversion internally using `SYMBOL_TRADE_TICK_SIZE`.
- `TickValue`is read at call time. This function should be called immediately before `OrderSend()`, not cached at initialization. If `TickValue` returns zero (market closed, feed unavailable), the function returns zero — no trade.
- `MathFloor`ensures the lot never exceeds intended risk. The trader may risk slightly less than 2% after rounding — never more.
Check Your Own EA
Pull up your last five trades. For each one, compare the lot size your EA logged (or calculated) with the lot size in your account history. If they match exactly, your EA handles normalization correctly. If they differ — even by 0.01 — one of the three failures above is active, and your actual risk per trade does not match what you configured. A [position size calculator](https://barmenteros.com/mt4-position-size-calculator/) can help you cross-check the expected values independently.
The fix is a single function. The cost of not fixing it is every trade at the wrong size, compounding silently, for as long as the EA runs.
If you suspect your EA has one of these issues — or you want a second pair of eyes on the position sizing logic — send us your code for a free assessment. We review EA code regularly and can identify lot size bugs in a single session.


Leave a Reply