FX NOOKAI CODE LAB
Loading

MQL5 Lot Size Calculator: Risk-Based Volume Code

By FXNook Lab team

To calculate lot size from a risk percentage in MQL5, estimate the loss a one-lot trade would take at the stop with OrderCalcProfit, divide your chosen cash risk by that loss, then round down to the symbol’s permitted volume step. The function below uses account equity as its risk base, respects the broker’s minimum, maximum, and volume step, and returns no trade when the valid volume would be below the minimum.

Use this MQL5 function to calculate risk-based volume

Pass the symbol, buy or sell order type, intended entry and stop prices, and risk percentage. If the function succeeds, volume contains a valid volume rounded down to the symbol’s step. If it returns false, leave the trade unsubmitted and inspect the inputs or symbol settings.

bool CalculateRiskVolume(const string symbol,
                         const ENUM_ORDER_TYPE orderType,
                         const double entryPrice,
                         const double stopPrice,
                         const double riskPercent,
                         double &volume)
{
   volume = 0.0;

   if(riskPercent <= 0.0 || entryPrice <= 0.0 || stopPrice <= 0.0)
      return false;

   if(orderType != ORDER_TYPE_BUY && orderType != ORDER_TYPE_SELL)
      return false;

   if(orderType == ORDER_TYPE_BUY && stopPrice >= entryPrice)
      return false;
   if(orderType == ORDER_TYPE_SELL && stopPrice <= entryPrice)
      return false;

   double equity = AccountInfoDouble(ACCOUNT_EQUITY);
   if(equity <= 0.0)
      return false;

   double oneLotProfit = 0.0;
   if(!OrderCalcProfit(orderType, symbol, 1.0, entryPrice,
                       stopPrice, oneLotProfit))
   {
      PrintFormat("OrderCalcProfit failed for %s. Error: %d",
                  symbol, GetLastError());
      return false;
   }

   double oneLotLoss = -oneLotProfit;
   if(oneLotLoss <= 0.0)
      return false;

   double riskCash = equity * riskPercent / 100.0;
   double rawVolume = riskCash / oneLotLoss;

   double volumeMin = 0.0;
   double volumeMax = 0.0;
   double volumeStep = 0.0;

   if(!SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN, volumeMin) ||
      !SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX, volumeMax) ||
      !SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP, volumeStep))
      return false;

   if(volumeMin <= 0.0 || volumeMax < volumeMin || volumeStep <= 0.0)
      return false;

   // Do not round a below-minimum risk amount up to the minimum lot.
   if(rawVolume < volumeMin)
      return false;

   double cappedVolume = MathMin(rawVolume, volumeMax);
   double stepCount = MathFloor(cappedVolume / volumeStep + 1e-10);
   double roundedVolume = NormalizeDouble(stepCount * volumeStep, 8);

   if(roundedVolume < volumeMin || roundedVolume > volumeMax)
      return false;

   volume = roundedVolume;
   return true;
}

The function deliberately accepts only ORDER_TYPE_BUY and ORDER_TYPE_SELL, because it is sizing a market-direction trade from entry to stop. A buy stop must be below the entry; a sell stop must be above it. The order direction and prices passed to OrderCalcProfit must match the trade you intend to make.

AccountInfoDouble(ACCOUNT_EQUITY) makes the risk amount respond to current equity. For a balance-based risk rule, replace it with AccountInfoDouble(ACCOUNT_BALANCE). Choose the basis your EA specification calls for, and make that choice explicit in logs and inputs rather than treating balance and equity as interchangeable.

The function returns false for unusable risk inputs, an invalid stop direction, a failed profit calculation, unavailable volume properties, or a risk-based volume below the broker minimum. A false result means the caller must not submit the trade with a fallback minimum lot.

Work through a sample risk and stop-loss calculation

Suppose an EA has account equity of $10,000 and is set to risk 0.5% on a buy trade. The selected entry and stop are passed to OrderCalcProfit, which hypothetically returns a loss of $253.81 for a one-lot position. These figures are illustrative: the result depends on the symbol, prices, contract details, and account currency.

  1. Calculate the cash risk: $10,000 × 0.5 ÷ 100 = $50.
  2. Divide cash risk by the one-lot loss: $50 ÷ $253.81 ≈ 0.197 lots.
  3. If the symbol’s volume step is 0.01, round down to 0.19 lots rather than rounding to 0.20.
  4. The estimated stop loss at 0.19 lots is about $48.22, before execution effects.

Rounding down keeps the estimated loss at or below the selected $50 risk amount. Rounding to the nearest step could increase volume, and therefore estimated loss, above that amount. The calculation is based on the chosen entry and stop prices; if either price changes, recalculate volume.

If the same calculation produces raw volume of 0.007 lots but the broker minimum is 0.01, the function returns false. Setting the order to 0.01 would exceed the requested risk under those inputs, so skipping the trade is the straightforward way to preserve the risk limit.

Why OrderCalcProfit is useful across symbols and account currencies

OrderCalcProfit estimates the profit or loss for a specified order type, symbol, volume, opening price, and closing price. Its result is expressed in the account’s deposit currency, so the function can use a one-lot loss directly without manually converting a pip value into account currency. For details on the function’s parameters and return value, see the MQL5 OrderCalcProfit documentation.

That matters because pip or tick value is not a single universal number. Contract size, tick size, calculation mode, conversion rates, and the account’s deposit currency can all affect the money value of a price move. Hard-coding a pip value for one forex pair can give the wrong volume on a different pair, a metal, an index, or an account in another currency.

The one-lot method asks the terminal to calculate the monetary result for the exact symbol and prices you intend to use. The function then scales that result by the cash risk. It is still an estimate: the trade can open at a different price, close beyond the stop during a gap, or incur costs that are not represented by the price-to-stop calculation. Tested or historical results do not guarantee future results.

If OrderCalcProfit fails, the function logs the terminal error code and stops sizing. A common cause is an unavailable or incorrectly specified symbol. Make sure the symbol name is the broker’s actual name and that the terminal can access its market data; for an EA that trades symbols other than the chart symbol, select and retrieve data for those symbols deliberately.

Read the symbol’s minimum, maximum, and volume step

MQL5 exposes the relevant broker volume constraints as SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX, and SYMBOL_VOLUME_STEP. The function reads them with SymbolInfoDouble. If the property lookup fails or the values are unusable, it returns no volume rather than guessing.

PropertyWhat it meansHow the function uses it
SYMBOL_VOLUME_MINSmallest permitted trade volume.Rejects a risk-based amount that cannot reach the minimum without rounding up.
SYMBOL_VOLUME_MAXLargest permitted trade volume for the symbol.Caps the raw volume before rounding down.
SYMBOL_VOLUME_STEPPermitted increment for trade volume.Rounds the capped amount down to a whole number of steps.

Capping at the maximum may mean the EA risks less than its chosen percentage: if the raw calculation is larger than the permitted maximum, the capped volume cannot reach the target risk. That is preferable to exceeding the broker limit. Log this case if the distinction matters to your EA, and do not increase volume through another path.

Volume-step validation is also why simply calling NormalizeDouble(rawVolume, 2) is not a complete solution. The symbol may use a step other than 0.01, and decimal formatting alone does not guarantee that a volume is on the permitted step. The calculation floors the number of steps, then applies decimal normalization to limit floating-point residue.

For a related MT4 issue, see OrderSend Error 131: Fix Invalid Volume in MT4. MT4 and MT5 expose different trading models and APIs, but the same basic mistake applies: a calculated volume still has to satisfy the symbol’s rules.

Pass the calculated volume into an MQL5 trade request

The calculator does not submit an order, validate that the stop is allowed by the broker, or guarantee execution. The caller must provide the entry and stop it actually intends to send, then handle the result from CTrade. The following helper shows the connection for a buy; it assumes the CalculateRiskVolume function above is included in the EA.

#include <Trade/Trade.mqh>

CTrade trade;

bool TryRiskSizedBuy(const double stopPrice, const double riskPercent)
{
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol, tick))
      return false;

   double entryPrice = tick.ask;
   double volume = 0.0;

   if(!CalculateRiskVolume(_Symbol, ORDER_TYPE_BUY, entryPrice,
                           stopPrice, riskPercent, volume))
   {
      Print("Buy skipped: risk volume could not be calculated.");
      return false;
   }

   trade.SetTypeFillingBySymbol(_Symbol);
   bool sent = trade.Buy(volume, _Symbol, entryPrice, stopPrice, 0.0,
                         "risk-sized buy");

   if(!sent)
   {
      PrintFormat("Buy request failed. Retcode: %u, %s",
                  trade.ResultRetcode(), trade.ResultRetcodeDescription());
      return false;
   }

   return true;
}

This helper captures the current ask and uses it both for sizing and for the request. A market price can still move before execution, so the eventual loss may differ from the estimate. If the EA changes the entry or stop before sending, it should calculate the volume again with those updated prices. For a sell, use the bid as the entry and call the calculator with ORDER_TYPE_SELL.

A successful method call is not the same as a filled trade. Inspect ResultRetcode() and the returned description, and handle broker rejections such as invalid volume or invalid stops in the EA’s order flow. Stop-distance and filling-mode requirements are separate from volume sizing.

Common lot-size calculation mistakes

MistakeWhy it causes troubleBetter approach
Confusing pips with points.A point is the symbol’s quoted price increment; pip conventions vary by instrument and quote format. A distance counted in the wrong unit changes the assumed stop loss.Pass actual entry and stop prices to OrderCalcProfit instead of converting a manually assumed pip value.
Using the wrong entry or stop price.A different entry or stop changes the one-lot loss and therefore the calculated volume.Use the intended executable price and the same stop price that will be sent with the request.
Ignoring the volume step.A decimal-rounded value can still be invalid for the symbol, producing an order rejection.Read the symbol’s volume properties and floor to a whole step.
Forcing the minimum lot.If the calculated amount is below minimum, rounding up can put estimated risk above the selected amount.Reject the trade, or make any alternative risk rule explicit rather than silently changing it.
Treating balance and equity as the same risk base.Open positions can make equity differ from balance, so the cash risk calculated from each can differ too.Choose balance or equity deliberately and use the selected account value consistently.

Test the calculator in MetaEditor and Strategy Tester

  1. Paste the function into an MQL5 EA in MetaEditor and compile it. Resolve compiler errors before testing the trade flow.
  2. Log the symbol, order type, entry, stop, account value used, risk percentage, one-lot result, raw volume, and final volume. For deeper diagnostics, add those values to the function or log them in the caller.
  3. Print the results of SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX, and SYMBOL_VOLUME_STEP for each traded symbol. Confirm that the rounded output is permitted by those properties.
  4. In Strategy Tester, try both buy and sell calculations, several stop distances, and risk amounts that produce volumes above and below the minimum. Verify that a below-minimum result leads to no order.
  5. Test symbols with different contract and tick-value behavior, and test with the account currency used by the intended trading environment. Inspect the tester’s order volume and retcodes, not just the function’s printed calculation.
  6. Test order handling separately: an allowed volume does not prove the stop distance, filling mode, trading permission, or market price is acceptable.

A clean compile only shows that MetaEditor accepted the source; it does not prove that the EA’s runtime inputs or order requests are correct. Use the tester’s journal and trade list to compare the requested volume with the symbol constraints and to confirm that rejected calculations never turn into orders.

If you are adding this calculator to a larger EA, keep the risk basis, entry-price source, stop rules, and below-minimum behavior visible as part of the strategy specification. The FXNook AI EA builder can build an MT4 or MT5 Expert Advisor from a description or confirmed specification, then compile and check it. The practical next step is to run this function in MetaEditor and Strategy Tester with the actual symbol properties and account settings you plan to use.

Questions

How do I calculate lot size from risk percentage in MQL5?

Choose a cash risk amount by multiplying your selected account basis, such as equity, by the risk percentage. Use OrderCalcProfit to estimate the loss on one lot from the intended entry to the stop, divide the cash risk by that loss, and round the result down to the symbol’s volume step. Reject the trade if the resulting valid volume is below the broker’s minimum.

How do I get the correct lot size for symbols with different tick values?

Use OrderCalcProfit for the specific symbol, order direction, volume, entry, and stop instead of applying a hard-coded pip value. It returns the estimated profit or loss in the account’s deposit currency, which lets the EA size across symbols using the same risk calculation. The estimate can still differ from realized loss because execution prices and costs can vary.

What should an MQL5 EA do if the calculated volume is below the broker’s minimum?

The safest default is to skip the trade and record why the volume was rejected. Raising it to the minimum can exceed the selected risk, so the EA should not do that silently. If the strategy permits a different risk rule, make that behavior explicit and test it separately.