Most Expert Advisors make their decisions once per bar, not on every tick. That sounds simple, and the usual snippet looks like this:
datetime lastBar = 0;
void OnTick()
{
datetime current = iTime(_Symbol, PERIOD_CURRENT, 0);
if(current == lastBar) return;
lastBar = current;
// new bar logic
}
It works on a chart in a quiet market. It breaks in four situations I see again and again in client code.
1. iTime can return 0
Right after the terminal starts, or when history for the symbol is still loading, iTime returns 0. The code above then stores 0, and on the next tick it sees a "new bar" that is really just the data arriving. Guard against it:
datetime current = iTime(_Symbol, PERIOD_CURRENT, 0);
if(current == 0) return; // no data yet, try again on the next tick
2. The first tick after attaching
When you attach the EA, lastBar is 0, so the very first tick counts as a new bar even if the bar is already half finished. If your entry logic assumes it is running at the open of a bar, it will trade on a partial candle. Decide what you want explicitly. Usually that means recording the current bar in OnInit and waiting for the next one:
int OnInit()
{
lastBar = iTime(_Symbol, PERIOD_CURRENT, 0);
return(INIT_SUCCEEDED);
}
3. Gaps and quiet symbols
New bar logic is driven by ticks. If a symbol has no ticks for twenty minutes, an M5 EA skips several bars and then wakes up once. Code that counts bars ("exit after 6 bars") will be wrong. Count time instead, by comparing the bar open time of the entry with the current one using iBarShift, so skipped bars are still counted.
4. Multiple timeframes
An EA that trades M15 but filters on H1 often reuses one lastBar variable for both, or checks H1 with iTime on every M15 bar and assumes it changed. Keep one tracker per timeframe:
class CNewBar
{
ENUM_TIMEFRAMES m_tf;
datetime m_last;
public:
CNewBar(ENUM_TIMEFRAMES tf) : m_tf(tf), m_last(0) {}
bool Check()
{
datetime t = iTime(_Symbol, m_tf, 0);
if(t == 0 || t == m_last) return false;
bool first = (m_last == 0);
m_last = t;
return !first; // the first call only primes the tracker
}
};
CNewBar barM15(PERIOD_M15), barH1(PERIOD_H1);
Testing it
Run the EA in the Strategy Tester with "Every tick based on real ticks" on a symbol with weekend gaps, and print a line every time Check() returns true. Then count the lines against the number of bars in the test window. If the numbers do not match, you have found the bug before your account did.
How do you handle new bar detection in your own EAs?
Top comments (1)
A small startup fixture I would add for the class: timestamps 0, T, T, T+15 minutes should return false, false, false, true. The first valid history value primes the tracker; it is not a new trading event. That also checks startup when iTime initially returns zero. If this becomes a multi-symbol helper, I would give each symbol/timeframe pair its own tracker.