🚀 AI One-Click Generation of mql4 Strategy Code

Mql4 Advanced Guide: Using Global Variables as a Mutex for Safe Multi-EA Trading

mql4 | Published on: 2025/12/2
The following content is generated by EasyQuant .

Problem Description

How can I make sure that only one of my trading robots sends a trade at a time to avoid conflicts?

Solution

To prevent multiple Expert Advisors (EAs) from attempting to trade simultaneously and triggering Error 146: Trade context is busy, you must implement a Semaphore (Mutex) system.

Since MQL4 programs run in separate threads but share a single trade execution thread per terminal, you need a global flag that all EAs check before trading. The most robust way to do this is using Global Variables of the Client Terminal, specifically the GlobalVariableSetOnCondition function, which performs an atomic check-and-set operation.

Implementation Strategy

  1. Check Lock: Before OrderSend, the EA attempts to "claim" a specific Global Variable.
  2. Wait: If the variable is already claimed by another EA, the current EA waits (sleeps) and retries.
  3. Execute: Once claimed, the EA sends the order.
  4. Release: Immediately after the trade attempt, the EA releases the Global Variable so others can trade.

MQL4 Code Implementation

Copy the following functions into your Expert Advisor (or save them as an include file).

//+------------------------------------------------------------------+
//| Global Constants                                                 |
//+------------------------------------------------------------------+
const string TRADE_LOCK_NAME = "GlobalTradeSemaphore"; // Name of the lock variable
const int    LOCK_TIMEOUT    = 10;                     // Seconds to wait for lock
const int    LOCK_EXPIRATION = 15;                     // Seconds before a lock is considered "stale" and forced open

//+------------------------------------------------------------------+
//| Function: GetTradeLock                                           |
//| Purpose:  Attempts to claim the global trading semaphore.        |
//| Returns:  true if lock acquired, false if timeout occurred.      |
//+------------------------------------------------------------------+
bool GetTradeLock()
{
   datetime startTime = TimeCurrent();
   
   // Initialize the variable if it doesn't exist (set to 0 = unlocked)
   if(!GlobalVariableCheck(TRADE_LOCK_NAME)) 
      GlobalVariableSet(TRADE_LOCK_NAME, 0);

   while(true)
   {
      // 1. Check if the terminal is busy internally
      if(IsTradeContextBusy())
      {
         Sleep(100);
         continue;
      }

      // 2. Atomic Check: Try to change variable from 0 (unlocked) to 1 (locked)
      // GlobalVariableSetOnCondition returns true only if the change succeeded
      if(GlobalVariableSetOnCondition(TRADE_LOCK_NAME, 1, 0))
      {
         return(true); // Lock acquired
      }

      // 3. Safety Mechanism: Check for "Stale" locks
      // If an EA crashed while holding the lock, we must force release it
      if(TimeCurrent() - GlobalVariableTime(TRADE_LOCK_NAME) > LOCK_EXPIRATION)
      {
         Print("Trade Lock expired (stale). Forcing release.");
         GlobalVariableSet(TRADE_LOCK_NAME, 0); // Reset to 0
      }

      // 4. Check Timeout
      if(TimeCurrent() - startTime > LOCK_TIMEOUT)
      {
         Print("Error: Timed out waiting for Trade Lock.");
         return(false);
      }

      // Wait a bit before retrying
      Sleep(200); 
   }
   
   return(false);
}

//+------------------------------------------------------------------+
//| Function: ReleaseTradeLock                                       |
//| Purpose:  Releases the global trading semaphore.                 |
//+------------------------------------------------------------------+
void ReleaseTradeLock()
{
   // Set the variable back to 0 (unlocked)
   GlobalVariableSet(TRADE_LOCK_NAME, 0);
}

//+------------------------------------------------------------------+
//| Example Usage in OnTick                                          |
//+------------------------------------------------------------------+
void OnTick()
{
   // ... Your strategy logic ...
   bool buySignal = false; // Assume logic sets this to true
   
   if(buySignal)
   {
      // 1. Attempt to get the lock
      if(GetTradeLock())
      {
         // 2. Refresh rates immediately before trading (crucial after waiting)
         RefreshRates(); 
         
         // 3. Send the Order
         int ticket = OrderSend(Symbol(), OP_BUY, 0.1, Ask, 3, 0, 0, "My EA", 12345, 0, clrBlue);
         
         if(ticket < 0)
         {
            Print("OrderSend failed with error #", GetLastError());
         }
         
         // 4. CRITICAL: Release the lock immediately
         ReleaseTradeLock();
      }
      else
      {
         Print("Could not trade: Trade context busy or locked by other EA.");
      }
   }
}

Key Logic Explanation

  1. GlobalVariableSetOnCondition(Name, 1, 0): This is the core of the solution. It attempts to set the variable to 1 (Locked) only if it is currently 0 (Unlocked). This operation is atomic, meaning the operating system ensures that if two EAs call this at the exact same millisecond, only one will succeed and return true.
  2. Stale Lock Protection: If an EA crashes or is manually removed while holding the lock (value is 1), other EAs would be blocked forever. The code checks GlobalVariableTime(). If the variable hasn't been touched in 15 seconds (LOCK_EXPIRATION), it assumes the lock is dead and resets it to 0.
  3. RefreshRates(): Because the EA might Sleep() while waiting for the lock, the Ask and Bid prices stored in memory might become outdated. Calling RefreshRates() immediately after acquiring the lock ensures you trade at the current market price.
  4. IsTradeContextBusy(): This is a built-in MT4 check. We check this in addition to our custom lock to ensure the terminal itself isn't busy processing a trade from a script or manual input.

Q&A

Q: Does this work if I run EAs on different MT4 terminals?
A: No. Global Variables are specific to one MT4 terminal instance. If you have multiple terminals running, they have separate trade contexts and do not conflict with each other regarding the "Trade Context Busy" error, though they might conflict regarding account margin.

Q: Why not just use IsTradeContextBusy()?
A: IsTradeContextBusy() only returns true if the terminal is currently sending a trade. It does not handle the split-second race condition where two EAs decide to trade at the exact same moment but haven't started sending yet. The Semaphore approach handles intent.

Q: Will this slow down my trading?
A: Slightly. If multiple EAs try to trade simultaneously, they will execute sequentially (one after another). This adds a delay of a few hundred milliseconds to the subsequent trades, which is usually acceptable to ensure execution reliability.