English 中文 Español Deutsch 日本語 Português
Конструктор трейдера: Украшение индикаторов

Конструктор трейдера: Украшение индикаторов

MetaTrader 4Примеры | 7 апреля 2009, 14:52
3 694 9
TheXpert
TheXpert

Введение

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

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

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

Статья рассчитана на тех, кто имеет хотя бы начальный уровень владения языком MQL4 и способен реализовывать простые идеи и алгоритмы в коде, а также имеет представление о структуре хранения кода в терминале и умеет пользоваться библиотеками (experts/libraries) и заголовочными файлами (experts/include).


1. Постановка задачи

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

  • Пересечение линий.

  • уровень - помечаться будут не пересечения с уровнем, а весь уровень целиком.


  • Вершины\впадины в простой интерпретации.


  • Раскраска по направлению вверх\вниз.



Про них и поговорим.


2. Базовые понятия

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

#property indicator_separate_window

// количество видимых буферов индикатора
#property indicator_buffers 3

// установка области значений индикатора
#property indicator_minimum 0
#property indicator_maximum 100

// установка цветов индикатора
#property indicator_color1  White
#property indicator_color2  Red
#property indicator_color3  Blue

// внешние настройки
extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;
extern int MAPeriod        = 5;

// объявление буферов индикатора. Здесь они могут быть объявлены в любом порядке.
// Имена буферам можно давать любые, лучше осмысленные

double Values[];           // Собственно значения
double SmoothedValues[];   // Сглаженные значения
double Crosses[];          // пересечения

// Значимое количество знаков после запятой в значениях индикатора
int DigitsUsed = 5;

// Используемое пустое значение. В языке MQL есть два пустых значения -- EMPTY (-1)
// -- используется в качестве пустого параметра при вызове функций
// EMPTY_VALUE (0x7FFFFFFF) -- используется в качестве недопустимого значения 
// (или значения по умолчанию) переменной в индикаторах и при вызове функций. 
// Дело в том, что большинство встроенных индикаторов возвращают 0 при отсутствии значения
// Кроме того, в пользовательских (iCustom) индикаторах пустое значение может быть 
// установлено вообще любое, это надо учитывать.
int EmptyValueUsed = 0;

// Функция инициализации.
int init()
{
   // Количество используемых буферов может быть больше, чем отображаемых, некоторые 
   // могут содержать промежуточные расчеты и вспомогательную информацию. Здесь указывается 
   // общее количество буферов, включая вспомогательные. 
   // Если вспомогательных нет,
   // эта строчка не нужна. Общее количество не должно превышать 8
   // IndicatorBuffers(3);

   // ассоциируем буферы. Важно, чтобы индексы шли от 0 до заявленного количества (не включая)
   // буферы отрисовываются в порядке возрастания индексов, это важно и может быть и будет
   // использовано при написании индикаторов ниже.
   // Это значит, что буфер с бОльшим индексом рисуется поверх буфера с меньшим
   SetIndexBuffer(0, Values);
   SetIndexBuffer(1, SmoothedValues);
   SetIndexBuffer(2, Crosses);
   // кроме того, важно, чтобы вспомогательные буферы располагались за отображаемыми 
   // (т.е. имели бОльший индекс) иначе могут быть проблемы с отображнением, 
   // причем иногда понять причину ошибки при отрисовке в таких случаях непросто

   // эта функция устанавливает пустое значение для буфера с заданным индексом
   // настоятельно не рекомендую использовать эту функцию, дабы не усложнять жизнь себе и людям
   // Пустое значение по умолчанию для буферов -- EMPTY_VALUE. 
   // На графике пустые значения буфера не рисуются (кроме DRAW_ZIGZAG)

   // SetIndexEmptyValue(0, EMPTY_VALUE);
   
   // задаем настройки для буферов
   SetIndexStyle(0, DRAW_LINE);     // Основной сигнал будет сплошной линией
   SetIndexStyle(1, DRAW_LINE, STYLE_DASH); // Сглаженный -- штриховой
   SetIndexStyle(2, DRAW_ARROW, STYLE_SOLID, 2); // Перечения -- крестиками размером 2
   
   SetIndexArrow(2, 251); // код крестика в Wingdings
   
   IndicatorDigits(DigitsUsed); // устанавливаем количество значимых знаков после запятой
   
   // Установка для каждого буфера начальной точки рисования. Если по отношению к текущему индексу 
   // глубина истории 
   // будет меньше записанного здесь значения, значение буфера с этим индексом прорисовано не будет.
   SetIndexDrawBegin(0, RSIPeriod); 
   SetIndexDrawBegin(1, RSIPeriod + MAPeriod);
   SetIndexDrawBegin(2, RSIPeriod + MAPeriod + 1);

   return(0);
}

int start()
{
   // вычисление количества баров для перерасчета
   int toCount = Bars - IndicatorCounted();  
   
   // Считаем значения
   // считаем от начала истории к текущему моменту
   for (int i = toCount - 1; i >=0; i--)
   {
      // понял, насколько это удобно только когда начал использовать
      // рекомендую производить нормализацию данных сразу, 
      // чтобы потом без проблем и головной боли сравнивать
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
      
   // Считаем сглаженные значения
   for (i = toCount - 1; i >=0; i--)
   {
      SmoothedValues[i] = NormalizeDouble(iMAOnArray(Values, 0, MAPeriod, 0, MODE_EMA, i), DigitsUsed);
   }
      
   // ...
   
   return(0);
}

3. Характеристики

Рассмотрим характеристики подробней.



3.1. Пересечение линий

Наверное, каждый кодер попробовал реализовать алгоритм торговли на пересечении двух МА(скользящих средних). Или таким же образом на MACD между основной и сигнальной линией. Попробуем сделать этот алгоритм визуально наглядным, отобразив на индикаторе точки пересечения.

В качестве "подопытного кролика" здесь и далее взят индикатор Relative Strength Index, т.к. именно он изначально являлся жертвой и объектом совершенствования.


3.1.1. Формализация задачи

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



3.1.2. Проблемы

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

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


if ((x1 > y1 && x2 < y2) || (x1 < y1 && x2 > y2))
{
    // имеем пересечение
}

Или, если записать более лаконично:

if ((x1 - y1)*(x2 - y2) < 0)
{
    // имеем пересечение
}

Однако рассмотрим еще одну ситуацию:


Точки, помеченные зеленым цветом, имеют одинаковые значения. В данном случае пересечения нет, имеет место касание.

А тут:


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

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

3.1.3. Решение

Здесь и далее я не буду приводить функцию init(), поскольку в этом нет необходимости. Полный вариант всегда можно посмотреть, заглянув в исходники.

Решение приводится для простого и сглаженного значений индикатора Relative Strength Index.

//|                                 RSI_Crosses_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;
extern int MAPeriod        = 5;

// buffers
double Values[];           // Собственно значения
double SmoothedValues[];   // Сглаженные значения
double Crosses[];          // пересечения

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   
   // Считаем значения, причем сразу нормализуем их до нужной нам величины
   // ...
      
   // Метим пересечения
   for (i = toCount - 1; i >=0; i--)
   {
      // надо история на 1 бар назад, иначе нет смысла проверять
      if (i + 1 >= Bars)
      {
         continue;
      }

      // если хотя бы одно значение пусто, проверять нет смысла
      if (
            Values[i]               == EmptyValueUsed || 
            Values[i + 1]           == EmptyValueUsed ||
            SmoothedValues[i]       == EmptyValueUsed || 
            SmoothedValues[i + 1]   == EmptyValueUsed ||
            Values[i]               == EMPTY_VALUE    || 
            Values[i + 1]           == EMPTY_VALUE    ||
            SmoothedValues[i]       == EMPTY_VALUE    || 
            SmoothedValues[i + 1]   == EMPTY_VALUE
      )
      {
         continue;
      }
      
      // чистим текущее значение
      Crosses[i] = EMPTY_VALUE;
      
      // проверка на пересечение (простой случай)
      if ((Values[i] - SmoothedValues[i])*(Values[i + 1] - SmoothedValues[i + 1]) < 0)
      {
         Crosses[i] = SmoothedValues[i];
         continue;
      }
      
      // условие пересечения -- сложный случай -- когда пересечение включает в себя
      // несколько баров с одинаковыми значениями
      if (Values[i + 1] == SmoothedValues[i + 1] && Values[i] != SmoothedValues[i])
      {
         // имеется потенциальное пересечение -- проверяем на вшивость
         // для этого находим второй ее конец.

         int index = i + 1;
         bool found = false;
         while (
               index < Bars &&    // выход за диапазон
               Values[index] != EmptyValueUsed &&     // проверка на пустое значение
               Values[index] != EMPTY_VALUE &&      // проверка на пустое значение
               SmoothedValues[index] != EmptyValueUsed &&      // проверка на пустое значение
               SmoothedValues[index] != EMPTY_VALUE)     // проверка на пустое значение
         {
            if (Values[index] != SmoothedValues[index])
            {
               // ура, второй конец найден
               found = true;
               break;
            }
            
            index++;
         }

         if (!found)
         {
            // или дошли до конца истории, или наткнулись на пустое значение
            // в обоих случаях считаем, что пересечения нет
            continue;
         }
         
         // проверяем концы на пересечение
         if ((Values[i] - SmoothedValues[i])*(Values[index] - SmoothedValues[index]) < 0)
         {
            // пересечение есть
            Crosses[i] = SmoothedValues[i];
         }  // else имеет место касание -- не помечаем
      }
   }
   
   return(0);
}

3.1.4. Автоматизация

Здесь и далее в этом подразделе показывается решение аналогичной задачи с использованием библиотеки Indicator_Painting.


//|                                 RSI_Crosses_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;
extern int MAPeriod        = 5;

// buffers
double Values[];           // Собственно значения
double SmoothedValues[];   // Сглаженные значения
double Crosses[];          // пересечения

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   // Считаем значения
   // ...
      
   // Метим пересечения
   MarkCrosses
   (
      Values,            // быстрый буфер со значениями для проверки
      SmoothedValues,    // медленный буфер со значениями для проверки
      Crosses,           // буфер, в который будут заноситься пересечения
      toCount - 1,       // начальный индекс проверки
      0,                 // конечный индекс проверки
      CROSS_ALL,         // CROSS_UP пересечение вверх CROSS_DOWN вниз CROSS_ALL все
      0);                // используемое пустое значение
   
   return(0);
}

3.2. Пометка уровня

У тех осцилляторов, которые имеют ограниченную и жестко заданную область значений (RSI, Stochastic Oscillator, DeMarker, Money Flow Indedx, Williams' Percent Range), да и у других тоже, очень часто выделяют зоны или уровни. Зона флета, зона перекупленности\перепроданности, зона тренда... Попробуем выделить заданный уровень другим цветом.


3.2.1. Формализация задачи

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



3.2.2. Проблемы

Опять же, несмотря на кажущуюся простоту, здесь есть подводные камни.

Первая проблема это прорисовка тех баров, на которых происходит пересечение с уровнем. Итак, в чем она состоит? Вот простой код решения.

//|                                 RSI_Cut_Levels_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

extern int HigherLevel     = 70;
extern int LowerLevel      = 30;

// buffers
double Higher[];           // Перепокупка
double Lower[];            // Перепродажа
double Values[];           // Собственно значения

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   
   // Считаем значения
   // ...
      
   // Метим уровни -- верхний
   for (i = toCount - 1; i >=0; i--)
   {
      // проверяем на пустые значения
      if (Values[i] == EMPTY_VALUE || Values[i] == EmptyValueUsed)
      {
         continue;
      }
      
      // обнуляем текущее значение
      Higher[i] = EMPTY_VALUE;
   
      // больше уровня
      if (Values[i] >= HigherLevel)
      {
         Higher[i] = Values[i];
      }
   }
   
   // Метим уровни -- нижний
   // код практически идентичный

   return(0);
}

Этот код решает поставленную задачу, но имеет одну проблему:


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

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

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

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


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

После решения этих проблем получается вот такая картинка для RSI:



3.2.3. Решение

Итак, запишем вышеизложенное в коде:

//|                                 RSI_Levels_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

extern int HigherLevel     = 70;
extern int LowerLevel      = 30;

// buffers
double Higher[];           // Перепокупка
double Lower[];            // Перепродажа
double Values[];           // Собственно значения

int DigitsUsed = 5;
int EmptyValueUsed = 0;

// просматриваем всегда минимум 2 бара -- нулевой и первый
int Depth = 2;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   
   // Считаем значения
   // ...
   
   // Чтобы не оставалось мусора на 1 баре, заглядываем на 1 бар назад
   toCount = MathMax(toCount, Depth);
      
   // Метим уровни -- верхний
   for (i = toCount - 1; i >=0; i--)
   {
      if (Values[i] == EMPTY_VALUE || Values[i] == EmptyValueUsed) continue;
      
      Higher[i] = EMPTY_VALUE;
   
      // больше уровня
      if (Values[i] >= HigherLevel)
      {
         Higher[i] = Values[i];
      
         // если предыдущий меньше
         if (Values[i + 1] < HigherLevel && Values[i + 1] != EmptyValueUsed)
         {
            // метим и его тоже, но значением уровня
            Higher[i + 1] = HigherLevel;
         }
      }
      // если текущий меньше
      else
      {
         // если предыдущий больше
         if (Values[i + 1] >= HigherLevel && Values[i + 1] != EMPTY_VALUE)
         {
            // метим и его тоже, но значением уровня
            Higher[i] = HigherLevel;
         }
      }
   }
   
   // Метим уровни -- нижний
   // код практически аналогичен
   // ...

   return(0);
}

3.2.4. Автоматизация

Решение той же задачи с помощью библиотеки Indicator_Painting.

//|                                 RSI_Levels_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

extern int HigherLevel     = 70;
extern int LowerLevel      = 30;

// buffers
double Higher[];           // Перепокупка
double Lower[];            // Перепродажа
double Values[];           // Собственно значения

int DigitsUsed = 5;
int EmptyValueUsed = 0;
int Depth = 2;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   
   // Считаем значения
   for (int i = toCount - 1; i >= 0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Метим уровни -- верхний
   MarkLevel(Values, Higher, 0, toCount - 1, HigherLevel, GREATER_THAN, EmptyValueUsed);
   // Метим уровни -- нижний
   MarkLevel(Values, Lower, 0, toCount - 1, LowerLevel, LESS_THAN, EmptyValueUsed);

   return(0);
}


3.3. Вершины и впадины

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

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


3.3.1. Формализация задачи

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



3.3.2. Проблемы

Итак, посмотрим на примеры:


Здесь значения, выделенные красными точками, являются явными экстремумами.

if ((x1 > x2 && x3 > x2) || (x1 < x2 && x3 < x2))
{
    // x2 экстремум
}

Или, если записать более лаконично:

if ((x1 - x2)*(x2 - x3) < 0)
{
    // x2 экстремум
}

Однако рассмотрим еще одну ситуацию:


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


Экстремума нет - будем считать, что это перегиб.

Решение таких ситуаций, как и в случае с пересечением, состоит в поиске второго конца экстремума.


3.3.3. Решение

Вот как это будет выглядеть в коде:

//|                                 RSI_Extremums_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Собственно значения
double Extremums[];        // пики

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
      
   for (i = toCount - 1; i >=0; i--)
   {
      // для проверки относительно текущего индекса нам надо
      // история глубиной на 2 больше, если это не так, можно не проверять
      if (i + 2 >= Bars)
      {
         continue;
      }

      // проверка на пустые значения, если они есть, проверка не имеет смысла
      if (
            Values[i]      == EmptyValueUsed || 
            Values[i + 1]  == EmptyValueUsed ||
            Values[i + 2]  == EmptyValueUsed
      )
      {
         continue;
      }
      
      // затираем текущее значение маркировочного буфера
      Extremums[i + 1] = EMPTY_VALUE;
      
      // условие пересечения -- простой случай
      if ((Values[i] - Values[i + 1])*(Values[i + 1] - Values[i + 2]) < 0)
      {
         // если пересечение имеет место быть
         Extremums[i + 1] = Values[i + 1];
         continue;
      }
      
      // условие пересечения -- сложный случай -- когда вершина включает в себя
      // несколько баров с одинаковыми значениями
      if (Values[i + 1] == Values[i + 2] && Values[i] != Values[i + 1])
      {
         // имеется потенциальный экстремум -- проверяем на вшивость
         // для этого находим второй ее конец.

         int index = i + 2;
         bool found = false;
         while (index < Bars && Values[index] != EmptyValueUsed && Values[index] != EMPTY_VALUE)
         {
            if (Values[i + 2] != Values[index])
            {
               // ура, второй конец найден
               found = true;
               break;
            }
            
            index++;
         }

         if (!found)
         {
            // или дошли до конца истории, или наткнулись на пустое значение
            // в обоих случаях считаем что экстремума нет
            continue;
         }
         
         // проверяем концы на пересечение
         if ((Values[i] - Values[i + 1])*(Values[i + 1] - Values[index]) < 0)
         {
            // пересечение есть
            Extremums[i + 1] = Values[i + 1];
         }  // else -- имеет место точка перегиба, помечать не надо
      }
   }
   
   return(0);
}

3.3.4. Автоматизация

Решение той же задачи с помощью библиотеки Indicator_Painting.

//|                                 RSI_Extremums_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Собственно значения
double Extremums[];        // пики

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  

   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   MarkExtremums(Values, Extremums, toCount - 1, 0, DIR_ALL, EmptyValueUsed);
   
   return(0);
}

3.4. Раскрашивание по направлению

Этот способ визуализации используется даже в некоторых стандартных индикаторах и также может быть полезен.


3.4.1. Формализация задачи

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


3.4.2. Проблемы

Даже не знаю с чего начать... Наверное, начнем не с проблемы, а с фишки.

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

Теперь фишка. Если рисовать одно из направлений поверх базового буфера, то другое направление раскрашивать не надо - его будут показывать незакрашенные куски базового буфера.

Базовый буфер:


Базовый с наложением куска сверху:

Поэтому дальше будем говорить о разметке только одного направления - было выбрано направление вверх.

Может быть, кому-то это покажется очевидным, но я, каюсь, до этого дошел не сразу. Поэтому самая первая версия, которая работала именно так, как я хотел, имела семь(!) буферов и выполняла только задачу раскрашивания. Согласитесь, это реально по-барски, даже если это вам очень необходимо, т.к. максимальное количество буферов в MQL4 равно восьми. Да и по ресурсам неэкономно.

А пока поговорим о проблемах, возникающих при решении задачи. Наивная реализация, с учетом фишки:

//|                                 RSI_Simple_Directions_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Собственно значения
double Growing[];          // буфер роста

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();

   // Считаем значения
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Метим уровни роста -- уровни падения автоматически выделятся сами
   for (i = toCount - 1; i >=0; i--)
   {
      // для проверки относительно текущего индекса нам надо
      // история глубиной на 2 больше, если это не так, можно не проверять
      // ...

      // проверка на пустые значения, если они есть, проверка не имеет смысла
      // ...
      
      // обнуляем текущие значения
      Growing[i] = EMPTY_VALUE;
      
      // если растет
      if (Values[i] > Values[i + 1])
      {
         Growing[i] = Values[i];
         Growing[i + 1] = Values[i + 1];
      }
   }

   return(0);
}

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


Они помечены точками сверху.

Разберемся, почему это происходит.

Раскрашивая одно направление, мы достигаем нужного эффекта за счет того, что на ненужном нам участке оставляем пустые значения (EMPTY_VALUE).

Рассмотрим такой случай:


Черными помечены точки дополнительного буфера, которые должны иметь непустое значение. А для того, чтобы между точками не рисовалась прямая (при стиле DRAW_LINE), необходимо, чтобы между ними было хотя бы одно пустое значение. На всем показанном промежутке пустых значений нет, поэтому базовый буфер зарисовывается на "пилообразных" участках.

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

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


Проблема решена, но есть еще одна небольшая проблема - с нулевым баром.

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

Пожалуй, можно переходить к реализации.


3.4.3. Решение


//|                                 RSI_Directions_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Собственно значения
double Growing1[];         // первый буфер роста
double Growing2[];         // второй буфер роста

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();

   // Считаем значения
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Метим уровни роста -- уровни падения автоматически выделятся сами
   for (i = toCount - 1; i >=0; i--)
   {
      // для проверки относительно текущего индекса нам надо
      // история глубиной на 2 больше, если это не так, можно не проверять
      // ...

      // проверка на пустые значения, если они есть, проверка не имеет смысла
      // ...
      
      // обнуляем текущие значения
      Growing1[i] = EMPTY_VALUE;
      Growing2[i] = EMPTY_VALUE;
      
      // если растет
      if (Values[i] > Values[i + 1])
      {
         // и росло на предыдущем баре
         if (Values[i + 1] > Values[i + 2])
         {
            // пишем в текущий буфер роста
            if (Growing1[i + 1] != EMPTY_VALUE) Growing1[i] = Values[i];
            else                                Growing2[i] = Values[i];
         }
         // если на предыдущем баре не росло
         else
         {
            // пишем в буфер который не использовался последние 2 бара
            // (1 такой должен быть обязательно)
            if (Growing2[i + 2] == EMPTY_VALUE) 
            {
               Growing2[i] = Values[i];
               Growing2[i + 1] = Values[i + 1];
            }
            else
            {
               Growing1[i] = Values[i];
               Growing1[i + 1] = Values[i + 1];
            }
         }
      }
      // удаление раскраски хвостика если не растет
      else if (i == 0)
      {
         if (Growing1[i + 1] != EMPTY_VALUE && Growing1[i + 2] == EMPTY_VALUE)
         {
            Growing1[i + 1] = EMPTY_VALUE;
         }

         if (Growing2[i + 1] != EMPTY_VALUE && Growing2[i + 2] == EMPTY_VALUE)
         {
            Growing2[i + 1] = EMPTY_VALUE;
         }
      }
   }

   return(0);
}


3.4.4. Автоматизация

Решение той же задачи с помощью библиотеки Indicator_Painting.

//|                                 RSI_Directions_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Собственно значения
double Growing1[];         // первый буфер роста
double Growing2[];         // второй буфер роста

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();

   // Считаем значения
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Метим уровни роста -- уровни падения автоматически выделятся сами
   MarkGrowing(Values, Growing1, Growing2, toCount - 1, 0, EmptyValueUsed);
   
   return(0);
}


4. Библиотека Indicator_Painting

Итогом проделанной выше работы является библиотека Indicator_Painting.

Она создана специально для автоматизации описанных операций, с некоторыми оговорками и дополнениями.

Вот список функций, составляющих ее на данный момент:

// ====================================================
// Разметка вершин и впадин
// ====================================================
void MarkExtremums( 
      double values[],        // значения для проверки
      double& extremums[],    // буфер для разметки экстремумов
      int startIndex,         // начальный индекс для проверки (включается)
      int endIndex,           // конечный индекс для проверки (включается)
      int direction,          // DIR_TOP вершины DIR_BOTTOM впадины DIR_ALL и вершины и впадины
      double emptyValueUsed); // используемое пустое значение
      
      
// ====================================================
// Разметка пересечений
// ====================================================
void MarkCrosses( 
      double values1[],       // значения для проверки
      double values2[],       // значения для проверки
      double& crosses[],      // буфер для разметки пересечений
      int startIndex,         // начальный индекс для проверки (включается)
      int endIndex,           // конечный индекс для проверки (включается)
      int direction,          // CROSS_UP пересечение вверх CROSS_DOWN вниз CROSS_ALL все
      double emptyValueUsed); // используемое пустое значение
      
      
// ====================================================
// Разметка пересечений c уровнем
// ====================================================
void MarkLevelCrosses( 
      double values[],        // значения для проверки
      double level,           // уровень для проверки на пересечение
      double& crosses[],      // буфер для разметки пересечений
      int startIndex,         // начальный индекс для проверки (включается)
      int endIndex,           // конечный индекс для проверки (включается)
      int direction,          // CROSS_UP пересечение вверх CROSS_DOWN вниз CROSS_ALL все
      double emptyValueUsed); // используемое пустое значение
      
      
// ====================================================
// Разметка уровней
// ====================================================
void MarkLevel( 
      double values[],        // значения для проверки
      double& level[],        // буфер для разметки пересечений
      int startIndex,         // начальный индекс для проверки (включается)
      int endIndex,           // конечный индекс для проверки (включается)
      double levelValue,      // значение уровня
      int condition,          // меньше (LESS_THAN = -1) или больше уровня (GREATER_THAN = 1)
      double emptyValueUsed); // используемое пустое значение
      
// ====================================================
// Разметка динамических уровней
// ====================================================
void MarkDynamicLevel( 
      double values[],        // значения для проверки
      double dynamicLevel[],  // значения динамического уровня для проверки
      double& level[],        // буфер для разметки пересечений
      int startIndex,         // начальный индекс для проверки (включается)
      int endIndex,           // конечный индекс для проверки (включается)
      int condition,          // меньше (LESS_THAN = -1) или больше уровня (GREATER_THAN = 1)
      double emptyValueUsed); // используемое пустое значение
      
// ====================================================
// Разметка направления (вверх)
// ====================================================
void MarkGrowing( 
      double values[],        // значения для проверки
      double& growing1[],     // 1-й буфер для разметки направления
      double& growing2[],     // 2-й буфер для разметки направления
      int startIndex,         // начальный индекс для проверки (включается)
      int endIndex,           // конечный индекс для проверки (включается)
      double emptyValueUsed); // используемое пустое значение

Вот несколько примеров того, что можно получить с использованием этой библиотеки, практически не прилагая усилий на реализацию:


Для использования библиотеки необходимо:

1. Поместить файл Indicator_Painting.mq4 в папку "experts/libraries"

2. Поместить файл Indicator_Painting.mqh в папку "experts/include"

3. В индикаторе, где используется библиотека, следует записать такую строку:

#include <Indicator_Painting.mqh>

Теперь можно использовать описанные в библиотеке функции. Все необходимые указания есть в файле Indicator_Painting.mqh.

Использование всех функций библиотеки есть в примерах (см. прикрепленные к статье файлы).


Заключение

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


Благодарности

Хочу высказать огромную благодарность Рустамову Виктору Хабибулаевичу (granit77) за то, что он предложил раскрыть эту тему, в принципе, описал цели и необходимость создать подобную библиотеку и особенно за то, что не поленился проштудировать этот опус, ну и высказал немало дельных мыслей.

Последние комментарии | Перейти к обсуждению на форуме трейдеров (9)
[Удален] | 13 апр. 2009 в 19:31

В индикаторе RSI Directions Lib Sample по-моему обнаружил "недокументированную  возможность".

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

К сожалению, эта информация хорошо поможет только тем, кто понимает как работают осцилляторы, остальным же могу сказать, что начинать рассматривать такие сигналы покупки нужно только отталкиваясь от мест потенциального разворота (вблизи уровня 30 + желательно на сильных технических уровнях). 

.

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

[Удален] | 29 апр. 2009 в 00:31
Какой труд ... а красиво то как  .... +10
TheXpert
TheXpert | 21 авг. 2009 в 14:16

Добавлена функция MarkReducing для пометки убывающих значений.

_________

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

Еще раз спасибо granit77

[Удален] | 8 апр. 2012 в 14:31
Хорошая работа! А если бы функция определения пересечения двух линий возвращала точку пересечения эти двух линий - было бы фантастически!
[Удален] | 27 дек. 2013 в 06:42
Огромное спасибо!!! Никак не мог придумать как раскрасить индикатор разными цветами. Да и другие советы тоже очень полезны.
Alert и Comment для внешних индикаторов Alert и Comment для внешних индикаторов
В практической работе трейдер иногда сталкивается с такой ситуацией: нужно получить «alert» или текстовое сообщение на экране монитора, (в окне графика) сообщение или информацию о появившемся сигнале от какого-либо индикатора. В статье приводится пример вывода информации о графических объектах, созданных сторонним индикатором.
Взаимодействие между MеtaTrader 4  и MATLAB Engine (виртуальная машина MATLAB) Взаимодействие между MеtaTrader 4 и MATLAB Engine (виртуальная машина MATLAB)
В данной статье рассматривается вопрос создания DLL библиотеки - обертки, которая позволит взаимодействовать MetaTrader 4 с математическим рабочим столом пакета MATLAB. Описаны "подводные камни" и пути их преодоления. Статья рассчитана на подготовленных программистов С/С++, использующих компилятор Borland C++ Builder 6.
Принцип суперпозиции и интерференции финансовых инструментов Принцип суперпозиции и интерференции финансовых инструментов
Чем больше факторов влияют на поведение валютной пары, тем сложнее оценить ее поведение и сделать прогнозы на будущее. И, следовательно, если бы нам удалось из валютной пары выделить ее составляющие, меняющиеся во времени значения национальной валюты, то тем самым, мы существенно сократили степень свободы движения национальной валюты по сравнению с валютной парой, в которую она входит, и количество факторов влияющих на ее поведение. А как результат, повысили точность оценки ее поведения и прогнозирования будущих значений. Как же нам это сделать?
MetaTrader для работы на фондовом рынке - легко! MetaTrader для работы на фондовом рынке - легко!
В данной статье поднимается проблема автоторговли на фондовом рынке. Приводится пример интеграции MetaTrader и QUIK. Описаны преимущества MT для решения данной задачи, приводится пример торгового робота, способного выполнять операции на ММВБ.