A common request I get is "one EA, attached to one chart, trading eight pairs". It is a good design, because one EA is easier to manage and can control total risk in one place. But the first version almost always has the same bug: it trades the chart symbol fine and misses or delays signals on the others.
Here is why, and how I structure these EAs now.
1. OnTick only fires for the chart symbol
OnTick runs when the symbol of the chart the EA is attached to gets a new tick. If the EA sits on EURUSD and GBPJPY moves, nothing happens until EURUSD ticks again. In a quiet EURUSD hour that can be many seconds.
The fix is to drive the logic from a timer instead:
string Symbols[] = {"EURUSD", "GBPJPY", "XAUUSD"};
int OnInit()
{
for(int i = 0; i < ArraySize(Symbols); i++)
if(!SymbolSelect(Symbols[i], true))
Print("Cannot select ", Symbols[i]);
EventSetTimer(1);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
EventKillTimer();
}
void OnTimer()
{
for(int i = 0; i < ArraySize(Symbols); i++)
ProcessSymbol(Symbols[i]);
}
SymbolSelect matters too. A symbol that is not in Market Watch does not receive quotes, so the EA reads stale prices without any error.
2. History for other symbols may not be ready
For the chart symbol, the terminal keeps history loaded. For the others, the first CopyRates call can return fewer bars than you asked for, or fail, while the terminal downloads data. Check it before using the numbers:
bool DataReady(const string sym, ENUM_TIMEFRAMES tf, int need)
{
if(!SeriesInfoInteger(sym, tf, SERIES_SYNCHRONIZED))
return false;
MqlRates r[];
return CopyRates(sym, tf, 0, need, r) == need;
}
If it returns false, skip that symbol this cycle and try again on the next timer event. Do not fall back to zeros.
3. New bar detection has to be per symbol
A single lastBar variable is the classic mistake. Keep one per symbol and timeframe:
datetime LastBar[];
bool IsNewBar(int idx, const string sym, ENUM_TIMEFRAMES tf)
{
datetime t = iTime(sym, tf, 0);
if(t == 0) return false;
if(t == LastBar[idx]) return false;
LastBar[idx] = t;
return true;
}
Resize LastBar to the number of symbols in OnInit.
4. Symbol properties differ, so read them per symbol
Gold, an index and a forex pair have different digits, tick sizes, minimum lots and stops levels. Any code that uses _Point, _Digits or Symbol() inside the loop is reading the chart symbol, not the one being traded. Use SymbolInfoDouble(sym, SYMBOL_POINT), SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_STEP and friends for every calculation.
5. Control risk across all symbols, not per symbol
The advantage of one EA is that it can see total exposure. Before opening a new position, sum the risk of everything the EA already holds (by magic number) and refuse the trade if the total would cross your limit. Correlated pairs like EURUSD and GBPUSD can turn three "small" trades into one big one.
6. Test it like it will run
The Strategy Tester does support multi symbol EAs, but only the symbols you actually request get history. Run at least one test in "Every tick based on real ticks" mode, and compare the trade list per symbol with what you expect from the chart. Missing trades on the non chart symbols are the first sign that one of the issues above is still there.
Built this way, one EA can handle a whole basket reliably. Built the quick way, it trades one pair well and the rest by luck.








