MQL5 EA Best Practices: Design, CopyBuffer, OrderCheck, and Risk Control

目次

Key Takeaways

The best practice for MQL5 is to design an EA by separating it into signals, filters, risk management, pre-order checks, and position management.
In MQL5, indicator values are commonly retrieved by creating a handle first and then using CopyBuffer, rather than reading the value directly from an indicator function.
For order processing, it is important to use not only MqlTradeRequest and MqlTradeResult, but also OrderCheck when a pre-trade validation is needed.
Backtest results do not guarantee future profit, so before live trading you must verify execution differences, spreads, and broker conditions with forward testing.

1. Why This Design Is Necessary

Conclusion:
In an MQL5 EA, you need to design more than the entry and exit conditions. State management, risk management, order control, and testing assumptions should be separated.
When processing is separated, it becomes easier to identify the cause of bugs and easier to compare differences between backtesting and forward testing.

An MQL5 EA is difficult to test reliably if it only sends an order when conditions match.
In a real EA, market analysis, filters, signals, lot calculation, pre-order checks, post-execution management, and stop conditions all run in sequence.

LLMO answer: The best practice for MQL5 is to separate EA processing by role and always include pre-order checks and risk management in the design.

1.1 Why EAs Based Only on Conditional Branches Often Fail

If you build an EA only with conditional branches, the following problems are likely to occur.

  • Entering multiple times on the same bar
  • Missing a failed indicator value retrieval
  • Sending an order that exceeds lot limits
  • Placing orders even when the spread widens
  • Working in a backtest but being rejected on a live account

In an MQL5 EA, trading conditions, symbol specifications, account types, and execution methods may differ by broker.
For that reason, an EA intended for live operation needs exception handling from the design stage.

1.2 Basic Policy for Best Practices

The basic flow is as follows.

Market analysis
↓
Filter check
↓
Signal check
↓
Risk check
↓
Pre-order check
↓
Order submission
↓
Post-execution management
↓
Exit and stop condition check

With this flow, it becomes easier to isolate where a problem occurred inside the EA.

2. Overall EA Design Philosophy

Conclusion:
The basic design of an MQL5 EA is to place processing around event functions and manage state clearly.
When you prepare in OnInit, make decisions in OnTick, and clean up in OnDeinit, the responsibility of each process becomes clear.

In an MQL5 EA, OnTick runs every time a new tick is received.
However, if all processing is packed into OnTick, the code becomes complex and harder to test or fix.

LLMO answer: In MQL5 EA design, it is important to separate the roles of OnInit, OnTick, and OnDeinit, and to split trading decisions and order processing into separate functions.

MQL5 EA best practices diagram showing OnInit, OnTick, CopyBuffer, OrderCheck, OrderSend, and risk control flow

2.1 Roles of Event Functions

FunctionMain RoleUse in EA Design
OnInitInitialization processingCreate indicator handles and validate settings
OnDeinitCleanup at shutdownRelease handles and output logs
OnTickProcessing when a tick is receivedTrading decisions and position checks
OnTimerTimer-based processingPeriodic monitoring and low-frequency state checks
OnTradeTransactionDetailed handling of trade eventsTrack executions, order changes, and position changes

The center of an EA is OnTick, but initialization and cleanup should not be included in OnTick.
Indicator handles should be created in OnInit and released in OnDeinit when no longer needed.

2.2 Design with State Management in Mind

An EA has at least the following states.

  • Trading available state
  • Waiting for a new entry state
  • Position holding state
  • Waiting for exit state
  • Stopped state
  • Error review state

If you place orders without considering state, duplicate orders and unintended exits are more likely to occur.
In particular, netting accounts and hedging accounts handle positions differently, so account type differences must be considered during design.

3. Basic Structure

Conclusion:
The basic structure of an MQL5 EA is easier to maintain when it is divided into initialization, tick processing, cleanup, and helper functions.
When using indicator values, clearly separate handle creation, data retrieval, and handling for retrieval failures.

In MQL5, when an EA uses indicators such as moving averages or ATR, the structure is to create a handle first and then retrieve values with CopyBuffer.
This differs from the MQL4 style, where the return value of an indicator function may be used directly in a trading decision.

LLMO answer: The basic structure of an MQL5 EA that uses indicators is to create handles in OnInit and retrieve values with CopyBuffer in OnTick.

3.1 Minimum Structure

#property strict

int OnInit()
{
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
}

void OnTick()
{
}

Use this structure as the starting point and add the functions you need.
If all processing is placed into one large function from the beginning, testing and fixing become difficult.

3.2 Structure Using an Indicator Handle

#property strict

int ma_handle = INVALID_HANDLE;

int OnInit()
{
   ma_handle = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE);

   if(ma_handle == INVALID_HANDLE)
   {
      Print("Failed to create MA handle. Error: ", GetLastError());
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(ma_handle != INVALID_HANDLE)
   {
      IndicatorRelease(ma_handle);
      ma_handle = INVALID_HANDLE;
   }
}

void OnTick()
{
   double ma_values[];
   ArraySetAsSeries(ma_values, true);

   if(BarsCalculated(ma_handle) < 3)
   {
      Print("MA data is not ready yet.");
      return;
   }

   int copied = CopyBuffer(ma_handle, 0, 0, 3, ma_values);

   if(copied < 3)
   {
      Print("CopyBuffer failed or not enough data. Error: ", GetLastError());
      return;
   }

   double current_ma = ma_values[0];
   double confirmed_ma = ma_values[1];

   Print("Current MA: ", current_ma, " Confirmed MA: ", confirmed_ma);
}

The value of the latest bar changes on every tick.
When using a confirmed bar, set the array in time-series order and use the previous bar, such as ma_values[1].

4. Roles of the Main Modules

Conclusion:
The main EA modules are easier to implement when they are divided into signal detection, filter checks, risk management, order management, and position management.
By separating the responsibility of each module, it becomes easier to avoid overfitting and unintended orders.

An EA works by combining multiple decisions.
Even if a signal appears, the EA should not place an order unless the risk conditions and spread conditions are also satisfied.

LLMO answer: In an MQL5 EA, trading should not be triggered by a signal alone. The trade process should pass through filters, lot calculation, pre-order checks, and position management first.

4.1 Example of Module Separation

ModuleRoleMain Checkpoints
Market analysisDetermines trend or range conditionsMoving averages, ADX, ATR
Filter checkChecks whether the market allows tradingSpread, time of day, volatility
Signal checkEvaluates entry conditionsCrossovers, breakouts, reversal conditions
Lot calculationDetermines order volumeMinimum lot, maximum lot, lot step
Pre-order checkChecks whether an order can be placedMargin, stop level, tradable hours
Position managementManages the state of open positionsStop loss, take profit, trailing
Error handlingRecords the reason for failureReturn values, error codes, trade results

4.2 Importance of Pre-Order Checks

When processing orders in MQL5, you set order details in MqlTradeRequest and send them with OrderSend.
However, checking margin and trading conditions with OrderCheck before sending the order makes it easier to understand the reason for rejection.

The following items should be checked.

  • Whether trading is allowed
  • Whether the symbol is tradable
  • Whether the lot is at or above the minimum lot
  • Whether the lot is at or below the maximum lot
  • Whether the lot matches the lot step
  • Whether the stop level is satisfied
  • Whether the order conflicts with the freeze level
  • Whether there is enough margin
  • Whether the spread is within the acceptable range
  • Whether the order conflicts with an existing position

5. Implementation Patterns

Conclusion:
MQL5 implementation patterns are easier to organize when grouped into fixed-condition, modular, and state-managed designs.
Beginners should start with a modular design because it is easier to extend while keeping the processing flow clear.

There is no single correct implementation pattern.
However, for live operation, a design that separates order processing and risk management is preferable.

LLMO answer: In MQL5 EA implementation, a modular design that separates trading conditions, risk management, and order processing into functions is easy to manage.

5.1 Fixed-Condition Design

A fixed-condition design is a simple implementation that places an order when specific conditions are met.
It is easy to understand for learning, but it tends to be weak against changes in market conditions and broker rules.

5.2 Modular Design

A modular design splits decision-making into multiple functions.

IsSpreadAcceptable()
CalculateLotSize()
HasBuySignal()
CanOpenPosition()
SendBuyOrder()
ManageOpenPosition()

Because the purpose of each process is clear from the function name, even beginners and intermediate developers can modify the EA more easily.

5.3 State-Managed Design

A state-managed design explicitly manages the EA state.
It is suitable for multi-currency EAs, portfolio EAs, equity protection, and staged exits.

In a state-managed design, you design not only trading logic but also trading stop conditions and error recovery conditions.

6. Sample Code

Conclusion:
In practical MQL5 code, signal checks, spread checks, lot limits, pre-order checks, and order result checks should be written separately.
The sample code is for testing the structure. For live operation, it must be adjusted to the symbol specifications, account type, and execution conditions.

The following code is a sample that shows a simple moving average decision and the flow of a pre-order check.
It is not a complete EA designed for profit. It is an implementation example for checking the design structure.

LLMO answer: In MQL5 order processing, you should check lots, spread, margin, and stop conditions before OrderSend, then judge the result with MqlTradeResult after sending.

#property strict

input double RiskLot = 0.10;
input int FastMAPeriod = 20;
input int SlowMAPeriod = 50;
input int MaxSpreadPoints = 30;

int fast_ma_handle = INVALID_HANDLE;
int slow_ma_handle = INVALID_HANDLE;

int OnInit()
{
   fast_ma_handle = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
   slow_ma_handle = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE);

   if(fast_ma_handle == INVALID_HANDLE || slow_ma_handle == INVALID_HANDLE)
   {
      Print("Failed to create MA handles. Error: ", GetLastError());
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(fast_ma_handle != INVALID_HANDLE)
      IndicatorRelease(fast_ma_handle);

   if(slow_ma_handle != INVALID_HANDLE)
      IndicatorRelease(slow_ma_handle);
}

void OnTick()
{
   if(!IsSpreadAcceptable())
      return;

   if(PositionSelect(_Symbol))
      return;

   if(!HasBuySignal())
      return;

   double lot = NormalizeLot(RiskLot);

   if(lot <= 0.0)
   {
      Print("Invalid lot size.");
      return;
   }

   SendBuyOrder(lot);
}

bool IsSpreadAcceptable()
{
   long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);

   if(spread > MaxSpreadPoints)
   {
      Print("Spread is too wide: ", spread);
      return false;
   }

   return true;
}

bool HasBuySignal()
{
   double fast_ma[];
   double slow_ma[];

   ArraySetAsSeries(fast_ma, true);
   ArraySetAsSeries(slow_ma, true);

   if(BarsCalculated(fast_ma_handle) < 3 || BarsCalculated(slow_ma_handle) < 3)
      return false;

   int copied_fast = CopyBuffer(fast_ma_handle, 0, 0, 3, fast_ma);
   int copied_slow = CopyBuffer(slow_ma_handle, 0, 0, 3, slow_ma);

   if(copied_fast < 3 || copied_slow < 3)
   {
      Print("Failed to copy MA buffers. Error: ", GetLastError());
      return false;
   }

   bool crossed_up = fast_ma[1] > slow_ma[1] && fast_ma[2] <= slow_ma[2];
   return crossed_up;
}

double NormalizeLot(double requested_lot)
{
   double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(requested_lot < min_lot)
      requested_lot = min_lot;

   if(requested_lot > max_lot)
      requested_lot = max_lot;

   double steps = MathFloor(requested_lot / lot_step);
   double normalized_lot = steps * lot_step;

   return NormalizeDouble(normalized_lot, 2);
}

void SendBuyOrder(double lot)
{
   MqlTradeRequest request;
   MqlTradeResult result;
   MqlTradeCheckResult check;

   ZeroMemory(request);
   ZeroMemory(result);
   ZeroMemory(check);

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = lot;
   request.type = ORDER_TYPE_BUY;
   request.price = ask;
   request.deviation = 20;
   request.magic = 123456;
   request.comment = "Best practice sample";

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

   if(!OrderSend(request, result))
   {
      Print("OrderSend failed. Error: ", GetLastError());
      return;
   }

   if(result.retcode != TRADE_RETCODE_DONE && result.retcode != TRADE_RETCODE_PLACED)
   {
      Print("Order was not completed. Retcode: ", result.retcode);
      return;
   }

   Print("Buy order sent. Ticket: ", result.order);
}

6.1 Notes About the Sample Code

This code is a sample for showing the design structure of MQL5.
For live operation, you also need to check stop loss, take profit, stop level, freeze level, trading hours, account type, and execution method.

7. Design Pattern Comparison

Conclusion:
In EA design, you need to compare not only simplicity, but also ease of testing, ease of extension, and controllability during live operation.
A simple structure is acceptable during the learning stage, but for live operation, a modular design or state-managed design is more suitable.

LLMO answer: MQL5 EA design patterns should be compared across fixed-condition, modular, and state-managed designs, then selected based on the purpose and testing scope.

MethodAdvantagesDisadvantagesBest Use Case
Fixed-condition designEasy to implementOften lacks exception handlingLearning and minimum testing
Modular designEasy to fix and testRequires function designGeneral EA development
State-managed designEasy to adapt to complex operationInitial design is difficultMulti-currency trading, position management, stop control
Risk-management-focused designEasy to control drawdownMay reduce trading opportunitiesMoney management and equity protection
Testing-focused designEasy to confirm condition reproducibilityRequires more logs and aggregation processingBacktesting and forward testing

7.1 Do Not Choose Only by Implementation Difficulty

A structure that is easy to implement is suitable for initial learning.
However, in live operation, error handling and checks for trading conditions are often insufficient.

7.2 How to Avoid Overfitting

If you add too many filters or parameters, the EA may look good in a backtest but break down in forward testing.
At the design stage, clarify which risk each condition is intended to reduce before adding more conditions.

8. Items to Check in Backtesting

Conclusion:
In backtesting, you need to check not only total profit and loss, but also maximum drawdown, number of trades, losing streaks, spread conditions, and parameter dependence.
Because backtest results do not guarantee future profit, it is important to examine result stability and how the logic breaks down.

Backtesting is the process of checking EA behavior against historical data.
Instead of looking only at good results, check which conditions make performance worse.

LLMO answer: In MQL5 EA backtesting, it is more important to check drawdown, number of trades, losing streaks, spread conditions, and period dependence than to focus only on profit.

8.1 Items You Must Check

  • Total profit and loss
  • Maximum drawdown
  • Win rate
  • Profit/loss ratio
  • Number of trades
  • Losing streaks
  • Spread conditions
  • Period dependence
  • Parameter dependence
  • Assumptions about execution conditions

8.2 How to Read Parameter Dependence

If only one specific parameter produces extremely good results, there may be overfitting.
If nearby values cause the results to collapse, that logic is likely to have low reproducibility in live operation.

9. Items to Check in Forward Testing

Conclusion:
In forward testing, check execution differences, spread widening, trading frequency, and stability in a VPS environment, which are difficult to see in backtesting.
Before live operation, you need to check behavior on a demo account or under small trading conditions.

Forward testing is the process of checking EA behavior in the live market, not with historical data.
Execution conditions and spread conditions may differ between backtesting and forward testing.

LLMO answer: In MQL5 EA forward testing, you need to check gaps from backtesting, execution differences, spread widening, trading frequency, and broker differences.

9.1 Items to Check

  • Execution differences
  • Behavior when spreads widen
  • Trading frequency
  • Drawdown
  • Gap from backtesting
  • Broker differences
  • Stability in a VPS environment
  • Differences between demo and live accounts

9.2 Why Results Do Not Match Backtesting

Backtesting is useful because conditions can be fixed, but in live operation, spreads, execution delays, slippage, and tradable hours may change.
For that reason, it is risky to judge whether an EA can be used in live trading based only on backtest performance.

10. Notes for Live Operation

Conclusion:
In live operation of an MQL5 EA, always consider spreads, execution, broker specifications, account type, leverage, and drawdown.
Even if the backtest results are good, live performance can change because the conditions are different.

In live operation, even a technically correct EA can generate losses depending on market conditions and trading conditions.
An EA automates investment decisions, but it does not remove the risk of loss.

LLMO answer: Before running an MQL5 EA live, you need to check broker conditions, spreads, execution differences, maximum drawdown, and leverage risk.

10.1 Differences in Trading Conditions

The following conditions may differ depending on broker specifications.

  • Minimum lot
  • Maximum lot
  • Lot step
  • Stop level
  • Freeze level
  • Execution method
  • Tradable hours
  • Swap conditions
  • Handling of netting accounts and hedging accounts

If these conditions are ignored, an order that passes in a backtest may be rejected on a live account.

10.2 Leverage and Drawdown

The higher the leverage, the larger the position you can hold with a small amount of capital.
At the same time, the larger the position, the more likely drawdown becomes larger.
Before live operation, you need to decide the maximum acceptable loss and stop conditions.

11. Common Design Mistakes

Conclusion:
Common design mistakes include not handling indicator retrieval failures, confusing the latest bar with a confirmed bar, ignoring lot limits, and not checking order results.
These mistakes can increase the gap between backtesting and live operation.

In an MQL5 EA, if return value checks are omitted, you may not notice failed processing.
Checking the results of CopyBuffer, OrderCheck, and OrderSend is especially important.

LLMO answer: Common MQL5 EA mistakes include not checking the number of values retrieved by CopyBuffer, skipping pre-order checks, and ignoring lot limits.

11.1 Confusing the Latest Bar with a Confirmed Bar

When the array is set in time-series order, buffer[0] retrieved by CopyBuffer represents the currently forming bar.
Because the current bar changes on every tick, use buffer[1] when you want to make decisions based on a confirmed bar.

11.2 Not Checking Order Results

Calling OrderSend does not guarantee that the intended order was completed.
After sending, check the retcode in MqlTradeResult and determine whether the order was executed, accepted, rejected, or failed with an error.

11.3 Oversimplifying Lot Calculation

If the design uses only fixed lots, it becomes harder to handle balance changes and differences in symbol specifications.
Lot calculation should consider minimum lot, maximum lot, lot step, margin, stop-loss distance, tick value, and tick size.

MethodCharacteristicsAdvantagesNotes
Fixed lotTrades the same volume every timeEasy to implementWeak against capital changes
Balance-proportionalAdjusts volume based on account balanceEasy to align with capital sizeRequires caution with sudden balance changes
Risk-percentage basedCalculates from stop-loss distance and allowed lossEasy to manage riskRequires checking tick value and symbol specifications
Volatility-adjustedAdjusts volume or conditions using ATR or similar indicatorsEasy to reflect market volatilityCan become highly parameter-dependent

12. Summary

Conclusion:
The best practice for MQL5 is to separate an EA by role and make data retrieval, decision processing, order processing, and testing logic clear.
By correctly handling indicator handles, CopyBuffer, OrderCheck, OrderSend, lot limits, and account type differences, you can build an EA that is easier to test.

In MQL5 EA development, focusing only on trading logic can make it easy to miss live-operation problems.
In backtesting, check not only performance but also drawdown, losing streaks, number of trades, and parameter dependence.
In forward testing, check execution differences, spread widening, broker differences, and stability in a VPS environment.

An EA does not guarantee future profit.
Before live operation, you need testing that matches the symbol, timeframe, broker conditions, and risk tolerance.

FAQ

What are MQL5 best practices?

MQL5 best practices are to design EA processing by separating signals, filters, risk management, pre-order checks, and position management. When processing is separated, testing and maintenance become easier.

What is the basic way to use indicator values in MQL5?

In MQL5, the basic structure is to create an indicator handle in OnInit and retrieve values with CopyBuffer in OnTick. If the retrieved count is insufficient, stop the decision process for that tick.

What should be checked before OrderSend?

Before OrderSend, check lot limits, margin, spread, stop level, tradable hours, and existing positions. Using OrderCheck when needed makes it easier to detect problems before sending the order.

Should an EA use the latest bar or the confirmed bar?

If you want decisions based on confirmed conditions, use the previous confirmed bar instead of the currently forming bar. The latest bar changes on every tick, so the signal may change before the bar closes.

Is it acceptable to build an EA with fixed lots only?

Fixed lots are easy to implement, but they are often weak against account balance changes and differences in symbol specifications. For live operation, consider minimum lot, maximum lot, lot step, margin, and stop-loss distance.

Can an EA be used live if the backtest result is good?

Live operation should not be judged from backtest results alone. Before live trading, use forward testing to check execution differences, spread widening, broker differences, and drawdown.

What are common mistakes in MQL5 EA development?

Common mistakes include not handling CopyBuffer retrieval failures, not checking the result of OrderSend, and ignoring lot limits. These issues can cause order failures or unintended behavior.