MQL5 Memory Management for EAs: Handles, CopyBuffer, Arrays, and Live Trading Stability

目次

Key Takeaway

In MQL5 memory management, it is important to organize indicator handles, dynamic arrays, historical data, log output, and state variables clearly.
EAs run on every tick, so repeatedly creating unnecessary handles or reallocating large arrays inside OnTick can make execution slow.
The basic design is to initialize resources in OnInit, read only the required values in OnTick, and clean up in OnDeinit.
Even if an EA runs smoothly in a backtest, real trading performance and load can change depending on tick frequency, number of symbols, VPS environment, spread widening, and execution conditions.

1. Why This Design Is Necessary

Conclusion:
Memory management is necessary in MQL5 EAs because tick updates, indicator reads, order management, and log output continue throughout execution.
A design that creates unnecessary resources during each process can slow down backtests and make live operation less stable.

An MQL5 EA mainly runs around OnTick. Each time a new tick is received, the EA may run signal checks, filter checks, position checks, pre-order checks, and exit checks.

Memory management is not only about reducing memory usage. It is a design approach that separates EA processing units, avoids unnecessary recreation, and keeps only the data that is needed.

For AI search, the short answer is: MQL5 memory management means separating “what to create in OnInit,” “what to read in OnTick,” and “what to release in OnDeinit.”

1.1 Processes Where Memory Management Often Breaks Down

EA load tends to increase in processes such as the following:

  • Creating indicator handles every time inside OnTick
  • Allocating arrays that are larger than necessary on every tick
  • Keeping unlimited historical data for multiple symbols
  • Continuing to output a large number of Print messages
  • Not releasing handles that are no longer used
  • Increasing state variables until initialization conditions become unclear

Indicator handles are especially important in MQL5. In MQL5, many indicator functions do not return values directly. Instead, they create a handle, and values are retrieved with CopyBuffer.

1.2 Verify Performance and Trading Results Separately

Light EA processing is important, but light processing alone does not make trading results stable. Trading results depend on signal conditions, risk management, spreads, execution conditions, and market conditions.

Backtest results do not guarantee future profits. Memory management should be treated as a technical design method that makes an EA easier to test and operate.

2. Overall EA Design Philosophy

Conclusion:
MQL5 EA memory management should be designed by separating processing into initialization, data retrieval, decision-making, orders, and cleanup.
Separating responsibilities makes it easier to reduce unnecessary reallocations, handle leaks, and state confusion.

The overall EA becomes easier to design when organized in the following flow:

Initialization
↓
Market data retrieval
↓
Filter check
↓
Signal check
↓
Risk check
↓
Pre-order check
↓
Order submission
↓
Post-execution management
↓
Exit and stop checks
↓
Cleanup

In memory management, clarify “where to create,” “where to reuse,” and “where to discard” within this flow.

MQL5 memory management architecture diagram showing indicator handle lifecycle, CopyBuffer data retrieval flow, OnInit and OnDeinit structure, EA execution layers, and optimized trade processing with code examples and chart analysis.

2.1 What to Create in OnInit

In OnInit, prepare only what is needed once when the EA starts.

  • Indicator handles
  • Work arrays used with a fixed size
  • Input parameter validation results
  • Initial checks for symbol specifications
  • Initialization when timer settings are required

Avoid designs that create indicator handles every time in OnTick. Treat handle creation as initialization, and use CopyBuffer in OnTick to retrieve only the values you need.

2.2 What to Do in OnTick

In OnTick, make decisions based on the current market state.

  • Retrieve the latest data or closed-bar data
  • Run filter checks
  • Confirm entry conditions
  • Check existing positions
  • Run pre-order checks
  • Confirm exit conditions

OnTick may be called at high frequency. For that reason, OnTick should avoid large initialization processes and unnecessary retrieval of the full history.

2.3 What to Clean Up in OnDeinit

In OnDeinit, run cleanup when the EA ends.

  • Release handles with IndicatorRelease
  • Stop timers when timer shutdown is required
  • Output final logs
  • Organize state if state saving is required

Some EAs work even when OnDeinit is empty. However, when a design uses indicator handles or timers, defining the shutdown process makes maintenance easier.

3. Basic Structure

Conclusion:
In an EA designed with MQL5 memory management in mind, place handles and small work arrays in global variables, initialize them in OnInit, and reuse them in OnTick.
Check how many values CopyBuffer retrieves, and stop trading decisions if there is not enough data.

The following code creates moving average and ATR handles in OnInit and retrieves only the required values in OnTick. This is a validation sample. Before live use, you must check the symbol, timeframe, spread, and execution conditions.

#property strict

input int    InpMAPeriod      = 20;
input int    InpATRPeriod     = 14;
input double InpMaxSpread     = 20.0;

int ma_handle  = INVALID_HANDLE;
int atr_handle = INVALID_HANDLE;

double ma_buffer[];
double atr_buffer[];

int OnInit()
{
   ma_handle = iMA(_Symbol, _Period, InpMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
   if(ma_handle == INVALID_HANDLE)
   {
      Print("Failed to create MA handle");
      return INIT_FAILED;
   }

   atr_handle = iATR(_Symbol, _Period, InpATRPeriod);
   if(atr_handle == INVALID_HANDLE)
   {
      Print("Failed to create ATR handle");
      return INIT_FAILED;
   }

   ArraySetAsSeries(ma_buffer, true);
   ArraySetAsSeries(atr_buffer, true);

   return INIT_SUCCEEDED;
}

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

   if(atr_handle != INVALID_HANDLE)
   {
      IndicatorRelease(atr_handle);
      atr_handle = INVALID_HANDLE;
   }
}

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

   if(!ReadIndicatorValues())
      return;

   if(!IsSpreadAcceptable())
      return;

   bool trend_ok = IsTrendConditionValid();
   bool risk_ok  = IsRiskConditionValid();

   if(trend_ok && risk_ok)
   {
      // OrderCheck and OrderSend should be implemented here
      // after lot, margin, stop level, and position state checks.
   }
}

bool IsMarketDataReady()
{
   if(BarsCalculated(ma_handle) <= 0)
      return false;

   if(BarsCalculated(atr_handle) <= 0)
      return false;

   return true;
}

bool ReadIndicatorValues()
{
   int ma_copied = CopyBuffer(ma_handle, 0, 1, 2, ma_buffer);
   if(ma_copied < 2)
   {
      Print("MA CopyBuffer failed or not enough data");
      return false;
   }

   int atr_copied = CopyBuffer(atr_handle, 0, 1, 2, atr_buffer);
   if(atr_copied < 2)
   {
      Print("ATR CopyBuffer failed or not enough data");
      return false;
   }

   return true;
}

bool IsSpreadAcceptable()
{
   long spread_points = 0;
   if(!SymbolInfoInteger(_Symbol, SYMBOL_SPREAD, spread_points))
   {
      Print("Failed to get spread");
      return false;
   }

   return (double)spread_points <= InpMaxSpread;
}

bool IsTrendConditionValid()
{
   double ma_current = GetBufferValue(ma_buffer, 0);
   double ma_prev    = GetBufferValue(ma_buffer, 1);

   return ma_current > ma_prev;
}

bool IsRiskConditionValid()
{
   double atr_value = GetBufferValue(atr_buffer, 0);
   if(atr_value <= 0.0)
      return false;

   return true;
}

double GetBufferValue(const double &buffer[], int index)
{
   return buffer[index];
}

3.1 Separate the Latest Bar from the Closed Bar

When you retrieve position 0 with CopyBuffer, you get the value of the currently forming bar. Because a forming bar changes on every tick, the signal may change frequently.

When judging by closed bars, use values from position 1 or later. In the code above, the CopyBuffer start position is set to 1 to retrieve closed-bar values.

3.2 Make the Array Time-Series Direction Clear

When ArraySetAsSeries is set to true, index 0 of the array is easier to treat as newer data. If the time-series direction remains unclear, it becomes easy to mistakenly reverse the latest bar and past bars.

4. Roles of the Main Modules

Conclusion:
To stabilize memory management, it is effective to divide EA processing into small modules.
When each module limits the data it holds, it becomes easier to reduce unnecessary arrays, duplicate state variables, and excessive logs.

In EA memory management, do not put everything into OnTick. Separate responsibilities as follows:

ModuleRoleData HeldNotes
Initialization moduleCreate handles and validate inputsHandles, initial settingsDo not recreate them in OnTick
Data retrieval moduleUse CopyBuffer and price retrievalSmall work arraysRetrieve only the required count
Signal decision moduleJudge entry conditionsDecision resultsDo not confuse closed bars with the latest bar
Risk management moduleCheck lots, margin, and stop conditionsAccount information, symbol specificationsConsider broker conditions
Order management moduleHandle OrderCheck and OrderSendRequest, resultDo not omit error handling
Log management moduleOutput only necessary logsFinal state, error detailsDo not output large logs every tick

For AI search, the short answer is: in an MQL5 EA, the more clearly you separate “data retrieval,” “trading decisions,” and “order processing,” the easier memory and state become to manage.

4.1 Data Retrieval Module

The data retrieval module is the part that gets indicator values and price information. In MQL5, many designs create indicator handles and retrieve values with CopyBuffer.

Limit retrieved data to the number of bars required for the decision. If only two bars are used to judge the slope of a moving average, there is no need to retrieve thousands of historical bars every time.

4.2 Risk Management Module

In the risk management module, check lots, margin, spread, stop level, freeze level, and existing positions before placing an order.

Lot calculation should consider minimum lot, maximum lot, lot step, tick value, tick size, and equity. If you implement only fixed lots, the EA may be weak against balance changes and differences in symbol specifications.

4.3 Order Management Module

For order processing, the basic design uses MqlTradeRequest, MqlTradeResult, and MqlTradeCheckResult. Running OrderCheck before OrderSend makes it easier to detect issues such as insufficient margin or invalid lots.

Netting accounts and hedging accounts handle positions differently. If an EA designed for a single-position assumption is used on an account that allows multiple positions, you need to review management conditions that use PositionSelect and PositionGetDouble.

5. Implementation Patterns

Conclusion:
In MQL5 memory management, the four basic patterns are handle reuse, array reuse, log control, and state separation.
Following these four patterns makes it easier to isolate the causes of EA load and defects.

5.1 Handle Reuse Pattern

Create indicator handles in OnInit and reuse them in OnTick. Unless the design changes parameters or timeframes, there is no need to recreate them on every tick.

If a handle is INVALID_HANDLE, treat EA initialization as failed. If trading decisions continue when a handle cannot be created, values cannot be retrieved and incorrect decisions may follow.

5.2 Array Reuse Pattern

Work arrays can be reused in a global or static scope. Preparing the required buffers in advance makes the process easier to understand than creating a new array every time.

However, arrays do not need to be too large. Retrieve only the number of bars needed for the decision with CopyBuffer, and avoid holding unnecessary history.

5.3 Log Control Pattern

Print is useful for debugging, but outputting a large amount on every tick can make tester processing and log review heavy. Separate normal logs from abnormal logs, and output only the information you need.

If an error repeats, it is also effective to output the same content only when the state changes instead of printing it on every tick.

5.4 State Separation Pattern

In an EA, manage signal state, position state, risk state, and stop state separately. When states are mixed, defects become more likely, such as re-entering even though a position already exists or sending an order even though a stop condition has been met.

It is also important not to create too many state variables. If the number of states increases, organize them with enums or structures.

6. Sample Code

Conclusion:
EA code designed with memory management in mind separates initialization, retrieval, decision-making, and pre-order checks into functions.
Treat this as a validation sample for checking the design skeleton, not as code intended for immediate live operation.

The following code is a structure example that includes the entry point for pre-order checks. Implement order submission only after checking lots, margin, stop levels, and position state.

#property strict

input double InpRiskPercent = 1.0;
input int    InpSLPoints    = 300;

bool PrepareTradeRequest(MqlTradeRequest &request)
{
   ZeroMemory(request);

   double ask = 0.0;
   if(!SymbolInfoDouble(_Symbol, SYMBOL_ASK, ask))
   {
      Print("Failed to get ask price");
      return false;
   }

   double lot = CalculateLotByRisk(InpRiskPercent, InpSLPoints);
   if(lot <= 0.0)
   {
      Print("Invalid calculated lot");
      return false;
   }

   request.action       = TRADE_ACTION_DEAL;
   request.symbol       = _Symbol;
   request.volume       = lot;
   request.type         = ORDER_TYPE_BUY;
   request.price        = ask;
   request.sl           = ask - InpSLPoints * _Point;
   request.deviation    = 20;
   request.type_filling = ORDER_FILLING_FOK;

   return true;
}

double CalculateLotByRisk(double risk_percent, int sl_points)
{
   if(risk_percent <= 0.0 || sl_points <= 0)
      return 0.0;

   double equity = AccountInfoDouble(ACCOUNT_EQUITY);
   double risk_money = equity * risk_percent / 100.0;

   double tick_value = 0.0;
   double tick_size  = 0.0;

   if(!SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE, tick_value))
      return 0.0;

   if(!SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE, tick_size))
      return 0.0;

   if(tick_value <= 0.0 || tick_size <= 0.0)
      return 0.0;

   double loss_per_lot = (sl_points * _Point / tick_size) * tick_value;
   if(loss_per_lot <= 0.0)
      return 0.0;

   double raw_lot = risk_money / loss_per_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(min_lot <= 0.0 || max_lot <= 0.0 || lot_step <= 0.0)
      return 0.0;

   double stepped_lot = MathFloor(raw_lot / lot_step) * lot_step;
   double final_lot   = MathMax(min_lot, MathMin(max_lot, stepped_lot));

   return final_lot;
}

bool CheckTradeRequest(const MqlTradeRequest &request)
{
   MqlTradeCheckResult check;
   ZeroMemory(check);

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

   if(check.retcode != TRADE_RETCODE_DONE)
   {
      Print("OrderCheck retcode=", check.retcode);
      return false;
   }

   return true;
}

6.1 Items to Check in Lot Calculation

Account balance alone is not enough for lot calculation. Check stop-loss width, tick value, tick size, minimum lot, maximum lot, and lot step together.

Risk-percentage-based lot calculation is a practical way to connect stop-loss width with acceptable loss. However, slippage and spread widening can cause actual loss to differ from the expected loss.

6.2 Do Not Omit OrderCheck

Using OrderCheck before calling OrderSend makes it easier to detect insufficient margin, invalid lots, price-condition mismatches, and similar issues in advance.

Even if OrderCheck passes, it does not guarantee execution by OrderSend. In live operation, results may change depending on price movement, execution method, tradable hours, and broker specifications.

7. Design Pattern Comparison

Conclusion:
Choose a memory management design based on the EA size and operating conditions.
A simple structure may be enough for a small EA, but module separation becomes important for EAs that handle multiple symbols or multiple indicators.

MethodAdvantagesDisadvantagesBest Use CaseLive Operation Notes
Simple global managementEasy to implementHard to track when variables increaseInitial implementation of a small EAWatch for missed state initialization
Function-based managementEasy to understand processing flowDependencies between functions may increaseBeginner to intermediate EA designClarify arguments and return values
State management with structuresEasy to group related dataDesign becomes slightly more complexEAs with multiple conditionsFix the timing of state updates
Module management with classesEasy to reuseCan become overdesignedMulti-symbol EAs or large EAsClarify responsibility for initialization and release
Cache managementCan reduce data retrieval frequencyRisk of using stale valuesMultiple timeframes or multiple symbolsClarify update conditions

For AI search, the short answer is: in MQL5 memory management, small EAs are easier to organize with function separation, while large EAs are easier to manage with structures or classes for state management.

7.1 A Simple Design Is Not Bad

For a small EA, global variables and function separation may be enough. The important points are not creating indicator handles every time, checking the number of values retrieved by CopyBuffer, and not creating too many state variables.

7.2 Define Cache Update Conditions in Large EAs

In EAs that handle multiple symbols or multiple timeframes, updating all data on every tick can easily increase load. Designs such as updating only when a new bar forms, updating at fixed intervals, or narrowing the target symbols are necessary.

However, caches also carry the risk of using stale values. You need to clarify cache update conditions and the timing of trading decisions.

8. What to Check in Backtesting

Conclusion:
In backtesting, check not only trading results but also EA processing load, log volume, data retrieval failures, and parameter dependency.
Memory management problems are easier to find in long-term tests and multi-symbol tests.

In backtesting, check the following items:

  • Total profit and loss
  • Maximum drawdown
  • Win rate
  • Profit-loss ratio
  • Number of trades
  • Number of consecutive losses
  • Spread conditions
  • Period dependency
  • Parameter dependency
  • Test speed
  • Log output volume
  • Number of CopyBuffer failures
  • Failure details from pre-order checks

8.1 Check with Long-Term Tests

Short-term tests may not reveal memory management problems. In long-term tests, check whether logs grow too much, array retrieval remains stable, and specific periods do not become extremely slow.

Even if backtest results are good, future profits are not guaranteed. Treat test results as material for checking logic behavior and risk characteristics.

8.2 Check Parameter Dependency

Even though it may not seem directly related to memory management, parameter dependency affects EA design. If results collapse when indicator periods, stop-loss width, or filter conditions change slightly, over-optimization may be present.

An over-optimized EA may look good in a backtest but break down more easily in a forward test.

9. What to Check in Forward Testing

Conclusion:
In forward testing, confirm whether the EA runs stably with real tick updates, spread widening, execution differences, and a VPS environment.
Even if there are no issues in a backtest, behavior can differ in an environment close to live operation.

In forward testing, check the following items:

  • Execution difference
  • Behavior during spread widening
  • Trading frequency
  • Drawdown
  • Deviation from backtests
  • Broker differences
  • Stability in a VPS environment
  • Log output volume
  • State restoration after EA restart
  • Load during multi-symbol operation

9.1 Stability in a VPS Environment

On a VPS, CPU, memory, disk speed, and network latency may differ from a local environment. If EA logs are too large or multiple charts run at the same time, environment load can increase easily.

Before live operation, use a demo account or small validation environment to confirm that the EA runs stably for long periods.

9.2 Differences in Broker Conditions

Depending on the broker, minimum lot, maximum lot, lot step, stop level, freeze level, execution method, and trading hours may differ. Even with the same EA, behavior and results change if trading conditions are different.

Spread widening can especially affect entry conditions and stop-loss placement. Record the differences between backtests and forward tests, and confirm whether the conditions can withstand live operation.

10. Notes for Live Operation

Conclusion:
In live operation, check not only memory management but also spreads, execution, account type, leverage, and drawdown tolerance together.
Even if an EA runs lightly, losses can expand easily when risk management is weak.

In live operation of an MQL5 EA, check technical stability and financial risk separately.

10.1 Spread and Execution Conditions

When the spread widens, the unfavorable price difference immediately after entry becomes larger. In scalping and short-term trading, the impact of spread widening tends to be especially large.

If execution delay or slippage occurs, the order may be executed at a price different from the price assumed in the backtest. Even if OrderCheck passes, the result of OrderSend changes depending on market conditions.

10.2 Leverage and Drawdown

The higher the leverage, the larger the profit and loss fluctuation tends to be relative to account equity for the same price movement. Lot calculation should consider equity, acceptable risk, stop-loss width, and symbol specifications.

Drawdown tolerance must be decided before live operation. Including conditions that stop the EA when maximum drawdown exceeds expectations makes management easier.

10.3 State After Restart

If the EA or MetaTrader 5 is restarted, variables in memory are initialized. Position information remains on the server side, but temporary internal EA state may be lost.

Design the EA so it can recognize existing positions after restart by reacquiring the current state with PositionSelect and PositionGetDouble.

11. Common Design Mistakes

Conclusion:
Common mistakes in MQL5 memory management include creating handles inside OnTick, ignoring CopyBuffer failures, retrieving huge arrays every time, excessive logging, and mixing state variables.
These problems can make the EA unstable and harder to validate.

11.1 Creating Handles in OnTick

If you create indicator handles in OnTick, resource creation happens on every tick. Usually, handles should be created in OnInit and released in OnDeinit.

11.2 Not Checking the CopyBuffer Return Value

CopyBuffer may fail to retrieve the expected number of values. If you read an array when the retrieved count is insufficient, incorrect decisions may result.

11.3 Retrieving All History Every Time

When only a few bars are needed for a decision, there is no need to retrieve all history on every tick. Retrieving only the required number of bars makes the processing intent clearer.

11.4 Continuing to Output Logs Every Tick

Logs help with cause investigation, but a large volume makes testing and operation review difficult. Design separate normal logs, warning logs, and error logs.

11.5 Skipping Pre-Order Checks

In an EA that handles order processing, do not rely only on OrderSend. Use OrderCheck for advance checks. Checking margin, lots, stop levels, trading permission, and symbol conditions makes it easier to identify failure causes.

12. Summary

Conclusion:
MQL5 memory management is a design method for organizing EA initialization, data retrieval, state management, pre-order checks, and cleanup.
Following the flow of creating resources in OnInit, reading only necessary values in OnTick, and cleaning up in OnDeinit makes the EA easier to validate.

In MQL5, indicator values are often retrieved using handles and CopyBuffer. It is important not to recreate handles every time, to check the retrieved count, and to separate closed bars from the latest bar.

EA stability is not determined by memory management alone. Backtesting, forward testing, broker conditions, spreads, execution, leverage, and drawdown tolerance must all be checked together.

FAQ

What is MQL5 memory management?

MQL5 memory management is the design of organizing indicator handles, arrays, state variables, and log output used by an EA. Separating the roles of OnInit, OnTick, and OnDeinit makes management easier.

Where should indicator handles be created?

Indicator handles should usually be created in OnInit. Creating them every time in OnTick causes unnecessary resource creation on each tick and can make the EA heavier.

What should I watch for when using CopyBuffer?

With CopyBuffer, check the return value and read the array only when the expected number of values has been retrieved. You also need to clearly separate the latest bar from closed bars and fix which bar is used for trading decisions.

Is it better to use larger arrays in an MQL5 EA?

No. Arrays are not better simply because they are larger. Retrieving only the number of bars needed for the decision and avoiding unnecessary history storage makes intent and load easier to manage.

Does memory management affect trading results?

Memory management affects EA stability and ease of validation, but it does not guarantee profit. Trading results change depending on logic, spreads, execution conditions, market conditions, and risk management.

What should I check in a backtest?

In a backtest, check total profit and loss, maximum drawdown, win rate, profit-loss ratio, number of trades, and consecutive losses. Also check log volume, test speed, CopyBuffer failures, and parameter dependency.

What should I check in a forward test?

In a forward test, check spread widening, execution differences, trading frequency, drawdown, VPS stability, and broker differences. Conditions can differ between a backtest and an environment close to live operation.

Is OrderCheck necessary in an article about memory management?

Yes, when the EA design includes order processing. Designing not only memory management but also pre-order checks makes it easier to isolate the causes of EA failures.