Особенности написания Пользовательских Индикаторов

MetaQuotes | 13 сентября, 2005

Написание пользовательских индикаторов в торговой системе MetaTrader также имеет ряд особенностей.

  • Для того, чтобы программа считалась пользовательским индикатором, необходимо наличие описания:

    #property indicator_chart_window      // индикатор рисуется в главном окне графика

    или

    #property indicator_separate_window   // индикатор рисуется в отдельном окне
  • Для фиксации масштаба отдельного окна индикатора используются определения:

    #property indicator_minimum Min_Value
    #property indicator_maximum Max_Value

    где "Min_Value" и "Max_Value" - соответствующие числовые значения. Например, для пользовательского индикатора RSI эти значения должны быть 0 и 100 соответственно.

  • Количество индикаторных массивов, необходимых для отрисовки индикатора, должно быть задано при помощи определения:

    #property  indicator_buffers N

    где N может принимать значение от 1 до 8.

  • Цвет линий индикатора задается определениями:

    #property  indicator_color1  Silver
    #property  indicator_color2  Red
    ...
    #property  indicator_colorN  <SomeColor>

    где N - количество индикаторных массивов, определенное при помощи "#property indicator_buffer".

  • Существует ряд функций, которые позволяют управлять расчетом и визуализацией индикатора. Для иллюстрации используем пользовательский индикатор Ichimoku Kinko Hyo:

    //+------------------------------------------------------------------+
    //|                                                     Ichimoku.mq4 |
    //|                      Copyright © 2004, MetaQuotes Software Corp. |
    //|                                       https://www.metaquotes.net/ |
    //+------------------------------------------------------------------+
    #property copyright "Copyright © 2004, MetaQuotes Software Corp."
    #property link      "https://www.metaquotes.net/"
     
    #property indicator_chart_window
    #property indicator_buffers 7
    #property indicator_color1 Red
    #property indicator_color2 Blue
    #property indicator_color3 SandyBrown
    #property indicator_color4 Thistle
    #property indicator_color5 Lime
    #property indicator_color6 SandyBrown
    #property indicator_color7 Thistle
    //---- input parameters
    extern int Tenkan=9;
    extern int Kijun=26;
    extern int Senkou=52;
    //---- indicator buffers
    double Tenkan_Buffer[];
    double Kijun_Buffer[];
    double SpanA_Buffer[];
    double SpanB_Buffer[];
    double Chinkou_Buffer[];
    double SpanA2_Buffer[];
    double SpanB2_Buffer[];
    //---- span_a drawing begin
    int a_begin;
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int init()
      {
    //----
       SetIndexStyle(0,DRAW_LINE);
       SetIndexBuffer(0,Tenkan_Buffer);
       SetIndexDrawBegin(0,Tenkan-1);
       SetIndexLabel(0,"Tenkan Sen");
    //----
       SetIndexStyle(1,DRAW_LINE);
       SetIndexBuffer(1,Kijun_Buffer);
       SetIndexDrawBegin(1,Kijun-1);
       SetIndexLabel(1,"Kijun Sen");
    //----
       a_begin=Kijun; if(a_begin<Tenkan) a_begin=Tenkan;
       SetIndexStyle(2,DRAW_HISTOGRAM,STYLE_DOT);
       SetIndexBuffer(2,SpanA_Buffer);
       SetIndexDrawBegin(2,Kijun+a_begin-1);
       SetIndexShift(2,Kijun);
       SetIndexLabel(2,NULL);
       SetIndexStyle(5,DRAW_LINE,STYLE_DOT);
       SetIndexBuffer(5,SpanA2_Buffer);
       SetIndexDrawBegin(5,Kijun+a_begin-1);
       SetIndexShift(5,Kijun);
       SetIndexLabel(5,"Senkou Span A");
    //----
       SetIndexStyle(3,DRAW_HISTOGRAM,STYLE_DOT);
       SetIndexBuffer(3,SpanB_Buffer);
       SetIndexDrawBegin(3,Kijun+Senkou-1);
       SetIndexShift(3,Kijun);
       SetIndexLabel(3,NULL);
       SetIndexStyle(6,DRAW_LINE,STYLE_DOT);
       SetIndexBuffer(6,SpanB2_Buffer);
       SetIndexDrawBegin(6,Kijun+Senkou-1);
       SetIndexShift(6,Kijun);
       SetIndexLabel(6,"Senkou Span B");
    //----
       SetIndexStyle(4,DRAW_LINE);
       SetIndexBuffer(4,Chinkou_Buffer);
       SetIndexShift(4,-Kijun);
       SetIndexLabel(4,"Chinkou Span");
    //----
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Ichimoku Kinko Hyo                                               |
    //+------------------------------------------------------------------+
    int start()
      {
       int    i,k;
       int    counted_bars=IndicatorCounted();
       double high,low,price;
    //----
       if(Bars<=Tenkan || Bars<=Kijun || Bars<=Senkou) return(0);
    //---- initial zero
       if(counted_bars<1)
         {
          for(i=1;i<=Tenkan;i++)    Tenkan_Buffer[Bars-i]=0;
          for(i=1;i<=Kijun;i++)     Kijun_Buffer[Bars-i]=0;
          for(i=1;i<=a_begin;i++) { SpanA_Buffer[Bars-i]=0; SpanA2_Buffer[Bars-i]=0; }
          for(i=1;i<=Senkou;i++)  { SpanB_Buffer[Bars-i]=0; SpanB2_Buffer[Bars-i]=0; }
         }
    //---- Tenkan Sen
       i=Bars-Tenkan;
       if(counted_bars>Tenkan) i=Bars-counted_bars-1;
       while(i>=0)
         {
          high=High[i]; low=Low[i]; k=i-1+Tenkan;
          while(k>=i)
            {
             price=High[k];
             if(high<price) high=price;
             price=Low[k];
             if(low>price)  low=price;
             k--;
            }
          Tenkan_Buffer[i]=(high+low)/2;
          i--;
         }
    //---- Kijun Sen
       i=Bars-Kijun;
       if(counted_bars>Kijun) i=Bars-counted_bars-1;
       while(i>=0)
         {
          high=High[i]; low=Low[i]; k=i-1+Kijun;
          while(k>=i)
            {
             price=High[k];
             if(high<price) high=price;
             price=Low[k];
             if(low>price)  low=price;
             k--;
            }
          Kijun_Buffer[i]=(high+low)/2;
          i--;
         }
    //---- Senkou Span A
       i=Bars-a_begin+1;
       if(counted_bars>a_begin-1) i=Bars-counted_bars-1;
       while(i>=0)
         {
          price=(Kijun_Buffer[i]+Tenkan_Buffer[i])/2;
          SpanA_Buffer[i]=price;
          SpanA2_Buffer[i]=price;
          i--;
         }
    //---- Senkou Span B
       i=Bars-Senkou;
       if(counted_bars>Senkou) i=Bars-counted_bars-1;
       while(i>=0)
         {
          high=High[i]; low=Low[i]; k=i-1+Senkou;
          while(k>=i)
            {
             price=High[k];
             if(high<price) high=price;
             price=Low[k];
             if(low>price)  low=price;
             k--;
            }
          price=(high+low)/2;
          SpanB_Buffer[i]=price;
          SpanB2_Buffer[i]=price;
          i--;
         }
    //---- Chinkou Span
       i=Bars-1;
       if(counted_bars>1) i=Bars-counted_bars-1;
       while(i>=0) { Chinkou_Buffer[i]=Close[i]; i--; }
    //----
       return(0);
      }
    //+------------------------------------------------------------------+
  • Функция "SetIndexStyle" управляет параметрами отрисовки индикаторного массива. Тип рисования DRAW_LINE предполагает отрисовку линий между значениями, определёнными в соответствующем индикаторном массиве. Тип рисования DRAW_HISTOGRAM, примененный к индикатору главного окна, имеет свои особенности. Гистограмма рисуется между соответствующими значениями двух индексных массивов: чётного (в нашем случае - SpanA_Buffer) и нечетного (SpanB_Buffer). При этом используется цвет того индексного массива, значение которого больше.

  • Функция "SetIndexDrawBegin" указывает, с какого элемента начинаются значимые данные индикаторного массива.

  • Функция "SetIndexBuffer" позволяет объявить любой одномерный массив типа "double" индексным массивов. При этом управлением индексными массивами занимается система. Именно по этой причине данным массивам не надо указывать размер.

    //---- indicator buffers
    double Tenkan_Buffer[];
    double Kijun_Buffer[];
    double SpanA_Buffer[];
    double SpanB_Buffer[];
    double Chinkou_Buffer[];
    double SpanA2_Buffer[];
    double SpanB2_Buffer[];

    Также к индикаторным массивам нельзя применять функцию ArrayResize - это бесполезно. Бесполезно применять к индикаторным массивам функцию ArrayInitialize, особенно в функции init, когда индикаторные массивы ещё не распределены. Инициализация индикаторных массивов производится автоматически при распределении и перераспределении памяти. В качестве инициализирующего значения используется EMPTY_VALUE либо значение, указанное функцией SetIndexEmptyValue. "Пустые" значения не отображаются.

  • Функция "SetIndexLabel" устанавливает имя, которое будет отображаться во всплывающих подсказках и в окне данных рядом с соответствующим значением (по умолчанию установлено имя "ValueN", где N - номер индексного массива). Если вместо имени передать NULL, то соответствующее значение не будет отображаться ни в подсказках, ни в окне данных. В нашем случае облака штрихуются при помощи гистограммы, а ограничиваются при помощи линии. При этом значения соответствующих "линейных" и "гистограммных" массивов одинаковы, и можно показывать только одно из них.

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

  • Рассмотрим ещё один пример. Пользовательский индикатор - Accelerator/Decelerator Oscillator:

    //+------------------------------------------------------------------+
    //|                                                  Accelerator.mq4 |
    //|                      Copyright © 2005, MetaQuotes Software Corp. |
    //|                                       https://www.metaquotes.net/ |
    //+------------------------------------------------------------------+
    #property  copyright "Copyright © 2005, MetaQuotes Software Corp."
    #property  link      "https://www.metaquotes.net/"
    //---- indicator settings
    #property  indicator_separate_window
    #property  indicator_buffers 3
    #property  indicator_color1  Black
    #property  indicator_color2  Green
    #property  indicator_color3  Red
    //---- indicator buffers
    double     ExtBuffer0[];
    double     ExtBuffer1[];
    double     ExtBuffer2[];
    double     ExtBuffer3[];
    double     ExtBuffer4[];
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int init()
      {
    //---- 2 additional buffers are used for counting.
       IndicatorBuffers(5);
    //---- drawing settings
       SetIndexStyle(0,DRAW_NONE);
       SetIndexStyle(1,DRAW_HISTOGRAM);
       SetIndexStyle(2,DRAW_HISTOGRAM);
       IndicatorDigits(Digits+2);
       SetIndexDrawBegin(0,38);
       SetIndexDrawBegin(1,38);
       SetIndexDrawBegin(2,38);
    //---- 4 indicator buffers mapping
       SetIndexBuffer(0,ExtBuffer0);
       SetIndexBuffer(1,ExtBuffer1);
       SetIndexBuffer(2,ExtBuffer2);
       SetIndexBuffer(3,ExtBuffer3);
       SetIndexBuffer(4,ExtBuffer4);
    //---- name for DataWindow and indicator subwindow label
       IndicatorShortName("AC");
       SetIndexLabel(1,NULL);
       SetIndexLabel(2,NULL);
    //---- initialization done
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Accelerator/Decelerator Oscillator                               |
    //+------------------------------------------------------------------+
    int start()
      {
       int    limit;
       int    counted_bars=IndicatorCounted();
       double prev,current;
    //---- last counted bar will be recounted
       if(counted_bars>0) counted_bars--;
       limit=Bars-counted_bars;
    //---- macd counted in the 1-st additional buffer
       for(int i=0; i<limit; i++)
          ExtBuffer3[i]=iMA(NULL,0,5,0,MODE_SMA,PRICE_MEDIAN,i)-
                        iMA(NULL,0,34,0,MODE_SMA,PRICE_MEDIAN,i);
    //---- signal line counted in the 2-nd additional buffer
       for(i=0; i<limit; i++)
          ExtBuffer4[i]=iMAOnArray(ExtBuffer3,Bars,5,0,MODE_SMA,i);
    //---- dispatch values between 2 buffers
       bool up=true;
       for(i=limit-1; i>=0; i--)
         {
          current=ExtBuffer3[i]-ExtBuffer4[i];
          prev=ExtBuffer3[i+1]-ExtBuffer4[i+1];
          if(current>prev) up=true;
          if(current<prev) up=false;
          if(!up)
            {
             ExtBuffer2[i]=current;
             ExtBuffer1[i]=0.0;
            }
          else
            {
             ExtBuffer1[i]=current;
             ExtBuffer2[i]=0.0;
            }
           ExtBuffer0[i]=current;
         }
    //---- done
       return(0);
      }
    //+------------------------------------------------------------------+
  • Функция "IndicatorBuffers" задает количество используемых буферов для расчета индикатора. Как правило, эта функция вызывается, если используется большее количество индексных массивов, чем это необходимо для отрисовки индикатора. При этом управлением дополнительными массивами занимается система.

  • Функция "SetIndexDigits" управляет точностью вывода информации. В данном случае при расчете разницы двух скользящих средних и дальнейшей разницы результата и сигнальной линией, стандартной точности 4 знака после запятой явно недостаточно.

  • Функция "SetIndexDrawBegin" указывает, с какого элемента начинаются значимые данные индикаторного массива. В нашем примере сигнальная линия рассчитывается как простая скользящая средняя от другой простой скользящей средней, поэтому первые 38 значений индикатора подразумеваются пустыми и не подлежат отрисовке.

  • Функция "IndicatorShortName" устанавливает так называемое короткое имя индикатора, которое будет отображаться в левом верхнем углу окна индикатора и в окне "DataWindow". В качестве короткого имени, если оно не задано, будет использовано название пользовательского индикатора. В данном примере нет необходимости использовать функцию SetIndexLabel, так как выводится всего одно значение. Поэтому для вывода единственного значения достаточно имени индикатора.

  • Функция "SetIndexStyle" управляет параметрами отрисовки индикаторного массива. Тип рисования DRAW_NONE говорит о том, что данную линию рисовать не надо. Дело в том, что в представленном индикаторе гистограмма должна быть раскрашена в 2 разных цвета. Данные из ExtBuffer0 распределяются по двум другим массивам - ExtBuffer1 и ExtBuffer2. Для того, чтобы не выводить в подсказки и в окно данных дублирующие данные, используется функция SetIndexLabel с параметром NULL. Тип рисования DRAW_HISTOGRAM, примененный к индикатору отдельного окна, позволяет рисовать гистограмму между нулевым значением и значением соответствующего массива (сравните с отрисовкой гистограммы в главном окне, описанной выше).

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

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

    double current_AC = iCustom( NULL, 0, "Accelerator", 0, 0 );

    Здесь передача первых двух значений "NULL" и "0" означает, что будет использован текущий график. В качестве имени пользовательского индикатора используется имя соответствующего файла без расширения mq4. Предпоследний параметр 0 означает, что нас интересуют данные из самого первого индикаторного массива. Последний параметр 0 означает, что нас интересует значение самого последнего элемента (то есть самое свежее, текущее значение) запрашиваемого индикаторного массива.

  • Параметры передаются в функцию расчета пользовательского индикатора в том порядке, в котором они описаны. Например, вызов пользовательского индикатора "Ichimoku" с параметрами (9,26,52) будет выглядеть следующим образом:

    iCustom( NULL, 0, "Ichimoku", 9, 26, 52, 0, shift );

    Строго говоря, параметры пользовательского индикатора можно не передавать в функцию. Если в программе не определено ни одной переменной extern, то параметры передавать бесполезно. Также можно воспользоваться начальными значениями, используемыми при описании параметров. Например, вызов того же пользовательского индикатора без параметров будет выглядеть следующим образом:

    iCustom( NULL, 0, "Ichimoku", 0, shift );

    Это означает, что будут использованы те значения, которыми инициализируются переменные "Tenkan", "Kijun", "Senkou", то есть 9, 26 и 52. Однако, если в одном эксперте вызывается один пользовательский индикатор с разными наборами параметров, то крайне не рекомендуется пользоваться умолчаниями.

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