Переход на новые рельсы: пользовательские индикаторы в MQL5

Автор: lexy Вторник, Сентябрь 9th, 2014 Нет комментариев

Рубрика: Разное

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

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

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

В этой статье остановимся подробней на индикаторах, их строении, отображении, видах, а также особенностях их написания по сравнению с MQL4.

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

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

1. ОБЩАЯ СТРУКТУРА

Общая структура индикатора по сравнению с MQL4 не поменялась.
Как и раньше, есть три функции — для инициализации, непосредственно обработки данных и завершения работы индикатора.

Как и раньше, многие параметры можно задавать свойствами (директива #property). БОльшая их часть предназначена именно для индикаторов. Свойства и входные параметры, как и раньше, задаются в глобальном контексте.

В качестве примера разберем реализацию раскраски направления индикатора RSI. Здесь представлена урезанная версия, полная же находится в файле Color_RSI.mq5
Рассмотрим код частями.

//+------------------------------------------------------------------+
//|                                                    Color_RSI.mq5 |
//+------------------------------------------------------------------+

// группа информационных свойств.
#property copyright "TheXpert"
#property link      "theforexpert@gmail.com"

#property version   "1.00"

// описание индикатора -- суммарно не должно превышать 511 символов
// с учетом символов перевода строки
#property description "Демонстрация построения индикатора"
#property description "на примере раскрашивания RSI"

Заданные выше свойства отображаются на информационной панели индикатора (закладка «Common» в свойствах). Вот как это выглядит:

// свойства непосредственно индикатора
#property indicator_separate_window // индикатор будет отображен в отдельном подокне

//#property indicator_minimum 0
//#property indicator_maximum 100

#property indicator_buffers 2       // количество используемых буферов
#property indicator_plots 1         // количество отображаемых буферов 

#property indicator_color1 DarkSalmon, DeepSkyBlue // используем 2 цвета
#property indicator_type1 DRAW_COLOR_LINE          // и специальный цветной тип отображения

Эти свойства относятся непосредственно к индикатору. Описание остальных свойств индикатора (и не только) можно посмотреть в справке.

//---- buffers
double Values[];           // буфер значений
double ValuesPainting[];   // буфер индексов цветов

// входные параметры индикатора
input string               _1             = "Параметры для RSI";
input int                  RSIPeriod      = 5;
input ENUM_APPLIED_PRICE   AppliedPrice   = PRICE_CLOSE;
input string               _2             = "Настройки цветов";
input color                Down           = DarkSalmon;
input color                Up             = DeepSkyBlue;

// переменная, в которой будет содержаться хэндл индикатора
int RSIHandle;

Это входные параметры и глобальные переменные (не путать с глобальными переменными терминала). Входные параметры индикатора задаются идентификатором input .

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

Параметр AppliedPrice будет отображен выпадающим списком с возможными допустимыми значениями.

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

//...
enum DayOfWeek
{
   Понедельник,
   Вторник,
   Среда,
   Четверг,
   Пятница,
   Суббота,
   Воскресенье
};

input DayOfWeek Day;

//...

будет отображен так:

 

int OnInit()
{
   // регистрируем индикаторные буферы
   // Values как буфер для отображения
   SetIndexBuffer(0, Values,           INDICATOR_DATA);
   // ValuesPainting как буфер для хранения цветов
   SetIndexBuffer(1, ValuesPainting,   INDICATOR_COLOR_INDEX);

   // Устанавливаем начало рисования буфера Values
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, RSIPeriod);
   // Устанавливаем имя индикатора
   IndicatorSetString(INDICATOR_SHORTNAME, "Цветной RSI(" + string(RSIPeriod) + ")");
   // Устанавливаем пустое значение для буфера Values
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   // Устанавливаем цвета буфера
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, Down);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, Up);

   RSIHandle = iRSI(NULL, 0, RSIPeriod, AppliedPrice);

   // Устанавливаем порядок индексации буферов
   ArraySetAsSeries(Values, true);
   ArraySetAsSeries(ValuesPainting, true);

   return(0);
}

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

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

// функция расчета даных
// параметры функции _можно_ переименовывать
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
{
   int toCount = (int)MathMin(rates_total, rates_total - prev_calculated + 1);

   if (prev_calculated < 0 || prev_calculated > rates_total)
   {
      toCount = rates_total;
   }

   int copied = CopyBuffer(RSIHandle, 0, 0, toCount, Values);
   if (copied == -1)
   {
      Print("Ошибка во время копирования данных №", GetLastError());
      return 0;
   }

   // раскрашивание. Да, теперь оно стало таким простым
   for (int i = toCount - 2; i >= 0; --i)
   {

      if (Values[i + 1] != EMPTY_VALUE &&
          Values[i] > Values[i + 1])   ValuesPainting[i] = 1;
      else                             ValuesPainting[i] = 0;
   }

   return rates_total;
}

Функция расчета данных. Эта функция может быть двух видов. Здесь приведен стандартный вид. Подробней об особенностях немного ниже.

// присутствие функции в коде необязательно
/*
void OnDeinit()
{}
*/

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

2. ДВЕ КОНЦЕПЦИИ ИНДИКАТОРОВ

Первая – стандартная, та, к которой мы привыкли в MQL4, правда в несколько видоизмененной форме. Вместо функции Start используется функция OnCalculate
Для стандартной формы она выглядит следующим образом:

int OnCalculate(const int rates_total,      // размер массивов
                const int prev_calculated,  // обработано баров на предыдущем вызове
                const datetime& time[],     // данные для текущего графика и ТФ ...
                const double& open[],
                const double& high[],      
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
{
   return rates_total;
}

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

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

Вторая концепция – замена и расширение класса функций MQL4 i<…>OnArray. В примерах терминала есть индикатор такого типа – Custom Moving Average. Этот класс индикаторов предназначен для обработки данных по выбору пользователя, в том числе и пользовательских индикаторов.

Функция обработки для этого типа индикаторов выглядит так:

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

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

First Indicator’s Data значит, что индикатор будет применяться к индикатору, который был первым повешен на график для выбранного окна.

Previous Indicator’s Data значит, что индикатор будет применяться к индикатору, который был последним повешен на график для выбранного окна.

Из таких индикаторов можно собирать целые стеки. Например, с помощью индикатора Custom Moving Average можно получить трехкратное сглаживание, наложив первый индикатор на необходимые данные, второй на первый, а третий на второй:

Многие стандартные индикаторы реализуют именно эту концепцию. Поэтому, когда в подсказке функции вы видите параметр applied_price_or_handle:

то это говорит о том, что индикатор реализован таким образом, что может быть рассчитан на пользовательских данных – хэндл на эти данные должен быть передан параметромapplied_price_or_handle.

Таким же образом можно организовать обработку данных прямо в коде индикатора:

{
   // ...
   RSIHandle = iRSI(NULL, 0, RSIPeriod, AppliedPrice);
   SmoothHandle = iMA(NULL, 0, SmoothPeriod, 0, MODE_EMA, RSIHandle);
   // ...
}

Еще одно новое применение данной концепции – возможность написания универсальных сервисных индикаторов. Пример такого индикатора приложен — он называетсяDirection_Brush.mq5.

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

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

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

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

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

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

3. ДОСТУП К ДАННЫМ

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

Копирование происходит с помощью системной функции CopyBuffer. В справке есть подробное ее описание.

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

Кроме этого, есть отдельные функции для доступа к историческим данным:

CopyBuffer Получает в массив данные указанного буфера от указанного индикатора
CopyRates Получает в массив исторические данные структуры Rates для указанных символа и периода
CopyTime Получает в массив исторические данные по времени открытия баров по соответствующим символу и периоду
CopyOpen Получает в массив исторические данные по цене открытия баров по соответствующим символу и периоду
CopyHigh Получает в массив исторические данные по максимальной цене баров по соответствующим символу и периоду
CopyLow Получает в массив исторические данные по минимальной цене баров по соответствующим символу и периоду
CopyClose Получает в массив исторические данные по цене закрытия баров по соответствующим символу и периоду
CopyTickVolume Получает в массив исторические данные по тиковым объемам для соответствующих символа и периода
CopyRealVolume Получает в массив исторические данные по торговым объемам для соответствующих символа и периода
CopySpread Получает в массив исторические данные по спредам для соответствующих символа и периода

Подробней про них можно почитать в справке.

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

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

Также следует помнить о томеще одна неописанная тонкость заключается в том, что если количество копируемых данных равно 0 (нулю), функция CopyBuffer (как и любая другая функция для копирования данных) сгенерирует ошибку 4003, поэтому количество копируемых данных должно быть не менее 1(одного) элемента.

4. БУФЕРЫ ИНДИКАТОРА

Количество буферов не ограничено.

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

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

#property indicator_buffers 2       // количество используемых буферов

число в свойстве должно строго соответствовать общему количеству буферов.

Количество рисуемых буферов задается в свойстве

#property indicator_plots 1         // количество отображаемых буферов

Здесь немного тоньше. Большинство стилей отображения требуют один буфер INDICATOR_DATA для отображения. Однако есть стили, которые требуют сразу несколько индикаторных буферов для отображения.

Речь идет о таких стилях отображения как:

  • DRAW_HISTOGRAM2 — требуется два индикаторных буфера для отображения (HistogramSample.mq5)

 

  • DRAW_FILLING — требуется два индикаторных буфера для отображения (CrossMa.mq5)

 

  • DRAW_CANDLES — требуется четыре буфера (CandleSample.mq5)

 

  • DRAW_BARS — требуется четыре буфера (BarsSample.mq5)

 

Для всех вышеперечисленных типов, кроме DRAW_FILLING (он не бывает не цветным), есть цветные аналоги.

Все индикаторные буферы теперь делятся на 3 типа:

  • INDICATOR_DATA – буферы, данные которых выводятся на график. Т.е. эти буферы предназначены для рисования и для обращения с помощью iCustom. Они всегда должны быть зарегистрированы в первую очередь. Если их порядок будет произвольным (неверным), все успешно скомпилируется и будет рисоваться при применении к графику, однако, скорей всего, неправильно.
  • INDICATOR_COLOR_INDEX – буферы для хранения цветов. Они необходимы для хранения индексов цветов буферов типа INDICATOR_DATA, имеющих один из специальных цветных типов (#property indicator_typeN). Такой буфер (назовем его цветовой) должен регистрироваться сразу после буфера, для которого он хранит цвета.
  • INDICATOR_CALCULATIONS – буферы этого типа предназначены для хранения результатов вспомогательных вычислений. На графике они не отображаются.
int OnInit()
{
   // ...
   SetIndexBuffer(0, V2, INDICATOR_DATA);
   SetIndexBuffer(1, V2C,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, V4, INDICATOR_DATA);
   SetIndexBuffer(3, V4C,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4, V1, INDICATOR_CALCULATIONS);
   SetIndexBuffer(5, V3, INDICATOR_CALCULATIONS);

   // ...
   return 0;
}

Есть также некоторые нюансы при обращении к индикаторам через iCustom.

  • Буферы для отображения (те, которые отображаются на графике) доступны для чтения. Номер буфера должен совпадать с тем, под которым буфер был зарегистрирован.
  • Буферы для хранения цветов могут быть доступны для чтения, но не всегда. Например, в коде выше буфер V2C можно прочитать и получить необходимые значения, но буфер V4C недоступен.
  • Буферы для промежуточных вычислений недоступны, как это было в MQL4. Если вы хотите гарантированно иметь внешний доступ к информации буфера, объявляйте его какINDICATOR_DATA.

В случае, если вы хотите обратиться к недоступному буферу, будет сгенерирована ошибка 4806 Запрошенные данные не найдены»)

Остановимся подробней на цветных буферах.

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

Кроме того, это нововведение в некоторых случаях намного упрощает логику применения нескольких цветов по сравнению с MQL4. Самый наглядный пример – разделение цветами направлений.
В MQL4 в самом экономном (из правильных) варианте исполнения для реализации задачи требовалось 3 (три) буфера и не самая тривиальная логика.
Сейчас это проще простого. Вот выкладки из кода, полностью код можно посмотреть в файле Color_RSI.mq5.

#property indicator_color1 DarkSalmon, DeepSkyBlue // используем 2 цвета
#property indicator_type1 DRAW_COLOR_LINE          // и специальный цветной тип отображения

//---- buffers
double Values[];           // буфер значений
double ValuesPainting[];   // буфер индексов цветов

int OnInit()
{
   // регистрируем индикаторные буферы
   // Values как буфер для отображения
   SetIndexBuffer(0, Values,           INDICATOR_DATA);
   // ValuesPainting как буфер для хранения цветов
   SetIndexBuffer(1, ValuesPainting,   INDICATOR_COLOR_INDEX);

   // …

   return(0);
}

// функция расчета даных
int OnCalculate(/*…*/)
{
   int toCount = (int)MathMin(rates_total, rates_total - prev_calculated + 1);

   int copied = CopyBuffer(RSIHandle, 0, 0, toCount, Values);
   if (copied == -1)
   {
      Print("Ошибка во время копирования данных №", GetLastError());
      return 1;
   }

   // раскрашивание. Да, теперь оно стало таким простым
   for (int i = toCount - 2; i >= 0; --i)
   {
      if (Values[i] > Values[i + 1])   ValuesPainting[i] = 1;
      else                             ValuesPainting[i] = 0;
   }

   return rates_total;
}

Еще немного кода, и получается:

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

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

#property indicator_color1 DarkSalmon, DeepSkyBlue // используем 2 цвета

цветовая схема буфера будет содержать максимум два цвета, даже если динамически (c помощью функции PlotIndexSetInteger) установить большее количество цветов.

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

#property indicator_color1  Red, Red, Red, Red, Red, Red, Red, Red, //…

Можно сделать так:

#define C Red
#property indicator_color1  C, C, C, C, C, C, C, C, C, C, C, C, C, C, //…

Максимальное количество цветов для одного буфера – 63. При количестве цветов больше максимального (при задании свойства indicator_colorN) буфер отображаться не будет.
Вот пример реализации тоновой визуализации, использовано максимальное количество цветов:

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

5. МАССИВЫ

При непосредственном обращении к данным массивов по индексам необходимо иметь в виду порядок данных – свойство AsSeries. Не у всех массивов можно его установить.

Флаг не может быть установлен у многомерных и у статических массивов. У массивов, передаваемых в функцию OnCalculate, флаг устанавливать можно.
Выполнение копирования функцией CopyBuffer от свойства AsSeries не зависит, однако поведение функции CopyBuffer для разных буферов различается.

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

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

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

Вообще, советую всегда проверять, как вы копируете данные и как обращаетесь. Самый простой и безопасный способ

 

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

Дополнительно очень советую внимательно проштудировать справку по CopyBuffer и функциям, связанным с AsSeries.

6. IndicatorCounted

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

int OnCalculate(const int rates_total,      // размер массивов
                const int prev_calculated,  // обработано баров на предыдущем вызове
                //...)
{
   return rates_total;
}

Чаще всего достаточно вернуть значение параметра rates_total, которое содержит количество баров при текущем вызове функции.

Правда, если с момента последнего вызова функции OnCalculate() ценовые данные были изменены (подкачана более глубокая история или были заполнены пропуски истории), то значение входного параметра prev_calculated будет установлено в нулевое значение самим терминалом.

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

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

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

Во время этого промежутка времени при выполнении функции CopyBuffer генерируется ошибка 4806 — »Запрошенные данные не найдены«.

Для того, чтобы узнать, доступны ли данные индикатора для копирования, можно использовать функцию BarsCalculated:

   int WPRHandle = iWPR(NULL, 0, WPRPeriod);

   int copied = CopyBuffer(WPRHandle, 0, 0, bars, Values);
   int err = GetLastError();

   if (-1 == copied)
   {
      if (4806 == err)
      {
         for (int i = 0; i < 1000; ++i)
         {
            if (BarsCalculated(WPRHandle) > 0) break;
         }
         copied = CopyBuffer(WPRHandle, 0, 0, bars, Values);
         err = GetLastError();
      }
   }

   if (-1 == copied)
   {
      Print("Error when trying to get WPR values, last error is ", err, " bars ", bars);
      return 0;
   }
   //...

Также есть очень полезная аналогичная функция для исторических данных — SeriesInfoInteger.

Свойств с помощью нее можно узнать немного — всего 4, однако все они могут понадобиться для корректной обработки данных.

Подробней про функцию можно почитать в справке.

 

ЗАКЛЮЧЕНИЕ

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

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

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

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

ПРИЛОЖЕНИЯ

Color.mqh — это include файл, его необходимо положить в папку MQL5/Include. Файл необходим для работы индикатора Toned_WPR.

Color.mq5 — это библиотека, ее необходимо положить в папку MQL5/Libraries. Файл необходим для работы индикатора Toned_WPR.

Все остальные файлы являются индикаторами.

БЛАГОДАРНОСТИ

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

Прикрепленные файлы:
 BarsSample.mq5 (1.1 Kb)
 CandleSample.mq5 (1.1 Kb)
 Color.mq5 (2.1 Kb)
 Color.mqh (831 bytes)
 Color_RSI.mq5 (5.4 Kb)
 CrossMa.mq5 (1.3 Kb)
 Direction_Brush.mq5 (1.7 Kb)
 HistogramSample.mq5 (1.3 Kb)
 Toned_WPR.mq5 (3.0 Kb)
Источник: mql4.com

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты