MQL5 invalid volume Error: Causes, Fixes, and Safe Lot Normalization

目次

1. What Is the MQL5 “invalid volume” Error?

1.1 Definition of the invalid volume error

The “invalid volume” error in MQL5 occurs when the lot size (volume) specified for an order does not match the broker’s trading conditions.

Specifically, it can occur in processes such as the following.

  • OrderSend() (sending an order)
  • OrderCheck() (pre-trade validation)
  • Trading functions in the CTrade class, such as Buy() and Sell()

Inside MQL5, this error is handled as the following return code.

TRADE_RETCODE_INVALID_VOLUME

The meaning is simple:
“The specified lot size is invalid.”

However, this “invalid” value is not always a simple typo.
It usually means a strict violation of broker-specific trading rules.


1.2 Why it happens (root causes)

The invalid volume error occurs when any of the following conditions apply.

  • The lot is below the minimum lot size (for example, below 0.01)
  • The lot violates the lot step (for example, an uneven value such as 0.015)
  • The lot exceeds the maximum lot size
  • The lot is too large for the available margin and is effectively treated as invalid

The key point is that lot size is not a number you can choose freely.

In MQL5, each currency pair or symbol has the following constraints.

ItemDescription
Minimum lotThe smallest tradable unit
Maximum lotThe upper limit allowed for one order
Lot stepThe unit by which the lot can increase or decrease

These values differ by broker and by symbol.

That means the same code can commonly behave like this:

  • FX pair → works normally
  • CFD → error
  • Cryptocurrency → error

1.3 Error code and when it occurs

The invalid volume error is mainly detected at the following timings.

1. OrderCheck (pre-trade validation)

MqlTradeCheckResult check;
OrderCheck(request, check);
  • Checks the order before it is actually sent
  • Ideally, the error should be detected at this stage

2. OrderSend (actual order submission)

MqlTradeResult result;
OrderSend(request, result);
  • The error occurs when the order is actually submitted
  • The error appears in the log

3. CTrade class

CTrade trade;
trade.Buy(volume);
  • Uses OrderSend internally
  • When an error occurs, check the return value and logs

Common stumbling points (important)

Beginners often get stuck on the following points.

■ The value looks correct but still causes an error

Example:

double lot = 0.01;

At first glance, this looks correct. However, if the broker has:

  • a minimum lot of 0.1
  • a step of 0.1

then it will be treated as invalid.


■ Assuming NormalizeDouble solves the issue

NormalizeDouble(lot, 2);

This is decimal rounding, not a guarantee that the value matches the lot step.


■ It works in backtesting but fails in live trading

Causes:

  • Specification differences between the tester and the live account
  • Differences in margin requirements

■ Symbol information is not being retrieved

SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);

If you do not use this,
the implementation is likely ignoring lot constraints.


Practical notes

  • Lot specifications are not fixed; they depend on the broker
  • They can differ even between symbols at the same broker
  • The error may suddenly appear after moving to a VPS or changing accounts

Therefore,
lot size must always be retrieved and adjusted dynamically.

2. Main Causes of invalid volume (in Priority Order)

Key point of this section:
This section covers the causes so you can quickly identify which one applies to your case. Check them from top to bottom to resolve the issue efficiently.


2.1 Below the minimum lot size (SYMBOL_VOLUME_MIN violation)

This is the most common cause.
If the specified lot is below the minimum lot size, the order immediately causes an error.

Example

double lot = 0.001;

If the broker specification is:

  • Minimum lot: 0.01

→ invalid volume


Correct way to retrieve it

double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);

Common mistakes

  • Setting the lot with a fixed value
  • Breaking when the currency pair changes
  • Especially common with cryptocurrencies and CFDs

Notes

  • The minimum lot can also vary by account type
  • Micro accounts and standard accounts may have different rules

2.2 Lot step violation (SYMBOL_VOLUME_STEP)

This is the point where beginner and intermediate developers most often get stuck.

A lot is not a free-form decimal number.
It can only be specified in the broker-defined step size.


Example

  • step: 0.01
  • specified lot: 0.015

→ invalid volume


Correct retrieval

double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

Bad pattern

double lot = 0.015;  // Looks OK, but it is not valid

Common misconceptions

  • “Two decimal places is OK” → incorrect
  • “NormalizeDouble makes it OK” → incorrect

Practical notes

  • Gold (GOLD) and indices may use a step of 0.1
  • Cryptocurrencies may use even more unusual step values

2.3 Exceeding the maximum lot size (SYMBOL_VOLUME_MAX)

This is often overlooked, but
exceeding the maximum lot size also causes an error.


Example

double lot = 200.0;

If:

  • Maximum lot: 100.0

→ invalid volume


How to retrieve it

double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

Common patterns where it occurs

  • Averaging-down logic that increases lot size
  • Martingale systems
  • Compound growth lot logic

Notes

  • The maximum lot depends on the broker
  • It differs by currency pair or symbol

2.4 Insufficient free margin (effectively invalid volume)

Even if the volume appears correct,
insufficient margin may cause it to be treated as invalid volume.


Pattern

double lot = 1.0;
  • There is not enough margin → Rejected by OrderCheck

How to check

MqlTradeCheckResult check;
OrderCheck(request, check);

Common misconception

  • “Insufficient margin is a separate error” → Depending on the environment, it may be handled as a volume-related issue

Practical point

  • It passes in backtesting
  • It fails in live trading, which is a common pattern

2.5 Symbol specifications not retrieved (SymbolInfo not used)

This is a fundamental design mistake.

If you decide the lot size without retrieving symbol information, problems are almost guaranteed to occur.


Bad example

double lot = 0.01;  // Fixed value

Correct direction

double min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double max = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

Why it is dangerous

  • Specifications differ by broker
  • The same EA can break in a different environment
  • It can cause trouble in long-term operation

Common stumbling points (summary)

  • Not considering step → most frequent cause
  • Fixed lot → breaks depending on the environment
  • Ignoring margin → error in live trading
  • Depending on NormalizeDouble → incomplete

Priority check order in real projects (important)

When the error appears, check the following in order:

  1. SYMBOL_VOLUME_MIN
  2. SYMBOL_VOLUME_STEP
  3. SYMBOL_VOLUME_MAX
  4. OrderCheck (margin)
  5. Whether symbol specifications are being retrieved

3. How to Fix invalid volume (with Copy-Paste Code)

Key point of this section:
This section provides an implementation that absorbs broker-dependent constraints and always generates a valid lot size. The focus is reproducibility.


3.1 Basic solution approach (important)

You can reliably solve invalid volume issues with the following three steps.

  1. Retrieve the symbol specifications
  2. Adjust the lot within the allowed constraints
  3. Validate it with OrderCheck

If you implement this flow,
you can eliminate most errors caused by environment differences.

MQL5 invalid volume error prevention diagram showing lot normalization and OrderCheck validation before OrderSend, including SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX, SYMBOL_VOLUME_STEP constraints and execution flow from lot calculation to trade order success

3.2 Lot normalization function (most important code)

The following is a practical, safe lot normalization function.

double NormalizeLot(double lot)
{
   double min  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double max  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   // 1. Minimum and maximum limits
   lot = MathMax(min, lot);
   lot = MathMin(max, lot);

   // 2. Round down to match the step
   lot = MathFloor(lot / step) * step;

   // 3. Round as a precaution
   lot = NormalizeDouble(lot, 8);

   return lot;
}

3.3 Usage example (practical code)

double lot = 0.015;  // Original lot

lot = NormalizeLot(lot);

// Place order
CTrade trade;
trade.Buy(lot);

Point-by-point explanation

  • MathFloor() → prevents step violations
  • MathMax/Min() → prevents out-of-range values
  • NormalizeDouble() → handles floating-point error

3.4 Pre-trade validation with OrderCheck (recommended)

This is essential for live operation.

MqlTradeRequest request;
MqlTradeCheckResult check;

request.volume = lot;
request.symbol = _Symbol;
request.action = TRADE_ACTION_DEAL;
request.type   = ORDER_TYPE_BUY;

if(!OrderCheck(request, check))
{
   Print("OrderCheck failed: ", check.comment);
}

Benefits

  • Detects errors before order submission
  • Makes the cause clear in the log
  • Greatly improves debugging efficiency

3.5 Lot calculation that considers margin (advanced)

For a safer implementation, also consider margin.

double free_margin = AccountInfoDouble(ACCOUNT_FREEMARGIN);

// Simple example (adjustment may be required depending on the environment)
double lot = free_margin / 100000.0;

lot = NormalizeLot(lot);

Notes

  • Margin calculation depends on the broker
  • The result changes depending on leverage
  • For strict calculation, it is safer to use OrderCalcMargin()

3.6 Common bad implementations to avoid

Bad pattern 1: Using only NormalizeDouble

lot = NormalizeDouble(lot, 2);

→ This does not prevent step violations.


Bad pattern 2: Fixed lot

double lot = 0.01;

→ This breaks depending on the environment.


Bad pattern 3: Checking only min

if(lot < min) lot = min;

→ This can still cause a step violation error.


Common stumbling points

  • Forgetting the existence of step → the most frequent mistake
  • Using MathRound → may round up and exceed max
  • Breaking with CFDs or cryptocurrencies
  • Sudden errors after VPS migration

Recommended practical setup

Implement the following as a set:

  • NormalizeLot function
  • OrderCheck
  • SymbolInfo retrieval

This gives you:

  • Reproducibility: high
  • Risk: low
  • Maintainability: high

Supplement: design-level view

Essentially, “lot size” should be treated as a calculated result, not an input.

In other words:

  • User input → bad design
  • Environment-dependent value → retrieve dynamically

This is the correct design direction.

4. Common Failure Patterns in Real Projects

Key point of this section:
invalid volume is not caused only by an incorrect lot value. It also recurs because of weak design or implementation. This section organizes the failures that often appear in real projects.


4.1 Handling it with only NormalizeDouble

One of the most common mistakes is assuming that matching the number of decimal places makes the lot valid.

For example:

double lot = 0.015;
lot = NormalizeDouble(lot, 2);

At first glance, it becomes 0.02, so it may look fine.
In reality, this is not an adjustment based on the lot step (SYMBOL_VOLUME_STEP).

Be careful in environments like the following.

  • step = 0.1
  • min = 0.1
  • max = 50.0

In this case, 0.02 is still invalid.
In other words, NormalizeDouble() is closer to display rounding and does not validate whether the value meets the trading conditions.

Common misconceptions

  • All FX pairs accept two decimal places → wrong
  • Using NormalizeDouble() makes it safe → insufficient

Notes

  • step is not always 0.01
  • CFDs, indices, and cryptocurrencies are especially likely to break this assumption

4.2 Using a fixed lot as-is

The next common case is hard-coding the lot as a fixed value.

double lot = 0.01;
trade.Buy(lot);

This may pass in a test environment but is likely to fail in live trading. The reason is simple: trading conditions differ by broker and by symbol.

For example, a 0.01 lot is valid only when that symbol and account type have a minimum lot of 0.01.
In another environment, you may see differences such as:

  • minimum lot is 0.1
  • step is 0.1
  • some symbols only allow 1.0 units

In that case, the same EA will produce invalid volume.

Common failures

  • Assuming that because it passed testing, it will also pass live trading
  • Checking only currency pairs and applying the same settings to other symbols
  • Not suspecting the EA after an account change causes a problem

Practical conclusion

Do not treat lot size as a fixed value. Design it so it is always adjusted using symbol information.


4.3 Not considering margin

Even if min, max, and step are correct, the order can fail because of insufficient margin.
In some environments or implementations, this appears as a volume-related problem, which makes the cause easy to misread.

For example:

  • The lot matches the step
  • The lot is within min / max
  • But the required margin is not available

In this case, reviewing only the lot value will not solve the root cause.

Process to check

MqlTradeCheckResult check;
OrderCheck(request, check);

Common failures

  • Being satisfied after implementing only volume adjustment
  • Skipping OrderCheck()
  • Ignoring margin because the backtest passes

Notes

  • Live accounts are strongly affected by leverage conditions
  • The result can change with the same lot depending on account balance and open positions
  • This is especially common in averaging-down and compound-growth EAs

4.4 Ignoring specification differences by currency pair or symbol

Beginners often overlook that lot specifications differ by symbol.
Even if an EA is written around _Symbol, the nature of that _Symbol is not always the same.

Differences are especially likely in:

  • FX currency pairs
  • Gold
  • Stock indices
  • Cryptocurrency CFDs
  • Energy CFDs

The following values can vary greatly:

  • SYMBOL_VOLUME_MIN
  • SYMBOL_VOLUME_STEP
  • SYMBOL_VOLUME_MAX

Typical failure examples

  • Using an EA built for EURUSD directly on GOLD
  • Placing orders on BTC-related symbols while assuming 0.01 lots
  • Making the step value common across symbols

Practical countermeasures

  • Retrieve specifications for each symbol every time
  • Output them to the log in OnInit and check them
  • Normalize lots separately for each symbol in multi-symbol EAs

4.5 Not checking the lot after normalization

Even if you have a normalization function, it becomes hard to identify the cause if you do not log the final lot after adjustment.

For example, after normalization:

  • Expected value: 0.015
  • Actual value: 0.01
  • Or it was adjusted to 0.1

If you do not know this difference, you cannot trace why the order is not executed or why the lot is larger than expected.

Recommended log example

double min  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

Print("min=", min, " max=", max, " step=", step, " lot=", lot);

Notes

  • Many live incidents take longer to investigate because of missing information
  • Logs make reproducibility easier to verify

Summary of common stumbling points

EAs where invalid volume keeps recurring often have these traits:

  • They depend on NormalizeDouble()
  • They keep lot size as a fixed value
  • They do not use OrderCheck()
  • They do not absorb symbol-specific differences
  • They have no logs, so normalized results cannot be checked

If any of these five apply, redesigning the lot handling is usually faster than applying a temporary patch.

5. Design Best Practices to Prevent invalid volume

Key point of this section:
Do not stop at a one-time fix. Build a design that prevents recurrence. This section covers practical measures for environment differences, long-term operation, and multiple accounts.


5.1 Treat lot size as a “calculated result,” not an “input value”

This is the most important concept.

A lot is not a fixed value. It is a result determined by the trading environment.


Bad design

double lot = 0.01;

This is an “input value,” and it breaks as soon as the environment changes.


Good design (basic structure)

double base_lot = 0.01;  // Base value
double lot = NormalizeLot(base_lot);

Or:

double lot = CalculateLotByRisk();
lot = NormalizeLot(lot);

Practical points

  • Do not use user input as-is
  • Always insert a normalization layer
  • Manage the “final lot” as a separate variable

5.2 Retrieve symbol specifications every time (do not cache them)

It may look efficient to cache these values, but
symbol specifications can change.


Values to retrieve

SYMBOL_VOLUME_MIN
SYMBOL_VOLUME_MAX
SYMBOL_VOLUME_STEP

Bad patterns

  • Retrieving them only once in OnInit
  • Fixing them in global variables
  • Sharing them across multiple symbols

Recommended

double min  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

Retrieve them whenever they are needed.


Structural reasons

  • Broker specification changes
  • Symbol changes
  • Server reconnection
  • VPS migration

These events can change the conditions.


5.3 Always run OrderCheck (live-operation assumption)

This is essential for live operation.


Recommended flow

lot = NormalizeLot(lot);

MqlTradeCheckResult check;
if(!OrderCheck(request, check))
{
   Print("Check error: ", check.comment);
   return;
}

Why it is necessary

  • It can detect errors other than volume
  • It helps avoid insufficient margin before order submission
  • It prevents live trading incidents

Common misconceptions

  • “It passed testing, so it is unnecessary” → incorrect
  • “Remove it to make the EA lighter” → incorrect

5.4 Build logging into the design (debugging assumption)

Operational trouble will occur at some point.
For that reason, your design must assume logging from the beginning.


Minimum logs to output

Print("Symbol=", _Symbol);
Print("min=", min, " max=", max, " step=", step);
Print("lot(before)=", raw_lot, " lot(after)=", lot);

Important points

  • Record the difference before and after normalization
  • Record symbol information
  • Output the error code as well

Effects

  • The cause can be identified quickly
  • Reproducibility improves
  • External environment effects can be separated from code issues

5.5 Design for multiple symbols

An EA is not always used on only one symbol.
In real operation, multi-symbol support is often standard.


Bad design

double min = 0.01;  // Fixed

Good design

double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);

Notes

  • Hard-coding _Symbol can be risky
  • Passing symbol as an argument is preferable
  • This is essential for multi-symbol EAs

5.6 Treat test and live environments separately

Many incidents happen here.


Common problem

  • Backtest → OK
  • Demo account → OK
  • Live account → invalid volume

Causes

  • Different lot specifications
  • Different leverage
  • Different margin requirements

Countermeasures

  • Test on a demo account with conditions close to live trading
  • Compare logs
  • Check the OrderCheck result

5.7 Relationship between risk management and lot size (important)

Lot size is not just a technical parameter.
It is risk management itself.


Recommended design

double risk_percent = 1.0;  // 1%
double lot = CalculateLotByRisk(risk_percent);
lot = NormalizeLot(lot);

Benefits

  • Drawdown (DD) control
  • Adjustment to account balance changes
  • Improved stability in long-term operation

Notes

  • The calculation logic depends on the broker
  • Adjustment is needed for each currency pair or symbol

Common stumbling points

  • Stopping the design at “it works”
  • Continuing to keep lot size as a fixed value
  • Skipping OrderCheck
  • Not outputting logs

All of these are typical patterns that become costly later.


Practical summary (important)

The shortest route to preventing invalid volume is:

  1. Implement NormalizeLot
  2. Retrieve SymbolInfo every time
  3. Run OrderCheck
  4. Output logs

If these four points are satisfied,
the chance of recurrence drops significantly.

6. Summary and Fastest Resolution Checklist

Key point of this section:
This section compresses the article into practical judgment criteria so even beginners can solve the issue without getting lost.


6.1 The true nature of invalid volume

invalid volume is not just an ordinary error.
It means “a violation of the broker’s trading rules.”

In other words, the cause is often:

  • not simply that the code is wrong
  • but that it does not fit the environment

Without this understanding, the error will keep recurring.


6.2 Fastest resolution checklist

When the error appears, check the following from top to bottom.


Check 1: Does it meet the minimum lot?

double min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
  • Is lot >= min?
  • Also check differences by account type

Check 2: Does it match the lot step?

double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
  • Does lot % step == 0?
  • Are you normalizing by step instead of only using NormalizeDouble?

Check 3: Does it exceed the maximum lot?

double max = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
  • Is lot <= max?
  • Does averaging-down or compound-growth logic push it above max?

Check 4: Are you running OrderCheck?

OrderCheck(request, check);
  • Check check.comment
  • Consider the possibility of insufficient margin

Check 5: Have you implemented lot normalization?

lot = NormalizeLot(lot);
  • Are you still using a fixed value as-is?
  • Does the logic support the step value?

6.3 Fastest practical fix code (reposted)

If you are unsure, use this implementation.

double NormalizeLot(double lot)
{
   double min  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double max  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   lot = MathMax(min, lot);
   lot = MathMin(max, lot);
   lot = MathFloor(lot / step) * step;

   return NormalizeDouble(lot, 8);
}

6.4 Common misconceptions (review)

  • NormalizeDouble alone is enough → incorrect
  • 0.01 works everywhere → incorrect
  • If it passes testing, it is fine → incorrect
  • All FX specifications are the same → incorrect

6.5 Important practical points

■ Reproducibility

  • Will it work when the environment changes?
  • Will it still work after VPS migration?

■ Risk management

  • Can lot size grow out of control?
  • Can it cause insufficient margin?

■ Maintainability

  • Can another person understand it?
  • Can the cause be traced from logs?

6.6 Conclusion (important)

The solution to invalid volume is simple.

Treat lot size as an environment-adjusted value, not a fixed value.

That is the core idea.

By changing to this design:

  • Error rate: greatly reduced
  • Debugging cost: greatly reduced
  • Long-term operational stability: improved

FAQ

Q1. Why does invalid volume suddenly occur?

A. It occurs suddenly because lot conditions can change when the broker, account type, or symbol changes.


Q2. Why is NormalizeDouble alone not enough?

A. NormalizeDouble only rounds decimal places. It does not guarantee that the lot matches the broker’s step size.


Q3. Is matching only the minimum lot enough?

A. No. The lot must also satisfy the step and maximum lot requirements.


Q4. Why does it work in backtesting but fail in live trading?

A. The live account may have different margin conditions and lot specifications from the backtest environment.


Q5. Why are CFDs and cryptocurrencies more likely to cause this error?

A. Their lot steps and minimum units often differ greatly from standard FX pairs.


Q6. Is OrderCheck required?

A. Yes, it is required for live operation because it can detect errors before sending the order.


Q7. Is it bad to use a fixed lot value?

A. It is not recommended. Fixed lots can fail depending on the broker or symbol, so normalization is necessary.


Q8. What is the safest countermeasure?

A. The safest approach is to retrieve specifications with SymbolInfo, normalize the lot with NormalizeLot, and validate the order with OrderCheck.