Advertisement IC Markets

[FIXED] 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.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Standard OrderSend() is a blocking, synchronous function. When you call it, your EA's execution thread halts completely while the terminal encrypts the payload, pushes it through the TCP socket to the broker's server, and waits for a validation response. In a volatile market, a 150-millisecond network ping means your EA is entirely deaf to incoming price ticks during that window.

OrderSendAsync() operates differently. It is a non-blocking, fire-and-forget mechanism—similar to publishing a payload to an asynchronous message broker. It dispatches your trade request to the terminal's internal queue and immediately returns control to OnTick().

To harness this without losing track of your orders, you must implement an event-driven architecture using the OnTradeTransaction() callback and a local state machine to prevent order duplication.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

The Asynchronous Execution Architecture

Code: Select all

// 1. Global state management
// Because execution is non-blocking, you must track pending states 
// to prevent your fast-looping OnTick() from firing 50 identical orders in 10ms.
bool is_order_in_flight = false;
uint pending_request_id = 0;

void ExecuteAsyncMarketBuy(double lot_size)
  {
      // Block redundant requests while one is routing to the broker
      if(is_order_in_flight) return;

      MqlTradeRequest request = {};
      MqlTradeResult result = {};
      
      request.action       = TRADE_ACTION_DEAL;
      request.symbol       = _Symbol;
      request.volume       = lot_size;
      request.type         = ORDER_TYPE_BUY;
      request.price        = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      request.deviation    = 3; // Slippage tolerance in points
      request.magic        = 9999;
      request.type_filling = ORDER_FILLING_FOK; // Fill or Kill
      
      // 2. Dispatch to the terminal queue
      if(OrderSendAsync(request, result))
        {
            // The function returns true ONLY if the terminal accepted the format.
            // It DOES NOT mean the broker executed the trade.
            
            is_order_in_flight = true;
            pending_request_id = result.request_id; // Store ID to match the callback
        }
      else
        {
            // Terminal-level validation failure (e.g., invalid lot size)
            Print("Async Dispatch Failed: ", GetLastError());
        }
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Catching the Broker Response

Once the broker processes the queue, it fires asynchronous events back to the terminal. You capture these in OnTradeTransaction().

Because a single trade generates multiple distinct transaction events (Order Add, Order Fill, Deal Add, History Sync), you must filter for the exact server response using the request_id you stored in your local state.

Code: Select all

void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
      // 3. We only care about the final server response to our specific request
      if(trans.type == TRADE_TRANSACTION_REQUEST)
        {
            // 4. Verify this response belongs to our pending order
            if(result.request_id == pending_request_id)
              {
                  // 5. Evaluate the server's return code
                  if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED)
                    {
                        // The broker successfully filled the order.
                        // Free the state machine to allow future trades.
                        is_order_in_flight = false;
                        pending_request_id = 0;
                    }
                  else
                    {
                        // The broker rejected the order (Requote, Off Quotes, No Liquidity).
                        // You must release the lock so the EA can attempt a new entry.
                        is_order_in_flight = false;
                        pending_request_id = 0;
                        
                        // Log exact rejection reason for microstructure analysis
                        Print("Async Order Rejected. Retcode: ", result.retcode);
                    }
              }
        }
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Critical Implementation Rules

Never mix synchronous and asynchronous calls: If you use OrderSendAsync(), do not follow it up by immediately querying PositionSelect(). The position will not exist in the local terminal state yet. All post-trade logic must originate from inside OnTradeTransaction().

Handle Partial Fills (IOC): If you use ORDER_FILLING_IOC (Immediate or Cancel), the result.volume returned in the transaction might be smaller than your requested volume due to thin liquidity. You must update your local position-sizing state inside the callback to account for the exact executed volume.

Connection Drops: If the TCP connection drops instantly after OrderSendAsync(), you may never receive the callback. To build true fault tolerance, implement a heartbeat timer in OnTimer() that resets is_order_in_flight = false if a pending order receives no server response after a reasonable timeout (e.g., 5 seconds).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Unlike a C# Task where you can cleanly pass a CancellationToken to an asynchronous operation, MQL5 requires you to manually manage the lifecycle of your asynchronous dispatch. You must build a detached polling mechanism that watches your state machine.

The most critical trap developers fall into here is using TimeCurrent() to measure the timeout. TimeCurrent() returns the timestamp of the last received price tick from the broker. If the TCP socket drops completely, server ticks stop arriving, TimeCurrent() freezes, and your timeout logic will never trigger.

To build a fault-tolerant heartbeat, you must use GetTickCount64(), which tracks your local machine's physical uptime in milliseconds, making it immune to network drops.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

1. Global State and Timer Initialization

Define your timeout threshold and initialize a high-resolution millisecond timer. A 100-millisecond interval provides snappy timeout detection without burning CPU cycles on your local workstation.

Code: Select all

// Global State Machine
bool is_order_in_flight = false;
uint pending_request_id = 0;

// Local machine timestamp of the dispatch
ulong request_dispatch_time = 0; 

// 3-second maximum wait time
const uint ASYNC_TIMEOUT_MS = 3000; 

int OnInit()
  {
      // A high-resolution timer acting as our background watcher
      EventSetMillisecondTimer(100);
      return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
      EventKillTimer();
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

2. Stamping the Dispatch Time

Modify your execution function to record the exact local millisecond the payload was pushed to the terminal's internal queue.

Code: Select all

void ExecuteAsyncMarketBuy(double lot_size)
  {
      if(is_order_in_flight) return;

      MqlTradeRequest request = {};
      MqlTradeResult result = {};
      
      // ... populate request fields ...
      
      if(OrderSendAsync(request, result))
        {
            is_order_in_flight = true;
            pending_request_id = result.request_id;
            
            // Record local machine uptime at the exact moment of dispatch
            request_dispatch_time = GetTickCount64(); 
        }
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

3. The OnTimer Watchdog

OnTimer() runs independently of OnTick(). Even if the market freezes, the connection drops, or the broker's server crashes, this function continues to execute every 100 milliseconds.

Code: Select all

void OnTimer()
  {
      // If no order is pending, exit immediately
      if(!is_order_in_flight) return;
      
      // Calculate elapsed milliseconds since dispatch
      ulong elapsed_time = GetTickCount64() - request_dispatch_time;
      
      if(elapsed_time > ASYNC_TIMEOUT_MS)
        {
            // The broker failed to respond within the acceptable window.
            // Force-release the state machine lock.
            is_order_in_flight = false;
            pending_request_id = 0;
            
            Print("CRITICAL: Async Order Timeout. Broker response took longer than ", ASYNC_TIMEOUT_MS, " ms.");
            
            // NOTE: Releasing the lock means OnTick() is now free to fire a new order.
            // If the broker was just severely lagging and processes the original 
            // order 5 seconds later, you risk opening duplicate positions. 
        }
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3159
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Releasing the state lock presents a new edge case: if the broker was experiencing severe lag rather than a hard disconnect, they might process your original order 10 seconds later. If your EA fired a new order in the meantime, you end up with double the exposure.

To mitigate this in a live environment, a robust timeout should be paired with a hard circuit breaker that pauses all new trading activity and forces a terminal reconnection (TerminalInfoInteger(TERMINAL_PING_LAST)) before allowing the state machine to resume.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply