A bug I see in many MT5 Expert Advisors looks like a strategy problem at first. The EA opens a trade right after it is attached to a chart, or right after the terminal restarts, and the entry makes no sense. The journal shows a moving average of 0, or a value like 1.7976931348623157e+308. A few minutes later everything works normally.
The cause is almost always the way indicator data is read. In MQL5, indicators are not functions that return a value. They are separate calculation objects that you create once and read from later, and they need time to calculate. Here is how I structure indicator access in every EA now.
1. Create handles once, in OnInit
Functions like iMA, iRSI and iATR return a handle, not a value. The terminal starts calculating the indicator in the background when you create it.
If you call iMA inside OnTick, the terminal usually hands back the same handle for identical parameters, so it may look harmless, but you pay a lookup on every tick, you make it easy to leak handles when parameters change, and you hide the fact that the first read after creation can fail. Create every handle in OnInit, check it, and fail the initialization if it is invalid. An EA that cannot build its indicators should not run at all.
2. Check that the data is ready before reading it
Right after creation, the indicator may not have calculated any bars yet. BarsCalculated(handle) returns how many bars are ready, or a negative value while it is still working. CopyBuffer will return a negative value or fewer values than you asked for, and GetLastError often reports 4806 (ERR_INDICATOR_DATA_NOT_FOUND).
The wrong reaction is to ignore the return value and use whatever is in the array. The array then holds zeros from initialization or leftovers from a previous tick, and your crossover logic happily compares them.
The right reaction is to treat "not ready" as a normal state: skip this tick and try again on the next one.
3. Read closed bars, and know your array direction
Bar 0 is the bar that is still forming. Its indicator value changes with every tick, so a signal on bar 0 can appear and vanish within the same minute. For most strategies, read from bar 1, the last closed bar.
CopyBuffer fills the array oldest first unless the array is set as a series. I always call ArraySetAsSeries(array, true) before copying, so index 0 means the most recent value I requested. Mixing the two conventions is a classic source of signals that fire one bar late or in the wrong direction.
4. Handle empty values
Some indicators, especially custom ones that draw arrows or gaps, write EMPTY_VALUE on bars where they have nothing to say. That is the huge number from the journal above. Compare against EMPTY_VALUE before using a value, otherwise a stop loss or a threshold check can be calculated from it.
5. Release handles in OnDeinit
Call IndicatorRelease for every handle when the EA is removed or reinitialized. It keeps the terminal clean when you switch timeframes or change inputs during testing.
Putting it together
input int InpFastPeriod = 20;
input int InpSlowPeriod = 50;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
int OnInit()
{
fastHandle = iMA(_Symbol, PERIOD_CURRENT, InpFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, PERIOD_CURRENT, InpSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
{
Print("Could not create indicator handles, error ", GetLastError());
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}
// Copies 'count' closed bar values (starting at bar 1) into out[].
// out[0] is the last closed bar. Returns false if data is not ready.
bool ReadClosedBars(const int handle, const int count, double &out[])
{
int ready = BarsCalculated(handle);
if(ready < count + 1)
return false;
ArraySetAsSeries(out, true);
int copied = CopyBuffer(handle, 0, 1, count, out);
if(copied != count)
{
PrintFormat("CopyBuffer returned %d of %d values, error %d",
copied, count, GetLastError());
return false;
}
for(int i = 0; i < count; i++)
if(out[i] == EMPTY_VALUE || out[i] == 0.0)
return false;
return true;
}
void OnTick()
{
double fast[], slow[];
if(!ReadClosedBars(fastHandle, 2, fast) ||
!ReadClosedBars(slowHandle, 2, slow))
return; // indicator not ready yet, try on the next tick
bool crossUp = fast[1] <= slow[1] && fast[0] > slow[0];
bool crossDown = fast[1] >= slow[1] && fast[0] < slow[0];
if(crossUp)
Print("Fast EMA crossed above slow EMA on the last closed bar");
if(crossDown)
Print("Fast EMA crossed below slow EMA on the last closed bar");
}
The zero check in the loop is a deliberate choice for a moving average, which can never be zero on a real price series. For an oscillator where zero is a legitimate value, remove it and rely on BarsCalculated and the copied count.
A note on other timeframes and symbols
If the handle is created on another timeframe or symbol, that history may not be loaded yet when the EA starts. BarsCalculated still protects you, but it can take longer. I also check SeriesInfoInteger(symbol, timeframe, SERIES_SYNCHRONIZED) before trusting a higher timeframe value, and log when the EA is waiting, so "no trades for the first two minutes" is explained in the journal instead of looking like a bug.
Testing it
The Strategy Tester rarely shows this problem, because history is prepared before the test starts. To reproduce it, attach the EA to a chart of a symbol you have not opened recently, or restart the terminal with the EA running, and watch the first lines of the Experts tab. With the guard in place you should see the EA wait rather than act on empty data.
Most "random bad trades after a restart" I have traced came down to one unchecked CopyBuffer call. It is a few lines of code to fix, and it removes a whole class of bugs that only appear on live charts.
Top comments (2)
One extra guard before using this example for orders: crossUp or crossDown stays true on every tick while those two closed bars stay the same, so the current OnTick can print the same crossing repeatedly.
I'd keep the last processed closed-bar timestamp and update it only after both buffer reads succeed. Updating it before the reads would lose that bar if the first tick arrives before the indicators are ready.
That gives two useful checks: repeated ticks produce one decision, and a failed first read still gets another chance on the next tick. Restart deduplication needs a separate decision too.
Good catch, and you are right that the example only shows the read side. In a real EA I gate the whole block on the closed bar time, something like this:
The order matters for exactly the reason you gave. The timestamp only moves forward after both reads succeed, so a first tick that arrives before BarsCalculated is ready just tries again on the next tick instead of skipping the bar.
For restarts I treat it as a separate question, as you said. lastProcessedBar starts at 0, so after a restart the EA would evaluate the last closed bar again. I usually decide that per strategy: either initialise it to the current closed bar in OnInit so nothing fires until a new bar forms, or let it evaluate once but check open positions and recent deals with the EA's magic number before acting, so it cannot enter the same signal twice.