English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Реализация мультивалютного режима в MetaTrader 5

Реализация мультивалютного режима в MetaTrader 5

MetaTrader 5Примеры | 10 января 2011, 12:08
12 835 46
Konstantin Gruzdev
Konstantin Gruzdev

Введение

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

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


Обзор традиционного подхода

Традиционным подходом, в нашем случае, можно считать попытку реализовать мультивалютную систему на основе стандартных функций OnTick() и OnCalculate(), которые пришли на смену функции start() из MQL4. Проще говоря, при появлении нового тика или бара на текущем графике последовательно опрашиваются все валютные пары (участвующие в мультивалютной системе) с целью последующего анализа и принятия соответствующих решений.

С чем обычно сталкивается разработчик при таком подходе?

  1. Зависимость всей системы от поступления тиков по торговому инструменту текущего графика.

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

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

  2. Сложность синхронизации исторических данных по всем используемым в мультивалютной системе инструментам.

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

    Такой подход в построении графиков сохранен и в MetaTrader 5. То есть, одинаковое количество баров на графике по каждому инструменту не означает, что они синхронизированы по времени. Например, сотый бар может иметь разное время открытия для всех инструментов. Поэтому, при построении и пересчете мультивалютного индикатора необходимо тщательно следить, чтобы все бары по каждому инструменту были согласованы друг с другом.

    Это также не очень сказывается или совсем не сказывается, если система настроена на работу на больших таймфреймах, поскольку при увеличении периода вероятность пропуска бара уменьшается. Хотя, как говорится, чем черт ни шутит. А черт не шутит, только если человек ничего не делает.

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

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

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

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

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


Новые надежды с функцией OnTimer()

Появление возможности в MQL-программе генерировать событие "Timer" и стандартного обработчика событий OnTimer() дало надежду на появление мультивалютных систем нового типа.  Это обусловлено, с одной стороны, тем, что теперь мультивалютный эксперт/индикатор  может быть независим от поступления тиков по инструменту текущего графика и, с другой стороны, можно контролировать работу эксперта по времени. Но...

Это частично решает проблему, обозначенную в пункте 1 предыдущего раздела и, конечно, дает некоторые преимущества. Но так же, как при поступлении события "NewTick", при поступлении события "Timer" приходится последовательно опрашивать все валютные пары в попытке отследить изменения. Иногда это приходится делать достаточно часто, что может существенно увеличивать ресурсоемкость MQL5-программ.

Вопросы, отмеченные в пунктах 2 и 3, остаются на прежнем уровне решения. В дополнение к этому приходится решать вопросы специфические для функции OnTimer(). Например, вопросы инициализации/деинициализации таймера, работы в нерабочие дни и т.д.

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


Новые возможности с функцией OnChartEvent() 

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

Спасением для создателя мультивалютника может стать стандартный обработчик пользовательских событий OnChartEvent(). Он полностью развязывает руки программисту и позволяет генерировать собственные события на его усмотрение.

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

Под забавным словом "шпион" я подразумеваю вот такой индикатор:

//+------------------------------------------------------------------+
//|                                                         iSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#define VERSION         "1.00 Build 2 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "Индикатор iSpy - индикатор-шпион."
#property description "Запускается на нужном торговом инструменте для получения тиков"
#property indicator_chart_window

input long            chart_id=0;        // идентификатор графика-получателя события
input ushort          custom_event_id=0; // идентификатор события

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,      // размер массива price[]
                 const int prev_calculated,// обработано баров на предыдущем вызове
                 const int begin,          // откуда начинаются значимые данные
                 const double& price[]      // массив для расчета
   )
  {
   double price_current=price[rates_total-1];

   //--- Инициализация:
   if(prev_calculated==0) 
     { // Генерируем и отправляем событие "Инициализация"
      EventChartCustom(chart_id,0,(long)_Period,price_current,_Symbol);
      return(rates_total);
     }
   
   // При появлении нового бара генерируем пользовательское событие "новый тик", 
   // которое можно будет поймать в эксперте или индикаторе
   EventChartCustom(chart_id,custom_event_id+1,(long)_Period,price_current,_Symbol);
   
   //--- return value of prev_calculated for next call
   return(rates_total);
  }

Мы можем запустить этого "шпиона" в тыл врага (график с нужным символом) и дальше отлавливать его шифровки в советнике или индикаторе функцией OnChartEvent(). Чтобы правильно декодировать шифровки "шпиона" мы должны следующим образом интерпретировать входящие переменные этой функции:

  • id - идентификатор события. Если id-CHARTEVENT_CUSTOM = 0 , то наш шпион "сообщает", что переменная prev_calculated обнулилась и стоит предпринять соответствующие действия;
  • lparam  - в данном случае означает период графика, на котором запущен "шпион";
  • dparam - цена тика. По умолчанию это последняя цена закрытия. Хотя при запуске "шпиона" можно задать любое значение из перечисления ENUM_APPLIED_PRICE;
  • sparam - имя торгового инструмента, по которому пришло событие.

Для демонстрации работы нескольких "шпионов" одновременно, сделаем простой советник  exSpy.mq5 (полная версия в архиве):

//+------------------------------------------------------------------+
//|                                                        exSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/ru/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "Эксперт демонстрирует работу индикатора-шпиона iSPY"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"iSpy",ChartID(),0)==INVALID_HANDLE) 
      { Print("Ошибка установки шпиона на GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"iSpy",ChartID(),1)==INVALID_HANDLE) 
      { Print("Ошибка установки шпиона на EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"iSpy",ChartID(),2)==INVALID_HANDLE) 
      { Print("Ошибка установки шпиона на USDJPY"); return(true);}
      
   Print("Шпионы засланы, ждем шифровок...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| Стандартная функция обработки событий. Она может использоваться  |
//| по своему прямому назначению, как описано в документации.        |
//|                                                                  |
//| В данном примере демонстрирует декодирвание сообщений индикатора-|
//| шпиона iSPY.                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,  // идентификатор события:
                              // если id-CHARTEVENT_CUSTOM=0 - событие "инициализация"
                  const long&   lparam, // период графика
                  const double& dparam, // цена
                  const string& sparam  // инструмент 
                 )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",
           id-CHARTEVENT_CUSTOM,":  ",sparam," ",
           EnumToString((ENUM_TIMEFRAMES)lparam)," price=",dparam);
     }
  }

Запускаем советник на любом графике. Вот результаты работы:

 

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

Подведем промежуточный итог. Итак:

  • Мы не зависим от тиков  по конкретному инструменту, как это было при использовании стандартных функций OnTick() и OnCalculate().
  • Мы не завязаны на строго заданных промежутках времени, как при использовании функции OnTimer().
  • Нет необходимости опрашивать все символы последовательно или в цикле с целью отследить изменения по ним. Как только произошли изменения по символу, мы получаем соответствующее событие. Символ, по которому произошли события, мы можем узнать по идентификатору события id или по параметру sparam.
  • Решен вопрос с отслеживанием синхронизации  исторических данных с сервером по каждому символу в отдельности.
  • Часть работы, хоть и не большую, отдаем на откуп дополнительным программам. Это имеет отношение к распараллеливанию потоков обработки данных.
  • В параметрах функции OnChartEvent() получаем дополнительную информацию: период, цену, имя символа. Это может существенно упростить код эксперта или индикатора, так как отпадает необходимость дополнительно запрашивать эти данные.

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


Реализация мультивалютного режима

Если использовать вышеизложенную идею реализации мультивалютного режима в чистом виде, то в какой-то момент вы начнете сталкиваться с трудностями. Дело в том, что такой подход позволяет получать ВСЕ тики по КАЖДОМУ торговому инструменту, на котором запущен "шпион".

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

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

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

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

Давайте сделаем его универсальным, чтобы больше никогда не возвращаться к вопросу о том, как получить то или иное событие по символу в мультивалютном эксперте или индикаторе. Для этой цели возьмем за образец перечисление событий ENUM_CHART_EVENT_SYMBOL из описания Панель управления мультивалютным режимом "Control panel MCM" :

enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_INIT      =0,          // Событие "Инициализация"
   CHARTEVENT_NO        =0,          // События отключены

   CHARTEVENT_NEWBAR_M1 =0x00000001, // Событие "новый бар" на 1 -минутном графике
   CHARTEVENT_NEWBAR_M2 =0x00000002, // Событие "новый бар" на 2 -минутном графике
   CHARTEVENT_NEWBAR_M3 =0x00000004, // Событие "новый бар" на 3 -минутном графике
   CHARTEVENT_NEWBAR_M4 =0x00000008, // Событие "новый бар" на 4 -минутном графике
   
   CHARTEVENT_NEWBAR_M5 =0x00000010, // Событие "новый бар" на 5 -минутном графике
   CHARTEVENT_NEWBAR_M6 =0x00000020, // Событие "новый бар" на 6 -минутном графике
   CHARTEVENT_NEWBAR_M10=0x00000040, // Событие "новый бар" на 10-минутном графике
   CHARTEVENT_NEWBAR_M12=0x00000080, // Событие "новый бар" на 12-минутном графике
   
   CHARTEVENT_NEWBAR_M15=0x00000100, // Событие "новый бар" на 15-минутном графике
   CHARTEVENT_NEWBAR_M20=0x00000200, // Событие "новый бар" на 20-минутном графике
   CHARTEVENT_NEWBAR_M30=0x00000400, // Событие "новый бар" на 30-минутном графике
   CHARTEVENT_NEWBAR_H1 =0x00000800, // Событие "новый бар" на 1 -часовом графике
   
   CHARTEVENT_NEWBAR_H2 =0x00001000, // Событие "новый бар" на 2 -часовом графике
   CHARTEVENT_NEWBAR_H3 =0x00002000, // Событие "новый бар" на 3 -часовом графике
   CHARTEVENT_NEWBAR_H4 =0x00004000, // Событие "новый бар" на 4 -часовом графике
   CHARTEVENT_NEWBAR_H6 =0x00008000, // Событие "новый бар" на 6 -часовом графике
   
   CHARTEVENT_NEWBAR_H8 =0x00010000, // Событие "новый бар" на 8 -часовом графике
   CHARTEVENT_NEWBAR_H12=0x00020000, // Событие "новый бар" на 12-часовом графике
   CHARTEVENT_NEWBAR_D1 =0x00040000, // Событие "новый бар" на дневном графике
   CHARTEVENT_NEWBAR_W1 =0x00080000, // Событие "новый бар" на недельном графике
     
   CHARTEVENT_NEWBAR_MN1=0x00100000, // Событие "новый бар" на месячном графике   
   CHARTEVENT_TICK      =0x00200000, // Событие "новый тик"
   
   CHARTEVENT_ALL       =0xFFFFFFFF, // Все события включены
  };

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

Комбинировать флаги можно при помощи побитовой операции "ИЛИ". Например, комбинация CHARTEVENT_NEWBAR_M1 | CHARTEVENT_NEWBAR_H1 будет означать, что мы собираемся посылать при помощи "шпиона" события "новый бар" с минутного и часового таймфрейма. Данные флаги будут являться входным параметром для нашего индикатора-шпиона. Теперь будем его называть солиднее: "индикатор - агент".

Сам индикатор, в соответствии с новыми задумками, теперь выглядит так:

//+------------------------------------------------------------------+
//|                                        Spy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/ru/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 3 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "Это индикатор-агент Control panel MCM."
#property description "Запускается на нужном инструменте на любом таймфрейме"
#property description "и генерирует пользовательское событие NewBar"
#property description "и/или NewTick для графика-получателя события"

#property indicator_chart_window
  
input long                    chart_id;                 // идентификатор графика-получателя события
input ushort                  custom_event_id;          // идентификатор события  
input ENUM_CHART_EVENT_SYMBOL flag_event=CHARTEVENT_NO;// флаг, определяющий тип события.

MqlDateTime time, prev_time;

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,       // размер массива price[]
                 const int prev_calculated, // обработано баров на предыдущем вызове
                 const int begin,           // откуда начинаются значимые данные
                 const double& price[]      // массив для расчета
   )
  {  
   double price_current=price[rates_total-1];

   TimeCurrent(time);
   
   if(prev_calculated==0)
     {
      EventCustom(CHARTEVENT_INIT,price_current);
      prev_time=time; 
      return(rates_total);
     }
   
//--- new tick
   if((flag_event & CHARTEVENT_TICK)!=0) EventCustom(CHARTEVENT_TICK,price_current);       

//--- check change time
   if(time.min==prev_time.min && 
      time.hour==prev_time.hour && 
      time.day==prev_time.day &&
      time.mon==prev_time.mon) return(rates_total);

//--- new minute
   if((flag_event & CHARTEVENT_NEWBAR_M1)!=0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current);     
   if(time.min%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_M2)!=0)  EventCustom(CHARTEVENT_NEWBAR_M2,price_current);
   if(time.min%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_M3)!=0)  EventCustom(CHARTEVENT_NEWBAR_M3,price_current); 
   if(time.min%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_M4)!=0)  EventCustom(CHARTEVENT_NEWBAR_M4,price_current);      
   if(time.min%5 ==0 && (flag_event & CHARTEVENT_NEWBAR_M5)!=0)  EventCustom(CHARTEVENT_NEWBAR_M5,price_current);     
   if(time.min%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_M6)!=0)  EventCustom(CHARTEVENT_NEWBAR_M6,price_current);     
   if(time.min%10==0 && (flag_event & CHARTEVENT_NEWBAR_M10)!=0) EventCustom(CHARTEVENT_NEWBAR_M10,price_current);      
   if(time.min%12==0 && (flag_event & CHARTEVENT_NEWBAR_M12)!=0) EventCustom(CHARTEVENT_NEWBAR_M12,price_current);      
   if(time.min%15==0 && (flag_event & CHARTEVENT_NEWBAR_M15)!=0) EventCustom(CHARTEVENT_NEWBAR_M15,price_current);      
   if(time.min%20==0 && (flag_event & CHARTEVENT_NEWBAR_M20)!=0) EventCustom(CHARTEVENT_NEWBAR_M20,price_current);      
   if(time.min%30==0 && (flag_event & CHARTEVENT_NEWBAR_M30)!=0) EventCustom(CHARTEVENT_NEWBAR_M30,price_current);      
   if(time.min!=0) {prev_time=time; return(rates_total);}
//--- new hour
   if((flag_event & CHARTEVENT_NEWBAR_H1)!=0) EventCustom(CHARTEVENT_NEWBAR_H1,price_current);
   if(time.hour%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_H2)!=0)  EventCustom(CHARTEVENT_NEWBAR_H2,price_current);
   if(time.hour%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_H3)!=0)  EventCustom(CHARTEVENT_NEWBAR_H3,price_current);      
   if(time.hour%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_H4)!=0)  EventCustom(CHARTEVENT_NEWBAR_H4,price_current);      
   if(time.hour%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_H6)!=0)  EventCustom(CHARTEVENT_NEWBAR_H6,price_current);      
   if(time.hour%8 ==0 && (flag_event & CHARTEVENT_NEWBAR_H8)!=0)  EventCustom(CHARTEVENT_NEWBAR_H8,price_current);      
   if(time.hour%12==0 && (flag_event & CHARTEVENT_NEWBAR_H12)!=0) EventCustom(CHARTEVENT_NEWBAR_H12,price_current);      
   if(time.hour!=0) {prev_time=time; return(rates_total);}
//--- new day
   if((flag_event & CHARTEVENT_NEWBAR_D1)!=0) EventCustom(CHARTEVENT_NEWBAR_D1,price_current);      
//--- new week
   if(time.day_of_week==1 && (flag_event & CHARTEVENT_NEWBAR_W1)!=0) EventCustom(CHARTEVENT_NEWBAR_W1,price_current);      
//--- new month
   if(time.day==1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current);      
   prev_time=time;
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

void EventCustom(ENUM_CHART_EVENT_SYMBOL event,double price)
  {
   EventChartCustom(chart_id,custom_event_id,(long)event,price,_Symbol);
   return;
  } 

Так как данный индикатор является составной частью "Control panel MCM", я не стал его переименовывать и в приложении выложил просто обновленную версию (см. "Spy Control panel MCM.mq5”). Но это не означает, что его нельзя использовать отдельно от панели.

Этот индикатор-агент генерирует пользовательские события  и транслирует их на график-получатель с целью дальнейшей обработки этих событий в эксперте или индикаторе с помощью функции  OnChartEvent(). Теперь входные параметры этой функции необходимо интерпретировать следующим образом:

  • id - идентификатор события;
  • lparam  -  флаг события поступившего от агента панели. Флаги соответствуют перечислению ENUM_CHART_EVENT_SYMBOL;
  • dparam -  цена тика или цена открытия нового бара на определенном таймфрейме;
  • sparam -  имя торгового инструмента, по которому пришло событие.

 Демонстрационный эксперт выглядит не сложнее предыдущего (полная версия в архиве):

//+------------------------------------------------------------------+
//|                                      exSpy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/ru/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (28 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "Эксперт демонстрирует работу индикатора-агента Spy Control panel MCM"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),0,
             CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_M5)==INVALID_HANDLE) 
      { Print("Ошибка установки шпиона на GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),1,
             CHARTEVENT_NEWBAR_M2)==INVALID_HANDLE) 
      { Print("Ошибка установки шпиона на EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"Spy Control panel MCM",ChartID(),2,
             CHARTEVENT_NEWBAR_M6)==INVALID_HANDLE) 
      { Print("Ошибка установки шпиона на USDJPY"); return(true);}
      
   Print("Агенты засланы, ждем сообщений...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| Стандартная функция обработки событий. Она может использоваться  |
//| по своему прямому назначению, как описано в документации.        |
//|                                                                  |
//| В данном примере демонстрируется декодирование сообщений         |
//| индикатора-агента iSPY Control panel MCM и реализацию            |
//| мультивалютного режима                                           |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,          // идентификатор события
                  const long&   lparam, // флаг события поступившего от агента панели.
                                       // Флаги соответствуют перечислению ENUM_CHART_EVENT_SYMBOL.
                  const double& dparam, // цена
                  const string& sparam  // инструмент 
                 )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",id-CHARTEVENT_CUSTOM,
           ":  ",sparam," ",EnumToString((ENUM_CHART_EVENT_SYMBOL)lparam)," price=",dparam);
     }
  }

Результат работы exSpy Control panel MCM:

 

Как видите, мы исправно получаем все запрошенные нами события.

Давайте еще раз подведем небольшой промежуточный итог:

  • Благодаря новой версии индикатора-агента мы сохранили все предыдущие достижения.
  • Теперь из своих мультивалютных MQL-программ можем задавать, как минимум, 23 события, которые включают в себя события "новый тик", "новый бар" и "инициализация".
  • Еще больше работы из эксперта/индикатора отдаем нашим агентам, разгружая их. 

Что ж, как оказалось, в MetaTrader 5 реализовать полноценный мультивалютный режим не так сложно.

В дополнение к этому хочу отметить пару нюансов.

Первый. Все события, которые генерируют "агенты" для нашего мультивалютного советника/индикатора являются внешними. В связи с этим возникает вопрос: "А обязательно ли запускать агентов непосредственно из эксперта/индикатора?". Ответ: "нет".

Второй. В функции OnChartEvent() идентификатор события id, как бы, является лишним, т.к. понять, по какому символу пришло событие, мы можем по параметру sparam - имя торгового инструмента. Поэтому, возможно, мы еще как-то можем его использовать? Ответ: "можем". 

Подобные рассуждения привели к тому, что появилась Панель управления мультивалютным режимом "Control panel MCM". Она является, своего рода, прослойкой между терминалом и экспертом/индикатором. Это дало еще больше преимуществ и гибкости в настройке мультивалютной среды:

  • Панель можно установить как самостоятельную единицу на график, а затем на него "бросать" мультивалютные эксперты и индикаторы, совместимые с панелью.
  • Панель можно включать как составную единицу индикаторов или экспертов. Она загрузится вместе с ними.  
  • Через меню "События" можно подключать/отключать символы из окна "Обзор рынка" для торговли или анализа. В функции OnChartEvent() по идентификатору события id теперь можно узнать порядковый номер символа в окне "Обзор рынка".
  • Можно установить режим торговли по тикам или по событию "новый бар" по любому периоду и выбранному в "Обзоре рынка" символу. Все это делается при помощи обычного меню.
  • Менять все вышеперечисленные установки "на лету" не выгружая, не останавливая или не заходя в окно свойств советника или индикатора.
  • Это все нисколько не ограничивает творческий потенциал при создании мультивалютных индикаторов и экспертов. Более того, теперь не придется "вшивать" в свой код то, что дает эта панель. Управление индикаторами-агентами теперь заложено в самой панели управления.
  • Структура мультивалютников получается проще.


Мультивалютный индикатор RSI для индекса доллара USDx

Чтобы прочувствовать все достоинства вышеизложенного метода, предлагаю реализовать мультивалютный вариант индикатора RSI для индекса доллара USDx с использованием "Control panel MCM".

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

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

Кто читал, вникая в суть статьи, мог заметить, что толком не был решен вопрос синхронизации друг с другом исторических данных по всем используемым в мультивалютной системе инструментам  (смотрите пункт 2 из раздела "Обзор традиционного подхода").

Этот вопрос решен в индикаторе при помощи класса функций для построения синхронизированных буферов RSI (файл CSynchronizedBufferRSI.mqh).  Нет смысла приводить весь код класса, выделю только особые моменты. 

Первое, индикаторный буфер определен внутри класса со спецификатором доступа public:

public:
   double   buffer[];           // индикаторный буфер

Второе, инициализация индикатора происходит с помощью метода класса:

//--- Методы инициализации:
bool Init(int n,string symbol,int rsi_count,int rsi_period);

И, третье, значение индикаторного буфера на каждом баре синхронизируется с текущим таймфреймом с помощью refresh-метода класса:

//+------------------------------------------------------------------+
//| Метод получения/обновления данных индикатора для одного бара     |
//| индикаторного буфера.                                            |
//| INPUT:  bar   - номер бара                                       |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CSynchronizedBufferRSI::Refresh(int bar=0)
  {
   buffer[bar]=EMPTY_VALUE; // Инициализация бара индикаторного буфера.
     
   //--- Запрашиваем время бара на текущем графике:
   datetime time[1];      
   if(CopyTime(_Symbol,_Period,bar,1,time)!=1) return; // При ошибке ждем следующего тика/бара...

   //--- Запрашиваем значение индикатора для символа по времени совпадающим с баром текущего графика:
   double value[1];
   if(CopyBuffer(m_handle,0,time[0],time[0],value)!=1) return; // При ошибке ждем следующего тика/бара...

   buffer[bar]=value[0];
   return;
  }

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

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

На графике индикатора очень хорошо видно, зачем это стоит делать:

 

Для больших таймфреймов обычно такое не наблюдается.

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

//+------------------------------------------------------------------+
//| Стандартная функция обработки событий. Она может использоваться  |
//| по своему прямому назначению, как описано в документации.        |
//|                                                                  |
//| Вместе с Control panel MCM является удобным средством для        |
//| мультивалютной торговли.                                         |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // идентификатор события или позиция символа в "Обзор рынка"+CHARTEVENT_CUSTOM  
                const long& lparam,   // флаг события
                const double& dparam, // цена
                const string& sparam  // инструмент 
                )
  {
   int custom_id=id-CHARTEVENT_CUSTOM-1;
   
   if(custom_id>=0)      
     {
      if(lparam!=CHARTEVENT_NEWBAR_NO)
        { 
         //--- Пересчет последнего незавершенного бара:
         if(EventToPeriod(lparam)==_Period && sparam==_Symbol)
           { // Пересчет индикатора при появлении нового бара на текущем графике
            iRSIUSDx_Ind[0]=EMPTY_VALUE;
            //--- Обновление значения RSI по всем валютным парам для нового бара
            for(int i=0;i<symbol_total;i++) buffer[i].Refresh();
            iRSIUSDx(symbol_total);    // Расчет текущего незавершенного бара RSI для индекса
            return;
           }
         
         buffer[custom_id].Refresh();  // Значение RSI для custom_id валютной пары для текущего бара
         iRSIUSDx(symbol_total);       // Расчет текущего незавершенного бара RSI для индекса
         return;
        }
      else 
        { 
         //--- Пересчет индикатора при событии "Инициализация" 
         buffer[custom_id].RefreshBuffer();     // Обновление всего буфера RSI для custom_id валютной пары
         Init_iRSIUSDx(symbol_total,calculate); // Обновление всего буфера RSI для индекса
         return;
        }
     }
  }

Особенности кода:

  • Использование идентификатора события id для обращения к массиву с указателями на экземпляры класса, предназначенные для расчета буферов индикатора RSI, синхронизированных с текущим таймфреймом. Такой подход существенно упрощает структуру кода.
  • Используется событие "Инициализация" для перерасчета индикаторного буфера RSI только для той валютной пары, по которой оно пришло, а не для всех шести символов. Как говорилось раньше, это позволяет синхронизировать индикатор, например, при обновлении истории по одному символу.
  • Используется событие "новый бар" для синхронизации всех индикаторных буферов RSI для нового бара на текущем графике.
  • Используются события "новый тик" от всех валютных пар для обновления значения индикатора на последнем незавершенном баре. Причем пересчет бара происходит только по той паре, по которой пришел "новый тик". 

Изучив полный код индикатора RSI для индекса доллара USDx, станет более понятно, как это все работает.

Особенности установки:

  • Загрузите на свой компьютер Панель управления мультивалютным режимом "Control panel MCM" и скомпилируйте файлы "iControl panel MCM.mq5" и "Spy Control panel MCM.mq5".
  • В окне "Обзор рынка" выставьте символы в следующем порядке:
    1. EURUSD
    2. USDJPY
    3. GBPUSD
    4. USDCAD
    5. USDSEK
    6. USDCHF
    Это условие вызвано лишь тем, что я не вставил в индикатор соответствующую проверку и эта последовательность нужна для правильного расчета индикатора.
  • Распакуйте архив iRSIUSDx.zip в каталог /MQL5. Из папки /MQL5/Indicators/iRSIUSDx/ запустите файл iRSIUSDx.ex5, например, на графике EURUSD с периодом М1. 
  • Последовательно по всем шести символам в меню "Событие" панели "Control panel MCM" установите событие "Новый тик", как описано здесь. Должны получить картинку, которая подобна картинке выше.
  • Дополнительно по символу EURUSD установите событие "новый бар" на минутном графике. В индикаторе это событие используется для синхронизации при появлении нового бара на текущем таймфрейме, который у нас равен М1.
  • Если вам интересно, то для наглядности установите индекс доллара, как описано здесь.

Заключение

Рассмотренные вопросы реализации полноценного мультивалютного режима в MetaTrader 5 в полной мере показали преимущества как самой платформы, так и языка программирования MQL5 для решения поставленной задачи. То, что раньше вызывало больше всего затруднений, теперь решается доступными средствами.

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

Прикрепленные файлы |
irsiusdx.zip (74.13 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (46)
Denis Zyatkevich
Denis Zyatkevich | 25 нояб. 2011 в 20:53
Interesting:

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

Спасибо, разобрался. Если объявлять структуры MqlTradeRequest и MqlTradeResult на глобальном уровне, то работает!
Andrei Etylkut
Andrei Etylkut | 10 февр. 2016 в 13:38
Большое спасибо за статью!
ooparadise
ooparadise | 20 июл. 2016 в 17:29

Пытаюсь получить цены по трем парам EURUSD, EURGBP, GBPUSD. Все работает хорошо при выборе "Все тики", либо "Только цены открытия" в тестере стратегий. Но если я выбираю "Каждый тик на основе реальных тиков", то почему то в однму минуту по одному инструменту может прийти несколько событий "Новый бар". 

Чтобы повторить, можно выбрать интервал, например с 2016.07.15 по 2016.07.19. Вот пример лога, смотрим 7-ую минуту, 9-ую минуту:

2016.07.15 00:05:00 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.8333

2016.07.15 00:05:00 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.1119

2016.07.15 00:05:00 >  -> id=1:  GBPUSD CHARTEVENT_NEWBAR_M1 price=1.33399

2016.07.15 00:06:00 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.8334

2016.07.15 00:06:00 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.1119

2016.07.15 00:06:00 >  -> id=1:  GBPUSD CHARTEVENT_NEWBAR_M1 price=1.33394

2016.07.15 00:07:19 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.8333700000000001

2016.07.15 00:07:19 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.11174

2016.07.15 00:07:19 >  -> id=1:  GBPUSD CHARTEVENT_NEWBAR_M1 price=1.33382

2016.07.15 00:07:19 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.8333700000000001

2016.07.15 00:07:19 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.11174

2016.07.15 00:07:19 >  -> id=1:  GBPUSD CHARTEVENT_NEWBAR_M1 price=1.33381

2016.07.15 00:07:19 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.8333700000000001

2016.07.15 00:07:19 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.11174

2016.07.15 00:07:19 >  -> id=1:  GBPUSD CHARTEVENT_NEWBAR_M1 price=1.33384

2016.07.15 00:08:00 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.83329

2016.07.15 00:08:00 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.11167

2016.07.15 00:08:00 >  -> id=1:  GBPUSD CHARTEVENT_NEWBAR_M1 price=1.33394

2016.07.15 00:09:00 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.83327

2016.07.15 00:09:00 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.11166

2016.07.15 00:09:00 >  -> id=1:  GBPUSD CHARTEVENT_NEWBAR_M1 price=1.33396

2016.07.15 00:09:00 >  -> id=2:  EURGBP CHARTEVENT_NEWBAR_M1 price=0.83327

2016.07.15 00:09:00 >  -> id=0:  EURUSD CHARTEVENT_NEWBAR_M1 price=1.11166

С чем связано такое поведение при выборе режима "Все тики на основе реальных" ?
Rashid Umarov
Rashid Umarov | 20 июл. 2016 в 17:35
ooparadise:

Пытаюсь получить цены по трем парам EURUSD, EURGBP, GBPUSD. Все работает хорошо при выборе "Все тики", либо "Только цены открытия" в тестере стратегий. Но если я выбираю "Каждый тик на основе реальных тиков", то почему то в однму минуту по одному инструменту может прийти несколько событий "Новый бар". 

Чтобы повторить, можно выбрать интервал, например с 2016.07.15 по 2016.07.19. Вот пример лога, смотрим 7-ую минуту, 9-ую минуту:

С чем связано такое поведение при выборе режима "Все тики на основе реальных" ?

Как отлавливаете событие "Новый бар"? В 1375 билде точность прихода тиков повышена до миллисекунд:

Tester: Добавлена поддержка времени с точностью до миллисекунд. Ранее в тестере стратегий квантом времени являлась одна секунда.

  • Теперь функции EventSetMillisecondTimer и Sleep работают более точно в тестере стратегий.
  • Повысилась точность подачи тиков при тестировании мультивалютных экспертов. Раньше, если в одну секунду укладывалось несколько тиков (тиковый объем минутного бара больше 60), всем им проставлялось одно и то же время. При тестировании моновалютных экспертов это не имеет большого значения, поскольку тики просто последовательно передаются эксперту. Однако при тестировании на нескольких парах важно знать, тик с какой пары пришел первым. Раньше тики по каждому символу передавались эксперту последовательно: сначала все тики за секунду по одному символу, затем - все тики по другому. Теперь они передаются с учетом миллисекунд.

    При тестировании на реальных тиках миллисекунды берутся из исходных тиковых данных. При генерации тиков проставление миллисекунд происходит в соответствии с тиковым объемом. Например, если в одной секунде умещается 3 тика, то им будет проставлено время 000, 333 и 666 миллисекунды.
ooparadise
ooparadise | 20 июл. 2016 в 17:51

Новый бар отлавливаю так, как написано в статье. То есть индикатор отправляет событие "Новый бар" таким образом (сравнивается с предыдущим временем минуты ,часы, дни, месяцы):

   double price_current=price[rates_total-1];

   TimeCurrent(time);

   if(prev_calculated==0)

     {

      EventCustom(CHARTEVENT_INIT,price_current);

      prev_time=time; 

      return(rates_total);

     }

//--- new tick

   if((flag_event & CHARTEVENT_TICK)!=0) EventCustom(CHARTEVENT_TICK,price_current);       


//--- check change time

   if(time.min==prev_time.min && 

      time.hour==prev_time.hour && 

      time.day==prev_time.day &&

      time.mon==prev_time.mon) return(rates_total);


//--- new minute

   if((flag_event & CHARTEVENT_NEWBAR_M1)!=0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current); 

UPDATE: Проблема ушла при установке билда 1375

Мастер MQL5: Как написать свой модуль управления капиталом и рисками Мастер MQL5: Как написать свой модуль управления капиталом и рисками
Генератор торговых стратегий Мастера MQL5 значительно упрощает проверку торговых идей. В статье рассказывается о том, как написать и подключить в Мастер MQL5 свой собственный модуль управления капиталом и рисками. В качестве примера рассматривается создание алгоритма управления капиталом, в котором размер торгового объема определяется в зависимости от результатов предыдущей сделки. Рассматривается структура и формат описания созданного класса для Мастера MQL5.
Ордерa, позиции и сделки в MetaTrader 5 Ордерa, позиции и сделки в MetaTrader 5
Надежный торговый робот не может быть создан без понимания механизмов работы торговой системы MetaTrader 5. Клиентский терминал получает от торгового сервера информацию о позициях, ордерах и сделках. Чтобы правильно обработать эти данные средствами MQL5 необходимо хорошо представлять как происходит взаимодействие mql5-программы и среды исполнения терминала.
Собери свой торговый советник в Мастере MQL5 Собери свой торговый советник в Мастере MQL5
Знание языков программирования теперь не является обязательным условием для создания торговых роботов. Если раньше это действительно служило непроходимым препятствием для реализации своих торговых стратегий, то появление Мастера MQL5 в корне изменило ситуацию. Начинающие трейдеры могут перестать тревожиться из-за отсутствия опыта программирования - с новым визардом, позволяющим быстро генерировать код советника, он не понадобится.
Мастер MQL5: Как написать свой модуль торговых сигналов Мастер MQL5: Как написать свой модуль торговых сигналов
Генератор торговых стратегий Мастера MQL5 значительно упрощает проверку торговых идей. В статье рассказывается о том, как написать и подключить в Мастер MQL5 свой собственный класс торговых сигналов с реализацией сигналов по пересечению ценой скользящей средней, рассматривается структура и формат описания созданного класса для Мастера MQL5.