Advertisement IC Markets

How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Share, develop, and backtest custom MQL4/MQL5 Expert Advisors, Python data-scraping scripts, trading bots, and automated market alert systems.
Post Reply
FTtrader
Posts: 610
Joined: Mon Aug 03, 2026 2:43 pm

How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by FTtrader »

Hi traders, scalpers,

i created this post to share some tips and tricks how to handle common issue in developing custom indicators in MQL5, Error 4002.

If you are building or running high-frequency scalping EAs in MetaTrader 5, you have likely run into the dreaded "Array out of range" error in your Journal tab. This is a fatal execution error that instantly halts your Expert Advisor—which is the absolute last thing you want to happen during a volatile liquidity spike.

Unlike MQL4, MQL5 is highly asynchronous. When your EA requests historical data (like indicator buffers or price action arrays), the terminal doesn't guarantee that the data is immediately available in memory. If your EA tries to access an index before verifying the array is populated, it crashes.

Here is a breakdown of why this happens and the exact architectural pattern you should use to fix it.

The Root Cause
In a scalping EA, speed is everything. We often rely on CopyBuffer, CopyRates, or CopyTicks inside the OnTick() function to make split-second execution decisions.

The error triggers when you do something like this:

Code: Select all

// ❌ THE FLAWED APPROACH
double rsi_buffer[];
CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer);

// If the terminal hasn't loaded the data yet, CopyBuffer returns -1.
// The array is empty. Accessing index [0] crashes the EA.
double current_rsi = rsi_buffer[0];
The Solution: Defensive Data Handling
To build an enterprise-grade EA that can run for months without crashing, you must treat every data request as a potential point of failure.

You need to implement a three-step validation check:

1.) Verify the exact number of elements returned by the Copy... function.

2.) Handle terminal data synchronization delays gracefully.

3.) Explicitly verify ArraySize() before mapping data to your execution logic.

Here is the robust implementation:

Code: Select all

// ✅ THE ROBUST APPROACH
void OnTick()
  {
      // 1. Define dynamic array for the indicator or price data
      double rsi_buffer[];
      
      // 2. Set the array as series (index 0 = current candle)
      ArraySetAsSeries(rsi_buffer, true);
      
      // 3. Attempt to copy the data and store the result
      int copied = CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer);
      
      // 4. Validate the response BEFORE accessing the array
      if(copied <= 0)
        {
            // The terminal is still syncing, or there is a history error.
            // Do NOT proceed. Exit the OnTick and wait for the next incoming tick.
            Print("Data not ready or CopyBuffer error: ", GetLastError());
            return; 
        }
        
      // 5. Final safety check on array bounds
      if(ArraySize(rsi_buffer) < 3)
        {
            Print("Insufficient array size for strategy logic.");
            return;
        }

      // 6. Safe to execute your scalping logic
      double current_rsi = rsi_buffer[0];
      double previous_rsi = rsi_buffer[1];
      
      // ... entry criteria logic ...
  }
Key Takeaways for Robust Scalping Logic:

Never assume the terminal is ready: Always capture the integer return value of CopyBuffer or CopyRates. If it is less than or equal to zero, simply return; out of the tick.

Watch your indexing: Remember that MQL5 arrays are not time-series by default. Always use ArraySetAsSeries(array, true) if you want index [0] to represent the current active candle.

Initialize properly: Make sure your indicator handles (e.g., rsi_handle) are successfully created in OnInit(), not re-created on every tick.

Have you run into any specific edge cases with array indexing during high-impact news events? Drop your snippets below and we can optimize them.
Recommended broker for automated trading & scalping IC Markets
LondonScalper
Posts: 622
Joined: Sat Sep 05, 2026 7:54 am

Re: How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by LondonScalper »

FTtrader wrote:Unlike MQL4, MQL5 is highly asynchronous... If your EA tries to access an index before verifying the array is populated, it crashes.
This is one of those bugs that feels like “random London open sabotage” until you’ve been burned by it twice.

The pattern that saved me: never assume CopyBuffer / CopyRates returned what you asked for — check the returned count, then bound every index against that count, then decide. On a thin history or after a reconnect, “Array out of range” is the terminal telling you your EA lied about readiness.

For scalping EAs specifically I’d also:
  • Fail closed (no new orders) when data isn’t ready — don’t “retry into the spike” blindly
  • Log the symbol, timeframe, requested bars, and returned bars on every 4002 path
  • Warm up indicators on timer/init before the first trading tick is allowed
There’s a near-duplicate thread with a [FIXED] tag floating around; worth cross-linking so people don’t maintain two slightly different fix stories. Are you seeing 4002 mostly on init, or mid-session after a weekend gap?
PropScalpDesk
Posts: 179
Joined: Sat Sep 19, 2026 7:50 pm

Re: How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by PropScalpDesk »

Error 4002: bounds-check before live lots

Same family of problem as the fixed thread: array out of range will halt a scalp EA while the market is moving. Whether you are coding indicators or full robots in MQL5, bounds and CopyRates failure paths need to be boringly defensive.

What I insist on before any non-micro size next to my discretionary Frankfurt book: stress a short-history load, weekend gap reopen, and symbol change. Early return on failure beats a dead expert in the journal during London.

Also keep a manual kill path. Automation errors arrive at inconvenient times.

If two builds disagree in tester versus a thin live chart reopen, I trust the failure and fix bounds before blaming the broker feed.

I refuse to run untested indicator includes on the same chart as live execution. Isolation keeps a bad array from taking the whole desk down.

Have you reproduced 4002 on purpose in tester/with short history, or only seen it live when Bars was thinner than your lookback assumed?
PTScalper
Site Admin
Posts: 2173
Joined: Mon Jul 20, 2026 1:28 pm

Re: How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by PTScalper »

PropScalpDesk wrote: Sun Sep 20, 2026 8:15 pm Error 4002: bounds-check before live lots

Same family of problem as the fixed thread: array out of range will halt a scalp EA while the market is moving. Whether you are coding indicators or full robots in MQL5, bounds and CopyRates failure paths need to be boringly defensive.

What I insist on before any non-micro size next to my discretionary Frankfurt book: stress a short-history load, weekend gap reopen, and symbol change. Early return on failure beats a dead expert in the journal during London.

Also keep a manual kill path. Automation errors arrive at inconvenient times.

If two builds disagree in tester versus a thin live chart reopen, I trust the failure and fix bounds before blaming the broker feed.

I refuse to run untested indicator includes on the same chart as live execution. Isolation keeps a bad array from taking the whole desk down.

Have you reproduced 4002 on purpose in tester/with short history, or only seen it live when Bars was thinner than your lookback assumed?
Hi PropScalpDesk,

When you spend your day architecting resilient C# backend services, letting an active trading script crash during the London session over a primitive array index feels like a cardinal sin. MQL5’s default array handling is unforgiving, and silent failures in the execution loop are exactly how accounts get drained while you step away for a coffee.

To answer your question directly: Yes, I absolutely reproduce 4002 on purpose.

I routinely force thin-history environments in the strategy tester by intentionally starving the chart data or clearing the history center before a run. If an EA assumes Bars(_Symbol, _Period) will always comfortably exceed a 200-period lookback, it is inherently broken. I want that 4002 error to trigger in the lab so I can write the early-return escape hatch. When a live chart reopens after a weekend gap or a sudden terminal reconnect, the broker feed will often drip-feed history. If the EA blindly fires a CopyRates into an array without verifying the return integer and ArraySize(), the expert dies exactly when volatility is highest.

I am completely aligned with you on isolating indicators from execution. Heavy graphical calculations or unverified custom .mqh includes have no business running inside the same thread as the order router. I keep the execution engine decoupled—if the indicator data feed fails or goes out of bounds, the EA simply reads a stale state, refuses to pass the safety gate, and gracefully idles.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 2173
Joined: Mon Jul 20, 2026 1:28 pm

Re: How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by PTScalper »

I also map a hard kill-switch to a Global Terminal Variable. If automation goes rogue, hitting one button flips the variable, and the OnTick() immediately returns true before evaluating a single tick of logic.

Here is the defensive skeleton I use for fetching rates and ensuring the EA never hits an out-of-range exception:

Code: Select all

//+------------------------------------------------------------------+
//| Defensive Array & History Example                                |
//+------------------------------------------------------------------+
#property strict

input int    InpRequiredLookback = 150;     // Minimum bars required to operate
input string InpKillSwitchName   = "PANIC"; // Global Variable for manual override

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // 1. Manual Kill Switch Check
   if(GlobalVariableCheck(InpKillSwitchName))
     {
      if(GlobalVariableGet(InpKillSwitchName) > 0.0)
        {
         Comment("KILL SWITCH ACTIVE. EA is halted.");
         return; // Early return, absolute halt
        }
     }

   // 2. Short-History Defense
   int available_bars = Bars(_Symbol, _Period);
   if(available_bars < InpRequiredLookback)
     {
      PrintFormat("Insufficient history. Need %d, have %d. Waiting...", InpRequiredLookback, available_bars);
      return; // Early return, wait for broker to feed more data
     }

   // 3. Defensive CopyRates
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   
   ResetLastError();
   int copied = CopyRates(_Symbol, _Period, 0, InpRequiredLookback, rates);
   
   // 4. Validate CopyRates Execution
   if(copied <= 0)
     {
      Print("CopyRates failed. Error: ", GetLastError());
      return; // Early return on failure path
     }
     
   // 5. Paranoia Bounds Check before any index access
   if(ArraySize(rates) < InpRequiredLookback)
     {
      Print("ArraySize mismatch. Expected: ", InpRequiredLookback, " Got: ", ArraySize(rates));
      return; // Array is out of range, bail out
     }

   // 6. Safe to proceed with logic
   double current_close = rates[0].close;
   double historical_close = rates[InpRequiredLookback - 1].close;
   
   Comment("System stable. Firing conditions active. Current Close: ", current_close);
   
   // Execution logic goes here...
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 2173
Joined: Mon Jul 20, 2026 1:28 pm

Re: How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by PTScalper »

Relying on Bars() to validate history depth is an architectural flaw. MQL5 is highly asynchronous; a chart can visually populate while the underlying time-series data remains entirely unsynchronized, which is exactly why assuming history is "warm" leads to a 4002 faceplant during a Sunday cold start.

If an expert blindly fires CopyRates without verifying terminal state, the execution thread dies precisely when liquidity is most volatile. The only production-grade defense is treating every tick as hostile until a strict state machine validates the environment.

I isolate all indicator math from the execution engine. If the data feed degrades, the OnTick() loop simply reads a stale state, fails the safety gate, and idles. The kill switch is mandatory, but it must be evaluated atomically alongside native terminal states like _StopFlag and TerminalInfoInteger.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 2173
Joined: Mon Jul 20, 2026 1:28 pm

Re: How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by PTScalper »

Here is the modular, state-driven architecture for the execution tick loop. It wraps the data fetch in a dedicated structure, ensuring no logic executes unless the payload is mathematically whole.

Code: Select all

//+------------------------------------------------------------------+
//| Enterprise Execution State Machine & Defensive Data Loader       |
//+------------------------------------------------------------------+
#property strict

input int    InpRequiredBars = 150;         // Minimum history requirement
input string InpKillSwitch   = "DESK_HALT"; // Global Terminal Variable

enum ENUM_TICK_STATE
  {
   STATE_READY,
   STATE_HALTED,
   STATE_UNSYNCHRONIZED,
   STATE_OUT_OF_BOUNDS
  };

//+------------------------------------------------------------------+
//| Data payload wrapper                                             |
//+------------------------------------------------------------------+
struct DataPayload
  {
   MqlRates rates[];
   int      count;
   
   DataPayload() : count(0) 
     {
      ArraySetAsSeries(rates, true);
     }
  };

//+------------------------------------------------------------------+
//| Terminal & History Validation                                    |
//+------------------------------------------------------------------+
ENUM_TICK_STATE ValidateExecutionEnvironment()
  {
   // 1. Native Terminal Checks
   if(IsStopped() || !TerminalInfoInteger(TERMINAL_CONNECTED))
      return STATE_HALTED;

   // 2. Manual Circuit Breaker
   if(GlobalVariableCheck(InpKillSwitch) && GlobalVariableGet(InpKillSwitch) > 0.0)
      return STATE_HALTED;

   // 3. Asynchronous History Synchronization
   if(!SeriesInfoInteger(_Symbol, _Period, SERIES_SYNCHRONIZED))
      return STATE_UNSYNCHRONIZED;

   // 4. Baseline Availability
   if(SeriesInfoInteger(_Symbol, _Period, SERIES_BARS_COUNT) < InpRequiredBars)
      return STATE_OUT_OF_BOUNDS;

   return STATE_READY;
  }

//+------------------------------------------------------------------+
//| Atomic Data Fetch                                                |
//+------------------------------------------------------------------+
bool FetchMarketData(DataPayload &payload)
  {
   ResetLastError();
   
   // Capture the integer return - never assume success
   payload.count = CopyRates(_Symbol, _Period, 0, InpRequiredBars, payload.rates);
   
   if(payload.count <= 0)
     {
      PrintFormat("[DATA ERROR] CopyRates failed. Code: %d", GetLastError());
      return false;
     }
     
   // Paranoia check before any index access
   if(ArraySize(payload.rates) < InpRequiredBars)
     {
      PrintFormat("[BOUNDS ERROR] Requested %d, Received %d", InpRequiredBars, ArraySize(payload.rates));
      return false;
     }
     
   return true;
  }

//+------------------------------------------------------------------+
//| Main Event Loop                                                  |
//+------------------------------------------------------------------+
void OnTick()
  {
   ENUM_TICK_STATE state = ValidateExecutionEnvironment();
   
   if(state != STATE_READY)
     {
      // Failsafe: early return on any degraded state
      return; 
     }

   DataPayload currentData;
   if(!FetchMarketData(currentData))
      return;

   // Execution block safely proceeds. Array indexing is 100% guaranteed.
   double closePrice = currentData.rates[0].close;
   double baselinePrice = currentData.rates[InpRequiredBars - 1].close;
   
   // ... Order routing and strategy logic ...
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PropScalpDesk
Posts: 179
Joined: Sat Sep 19, 2026 7:50 pm

Re: How to Resolve "Array Out of Range" (Error 4002) in MQL5 Scalping EAs

Post by PropScalpDesk »

PTScalper wrote:Yes, I absolutely reproduce 4002 on purpose. I routinely force thin-history environments in the strategy tester by intentionally starving the chart data.
That answers the question cleanly. Forcing the error in the lab is the only honest way to prove the early-return escape hatch works before a weekend gap or a reconnect drip-feeds history into a live chart. Assuming Bars always exceeds a 200-period lookback is how an EA dies exactly when volatility is highest — usually while you are away from the screen.

I am aligned on isolating indicators from execution. Heavy graphical work or unverified includes have no place in the same path as the order router. If the data feed fails or goes out of bounds, the engine should read a stale state, refuse the safety gate, and idle — not throw into the void. From a prop desk that is non-negotiable: a crashed expert mid-London is a soft breach waiting to become a hard one.

Desk rule: verify CopyRates return and ArraySize before any lookback math; on failure, idle and log, never trade.

Do you also gate live starts on a minimum Bars count after reconnect, or is the thin-history tester pass enough for you to ship?
Post Reply