Using iBarShift on different symbols and timeframes

The Metatrader iBarShift function is able to return the bar number matching a specified symbol and timeframe. The online documentation can be found here.

Occasionally, however, people are having difficulty using the function and getting incorrect results like bar number -1 or 0 when there should be a matched bar. This is usually a simple problem to resolve and I will explain here why it happens and how to fix the problem.

iBarShift does exactly as described and in most cases you will have no problem assuming you follow the instructions and use it correctly. But where the function becomes most useful is in searching a timeframe or symbol that is not the same as the current chart. For example, if you are using the 1 hour EURUSD chart and need a function to find the highest point of the last 15 minutes you might use code like this.

//
// Simply find the bar 5 minutes earlier and print the bar time
//
datetime now = TimeCurrent();
datetime then = now-300;
int bar = iBarShift(xSymbol, PERIOD_M1, then, true);
PrintFormat("now=%s, then=%s, bar=%i, bar time=%s", TimeToStr(now), TimeToStr(then), bar, TimeToStr(iTime(xSymbol, PERIOD_M1, bar)));

What is not immediately obvious is that if you do not also have the EURUSD 1 minute chart open then Metatrader does not have these bars in memory and cannot return a result. Metatrader only loads information as required to save resources and if the 1 minute chart is not open and there have been no other calls to search the 1 minute charts then that information is not available and the results are incorrect. You can, however identify when this happens by checking for error code 4066 which indicates that the information requested is not in memory and is being loaded.

Now, the reason I have written this article is that I recently stumbled on a number of forum questions asking about using iBarShift and some of those had a suggested solution to the problem but the solution was not quite right. The proposed solution involved a number of retries with code like this:

//
// Simply find the bar 5 minutes earlier and print the bar time
//
datetime	now	=	TimeCurrent();
datetime	then	=	now-300;
int		bar	=	iBarShift(xSymbol, PERIOD_M1, then, true);
int		error	=	GetLastError();
PrintFormat("now=%s, then=%s, bar=%i, bar time=%s, error=%i", TimeToStr(now), TimeToStr(then), bar, TimeToStr(iTime(xSymbol, PERIOD_M1, bar)), error);
ResetLastError();
for (int i = 0; i < 20 && error!=0; i++) {
   Sleep(1000);
   bar	=	iBarShift(xSymbol, PERIOD_M1, then, true);
   error	=	GetLastError();
   PrintFormat("Attempt %i, now=%s, then=%s, bar=%i, bar time=%s, error=%i", i, TimeToStr(now), TimeToStr(then), bar, TimeToStr(iTime(xSymbol, PERIOD_M1, bar)), error);
   ResetLastError();
}

This code has introduced a check for the last error returned followed by a loop to sleep for one second and try again until the error is resolved. Yo can see that I have added more parameters to the PrintFormat statement to show the error number and the attempt number in the loop. To avoid getting into an infinite loop I have limited the number of retries to 20.

You can also see 2 additional ResetLastError statements just after the PrintFormat statements. These are to clear any errors generated by the call to iTime using an invalid bar number.

Unfortunately this is not the correct solution and the reason is subtle. If you run this code (remember that each time you run this kind of code it may fail but will result in the relevant data being loaded so repeat tests will pass, so you will only see the problem on the first pass unless you select a different symbol each time) you will see that the first call returns error 4066 but the second call returns error 0 and yet still shows bar number 0 or -1, which is incorrect. The subtle reason is that Metatrader has downloaded the bar information after the first call, hence error 4066 is no longer being reported, but the new information is not yet available to the script. During the OnTick event or running a script Metatrader provides a snapshot of rate information taken at the beginning of the action and the snapshot will not normally change until that event or script completes. So while the first call resolves the 4066 error subsequent calls to iBarShift still do not have access to updated bar information.

The final fix is simple, include a call to RefreshRates() which will update the snapshot. Your code should look something like this.

//
// Simply find the bar 5 minutes earlier and print the bar time
//
datetime	now	=	TimeCurrent();
datetime	then	=	now-300;
int		bar	=	iBarShift(xSymbol, PERIOD_M1, then, true);
int		error	=	GetLastError();
PrintFormat("now=%s, then=%s, bar=%i, bar time=%s, error=%i", TimeToStr(now), TimeToStr(then), bar, TimeToStr(iTime(xSymbol, PERIOD_M1, bar)), error);
ResetLastError();
for (int i = 0; i < 20 && error!=0; i++) {
   Sleep(1000);
   RefreshRates();
   bar	=	iBarShift(xSymbol, PERIOD_M1, then, true);
   error	=	GetLastError();
   PrintFormat("Attempt %i, now=%s, then=%s, bar=%i, bar time=%s, error=%i", i, TimeToStr(now), TimeToStr(then), bar, TimeToStr(iTime(xSymbol, PERIOD_M1, bar)), error);
   ResetLastError();
}

The call to RefreshRates will reload the snapshot of rate information and the next call to iBarShift will succeed.

Of course, introducing a potential 20 second delay to your code would normally not be recommended but you should be able to use the information here to understand why the problem occurs and implement a better solution for your situation.

Leave a Reply