[СЕРВИСДЕСК] Ошибка получения времени старшего ТФ в таймере! - страница 7

 
Ihor Herasko:

Да, именно. В OnInit() просто делаете обращение к нужным ТФ без проверки результата (на него там нельзя полагаться), а уже в OnCalculate вызывать функцию IsTFDataReady(). Как только для всех запрашиваемых ТФ возвращено true, можно приступать к выполнению алгоритма индикатора.

Ок, с этим разобрались. Но нужно явно дописывать документацию, иначе быстрый таймер вызовет много вопросов у разработчиков.

 
Ihor Herasko:

Вообще, что за задача решается, для которой настолько принципиально наличие подключения терминала? В моем понимании индикатор - инструмент визуализации данных. Тех данных, которые имеются. Придут новые данные, он обновит визуализацию. От него вроде не должна требоваться проверка актуальности данных. Это задача терминала.

Задача получить данные старших ТФ максимально быстро. Про IsConnected() напомнил Vitaly Gorbunov

 
Alexey Kozitsyn:

Ну е-мае... Мы же уже прошли этот этап разбора. Смотрите ваш же лог:

Последовательность. Сначала проверяем связь. Как только связь установлена, получаем время. Объясните мне, пожалуйста, какого лешего сначала возвращается ошибка 4066, а потом она не возвращается!? Что изменилось за 20 мс с последнего вызова?

Ошибка 4066 говорит о том, что данных нет, отправлен запрос на обновление.

После отправления запроса другой запрос уже не отправляется, поэтому ошибка 4066 не взводится. Это уже многократно обсуждалось.

Зачем Вы запускаете таймер в индикаторе? Такой маленький. Вы должны понимать, что в MT4 индикаторы работают в интерфейсном потоке. Через интерфейсный же поток идут все виндовые месседжи

 
Slava:

Ошибка 4066 говорит о том, что данных нет, отправлен запрос на обновление.

После отправления запроса другой запрос уже не отправляется, поэтому ошибка 4066 не взводится. Это уже многократно обсуждалось.

Зачем Вы запускаете таймер в индикаторе? Такой маленький. Вы должны понимать, что в MT4 индикаторы работают в интерфейсном потоке. Через интерфейсный же поток идут все виндовые месседжи

Рад, что Вы присоединились к обсуждению.

Я уже не первый день на форуме + здесь высказалось несколько человек, которые также не первый день на форуме. Никто не сказал про то, что:

После отправления запроса другой запрос уже не отправляется, поэтому ошибка 4066 не взводится. Это уже многократно обсуждалось.

Спасибо, будем знать. Очень бы хотелось увидеть это в справке. Получается, что ошибка "взводится" наверняка только по приходу события OnTick()/OnCalculate()?

Зачем Вы запускаете таймер в индикаторе? Такой маленький.

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

Вы должны понимать, что в MT4 индикаторы работают в интерфейсном потоке. Через интерфейсный же поток идут все виндовые месседжи

Хорошо, идут, а дальше что? Можете подробнее объяснить чем это плохо/хорошо/как это влияет на работу с таймером?
 

Многократно обсуждалось. 12 страниц на запрос "ошибка 4066"

И Вам правильно посоветовали в OnInit отправить запрос, а в OnCalculate анализировать.

Миллисекундный таймер зачем? Вы своими действиями мешаете клиентскому терминалу нормально подняться. Это не виндовые сообщения мешают вашему таймеру, а ваш таймер мешает всем. Ещё раз: ИНДИКАТОРЫ В КЛИЕНТСКОМ ТЕРМИНАЛЕ MT4 РАБОТАЮТ В ИНТЕРФЕЙСНОМ ПОТОКЕ.

 
Slava:

Многократно обсуждалось. 12 страниц на запрос "ошибка 4066"

Ознакомился, спасибо. Только вот с работой в OnCalculate() или OnTick() нет проблем, проблемы именно в OnTimer(). А на запрос "ошибка 4066 таймер" выпадают результаты только этой ветки :(

И Вам правильно посоветовали в OnInit отправить запрос, а в OnCalculate анализировать.

Я прислушался к советам, но это не отменяет особенностей работы с таймером, о которых в документации не написано.

Миллисекундный таймер зачем? Вы своими действиями мешаете клиентскому терминалу нормально подняться. Это не виндовые сообщения мешают вашему таймеру, а ваш таймер мешает всем. Ещё раз: ИНДИКАТОРЫ В КЛИЕНТСКОМ ТЕРМИНАЛЕ MT4 РАБОТАЮТ В ИНТЕРФЕЙСНОМ ПОТОКЕ.

Еще раз, миллисекундный таймер для того, чтобы получать информацию с нескольких символов как можно быстрее! Т.е. алгоритм следующий: индикатор загружается, как можно скорее получает данные старших ТФ, затем в миллисекундном таймере мониторит биды нужных символов. Можно как-то иначе решить вопрос мониторинга, нежели чем через таймер?

Давайте подытожим все, что тут было написано, поправьте, если я ошибаюсь:

1. Задача: получать котировки нескольких символов через таймер:

Реализация: При работе с высокочастотным таймером, при загрузке терминала нужно дождаться установления связи IsConnected() с сервером в функции OnCalculate(), только после этого можно обращаться к таймеру;

2. Задача: получить как можно скорее данные старших ТФ после старта индикатора (в индикаторе используется быстрый таймер);

Реализация: сначала делаем запрос нужных данных в OnInit(), затем ждем установления связи IsConnected() в OnCalculate(), затем уже получаем данные старших ТФ также в OnCalculate();

3. Запуск высокочастотного таймера тормозит интерфейсный поток, как следствие и компьютер и запускать его не стоит совсем? Как тогда решать задачу #1?

4. Задача: подгружать актуальные данные старших ТФ.

Реализация: не использовать для этого высокочастотный таймер, т.к. функции получения данных не предназначены для работы в таком таймере? Используем только OnCalculate()? 

5. В случае получения ошибки 4066 и ее последующего сброса, в OnCalculate() она взводится на каждом тике?

6. OnTimer() в МТ5 работает не в интерфейсном потоке?

@Slava, пожалуйста ответьте также по пунктам;

 

1. Как правило состояние IsConnected у Вас будет на втором вызове OnCalculate. Первый вызов сразу после старта терминала, второй вызов на приходе порции исторических данных

2. Не надо использовать быстрый таймер. Сначала оцените, какой тайминг является для вас приемлемым. Это может оказаться и 100 миллисекунд, и 500. Мы ведь не случайно изначально вводили секундный таймер, SetMillisecondsTimer был введён в пятёрке(!) только через 3 или 4 года. Но в пятёрке другая архитектура.

3.1. Заведите себе мощный компьютер, который сможет обеспечить моментальное разруливание очередей сообщений.

3.2. Не запускайте миллисекундный таймер сразу, а только хотя бы после первого OnCalculate. Вернее так: в первом OnCalculate запустите секундный таймер (а вдруг нет связи или день выходной), чтобы можно было анализировать окружение. А уже потом, когда вы убедились, что все данные подгружены, связь есть и всё в порядке, убейте секундный таймер и запускайте миллисекундный тааймер.  Тогда вы спокойно проскочите узкую входную дверь. В лучшем случае (а таких будет 99 процентов) вы потеряете от 2 до 5 секунд на старте

4. Можно таймер. Но не сразу (см 3.2). И я думаю, 50 миллисекунд хватит за глаза. Вы ведь не HFT обеспечиваете?

5. 4066 появляется только при первом запросе данных по чужому символу-периоду. На следующем запросе этого же символа-периода 4066 больше не получите

6. В MT5 индикаторы считаются в отдельном потоке обработки символа. Так что, если у вас больше одного графика на этом символе (либо есть другие индикаторы на этом символе), то вы можете их затормозить. Но всё-таки это не интерфейсный поток

 
Slava:

1. Как правило состояние IsConnected у Вас будет на втором вызове OnCalculate. Первый вызов сразу после старта терминала, второй вызов на приходе порции исторических данных

2. Не надо использовать быстрый таймер. Сначала оцените, какой тайминг является для вас приемлемым. Это может оказаться и 100 миллисекунд, и 500. Мы ведь не случайно изначально вводили секундный таймер, SetMillisecondsTimer был введён в пятёрке(!) только через 3 или 4 года. Но в пятёрке другая архитектура.

3.1. Заведите себе мощный компьютер, который сможет обеспечить моментальное разруливание очередей сообщений.

3.2. Не запускайте миллисекундный таймер сразу, а только хотя бы после первого OnCalculate. Вернее так: в первом OnCalculate запустите секундный таймер (а вдруг нет связи или день выходной), чтобы можно было анализировать окружение. А уже потом, когда вы убедились, что все данные подгружены, связь есть и всё в порядке, убейте секундный таймер и запускайте миллисекундный тааймер.  Тогда вы спокойно проскочите узкую входную дверь. В лучшем случае (а таких будет 99 процентов) вы потеряете от 2 до 5 секунд на старте

4. Можно таймер. Но не сразу (см 3.2). И я думаю, 50 миллисекунд хватит за глаза. Вы ведь не HFT обеспечиваете?

5. 4066 появляется только при первом запросе данных по чужому символу-периоду. На следующем запросе этого же символа-периода 4066 больше не получите

6. В MT5 индикаторы считаются в отдельном потоке обработки символа. Так что, если у вас больше одного графика на этом символе (либо есть другие индикаторы на этом символе), то вы можете их затормозить. Но всё-таки это не интерфейсный поток

1. Именно так и получается;

2. Вот тут и проблема. Чем быстрее, тем лучше. И оценка была произведена. Индикатор написан для арбитража (точнее исследования на тему арбитража), т.е. важна каждая миллисекунда и чем быстрее будет получена котировка - тем лучше;

3.1. И сейчас система достаточно мощная: процессор 8600к, терминалы на SSD, 16гб оперативы DDR4;

3.2. Уоу... хорошо, принял к сведению;

4. Скорее всего задача арбитража относится к HFT;

5. Изначально как раз это и напрягало. Если бы я и дальше продолжал получать ошибку и знал, что данные еще не готовы, то этой ветки бы не было;

6. Понятно. 

Спасибо за развернутый ответ.

 
Igor Makanu:

если не затруднит, вот по теме топика - корректная подгрузка истории со старшего ТФ, вот индикатор: "нужно нарисовать МАшку" со старшего ТФ на барах младшего ТФ, сделал в течении 5 минут, работать будет на 98% точно корректно, где вот в этом коде 2% "подводные камни" которые и вызовут баги ?

Да, как раз по теме топика. И здесь ведь уже все разобрано.

Во-первых, перед любым обращением к таймсериям других ТФ/символов обязательно проверить, что данные доступны (см. функцию IsTFDataReady() выше). В приведенном коде Вы ориентируетесь только на результат, полученный от CopyClose. Но она ничего не знает о подгрузке истории. Поэтому сначала - убедиться в доступности данных и только потом запрашивать их.

Во-вторых, вызов одной функции в качестве аргумента другой функции далеко не всегда оправдан. А в приведенном случае он в принципе недопустим. Ведь результат вызова iBars также нужно проверить. Таким образом, сначала вызывается iBars, результат кэшируется и проверяется, а потом только полученное значение передается в CopyClose().

В-третьих, после вызова CopyClose нет проверки получения всех запрошенных данных. Ведь вернуть фукнкция может 1 или 2 бара, а запрашивалось, к примеру, 10. Такой результат я бы относил к ошибке.

В-четвертых, в самой идее подхода - ошибка. В цикле предполагается оперирование барами другого ТФ, но при этом смешиваются расчеты с переменной rates_total, значение которой относится к текущему ТФ. Тут возможно два подхода, которые я использую в том или ином случае:

  1. Вести цикл по барам текущего ТФ, а перед запросом данных преобразовывать индекс бара текущего ТФ к индексу бара другого ТФ (этот подход использован в приведенном ниже коде).
  2. Вести цикл по барам другого ТФ. Но тогда нужно включать дополнительный цикл для случая, когда текущий ТФ младший по отношению к другому. Ведь одному бару старшего ТФ будет соответствовать несколько баров текущего ТФ. 

интересует корректный код под МТ4

В свете перечисленных четырех пунктов примерно так (не проверял, делал на коленке, но смысл должен быть понятен):

   if (!IsTFDataReady(TimeFrame))
      return 0;

   int i,limit;

   static int nOldBars = 0;
   int nBars = iBars(_Symbol, TimeFrame);
   if (nBars == 0)
      return 0;
      
   if (nOldBars == 0 || nBars - nOldBars > 1)
   {
      if(nBars < MAPeriod)
      {
         Comment("Большой период МА!!!, в истории доступно ", nBars," баров");
         return 0;
      }
      
      limit = nBars - fmin(MAPeriod, nBars);
   }
   else
      limit = nBars - nOldBars;  // здесь всегда будет 0 или 1
   
   nOldBars = nBars;
   datetime dtTime = iTime(NULL, TimeFrame, limit);
   if (dtTime == 0)
      return 0;

   limit = iBarShift(NULL, PERIOD_CURRENT, dtTime);

// основной цикл расчета индикатора
   for(i = limit; i >= 0 && !IsStopped(); i--)
   {
      int nOtherTFBarIndex = iBarShift(NULL, TimeFrame, time[i]);
      if (nOtherTFBarIndex < 0 || nOtherTFBarIndex >= nBars)
         continue;
      
      BufMA[i] = iMA(_Symbol,TimeFrame,MAPeriod,0,MODE_SMA,PRICE_CLOSE,nOtherTFBarIndex);
   }
//---
   return rates_total;

Кстати, проверил:


 
Ihor Herasko:

Да, как раз по теме топика. И здесь ведь уже все разобрано.

Во-первых, перед любым обращением к таймсериям других ТФ/символов обязательно проверить, что данные доступны (см. функцию IsTFDataReady() выше). В приведенном коде Вы ориентируетесь только на результат, полученный от CopyClose. Но она ничего не знает о подгрузке истории. Поэтому сначала - убедиться в доступности данных и только потом запрашивать их.

Во-вторых, вызов одной функции в качестве аргумента другой функции далеко не всегда оправдан. А в приведенном случае он в принципе недопустим. Ведь результат вызова iBars также нужно проверить. Таким образом, сначала вызывается iBars, результат кэшируется и проверяется, а потом только полученное значение передается в CopyClose().

В-третьих, после вызова CopyClose нет проверки получения всех запрошенных данных. Ведь вернуть фукнкция может 1 или 2 бара, а запрашивалось, к примеру, 10. Такой результат я бы относил к ошибке.

В-четвертых, в самой идее подхода - ошибка. В цикле предполагается оперирование барами другого ТФ, но при этом смешиваются расчеты с переменной rates_total, значение которой относится к текущему ТФ. Тут возможно два подхода, которые я использую в том или ином случае:

  1. Вести цикл по барам текущего ТФ, а перед запросом данных преобразовывать индекс бара текущего ТФ к индексу бара другого ТФ (этот подход использован в приведенном ниже коде).
  2. Вести цикл по барам другого ТФ. Но тогда нужно включать дополнительный цикл для случая, когда текущий ТФ младший по отношению к другому. Ведь одному бару старшего ТФ будет соответствовать несколько баров текущего ТФ. 

В свете перечисленных четырех пунктов примерно так (не проверял, делал на коленке, но смысл должен быть понятен):

Кстати, проверил:


1. взял на вооружение! спасибо!

2. золотые слова! я раньше так и писал, но со временем читая чужие коды, которые гонятся за компактностью... я уделяю больше внимания на "краткость сестра таланта".... и трачу время на поиск возникших багов. 

4. "краткость сестра таланта"... Вы правы!

3. интересен момент анализа, что возвращает CopyClose(), у себя проверял, если нет .hst файла по запрашиваемому ТФ, то CopyClose() больше 2048 никогда не возвращает - т.е. это максимальное значение которое можно скачать?