English Deutsch 日本語
preview
Создание пользовательских индикаторов в MQL5 (Часть 1): Построение трендового индикатора на основе пивотов с градиентной заливкой на Canvas

Создание пользовательских индикаторов в MQL5 (Часть 1): Построение трендового индикатора на основе пивотов с градиентной заливкой на Canvas

MetaTrader 5Трейдинг |
79 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В этой статье мы разработаем трендовый индикатор на основе пивотов в MetaQuotes Language 5 (MQL5), который рассчитывает быстрые/медленные пивотные линии, определяет тренды и отмечает их направленными стрелками, продлевает пивотные линии вперед на графике и для лучшей читаемости поддерживает необязательную градиентную заливку на Canvas для выделения бычьих или медвежьих областей. В этой статье мы рассмотрим следующие темы:

  1. Понимание архитектуры трендового индикатора на основе пивотов
  2. Реализация в MQL5
  3. Бэктестирование
  4. Заключение

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


Понимание архитектуры трендового индикатора на основе пивотов

Индикатор Pivot Trend Detector — это технический инструмент, который использует быстрые и медленные пивотные линии, построенные по диапазонам максимумов/минимумов за заданные периоды, чтобы определять направление тренда и потенциальные развороты. Он сглаживает ценовые данные и при этом выделяет изменения с помощью цветных линий и стрелок. Индикатор состоит из трех основных линий: медленной линии, которая служит главным ориентиром тренда (вверх или вниз в зависимости от положения цены), быстрой пунктирной линии, меняющей цвет при развороте тренда, и стрелок, отмечающих начало новых трендов при пересечении ценой обеих линий.

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

Архитектуру индикатора мы построим на четком разделении ответственности: входные параметры, буферы индикатора и графические свойства. Начнем с определения ключевых входных параметров: быстрых/медленных периодов, цветов, прозрачности, кода стрелки и продлений, которые будут задавать поведение индикатора. Затем выделим восемь буферов для хранения медленных линий вверх/вниз, быстрых линий с цветами, трендовых стрелок с цветами, а также внутренних расчетов для значений тренда и медленной линии. Эти буферы будут связаны с отображаемыми сериями, свойства которых — тип (линия/цветная линия/стрелка), цвет, ширина и сдвиг — настраиваются с помощью встроенных функций MQL5. Кроме того, мы используем класс CCanvas для градиентной заливки пространства между линиями, чтобы индикатор динамически адаптировался к рыночной волатильности. Вкратце, ниже показан пример того, что мы получим.

АРХИТЕКТУРА ИНДИКАТОРА


Реализация в MQL5

Чтобы создать индикатор в MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку Indicators (Индикаторы), нажмите кнопку "New" (Создать) и следуйте подсказкам для создания файла. После его создания в среде программирования мы определим свойства и настройки индикатора, такие как количество буферов, отображаемых серий и отдельные свойства линий, включая цвет, ширину и метку.

//+------------------------------------------------------------------+
//|                                      1. Pivot Trend Detector.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#property indicator_chart_window
#property indicator_buffers 8
#property indicator_plots 4

#property indicator_label1 "PTD slow line up"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2

#property indicator_label2 "PTD slow line down"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrCrimson
#property indicator_width2 2

#property indicator_label3 "PTD fast line"
#property indicator_type3 DRAW_COLOR_LINE
#property indicator_color3 clrDodgerBlue,clrCrimson
#property indicator_style3 STYLE_DOT

#property indicator_label4 "PTD trend start"
#property indicator_type4 DRAW_COLOR_ARROW
#property indicator_color4 clrDodgerBlue,clrCrimson
#property indicator_width4 2

Реализацию начнем с определения метаданных индикатора с помощью директив property, указав, что он рисуется в основном окне графика через "indicator_chart_window", выделив 8 буферов с помощью indicator_buffers, и определив 4 графических построения через "indicator_plots". Для первого графического построения задаем метку "PTD slow line up" и тип DRAW_LINE, цвет DodgerBlue и ширину 2. Второе графическое построение получает метку "PTD slow line down", тип линии, цвет Crimson и ширину 2. Третье графическое построение имеет метку "PTD fast line", тип цветной линии, цвета DodgerBlue и Crimson, а также пунктирный стиль. Четвертое графическое построение получает метку "PTD trend start", тип цветной стрелки, цвета DodgerBlue и Crimson, ширину 2. Эти свойства задают визуальную структуру медленных линий вверх/вниз, быстрых линий с изменяемым цветом и стрелок начала тренда. Затем определим входные параметры и глобальные переменные для использования в программе.

#include <Canvas/Canvas.mqh>
//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas obj_Canvas;                                               //--- Canvas object
//--- input parameters
input int    fastPeriod       = 5;                                // Fast period
input int    slowPeriod       = 10;                               // Slow period
input color  upColor          = clrDodgerBlue;                    // Up trend color
input color  downColor        = clrCrimson;                       // Down trend color
input int    fillOpacity      = 128;                              // Fill opacity (0-255)
input int    arrowCode        = 77;                               // Arrow code for trend start
input bool   showExtensions   = true;                             // Show line extensions
input bool   enableFilling    = true;                             // Enable canvas fill (disable for speed)
input int    extendBars       = 1;                                // Extension bars to protrude lines/fill

//--- indicator buffers
double slowLineUpBuffer[],slowLineDownBuffer[],slowLineBuffer[],fastLineBuffer[],fastLineColorBuffer[],trendArrowColorBuffer[],trendArrowBuffer[],trendBuffer[]; //--- Indicator buffers

//--- chart properties
int    currentChartWidth = 0;                                     //--- Current chart width
int    currentChartHeight = 0;                                    //--- Current chart height
int    currentChartScale = 0;                                     //--- Current chart scale
int    firstVisibleBarIndex = 0;                                  //--- First visible bar index
int    visibleBarsCount = 0;                                      //--- Visible bars count
double minPrice = 0.0;                                            //--- Minimum price
double maxPrice = 0.0;                                            //--- Maximum price

//--- optimization flags
static datetime lastRedrawTime = 0;                               //--- Last redraw time
static double   previousTrend = -1;                               //--- Previous trend
string objectPrefix = "PTD_";                                     //--- Object prefix

Здесь мы подключаем библиотеку Canvas с помощью "#include <Canvas/Canvas.mqh>", чтобы задействовать пользовательскую графическую отрисовку, например градиентную заливку между линиями индикатора для более наглядной визуализации. Затем объявляем "obj_Canvas" как глобальный экземпляр класса CCanvas для управления объектом Canvas при заливке областей. Мы задаем входные параметры для настройки: "fastPeriod" со значением по умолчанию 5 для окна расчета быстрого пивота, "slowPeriod" со значением 10 для медленного, "upColor" как DodgerBlue для восходящих трендов, "downColor" как Crimson для нисходящих, "fillOpacity" со значением 128 (полупрозрачность) для заливки области в диапазоне 0-255, "arrowCode" со значением 77 для символов начала тренда из Wingdings, "showExtensions" со значением true для продления линий за текущий бар, "enableFilling" со значением true для включения заливки Canvas (можно отключить ради производительности), "extendBars" со значением 1 как количество баров для продления. Код стрелки можно изменить и использовать любой из определенных в MQL5 символов Wingdings как показано ниже.

WINGDINGS В MQL5

Затем выделяем восемь глобальных массивов как буферы индикатора: "slowLineUpBuffer" и "slowLineDownBuffer" для отдельных медленных линий вверх/вниз, "slowLineBuffer" для внутренних расчетов медленной линии, "fastLineBuffer" для быстрой линии, "fastLineColorBuffer" для ее цветов, "trendArrowColorBuffer" и "trendArrowBuffer" для позиций и цветов стрелок, а также "trendBuffer" для состояний тренда. Мы задаем глобальные переменные для свойств графика: "currentChartWidth"/"Height" со значением 0 для начального размера, "currentChartScale" со значением 0, "firstVisibleBarIndex" со значением 0 для крайнего левого бара, "visibleBarsCount" со значением 0, а также "minPrice" и "maxPrice" со значением 0.0 для диапазона. Для оптимизации мы используем статическую переменную "lastRedrawTime" со значением 0 для ограничения частоты перерисовок, статическую "previousTrend" со значением -1 для обнаружения изменений и "objectPrefix" со значением "PTD_" для именования продлений. После компиляции получаем следующее окно входных параметров.

ОКНО ВХОДНЫХ ПАРАМЕТРОВ

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

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit() {
// Set chart properties
   currentChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);      //--- Get chart width
   currentChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);    //--- Get chart height
   currentChartScale = (int)ChartGetInteger(0, CHART_SCALE);                //--- Get chart scale
   firstVisibleBarIndex = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get first visible bar
   visibleBarsCount = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);          //--- Get visible bars
   minPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);                        //--- Get min price
   maxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);                        //--- Get max price
   
// Indicator buffers
   SetIndexBuffer(0,slowLineUpBuffer,INDICATOR_DATA);             //--- Set slow up buffer
   SetIndexBuffer(1,slowLineDownBuffer,INDICATOR_DATA);           //--- Set slow down buffer
   SetIndexBuffer(2,fastLineBuffer,INDICATOR_DATA);               //--- Set fast buffer
   SetIndexBuffer(3,fastLineColorBuffer,INDICATOR_COLOR_INDEX);   //--- Set fast color buffer
   SetIndexBuffer(4,trendArrowBuffer,INDICATOR_DATA);             //--- Set arrow buffer
   SetIndexBuffer(5,trendArrowColorBuffer,INDICATOR_COLOR_INDEX); //--- Set arrow color buffer
   SetIndexBuffer(6,trendBuffer,INDICATOR_CALCULATIONS);          //--- Set trend buffer
   SetIndexBuffer(7,slowLineBuffer,INDICATOR_CALCULATIONS);       //--- Set slow buffer
   
// Plot settings
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,slowPeriod);             //--- Set slow draw begin
   PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,slowPeriod);             //--- Set slow draw begin
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,fastPeriod);             //--- Set fast draw begin
   PlotIndexSetInteger(3,PLOT_DRAW_BEGIN,fastPeriod);             //--- Set fast draw begin
   PlotIndexSetInteger(4,PLOT_DRAW_BEGIN,slowPeriod);             //--- Set arrow draw begin
   PlotIndexSetInteger(3,PLOT_ARROW,arrowCode);                   //--- Set arrow code
   
// Line extensions
   PlotIndexSetInteger(0,PLOT_SHIFT,extendBars);                  //--- Set slow up shift
   PlotIndexSetInteger(1,PLOT_SHIFT,extendBars);                  //--- Set slow down shift
   PlotIndexSetInteger(2,PLOT_SHIFT,extendBars);                  //--- Set fast shift
   PlotIndexSetInteger(3,PLOT_SHIFT,0);                           //--- Set arrow shift
   
// Set plot colors dynamically
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, upColor);           //--- Set slow up color
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, downColor);         //--- Set slow down color
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, upColor);           //--- Set fast up color
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, downColor);         //--- Set fast down color
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, upColor);           //--- Set arrow up color
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 1, downColor);         //--- Set arrow down color
   
// Short name
   string shortName = "PTD(" + IntegerToString(fastPeriod) + "," + IntegerToString(slowPeriod) + ")"; //--- Set short name
   IndicatorSetString(INDICATOR_SHORTNAME, shortName);            //--- Set indicator short name

   return(INIT_SUCCEEDED);                                        //--- Return success
}

В обработчике OnInit, который выполняется при добавлении индикатора на график или его перезагрузке, сначала получаем и сохраняем текущие размеры графика и параметры отображения, поскольку они понадобятся позже при отрисовке на Canvas: ширину в пикселях получаем через ChartGetInteger с использованием CHART_WIDTH_IN_PIXELS и записываем в "currentChartWidth", высоту через CHART_HEIGHT_IN_PIXELS и записываем в "currentChartHeight", масштаб через CHART_SCALE и записываем в "currentChartScale", первый видимый бар через CHART_FIRST_VISIBLE_BAR и записываем в "firstVisibleBarIndex", количество видимых баров через CHART_VISIBLE_BARS и записываем в "visibleBarsCount", минимальную цену через ChartGetDouble и "CHART_PRICE_MIN" записываем в "minPrice", а максимум через CHART_PRICE_MAX записываем в "maxPrice". Эти значения позволят адаптивно выполнять отрисовку в зависимости от текущего вида графика.

Затем связываем восемь буферов с отображаемыми сериями: "slowLineUpBuffer" назначаем индексу 0 как данные, "slowLineDownBuffer" — индексу 1 как данные, "fastLineBuffer" — индексу 2 как данные, "fastLineColorBuffer" — индексу 3 как индекс цвета, "trendArrowBuffer" — индексу 4 как данные, "trendArrowColorBuffer" — индексу 5 как индекс цвета, "trendBuffer" — индексу 6 как расчеты, "slowLineBuffer" — индексу 7 как расчеты, используя SetIndexBuffer с соответствующими типами. Начало отрисовки графических построений настраиваем с помощью PlotIndexSetInteger и PLOT_DRAW_BEGIN: медленные серии — от "slowPeriod", быстрые и стрелочные — от "fastPeriod" или "slowPeriod". Символ стрелочной серии задаем через PLOT_ARROW и устанавливаем его равным "arrowCode". Для продлений применяем сдвиги через "PLOT_SHIFT": extendBars для медленных линий вверх/вниз и быстрой линии, 0 для стрелок. Цвета отображаемых серий задаем динамически с помощью "PlotIndexSetInteger" и "PLOT_LINE_COLOR": индекс 0 — "upColor", индекс 1 — "downColor", для быстрой линии с индексом 2 — "upColor" в позиции 0 и "downColor" в позиции 1, аналогично для стрелок с индексом 4. Создаем строку короткого имени как "PTD(" плюс быстрый и медленный периоды через запятую плюс ")" и устанавливаем ее с помощью IndicatorSetString и INDICATOR_SHORTNAME. Возвращаем INIT_SUCCEEDED для подтверждения успешной инициализации. После компиляции получаем следующий результат.

ИНИЦИАЛИЗАЦИЯ ИНДИКАТОРА

На изображении видно, что индикатор корректно настраивается при загрузке. В окне данных отображаются буферы, и теперь нужно заполнить их, а также выполнить расчеты индикатора, чтобы получить значения по нашей стратегии. Сделаем это в обработчике 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[]) {
// Always calculate buffers
   int startBar = prev_calculated - 1;                            //--- Set start bar
   if(startBar < 0) startBar = 0;                                 //--- Adjust start bar
   for(int barIndex = startBar; barIndex < rates_total && !_StopFlag; barIndex++) {
      int fastStartBar = barIndex - fastPeriod + 1;               //--- Calc fast start
      if(fastStartBar < 0) fastStartBar = 0;                      //--- Adjust fast start
      int slowStartBar = barIndex - slowPeriod + 1;               //--- Calc slow start
      if(slowStartBar < 0) slowStartBar = 0;                      //--- Adjust slow start
      double slowHigh = high[ArrayMaximum(high, slowStartBar, slowPeriod)]; //--- Get slow high
      double slowLow = low[ArrayMinimum(low, slowStartBar, slowPeriod)];    //--- Get slow low
      double fastHigh = high[ArrayMaximum(high, fastStartBar, fastPeriod)]; //--- Get fast high
      double fastLow = low[ArrayMinimum(low, fastStartBar, fastPeriod)];    //--- Get fast low
      if(barIndex > 0) {
         slowLineBuffer[barIndex] = (close[barIndex] > slowLineBuffer[barIndex-1]) ? slowLow : slowHigh; //--- Set slow line
         fastLineBuffer[barIndex] = (close[barIndex] > fastLineBuffer[barIndex-1]) ? fastLow : fastHigh; //--- Set fast line
         trendBuffer[barIndex] = trendBuffer[barIndex-1];          //--- Set trend
         if(close[barIndex] < slowLineBuffer[barIndex] && close[barIndex] < fastLineBuffer[barIndex]) trendBuffer[barIndex] = 1; //--- Set up trend
         if(close[barIndex] > slowLineBuffer[barIndex] && close[barIndex] > fastLineBuffer[barIndex]) trendBuffer[barIndex] = 0; //--- Set down trend
         trendArrowBuffer[barIndex] = (trendBuffer[barIndex] != trendBuffer[barIndex-1]) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set arrow
         slowLineUpBuffer[barIndex] = (trendBuffer[barIndex] == 0) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow up
         slowLineDownBuffer[barIndex] = (trendBuffer[barIndex] == 1) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow down
      } else {
         trendArrowBuffer[barIndex] = slowLineUpBuffer[barIndex] = slowLineDownBuffer[barIndex] = EMPTY_VALUE; //--- Set empties
         trendBuffer[barIndex] = fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = 0; //--- Set zeros
         fastLineBuffer[barIndex] = slowLineBuffer[barIndex] = close[barIndex]; //--- Set first lines
      }
      fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = trendBuffer[barIndex]; //--- Set colors
   }
   
   return(rates_total);                                           //--- Return total rates
}

В обработчике OnCalculate, который вызывается на каждом новом тике или баре, мы обновляем буферы индикатора свежими ценовыми данными, чтобы графические построения отражали текущие рыночные условия. Начальный бар для расчетов определяем как "prev_calculated - 1" и корректируем его до 0, если значение отрицательное, чтобы избежать недопустимых индексов. Затем выполняем цикл от "startBar" до "rates_total - 1", пока выполнение не остановлено: для каждого "barIndex" рассчитываем начало быстрого периода как "barIndex - fastPeriod + 1" (ограничиваем снизу нулем), а медленного — как "barIndex - slowPeriod + 1" (также ограничиваем нулем). Медленный максимум находим как максимальное значение High за медленный период с помощью ArrayMaximum по массиву high от "slowStartBar", медленный минимум — как минимальное значение Low с помощью ArrayMinimum, быстрый максимум — как максимальное значение High за быстрый период, быстрый минимум — как минимальное значение Low.

Для "barIndex > 0" мы записываем в "slowLineBuffer[barIndex]" медленный минимум, если close находится выше предыдущей медленной линии, иначе — медленный максимум; в "fastLineBuffer[barIndex]" записываем быстрый минимум, если close находится выше предыдущей быстрой линии, иначе — быстрый максимум. Предыдущее состояние тренда копируем в "trendBuffer[barIndex]", затем обновляем его: при close ниже обеих линий переменной trend присваивается 1, а при close выше обеих — 0; далее эти числовые состояния используются в коде для выбора цвета и отображаемой линии. В "trendArrowBuffer[barIndex]" помещаем стрелку на уровне медленной линии, если тренд изменился относительно предыдущего значения, иначе оставляем пустое значение. В "slowLineUpBuffer[barIndex]" записываем медленную линию, если trend равен 0, иначе пустое значение; в "slowLineDownBuffer[barIndex]" — медленную линию, если trend равен 1, иначе пустое значение. Для первого бара ("barIndex == 0") задаем стрелки и медленные линии вверх/вниз как пустые значения, trend/цвет быстрой линии/цвет стрелки — как 0, а быстрые/медленные линии — как close[0] для инициализации. Значения "fastLineColorBuffer[barIndex]" и "trendArrowColorBuffer[barIndex]" устанавливаем равными значению тренда для индексирования цвета. Возвращаем "rates_total", чтобы указать, что обработаны все бары. Теперь после компиляции получаем следующий результат.

РАССЧИТАННЫЙ ИНДИКАТОР

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

//+------------------------------------------------------------------+
//| Draw right price extension line/label                            |
//+------------------------------------------------------------------+
bool drawRightPrice(string objectName, datetime lineTime, double linePrice, color lineColor, ENUM_LINE_STYLE lineStyle = STYLE_SOLID) {
   bool objectExists = (ObjectFind(0, objectName) >= 0);          //--- Check exists
   if(!objectExists) {
      if(!ObjectCreate(0, objectName, OBJ_ARROW_RIGHT_PRICE, 0, lineTime, linePrice)) {
         Print("Failed to create ", objectName);                  //--- Log failure
         return false;                                            //--- Return failure
      }
   } else {
      ObjectSetInteger(0, objectName, OBJPROP_TIME, 0, lineTime);  //--- Set time
      ObjectSetDouble(0, objectName, OBJPROP_PRICE, 0, linePrice); //--- Set price
   }
   long currentScale = ChartGetInteger(0, CHART_SCALE);           //--- Get scale
   int lineWidth = 1;                                             //--- Init width
   if(currentScale <= 1) lineWidth = 1;                           //--- Set width small
   else if(currentScale <= 3) lineWidth = 2;                      //--- Set width medium
   else lineWidth = 3;                                            //--- Set width large
   ObjectSetInteger(0, objectName, OBJPROP_COLOR, lineColor);     //--- Set color
   ObjectSetInteger(0, objectName, OBJPROP_WIDTH, lineWidth);     //--- Set width
   ObjectSetInteger(0, objectName, OBJPROP_STYLE, lineStyle);     //--- Set style
   ObjectSetInteger(0, objectName, OBJPROP_BACK, false);          //--- Set foreground
   ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false);    //--- Set not selectable
   ObjectSetInteger(0, objectName, OBJPROP_SELECTED, false);      //--- Set not selected
   ChartRedraw(0);                                                //--- Redraw chart
   return true;                                                   //--- Return success
}


// we then call this function in the "OnCalculate" event handler

// Draw line extensions if enabled
   if(showExtensions && rates_total > 0) {
      int latestBarIndex = rates_total - 1;                       //--- Get latest index
      double slowLineValue = slowLineBuffer[latestBarIndex];      //--- Get slow value
      double fastLineValue = fastLineBuffer[latestBarIndex];      //--- Get fast value
      double currentTrend = trendBuffer[latestBarIndex];          //--- Get trend
      color lineColor = (currentTrend == 0.0) ? upColor : downColor; //--- Set line color
      datetime currentBarTime = iTime(_Symbol, _Period, 0);       //--- Get current time
      long timeOffset = (long)extendBars * PeriodSeconds(_Period); //--- Calc offset
      datetime extensionTime = currentBarTime + (datetime)timeOffset; //--- Calc extension time
      drawRightPrice(objectPrefix + "SLOW", extensionTime, slowLineValue, lineColor, STYLE_SOLID); //--- Draw slow extension
      drawRightPrice(objectPrefix + "FAST", extensionTime, fastLineValue, lineColor, STYLE_DOT); //--- Draw fast extension
   }

Для логики отрисовки цены справа определяем функцию "drawRightPrice", которая создает или обновляет объект правой ценовой отметки (OBJ_ARROW_RIGHT_PRICE). Он визуально показывает цену справа от линии; благодаря этому линии индикатора продлеваются горизонтально вправо и выводятся на будущие бары в соответствии с входными настройками. Сначала проверяем наличие объекта с помощью ObjectFind — если его нет, создаем OBJ_ARROW_RIGHT_PRICE в заданных "lineTime" и "linePrice" с помощью ObjectCreate, при неудаче записываем сообщение в журнал и возвращаем false. Если объект существует, обновляем его временную и ценовую привязки с помощью ObjectSetInteger для OBJPROP_TIME и ObjectSetDouble для "OBJPROP_PRICE". Текущий масштаб графика получаем с помощью ChartGetInteger и CHART_SCALE и записываем в "currentScale", затем задаем "lineWidth" в зависимости от масштаба: 1 для scale <=1, 2 для <=3, 3 для большего масштаба, чтобы обеспечить видимость при разных уровнях приближения.

Настраиваем объект: задаем цвет с помощью OBJPROP_COLOR равным "lineColor", ширину — "lineWidth", стиль — "lineStyle" (по умолчанию сплошной), передний план через "OBJPROP_BACK" false, а также запрет выделения и выбора через "OBJPROP_SELECTABLE" и "OBJPROP_SELECTED" false. Перерисовываем график с помощью ChartRedraw и при успешном выполнении возвращаем true. Эту функцию вызываем в обработчике OnCalculate если "showExtensions" равно true и бары существуют: получаем последний индекс как "rates_total - 1", извлекаем медленное и быстрое значения из буферов, тренд из "trendBuffer", выбираем "lineColor" как "upColor", если trend равен 0.0, иначе "downColor", получаем время текущего бара с помощью iTime со сдвигом 0, рассчитываем смещение как "extendBars * PeriodSeconds(_Period)", время продления — как текущее время плюс смещение, затем вызываем "drawRightPrice" для медленной линии со сплошным стилем и для быстрой линии с пунктирным стилем, используя "objectPrefix + "SLOW"" или "FAST". После компиляции получаем следующий результат.

ИНДИКАТОР С ОТРИСОВКОЙ ПРАВОЙ ЦЕНЫ

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

//+------------------------------------------------------------------+
//| Convert chart scale to bar width                                 |
//+------------------------------------------------------------------+
int BarWidth(int chartScale) {
   return (int)MathPow(2.0, chartScale);                          //--- Return bar width
}

//+------------------------------------------------------------------+
//| Convert bar shift to x pixel                                     |
//+------------------------------------------------------------------+
int ShiftToX(int barShift) {
   return (int)((firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1); //--- Return x pixel
}

//+------------------------------------------------------------------+
//| Convert price to y pixel                                         |
//+------------------------------------------------------------------+
int PriceToY(double price) {
   if(maxPrice - minPrice == 0.0) return 0;                      //--- Return zero if no range
   return (int)MathRound(currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1); //--- Return y pixel
}

Сначала определяем функцию "BarWidth" для расчета ширины каждого бара в пикселях на основе текущего масштаба графика. Она возвращает целое число из "MathPow(2.0, chartScale)" — это дает экспоненциальную оценку (1 при масштабе 0, 2 при 1, 4 при 2 и т. д.) для позиционирования в координатах Canvas. Затем реализуем функцию "ShiftToX", которая преобразует сдвиг бара (относительно крайнего левого видимого бара) в x-координату на графике в пикселях, вычисляя "(firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1" с приведением к int. Это размещает элементы справа (более свежие) налево (более старые) с корректировкой на 1 для выравнивания. Наконец, создаем функцию "PriceToY", которая сопоставляет цену с y-координатой Canvas в пикселях: возвращает 0, если ценовой диапазон отсутствует ("maxPrice - minPrice == 0.0"), иначе округляет "currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1" с помощью MathRound с приведением к int. Это инвертирует ось Y (более высокие цены находятся сверху) и корректирует значение на 1 для точной отрисовки. Теперь используем эти функции, чтобы создать основную функцию, выполняющую основную работу.

//+-----------------------------------------------------------------------------------------+
//| Fill area between two lines using trend for color with gradient alpha from slow to fast |
//+-----------------------------------------------------------------------------------------+
void DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0) {
   int firstVisibleBar = firstVisibleBarIndex;                    //--- Get first visible
   int totalBarsToDraw = visibleBarsCount + extendShift;          //--- Calc bars to draw
   int bufferSize = (int)ArraySize(slowLineValues);               //--- Get buffer size
   if(bufferSize == 0 || bufferSize != ArraySize(fastLineValues) || bufferSize != ArraySize(trendValues)) return; //--- Return if invalid
   int previousX = -1;                                            //--- Init previous X
   int previousY1 = -1;                                           //--- Init previous Y1
   int previousY2 = -1;                                           //--- Init previous Y2
   for(int offset = 0; offset < totalBarsToDraw; offset++) {
      int barPosition = firstVisibleBar - offset;                 //--- Calc bar position
      int x = ShiftToX(barPosition);                              //--- Calc x
      if(x >= currentChartWidth) break;                           //--- Break if beyond width
      int dataBarShift = firstVisibleBar - offset + extendShift;  //--- Calc data shift
      int bufferBarIndex = bufferSize - 1 - dataBarShift;         //--- Calc buffer index
      if(bufferBarIndex < 0 || bufferBarIndex >= bufferSize) {
         previousX = -1;                                          //--- Reset previous X
         continue;                                                //--- Continue
      }
      double value1 = slowLineValues[bufferBarIndex];             //--- Get value1
      double value2 = fastLineValues[bufferBarIndex];             //--- Get value2
      if(value1 == EMPTY_VALUE || value2 == EMPTY_VALUE) {
         previousX = -1;                                          //--- Reset previous X
         continue;                                                //--- Continue
      }
      int y1 = PriceToY(value1);                                  //--- Calc y1
      int y2 = PriceToY(value2);                                  //--- Calc y2
      double currentTrend = trendValues[bufferBarIndex];          //--- Get trend
      uint baseColorRGB = (currentTrend == 0.0) ? (ColorToARGB(fillUpColor, 255) & 0x00FFFFFF) : (ColorToARGB(fillDownColor, 255) & 0x00FFFFFF); //--- Set base RGB
      if(previousX != -1 && x > previousX) {
         double deltaX = x - previousX;                           //--- Calc delta X
         int endColumn = MathMin(x, currentChartWidth - 1);       //--- Calc end column
         double maxT = (double)(endColumn - previousX) / deltaX;  //--- Calc max T
         for(int column = previousX; column <= endColumn; column++) {
            double t = (column - previousX) / deltaX;             //--- Calc t
            double interpolatedY1 = previousY1 + t * (y1 - previousY1); //--- Interpolate Y1
            double interpolatedY2 = previousY2 + t * (y2 - previousY2); //--- Interpolate Y2
            int upperY = (int)MathRound(MathMin(interpolatedY1, interpolatedY2)); //--- Calc upper Y
            int lowerY = (int)MathRound(MathMax(interpolatedY1, interpolatedY2)); //--- Calc lower Y
            if(upperY > lowerY) continue;                        //--- Continue if invalid
            double slowLineY = interpolatedY1;                    //--- Set slow Y
            double height = MathAbs(interpolatedY1 - interpolatedY2); //--- Calc height
            if(height == 0.0) continue;                          //--- Continue if no height
            // Fill per row with gradient from slow (opaque) to fast (transparent)
            for(int row = upperY; row <= lowerY; row++) {
               double distanceFromSlow = MathAbs(row - slowLineY); //--- Calc distance
               double gradientFraction = distanceFromSlow / height; //--- Calc fraction
               uchar alphaValue = (uchar)(fillAlpha * (1.0 - gradientFraction)); //--- Calc alpha
               if(alphaValue > fillAlpha) alphaValue = fillAlpha; //--- Cap alpha
               uint pixelColor = ((uint)alphaValue << 24) | baseColorRGB; //--- Set pixel color
               obj_Canvas.FillRectangle(column, row, column, row, pixelColor); //--- Fill pixel
            }
         }
      }
      previousX = x;                                              //--- Update previous X
      previousY1 = y1;                                            //--- Update previous Y1
      previousY2 = y2;                                            //--- Update previous Y2
   }
}

//+------------------------------------------------------------------+
//| Redraw the canvas                                                |
//+------------------------------------------------------------------+
void Redraw(void) {
   if(currentChartWidth <= 0 || currentChartHeight <= 0) return;  //--- Return if invalid size
   uint defaultColor = 0;                                         //--- Default color
   obj_Canvas.Erase(defaultColor);                                //--- Erase canvas
   DrawFilling(slowLineBuffer, fastLineBuffer, trendBuffer, upColor, downColor, (uchar)fillOpacity, extendBars); //--- Draw filling
   obj_Canvas.Update();                                           //--- Update canvas
}

Определяем функцию "DrawFilling" для отрисовки области между медленной и быстрой линиями на Canvas с градиентной заливкой. Тренд используется для выбора цвета восходящего или нисходящего движения, а прозрачность плавно уменьшается от медленной линии (полное значение "fillAlpha") к быстрой линии (прозрачно), создавая плавное визуальное затухание с продлением на "extendShift" баров, если оно включено. Сначала получаем первый видимый бар и рассчитываем "totalBarsToDraw" как количество видимых баров плюс "extendShift", затем получаем размер буфера из "slowLineValues" и досрочно выходим, если данные недействительны или не совпадают с быстрым/трендовым буферами. Инициализируем предыдущие X/Y1/Y2 значением -1 для отслеживания интерполяции, затем выполняем цикл по смещениям от 0 до "totalBarsToDraw - 1": для каждого смещения вычисляем позицию бара как "firstVisibleBar - offset", x-пиксель через "ShiftToX" и прерываем цикл, если значение выходит за ширину графика; сдвиг данных как видимый бар минус смещение плюс "extendShift", индекс буфера как размер минус 1 минус сдвиг данных — пропускаем значения вне диапазона или пустые значения, сбрасывая предыдущий X.

Затем получаем медленное value1 и быстрое value2 из буферов, преобразуем их в y1/y2 через "PriceToY", определяем тренд из "trendValues" и задаем базовый RGB из "fillUpColor" или "fillDownColor" с помощью ColorToARGB с маской до RGB. Если предыдущий X действителен и текущий X больше предыдущего, выполняем интерполяцию: рассчитываем delta X, конечный столбец как минимум из X и ширины минус 1, максимальный t как (end - previous) / delta. Для каждого столбца от предыдущего до конечного вычисляем t как (column - previous) / delta, интерполируем y1 и y2, округляем min/max до верхнего/нижнего Y и пропускаем, если upper > lower. Устанавливаем "slowLineY" равным интерполированному y1, высоту — как abs y1 минус y2, пропуская нулевую высоту. Для каждой строки от upper до lower рассчитываем расстояние от медленной линии, долю как distance / height, alpha как "fillAlpha * (1.0 - fraction)" с приведением к uchar, ограничиваем alpha значением "fillAlpha", объединяем цвет пикселя как alpha, сдвинутую на 24 бита, с OR по базовому RGB, и заполняем один пиксель в столбце/строке с помощью "obj_Canvas.FillRectangle" (1x1). Обновляем previous X до текущего X, Y1 до Y1 и Y2 до Y2 для следующей итерации.

Реализуем функцию "Redraw", чтобы при необходимости обновлять отрисовку Canvas; если ширина или высота недействительны (<=0), выходим досрочно. Устанавливаем цвет по умолчанию равным 0 (прозрачный), очищаем Canvas через "obj_Canvas.Erase", вызываем "DrawFilling" и передаем медленный/быстрый/трендовый буферы, цвета вверх/вниз, "fillOpacity" с приведением к uchar и "extendBars", затем обновляем отображение Canvas через "obj_Canvas.Update". Именно эту функцию будем вызывать, когда нужно отрисовать градиентную заливку области между линиями индикатора, как показано ниже в обработчике расчета.

if(!enableFilling) return(rates_total);                       //--- Return if no filling

// Canvas logic only if enabled
bool isNewBar = (rates_total > prev_calculated);               //--- Check new bar
bool hasTrendChanged = false;                                  //--- Init trend changed
if(rates_total > 0 && trendBuffer[rates_total-1] != previousTrend) {
   hasTrendChanged = true;                                     //--- Set changed
   previousTrend = trendBuffer[rates_total-1];                 //--- Update previous trend
}

// Update chart properties (only if changed)
bool hasChartChanged = false;                                  //--- Init chart changed
int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width
int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height
int newChartScale = (int)ChartGetInteger(0, CHART_SCALE);     //--- Get new scale
int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first visible
int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars
double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);    //--- Get new min price
double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);    //--- Get new max price
if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) {
   obj_Canvas.Resize(newChartWidth, newChartHeight);               //--- Resize canvas
   currentChartWidth = newChartWidth;                          //--- Update width
   currentChartHeight = newChartHeight;                        //--- Update height
   hasChartChanged = true;                                     //--- Set changed
}
if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
      newMinPrice != minPrice || newMaxPrice != maxPrice) {
   currentChartScale = newChartScale;                          //--- Update scale
   firstVisibleBarIndex = newFirstVisibleBar;                  //--- Update first visible
   visibleBarsCount = newVisibleBars;                          //--- Update visible bars
   minPrice = newMinPrice;                                     //--- Update min price
   maxPrice = newMaxPrice;                                     //--- Update max price
   hasChartChanged = true;                                     //--- Set changed
}

// Redraw only on: new bar, trend change, or chart resize/scroll. Debounce to 1x/sec max.
datetime currentTime = TimeCurrent();                          //--- Get current time
if((isNewBar || hasTrendChanged || hasChartChanged) && (currentTime - lastRedrawTime >= 1)) {
   Redraw();                                                   //--- Redraw canvas
   lastRedrawTime = currentTime;                               //--- Update last redraw
}

Здесь мы досрочно возвращаем "rates_total", если "enableFilling" равно false, пропуская логику Canvas для повышения производительности при отключенной заливке. Затем выполняем операции, связанные с Canvas, только если заливка включена: проверяем появление нового бара через "rates_total > prev_calculated" и записываем результат в "isNewBar", обнаруживаем изменения тренда, сравнивая "trendBuffer[rates_total-1]" с "previousTrend", если бары существуют, устанавливаем "hasTrendChanged" в true и обновляем "previousTrend", если значение отличается. Отслеживаем изменения графика: инициализируем "hasChartChanged" как false, получаем новые ширину/высоту/масштаб/первый видимый бар/количество видимых баров/min price/max price с помощью функций ChartGetInteger и ChartGetDouble. Если ширина или высота изменились, изменяем размер Canvas через "obj_Canvas.Resize" до новых размеров, обновляем "currentChartWidth" и "currentChartHeight" и устанавливаем "hasChartChanged" в true. Если изменились масштаб, первый видимый бар, количество видимых баров, минимальная или максимальная цена, соответствующим образом обновляем глобальные переменные и устанавливаем "hasChartChanged" в true.

Наконец, оптимизируем перерисовки: получаем текущее время с помощью TimeCurrent и записываем в "currentTime". Если появился новый бар, изменился тренд или изменился график, а с момента "lastRedrawTime" прошла как минимум 1 секунда, вызываем "Redraw" для обновления Canvas и устанавливаем "lastRedrawTime" равным текущему времени. Это ограничивает частоту перерисовки максимум одним разом в секунду и снижает лишние вычисления. Теперь остается только перерисовывать изменения при обнаружении событий графика и удалять их при деинициализации, как показано ниже.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) {
   if(id != CHARTEVENT_CHART_CHANGE || !enableFilling) return;
   int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width
   int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height
   if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) {
      obj_Canvas.Resize(newChartWidth, newChartHeight);               //--- Resize canvas
      currentChartWidth = newChartWidth;                          //--- Update width
      currentChartHeight = newChartHeight;                        //--- Update height
      Redraw();                                                   //--- Redraw canvas
      return;                                                     //--- Return
   }
   int newChartScale = (int)ChartGetInteger(0, CHART_SCALE);      //--- Get new scale
   int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first visible
   int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars
   double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);    //--- Get new min price
   double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);    //--- Get new max price
   if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
         newMinPrice != minPrice || newMaxPrice != maxPrice) {
      currentChartScale = newChartScale;                          //--- Update scale
      firstVisibleBarIndex = newFirstVisibleBar;                  //--- Update first visible
      visibleBarsCount = newVisibleBars;                          //--- Update visible bars
      minPrice = newMinPrice;                                     //--- Update min price
      maxPrice = newMaxPrice;                                     //--- Update max price
      Redraw();                                                   //--- Redraw canvas
   }
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if(enableFilling) obj_Canvas.Destroy();                            //--- Destroy canvas if enabled
   ObjectsDeleteAll(0,objectPrefix,0,OBJ_ARROW_RIGHT_PRICE);      //--- Delete right price arrows
   ChartRedraw(0);                                                //--- Redraw chart
}

Здесь мы вызываем обработчик OnChartEvent для обработки событий, связанных с графиком. В частности, реагируем на изменения только если заливка включена, то есть "enableFilling" равно true; в противном случае выходим досрочно. Сначала получаем новую ширину и высоту графика с помощью ChartGetInteger с использованием CHART_WIDTH_IN_PIXELS и CHART_HEIGHT_IN_PIXELS. Если какое-либо из этих значений отличается от "currentChartWidth" или "currentChartHeight", изменяем размер Canvas до новых размеров через "obj_Canvas.Resize", обновляем глобальные переменные, вызываем "Redraw" для обновления заливки и выходим. Затем получаем новый масштаб через CHART_SCALE, первый видимый бар через "CHART_FIRST_VISIBLE_BAR", видимые бары через "CHART_VISIBLE_BARS", минимальную цену через ChartGetDouble и "CHART_PRICE_MIN", максимум через CHART_PRICE_MAX. Если масштаб, первый видимый бар, количество видимых баров, минимальная или максимальная цена отличаются от сохраненных глобальных значений, обновляем "currentChartScale", "firstVisibleBarIndex", "visibleBarsCount", "minPrice", "maxPrice" и вызываем "Redraw", чтобы адаптировать заливку Canvas к новому виду графика.

В обработчике OnDeinit, который выполняется при удалении индикатора или закрытии терминала для очистки ресурсов: если "enableFilling" равно true, уничтожаем Canvas через "obj_Canvas.Destroy"; удаляем все объекты правых ценовых меток, начинающиеся с "objectPrefix", с помощью ObjectsDeleteAll, указывая график 0, окно 0 и тип OBJ_ARROW_RIGHT_PRICE; затем перерисовываем график с помощью функции ChartRedraw После компиляции получаем следующий результат.

ТЕСТОВЫЙ GIF ИНДИКАТОРА PIVOT TREND DETECTOR

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


Бэктестирование

Мы провели тестирование, и ниже показана итоговая визуализация в виде единого растрового изображения формата Graphics Interchange Format (GIF).

GIF БЭКТЕСТИРОВАНИЯ


Заключение

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

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

Прикрепленные файлы |
Реализация модели гауссовской смеси в MQL5 Реализация модели гауссовской смеси в MQL5
В статье рассматриваются теоретические основы и практическая реализация модели смеси нормальных распределений (Gaussian Mixture Model) на языке MQL5. Представлен класс CGMM и примеры его использования, а также методология выбора оптимального количества компонентов модели с помощью информационных критериев AIC/BIC. Значительное внимание уделяется принципу работы EM-алгоритма — признанного стандарта при обучении вероятностных моделей со скрытыми(латентными) переменными. Реализована поддержка режима обучения MAP и инициализация параметров с помощью K-Means++. Готовый инструмент для кластеризации, оценки многомерной плотности и генерации данных теперь доступен и в MetaTrader 5.
Моделирование рынка: Position View (VII) Моделирование рынка: Position View (VII)
В данной статье мы начнем вносить некоторые улучшения в индикатор позиции, чтобы иметь возможность взаимодействовать с ним и изменять ценовые линии или закрывать позицию напрямую через взаимодействие с индикатором позиции. Прежде чем мы перейдем к реализации, стоит кое-что уточнить, особенно для тех, кто об этом не знает. Индикатор никак не может использоваться для изменения чего-либо на торговом сервере. Это связано с тем, что в MetaTrader 5 действует система безопасности, которая позволяет воздействовать на ордера и позиции только советникам. Никакое другое приложение, кроме советника, не сможет манипулировать ордерами или позициями.
Создание пользовательских индикаторов в MQL5 (Часть 2): Разработка индикатора RSI в виде шкалы со стрелкой на Canvas Создание пользовательских индикаторов в MQL5 (Часть 2): Разработка индикатора RSI в виде шкалы со стрелкой на Canvas
В этой статье мы разработаем индикатор RSI в формате круговой шкалы на MQL5, использующий Canvas и механику стрелочного указателя для отображения значений индекса относительной силы с помощью динамической стрелки, цветовых зон перекупленности и перепроданности и настраиваемых текстовых обозначений. Мы используем класс Canvas для отрисовки таких элементов, как дуги, деления и секторы, обеспечивая плавное и эффективное обновление при поступлении новых данных RSI.
От начального до среднего уровня: Навигация по песочнице От начального до среднего уровня: Навигация по песочнице
В данной статье мы рассмотрим два способа наблюдения за содержимым песочницы и даже взаимодействия с ним, используя MetaTrader 5 в качестве базовой платформы. Понимание содержания этой статьи имеет основополагающее значение для понимания того, что будет рассмотрено в последующих статьях.