English Español Deutsch 日本語 Português
preview
Пользовательские индикаторы (Часть 1): Пошаговое руководство по разработке простых индикаторов на MQL5

Пользовательские индикаторы (Часть 1): Пошаговое руководство по разработке простых индикаторов на MQL5

MetaTrader 5Трейдинг | 17 сентября 2024, 12:18
851 2
Kelvin Muturi Muigua
Kelvin Muturi Muigua

Введение

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

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

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


Что такое индикатор?

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

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

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

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


Типы индикаторов в MQL5

С точки зрения формы, в MQL5 разделяют два типа индикаторов: технические и пользовательские.



1. Технические индикаторы


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

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

Примеры стандартных технических индикаторов MetaTrader 5:

  • iMA (Простая скользящая средняя) — вычисляет простую скользящую среднюю указанного ряда цен.
  • iRSI (Индекс относительной силы) — измеряет величину недавних изменений цен для оценки состояний перекупленности или перепроданности.
  • iMACD (Схождение-расхождение скользящих средних) — определяет направление тренда и потенциальные развороты, анализируя конвергенции и дивергенции двух скользящих средних.

Полный список всех Функции технических индикаторов в MQL5 можно найти в справочнике документации по языку.


2. Пользовательские индикаторы


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



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


Вот некоторые свойства и преимущества пользовательских индикаторов:

Гибкость в расчетах:

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

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

  • В MQL5 можно не просто создавать индикаторы с нуля.
  • Можно использовать внешние источники данных, а не только стандартные ценовые данные, для создания индикаторов.
  • Также можно импортировать готовые пользовательские индикаторы, созданные другими, и улучшить их или реализовать дополнительный функционал.
  • Это относится и к встроенным индикаторам на их основе можно создавать комплексные пользовательские индикаторы.

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

Примеры бесплатных пользовательских индикаторов в терминале MetaTrader 5. В терминал MetaTrader 5 включены примеры индикаторов, которые рекомендуется изучить, чтобы лучше понять, как разрабатываются индикаторы. Исходный код бесплатных примеров индикаторов является ценным ресурсом для всех, кто хочет учиться и экспериментировать с созданием индикаторов на MQL5. Примеры MQL5-индикаторов доступны в папках MQL5\Indicators\Examples и MQL5\Indicators\Free Indicators в каталоге установки MetaTrader 5.

Бесплатные примеры индикаторов в MT5

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



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

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



Файл пользовательского индикатора (.mq5)


Индикатор MQL5 хранится в файле с расширением .mq5. Файл содержит исходный код, написанный на языке MQL5, определяющий логику и поведение индикатора. Все индикаторы хранятся в папке MQL5\Indicators в каталоге установки MetaTrader 5.
В торговом терминале MetaTtader 5 и в редакторе MetaEditor есть панель Навигатора для доступа к папке Indicators. Доступ к папке Indicators в папке MQL5 можно также получить следующими способами:
Как получить доступ к файлам индикаторов из торгового терминала MetaTrader 5:
  • Нажмите Файл в верхнем меню.
  • Выберите “Открыть папку данных” или нажмите сочетание клавиш Ctrl + Shift + D.
  • Перейдите в папку “MQL5/Indicators”.

Открыть папку данных MT5


Как получить доступ к файлам индикаторов из MetaEditor:
  • В MetaEditor панель Навигатора по умолчанию расположена с левой стороны в окне MetaEditor. Через нее можно получить прямой доступ к папке MQL5.
  • Если панель Навигатора скрыта, вы можете включить ее с помощью сочетания клавиш Ctrl+D или через меню Вид в верхней части окна MetaEditor. В меню выберите опцию Навигатор. Выбор этой опции активирует панель навигации, через  оторую можно получить доступ к папке MQL5.

Доступ к папке индикаторов в MetaEditor


Пример 1: Пользовательский индикатор линейной скользящей средней гистограммы - (LinearMovingAverageHistogram.mq5). Создадим пользовательский индикатор, чтобы получить визуальное представление о различных компонентах кода, необходимых для написания пользовательского индикатора. Назовем наш первый индикатор 'LinearMovingAverageHistogram'. Он будет отображать линейно-взвешенную скользящую среднюю в виде гистограммы и линию, представляющую текущую цену, в отдельном окне под ценовым графиком.

Индикатор LinearMovingAverageHistogram

Для начала создадим новый файл для нашего индикатора с помощью Мастера MQL5.


Как создать новый файл индикатора с помощью мастера MQL5

Шаг 1. Откройте MetaEditor IDE и запустите Мастер MQL с помощью команды меню 'Создать'.

Новый файл в Мастере MQL5

Шаг 2. Выберите Custom Indicator и нажмите "Далее".

Создание индикатора с помощью Мастера MQL5


Шаг 3. В общих параметрах укажите папку и имя для вашего индикатора, у нас это Indicators\Article\LinearMovingAverageHistogram нажмите "Далее".

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


Шаг 4. В разделе "Обработчики событий" выбираем второй вариант 'OnCalculate(...,price)', оставляем OnTimer и OnChartEvent невыбранными и жмем "Далее".

Создание нового индикатора с помощью мастера MQL5

Шаг 5. В параметрах отображения отметим галочкой Индикатор в отдельном окне. Отключим "Минимум" и "Максимум", поле "Отрисовка" оставим пустым. Нажимаем "Готово", и создастся файл пользовательского индикатора.

Создание нового индикатора с помощью мастера MQL5

В папке MQL5/Indicators вы найдете подпапку Articles. В ней и содержится только что созданный файл кастомного индикатора LinearMovingAverageHistogram.mq5. В рамках практической демонстрации в этой статье мы напишем еще несколько пользовательских индикаторов. Чтобы все организовать как следует, будем сохранять все файлы индикаторов в этой новой папке Articles папка.

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

//+------------------------------------------------------------------+
//|                                 LinearMovingAverageHistogram.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
#property indicator_separate_window
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

//--- возвращаемое значение prev_calculated для следующего вызова
   return(rates_total);
  }
//+------------------------------------------------------------------+


Основные компоненты файла пользовательского индикатора (.mq5). Файл индикатора состоит из различных разделов. Давайте посмотрим, как работают различные части кода индикатора:

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

Рассмотрим их все подробнее:

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

//+------------------------------------------------------------------+
//|                                 LinearMovingAverageHistogram.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
2. Директивы — директивы property предоставляют дополнительную информацию об индикаторе. Они включают в себя авторские права, ссылку на индикатор или сайт автора, текущую версию индикатора и инструкции по отображению индикатора. Самая важная директива — это #property indicator_separate_window, она говорит платформе отобразить индикатор в отдельном окне.
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
#property indicator_separate_window

Директивы copyright, link, author и description отображаются на вкладке "Общие" в окне запуска настройки индикатора, которое открывается, когда вы запускаете его на графике.

Окно ввода настроек индикатора в MetaTrader 5

3. Глобальные переменные — все глобальные переменные располагаются под директивами. В настоящее время код нашего индикатора не содержит глобальных переменных, поскольку мы не указали их при создании файла с помощью Мастера MQL5. Далее в статье мы определим все наши глобальные и пользовательские переменные.

Стандартные функции пользовательских индикаторов в MQL5 Под заголовком вы найдете различные функции. Во всех индикаторах обязательно должны быть стандартные функции MQL5 OnInit и OnCalculate. Пользовательские функции необязательны, но приветствуются для правильной организации кода. Ниже дано описание различных функций в коде нашего индикатора:1. Функция инициализации индикатора OnInit() — вызывается при запуске индикатора. Обычно в ней выполняются задачи настройки, такие как сопоставление буферов индикаторов и инициализация любых глобальных переменных. Мы познакомимся с индикаторными буферами далее в статье. Если функция выполнена успешно, она возвращает INIT_SUCCEEDED. В случае ошибки возвращается INIT_FAILED.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }
2. Функция расчета индикатора OnCalculate()это главный нервный центр всех расчетов пользовательских индикаторов. Она вызывается всякий раз, когда изменяются ценовые данные, чтобы индикатор соответственно обновил свои значения. Существуют две основные версии OnCalculate(), которые также будут объяснены позднее.
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

//--- возвращаемое значение prev_calculated для следующего вызова
   return(rates_total);
  }
3. Функция деинициализации индикатора OnDeinit()она не показана в нашем коде, но это очень важная функция. Он вызывается при завершении работы индикатора и отвечает за выполнение процедур деинициализации. Обычно она выполняет все задачи по очистке, включая освобождение любых дескрипторов технических индикаторов.
//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //-- deinitialization code
  }
//+------------------------------------------------------------------+

При компиляции кода индикатора получаем одно предупреждение: "no indicator plot defined for indicator" (для индикатора не определено графическое построение).

Ошибка компиляции индикатора в MQL5

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

Описание директив свойств

Начнем с краткого описания пользовательского индикатора в разделе заголовка:

#property description "A custom indicator to demonstrate a linear weighted moving average."
#property description "A histogram and line are drawn in a separate window to indicate "
#property description "the moving average direction."

Буферы и построения

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

  • indicator_separate_window или indicator_chart_window — указывают, в каком окне будет отрисовываться индикатор: в отдельном окне или в прямо в окне графика.
  • indicator_buffers — количество индикаторных буферов, используемых пользовательским индикатором.
  • indicator_plots — указывает количество графических построений, которые будет использовать индикатор.

Итак, определим индикаторные буферы и свойства графика. Под свойством В indicator_separate_window мы размещаем код, отвечающий за указание буферов и построений. Индикаторные буферы и построения используются для отображения данных индикатора на графиках. В свойствах задаем количество буферов, которые будут доступны в коде для расчета индикатора. Это целое число от 1 до 512. Поскольку это директива препроцессора, на этапе предварительной обработки исходного кода еще не существует никаких переменных, поэтому в качестве значения мы должны указать количество.

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

//--- буферы и построения
#property indicator_buffers 2
#property indicator_plots   2

Метки, тип и стиль

Далее нужно указать остальные настройки, такие как метка индикатора, тип, цвет, стиль и ширина гистограммы и ценовой линии.

//--- plots1 details for the ma histogram
#property indicator_label1  "MA_Histogram"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- plots2 details for the price line
#property indicator_label2  "Current_Price_Line"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGoldenrod
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

Глобальные переменные для ввода пользователем

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

//--- input parameters for the moving averages
input int            _maPeriod = 50;       // MA Period
input int            _maShift = 0;         // MA Shift


Объявление динамических массивов буфера индикатора в глобальной области

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

Сначала объявим буферы гистограммы и линейного индикатора как динамические массивы, а затем, в функции OnInit() будем использовать функцию SetIndexBuffer, чтобы преобразовать их в управляемые терминалом массивы временных рядов.

//--- индикаторный буфер
double maHistogramBuffer[], priceLineBuffer[];

Функция инициализации пользовательского индикатора GetInit()

Далее создадим пользовательскую функцию, которая будет отвечать за инициализацию нашего индикатора. Начните с создания пустой функции типа void, что означает, что он не возвращает никаких данных. Назовем функцию 'GetInit()'. Добавим функцию SetIndexBuffer(...) чтобы из динамических массивов индикаторных буферов maHistogramBuffer и priceLineBuffer, которые мы объявили ранее, сделать массивы временных рядов.

//+------------------------------------------------------------------+
//| User custom function for custom indicator initialization         |
//+------------------------------------------------------------------+
void GetInit()
  {
//--- установка буферов для отрисовки индикатора
   SetIndexBuffer(0, maHistogramBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, priceLineBuffer, INDICATOR_DATA);

  }
//+------------------------------------------------------------------+
Далее устанавливаем точность индикатора в соответствии с цифровым значением символа.
//--- укажем точность значений
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits + 1);
Определяем первый бар, с которого начнется отрисовка индекса.
//--- установим первый бар, начиная с которого будет строиться индикатор
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, _maPeriod);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 0);
Устанавливаем смещение индикатора скользящей средней на указанное пользователем значение для хэндла скользящей средней, а для линии цены устанавливаем нулевое значение. Эти значения будут использоваться при построении индикатора.
//--- сдвиг индикатора
   PlotIndexSetInteger(0, PLOT_SHIFT, _maShift);
   PlotIndexSetInteger(1, PLOT_SHIFT, 0);

Далее задаем имя, которое будет отображаться в Окне данных терминала при отображении значений индикатора из буфера. Также задаем короткое имя индикатора для окна данных.
//--- имя индикатора, отображаемое в окне данный MetaTrader 5
   IndicatorSetString(INDICATOR_SHORTNAME, "LWMA_Histo" + "(" + string(_maPeriod) + ")");
В завершении функции инициализации индикатора установим гистограмму отрисовки на пустое значение.
//--- пустые значения для гистограммы и линии
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);

Пользовательская функция для расчета линейно-взвешенного скользящего среднего - GetLWMA()

Далее нужно создать пользовательскую функцию под названием GetLWMA(..), которая будет отвечать за расчет линейно-взвешенной скользящей средней. Функция будет иметь тип void, чтобы она не возвращала никакие данные. Функция будет принимать в параметрах четыре аргумента (rates_total, prev_calculated, begin, price). 

//+------------------------------------------------------------------+
//|  Функция для расчета линейно-взвешенного скользящего среднего    |
//+------------------------------------------------------------------+
void GetLWMA(int rates_total, int prev_calculated, int begin, const double &price[])
  {
   int    weight = 0;
   int    x, l, start;
   double sum = 0.0, lsum = 0.0;
//--- это первый расчет или количество баров изменилось
   if(prev_calculated <= _maPeriod + begin + 2)
     {
      start = _maPeriod + begin;
      //--- пустое значение для начала первого расчета
      for(x=0; x < start; x++)
        {
         maHistogramBuffer[x] = 0.0;
         priceLineBuffer[x] = price[x];
        }
     }
   else
      start = prev_calculated - 1;

   for(x = start - _maPeriod, l = 1; x < start; x++, l++)
     {
      sum   += price[x] * l;
      lsum  += price[x];
      weight += l;
     }
   maHistogramBuffer[start-1] = sum/weight;
   priceLineBuffer[x] = price[x];
//--- основной цикл
   for(x=start; x<rates_total && !IsStopped(); x++)
     {
      sum             = sum - lsum + price[x] * _maPeriod;
      lsum            = lsum - price[x - _maPeriod] + price[x];
      maHistogramBuffer[x] = sum / weight;
      priceLineBuffer[x] = price[x];
     }
  }

Основная функция индикатора OnCalculate()

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

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   return(rates_total);
  }
Эта функция принимает четыре входных параметра:
  1. rates_total — количество элементов в массиве цен price[]. Он передается на вход для расчета значений индикатора как в GetLWMA(..).
  2. prev_calculated — сохраняет результат выполнения функции OnCalculate(..) на предыдущем вызове. Это важно для того, чтобы мы не делали расчеты за весь исторический период на каждом вызове функции OnCalculate(..) или на каждом изменении цены.
  3. begin — индекс начального значения в массиве цен, который не содержит данных для расчета. В нашем индикаторе это значение _maPeriod. Мы говорим функции OnCalculte(...) остановить все вычисления до тех пор, пока все бары не достигнут значения в _maPeriod, чтобы было достаточно баров для расчета индикатора.
  4. price — цена, используемая для расчета данных индикатора. Указывается пользователем при загрузке пользовательского индикатора на график. Для выбора доступны: Close, Open, High, Low, Median Price (HL/2), Typical Price (HLC/3), Weighted Close (HLCC/4), а также показатели других индикаторов.

Выбор цены расчета индикатора

Полный код функции OnCalculate(...):
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- проверим, достаточно ли баров для расчета
   if(rates_total < _maPeriod - 1 + begin)
      return(0);

//--- это первый расчет или количество баров изменилось
   if(prev_calculated == 0)
     {
      ArrayInitialize(maHistogramBuffer, 0);
      PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, _maPeriod - 1 + begin);
     }
//--- рассчитаем линейно-взвешенную скользящую среднюю и построим на графике
   GetLWMA(rates_total, prev_calculated, begin, price);

//--- возвращаемое значение prev_calculated для следующего вызова
   return(rates_total);
  }
//+------------------------------------------------------------------+
Теперь у нас есть все сегменты кода для нашего индикатора. Файл LinearMovingAverageHistogram должен выглядеть следующим образом:
#property version   "1.00"
#property indicator_separate_window

//--- буферы и построения
#property indicator_buffers 2
#property indicator_plots   2

//--- plots1 details for the ma histogram
#property indicator_label1  "MA_Histogram"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- plots2 details for the price line
#property indicator_label2  "Current_Price_Line"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGoldenrod
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

//--- input parameters for the moving averages
input int            _maPeriod = 50;       // MA Period
input int            _maShift = 0;         // MA Shift

//--- индикаторный буфер
double maHistogramBuffer[], priceLineBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- вызов функции инициализации
   GetInit();

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- проверим, достаточно ли баров для расчета
   if(rates_total < _maPeriod - 1 + begin)
      return(0);

//--- это первый расчет или количество баров изменилось
   if(prev_calculated == 0)
     {
      ArrayInitialize(maHistogramBuffer, 0);
      PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, _maPeriod - 1 + begin);
     }
//--- рассчитаем линейно-взвешенную скользящую среднюю и построим на графике
   GetLWMA(rates_total, prev_calculated, begin, price);

//--- возвращаемое значение prev_calculated для следующего вызова
   return(rates_total);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| User custom function for custom indicator initialization         |
//+------------------------------------------------------------------+
void GetInit()
  {
//--- установка буферов для отрисовки индикатора
   SetIndexBuffer(0, maHistogramBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, priceLineBuffer, INDICATOR_DATA);

//--- укажем точность значений
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits + 1);

//--- установим первый бар, начиная с которого будет строиться индикатор
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, _maPeriod);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 0);

//--- сдвиг индикатора
   PlotIndexSetInteger(0, PLOT_SHIFT, _maShift);
   PlotIndexSetInteger(1, PLOT_SHIFT, 0);

//--- имя индикатора, отображаемое в окне данный MetaTrader 5
   IndicatorSetString(INDICATOR_SHORTNAME, "LWMA_Histo" + "(" + string(_maPeriod) + ")");
   
//--- пустые значения для гистограммы и линии
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|  Функция для расчета линейно-взвешенного скользящего среднего    |
//+------------------------------------------------------------------+
void GetLWMA(int rates_total, int prev_calculated, int begin, const double &price[])
  {
   int    weight = 0;
   int    x, l, start;
   double sum = 0.0, lsum = 0.0;
//--- это первый расчет или количество баров изменилось
   if(prev_calculated <= _maPeriod + begin + 2)
     {
      start = _maPeriod + begin;
      //--- пустое значение для начала первого расчета
      for(x=0; x < start; x++)
        {
         maHistogramBuffer[x] = 0.0;
         priceLineBuffer[x] = price[x];
        }
     }
   else
      start = prev_calculated - 1;

   for(x = start - _maPeriod, l = 1; x < start; x++, l++)
     {
      sum   += price[x] * l;
      lsum  += price[x];
      weight += l;
     }
   maHistogramBuffer[start-1] = sum/weight;
   priceLineBuffer[x] = price[x];
//--- основной цикл
   for(x=start; x<rates_total && !IsStopped(); x++)
     {
      sum             = sum - lsum + price[x] * _maPeriod;
      lsum            = lsum - price[x - _maPeriod] + price[x];
      maHistogramBuffer[x] = sum / weight;
      priceLineBuffer[x] = price[x];
     }
  }
Сохраните и скомпилируйте индикатор, чтобы убедиться, что нет предупреждений или ошибок. Откройте торговый терминал MetaTrader 5 и запустите индикатор на графике.


Больше практических примеров

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

Пример 2:  Индикатор для мониторинга спреда Custom Indicator SpreadMonitor.mq5

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

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

После создания того файла пользовательского индикатора 'SpreadMonitor.mq5' с помощью мастера MQL, добавим следующий код.

Для начала выберем, в каком окне будет отображаться индикатор:

//--- настройки окна индикатора
#property indicator_separate_window

Укажем количество индикаторных буферов и построение:

//--- буферы и построения
#property indicator_buffers 2
#property indicator_plots   1

Задаем тип индикатора, стиль и цвета:

//--- настройки типа и стиля отображения
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrDarkBlue, clrTomato
#property indicator_style1  0
#property indicator_width1  1
#property indicator_minimum 0.0

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

//--- индикаторные буферы
double spreadDataBuffer[];
double histoColorsBuffer[];

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

//+------------------------------------------------------------------+
//| User custom function for custom indicator initialization         |
//+------------------------------------------------------------------+
void GetInit(){
}
//+------------------------------------------------------------------+

Внутри функции инициализации 'GetInit()' укажем индикаторные буферы:

//--- буферы для построения индикатора
   SetIndexBuffer(0, spreadDataBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, histoColorsBuffer, INDICATOR_COLOR_INDEX);

Зададим имя, которое будет отображаться в окне данных MetaTrader 5 и в метке подокна индикатора:

//--- имя индикатора
   IndicatorSetString(INDICATOR_SHORTNAME,"Spread Histogram");

Установим количество знаков после запятой:

//--- точность индикатора
   IndicatorSetInteger(INDICATOR_DIGITS, 0);

На следующем шаге создадим пользовательскую функцию для расчета спреда. Назовем функцию GetSpreadData() и укажем, что в ней должны передаваться три параметра. Функция будет иметь тип void, нам не нужно, чтобы она возвращала какие-либо данные:

//+------------------------------------------------------------------+
//| Пользовательская функция для расчета спреда                      |
//+------------------------------------------------------------------+
void GetSpreadData(const int position, const int rates_total, const int& spreadData[])
  {
   spreadDataBuffer[0] = (double)spreadData[0];
   histoColorsBuffer[0] = 0.0;
//---
   for(int x = position; x < rates_total && !IsStopped(); x++)
     {
      double currentSpread = (double)spreadData[x];
      double previousSpread = (double)spreadData[x - 1];

      //--- считаем и сохраняем спред
      spreadDataBuffer[x] = currentSpread;
      if(currentSpread > previousSpread)
        {
         histoColorsBuffer[x] = 1.0; //-- для гистограммы установим цвет clrTomato
        }
      else
        {
         histoColorsBuffer[x] = 0.0; //-- для гистограммы установим цвет clrDarkBlue
        }
     }
//---
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//--- проверим, достаточно ли данных для начала расчета
   if(rates_total < 2) //--- не делаем расчетов, выходим и перезагружаем функцию
      return(0);

//--- у нас есть новые данные, начинаем расчеты
   int position = prev_calculated - 1;

//--- обновляем переменную позиции
   if(position < 1)
     {
      spreadDataBuffer[0] = 0;
      position = 1;
     }
//--- считаем и получаем тиковый объем
   GetSpreadData(position, rates_total, spread);

//--- выходим из функции, возвращаем значение prev_calculated
   return(rates_total);
  }

Пользовательский индикатор SpreadMonitor почти готов, осталось соединить все различные сегменты кода и сохранить файл, который можно будет скомпилировать и запустить на графике в MetaTrader 5.

//--- настройки окна индикатора
#property indicator_separate_window

//--- буферы и построения
#property indicator_buffers 2
#property indicator_plots   1

//--- настройки типа и стиля отображения
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrDarkBlue, clrTomato
#property indicator_style1  0
#property indicator_width1  1
#property indicator_minimum 0.0

//--- индикаторные буферы
double spreadDataBuffer[];
double histoColorsBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- инициализируем индикатор
   GetInit();

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//--- проверим, достаточно ли данных для начала расчета
   if(rates_total < 2) //--- не делаем расчетов, выходим и перезагружаем функцию
      return(0);

//--- у нас есть новые данные, начинаем расчеты
   int position = prev_calculated - 1;

//--- обновляем переменную позиции
   if(position < 1)
     {
      spreadDataBuffer[0] = 0;
      position = 1;
     }
//--- считаем и получаем тиковый объем
   GetSpreadData(position, rates_total, spread);

//--- выходим из функции, возвращаем значение prev_calculated
   return(rates_total);
  }

//+------------------------------------------------------------------+
//| Пользовательская функция для расчета спреда                      |
//+------------------------------------------------------------------+
void GetSpreadData(const int position, const int rates_total, const int& spreadData[])
  {
   spreadDataBuffer[0] = (double)spreadData[0];
   histoColorsBuffer[0] = 0.0;
//---
   for(int x = position; x < rates_total && !IsStopped(); x++)
     {
      double currentSpread = (double)spreadData[x];
      double previousSpread = (double)spreadData[x - 1];

      //--- считаем и сохраняем спред
      spreadDataBuffer[x] = currentSpread;
      if(currentSpread > previousSpread)
        {
         histoColorsBuffer[x] = 1.0; //-- для гистограммы установим цвет clrTomato
        }
      else
        {
         histoColorsBuffer[x] = 0.0; //-- для гистограммы установим цвет clrDarkBlue
        }
     }
//---
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| User custom function for custom indicator initialization         |
//+------------------------------------------------------------------+
void GetInit()
  {
//--- буферы для построения индикатора
   SetIndexBuffer(0, spreadDataBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, histoColorsBuffer, INDICATOR_COLOR_INDEX);

//--- имя индикатора
   IndicatorSetString(INDICATOR_SHORTNAME,"Spread Histogram");

//--- точность индикатора
   IndicatorSetInteger(INDICATOR_DIGITS, 0);
  }
//+------------------------------------------------------------------+

Вот как выглядит индикатор SpreadMonitor при запуске В MetaTrader 5 на графике GBPJPY с таймфреймом 5 минут.

Индикатор SpreadMonitor на графике


Пример 3: Сглаженный цветной свечной индикатор SmoothedCandlesticks.mq5

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

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

Свечи становятся зелеными, когда цены Open, High, Low и Close находятся выше, чем сглаженная скользящая средняя. Они становятся красными, когда цены Open, High, Low и Close находятся ниже, чем сглаженная скользящая средняя. Если средняя касается любой части тела свечи, то есть она находится между максимумом и минимумом свечи, такая свеча станет темно-серой, что будет означать, что индикатор не формирует сигнала на вход.

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


Указываем директивы #property

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

//--- укажем окно отображения индикатора
#property indicator_separate_window
//#property indicator_chart_window

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

//--- индикаторные буферы
#property indicator_buffers 6

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

//--- построения индикатора
#property indicator_plots   2

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

//--- детали plots1 для сглаженных свечей
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrDodgerBlue, clrTomato, clrDarkGray
#property indicator_label1  "Smoothed Candle Open;Smoothed Candle High;Smoothed Candle Low;Smoothed Candle Close;"

Повторим предыдущий шаг и укажем данные второго построения для сглаженной скользящей средней линии. Укажем только один цвет для линии.

//--- детали построения линии
#property indicator_label2  "Smoothing Line"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGoldenrod
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

SmoothedCandlesticks - выбор цвета в панели индикатора


Пользовательские переменные в глобальной области

Объявим переменные для периода и цены расчета скользящей средней.

//--- указываемые пользователем параметры скользящего среднего
input int                _maPeriod = 50;                    // период
input ENUM_APPLIED_PRICE _maAppliedPrice = PRICE_CLOSE;     // цена расчета

SmoothedCandlesticks - ввод параметров индикатора


Переменные, буферы и хэндл технического индикатора в глобальной области

Объявим динамические массивы для хранения буферов. Ранее мы использовали директиву #property для выделения шести буферов индикаторов. Давайте начнем с объявления пяти буферов индикаторов для хранения цен открытия, закрытия, максимума, минимума и свечей. Оставшийся буфер для сглаженной линии объявим ниже, поскольку для управления этим буфером мы будем использовать функцию стандартного технического индикатора MQL5 iMA.

//--- индикаторные буферы
double openBuffer[];
double highBuffer[];
double lowBuffer[];
double closeBuffer[];
double candleColorBuffer[];

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

// Динамический массив скользящего среднего (буфер) и переменные
double iMA_Buffer[];
int maHandle; //хранит хэндл индикатора iMA

Здесь мы объявляем и инициализируем нашу последнюю глобальную переменную barsCalculated значением ноль. Эта целочисленная переменная используется для хранения количества баров, рассчитанного на основе сглаженной скользящей средней iMA. Мы будем использовать ее в функции OnCalculate().

//--- целое число для хранения количества значений в индикаторе скользящей средней
int barsCalculated = 0;


Пользовательская функция для инициализации индикатора GetInit()

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

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

//+------------------------------------------------------------------+
//| User custom function for custom indicator initialization         |
//+------------------------------------------------------------------+
bool GetInit()
  {
//--- установка буферов для отрисовки индикатора, назначаем массив буферов
   SetIndexBuffer(0, openBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, highBuffer, INDICATOR_DATA);
   SetIndexBuffer(2, lowBuffer, INDICATOR_DATA);
   SetIndexBuffer(3, closeBuffer, INDICATOR_DATA);
   SetIndexBuffer(4, candleColorBuffer, INDICATOR_COLOR_INDEX);

//--- буфер для iMA
   SetIndexBuffer(5, iMA_Buffer, INDICATOR_DATA);

//--- установим точность цены по количеству знаков после запятой в цене инструмента
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- зададим символ, таймфрейм, период и тип цены для расчета индикатора
   string indicatorShortName = StringFormat("SmoothedCandles(%s, Period %d, %s)", _Symbol,
                               _maPeriod, EnumToString(_maAppliedPrice));
   IndicatorSetString(INDICATOR_SHORTNAME, indicatorShortName);
//IndicatorSetString(INDICATOR_SHORTNAME, "Smoothed Candlesticks");

//--- отрисовка цены на пустое значение
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);

//--- создаем хэндл скользящей maHandle
   maHandle = iMA(_Symbol, PERIOD_CURRENT, _maPeriod, 0, MODE_SMMA, _maAppliedPrice);

//--- проверим успешность создания maHandle
   if(maHandle == INVALID_HANDLE)
     {
      //--- не удалось создать, вернем ошибку
      ResetLastError();
      PrintFormat("Failed to create maHandle of the iMA for symbol %s, error code %d",
                  _Symbol, GetLastError());
      //--- завершаем программу, выходим из функции init
      return(false);
     }

   return(true); // вернем true, если ok
  }
//+------------------------------------------------------------------+

После создания функции GetInit() вызовем ее в стандартной индикаторной функции OnInit(), чтобы она выполнила свою задачу.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- вызов функции инициализации
   if(!GetInit())
     {
      return(INIT_FAILED); //-- если инициализация не удалась, завершим приложение
     }

//---
   return(INIT_SUCCEEDED);
  }


Основная функция индикатора OnCalculate()

Далее добавим код в функцию OnCalculate(....) для расчета индикатора. В этом примере будем использовать длинную версию этой стандартной функции с десятью параметрами. OnCalculate(...) будет использовать расчеты на текущем таймфрейме. Передаем в ней параметры:

  • rates_total — для хранения общего количества баров на графике при запуске и обновлении индикатора для отражения текущего состояния общего количества доступных баров по мере загрузки новых баров или данных.
  • prev_calculated — для хранения количества баров, просчитанных на предыдущем вызове. Помогает узнать, какие данные уже рассчитали или обработали, чтобы не приходилось рассчитывать каждый бар заново на каждом вызове OnCalculate(...) или при появлении новых баров. OnCalculate(..) возвращает новое значение этой переменной при каждом вызове.
  • time, open, high, low, close, tick_volume, volume, spread — названия переменных говорят сами за себя, содержат соответствующие данные. Наш индикатор будет рассчитываться на данных из этих массивов.

Добавим функцию OnCalculate(...) в наш код индикатора.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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 для сохранения количества значений, скопированных из индикатора iMA
   int iMA_valuesToCopy;

//--- найдем количество рассчитанных значений в индикаторе
   int iMA_calculated = BarsCalculated(maHandle);
   if(iMA_calculated <= 0)
     {
      PrintFormat("BarsCalculated() for iMA handle returned %d, error code %d", iMA_calculated, GetLastError());
      return(0);
     }

   int start;
//--- проверим, первый ли это вызов OnCalculate() или есть новые нерассчитанные данные
   if(prev_calculated == 0)
     {
      //--- установим все буферы на первый индекс
      lowBuffer[0] = low[0];
      highBuffer[0] = high[0];
      openBuffer[0] = open[0];
      closeBuffer[0] = close[0];
      start = 1;


      if(iMA_calculated > rates_total)
         iMA_valuesToCopy = rates_total;
      else   //--- копируем рассчитанные бары, которые меньше данных буферов индикатора
         iMA_valuesToCopy = iMA_calculated;
     }
   else
      start = prev_calculated - 1;

   iMA_valuesToCopy = (rates_total - prev_calculated) + 1;

//--- заполним массив iMA_Buffer значениями индикатора скользящей средней
//--- сбросим код ошибки
   ResetLastError();
//--- копируем часть массива iMA_Buffer с данными в нулевой индекс буфера индикатора
   if(CopyBuffer(maHandle, 0, 0, iMA_valuesToCopy, iMA_Buffer) < 0)
     {
      //--- если копирование не удалось, вывести код ошибки
      PrintFormat("Failed to copy data from the iMA indicator, error code %d", GetLastError());
      //--- выходим из функции с нулевым результатом, чтобы указать, что расчеты индикатора не были выполнены
      return(0);
     }

//--- проходим по основному циклу и выполняем все вычисления
   for(int x = start; x < rates_total && !IsStopped(); x++)
     {
      //--- сохраняем все цены массива свечей в новых переменных, не являющихся массивами, для быстрого доступа
      double candleOpen = open[x];
      double candleClose = close[x];
      double candleHigh = high[x];
      double candleLow  = low[x];

      lowBuffer[x] = candleLow;
      highBuffer[x] = candleHigh;
      openBuffer[x] = candleOpen;
      closeBuffer[x] = candleClose;

      //--- проверяем сигналы тренда и устанавливакм нужный цвет свечи
      candleColorBuffer[x] = 2.0; // установим цвет clrDarkGray по умолчанию (нет сигнала)
      if(candleOpen > iMA_Buffer[x] && candleClose > iMA_Buffer[x] && candleHigh > iMA_Buffer[x] && candleLow > iMA_Buffer[x])
         candleColorBuffer[x]=0.0; // цвет clrDodgerBlue - сигнал на покупку

      if(candleOpen < iMA_Buffer[x] && candleClose < iMA_Buffer[x] && candleHigh < iMA_Buffer[x] && candleLow < iMA_Buffer[x])
         candleColorBuffer[x]=1.0; // увет clrTomato - сигнал на продажу
     }

//--- возвращаем rates_total, который включает prev_calculated значение для следующего вызова
   return(rates_total);
  }


Функция деинициализации индикатора OnDeinit()

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

//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(maHandle != INVALID_HANDLE)
     {
      IndicatorRelease(maHandle);//-- очищаем и освобождаем хэндл iMA
     }
  }
//+------------------------------------------------------------------+

Наш индикатор почти готов, соединим все вместе. Убедитесь, что ваш код содержит все части в указанном порядке.

//--- укажем окно отображения индикатора
#property indicator_separate_window
//#property indicator_chart_window

//--- индикаторные буферы
#property indicator_buffers 6

//--- построения индикатора
#property indicator_plots   2

//--- детали plots1 для сглаженных свечей
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrDodgerBlue, clrTomato, clrDarkGray
#property indicator_label1  "Smoothed Candle Open;Smoothed Candle High;Smoothed Candle Low;Smoothed Candle Close;"

//--- детали построения линии
#property indicator_label2  "Smoothing Line"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGoldenrod
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

//--- указываемые пользователем параметры скользящего среднего
input int                _maPeriod = 50;                    // период
input ENUM_APPLIED_PRICE _maAppliedPrice = PRICE_CLOSE;     // цена расчета

//--- индикаторные буферы
double openBuffer[];
double highBuffer[];
double lowBuffer[];
double closeBuffer[];
double candleColorBuffer[];

// Динамический массив скользящего среднего (буфер) и переменные
double iMA_Buffer[];
int maHandle; //хранит хэндл индикатора iMA

//--- целое число для хранения количества значений в индикаторе скользящей средней
int barsCalculated = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- вызов функции инициализации
   if(!GetInit())
     {
      return(INIT_FAILED); //-- если инициализация не удалась, завершим приложение
     }

//---
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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 для сохранения количества значений, скопированных из индикатора iMA
   int iMA_valuesToCopy;

//--- найдем количество рассчитанных значений в индикаторе
   int iMA_calculated = BarsCalculated(maHandle);
   if(iMA_calculated <= 0)
     {
      PrintFormat("BarsCalculated() for iMA handle returned %d, error code %d", iMA_calculated, GetLastError());
      return(0);
     }

   int start;
//--- проверим, первый ли это вызов OnCalculate() или есть новые нерассчитанные данные
   if(prev_calculated == 0)
     {
      //--- установим все буферы на первый индекс
      lowBuffer[0] = low[0];
      highBuffer[0] = high[0];
      openBuffer[0] = open[0];
      closeBuffer[0] = close[0];
      start = 1;


      if(iMA_calculated > rates_total)
         iMA_valuesToCopy = rates_total;
      else   //--- копируем рассчитанные бары, которые меньше данных буферов индикатора
         iMA_valuesToCopy = iMA_calculated;
     }
   else
      start = prev_calculated - 1;

   iMA_valuesToCopy = (rates_total - prev_calculated) + 1;

//--- заполним массив iMA_Buffer значениями индикатора скользящей средней
//--- сбросим код ошибки
   ResetLastError();
//--- копируем часть массива iMA_Buffer с данными в нулевой индекс буфера индикатора
   if(CopyBuffer(maHandle, 0, 0, iMA_valuesToCopy, iMA_Buffer) < 0)
     {
      //--- если копирование не удалось, вывести код ошибки
      PrintFormat("Failed to copy data from the iMA indicator, error code %d", GetLastError());
      //--- выходим из функции с нулевым результатом, чтобы указать, что расчеты индикатора не были выполнены
      return(0);
     }

//--- проходим по основному циклу и выполняем все вычисления
   for(int x = start; x < rates_total && !IsStopped(); x++)
     {
      //--- сохраняем все цены массива свечей в новых переменных, не являющихся массивами, для быстрого доступа
      double candleOpen = open[x];
      double candleClose = close[x];
      double candleHigh = high[x];
      double candleLow  = low[x];

      lowBuffer[x] = candleLow;
      highBuffer[x] = candleHigh;
      openBuffer[x] = candleOpen;
      closeBuffer[x] = candleClose;

      //--- проверяем сигналы тренда и устанавливакм нужный цвет свечи
      candleColorBuffer[x] = 2.0; // установим цвет clrDarkGray по умолчанию (нет сигнала)
      if(candleOpen > iMA_Buffer[x] && candleClose > iMA_Buffer[x] && candleHigh > iMA_Buffer[x] && candleLow > iMA_Buffer[x])
         candleColorBuffer[x]=0.0; // цвет clrDodgerBlue - сигнал на покупку

      if(candleOpen < iMA_Buffer[x] && candleClose < iMA_Buffer[x] && candleHigh < iMA_Buffer[x] && candleLow < iMA_Buffer[x])
         candleColorBuffer[x]=1.0; // увет clrTomato - сигнал на продажу
     }

//--- возвращаем rates_total, который включает prev_calculated значение для следующего вызова
   return(rates_total);
  }

//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(maHandle != INVALID_HANDLE)
     {
      IndicatorRelease(maHandle);//-- очищаем и освобождаем хэндл iMA
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| User custom function for custom indicator initialization         |
//+------------------------------------------------------------------+
bool GetInit()
  {
//--- установка буферов для отрисовки индикатора, назначаем массив буферов
   SetIndexBuffer(0, openBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, highBuffer, INDICATOR_DATA);
   SetIndexBuffer(2, lowBuffer, INDICATOR_DATA);
   SetIndexBuffer(3, closeBuffer, INDICATOR_DATA);
   SetIndexBuffer(4, candleColorBuffer, INDICATOR_COLOR_INDEX);

//--- буфер для iMA
   SetIndexBuffer(5, iMA_Buffer, INDICATOR_DATA);

//--- установим точность цены по количеству знаков после запятой в цене инструмента
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- зададим символ, таймфрейм, период и тип цены для расчета индикатора
   string indicatorShortName = StringFormat("SmoothedCandles(%s, Period %d, %s)", _Symbol,
                               _maPeriod, EnumToString(_maAppliedPrice));
   IndicatorSetString(INDICATOR_SHORTNAME, indicatorShortName);
//IndicatorSetString(INDICATOR_SHORTNAME, "Smoothed Candlesticks");

//--- отрисовка цены на пустое значение
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);

//--- создаем хэндл скользящей maHandle
   maHandle = iMA(_Symbol, PERIOD_CURRENT, _maPeriod, 0, MODE_SMMA, _maAppliedPrice);

//--- проверим успешность создания maHandle
   if(maHandle == INVALID_HANDLE)
     {
      //--- не удалось создать, вернем ошибку
      ResetLastError();
      PrintFormat("Failed to create maHandle of the iMA for symbol %s, error code %d",
                  _Symbol, GetLastError());
      //--- завершаем программу, выходим из функции init
      return(false);
     }

   return(true); // вернем true, если ok
  }
//+------------------------------------------------------------------+

Сохраняем и компилируем код индикатора. Он должен скомпилироваться без ошибок и предупреждений. Запустим его в MetaTrader 5 и проверим, как он работает. Попробуйте указать разные входные параметры.

Индикатор SmoothedCandlesticks - окно данных

Индикатор SmoothedCandlesticks Indicator


Заключение

В этой статье мы обсудили, что такое индикаторы, различные типы индикаторов в платформе MetaTrader 5, различные компоненты и строительные блоки пользовательских индикаторов. Также читатели получили непосредственный практический опыт разработки нескольких пользовательских индикаторов на MQL5 с нуля. 

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

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14481

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Roman Shiredchenko
Roman Shiredchenko | 18 сент. 2024 в 07:35
Спасибо Вам большое - как раз мне в тему - еще раз все актуализировать в одном месте - усвоить и перечитать.....
Как раз тут индикаторы с МТ4 на МТ5 перевожу оптимальным образом надо... 
А данные с прошлых подобных статей - тут - местами и устарели возможно из-за развития языка.
Kelvin Muturi Muigua
Kelvin Muturi Muigua | 18 сент. 2024 в 12:00
Roman Shiredchenko #:
Спасибо большое - как раз в мою тему - еще раз актуализировать все в одном месте - переварить и перечитать......
Как раз вот индикаторы с МТ4 на МТ5 перевожу оптимальным способом надо ...
А данные из предыдущих подобных статей - здесь - местами устарели, возможно, в связи с развитием языка.

Спасибо, Роман, за добрый отзыв! Я рад, что статья оказалась для вас полезной. Согласен, язык программирования MQL5 постоянно развивается, и если старые статьи могут не отражать последние обновления, то новые, такие как моя, помогают решить эту проблему. Желаю вам успехов в конвертации индикаторов из MT4 в MT5!

Построение модели ограничения тренда свечей (Часть 2): Объединение нативных индикаторов Построение модели ограничения тренда свечей (Часть 2): Объединение нативных индикаторов
В статье рассматривается использование встроенных индикаторов MetaTrader 5 для отсеивания нетрендовых сигналов. Продолжая предыдущую статью, мы рассмотрим, как это сделать с помощью кода MQL5, чтобы воплотить нашу идею в виде программы.
Нейросети в трейдинге: Изучение локальной структуры данных Нейросети в трейдинге: Изучение локальной структуры данных
Эффективное выявление и сохранение локальной структуры рыночных данных в условиях шума является важной задачей в трейдинге. Использование механизма Self-Attention показало хорошие результаты в обработке подобных данных, но классический метод не учитывают локальные особенности исходной структуры. В данной статье я предлагаю познакомиться с алгоритмом, способным учитывать эти структурные зависимости.
Торговля на разрывах справедливой стоимости (FVG)/дисбалансах шаг за шагом: Подход Smart Money Торговля на разрывах справедливой стоимости (FVG)/дисбалансах шаг за шагом: Подход Smart Money
Пошаговое руководство по созданию и реализации автоматизированного торгового алгоритма на основе разрывов справедливой стоимости (Fair Value Gap, FVG) на языке MQL5. Подробное руководство может быть полезно как новичкам, так и опытным трейдерам.
Количественный подход в управлении рисками: Применение VaR модели для оптимизации мультивалютного портфеля с Python и MetaTrader 5 Количественный подход в управлении рисками: Применение VaR модели для оптимизации мультивалютного портфеля с Python и MetaTrader 5
Эта статья раскрывает потенциал Value at Risk (VaR) модели для оптимизации мультивалютного портфеля. Используя мощь Python и функционал MetaTrader 5, мы демонстрируем, как реализовать VaR-анализ для эффективного распределения капитала и управления позициями. От теоретических основ до практической реализации, статья охватывает все аспекты применения одной из наиболее устойчивых систем расчета рисков — VaR — в алгоритмической торговле.