Problem Description
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
- Check Lock: Before
OrderSend, the EA attempts to "claim" a specific Global Variable. - Wait: If the variable is already claimed by another EA, the current EA waits (sleeps) and retries.
- Execute: Once claimed, the EA sends the order.
- 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
GlobalVariableSetOnCondition(Name, 1, 0): This is the core of the solution. It attempts to set the variable to1(Locked) only if it is currently0(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 returntrue.- 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. RefreshRates(): Because the EA mightSleep()while waiting for the lock, theAskandBidprices stored in memory might become outdated. CallingRefreshRates()immediately after acquiring the lock ensures you trade at the current market price.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.