Синхронизация разных данных с разных таймфреймов при вызове из индикатора

 
Добрый вечер.

Проблема синхронизации в ИНДИКАТОРЕ данных с других таймфреймов на МТ5 обсуждалась давно и много. Я изучил документацию, прочитал все, что смог найти на форуме, но ответа на свой вопрос не нашел.


Ситуация следующая: Индикатор запускается и пытается получить данные с большего таймфрейма того же символа. Я пока не поднимаю тему получения хэндлов индикаторов. Нужно хотя бы получить историю функциями Bars, iBarsShift и т.д. В момент запуска индикатора эти данные могут быть не рассчитаны. При этом использование стандартного примера проверки (из описания функции Bars()) не возможно по причине запрета на использование Sleep(). Индикатор не будет ждать подгрузки истории, чтобы не тормозить общий поток. Документация рекомендует в этом случае использовать проверку в процедурах-обработчиках событий (OnTimer, OnCalculate и т.д.) В нижеприведенном примере я так и сделал. Индикатор ждет  прихода следующего тика, а в это время подкачивается история. Но что делать, когда индикатор запускается в нерабочее время или же когда тики не возможно получить по другим причинам (например, по фьючерсам с истекшим сроком исполнения). Особенно часто такая проблема возникает при старте терминала. С приходом тика история подкачивается. А если тик не придет в разумное время? 
Вот тут и нужны какие-то костыли для решения этой проблемы. Как вариант - создать принудительный вызов процедур OnInit или OnCalculate из таймера (кстати, как это сделать?). Поделитесь опытом, как вы решали данную задачу. Текст шаблона индикатора с проверками прилагаю.
Прошу обратить особое внимание на его работу при старте терминала. И еще раз напоминаю - речь идет о получении данных индикатором, а не экспертом. Там проще.

Заранее спасибо.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2012, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  DodgerBlue
//--- input parameters
double    TestBuffer[];

input ENUM_TIMEFRAMES TIMEFRAME_1=PERIOD_H1;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,TestBuffer,INDICATOR_DATA);
   if(PeriodSeconds(_Period)>=PeriodSeconds(TIMEFRAME_1))
     {
      Print("Выберите больший период");
      return INIT_FAILED;
     }
// Проверка заполненности истории из документации (функция BARS())
// не подходит для индикаторов
   int bars=Bars(_Symbol,TIMEFRAME_1);
   if(bars>0)
     {
      Print("Количество баров в истории терминала по символу-периоду на данный момент = ",bars);
     }
   else  //нет доступных баров 
     {
      //--- видимо, данные по символу не синхронизированы с данными на сервере 
      bool synchronized=false;
      //--- счетчик цикла 
      int attempts=0;
      // сделаем 5 попыток дождаться синхронизации 
      while(attempts<5)
        {
         if(SeriesInfoInteger(Symbol(),0,SERIES_SYNCHRONIZED))
           {
            //--- есть синхронизация, выходим 
            synchronized=true;
            break;
           }
         //--- увеличим счетчик 
         attempts++;
         //--- подождем 10 миллисекунд до следующей итерации 
         Sleep(10);                 // А Sleep(10) вообще не работает в индикаторах
        }
      //--- вышли из цикла по факту синхронизации 
      if(synchronized)
        {
         Print("Количество баров в истории терминала по символу-периоду на данный момент = ",bars);
         Print("Самая первая в истории терминала дата по символу-периоду на данный момент = ",
               (datetime)SeriesInfoInteger(Symbol(),0,SERIES_FIRSTDATE));
         Print("Самая первая дата в истории по символу на сервере = ",
               (datetime)SeriesInfoInteger(Symbol(),0,SERIES_SERVER_FIRSTDATE));
        }
      //--- синхронизация данных так и не была достигнута 
      else
        {
         Print("Не удалось получить количество баров на ",_Symbol);
        }
     }

   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//| Average True Range                                               |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   int i,limit;
//--- check for bars count
   if(prev_calculated==0)
      limit=0;
   else
      limit=prev_calculated-1;

// Выполняем проверку на загрузку истории в обработчике, согласно "Документации"
   static bool Check_History=true;
   if(Check_History)
     {
      int bars=Bars(_Symbol,TIMEFRAME_1);
      if(bars<=0)
        {
         Print("История не загружена");
         return 0;  // не запускаем расчет индикатора, пока не загружена история по большему таймфрейму
        }
      Print("Все нормально. История загружена");
      Check_History=false;  // Все нормально, история загружена
     }
   for(i=limit;i<rates_total && !IsStopped();i++)
     {
      TestBuffer[i] = close[i]; // что-нибудь выведем, чтобы индикатор нарисовался
      // Здесь должен быть расчет данных с большего таймфрейма
     }

   return(rates_total);
  }


 

Я тоже сломал себе голову с этой синхронизацией. Проблем намного больше, чем кажется.

Более или менее рабочая версия — запуск таймера в ините, проверка и подгрузка истории в таймере (но только через какое-то время после запуска, иначе гарантированно в клинч войдет!), и перерисовка всех буферов при этой самой подгрузке и при prev_calculated == 0. 

Сам вызов ОнКалкулейт сделать не сложно:

void OnTimer()
{
        //--- Не пытаемся подгрузить историю сразу после запуска, чтобы не подвесить терминал
        static datetime launch_time = 0;
        if ( launch_time <= 0 ) launch_time = TimeLocal();
        if ( TimeLocal() - launch_time < 10 ) return;

        //--- Получаем самую раннюю дату среди всех инструемнтов
        datetime prevStartTime = newStartTime;
        newStartTime = RefreshStartTime();
        if ( newStartTime < prevStartTime )
        {
                Print( "Deeper history have been loaded (", prevStartTime, " -> ", newStartTime, ")..." );
                double p[];
                OnCalculate( LastRatesTotal, 0, 0, p );
        }
}

int OnCalculate (const int rates_total, const int prev_calculated,
                 const int begin, const double& price[] )
{
        LastRatesTotal  = rates_total;
        if ( LastRatesTotal > TerminalInfoInteger( TERMINAL_MAXBARS ) ) LastRatesTotal = TerminalInfoInteger( TERMINAL_MAXBARS );


Еще с недавнего времени rates_total может оказаться больше кол-ва баров на графике. Разработчики на форуме на это не ответили (в СД не писал).


И, несмотря на все эти хитрости, при старте терминала бывают случаи, когда индикаторы не могут рассчитаться (грузят ядро процессора, но ничего не отображают). Проявляется, когда запущено несколько копий, каждая из которых использует историю одного или нескольких совпадающих инструментов.

 

Sergey Savinkin:

Индикатор запускается и пытается получить данные с большего таймфрейма того же символа. Я пока не поднимаю тему получения хэндлов индикаторов. Нужно хотя бы получить историю функциями Bars, iBarsShift и т.д.

Как только rates_total положительный, сразу старшие ТФ доступны для работы через соответствующий пересчет текущих.

 
Andrey Khatimlianskii:

Я тоже сломал себе голову с этой синхронизацией. Проблем намного больше, чем кажется.

Более или менее рабочая версия — запуск таймера в ините, проверка и подгрузка истории в таймере (но только через какое-то время после запуска, иначе гарантированно в клинч войдет!), и перерисовка всех буферов при этой самой подгрузке и при prev_calculated == 0. 

Сам вызов ОнКалкулейт сделать не сложно:


Еще с недавнего времени rates_total может оказаться больше кол-ва баров на графике. Разработчики на форуме на это не ответили (в СД не писал).


И, несмотря на все эти хитрости, при старте терминала бывают случаи, когда индикаторы не могут рассчитаться (грузят ядро процессора, но ничего не отображают). Проявляется, когда запущено несколько копий, каждая из которых использует историю одного или нескольких совпадающих инструментов.

Спасибо, но не понятно, какие параметры передавать в OnCalculate. Вы передаете пустой массив p[]. Чем он заполнится в процедуре? Могу сам проверить, но спросить быстрее. ))) И если вызывается вторая форма OnCalculate с бОльшим количеством параметров, их также можно пустыми массивами передать?

 

попытался добавить макс. комментов в коде, работает как часы, использую для синхронизации нескольких инструментов, если индикатор запущен в выходные дни

пока не видел мультивалютных / мультитаймфреймных индикаторов от MQ, поэтому скорей всего с их стороны это проблемой не считается

int iStart = 1;
double Buffer1[];
double Buffer2[];

void OnInit()
{
  if (MQLInfoInteger(MQL_TESTER) == 0) // tester loads all data properly, do not call timer in testing mode
  {
    EventSetMillisecondTimer(1000);
  }
}

void OnTimer()
{
  if (sync() > 0) // timer will be called only for weekends and holidays
  {
    EventKillTimer();
  }
}

int OnCalculate(const int bars, const int counted, const int start, const double &price[])
{
  if (iStart == 0)  // execute this code once, when indicator is loaded
  {
    iStart = 1;
    ZeroMemory(Buffer1);
    ZeroMemory(Buffer2);
    return bars;
  }

  if (bars != counted)  // if new bar has come, but we're still waiting for syncing, keep indicator updated with previous value
  {
    Buffer1[0] = Buffer1[1];
    Buffer2[0] = Buffer2[1];
  }

  if (sync() > 0)
  {
    // draw indicator's data
  }

  return bars;
}

int sync()
{
  int M1 = CopyRates(Symbol(), PERIOD_M1, ...);
  int D1 = CopyRates(Symbol(), PERIOD_D1, ...);
  int D1External = CopyRates("AMZN", PERIOD_D1, ...);

  if (...) // if M1 < 1 or D1 < 0, stop and wait for the next tick
  {
    return 0; // failed, wait for next tick
  }

  return 1; // synchronized successfully, draw indicator data
}
код выше всегда будет выполняться по одному из сценариев

1. если индикатор подгружен в тестере, то функция OnCalculate всегда подгружает данные корректно, без задержек
2. если индикатор запущен в выходной день, то OnCalculate выполнится один раз, вызовет функцию sync(), которая с первого раза все данные не подгрузит и вернет 0

3. сразу за этим вызовом будет вызов sync() из таймера, и он будет вызываться до тех пор, пока данные не подгрузятся

Relation - Chart Builder Legacy
Relation - Chart Builder Legacy
  • голосов: 9
  • 2016.11.28
  • XXX
  • www.mql5.com
Индикатор для построения произвольных графиков, в основе которых лежит заданная пользователем формула. Графики отрисовываются в барах для таймфрейма выбранного в параметрах индикатора. Графики выбранных инструментов синхронизированы по времени. Пробелы в формуле необязательны, дробные числа задаются только через точку. Поддерживаемые...
 
Sergey Savinkin:

Спасибо, но не понятно, какие параметры передавать в OnCalculate. Вы передаете пустой массив p[]. Чем он заполнится в процедуре? Могу сам проверить, но спросить быстрее. ))) И если вызывается вторая форма OnCalculate с бОльшим количеством параметров, их также можно пустыми массивами передать?

Да, у меня вариант, в котором встроенные массивы не используются.

Если они нужны, то нужно хранить и поддерживать в актуальном состоянии свои копии таких массивов.

 
Andrey Khatimlianskii:

1. рискну предположить, что либо не вызывается ChartRedraw() и не видно изменений

2. либо по одному из синхронизируемых инструментов запрашивается больше баров, чем есть в истории, например, в РобоФХ индекс S&P < 500 баров, а в FXChoice > 1000

есть код, который синхронизирует по наименьшему количеству баров из всех выбранных инструментов

struct SSets
{
  string name;
  MqlTick ticks[];
  MqlRates rates[];
};

int getSyncQuotes(
  SSets &series[], 
  const int position = 0, 
  const int count = 0,
  ENUM_TIMEFRAMES period = PERIOD_CURRENT) 
{
  int indexes[];
  int items = 10000; // can be replaced with TERMINAL_MAXBARS - 1
  int size = ArraySize(series);

  SSets sources[];

  ArrayResize(sources, size);
  ArrayResize(indexes, size);
  ZeroMemory(indexes);

  for (int k = 0; k < size; k++)
  {
    indexes[k] = 0;
    items = MathMin(items, CopyRates(series[k].name, period, position, count, sources[k].rates)); // if EURUSD = 1K bars, and GBPUSD = 2K bars, cut them both to 1K

    if (items < 1)
    {
      Print(
        "Synchronization : " + series[k].name + ", " + 
        "Position : " + IntegerToString(position) + ", " + 
        "Depth : " + IntegerToString(count));

      return 0;
    }
  }

  for (int k = 0; k < size; k++)
  {
    ArraySetAsSeries(series[k].rates, true);
    ArraySetAsSeries(sources[k].rates, true);
    ArrayResize(series[k].rates, items);
    ArrayResize(sources[k].rates, items);
  }

  for (int n = 0; n < items; n++) 
  {
    datetime pointTime = 0;

    for (int k = 0; k < size; k++)
    {
      pointTime = MathMax(pointTime, sources[k].rates[n].time);
    }

    for (int k = 0; k < size; k++)
    {
      MqlRates currentRate = sources[k].rates[indexes[k]];

      if (currentRate.time >= pointTime) 
      {
        indexes[k]++;
      }

      series[k].rates[n] = currentRate;
      series[k].rates[n].time = pointTime;
    }
  }

  return items;
}
 
XXX:

попытался добавить макс. комментов в коде, работает как часы, использую для синхронизации нескольких инструментов, если индикатор запущен в выходные дни

Спасибо за код, попробую у себя.

Если не затруднит, ответьте еще на вопрос: проверяли ли поведение при загрузке терминала с несколькими запущенными индикаторами, которые используют одни и те же чужие тайм-серии для расчетов?

 
XXX:

1. рискну предположить, что либо не вызывается ChartRedraw() и не видно изменений

2. либо по одному из синхронизируемых инструментов запрашивается больше баров, чем есть в истории, например, в РобоФХ индекс S&P < 500 баров, а в FXChoice > 1000

есть код, который синхронизирует по наименьшему количеству баров из всех выбранных инструментов

У меня аналогичный код, только вместо CopyRates я использую iBars (а в более ранних версиях - SeriesInfoInteger( SERIES_FIRSTDATE )).

Не спасало.

 

Проблема еще в том, что ошибка возникает не стабильно.

Я пробовал обнаружить причины ее возникновения, но не смог (удалял кэши тайм-серий, просто ждал произвольное время перед загрузкой терминала, запускал индикаторы на М1 или наоборот - на старших ТФ).

 
Andrey Khatimlianskii:

Проблема еще в том, что ошибка возникает не стабильно.

Я пробовал обнаружить причины ее возникновения, но не смог (удалял кэши тайм-серий, просто ждал произвольное время перед загрузкой терминала, запускал индикаторы на М1 или наоборот - на старших ТФ).

только что запустил приаттаченный Movings.ex5


1. добавил две копии индикатора на чарт AUDUSD - рынок основного чарта открыт

- EURUSD,GBPUSD,EURGBP - рынок открыт

- BA,AMZN,TSLA,NFLX - рынок акций закрыт


2. второй чарт TSLA с одной копией индикатора - рынок основного чарта закрыт

- SPY,UVXY - рынок акций закрыт


при открытиии / закрытиии терминала все подгружется корректно

при накидывании новых копий индикатора с любыми комбинациями на уже открытые чарты - корректно

при попытке сменить символ первого чарта на другой, которого нет в обзоре рынка, через Enter, индикаторы выдали ошибку выхода за границы массива ... где-то не проверяю на ArraySize(IndBuffer)


вобщем, можно допилить больше проверок, но лень :) 

Файлы:
Movings.mq5  7 kb
Helpers.mqh  11 kb
Matrices.mqh  14 kb