Склейка фьючерсов в MetaTrader 5

Vladimir Karputov | 12 марта, 2014

Введение

В MetaTrader 5 пользователю нельзя создавать собственные графики, так как график может строиться только на основе символов, которые поставляет ваш брокер. В случае с фьючерсами трейдеру для анализа нужен синтетический инструмент – склеенный непрерывный фьючерс. Беда в том, что такую склейку проводит только брокер, а значит, только брокер решает, будет ли он производить склейку по данному символу или нет.

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


Принятые условности:

  1. Все данные и выкладки, а также рисунки и скриншоты в статье основываются на реальных значениях фьючерса на индекс Украинской биржи.
  2. Участки кода, которые были изменены или добавлены в статье будут выделяться цветом. Например:
    //+------------------------------------------------------------------+
    //|                                                       Это пример |
    //|                                            редактированного кода |
    //+------------------------------------------------------------------+

Проблема первая: перекрытие по датам

Соседние фьючерсы торгуются с перекрытием по датам.

То есть, когда до последнего дня исполнения по текущему фьючерсу остаётся ещё около 2 месяцев, следующий фьючерс уже начинает торговаться.

Рис. 1. Перекрытие по датам фьючерсов

Рис. 1. Перекрытие по датам фьючерсов

На рисунке видно, что 2013.09.16 – дата начала обращения следующего фьючерса UX-3.14, хотя в это время еще существует текущий фьючерс UX-12.13.


Проблема вторая: выбираем тип склейки

Склейки бывают следующих типов:

Рис. 2. Склейка. Простое добавление.

Рис. 2. Склейка. Простое добавление

Рис. 3. Склейка. Добавление со смещением

Рис. 3. Склейка. Добавление со смещением


Необходимые настройки в терминале

В окне «Обзор рынка» следует расположить фьючерсы в порядке убывания:

Рис. 4. Обзор рынка 

Рис. 4. Обзор рынка


Создаём индикатор

По умолчанию индикатор должен находиться в папке terminal_data_folder\MQL5\Indicators. Чтобы не засорять папку ...\Indicators Стандартной библиотеки, а также для дальнейшего удобства синхронизации своих индикаторов с помощью MQL5 Storage, создадим в директории ...\Indicators свою папку MyIndicators и в ней папку Synthetics. Итоговый путь к папке будет выглядеть так: terminal_data_folder\MQL5\Indicators\MyIndicators\Synthetics.

В папке Synthetics создадим новый файл:

Рис. 5. Создаём новый файл индикатора

Рис. 5. Создаём новый файл индикатора

Укажем тип нового файла - "Пользовательский индикатор":

Рис. 3. Тип нового файла - "Пользовательский индикатор" 

Рис. 6. Тип нового файла - "Пользовательский индикатор"

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

Рис. 7. Параметры пользовательского индикатора 

Рис. 7. Параметры пользовательского индикатора

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

В следующем окне выбираем обработчики событий для индикатора:

Рис. 8. Обработчики событий для индикатора 

Рис. 8. Обработчики событий для индикатора

Обратите внимание, в индикаторе "SYNT" будет использоваться функция OnTimer(). В OnTimer() будет организован основной функционал индикатора. Индикатор можно будет прикрепить как на график символа, на котором торговля прекращена (соответственно, на таком символе не будет событий OnCalculate), так и на график символа, по которому торговля активна.

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

Рис. 9. Выбор опции индикатор в отдельном окне

Рис. 9. Выбор опции "индикатор в отдельном окне"

Нажимаем кнопку "Готово" и у нас появилась заготовка индикатора "SYNT".


Организация выпадающего списка типа склейки

Чтобы список типов склейки можно было видеть как выпадающий список, в параметрах индикатора нужно объявить перечисление ENUM_GLUING_TYPE.

Указанное перечисление объявим в глобальной области в начале блока входных параметров:

#property indicator_separate_window
//+------------------------------------------------------------------+
//|  перечисление способов склейки                                   |
//+------------------------------------------------------------------+
enum ENUM_GLUING_TYPE
  {
   simple_addition,        // простое добавление||simple addition
   addition_with_shift     // добавление со смещением||addition with shift
  };  
//--- input parameters || входные параметры
input ENUM_GLUING_TYPE  gluing_type=simple_addition;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

Сейчас можно проверить, как отображается выпадающий список.

Следует компилировать файл индикатора (F7). Теперь при прикреплении индикатора в параметрах можно увидеть, что выпадающий список работает:

Рис. 10. Теперь в параметрах есть выпадающий список

Рис. 10. Теперь в параметрах есть выпадающий список
 

Добавим описание индикатора, которое будет видно во вкладке "Общие" при первом присоединении индикатора к графику или при изменении свойств индикатора:

#property version   "1.00"
//+------------------------------------------------------------------+
//| version   "1.00": Подкачка истории по таймеру                    |
//+------------------------------------------------------------------+
#property description "Индикатор для склейки нескольких фьючерсов."
#property description "Рисуется в отдельном окне самого молодого фьючерса."
#property description "Для отрисовки использует N первых символов"
#property description "взятых из окна \"Обзор рынка\"."

#property indicator_separate_window 

Стиль построения индикатора - DRAW_COLOR_CANDLES - разноцветные свечи.

Для этого стиля необходимо 4 индикаторных буфера и 1 буфер для хранения индекса цвета. Стиль линии, индикатора - STYLE_SOLID - сплошная линия. Отобразим все это в коде индикатора:

#property description "взятых из окна \"Обзор рынка\"."

//--- indicator settings || настройки индикатора
#property indicator_separate_window
#property indicator_buffers 5 
#property indicator_plots   1
//--- plot Bars || отрисовка баров
#property indicator_label1    "SYNT"
#property indicator_type1     DRAW_COLOR_CANDLES
#property indicator_style1    STYLE_SOLID
#property indicator_width1    1
//+------------------------------------------------------------------+
//|  перечисление способов склейки                                   |
//+------------------------------------------------------------------+

Введём входной параметр "numder_futures_gluing" - количество инструментов для построения. Значение "numder_futures_gluing" по умолчанию равно 2:

//--- input parameters || входные параметры
input int               numder_futures_gluing=2;
input ENUM_GLUING_TYPE  gluing_type=simple_addition;

Объявим 4 индикаторных буфера и 1 буфер для хранения индекса цвета, а также вспомогательный массив LoadHistory[]:

#property indicator_style1    STYLE_SOLID
#property indicator_width1    1
//--- indicator buffers || индикаторные буферы
double            OpenBuffer[];
double            HighBuffer[];
double            LowBuffer[];
double            CloseBuffer[];
double            ColorCandlesColors[];
double            LoadHistory[];
//+------------------------------------------------------------------+
//|  перечисление способов склейки                                   |
//+------------------------------------------------------------------+

Свяжем индикаторные буферы с одномерными динамическими массивами, а также для массивов установим индексацию как в таймсериях:

//--- indicator buffers mapping
//--- связывание индикаторных буферов
   SetIndexBuffer(0,OpenBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,HighBuffer,INDICATOR_DATA);
   SetIndexBuffer(2,LowBuffer,INDICATOR_DATA);
   SetIndexBuffer(3,CloseBuffer,INDICATOR_DATA);
   SetIndexBuffer(4,ColorCandlesColors,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(5,LoadHistory,INDICATOR_CALCULATIONS);
//--- set buffer indexing as timeseries
//--- установим индексацию для буфера как в таймсерии
   ArraySetAsSeries(OpenBuffer,true);
   ArraySetAsSeries(HighBuffer,true);
   ArraySetAsSeries(LowBuffer,true);
   ArraySetAsSeries(CloseBuffer,true);
   ArraySetAsSeries(ColorCandlesColors,true);
//---
   return(INIT_SUCCEEDED);

Для отображения имени индикаторных серий ("Open", "High", "Low" и "Close") в окне "Окно данных" нужна переменная s_symbol:

input int               numder_futures_gluing=2;
input ENUM_GLUING_TYPE  gluing_type=simple_addition;
//--- symbol name
string s_symbol;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

Для работы индикатора также понадобятся переменная shft_array и два флага good_history и indicator_rendered:

input ENUM_GLUING_TYPE  gluing_type=simple_addition;
//--- symbol name
string   s_symbol;
int      shift_array=0;
bool     good_history=false;        // история не подготовлена||history not prepared
bool     indicator_rendered=false;  // индикатор не отрисован
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

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

   ArraySetAsSeries(CloseBuffer,true);
   ArraySetAsSeries(ColorCandlesColors,true);
//--- set accuracy || точность отображения значений индикатора
   IndicatorSetInteger(INDICATOR_DIGITS,0);
//--- set drawing line empty value || пустое значение для построения, для которого нет отрисовки
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
//--- set labels for the line || имя для отображения в окне DataWindow
   PlotIndexSetString(0,PLOT_LABEL,s_symbol+" Open;"+s_symbol+" High;"+s_symbol+" Low;"+s_symbol+" Close");
   IndicatorSetString(INDICATOR_SHORTNAME,"SYNT");
//--- set number of colors in color buffer || кол-во цветов в буфере
   PlotIndexSetInteger(0,PLOT_COLOR_INDEXES,9);
//--- set line color || установка цвета линии
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,clrBlue);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,1,clrOrange);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,2,clrRed);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,3,clrGreen);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,4,clrPink);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,5,clrIndigo);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,6,clrPaleVioletRed);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,7,clrDarkViolet);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,8,clrDimGray);
//---
   return(INIT_SUCCEEDED);

Также в функцию инициализации OnInit() добавим инициализацию таймера с периодичностью 3 секунды и инициализацию генератора случайных чисел:

   PlotIndexSetInteger(0,PLOT_LINE_COLOR,7,clrDarkViolet);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,8,clrDimGray);
//---
   EventSetTimer(3);
//--- инициализируем генератор случайных чисел
   MathSrand(GetTickCount());
//---
   return(INIT_SUCCEEDED);

Для операции склейки нужно минимум два символа.

В функции OnCalculate() выполним проверку на количество инструментов, участвующих в склейке:

                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- checking the number of instruments || проверка количества инструментов
   if(numder_futures_gluing<=1)
     {
      //--- сформируем строку сообщения
      string comm=StringFormat("Для работы индикатора необходимо выбрать не менее %d символов",numder_futures_gluing);
      //--- выведем сообщение в комментарий на главное окно графика
      Comment(comm);
      return(0);
     }
//--- return value of prev_calculated for next call
   return(rates_total);

После проверки на достаточное количество символов для склейки проверим, был ли индикатор уже отрисован. Если индикатор уже отрисован, значит из OnCalculate() можно выходить:

      Comment(comm);
      return(0);
     }
   if(indicator_rendered==true) return(rates_total);
//--- return value of prev_calculated for next call
   return(rates_total);

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

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

   if(indicator_rendered==true) return(rates_total);
//--- если это первый запуск вычислений нашего индикатора
//--- или если необходимо рассчитать индикатор для двух или более баров (значит что-то изменилось в истории)
//--- помним, что бар под номером "0" - самый левый
   if(prev_calculated==0 || rates_total>prev_calculated+1)
     {
     }
//--- return value of prev_calculated for next call
   return(rates_total);


Принудительная инициализация индикаторных буферов

Индикаторные буферы индикатора "SYNT" - это связанные динамические массивы.

При первом запуске индикатора такие буферы в обязательном порядке должны быть инициализированы принудительно. Инициализацию нужно проводить в OnCalculate(). Почему в OnCalculate(), а не в OnInit? Пояснение в рисунке ниже:

Рис. 11. Почему инициализация массива именно в OnCalculate

Рис. 11. Почему инициализация массива именно в OnCalculate()

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

//--- помним, что бар под номером "0" - самый левый
   if(prev_calculated==0 || rates_total>prev_calculated+1)
     {
      //--- инициализируем массивы
      ArrayInitialize(OpenBuffer,0);
      ArrayInitialize(HighBuffer,0);
      ArrayInitialize(LowBuffer,0);
      ArrayInitialize(CloseBuffer,0);
     }
//--- return value of prev_calculated for next call
   return(rates_total);

Функция  ArrayInitialize() выполняет инициализацию индикаторного буфера. В данном случае инициализация идет нулями.

При попытке инициализации индикаторного буфера значением EMPTY_VALUE на индикатор "SYNT" нельзя будет наложить индикаторы.


Алгоритм простого добавления

Рис. 10. Алгоритм простого добавления

Рис. 12. Алгоритм простого добавления

 

Даты на рисунке - это даты начала обращения и даты последнего дня обращения для фьючерсов UX-9.13, UX-12.13 и UX-3.14. Эти же данные в виде таблицы:

СимволНачало обращенияПоследний день обращения
UX-9.132013.03.152013.09.16
UX-12.132013.06.172013.12.16
UX-3.142013.09.162014.03.17

 

На рис. 10 дата 2013.12.25 - это реальная дата на календаре. То есть, символ UX-3.14 еще действует.

Метод склейки "Простое добавление" будет реализован в функции SimpleAddition:

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
  }
//+------------------------------------------------------------------+
//| Simple addition                                                  |
//| Простое добавление. В индикаторный массив добавляем только       |
//| символ sibmUP                                                    |
//+------------------------------------------------------------------+
bool SimpleAddition(string simbUP,string simbDOWN,ENUM_TIMEFRAMES period,int Color)
  {
  }
//+------------------------------------------------------------------+

simbUP и simbDOWN - это фьючерсы, расположенные в окне "Обзор рынка" выше и ниже соответственно. Color - цвет которым будет отрисовываться фьючерс. 

Полный код функции SimpleAddition() приведен ниже:

//+------------------------------------------------------------------+
//| Simple addition                                                  |
//| Простое добавление. В индикаторный массив добавляем только       |
//| символ sibmUP                                                    |
//+------------------------------------------------------------------+
bool SimpleAddition(string simbUP,string simbDOWN,ENUM_TIMEFRAMES period,int Color)//////
  {
   datetime expiration_time_UP;     // дата окончания обращения символа simbUP
   datetime expiration_time_DOWN;   // дата окончания обращения символа simbDOWN

   expiration_time_UP=int(SymbolInfoInteger(simbUP,SYMBOL_EXPIRATION_TIME));
   if(expiration_time_UP>TimeLocal())
     {
      expiration_time_UP=TimeLocal();
     }
   if(simbDOWN!="")
     {
      expiration_time_DOWN=int(SymbolInfoInteger(simbDOWN,SYMBOL_EXPIRATION_TIME));
     }
   else
     {
      expiration_time_DOWN=int(SymbolInfoInteger(simbUP,SYMBOL_START_TIME));
     }
//--- в массив rates[] будем копировать цены Open, High, Low и Close
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int copied=0;           //--- сколько скопировано
   copied=CopyRates(simbUP,period,expiration_time_DOWN,expiration_time_UP,rates);
   if(copied>0)
     {
      for(int j=shift_array;j<shift_array+copied;j++)
        {
         //--- записываем цены в буферы
         OpenBuffer[j]=rates[j-shift_array].open;
         HighBuffer[j]=rates[j-shift_array].high;
         LowBuffer[j]=rates[j-shift_array].low;
         CloseBuffer[j]=rates[j-shift_array].close;
         ColorCandlesColors[j]=Color;
        }
      shift_array=shift_array+copied;
      indicator_rendered=true;
      ChartRedraw();
     }
   else
     {
      Print("Не удалось получить исторические данные по символу ",simbUP);
      indicator_rendered=false;
      return(false);
     }
//---  Simple addition end
   return(true);
  }
//+------------------------------------------------------------------+


Алгоритм добавления со смещением


Рис. 11. Алгоритм склейки добавление со смещением

Рис. 13. Алгоритм склейки добавление со смещением 

Главное отличие от алгоритма простого добавления - склейка начинается за 10 дней до последнего дня обращения символа. Метод склейки "Добавление со смещением" будет реализован в функции AdditionWithShift():

//--- Simple addition end
   return(true);
  }
//+------------------------------------------------------------------+
//| Addition With Shift                                              |
//| Добавление со смещением. В индикаторный массив добавляем только       |
//| символ sibmUP                                                    |
//+------------------------------------------------------------------+
bool AdditionWithShift(string simbUP,string simbDOWN,ENUM_TIMEFRAMES period,int Color)
  {
//--- 
   return(true);
  }
//+------------------------------------------------------------------+

Функции AdditionWithShift() отличается от функции SimpleAddition() всего двумя строчками - от дат отнимается 10 дней:

   .
   .
   .
   expiration_time_UP=int(SymbolInfoInteger(simbUP,SYMBOL_EXPIRATION_TIME))-86400*10;
   .
   .
   .
   expiration_time_DOWN=int(SymbolInfoInteger(simbDOWN,SYMBOL_EXPIRATION_TIME))-86400*10;
   .
   .
   .

Из-за такого небольшого отличия в коде я не буду приводить полный код функции AdditionWithShift(), код можно посмотреть в файле индикатора в конце статьи.

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


Предварительная загрузка истории по символам

Функция CheckLoadHistory() выполняет копирование всей истории по символу во вспомогательный буфер tmp_rates.

При удачном копировании флагу good_history присваивается значение true - это значит, что теперь можно отрисовывать индикатор:

//--- Addition With Shift end
   return(true);
  }
//+------------------------------------------------------------------+
//| Request to receive all history from a trade server               |
//| Получение всей истории с торгового сервера                       |
//+------------------------------------------------------------------+
bool CheckLoadHistory(string symbol,ENUM_TIMEFRAMES period)
  {
   MqlRates tmp_rates[];      // в массив rates[] будем копировать цены Open, High, Low и Close
   datetime start_time;       // дата начала торгов по инструменту
   datetime expiration_time;  // дата окончания торгов по инструменту
   start_time=int(SymbolInfoInteger(symbol,SYMBOL_START_TIME));
   expiration_time=int(SymbolInfoInteger(symbol,SYMBOL_EXPIRATION_TIME));
   if(CopyRates(symbol,period,start_time,expiration_time,tmp_rates)>0)
     {
      good_history=true;
     }
   else
     {
      good_history=false;
     }
//--- 
   return(true);
  }
//+------------------------------------------------------------------+

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


OnTimer - основная функция индикатора

Теперь, когда есть код для двух видов склейки и код предварительной загрузки истории, можно изменить функцию OnTimer():

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(indicator_rendered==true) return;
   if(good_history==true)
     {
      int t=0;       // color || цвет
      int number;
      switch(gluing_type)
        {
         case simple_addition:
            for(int n=0;n<numder_futures_gluing;n++)
              {
               //--- получим случайное число
               number=MathRand();
               //--- получим индекс цвета, как остаток от целочисленного деления
               t=number%(PlotIndexGetInteger(0,PLOT_COLOR_INDEXES)-1);
               SimpleAddition(SymbolName(n,true),SymbolName(n+1,true),PERIOD_D1,t);
              }
            break;
         case addition_with_shift:
            for(int n=0;n<numder_futures_gluing;n++)
              {
               //--- получим случайное число
               number=MathRand();
               //--- получим индекс цвета, как остаток от целочисленного деления
               t=number%(PlotIndexGetInteger(0,PLOT_COLOR_INDEXES)-1);
               AdditionWithShift(SymbolName(n,true),SymbolName(n+1,true),PERIOD_D1,t);
              }
            break;
        }
     }
   else
     {
      for(int n=0;n<numder_futures_gluing;n++)
        {
         CheckLoadHistory(SymbolName(n,true),PERIOD_D1);
        }
     }
  }
//+------------------------------------------------------------------+

Основная идея индикатора реализована. Можно компилировать и прикреплять на график. Лучше выбирать символ, по которому торговля уже закрыта и устанавливать период H1.


Предварительные результаты работы индикатора "SYNT"

Прикрепив на график два экземпляра индикатора "SYNT" с разными вариантами типа склейки, можно визуально сравнить два метода склейки:

Рис. 12. Сравнение двух методов склейки фьючерсов

  Рис. 14. Сравнение двух методов склейки фьючерсов


Возможность наложить на индикатор другие (стандартные и пользовательские) индикаторы

На индикатор "SYNT" можно прикрепить пользовательские индикаторы, которые имеют первую форму вызова OnCalculate:

int OnCalculate (const int rates_total,      // размер массива price[]
                 const int prev_calculated,  // обработано баров на предыдущем вызове
                 const int begin,            // откуда начинаются значимые данные
                 const double& price[]       // массив для расчета
   );

Для этого нужно в окне "Навигатор" раскрыть список "Пользовательские индикаторы", далее раскрыть список "Examples" в котором выбрать индикатор и перенести его на "SYNT". Далее во вкладке "Параметры" в выдающем списке выбрать применить к "Данные предыдущего индикатора". 

Список индикаторов, которые можно прикрепить на индикатор "SYNT", и которые корректно работают на индикаторе "SYNT":

Пример индикатора "SYNT", склеившего три фьючерса с наложенным на него индикатором Custom Moving Average:

Рис. 15. Пример склейки трех символов

Рис. 15. Пример склейки трех символов


Заключение

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