English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Обработчик события "новый бар"

Обработчик события "новый бар"

MetaTrader 5Примеры | 4 октября 2010, 12:46
28 223 38
Konstantin Gruzdev
Konstantin Gruzdev

Введение

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

  • о способах обнаружения нового бара;
  • о недостатках уже существующих алгоритмов определения нового бара;
  • о создании универсального метода обнаружения нового бара;
  • о тонкостях и способах применения этого метода;
  • о событии NewBar и обработчике этого события OnNewBar().

Способы обнаружения нового бара

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

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

Например, в пользовательских индикаторах можно использовать для этой цели пару входных параметров функции OnCalculate(): rates_total и prev_calculated. Ограниченность этого метода в основном заключается в том, что его можно использовать только для определения нового бара текущего графика и только в индикаторах. Если вы хотите обнаружить новый бар на другом периоде или символе, то необходимо использовать дополнительные приемы.

Или, например, можно попытаться поймать новый бар на его первом тике, когда тиковый объем Tick Volume =1, или когда все цены бара равны Open=High=Low=Close. Эти способы вполне могут использоваться при тестировании, но в реальной торговле они зачастую дают сбои. Это связано с тем, что мгновения между первым и вторым тиком бывает недостаточно, чтобы поймать образовавшийся бар. Особенно это становится заметно при сильном движении рынка или при плохом качестве канала связи.

Есть способ обнаружения нового бара на основе функции TimeCurrent(). Кстати, хороший способ, если вам нужно определить появление нового бара на текущем графике. Мы его используем в конце статьи.

Ну, можно еще спросить у соседа: "Слушай, а не появился новый бар?". Интересно, что он ответит? Ну да ладно, давайте остановим свой выбор на принципе отслеживания времени открытия текущего незавершенного бара для обнаружения нового. По степени простоты и надежности он внушает доверие и уже зарекомендовал себя. 

Отправная точка

В выше перечисленных материалах с отслеживанием появления нового бара дела обстоят неплохо. Но...  

Чтобы понять в чем состоит "но", выберем в качестве отправной точки или прототипа простую и хорошую функцию проверки появления нового бара из статьи "Ограничения и проверки в экспертах". Вот она:

//+------------------------------------------------------------------+
//| Возвращает true, если появился новый бар для пары символ/период  |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- в статической переменной будем помнить время открытия последнего бара
   static datetime last_time=0;
//--- текущее время
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- если это первый вызов функции
   if(last_time==0)
     {
      //--- установим время и выйдем 
      last_time=lastbar_time;
      return(false);
     }

//--- если время отличается
   if(last_time!=lastbar_time)
     {
      //--- запомним время и вернем true
      last_time=lastbar_time;
      return(true);
     }
//--- дошли до этого места - значит бар не новый, вернем false
   return(false);
  }

Эта наша функция-прототип в действительности работоспособная и имеет полное право на жизнь. Но... 

Анализ функции-прототипа

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

Заголовок функции. Давайте рассмотрим все по порядку. Начнем с заголовка функции:

bool isNewBar()

Заголовок функции мне нравится, очень простой, интуитивно понятный, и не надо разбираться с передаваемыми параметрами. Было бы хорошо сохранить его в будущем в таком виде.

Ограничение по количеству вызовов. Следом за заголовком идет первая инструкция, инициализирующая статическую переменную:

//--- в статической переменной будем помнить время открытия последнего бара
   static datetime last_time=0;

Вроде всё красиво. Но...

Проблема в том, что мы и спользуем статическую переменную. Справка гласит:

Статические переменные существуют с момента выполнения программы и инициализируются однократно перед вызовом специализированной функции OnInit(). Если начальные значения не указаны, то переменные статического класса памяти принимают нулевые начальные значения. 

Локальные переменные, объявленные с ключевым словом static, сохраняют свои значения в течение всего времени существования  функции. При каждом следующем вызове функции такие локальные переменные содержат те значения, которые они имели при предыдущем вызове.

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

Вопрос универсальности. Следующая инструкция в функции-прототип выглядит так:

//--- текущее время
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

Логично, что для получения времени открытия последнего незавершенного бара используется функция SeriesInfoInteger() с модификатором SERIES_LASTBAR_DATE.

Наша функция-прототип isNewBar() изначально задумывалась простой и по умолчанию использует торговый инструмент и период текущего графика. Это допустимо, если вам нужно отслеживать появление нового бара только на текущем графике. Но что делать, если я использую период и инструмент не только текущего графика? Кроме того, а что если у меня вообще какой-нибудь хитрый график? Например я решил построить график Ренко или Каги?

Отсутствие универсальности может сильно ограничить нас. Позже рассмотрим, как легким движением руки исправить это.  

Обработка ошибок. Давайте еще раз взглянем на функцию SeriesInfoInteger(). Как вы думаете, что она вернет, если её заставили выполняться, когда график еще не сформировался? Такая ситуация может возникнуть, например, если вы присоединили к графику свой эксперт или индикатор и решили сменить период или символ графика, а также при перезапуске терминала. А что будет во время обновления таймсерии? Кстати, в справке есть такое предупреждение:

Доступность данных

Наличие данных в формате HCC или даже в готовом для использования формате HC не всегда означает безусловную доступность этих данных для отображения на графике или для использования в mql5-программах.

При доступе к ценовым данным или к значениям индикаторов из mql5-программ следует помнить, что не гарантируется их доступность в определенный момент времени, либо с определенного момента времени. Это связано с тем, что в целях экономии ресурсов в MetaTrader 5 не хранится полная копия требуемых данных для mql5-программы, а дается прямой доступ к базе данных терминала.

Ценовая история по всем таймфреймам строится из общих данных формата HCC, и любое обновление данных с сервера приводит к обновлению данных по всем таймфреймам и пересчету индикаторов. Вследствие этого, в доступе к данным может быть отказано даже в том случае, если эти данные были доступны мгновение назад.

Так что вернет функция? Чтобы уйти от этой неопределенности, необходимо неким образом начать отлавливать ошибки запросов времени открытия последнего незавершенного бара.  

Возможность инициализации. Двигаемся дальше. Рассмотрим следующие инструкции нашей функции-прототипа:

//--- если это первый вызов функции
   if(last_time==0)
     {
      //--- установим время и выйдем 
      last_time=lastbar_time;
      return(false);
     }

Уж тут-то точно все хорошо. Есть, правда, один нюанс. Обратили внимание на вышеприведенную фразу из справки: "Статические переменные существуют с момента выполнения программы и инициализируются однократно перед вызовом специализированной функции OnInit()"? А как быть, если нам необходимо еще раз инициализировать переменную last_time. Точнее, что делать, если необходимо искусственно создать ситуацию первого вызова? Или какую другую ситуацию? Легко задавать вопросы, когда знаешь ответы. Но об этом позже.

Количество баров. Дальше в нашей функции-прототипе идет следующий код:

//--- если время отличается
   if(last_time!=lastbar_time)
     {
      //--- запомним время и вернем true
      last_time=lastbar_time;
      return(true);
     }

Понимаете, такой программист как я, может сделать так, что этот оператор if будет удивлять клиентский терминал и тестер стратегий. Дело в том, что, по логике вещей, прошлое время всегда меньше настоящего. То есть last_time < lastbar_time. У меня из-за случайной программной ошибки получилась машина времени, точнее, получилось наоборот lastbar_time < last_time. Как тут не удивишься? В общем-то, такой временной парадокс легко обнаружить и выдать сообщение об ошибке.

Нет худа без добра. Наблюдая за работой своей машины времени, обнаружил, что оказывается между вызовами isNewBar() может образоваться не один новый бар. Чем меньше период графика, тем выше вероятность появления нескольких баров между вызовами функции. Причин может быть множество: начиная от "долго считает", кончая временным отсутствием связи с сервером. Возможность получать не только сигнал, что появился новый бар, но и количество баров, наверняка будет полезной.

Заканчивается наша функция-прототип так:

//--- дошли до этого места - значит бар не новый, вернем false
   return(false);

Да, коль дошли до этого места - значит бар не новый.

Создание  новой функции isNewBar() 

Начинается интересное. Будем устранять обнаруженные минусы. Сразу скажу, что поскромничал, назвав раздел "Создание новой функции isNewBar()". Будем делать нечто более солидное.

Начнем с того, как нам избавиться от ограничения по количеству вызовов функции.

С ходу приходит на ум, что можно использовать подходящие одноименные функции isNewBar() из статьи "Принципы экономного пересчета индикаторов" или отсюда isNewBar, то есть в тело функции включить массивы для хранения нескольких значений last_time, поставить счетчики вызовов функции isNewBar() из разных мест и так далее. Безусловно, все это рабочие варианты и они могут быть воплощены в жизнь. Но представим, что мы пишем мультивалютный советник для работы на 12 валютных парах. Это же сколько всяких нюансов надо учесть и не запутаться?

Что делать? Ответ здесь!

Красота объектно-ориентированного программирования заключается в том, что  объект или экземпляр некоего класса может жить собственной жизнью независимо от других экземпляров этого же класса. Поэтому давайте начнем создавать класс СisNewBar, чтобы у нас была возможность "плодить" экземпляры этого класса в любом месте нашего советника или индикатора любое количество раз. И пусть каждый экземпляр "живет своей жизнью".

Вот что для начала мы имеем:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Время открытия последнего бара
      
   public:
      void              CisNewBar();      // Конструктор CisNewBar      
      //--- Методы определения нового бара:
      bool              isNewBar();       // Первый тип запроса на появление нового бара.
  };  

bool CisNewBar::isNewBar()
  {
   //--- здесь было определение статической переменной

   //--- здесь будет остальной код метода   
   ...

   //--- дошли до этого места - значит бар не новый или ошибка, вернем false
   return(false);
  }

То, что было функцией isNewBar(), теперь является методом. Обратите внимание, что у нас нет теперь статической переменной last_time - вместо неё появилась защищенная переменная класса m_lastbar_time. Если бы мы оставили статическую переменную в методе isNewBar(), то все наши труды пошли бы насмарку, так как остались бы все те же проблемы, что и раньше с функцией isNewBar() - таковы особенности статических переменных.

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

Уже что-то нам удалось. Теперь займемся вопросом универсальности. Прежде чем что-то добавлять в наш новый класс, хотел вас подвести к одной забавной идее.

Давайте рассуждать. Что мы хотим? Хотим получить сигнал о появлении нового бара. Как мы хотим это сделать? Так, если время открытия текущего незавершенного бара на последнем тике (или в последний момент времени) больше времени открытия текущего незавершенного бара на предыдущем тике (или в предыдущий момент времени), то образовался новый бар. Не понял что написал, но зато правильно написал. Суть в том, что нам нужно сравнивать время. Поэтому решил, что если в качестве параметра передавать в метод isNewBar() время открытия текущего незавершенного бара newbar_time - будет логично. Тогда заголовок метода будет таким:

bool isNewBar(datetime newbar_time)

Пока не спрашивайте, откуда мы возьмем newbar_time, предположим, что оно известно. Позже разберемся.  

Кстати, передавая время в метод isNewBar(), мы получим достаточно гибкий инструмент для отслеживания появления нового бара. Мы сможем охватить все стандартные периоды графиков со всевозможными торговыми инструментами. Получилось так, что мы не зависим сейчас от названия инструмента и размера периода.  

Также мы можем охватить нестандартные графики. Например, если вы строите тиковые свечи, или графики Ренко или Каги, то у них время открытия бара практически никогда не совпадает со стандартными временными периодами графиков. В этом случае наша функция будет незаменимой.

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

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Время открытия последнего бара

      uint              m_retcode;        // Код результата определения нового бара 
      int               m_new_bars;       // Количество новых баров
      string            m_comment;        // Комментарий выполнения
      
   public:
      void              CisNewBar();      // Конструктор CisNewBar      
      //--- Методы определения нового бара:
      bool              isNewBar(datetime new_Time); // Первый тип запроса на появление нового бара.
  };
   
//+------------------------------------------------------------------+
//| Первый тип запроса на появление нового бара.                     |
//| INPUT:  newbar_time - время открытия предположительно нового бара|
//| OUTPUT: true   - если появился новый бар(ы)                      |
//|         false  - если не появился новый бар или получили ошибку  |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CisNewBar::isNewBar(datetime newbar_time)
  {
   //--- Инициализация защищенных переменных
   m_new_bars = 0;      // Количество новых баров
   m_retcode  = 0;      // Код результата определения нового бара: 0 - ошибки нет
   m_comment  =__FUNCTION__+" Проверка появления нового бара завершилась успешно";
   //---
   
   //--- На всякий случай проверим: не оказалось ли время предположительно нового бара newbar_time меньше старого бара m_lastbar_time? 
   if(m_lastbar_time>newbar_time)
     { // Если новый бар старее старого бара, то выдаем сообщение об ошибке
      m_comment=__FUNCTION__+" Ошибка синхронизации: время предыдущего бара "+TimeToString(m_lastbar_time)+
                                                  ", время запроса нового бара "+TimeToString(newbar_time);
      m_retcode=-1;     // Код результата определения нового бара: возвращаем -1 - ошибка синхронизации
      return(false);
     }
   //---
        
   //--- если это первый вызов 
   if(m_lastbar_time==0)
     {  
      m_lastbar_time=newbar_time; //--- установим время последнего бара и выйдем
      m_comment   =__FUNCTION__+" Инициализация lastbar_time="+TimeToString(m_lastbar_time);
      return(false);
     }   
   //---

   //--- Проверяем появление нового бара: 
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // Количество новых баров
      m_lastbar_time=newbar_time; // запоминаем время последнего бара
      return(true);
     }
   //---
   
   //--- дошли до этого места - значит бар не новый или ошибка, вернем false
   return(false);
  }

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

Все бы хорошо, но наш универсальный метод isNewBar(datetime newbar_time) содержит в себе одно существенное неудобство. Это неудобство заключается в том, что нам в коде своего эксперта или индикатора всегда нужно заботиться о вычислении времени предполагаемого нового бара newbar_time.  

К счастью, в некоторых случаях мы можем упростить себе жизнь, поручив эту функцию новому дополнительному методу нашего класса. Это можно сделать для стандартных периодов и торговых инструментов как в нашей функции-прототипе, используя второй вариант знакомой функции SeriesInfoInteger с модификатором SERIES_LASTBAR_DATE, а во всех остальных случаях - пользоваться универсальным методом. Итак, вот что у меня получилось:

//+------------------------------------------------------------------+
//| Второй тип запроса на появление нового бара.                     |
//| INPUT:  no.                                                      |
//| OUTPUT: m_new_bars - количество новых баров                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CisNewBar::isNewBar()
  {
   datetime newbar_time;
   datetime lastbar_time=m_lastbar_time;
      
   //--- Запрашиваем время открытия последнего бара:
   ResetLastError(); // Устанавливает значение предопределенной переменной _LastError в ноль.
   if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time))
     { // Если запрос был неудачным, то выдаем сообщение об ошибке:
      m_retcode=GetLastError();  // Код результата определения нового бара: записываем значение переменной _LastError
      m_comment=__FUNCTION__+" Ошибка при получении времени открытия последнего бара: "+IntegerToString(m_retcode);
      return(0);
     }
   //---
   
   //---Далее используем первый тип запроса на появление нового бара для завершения анализа:
   if(!isNewBar(newbar_time)) return(0);
   
   //---Уточним количество новых баров:
   m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1;
   
   //--- дошли до этого места - значит появился новый бар(ы), вернем их количество:
   return(m_new_bars);
  }

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

Осталось ли у нас еще что-то? Да. Остался последний момент — инициализация. Для этого мы воспользуемся конструктором класса и несколькими Set-методами. Наш конструктор класса выглядит так:  

//+------------------------------------------------------------------+
//| Конструктор CisNewBar.                                           |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CisNewBar::CisNewBar()
  {
   m_retcode=0;         // Код результата определения нового бара 
   m_lastbar_time=0;    // Время открытия последнего бара
   m_new_bars=0;        // Количество новых баров
   m_comment="";        // Комментарий выполнения
   m_symbol=Symbol();   // Имя инструмента, по умолчанию символ текущего графика
   m_period=Period();   // Период графика, по умолчанию период текущего графика    
  }

А Set-методы так:

//--- Методы инициализации защищенных данных:
void              SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time;                            }
void              SetSymbol(string symbol)             {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol;  }
void              SetPeriod(ENUM_TIMEFRAMES period)    {m_period=(period==PERIOD_CURRENT)?Period():period;      }

Благодаря конструктору класса, нам не нужно уделять внимание инициализации торгового инструмента и периода для текущего графика. Как и в функции-прототипе, они будут использоваться по умолчанию. Но если нам нужно использовать другой торговый инструмент или период графика, то можем для этого применить созданные нами Set-методы. Кроме этого, используя SetLastBarTime(datetime lastbar_time), при необходимости можно повторно создавать ситуацию "первый вызов".

В заключение сделаем несколько Get-методов с целью получения данных из нашего класса в экспертах и индикаторах: 

      //--- Методы доступа к защищенным данным:
uint              GetRetCode()     const  {return(m_retcode);     }  // Код результата определения нового бара 
datetime          GetLastBarTime() const  {return(m_lastbar_time);}  // Время открытия последнего бара
int               GetNewBars()     const  {return(m_new_bars);    }  // Количество новых баров
string            GetComment()     const  {return(m_comment);     }  // Комментарий выполнения
string            GetSymbol()      const  {return(m_symbol);      }  // Имя инструмента
ENUM_TIMEFRAMES   GetPeriod()      const  {return(m_period);      }  // Период графика

Теперь мы можем в своих mql5-программах получать всю необходимую информацию. На этом в создании класса СisNewBar можно поставить точку.

Полный код нашего класса в прилагаемом файле Lib СisNewBar.mqh.

Примеры использования класса CisNewBar

Предлагаю рассмотреть примеры использования нашего класса с целью вникнуть во все тонкости того, что мы создали. Быть может, есть не только плюсы, но и минусы.

Пример 1. Для начала создадим абсолютно идентичный советник, что и для функции isNewBar() из статьи "Ограничения и проверки в экспертах":

//+------------------------------------------------------------------+
//|                                               Example1NewBar.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               Lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar current_chart; // экземпляр класса CisNewBar: текущий график

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(current_chart.isNewBar()>0)
     {     
      PrintFormat("Новый бар: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Запустим оба советника на графиках с одинаковой парой и одинаковым периодом. Посмотрим, что у нас получилось:

 

 

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

Далее, я сменил период графика на двухминутный. Советники по разному отреагировали на это. Советник CheckLastBar стал сообщать о появлении нового бара раз в 2 минуты, а Example1NewBar показывает появление новых баров каждую минуту, как будто смены периода не произошло (отмечено цифрой 2).

Дело в том, что инициализация нашего экземпляра класса current_chart произошла при присоединении советника к графику конструктором класса. При смене периода, при уже присоединенном к графику эксперте, конструктор класса не запускается, и наш советник продолжает работать с минутным периодом. Это говорит о том, что созданный нами экземпляр класса живет собственной жизнью и на него даже не влияют изменения внешней среды. Это может быть как плюсом так и минусом - все зависит о решаемых задач.  

Для того, чтобы наш эксперт всё же действовал также как и советник CheckLastBar, необходимо в функции OnInit() произвести инициализацию защищенных переменных класса m_symbol и m_period. Давайте сделаем это.

Пример 2. Введем некоторые дополнения в наш советник и снова сравним его работу с CheckLastBar. Код советника приложен в файле Example2NewBar.mq5. Запустим советники на графиках с одинаковой парой и одинаковым периодом. Создадим для них те же помехи, что и в прошлый раз. Посмотрим, что у нас получилось:

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

Далее, я сменил  период графиков на 2-х минутный. В отличии от примера 1, советники работают синхронно (отмечено цифрой 2). Инициализация в функции OnInit() защищенных переменных класса m_symbol и m_period помогла. При смене рабочего инструмента (отмечено цифрой 3), эксперты тоже работают одинаково. 

Пример 3. В наш класс CisNewBar мы заложили возможность отслеживания появления ошибок. Бывает, что советник построен так, что необходимость отслеживать ошибки отсутствует. Что ж, тогда  просто не используйте такую возможность. Мы же попробуем искусственно создать такую ситуацию, когда ошибка возможна, и попробуем поймать её. Для этого немного дополним код эксперта (файл Example3NewBar.mq5).

Что я буду делать? Как обычно запущу советник  Example3NewBar на минутных графиках. Потом начну менять, например, инструменты графика в надежде, что возникнет ситуация, что терминал не успеет построить таймсерию до запроса советника. В общем, от души поиздеваюсь над клиентским терминалом и посмотрю, что получится... Ну, это нормальный способ исследований для России.  

После нескольких попыток наш советник поймал ошибку:

 

Теперь можно с уверенностью сказать, что мы умеем ловить ошибки выполнения. Как их обрабатывать - дело вкуса.  Заметьте, что ошибку мы отследили четырежды. Когда загрузка и формирование графика завершились, советник подсказал, что мы пропустили всего 1 бар.

Кстати, те, кто посмотрел код советника, могли обратить внимание, что ошибки есть смысл проверять, только когда метод isNewBar() возвращает значение меньше или равное нулю.

Предупреждение: если во время этого эксперимента вы начнете менять период графика, то при смене графика с маленького периода на большой получите ошибку синхронизации, т.к. время открытия бара, например, у периода Н1 более раннее, чем, к примеру, у M1 в 59 случаях. Чтобы избежать появления этой ошибки при переключении периода графика, нужно правильно инициализировать переменную m_lastbar_time в функции OnInit() методом SetLastBarTime(datetime lastbar_time).

Пример 4. В этом примере усложним задачу эксперта. Возьмем три валютные пары: EURUSD на M1, GBPUSD на M1 и USDJPY на M2. График с первой парой будет текущим, и на нем мы будем просто отслеживать появление нового бара. По второй паре мы будем считать количество образовавшихся баров после старта советника. Счет будем вести когда первая пара просигнализирует, что появился новый бар. А по третей будем постоянно (при появлении бара на EURUSD) проводить инициализацию защищенной переменной класса m_lastbar_time. Код советника приложен в файле Example4NewBar.mq5.

Создав этот пример, хочу выяснить, как наш класс функций CisNewBar будет работать в мультивалютном режиме. Что ж, запускаю... Вот что получилось:

 

Результаты вызывают вопросы. Подолью масла в огонь и прогоню этот же участок в тестере. Результаты тестера:

 

Дальше можно поиграть в игру "найдите десять отличий". Кроме странностей работы эксперта на демо-счете, очевидно, что на демо-счете и в тестере есть отличия - и они видны невооруженным взглядом. Подобное сравнение, при правильном подходе, позволит не только выявить минусы в эксперте, но и научиться устранять их. Пожалуй, не буду сейчас проводить анализ, почему так получилось, как получилось и что нужно исправить в эксперте. Оставлю это для страстей на форуме или для следующей статьи, если в этом будет потребность.  

Пример 5. Пока мы еще ни разу в примерах явно не использовали самый универсальный метод определения нового бара isNewBar(datetime newbar_time). Для этого я возьму индикатор тиковых свечей из статьи "Создание тиковых индикаторов" и добавлю в него буфер для хранения времени открытия бара (файл TickColorCandles v2.00.mq5). Напишу коротенький советник, который будет сообщать о времени появления новой тиковой свечи (файл Example5NewBar.mq5):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar newbar_ind; // экземпляр класса CisNewBar: определение новой тиковой свечи
int HandleIndicator;  // хэндл индикатора
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Получаем хэндл индикатора:
   HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); 
   if(HandleIndicator==INVALID_HANDLE)
     {
      Alert(" Ошибка при создании хэндла индикатора, номер ошибки: ",GetLastError());
      Print(" Инициализация советника завершена некорректно. Торговля запрещена.");
      return(1);
     }

//--- Присоединяем индикатор к графику:  
   if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator))
     {
      Alert(" Ошибка присоединения индикатора к графику, номер ошибки: ",GetLastError());
      return(1);
     }
//--- Если дошли до сюда, то инициализация прошла успешно     
   Print(" Инициализация советника завершена успешно. Торговля разрешена.");
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double iTime[1];

//--- Получаем время открытия последней незавершенной тиковой свечи:
   if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0)
     {
      Print(" Неудачная попытка получить значение времени индикатора. "+
            "\nСледующая попытка получить значения индикатора будет предпринята на следующем тике.",GetLastError());
      return;
     }
//--- Определяем появление новой тиковой свечи:
   if(newbar_ind.isNewBar((datetime)iTime[0]))
     {
      PrintFormat("Новый бар. Время открытия: %s  Время последнего тика: %s",
                  TimeToString((datetime)iTime[0],TIME_SECONDS),
                  TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Наверняка вы обратили внимание, каким образом мы получаем время открытия тиковой свечи. Очень просто, правда? Помещаю индикатор и советник в свои папки, компилирую и запускаю эксперт. Заработало, вот результаты:   

 

Обработчик события "новый бар"


Подходя к концу статьи, хотелось бы поделиться еще одной идеей. На форуме как-то проскользнула мысль о том, что было бы хорошо иметь стандартный обработчик события "новый бар". Быть может когда-то разработчики придут к этому, а может и нет. Но прелесть MQL5 в том, что в нем можно реализовать самые умопомрачительные задумки элегантно и просто.

Коль есть желание иметь обработчик события "новый бар" (или NewBar) - так давайте его создадим! Тем более, что сейчас мы с легкостью, используя наш класс, можем это событие ловить. Вот так будет выглядеть наш эксперт, в который мы вставили обработчик OnNewBar() события NewBar:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include "OnNewBar.mqh" // здесь находится секрет запуска обработчика события "новый бар"

//+------------------------------------------------------------------+
//| Функция-обработчик события "новый бар"                           |
//+------------------------------------------------------------------+
void OnNewBar()
  {
   PrintFormat("Новый бар: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
  }

Получилось красиво. Внешне наш эксперт выглядит очень просто. Все, что наш обработчик делает, так это печатает строчку о появлении нового бара. Чтобы понять, как отслеживаем событие NewBar и как запускается обработчик, нужно заглянуть в файл OnNewBar.mqh:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar@mail.ru"

#include <Lib CisNewBar.mqh>
CisNewBar current_chart; // экземпляр класса CisNewBar: текущий график

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int period_seconds=PeriodSeconds(_Period);                     // Количество секунд в периоде текущего графика
   datetime new_time=TimeCurrent()/period_seconds*period_seconds; // Время открытия бара на текущем графике
   if(current_chart.isNewBar(new_time)) OnNewBar();               // При появлении нового бара запускаем обработчик события NewBar
  }

Как видите, здесь тоже нет ничего сложного. Но есть пара моментов, на которые хотел обратить ваше внимание:

Первое. Как вы заметили, я использую функцию TimeCurrent() для вычисления времени открытия бара и применяю первый метод проверки появления события NewBar из нашего класса. В этом есть хороший плюс. Он заключается в том, что такой прием не требует обработки ошибок, как при использовании функции SeriesInfoInteger с модификатором SERIES_LASTBAR_DATE. Для нас это важно, так как наш обработчик OnNewBar() должен быть как можно более надежным.

Второе. Использование функции TimeCurrent() для вычисления времени открытия бара является наиболее быстрым способом. Применение функции SeriesInfoInteger, даже без контроля появления ошибок, для этих же целей — более медленный способ.

Результат работы нашего обработчика :

 

Заключение

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

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

Созданный нами обработчик события "новый бар" подходит только для одновалютных советников. Но мы научились использовать для этой цели наиболее надежный и быстрый способ. Теперь можно пойти дальше и сделать мультивалютный обработчик событий NewBar. Но это тема целой статьи.

Прикрепленные файлы |
example1newbar.mq5 (0.94 KB)
example3newbar.mq5 (2.99 KB)
example4newbar.mq5 (6.61 KB)
example5newbar.mq5 (2.46 KB)
lib_cisnewbar.mqh (6.99 KB)
onnewbar.mq5 (0.8 KB)
onnewbar.mqh (1.12 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (38)
Fast235
Fast235 | 12 нояб. 2021 в 16:58
выбросил это вообще, все равно алгоритм смотрит на прошлый бар, и в этом случае есть даже преимущество, если отмена сделки, никуда она не денется в пределах ограничения спреда и "отклонения"
MrBrooklin
MrBrooklin | 12 нояб. 2021 в 16:59
Mihail Marchukajtes #:

Кстати да я то же использую этот код и вполне работает, только он у меня чуть шире, можно использовать для любого ТФ!

Спасибо, Михаил! Добавлю в свою библиотеку функций.

С уважением, Владимир.

Алексей Тарабанов
Алексей Тарабанов | 12 нояб. 2021 в 23:54
Armen Shahinyan #:

Проще данного метода у меня нет.
Отслеживает время открытия текущего бара и при каждом тике сравнивает их.


Первую функцию выкиньте из кода. И жизнь наладится. 

Алексей Тарабанов
Алексей Тарабанов | 13 нояб. 2021 в 00:44

Вообще, интересны 2 обстоятельства: 

1. Всех зачем-то забанили. 

2. Новый бар всегда отличается по времени от предыдущего. По любому времени, потому, как оно всегда слева направо идёт. Берите любое. Если не совпадёт с таким - же предыдущим, то бар - новый. 

Line00
Line00 | 13 мая 2022 в 13:41

В отношении последней главы статьи.

При компиляции файла  onnewbar.mqh (1.12 KB) и соответственно  onnewbar.mq5 (0.8 KB) получаю ошибку.

Объясните, пожалуйста, почему не работает.

В статье, в строке

#include "OnNewBar.mqh" // здесь находится секрет запуска обработчика события "новый бар"

говорится о секрете запуска обработчика. Что за секрет? А то получается что, ссылаемся на файл в котором не определена функция onNewBar().

Технический анализ: Как мы анализируем? Технический анализ: Как мы анализируем?
Данная статья в краткой форме отражает отношение автора к таким явлениям, как перерисовывающие индикаторы, индикаторы Multi-timeframe и представление котировок при помощи японских свечей. Техника программирования в статье не затрагивается, статья носит общий характер.
Повышаем качество кода при помощи Unit Test Повышаем качество кода при помощи Unit Test
Даже в простых программах зачастую находятся ошибки, которые кажутся невероятными. "Как я такое написал?" - первое, что приходит в голову, когда мы обнаруживаем такую ошибку. Второй вопрос - "Как этого избежать?" - приходит гораздо реже. Нельзя написать 100%-ный безошибочный код, особенно в больших проектах, но можно использовать технологии для их своевременного обнаружения. Статья рассказывает о том, как можно повысить качество MQL4 кода, применяя распространенную методику модульного тестирования (Unit Testing).
Простой пример построения индикатора с использованием нечеткой логики (Fuzzy Logic) Простой пример построения индикатора с использованием нечеткой логики (Fuzzy Logic)
Статья посвящена вопросам практического применения концепции нечеткой логики (fuzzy logic) для анализа финансовых рынков. Предложен пример индикатора, выдающего сигналы на основе двух нечетких правил, основанных на индикаторе Envelopes. Разработанный индикатор использует несколько индикаторных буферов: 7 буферов для расчетов, 5 буферов для вывода графиков и 2 буфера цвета.
Построение кода индикаторов с несколькими индикаторными буферами для начинающих Построение кода индикаторов с несколькими индикаторными буферами для начинающих
Процесс превращения простого в сложное зачастую сопровождается нагромождением количественных изменений до таких размеров, что непосвященному начинает казаться, что невозможно подобное хоть как-то объять разумом. Совсем другое дело, когда все фундаментальные элементы этого сложного уже до боли знакомы и процесс созидания превращается в незатейливую демонстрацию того, как на деле все необыкновенно просто. В данной статье автор, основываясь на материалах двух своих предыдущих статей, знакомит читателей с тем, как написать код такого индикатора, как Aroon.