Графики без "дыр"

Andrey Khatimlianskii | 15 июня, 2006


Мотивация

Система построения графиков в МТ 4 устроена так, что рисуются только те бары, во временных промежутках которых было хоть одно изменение цены. Если в течение минуты изменения цены не было, то на минутном графике образуется пропуск в один бар.

Разработчики сознательно выбрали такой способ построения графиков, так как  большинство пользователей их продукта отдают предпочтение графикам, содержащим только существующие цены. Тем не менее, имеются и поклонники непрерывных графиков. Они считают, что если даже изменения цены не было, то должен быть нарисован бар, у которого цена открытия равна цене закрытия предыдущего бара. Таким образом, на графике не будет пропусков по шкале времени, и 100 баров на минутном графике будут всегда соответствовать периоду 100 минут. При текущей реализации эти цифры могут отличаться. Например, 100 минут могут "вместиться" в 98 баров, если за 100 минут было две минуты без поступления котировок.

К счастью, в MQL 4 есть все необходимые инструменты для самостоятельной реализации таких графиков.


Реализация

Для начала, поделим задачу на два этапа:

  • Обработка исторических данных;

  • Обновление последнего бара.

На первом этапе мы создадим новый файл истории с приставкой "ALL" к имени символа ("ALL" – "все", в нашем контексте – "все бары")  и запишем в него историю с добавленными барами.

Схожая задача решается в скрипте "period_converter", которым укомплектован клиентский терминал  МТ 4. Скрипт генерирует график с нестандартным периодом. На его примере мы и научимся работать с файлом истории.

Перед тем, как создать программу, нужно решить, в каком виде она должна быть оформлена: в виде скрипта, индикатора или эксперта. Индикатор предназначен для отображения содержимого массивов. Это нам не понадобится. А принципиальное отличие скрипта от эксперта только в одном – скрипт удаляется с графика сразу после выполнения. На данном этапе это нас устраивает, поэтому мы будем делать именно скрипт.

Вот, что у нас получилось (AllMinutes_Step1.mq4):

#property show_inputs

//---- Разрешить/запретить рисовать бары в выходные
//---- Если == true, выходные останутся незаполнеными
//---- Если == false, выходные будут заполнены барами O=H=L=C
extern bool  SkipWeekEnd=true;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int start()
  {
   int HistoryHandle=-1,pre_time,now_time,_PeriodSec;
   double  now_close,now_open,now_low,now_high,now_volume,pre_close;

   int    _GetLastError=0,cnt_copy=0,cnt_add=0;
   int    temp[13];

//---- запоминаем символ и период графика
   string _Symbol=Symbol();
   int _Period= Period();
   _PeriodSec = _Period * 60;

//---- открываем файл, в который будем записывать историю
   string file_name=StringConcatenate("ALL",_Symbol,_Period,".hst");
   HistoryHandle=FileOpenHistory(file_name,FILE_BIN|FILE_WRITE);
   if(HistoryHandle<0)
     {
      _GetLastError=GetLastError();
      Alert("FileOpenHistory( \"",file_name,"\",FILE_BIN | FILE_WRITE )"," - Error #",_GetLastError);
      return(-1);
     }

//---- Записываем заголовок файла
   FileWriteInteger(HistoryHandle,400,LONG_VALUE);
   FileWriteString(HistoryHandle,"Copyright © 2006, komposter",64);
   FileWriteString(HistoryHandle,"ALL"+_Symbol,12);
   FileWriteInteger(HistoryHandle,_Period,LONG_VALUE);
   FileWriteInteger(HistoryHandle,Digits,LONG_VALUE);
   FileWriteInteger(HistoryHandle, 0, LONG_VALUE);       //timesign
   FileWriteInteger(HistoryHandle, 0, LONG_VALUE);       //last_sync
   FileWriteArray(HistoryHandle,temp,0,13);
//+-----------------------------------------------------------------+
//| Обрабатываем историю                                            |
//+-----------------------------------------------------------------+
   int bars = Bars;
   pre_time = Time[bars-1];
   for(int i= bars-1; i>= 0; i--)
     {
      //---- Запоминаем параметры бара
      now_open = Open[i];
      now_high = High[i];
      now_low=Low[i];
      now_close=Close[i];
      now_volume=Volume[i];
      now_time=Time[i]/_PeriodSec;
      now_time*=_PeriodSec;
      //---- если есть пропущенные бары,
      while(now_time>pre_time+_PeriodSec)
        {
         pre_time += _PeriodSec;
         pre_time /= _PeriodSec;
         pre_time *= _PeriodSec;

         //---- если это не выходные,
         if(SkipWeekEnd)
           {
            if(TimeDayOfWeek(pre_time)<=0 || 
               TimeDayOfWeek(pre_time)>5)
               continue;
            if(TimeDayOfWeek(pre_time)==5)
              {
               if(TimeHour(pre_time)==23 || 
                  TimeHour(pre_time+_PeriodSec)==23)
                 {
                  continue;
                 }
              }
           }

         //---- записываем пропущенный бар в файл
         FileWriteInteger(HistoryHandle,pre_time,LONG_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,1,DOUBLE_VALUE);
         FileFlush(HistoryHandle);
         cnt_add++;
        }

      //---- записываем новый бар в файл
      FileWriteInteger(HistoryHandle,now_time,LONG_VALUE);
      FileWriteDouble(HistoryHandle,now_open,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_low,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_high,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_close,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_volume,DOUBLE_VALUE);
      FileFlush(HistoryHandle);
      cnt_copy++;

      //---- запоминаем значение времени и цену закрытия 
      //---- записанного бара
      pre_close = now_close;
      pre_time  = now_time / _PeriodSec;
      pre_time*=_PeriodSec;
     }

//---- закрываем файл
   FileClose(HistoryHandle);

//---- выводим статистику
   Print("< - - - ",_Symbol,_Period,": было ",cnt_copy," баров, добавлено ",cnt_add," баров - - - >");
   Print("< - - - Для просмотра результатов, откройте график \"ALL",_Symbol,_Period,"\" - - - >");
   return(0);
  }

Обратите внимание на переменную SkipWeekEnd. Если её значение false, выходные тоже будут заполнены барами O=H=L=C (черточками).

Давайте проверим, как работает наш скрипт, – просто присоединим его к минутному графику GBPUSD:

Теперь давайте откроем график ALLGBPUSD1 в режиме "офф-лайн" и сравним его с исходным графиком:

Как видите, в график добавилось несколько пропущенных минуток. Они обведены красными кругами. Этого мы и добивались, не правда ли?

Теперь, когда у нас есть график с заполненными пропусками, займемся его обновлением. Сейчас на нём не будут отображаться новые котировки и, соответственно, не будут заполняться новые пропуски.

За примером опять обратимся к скрипту "period_converter". Задача обновления графиков в нём тоже решается. Сделаем только одно изменение: добавим блок заполнения пропущенных баров.  Поскольку обновлять график необходимо каждый тик, перенесём весь наш код в эксперта. Он будет запускаться с приходом каждой новой котировки. Код из первой части поместим в функцию init(), так как его необходимо выполнить только один раз, а всю новую часть – в функцию start(), так как она будет востребована каждый тик. Кроме того, закрытие файла перенесём в deinit(), ему там самое место.

Итак, код эксперта (AllMinutes_Step2.mq4):

#include <WinUser32.mqh>

//---- Разрешить/запретить рисовать бары в выходные
//---- Если == true, выходные останутся незаполнеными
//---- Если == false, выходные будут заполнены барами O=H=L=C
extern bool  SkipWeekEnd=true;

int  HistoryHandle=-1,hwnd=0,last_fpos=0,pre_time,now_time;
int  _Period,_PeriodSec;
double  now_close,now_open,now_low,now_high,now_volume;
double  pre_close,pre_open,pre_low,pre_high,pre_volume;
string  _Symbol;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int init()
  {
   int _GetLastError=0,cnt_copy=0,cnt_add=0;
   int temp[13];

//---- запоминаем символ и период графика
   _Symbol = Symbol();
   _Period = Period();
   _PeriodSec=_Period*60;
   hwnd=0;

//---- открываем файл, в который будем записывать историю
   string file_name=StringConcatenate("ALL",_Symbol,_Period,".hst");
   HistoryHandle=FileOpenHistory(file_name,FILE_BIN|FILE_WRITE);
   if(HistoryHandle<0)
     {
      _GetLastError=GetLastError();
      Alert("FileOpenHistory( \"",file_name,"\",FILE_BIN | FILE_WRITE )"," - Error #",_GetLastError);
      return(-1);
     }

//---- Записываем заголовок файла
   FileWriteInteger(HistoryHandle,400,LONG_VALUE);
   FileWriteString(HistoryHandle,"Copyright © 2006, komposter",64);
   FileWriteString(HistoryHandle,StringConcatenate("ALL",_Symbol),12);
   FileWriteInteger(HistoryHandle,_Period,LONG_VALUE);
   FileWriteInteger(HistoryHandle,Digits,LONG_VALUE);
   FileWriteInteger(HistoryHandle, 0, LONG_VALUE);       //timesign
   FileWriteInteger(HistoryHandle, 0, LONG_VALUE);       //last_sync
   FileWriteArray(HistoryHandle,temp,0,13);

//+-----------------------------------------------------------------+
//| Обрабатываем историю                                            |
//+-----------------------------------------------------------------+
   int bars = Bars;
   pre_time = Time[bars-1];
   for(int i= bars-1; i>= 1; i--)
     {
      //---- Запоминаем параметры бара
      now_open = Open[i];
      now_high = High[i];
      now_low=Low[i];
      now_close=Close[i];
      now_volume=Volume[i];
      now_time=Time[i]/_PeriodSec;
      now_time*=_PeriodSec;

      //---- если есть пропущенные бары,
      while(now_time>pre_time+_PeriodSec)
        {
         pre_time += _PeriodSec;
         pre_time /= _PeriodSec;
         pre_time *= _PeriodSec;

         //---- если это не выходные,
         if(SkipWeekEnd)
           {
            if(TimeDayOfWeek(pre_time)<=0 || TimeDayOfWeek(pre_time)>5)
               continue;
            if(TimeDayOfWeek(pre_time)==5)
              {
               if(TimeHour(pre_time)==23 || 
                  TimeHour(pre_time+_PeriodSec)==23)
                  continue;
              }
           }

         //---- записываем пропущенный бар в файл
         FileWriteInteger(HistoryHandle,pre_time,LONG_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle,1,DOUBLE_VALUE);
         FileFlush(HistoryHandle);
         cnt_add++;
        }

      //---- записываем новый бар в файл
      FileWriteInteger(HistoryHandle,now_time,LONG_VALUE);
      FileWriteDouble(HistoryHandle,now_open,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_low,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_high,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_close,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,now_volume,DOUBLE_VALUE);
      FileFlush(HistoryHandle);
      cnt_copy++;

      //---- запоминаем значение времени и цену закрытия записанного 
      //---- бара
      pre_close= now_close;
      pre_time = now_time/_PeriodSec;
      pre_time*=_PeriodSec;
     }

   last_fpos=FileTell(HistoryHandle);

//---- выводим статистику
   Print("< - - - ",_Symbol,_Period,": было ",cnt_copy," баров,добавлено ",cnt_add," баров - - - >");
   Print("< - - - Для просмотра результатов, откройте график \"ALL",_Symbol,_Period,"\" - - - >");

//---- вызываем функцию старт, чтоб сразу наисовался 0-й бар
   start();

   return(0);
  }
//----
int start()
  {
//+---------------------------------------------------------------+
//| Обрабатываем поступающие тики                                 |
//+---------------------------------------------------------------+

//---- ставим "курсор" перед последним баром
//---- (это необходимо на всех запусках, кроме первого)
   FileSeek(HistoryHandle,last_fpos,SEEK_SET);

//---- Запоминаем параметры бара
   now_open = Open[0];
   now_high = High[0];
   now_low=Low[0];
   now_close=Close[0];
   now_volume=Volume[0];
   now_time=Time[0]/_PeriodSec;
   now_time*=_PeriodSec;

//---- если бар сформировался, 
   if(now_time>=pre_time+_PeriodSec)
     {
      //---- записываем сформировавшийся бар
      FileWriteInteger(HistoryHandle,pre_time,LONG_VALUE);
      FileWriteDouble(HistoryHandle,pre_open,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,pre_low,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,pre_high,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,pre_volume,DOUBLE_VALUE);
      FileFlush(HistoryHandle);

      //---- запоминаем место в файле, перед записью 0-го бара
      last_fpos=FileTell(HistoryHandle);
     }

//---- если появились пропущенные бары,
   while(now_time>pre_time+_PeriodSec)
     {
      pre_time += _PeriodSec;
      pre_time /= _PeriodSec;
      pre_time *= _PeriodSec;

      //---- если это не выходные,
      if(SkipWeekEnd)
        {
         if(TimeDayOfWeek(pre_time)<=0 || 
            TimeDayOfWeek(pre_time)>5)
            continue;
         if(TimeDayOfWeek(pre_time)==5)
           {
            if(TimeHour(pre_time)==23 || 
               TimeHour(pre_time+_PeriodSec)==23)
               continue;
           }
        }

      //---- записываем пропущенный бар в файл
      FileWriteInteger(HistoryHandle,pre_time,LONG_VALUE);
      FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,pre_close,DOUBLE_VALUE);
      FileWriteDouble(HistoryHandle,1,DOUBLE_VALUE);
      FileFlush(HistoryHandle);

      //---- запоминаем место в файле, перед записью 0-го бара
      last_fpos=FileTell(HistoryHandle);
     }

//---- записываем текущий бар
   FileWriteInteger(HistoryHandle,now_time,LONG_VALUE);
   FileWriteDouble(HistoryHandle,now_open,DOUBLE_VALUE);
   FileWriteDouble(HistoryHandle,now_low,DOUBLE_VALUE);
   FileWriteDouble(HistoryHandle,now_high,DOUBLE_VALUE);
   FileWriteDouble(HistoryHandle,now_close,DOUBLE_VALUE);
   FileWriteDouble(HistoryHandle,now_volume,DOUBLE_VALUE);
   FileFlush(HistoryHandle);

//---- запоминаем параметры записанного бара
   pre_open = now_open;
   pre_high = now_high;
   pre_low=now_low;
   pre_close=now_close;
   pre_volume=now_volume;
   pre_time=now_time/_PeriodSec;
   pre_time*=_PeriodSec;

//---- находим окно, в которое будем "отправлять" свежие котировки
   if(hwnd==0)
     {
      hwnd=WindowHandle(StringConcatenate("ALL",_Symbol),_Period);
      if(hwnd!=0)
        {
         Print("< - - - График ","ALL"+_Symbol,_Period," найден! - - - >");
        }
     }
//---- и, если нашли, обновляем его
   if(hwnd!=0)
     {
      PostMessageA(hwnd,WM_COMMAND,33324,0);
     }
  }
//----
int deinit()
  {
   if(HistoryHandle>=0)
     {
      //---- закрываем файл
      FileClose(HistoryHandle);
      HistoryHandle=-1;
     }
   return(0);
  }

Сразу сделаю оговорку, что процесс обновления графика достаточно ресурсоемкий, потому что терминал загружает все бары, записанные в файл. Если баров в файле достаточно много, терминал может заметно тормозить. Это во многом зависит и от характеристик компьютера, на котором установлен клиентский терминал  МТ 4. В любом случае ресурсы не могут быть бесконечными. Решим мы этот вопрос просто – уменьшим количество отображаемых на графике баров до 10 000 ("Сервис" – "Настройки" – "Графики", параметр "Макс. баров в окне"). Теперь перезапустим терминал и подключим наш эксперт:

  

Эксперт сразу "залатал" историю и начал ждать появления новых тиков. Спустя две минуты, эти же графики выглядели так:

   

Как видите, на верхнем графике была добавлена одна "минутка", в то время как на нижнем добавился ещё и пропущенный бар.

Значит, мы достигли желаемого результата!


Масштабирование

Один график - это, конечно, хорошо, но что делать, если надо открыть 10 графиков без пропущенных баров? Открывать для каждого графика ещё один, "рабочий", на котором будет работать эксперт, – не самый лучший выход. Будут потрачены лишние ресурсы, и, соответственно, работать станет менее комфортно.

Давайте сделаем эксперт, умеющего обрабатывать любое количество графиков. Это будет удобное в использовании и экономное решение.

Итак, что нам надо поменять в нашем коде, чтобы он работал с несколькими графиками:

  • добавить внешнюю переменную, с помощью которой можно будет менять список графиков;
  • все переменные заменить массивами с количеством элементов равным количеству обрабатываемых графиков;
  • весь код поместить в цикл, в котором будут перебираться эти самые графики;
  • блок обновления поместить в бесконечный цикл, таким образом отвязавшись от прихода котировок. Если в списке будут разные символы, их время обновления может быть разным.

Вот, что у нас должно получиться (AllMinutes.mq4):

#include <WinUser32.mqh>

//---- Список графиков, которые необходимо обрабатывать, разделённый 
запятой(",")
extern string    ChartList="EURUSD1,GBPUSD1";
//---- Разрешить/запретить рисовать бары в выходные
//---- Если == true, выходные останутся незаполнеными
//---- Если == false, выходные будут заполнены барами O=H=L=C
extern bool     SkipWeekEnd=true;
//---- Частота, с которой будут обновляться графики, в милисекундах
//---- Чем больше значение, тем меньше ресурсов будет использовать 
//---- эксперт.
extern int   RefreshLuft=1000;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int init()
  {
   start();
   return(0);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int start()
  {
   int _GetLastError=0,cnt_copy=0,cnt_add=0,temp[13];
   int Charts=0,pos=0,curchar=0,len=StringLen(ChartList);
   string cur_symbol="",cur_period="",file_name="";

   string _Symbol[100]; int _Period[100],_PeriodSec[],_Bars[];
   int HistoryHandle[],hwnd[],last_fpos[],pre_time[],now_time[];
   double now_close[],now_open[],now_low[],now_high[],now_volume[];
   double pre_close[],pre_open[],pre_low[],pre_high[],pre_volume[];

//---- считаем количество графиков, которые необходимо обработать
   while(pos<=len)
     {
      curchar=StringGetChar(ChartList,pos);
      if(curchar>47 && curchar<58)
         cur_period=cur_period+CharToStr(curchar);
      else
        {
         if(curchar==',' || pos==len)
           {
            MarketInfo(cur_symbol,MODE_BID);
            if(GetLastError()==4106)
              {
               Alert("Неизвестный символ ",cur_symbol,"!!!");
               return(-1);
              }
            if(iClose(cur_symbol,StrToInteger(cur_period),0)<=0)
              {
               Alert("Неизвестный период ",cur_period,"!!!");
               return(-1);
              }

            _Symbol[Charts] = cur_symbol;
            _Period[Charts] = StrToInteger(cur_period);
            cur_symbol = "";
            cur_period = "";

            Charts++;
           }
         else
            cur_symbol=cur_symbol+CharToStr(curchar);
        }
      pos++;
     }
   Print("< - - - Найдено ",Charts," корректных графиков. - - - >");
   ArrayResize(_Symbol,Charts);
   ArrayResize(_Period,Charts);
   ArrayResize(HistoryHandle,Charts);
   ArrayResize(hwnd,Charts);
   ArrayResize(last_fpos,Charts);
   ArrayResize(pre_time,Charts);
   ArrayResize(now_time,Charts);
   ArrayResize(now_close,Charts);
   ArrayResize(now_open,Charts);
   ArrayResize(now_low,Charts);
   ArrayResize(now_high,Charts);
   ArrayResize(now_volume,Charts);
   ArrayResize(pre_close,Charts);
   ArrayResize(pre_open,Charts);
   ArrayResize(pre_low,Charts);
   ArrayResize(pre_high,Charts);
   ArrayResize(pre_volume,Charts);
   ArrayResize(_PeriodSec,Charts);
   ArrayResize(_Bars,Charts);

   for(int curChart=0; curChart<Charts; curChart++)
     {
      _PeriodSec[curChart]=_Period[curChart] *60;

      //---- открываем файл, в который будем записывать историю
      file_name=StringConcatenate("ALL",_Symbol[curChart],_Period[curChart],".hst");
      HistoryHandle[curChart]=FileOpenHistory(file_name,FILE_BIN|FILE_WRITE);
      if(HistoryHandle[curChart]<0)
        {
         _GetLastError=GetLastError();
         Alert("FileOpenHistory( \"",file_name,"\", FILE_BIN | FILE_WRITE)"," - Error #",_GetLastError);
         continue;
        }

      //---- Записываем заголовок файла
      FileWriteInteger(HistoryHandle[curChart],400,LONG_VALUE);
      FileWriteString(HistoryHandle[curChart],"Copyright © 2006, komposter",64);
      FileWriteString(HistoryHandle[curChart],StringConcatenate("ALL",_Symbol[curChart]),12);
      FileWriteInteger(HistoryHandle[curChart],_Period[curChart],LONG_VALUE);
      FileWriteInteger(HistoryHandle[curChart],MarketInfo(_Symbol[curChart],MODE_DIGITS),LONG_VALUE);
      FileWriteInteger(HistoryHandle[curChart], 0, LONG_VALUE);    // timesign
      FileWriteInteger(HistoryHandle[curChart], 0, LONG_VALUE);    // last_sync
      FileWriteArray(HistoryHandle[curChart],temp,0,13);

      //+-----------------------------------------------------------+
      //| Обрабатываем историю                                      |
      //+-----------------------------------------------------------+
      _Bars[curChart]=iBars(_Symbol[curChart],_Period[curChart]);
      pre_time[curChart]=iTime(_Symbol[curChart],_Period[curChart],_Bars[curChart]-1);
      for(int i=_Bars[curChart]-1; i>=1; i--)
        {
         //---- Запоминаем параметры бара
         now_open[curChart] = iOpen(_Symbol[curChart], _Period[curChart], i);
         now_high[curChart] = iHigh(_Symbol[curChart], _Period[curChart], i);
         now_low[curChart]=iLow(_Symbol[curChart],_Period[curChart],i);
         now_close[curChart]=iClose(_Symbol[curChart],_Period[curChart],i);
         now_volume[curChart]=iVolume(_Symbol[curChart],_Period[curChart],i);
         now_time[curChart]=iTime(_Symbol[curChart],_Period[curChart],i)/
                            _PeriodSec[curChart];
         now_time[curChart]*=_PeriodSec[curChart];

         //---- если есть пропущенные бары,
         while(now_time[curChart]>pre_time[curChart]+_PeriodSec[curChart])
           {
            pre_time[curChart] += _PeriodSec[curChart];
            pre_time[curChart] /= _PeriodSec[curChart];
            pre_time[curChart] *= _PeriodSec[curChart];

            //---- если это не выходные,
            if(SkipWeekEnd)
              {
               if(TimeDayOfWeek(pre_time[curChart])<=0 || 
                  TimeDayOfWeek(pre_time[curChart])>5)
                  continue;
               if(TimeDayOfWeek(pre_time[curChart])==5)
                 {
                  if(TimeHour(pre_time[curChart])==23 || 
                     TimeHour(pre_time[curChart]+
                     _PeriodSec[curChart])==23)
                     continue;
                 }
              }

            //---- записываем пропущенный бар в файл
            FileWriteInteger(HistoryHandle[curChart],pre_time[curChart],LONG_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],1,DOUBLE_VALUE);
            FileFlush(HistoryHandle[curChart]);
            cnt_add++;
           }

         //---- записываем новый бар в файл
         FileWriteInteger(HistoryHandle[curChart],now_time[curChart],LONG_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_open[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_low[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_high[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_close[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_volume[curChart],DOUBLE_VALUE);
         FileFlush(HistoryHandle[curChart]);
         cnt_copy++;

         //---- запоминаем значение времени и цену закрытия 
         //---- записанного бара
         pre_close[curChart]= now_close[curChart];
         pre_time[curChart] = now_time[curChart]/_PeriodSec[curChart];
         pre_time[curChart]*=_PeriodSec[curChart];
        }

      last_fpos[curChart]=FileTell(HistoryHandle[curChart]);

      //---- выводим статистику
      Print("< - - - ",_Symbol[curChart],_Period[curChart],": было ",cnt_copy," баров, добавлено ",cnt_add," баров - - - >");
      Print("< - - - Для просмотра результатов, откройте график \"ALL",_Symbol[curChart],_Period[curChart],"\" - - - >");

     }

//+---------------------------------------------------------------+
//| Обрабатываем поступающие тики                                 |
//+---------------------------------------------------------------+
   while(!IsStopped())
     {
      RefreshRates();
      for(curChart=0; curChart<Charts; curChart++)
        {
         //---- ставим "курсор" перед последним баром
         //---- (это необходимо на всех запусках, кроме первого)
         FileSeek(HistoryHandle[curChart],last_fpos[curChart],
                  SEEK_SET);

         //---- Запоминаем параметры бара
         now_open[curChart]=iOpen(_Symbol[curChart],_Period[curChart],0);
         now_high[curChart]=iHigh(_Symbol[curChart],_Period[curChart],0);
         now_low[curChart]=iLow(_Symbol[curChart],_Period[curChart],0);
         now_close[curChart]=iClose(_Symbol[curChart],_Period[curChart],0);
         now_volume[curChart]=iVolume(_Symbol[curChart],_Period[curChart],0);
         now_time[curChart]=iTime(_Symbol[curChart],_Period[curChart],0)/_PeriodSec[curChart];
         now_time[curChart]*=_PeriodSec[curChart];

         //---- если бар сформировался, 
         if(now_time[curChart]>=pre_time[curChart]+_PeriodSec[curChart])
           {
            //---- записываем сформировавшийся бар
            FileWriteInteger(HistoryHandle[curChart],pre_time[curChart],LONG_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_open[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_low[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_high[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_volume[curChart],DOUBLE_VALUE);
            FileFlush(HistoryHandle[curChart]);

            //---- запоминаем место в файле, перед записью 0-го бара
            last_fpos[curChart]=FileTell(HistoryHandle[curChart]);
           }

         //---- если появились пропущенные бары,
         while(now_time[curChart]>pre_time[curChart]+_PeriodSec[curChart])
           {
            pre_time[curChart] += _PeriodSec[curChart];
            pre_time[curChart] /= _PeriodSec[curChart];
            pre_time[curChart] *= _PeriodSec[curChart];

            //---- если это не выходные,
            if(SkipWeekEnd)
              {
               if(TimeDayOfWeek(pre_time[curChart])<=0 || TimeDayOfWeek(pre_time[curChart])>5)
                  continue;
               if(TimeDayOfWeek(pre_time[curChart])==5)
                 {
                  if(TimeHour(pre_time[curChart])==23 || TimeHour(pre_time[curChart]+_PeriodSec[curChart])==23)
                     continue;
                 }
              }

            //---- записываем пропущенный бар в файл
            FileWriteInteger(HistoryHandle[curChart],pre_time[curChart],LONG_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],pre_close[curChart],DOUBLE_VALUE);
            FileWriteDouble(HistoryHandle[curChart],1,DOUBLE_VALUE);
            FileFlush(HistoryHandle[curChart]);

            //---- запоминаем место в файле, перед записью 0-го бара
            last_fpos[curChart]=FileTell(HistoryHandle[curChart]);
           }

         //---- записываем текущий бар
         FileWriteInteger(HistoryHandle[curChart],now_time[curChart],LONG_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_open[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_low[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_high[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_close[curChart],DOUBLE_VALUE);
         FileWriteDouble(HistoryHandle[curChart],now_volume[curChart],DOUBLE_VALUE);
         FileFlush(HistoryHandle[curChart]);

         //---- запоминаем параметры записанного бара
         pre_open[curChart] = now_open[curChart];
         pre_high[curChart] = now_high[curChart];
         pre_low[curChart]=now_low[curChart];
         pre_close[curChart]=now_close[curChart];
         pre_volume[curChart]=now_volume[curChart];
         pre_time[curChart]=now_time[curChart]/
                            _PeriodSec[curChart];
         pre_time[curChart]*=_PeriodSec[curChart];

         //---- находим окно, в которое будем "отправлять" свежие котировки
         if(hwnd[curChart]==0)
           {
            hwnd[curChart]=WindowHandle(StringConcatenate("ALL",_Symbol[curChart]),_Period[curChart]);
            if(hwnd[curChart]!=0)
               Print("< - - - График ","ALL"+_Symbol[curChart],_Period[curChart]," найден! - - - >");
           }
         //---- и, если нашли, обновляем его
         if(hwnd[curChart]!=0)
            PostMessageA(hwnd[curChart],WM_COMMAND,33324,0);
        }
      Sleep(RefreshLuft);
     }
   for(curChart=0; curChart<Charts; curChart++)
     {
      if(HistoryHandle[curChart]>=0)
        {
         //---- закрываем файл
         FileClose(HistoryHandle[curChart]);
         HistoryHandle[curChart]=-1;
        }
     }
   return(0);
  }

Теперь попробуем запустить эксперт на пятиминутном графике EURUSD с параметром ChartList, равным "EURUSD1,GBPUSD1,EURGBP1", и откроем все три графика в режиме "офф-лайн":

 


Кажется, у нас всё получилось.

Все три графика синхронно обновляются и при появлении пропусков будут "залатаны".