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.
FTtrader
Posts: 1800
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: 965
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: 521
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: 7643
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: 7643
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: 7643
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: 7643
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: 521
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?
LondonNewsTrader
Posts: 421
Joined: Mon Sep 21, 2026 9:30 am

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

Post by LondonNewsTrader »

PTScalper wrote: It wraps the data fetch in a dedicated structure, ensuring no logic executes unless the payload is mathematically whole.
The SERIES_SYNCHRONIZED check is the part most EAs skip, and it is the usual reason for out-of-range errors just after start-up, so good to see it. One gap in the fetch: the success test is count <= 0. CopyRates can return fewer bars than requested, for example 90 when you asked for 150, and anything later in the logic that indexes up to InpRequiredBars - 1 will still go out of range. Comparing against the requested count, or using payload.count as the loop bound everywhere, closes that.

The kill switch via a terminal global variable is a nice touch. Worth knowing that MT5 deletes global variables that haven't been accessed for four weeks, so a DESK_HALT left at 1.0 for a long holiday may quietly disappear. It's fine for daily use.

I'd add a fourth halt condition alongside the others: a short window around releases you've marked in advance. The data checks protect against missing history; this one protects against the market itself.
FXS Prop Desk
Posts: 1572
Joined: Wed Oct 07, 2026 9:52 am

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

Post by FXS Prop Desk »

Forcing thin history in the tester to provoke 4002 is good practice. A guard pattern that covers most cases: check the return value of CopyRates or CopyBuffer and compare it with the number of bars the logic needs. If fewer were copied, return from OnTick and wait for the next tick instead of reading indexes that don't exist.

The other frequent cause is ArraySetAsSeries. When code is copied between projects, one array may be series-indexed and another not, so index 0 means the newest bar in one and the oldest in the other. Declaring the indexing explicitly right after each copy prevents that.
FXS Official Team | The Idea Lab & Strategy
Post Reply