- 1 Key Takeaways
- 2 1. Why This Design Is Necessary
- 3 2. Overall EA Design Philosophy
- 4 3. Basic Structure
- 5 4. Roles of the Main Modules
- 6 5. Implementation Patterns
- 7 6. Sample Code
- 8 7. Design Pattern Comparison
- 9 8. Items to Check in Backtesting
- 10 9. Items to Check in Forward Testing
- 11 10. Notes for Live Operation
- 12 11. Common Design Mistakes
- 13 12. Summary
- 14 FAQ
- 14.1 What are MQL5 best practices?
- 14.2 What is the basic way to use indicator values in MQL5?
- 14.3 What should be checked before OrderSend?
- 14.4 Should an EA use the latest bar or the confirmed bar?
- 14.5 Is it acceptable to build an EA with fixed lots only?
- 14.6 Can an EA be used live if the backtest result is good?
- 14.7 What are common mistakes in MQL5 EA development?
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.

2.1 Roles of Event Functions
| Function | Main Role | Use in EA Design |
|---|---|---|
OnInit | Initialization processing | Create indicator handles and validate settings |
OnDeinit | Cleanup at shutdown | Release handles and output logs |
OnTick | Processing when a tick is received | Trading decisions and position checks |
OnTimer | Timer-based processing | Periodic monitoring and low-frequency state checks |
OnTradeTransaction | Detailed handling of trade events | Track 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
| Module | Role | Main Checkpoints |
|---|---|---|
| Market analysis | Determines trend or range conditions | Moving averages, ADX, ATR |
| Filter check | Checks whether the market allows trading | Spread, time of day, volatility |
| Signal check | Evaluates entry conditions | Crossovers, breakouts, reversal conditions |
| Lot calculation | Determines order volume | Minimum lot, maximum lot, lot step |
| Pre-order check | Checks whether an order can be placed | Margin, stop level, tradable hours |
| Position management | Manages the state of open positions | Stop loss, take profit, trailing |
| Error handling | Records the reason for failure | Return 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.
| Method | Advantages | Disadvantages | Best Use Case |
|---|---|---|---|
| Fixed-condition design | Easy to implement | Often lacks exception handling | Learning and minimum testing |
| Modular design | Easy to fix and test | Requires function design | General EA development |
| State-managed design | Easy to adapt to complex operation | Initial design is difficult | Multi-currency trading, position management, stop control |
| Risk-management-focused design | Easy to control drawdown | May reduce trading opportunities | Money management and equity protection |
| Testing-focused design | Easy to confirm condition reproducibility | Requires more logs and aggregation processing | Backtesting 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.
| Method | Characteristics | Advantages | Notes |
|---|---|---|---|
| Fixed lot | Trades the same volume every time | Easy to implement | Weak against capital changes |
| Balance-proportional | Adjusts volume based on account balance | Easy to align with capital size | Requires caution with sudden balance changes |
| Risk-percentage based | Calculates from stop-loss distance and allowed loss | Easy to manage risk | Requires checking tick value and symbol specifications |
| Volatility-adjusted | Adjusts volume or conditions using ATR or similar indicators | Easy to reflect market volatility | Can 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.