Язык MQL4 для "чайников". Пользовательские индикаторы (часть 1)

Antoniuk Oleg | 12 октября, 2007


Введение

Это четвертая статья из цикла "Язык MQL4 для 'чайников'". Сегодня мы будем учиться писать пользовательские индикаторы. Мы изучим классификацию свойств индикаторов, посмотрим, как эти свойства влияют на сам индикатор, узнаем про новые функции и оптимизацию, и наконец-то напишем несколько своих индикаторов. Кроме того, в конце статьи вас ждут советы по стилю программирования. Если это первая статья "для чайников", которую вы читаете, то, пожалуйста, прочитайте предыдущие статьи, чтобы у вас не возникало никаких вопросов. Кроме того убедитесь, что вы хорошо разобрались в старом материале, так как в этой статье я не буду объяснять основы.



Какие бывают индикаторы?

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

Это Скользящее Среднее (Moving Average, MA), часто используемый технический индикатор. Обратите внимание на следующие важные вещи:

Теперь давайте посмотрим на другой индикатор:

Это Процентный Диапазон Вильямса (Williams’ Percent Range, %R). Обратите внимание, что:

Таким образом, существуют следующие свойства индикаторов:


Теперь давайте посмотрим на еще один индикатор:



Как видите, индикатор Объемов (Volumes) рисуется в виде гистограммы. Таким образом, существуют еще несколько видов вывода показателей индикатора. Вот пример другого типа вывода:



индикатор Фракталов (Fractals) рисуется в виде определенных символов. А теперь внимательно посмотрите на следующий индикатор:



Это индикатор Аллигатор (Alligator). Обратите внимание, что индикатор одновременно рисует 3 показателя (линии баланса). Как это работает?? Дело в том, что любой (есть и исключения, но о них в другой раз) индикатор при выводите использует буферы данных.


Буфер данных - это почти обычный массив. Его отличительная особенность заключается в том, что этим массивом частично управляет терминал. Терминал изменяет массив таким образом, что с появлением каждого нового бара происходит смещение. Это делается для того, чтобы каждый элемент массива соответствовал определенному бару. Максимальное количество выводимых буферов данных в одном индикаторе: 8. Да, сейчас все звучит очень запутанно, но скоро вы поймете, что иначе и быть не могло. Просто запомните, что каждой линии в индикаторе Alligator соответствует свой буфер данных. Также каждый буфер имеет свои параметры, в соответствии с которыми терминал и рисует их. В нашем случае есть 3 буфера, которые можно описать следующим образом:

  1. Первый буфер: рисуется сплошной линией зеленым цветом с толщиной 3.
  2. Второй буфер: рисуется пунктирной линией красным цветом с толщиной 1.
  3. Третий буфер: рисуется сплошной линией синим цветом с толщиной 2.

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

Теперь давайте подведем итог нашей небольшой экскурсии. Любой индикатор имеет такие свойства:

Убедитесь, что вы хорошо разобрались и понимаете все эти свойства. Сейчас мы воспользуемся Мастером, чтобы создать пользовательский индикатор.


Создание пользовательского индикатора

Запускаем Meta Editor, выбираем Файл->Создать:



Появляется окно Мастера создания советника, выбираем Пользовательский индикатор, нажимаем Далее:



Заполняем поля Имя, Автор и Ссылка. Тут все как обычно, но теперь вы можете добавлять параметры. Что же это такое??

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

Сейчас (для наглядного примера) мы добавим параметр, который будет указывать, сколько баров должно обрабатываться для расчета показателя нашего индикатора. Где это может быть использовано?? Представьте, например, что ваш индикатор серьезно нагружает процессор, так как проводит много сложных вычислений. При этом вы часто меняете таймфрейм графика и просматриваете лишь 100-200 последних баров. Зачем в таком случае проводить лишние вычисления и ждать несколько лишних секунд?? Вот тут нас и выручит этот параметр. Конечно, в нашем индикаторе не будет ничего сложного и затратного для ресурсов компьютера. Это просто один из вариантов использования параметров индикатора.

Итак, чтобы добавить параметр нужно нажать на кнопку Добавить (1). После этого вы можете изменить название переменной на что-нибудь более подходящее (2). В нашем случае меняем название на barsToProcess (баров для обработки). Также вы можете изменить начальное значение (3), то есть значение по умолчанию. Меняем на 100. Кроме того, вы можете изменить тип переменной, но в нашем случае ничего менять не нужно, так как тип int идеально подходит для наших целей. После внесения всех необходимых изменений нажимаем Далее:



Почти все готово. Теперь нужно указать, как рисовать индикатор: в отдельном окне или в окне графика цен. Также вы можете ограничить диапазон. Ставим галочку напротив Индикатор в отдельном окне. Ниже мы видим пустое поле Индексы(буферы данных). Здесь вы можете добавить, сколько вам нужно буферов данных (максимум 8). Кроме того, вы всегда сможете добавить или убрать буфера позже, изменив код. Нажимаем Добавить, чтобы добавить один буфер. Теперь вы можете изменить то, как будет отображаться буфер: обычная линия, гистограмма, отрезки или стрелки (символы). Оставим все как есть, то есть тип - Line. Настройте цвет для рисования этого буфера. Нажимаем Готово:



Вуаля!! Ваш первый индикатор готов! Ну и что, что он ничего не рисует, зато сколько кода. Файл с исходным кодом будет размещен в папке с индикаторами: MetaTrader4\experts\indicators.


Разбираем каждую строчку

А теперь давайте посмотри, что для нас создал Meta Editor:

//+------------------------------------------------------------------+
//|                                             myFirstIndicator.mq4 |
//|                                                     Antonuk Oleg |
//|                                                   banderass@i.ua |
//+------------------------------------------------------------------+

Как всегда, "шапка" из однострочных комментариев включает введенные ранее вами данные. Очень мило, смотрим дальше:

#property copyright "Antonuk Oleg"

Вы еще помните директиву препроцессора #define из второй статьи? Мы ее использовали для объявления констант. Так вот, это еще одна директива. Она используется для того, чтобы указать какие-то специфические свойства индикатора. В нашем случае, чтобы указать авторство. Обратите внимание, что сначала идет специальный знак - # (решетка), потом ключевое слово property (без пробела). Property означает свойство. Дальше идет конкретное свойство, которое мы хотим задать, в нашем случае copyright, а потом значение этого свойства. В нашем случае - это строка с вашим именем. С помощью директивы #property можно настроить много специфических аспектов индикатора, в чем вы сейчас убедитесь. Все эти свойства будут заданы по умолчанию. Смотрим дальше:

#property link      "banderass@i.ua"

Эта директива указывает, как можно связаться с автором. Вы можете спросить: где же эти данные (имя автора, как связаться), они ведь нигде не отображаются? Да, не отображаются, но они "прошиваются" в исполняемый файл. Если просмотреть исполняемый файл как обычный текст, то вы сможете увидеть эти данные:

Смотрим дальше:

#property indicator_separate_window

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

#property indicator_buffers 1

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

#property indicator_color1 DarkOrchid

Указываем цвет по умолчанию для первого буфера. Обратите внимание, что нумерация буферов в директивах начинается с единицы, а не с нуля. Постарайтесь запомнить это, чтобы в дальнейшем не возникало никакой путаницы. Цвет указывается с помощью одного из множества предопределенных названий. Посмотреть ключевые слова для всех доступных цветов можно в справке: Справочник MQL4 -> Стандартные константы -> Набор Web-цветов. Аналогично можно указать цвет и для других буферов, нужно просто изменить номер буфера.

extern int       barsToProcess=100;

Это наш параметр индикатора. Мы его настраивали в Мастере. Обратите внимание, что единственное отличие от обычной переменной - это наличие ключевого слова extern перед типом переменной. Вот как будет выглядеть этот параметр для пользователя при запуске индикатора:



Смотри далее:

double ExtMapBuffer1[];

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

Дальше у нас идет объявление и описание функций. В отличие от привычного для вас скрипта, в каждом индикаторе имеется 3, а не 1 функция:

Посмотрим, что происходит в каждой функции:

int init()
{
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMapBuffer1);
 
   return(0);
}

Здесь у нас вызываются 2 важные функции для настройки буфера данных:

SetIndexStyle(0,DRAW_LINE);

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

SetIndexBuffer(0,ExtMapBuffer1);

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

return(0);

Завершение функции, возвращаем нуль - инициализация прошла успешно.

int deinit()
{
//----
   
//----
   return(0);
}

Функция деинициализации по умолчанию пуста.

int start()
{
   int counted_bars=IndicatorCounted();
//----
   
//----
   return(0);
}

Вот мы и добрались до самой главной функции. Весь основной код размещается здесь. Обратите внимание, что заранее объявлена переменная counted_bars (посчитанные бары), которая инициализируется функцией IndicatorCounted(). Эта переменная обычно используется для оптимизации и ускорения работы индикатора, о чем мы поговорим позже. А сейчас давайте наконец-то что-то нарисуем в окне индикатора!



Дописываем индикатор

Определимся что выводить. Что вообще нам будет показывать индикатор? Что-нибудь простое. Для начала давайте рисовать случайные числа. А что?? Этот индикатор гарантирует вам 50% прибыльных сигналов. Решено.

Идем к нашей функции init() и дописываем код для инициализации генератора случайных чисел:

int init()
{
 
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMapBuffer1);
 
   // инициализация генератора случайных чисел
   MathSrand(TimeLocal());
 
   return(0);
}

Инициализация готова, переходим к функции start():

int start()
{
   int counted_bars=IndicatorCounted();
 
   for(int i=0;i<Bars;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

Компилируем - F7. Запускаем терминал, находим панель Навигатор, выбираем раздел Пользовательские Индикаторы и делаем двойной клик на названии нашего индикатора:

Индикатор будет добавлен к активному графику:

Как видите, все работает, теперь давайте хорошенько разберемся, что делает этот код:

for(int i=0;i<Bars;i++)

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

{
   ExtMapBuffer1[i]=MathRand()%1001;
}

Каждую итерацию счетчик увеличивается на единицу, и мы двигаемся от последнего до первого доступного бара и одновременно присваиваем каждому элементу буфера (который соответствует своему бару) случайное число от 0 до 1000. Если вам сложно понять, как каждому элементу буфера соответствует свой бар, то попробуйте изменить цикл следующим образом и посмотрите на результат в терминале:

for(int i=0;i<Bars;i++)
{
   ExtMapBuffer1[i]=i;
}

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



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

Давайте вернемся к коду "случайного" индикатора. Если бы вы начали использовать его хотя бы на протяжении нескольких минут, то заметили бы, что каждый тик индикатор будет рисовать абсолютно другой график. То есть каждый тик индикатор выполняет повторные вычисления того, что мы уже посчитали в прошлый раз. Нам это будет неудобно хотя бы потому, что мы не будем видеть даже то, что было тик назад. Но дело даже не в этом, потому что такой индикатор никто использовать не будет - мы всего лишь учимся. Дело в другом. Представьте, что ваш индикатор выполняет очень много сложных вычислений и расчет одного бара занимает много ресурсов процессора. В таком случае, когда появится новая цена, ваш индикатор снова и снова будет рассчитывать показатель для каждого доступного бара, даже если это уже происходило раньше. Понимаете? Вместо того, чтобы один раз посчитать, он будет делать это снова и снова. Устранение подобных проблем, которые связаны с нерациональным использованием ресурсов и называется оптимизацией.

Как же решить эту проблему? Обычно поступают следующим образом. Сначала индикатор рассчитывается на всех доступных свечах, а уже потом по мере поступления котировок, будет пересчитываться показатель только для последней свечи. То есть поступаем рационально - никаких лишних телодвижений. Давайте оптимизируем функцию start(), чтобы она работала именно так:

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
  
   for(int i=0;i<limit;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

Разбираем каждую строку:

int counted_bars=IndicatorCounted(),

Объявляем переменную counted_bars, которая будет хранить количество посчитанных индикатором баров. На самом деле функция IndicatorCounted() возвращает количество неизмененных баров после прошлого вызова функции start(). Таким образом, если это первый вызов функции start(), то IndicatorBars() возвратит нам 0, так как все бары для нас новые. Если же, это не первый вызов, то очевидно, что изменился только последний бар, поэтому IndicatorBars() возвратит число равное Bars-1.

limit;

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

   if(counted_bars>0)
      counted_bars--;

Как уже было сказано, если IndicatorCounted() возвращает 0, то это значит, что функция start() вызывается впервые и все бары для нас "новые" (еще не рассчитывался индикатор для них). Но если это не первый вызов start(), то нам будет возвращено значение равное Bars-1. Так вот, это условие отслеживает как раз такую ситуацию. После чего мы уменьшаем переменную counted_bars на 1. Зачем это делается, ведь измениться может лишь последний бар?? Оказывается, что бывают ситуации, при которых последний тик предыдущего бара оказывается необработанным из-за того, что в момент прихода этого последнего тика обрабатывался предпоследний тик. Поэтому пользовательский индикатор не был вызван и не был рассчитан. Именно поэтому мы уменьшаем на 1 переменную counted_bars, чтобы исключить эту ситуацию.

limit=Bars-counted_bars;

Здесь мы присваиваем переменной limit (ограничителю) количество последних баров, которые нужно пересчитать. Так как в переменной counted_bars хранится количество свечей, которые уже рассчитаны, то мы просто находим разницу между Bars (всего доступных баров) и counted_bars, чтобы определить, сколько свечей необходимо пересчитать.

for(int i=0;i<limit;i++)
{
   ExtMapBuffer1[i]=MathRand()%1001;
}

Сам цикл почти не изменился. Мы всего лишь поменяли условие выполнения. Теперь цикл будет выполняться пока счетчик i меньше чем ограничитель limit.

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

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

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
   
   if(limit>barsToProcess)
      limit=barsToProcess;
  
   for(int i=0;i<limit;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

Как видите, все достаточно тривиально. Мы проверяем или больше limit чем barsToProcess и если да, то уменьшаем ограничитель через присваивание. В результате, если установить barsToProcess=100, то вы сможете наблюдать похожую картину:

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

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

Уровни - это горизонтальные линии, которые рисуются индикатором определенным стилем, цветом и толщиной. Важно отметить, что максимальное количество уровней на одном баре: 8. Кроме того вы можете задавать уровни как с помощью директив, так и используя функции. Предпочтительнее использовать первый вариант, если вы собираетесь настроить уровни по умолчанию. Для динамического изменения уровней во время работы индикатора используйте функции. Итак, разместим 2 уровня: первый на отметке 800, второй - 200. Для этого добавим несколько директив в начале кода индикатора:

//+------------------------------------------------------------------+
//|                                             myFirstIndicator.mq4 |
//|                                                     Antonuk Oleg |
//|                                                   banderass@i.ua |
//+------------------------------------------------------------------+
#property copyright "Antonuk Oleg"
#property link      "banderass@i.ua"
 
#property indicator_level1 800.0
#property indicator_level2 200.0
#property indicator_levelcolor LimeGreen
#property indicator_levelwidth 2
#property indicator_levelstyle 0
 
#property indicator_separate_window

Разберемся в новых директивах:

#property indicator_level1 800.0

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

#property indicator_level2 200.0

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

#property indicator_levelcolor LimeGreen

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

#property indicator_levelwidth 2

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

#property indicator_levelstyle STYLE_SOLID

Эта директива задает стиль для рисования линии. Доступны такие предопределенные константы:



На этом разработка нашего "случайного" индикатора закончена. Давайте сохраним исходный файл с более подходящим названием - randomIndicator.mq4. Перекомпилируйте исходники еще раз. В следующем разделе этот индикатор нам еще пригодиться. Финальная версия с уровнями должна выглядеть примерно так:



Функция iCustom

Сейчас мы познакомимся с очень полезной функцией - iCustom. Она используется для того, чтобы получить показатели любого пользовательского индикатора. То есть, если для встроенных индикаторов мы используем функции для работы с техническими индикаторами, которые мы рассмотрели в прошлой статье (например: iADX(), iMACD и т.д.), то для всех остальных индикаторов (пользовательских индикаторов) мы используем функцию iCustom. Эта функция является универсальной и может использоваться с любым пользовательским индикатором, который соответствует таким требованиям:

Прототип функции имеет следующий вид:

double iCustom( string symbol, int timeframe, string name, ..., int mode, int shift);

Параметры:

Примеры использования:

ExtMapBuffer[0]=iCustom(NULL,PERIOD_H1,"Momentum",14,0,0);
 
// присваиваем первому элементу массива ExtMapBuffer значение пользовательского 
// индикатора Momentum на последнем доступном баре. При этом используется активный 
// финансовый инструмент на часовом графике. Название исполняемого файла: Momentum. 
// Этот индикатор имеет единственный параметр - период. В нашем случае период 
// равен 14. Также этот индикатор имеет один буфер данных, поэтому используем нуль, 
// чтобы получить доступ к его значениям.
double signalLast=iCustom("EURUSD",PERIOD_D1,"MACD",12,26,9,1,0);
 
// объявляем новую переменную signalLast и присваиваем ей значение пользовательского 
// индикатора MACD на последнем доступном баре. При этом используется пара EURUSD на 
// дневном графике. Название исполняемого файла: MACD. Этот индикатор имеет 3 параметра: 
// период для быстрой средней, период для медленной средней и период для сигнальной линии. 
// Также этот индикатор имеет 2 буфера данных. Первый с значениями главной линии. Второй 
// с значениями сигнальной линии. В нашем случае берется значение сигнальной линии.

Сигнальный индикатор

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

Мы будем писать сигнальный индикатор на основе прошлого индикатора RandomIndicator. Сначала нужно точно определить условия для входа в рынок. Уровни мы ведь не просто так добавляли, поэтому условия будут следующие:

Теперь пришло самое подходящее время для написания нового индикатора. Воспользуйтесь мастером для создания нового пользовательского индикатора. Добавьте один дополнительный параметр, как в прошлый раз:

А последний шаг настройте следующим образом:



Сначала нужно добавить 2 буфера данных, которые будут использоваться, чтобы рисовать сигналы на покупку и продажу в виде стрелочек. Измените тип буферов данных на Arrow. Поменяйте цвета и измените коды символов. Ниже представлены все доступные коды символов:

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

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

int init()
{
//---- indicators
   SetIndexStyle(0,DRAW_ARROW);
   SetIndexArrow(0,236);
   SetIndexBuffer(0,ExtMapBuffer1);
   SetIndexEmptyValue(0,0.0);
   SetIndexStyle(1,DRAW_ARROW);
   SetIndexArrow(1,238);
   SetIndexBuffer(1,ExtMapBuffer2);
   SetIndexEmptyValue(1,0.0);
//----
   return(0);
}

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

SetIndexStyle(0,DRAW_ARROW);

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

SetIndexArrow(0,236);

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

SetIndexEmptyValue(0,0.0);

Остальной код инициализации настраивает буферы аналогично "случайному" индикатору, который мы рассмотрели выше. Теперь давайте допишем код в функцию start():

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
   
   if(limit>barsToProcess)
      limit=barsToProcess;
  
   for(int i=0;i<limit;i++)
   {
      double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
      
      if(randomValue>800.0)
         ExtMapBuffer1[i]=High[i]+5*Point;
      else
         ExtMapBuffer1[i]=0.0;
         
      if(randomValue<200.0)
         ExtMapBuffer2[i]=Low[i]-5*Point;         
      else
         ExtMapBuffer2[i]=0.0;         
   }
   
   return(0);
}

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

   for(int i=0;i<limit;i++)
   {
      double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
      
      if(randomValue>800.0)
         ExtMapBuffer1[i]=High[i]+5*Point;
      else
         ExtMapBuffer1[i]=0.0;
         
      if(randomValue<200.0)
         ExtMapBuffer2[i]=Low[i]-5*Point;         
      else
         ExtMapBuffer2[i]=0.0;         
   }

Сначала мы объявляем переменную randomValue (случайное значение), которой присваиваем значение нашего "случайного" индикатора на текущем баре. Для этого мы используем функцию iCustom:

double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
 
// получаем значение "случайного" индикатора на i-ом баре. Используем активный график на текущем периоде. 
// Название исполняемого файла индикатора: RandomIndicator. Единственный параметр "случайного" индикатора -
// это количество баров для расчета. В нашем индикаторе также имеется аналогичная переменная, поэтому
// используем ее. В "случайном" индикаторе всего 1 буфер данных, поэтому используем 0, чтобы получить
// доступ к его значениям.

Если значение "случайного" индикатора больше верхнего уровня (800), то это сигнал на покупку:

if(randomValue>800.0)
   ExtMapBuffer1[i]=High[i]+5*Point;

// если есть сигнал на покупку, то присваиваем текущему элементу буфера данных наивысшее
// значение текущего бара. Кроме того добавляем 5 пунктов, чтобы стрелочка была немного 
// выше текущей цены. Предопределенная переменная Point используется, чтобы автоматически
// получить множитель для представления пунктов. Иначе нам пришлось бы писать что-то вроде
// этого: ExtMapBuffer1[i]=High[i]+0.0005; 

Иначе, если сигнала на покупку нет, то:

else
   ExtMapBuffer1[i]=0.0;
 
// если сигнала на покупку нет, то присваиваем текущему элементу буфера
// данных "пустое" значение, которое у нас равно 0.0.
// Теперь у нас не будет выводится никаких символов на этом баре.

Если же значение "случайного" индикатора меньше нижнего уровня (200), то это сигнал на продажу:

if(randomValue<200.0)
   ExtMapBuffer2[i]=Low[i]-5*Point;
 
// если это сигнал на продажу, то присваиваем текущему элементу буфера данных наименьшее
// значение текущего бара. Кроме того уменьшаем значение на 5 пунктов, чтобы стрелочка была 
// немного ниже текущей цены.

Иначе, если сигнал на продажу отсутствует, то:

else
   ExtMapBuffer2[i]=0.0;
 
// если сигнала на продажу нет, то присваиваем текущему элементу буфера
// данных "пустое" значение, чтобы не выводить никаких символов на этом баре.

Вот и весь цикл. Откомпилируйте индикатор и запустите его в терминале:





О стиле

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



Заключение

Сегодня вы кое-чему научились. Вы написали два простых индикатора. Да, они бесполезны, но я ведь не учу вас прибыльно торговать!! Вы получили представление о том, как работают индикаторы, какими параметрами и свойствами они обладают. Вы научились настраивать и работать с буферами. Вы узнали несколько новых функций. Функция iCustom очень важна и будет использоваться в дальнейшем даже в советниках. Если у вас возникают какие-то сложности, то медленно перечитывайте статью еще раз и если проблема осталась, то смело задавайте вопросы на форуме или в комментариях.