Blog
MQL5 Lot Size Calculation: EA Position Sizing Guide
MQL5 Lot Size Calculation: Risk-Based Position Sizing for an EA
Quick Answer
To calculate an MQL5 lot size from a risk percentage, first define the cash amount you are willing to risk and the trade's entry and stop-loss prices. Use OrderCalcProfit() to estimate the loss for one lot between those prices, divide the risk amount by that loss, then round the result down to the symbol's permitted volume step. If the calculated volume is below the broker's minimum, skip the trade rather than rounding up and exceeding the intended risk.
When I first started algo trading, I blew my account in 3 days because I was using a fixed lot size without calculating risk-based position sizing. That painful lesson taught me why lot size calculation is the most important part of any EA. Here is exactly how I do it now...
Key Facts
| Sizing step | What to use | Why it matters |
|---|---|---|
| Risk budget | Account balance or equity × risk percentage | Defines the planned cash risk |
| Stop distance | Planned entry price to stop price | A wider stop generally means smaller volume |
| One-lot loss | OrderCalcProfit() for 1.0 lot | Accounts for the symbol's contract and account-currency conversion assumptions |
| Valid volume | SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX, SYMBOL_VOLUME_STEP | Broker volume constraints can make the raw calculation invalid |
| Final check | Margin, costs, and trade result | A sizing calculation does not guarantee an order will be accepted or limit realized loss |
Choosing volume is not just a matter of entering 0.10 lots in an EA. The same lot size can represent very different cash exposure across forex pairs, metals, indices, and other instruments. A fixed volume also does not adapt when the stop distance changes. Risk-based sizing connects position volume to the amount at risk at the planned stop.
For retail algo traders, this can make risk more consistent across setups. Prop firm traders should also account for their own daily loss and drawdown limits; position sizing by itself cannot ensure compliance or prevent a loss beyond a stop during gaps or fast markets.
The Risk-Based Lot Size Formula
The general calculation is:
cash risk = account value × risk percentage / 100
lots = cash risk / estimated loss for 1.0 lot at the stop
For example, if a strategy sets a risk budget of 50 units of account currency and the estimated loss for one lot at the stop is 250, the raw volume is 50 / 250 = 0.20 lots. This is only an illustration: actual values depend on the account, symbol, prices, and trading costs.
The stop-based loss per lot is more informative than price distance alone. OrderCalcProfit() estimates the profit or loss for a specified order type, symbol, volume, open price, and close price in the account currency. That can help avoid hand-written tick-value conversions, which are easy to get wrong across different contract specifications or account currencies.
Balance or equity?
Balance reflects closed-account results; equity includes floating profit and loss. An EA can base its risk budget on either, but should use the choice intentionally and consistently. For example, equity-based sizing changes as open positions move. Neither choice replaces portfolio-level limits for total exposure across multiple EAs.
Pine Script and MQL5: Position Size Examples
Pine strategies can set quantity directly on an order. MQL5 EAs typically calculate volume explicitly and must respect the symbol's volume rules. These examples illustrate the difference; they do not guarantee identical live risk because entry fills, currency conversion, commissions, and execution conditions can differ.
Pine Script (v6), simplified percentage-risk sizing:
//@version=6
strategy("Risk-based sizing example", overlay = true)
riskPercent = input.float(1.0, "Risk per trade (%)", minval = 0.01)
stopPercent = input.float(1.0, "Stop distance (%)", minval = 0.01)
stopDistance = close * stopPercent / 100.0
riskCash = strategy.equity * riskPercent / 100.0
qty = riskCash / (stopDistance * syminfo.pointvalue)
if ta.crossover(ta.ema(close, 10), ta.ema(close, 30))
strategy.entry("Long", strategy.long, qty = qty)
strategy.exit("Long SL", from_entry = "Long",
stop = close - stopDistance)
This simplified formula assumes syminfo.pointvalue and the strategy's quantity units express the price move's value in the strategy account currency. Verify those assumptions for the instrument and account. A Pine order can also fill at a different price from the close used to calculate the example's stop and quantity.
MQL5, calculate and normalize volume for a planned stop:
int VolumeDigits(const double step)
{
for (int digits = 0; digits <= 8; digits++)
{
if (MathAbs(step - NormalizeDouble(step, digits)) < 1e-8)
return digits;
}
return 8;
}
double CalculateRiskVolume(const ENUM_ORDER_TYPE orderType,
const string symbol,
const double entryPrice,
const double stopPrice,
const double riskPercent)
{
if (riskPercent <= 0.0 || entryPrice <= 0.0 || stopPrice <= 0.0)
return 0.0;
if (orderType != ORDER_TYPE_BUY && orderType != ORDER_TYPE_SELL)
return 0.0;
if ((orderType == ORDER_TYPE_BUY && stopPrice >= entryPrice) ||
(orderType == ORDER_TYPE_SELL && stopPrice <= entryPrice))
return 0.0;
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskCash = equity * riskPercent / 100.0;
double oneLotResult = 0.0;
if (equity <= 0.0 || riskCash <= 0.0)
return 0.0;
if (!OrderCalcProfit(orderType, symbol, 1.0,
entryPrice, stopPrice, oneLotResult))
{
Print("OrderCalcProfit failed. Error: ", GetLastError());
return 0.0;
}
if (oneLotResult >= 0.0)
return 0.0;
double lossPerLot = -oneLotResult;
if (lossPerLot <= 0.0)
return 0.0;
double minVolume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxVolume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double volumeStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if (minVolume <= 0.0 || maxVolume <= 0.0 || volumeStep <= 0.0)
return 0.0;
double rawVolume = riskCash / lossPerLot;
double cappedVolume = MathMin(rawVolume, maxVolume);
double volume = MathFloor(cappedVolume / volumeStep) * volumeStep;
volume = NormalizeDouble(volume, VolumeDigits(volumeStep));
if (volume < minVolume)
{
Print("Calculated volume is below the symbol minimum; trade skipped.");
return 0.0;
}
return volume;
}
Pass the actual symbol and the intended entry and stop prices. For a market buy, an EA will commonly use the current Ask as its planned entry; for a market sell, the Bid. Prices can change before execution, so calculate or re-check volume close to order submission. The helper rounds volume down to a valid step and returns zero when even the minimum permitted volume would exceed the requested risk budget.
Broker Volume Rules and Common Mistakes
Read the symbol properties rather than assuming every instrument accepts 0.01 lots:
SYMBOL_VOLUME_MINis the minimum permitted trade volume.SYMBOL_VOLUME_MAXis the maximum permitted volume for a deal.SYMBOL_VOLUME_STEPis the increment accepted by the symbol.
Normalize downward after calculating raw volume. Rounding to the nearest step can increase risk above the amount requested. If volume is below the minimum, skipping is safer than silently forcing it up to the minimum.
Other common pitfalls include:
- Using only stop distance: a 100-point stop does not represent the same cash loss on every symbol or account currency.
- Mixing units: distinguish price distance, points, ticks, tick size, tick value, and lots. They are related but not interchangeable.
- Ignoring execution costs: commissions, spread, swaps, slippage, and gaps can make realized loss larger than the planned stop loss.
- Assuming a stop guarantees a fill price: stop orders may execute at a worse price in fast or gapping markets.
- Ignoring margin: valid volume can still require more free margin than the account has. Check margin and inspect the trade server's result before treating an order as successful.
- Sizing each EA independently: multiple positions can create combined exposure that is much larger than the risk on one trade.
Before live use, verify the symbol's properties in the terminal, test calculations on a demo account, and log the inputs and returned volume for representative trades. Then review behavior across different stop sizes and instruments.
Frequently Asked Questions
How do I calculate lot size from risk percentage in MQL5?
Calculate the cash risk from account balance or equity and the selected percentage. Use OrderCalcProfit() to estimate the loss for one lot from planned entry to stop, divide cash risk by that loss, then round volume down to the symbol's step. If the result is below minimum volume, do not force it upward.
Should an EA use balance or equity for position sizing?
Either can be used, but the choice changes how the risk budget behaves while positions are open. Balance is based on closed results; equity includes floating profit and loss. Document the policy and make sure other account-level exposure limits are handled separately.
Does risk-based sizing guarantee the trade will lose no more than the selected percentage?
No. It estimates loss at a planned stop price. Gaps, slippage, spread, commissions, swaps, rejected or partially filled orders, and other open positions can change actual loss. Backtest, forward-test, and monitor the EA; do not treat a sizing formula as a guarantee.
Add Sizing Logic, Then Test It
Position sizing is part of the strategy, not a cosmetic order setting. Review symbol specifications, expected costs, and the stop price used in the calculation, and test the EA in MT5 Strategy Tester and on a demo account before considering live execution. For related workflows, see the Pine Script to MQL5 conversion guide and our pricing page.
CodeFlowOS can help translate Pine Script logic into compiler-verified MQL5 output. Treat converted code as a starting point: confirm the risk model and volume calculations match your intent, then validate them under realistic test conditions.