English Deutsch 日本語
preview
Создание пользовательских индикаторов в MQL5 (Часть 4): Smart WaveTrend Crossover на основе двух осцилляторов

Создание пользовательских индикаторов в MQL5 (Часть 4): Smart WaveTrend Crossover на основе двух осцилляторов

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

Введение

В нашей предыдущей статье (Часть 3) мы разработали индикатор с несколькими шкалами на MetaQuotes Language 5 (MQL5) с доработками для секторного и кругового стилей отображения, что позволило динамически визуализировать несколько показателей с помощью настраиваемых индикаторных шкал и секторов с цветовой кодировкой. В четвёртой части мы разработаем индикатор Smart WaveTrend Crossover, использующий два осциллятора: один — для сигналов, второй — для фильтрации тренда. Он формирует оповещения на покупку и продажу на основе пересечений с возможностью подтверждения по тренду. Этот индикатор окрашивает свечи по направлению тренда, отображает стрелки при пересечениях и поддерживает настраиваемые параметры длины и визуального стиля. Мы рассмотрим следующие темы:

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

К концу статьи у вас будет рабочий индикатор MQL5 для пересечений WaveTrend, готовый к дальнейшей настройке. Давайте начнем!


Понимание структуры Smart WaveTrend Crossover

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

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

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

СТРУКТУРА ИНДИКАТОРА


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

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

//+------------------------------------------------------------------+
//|                           1. Smart WaveTrend Crossover PART1.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"

#property indicator_chart_window
#property indicator_buffers 23
#property indicator_plots 3
#property indicator_label1 "Colored Candles"
#property indicator_type1 DRAW_COLOR_CANDLES
#property indicator_color1 clrTeal, clrRed
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
#property indicator_label2 "Buy Signals"
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrForestGreen
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
#property indicator_label3 "Sell Signals"
#property indicator_type3 DRAW_ARROW
#property indicator_color3 clrOrangeRed
#property indicator_style3 STYLE_SOLID
#property indicator_width3 1

Начнем с настройки индикатора для отображения непосредственно в основном окне графика с помощью "#property indicator_chart_window", благодаря чему он накладывается на ценовые данные без открытия отдельного подокна. Затем мы выделяем в общей сложности 23 буфера с помощью "#property indicator_buffers 23" для хранения всех необходимых массивов данных для расчетов и визуализации, а также задаем 3 графических построения через "#property indicator_plots 3", чтобы определить видимые элементы на графике.

Для первого отображаемого элемента мы задаем метку "Colored Candles" и устанавливаем тип DRAW_COLOR_CANDLES для отображения ценовых баров с динамическими цветами: "clrTeal" для бычьего состояния и "clrRed" для медвежьего, со сплошным стилем и шириной 1. Второй отображаемый элемент имеет метку "Buy Signals", тип "DRAW_ARROW" и цвет clrForestGreen, сплошной стиль и ширину 1, чтобы визуально отмечать потенциальные точки входа. Аналогично третий отображаемый элемент с меткой "Sell Signals" использует DRAW_ARROW тип, цвет "clrOrangeRed", сплошной стиль и ширину 1 для обозначения возможностей продажи. Теперь определим несколько входных параметров для управления индикатором.

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "Colors"
input color col_up = clrTeal;                // Bull Color
input color col_dn = clrRed;                 // Bear Color

input group "WaveTrend Settings for Signals"
input int wt_channel_len = 5;                // Signal Channel Length
input int wt_average_len = 10;               // Signal Average Length
input int wt_ma_len = 4;                     // Signal MA Length

input group "WaveTrend Settings for Trend"
input int wt_trend_channel_len = 10;         // Trend Channel Length
input int wt_trend_average_len = 100;        // Trend Average Length
input int wt_trend_ma_len = 10;              // Trend MA Length

input group "Signal Settings"
input bool use_trend_filter = true;          // Use Trend Filter?
input color signal_buy_col = clrForestGreen; // Buy Signal Color
input color signal_sell_col = clrOrangeRed;  // Sell Signal Color
input int base_offset = 10;                  // Base Signal Offset from Candle

input group "Visual Settings"
input bool color_candles = true;             // Color Candles by Trend?

Здесь мы организуем пользовательские входные параметры в логические группы с помощью "input group", чтобы сделать настройку более понятной в диалоговом окне параметров индикатора. Сначала в группе "Colors" мы даем возможность выбрать цвета бычьего и медвежьего состояния — по умолчанию бирюзовый и красный соответственно; эти цвета будут применяться при окрашивании свечей. Далее в группе "WaveTrend Settings for Signals" задаются целочисленные входные параметры для сигнального осциллятора: длина канала равна 5, период усреднения — 10, а длина скользящей средней — 4, что позволяет настраивать чувствительность формирования сигналов пересечения.

Затем группа "WaveTrend Settings for Trend" включает аналогичные целочисленные входные параметры, но с более длинными значениями по умолчанию: длина канала — 10, период усреднения — 100, длина скользящей средней — 10. Они предназначены для определения более широкого рыночного тренда. В группе "Signal Settings" мы добавляем логический параметр, по умолчанию включающий трендовую фильтрацию, выбор цветов для сигналов покупки — clrForestGreen по умолчанию — и сигналов продажи — оранжево-красный, а также целочисленный параметр базового смещения от свечей, равный 10 пунктам, который управляет размещением сигнальных стрелок. Наконец, группа "Visual Settings" содержит логический входной параметр, по умолчанию включенный, который окрашивает свечи на основе обнаруженного тренда и дает пользователю возможность управлять применением динамической визуализации свечей. После компиляции мы получаем следующий раздел входных параметров.

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

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

//+------------------------------------------------------------------+
//| Buffers                                                          |
//+------------------------------------------------------------------+
double esa_signal[];          //--- Signal ESA buffer
double d_signal[];            //--- Signal D buffer
double ci_signal[];           //--- Signal CI buffer
double wt1_signal[];          //--- Signal WT1 buffer
double wt2_signal[];          //--- Signal WT2 buffer
double signal_hist[];         //--- Signal histogram buffer
double signal_bull_cross[];   //--- Signal bull cross buffer
double signal_bear_cross[];   //--- Signal bear cross buffer

double esa_trend[];           //--- Trend ESA buffer
double d_trend[];             //--- Trend D buffer
double ci_trend[];            //--- Trend CI buffer
double wt1_trend[];           //--- Trend WT1 buffer
double wt2_trend[];           //--- Trend WT2 buffer
double trend_hist[];          //--- Trend histogram buffer
double trend_is_bull[];       //--- Trend bull indicator buffer
double trend_is_bear[];       //--- Trend bear indicator buffer

double openBuf[];             //--- Open price buffer
double highBuf[];             //--- High price buffer
double lowBuf[];              //--- Low price buffer
double closeBuf[];            //--- Close price buffer
double candleColorBuf[];      //--- Candle color buffer
double buyArrowBuf[];         //--- Buy arrow buffer
double sellArrowBuf[];        //--- Sell arrow buffer

//+------------------------------------------------------------------+
//| Calculate EMA manually                                           |
//+------------------------------------------------------------------+
double CalcEMA(double prev, double val, int period) {
   if (period < 1) return val;              //--- Handle invalid period
   double alpha = 2.0 / (period + 1);       //--- Compute alpha factor
   return alpha * val + (1 - alpha) * prev; //--- Return EMA value
}

Мы объявляем серию массивов double, которые будут служить буферами для хранения промежуточных и итоговых данных во время расчетов индикатора. Сюда входят буферы компонентов сигнального WaveTrend: экспоненциальные сглаженные средние, разности, индексы канала и две основные линии, используемые для обнаружения пересечений, а также гистограмма и флаги бычьих и медвежьих пересечений. Аналогично мы создаем параллельные буферы для трендового WaveTrend, включая его собственное сглаживание, разности, индексы, линии, гистограмму и логические флаги для определения бычьего или медвежьего тренда. Кроме того, мы выделяем буферы для ценовых данных, чтобы перерисовывать свечи с цветами, включая open, high, low, close и буфер цветового индекса, а также отдельные буферы для отображения стрелок покупки и продажи в точках сигналов.

Для поддержки расчетов WaveTrend мы определяем функцию "CalcEMA", которая вручную рассчитывает экспоненциальную скользящую среднюю: сначала проверяет некорректные периоды и в таком случае возвращает само значение, затем вычисляет коэффициент сглаживания alpha как 2.0, деленное на период плюс один, и затем вычисляет новое значение EMA как взвешенную комбинацию текущего значения и предыдущей EMA. Мы будем использовать эту функцию при выполнении расчетов на каждом тике, а пока инициализируем индикатор следующим образом.

//+------------------------------------------------------------------+
//| Initialize indicator                                             |
//+------------------------------------------------------------------+
int OnInit() {
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);   //--- Set indicator digits
   IndicatorSetString(INDICATOR_SHORTNAME, "Smart WaveTrend Crossover PART1"); //--- Set short name

   SetIndexBuffer(0, openBuf, INDICATOR_DATA);       //--- Bind open buffer
   SetIndexBuffer(1, highBuf, INDICATOR_DATA);       //--- Bind high buffer
   SetIndexBuffer(2, lowBuf, INDICATOR_DATA);        //--- Bind low buffer
   SetIndexBuffer(3, closeBuf, INDICATOR_DATA);      //--- Bind close buffer
   SetIndexBuffer(4, candleColorBuf, INDICATOR_COLOR_INDEX); //--- Bind color buffer
   PlotIndexSetInteger(0, PLOT_SHOW_DATA, color_candles); //--- Set candle visibility

   SetIndexBuffer(5, buyArrowBuf, INDICATOR_DATA);   //--- Bind buy arrow buffer
   SetIndexBuffer(6, sellArrowBuf, INDICATOR_DATA);  //--- Bind sell arrow buffer
   PlotIndexSetInteger(1, PLOT_ARROW, 233);          //--- Set buy arrow symbol
   PlotIndexSetInteger(1, PLOT_SHOW_DATA, true);     //--- Enable buy arrow display
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 0);       //--- Set buy draw begin
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, signal_buy_col); //--- Set buy color

   PlotIndexSetInteger(2, PLOT_ARROW, 234);          //--- Set sell arrow symbol
   PlotIndexSetInteger(2, PLOT_SHOW_DATA, true);     //--- Enable sell arrow display
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, 0);       //--- Set sell draw begin
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, signal_sell_col); //--- Set sell color

   SetIndexBuffer(7, esa_signal, INDICATOR_CALCULATIONS);  //--- Bind signal ESA
   SetIndexBuffer(8, d_signal, INDICATOR_CALCULATIONS);    //--- Bind signal D
   SetIndexBuffer(9, ci_signal, INDICATOR_CALCULATIONS);   //--- Bind signal CI
   SetIndexBuffer(10, wt1_signal, INDICATOR_CALCULATIONS); //--- Bind signal WT1
   SetIndexBuffer(11, wt2_signal, INDICATOR_CALCULATIONS); //--- Bind signal WT2
   SetIndexBuffer(12, signal_hist, INDICATOR_CALCULATIONS); //--- Bind signal hist
   SetIndexBuffer(13, signal_bull_cross, INDICATOR_CALCULATIONS); //--- Bind bull cross
   SetIndexBuffer(14, signal_bear_cross, INDICATOR_CALCULATIONS); //--- Bind bear cross

   SetIndexBuffer(15, esa_trend, INDICATOR_CALCULATIONS); //--- Bind trend ESA
   SetIndexBuffer(16, d_trend, INDICATOR_CALCULATIONS);   //--- Bind trend D
   SetIndexBuffer(17, ci_trend, INDICATOR_CALCULATIONS);  //--- Bind trend CI
   SetIndexBuffer(18, wt1_trend, INDICATOR_CALCULATIONS); //--- Bind trend WT1
   SetIndexBuffer(19, wt2_trend, INDICATOR_CALCULATIONS); //--- Bind trend WT2
   SetIndexBuffer(20, trend_hist, INDICATOR_CALCULATIONS); //--- Bind trend hist
   SetIndexBuffer(21, trend_is_bull, INDICATOR_CALCULATIONS); //--- Bind trend bull
   SetIndexBuffer(22, trend_is_bear, INDICATOR_CALCULATIONS); //--- Bind trend bear

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

В обработчике OnInit мы настраиваем свойства индикатора, задавая количество отображаемых знаков в соответствии с точностью символа с помощью IndicatorSetInteger и назначая короткое имя с помощью IndicatorSetString для удобной идентификации в платформе. Мы привязываем ценовые буферы open, high, low и close к начальным индексам построения как типы данных, а цветовой буфер подключаем как цветовой индекс для поддержки динамического отображения свечей, одновременно управляя видимостью построения через PlotIndexSetInteger на основе пользовательских настроек. Для стрелочных построений мы связываем буферы стрелок покупки и продажи с соответствующими индексами, задаем символы стрелок с конкретными кодами, например 233 для покупок и 234 для продаж, включаем отображение и задаем начало отрисовки с нулевого бара и применяем пользовательские цвета с помощью функции "PlotIndexSetInteger". Вы можете использовать любые коды по своему выбору из MQL5 Wingdings, как показано ниже.

КОДЫ MQL5 WINGDINGS

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

//+------------------------------------------------------------------+
//| Calculate indicator values                                       |
//+------------------------------------------------------------------+
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 start = prev_calculated - 1;               //--- Set start index
   if (start < 0) start = 0;                      //--- Adjust invalid start

   if (prev_calculated == 0) {                    //--- Handle initial calculation
      ArrayInitialize(esa_signal, EMPTY_VALUE);   //--- Init signal ESA
      ArrayInitialize(d_signal, EMPTY_VALUE);     //--- Init signal D
      ArrayInitialize(ci_signal, EMPTY_VALUE);    //--- Init signal CI
      ArrayInitialize(wt1_signal, EMPTY_VALUE);   //--- Init signal WT1
      ArrayInitialize(wt2_signal, EMPTY_VALUE);   //--- Init signal WT2
      ArrayInitialize(signal_hist, EMPTY_VALUE);  //--- Init signal hist
      ArrayInitialize(signal_bull_cross, 0);      //--- Init bull cross
      ArrayInitialize(signal_bear_cross, 0);      //--- Init bear cross

      ArrayInitialize(esa_trend, EMPTY_VALUE);    //--- Init trend ESA
      ArrayInitialize(d_trend, EMPTY_VALUE);      //--- Init trend D
      ArrayInitialize(ci_trend, EMPTY_VALUE);     //--- Init trend CI
      ArrayInitialize(wt1_trend, EMPTY_VALUE);    //--- Init trend WT1
      ArrayInitialize(wt2_trend, EMPTY_VALUE);    //--- Init trend WT2
      ArrayInitialize(trend_hist, EMPTY_VALUE);   //--- Init trend hist
      ArrayInitialize(trend_is_bull, 0);          //--- Init trend bull
      ArrayInitialize(trend_is_bear, 0);          //--- Init trend bear

      ArrayInitialize(buyArrowBuf, EMPTY_VALUE);  //--- Init buy arrows
      ArrayInitialize(sellArrowBuf, EMPTY_VALUE); //--- Init sell arrows
   }

   return(rates_total);                           //--- Return total rates
}

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

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

for (int i = start; i < rates_total; i++) {   //--- Loop through bars
   double src_wt = (high[i] + low[i] + close[i]) / 3.0; //--- Compute source

   if (i == 0) {                               //--- Handle first bar signal
      esa_signal[i] = src_wt;                  //--- Set initial ESA
      d_signal[i] = MathAbs(src_wt - esa_signal[i]); //--- Set initial D
      double denom_signal = 0.015 * d_signal[i]; //--- Compute denominator
      ci_signal[i] = (denom_signal != 0.0) ? (src_wt - esa_signal[i]) / denom_signal : 0.0; //--- Set CI
      wt1_signal[i] = ci_signal[i];            //--- Set initial WT1
      wt2_signal[i] = wt1_signal[i];           //--- Set initial WT2
   } else {                                    //--- Handle subsequent bars
      esa_signal[i] = CalcEMA(esa_signal[i-1], src_wt, wt_channel_len); //--- Update ESA
      d_signal[i] = CalcEMA(d_signal[i-1], MathAbs(src_wt - esa_signal[i]), wt_channel_len); //--- Update D
      double denom_signal = 0.015 * d_signal[i]; //--- Compute denominator
      ci_signal[i] = (denom_signal != 0.0) ? (src_wt - esa_signal[i]) / denom_signal : 0.0; //--- Update CI
      wt1_signal[i] = CalcEMA(wt1_signal[i-1], ci_signal[i], wt_average_len); //--- Update WT1
      wt2_signal[i] = 0;                       //--- Reset WT2
      int cnt_ma = 0;                          //--- Init MA count
      for (int k = 0; k < wt_ma_len; k++) {    //--- Loop for MA
         if (i - k < 0) break;                 //--- Skip invalid index
         wt2_signal[i] += wt1_signal[i - k];   //--- Accumulate WT1
         cnt_ma++;                             //--- Increment count
      }
      if (cnt_ma > 0) wt2_signal[i] /= cnt_ma; //--- Average WT2
   }

   signal_hist[i] = wt1_signal[i] - wt2_signal[i]; //--- Compute signal hist
   signal_bull_cross[i] = (wt1_signal[i] > wt2_signal[i] && (i == 0 || wt1_signal[i-1] <= wt2_signal[i-1])) ? 1 : 0; //--- Detect bull cross
   signal_bear_cross[i] = (wt1_signal[i] < wt2_signal[i] && (i == 0 || wt1_signal[i-1] >= wt2_signal[i-1])) ? 1 : 0; //--- Detect bear cross

   double src_wt_trend = src_wt;              //--- Set trend source
   if (i == 0) {                              //--- Handle first bar trend
      esa_trend[i] = src_wt_trend;            //--- Set initial ESA
      d_trend[i] = MathAbs(src_wt_trend - esa_trend[i]); //--- Set initial D
      double denom_trend = 0.015 * d_trend[i]; //--- Compute denominator
      ci_trend[i] = (denom_trend != 0.0) ? (src_wt_trend - esa_trend[i]) / denom_trend : 0.0; //--- Set CI
      wt1_trend[i] = ci_trend[i];             //--- Set initial WT1
      wt2_trend[i] = wt1_trend[i];            //--- Set initial WT2
   } else {                                   //--- Handle subsequent bars
      esa_trend[i] = CalcEMA(esa_trend[i-1], src_wt_trend, wt_trend_channel_len); //--- Update ESA
      d_trend[i] = CalcEMA(d_trend[i-1], MathAbs(src_wt_trend - esa_trend[i]), wt_trend_channel_len); //--- Update D
      double denom_trend = 0.015 * d_trend[i]; //--- Compute denominator
      ci_trend[i] = (denom_trend != 0.0) ? (src_wt_trend - esa_trend[i]) / denom_trend : 0.0; //--- Update CI
      wt1_trend[i] = CalcEMA(wt1_trend[i-1], ci_trend[i], wt_trend_average_len); //--- Update WT1
      wt2_trend[i] = 0;                       //--- Reset WT2
      int cnt_ma_trend = 0;                   //--- Init MA count
      for (int k = 0; k < wt_trend_ma_len; k++) { //--- Loop for MA
         if (i - k < 0) break;                //--- Skip invalid index
         wt2_trend[i] += wt1_trend[i - k];    //--- Accumulate WT1
         cnt_ma_trend++;                      //--- Increment count
      }
      if (cnt_ma_trend > 0) wt2_trend[i] /= cnt_ma_trend; //--- Average WT2
   }

   trend_hist[i] = wt1_trend[i] - wt2_trend[i]; //--- Compute trend hist
   trend_is_bull[i] = (wt1_trend[i] > wt2_trend[i]) ? 1 : 0; //--- Set bull trend
   trend_is_bear[i] = (wt1_trend[i] < wt2_trend[i]) ? 1 : 0; //--- Set bear trend

   if (color_candles) {                       //--- Check candle coloring
      openBuf[i] = open[i];                   //--- Set open
      highBuf[i] = high[i];                   //--- Set high
      lowBuf[i] = low[i];                     //--- Set low
      closeBuf[i] = close[i];                 //--- Set close
      candleColorBuf[i] = trend_is_bull[i] == 1 ? 0 : 1; //--- Set color index
   }

   buyArrowBuf[i] = EMPTY_VALUE;              //--- Reset buy arrow
   sellArrowBuf[i] = EMPTY_VALUE;             //--- Reset sell arrow
   if (signal_bull_cross[i] == 1 && (!use_trend_filter || trend_is_bull[i] == 1)) { //--- Check buy condition
      buyArrowBuf[i] = low[i] - _Point * base_offset; //--- Place buy arrow
   }
   if (signal_bear_cross[i] == 1 && (!use_trend_filter || trend_is_bear[i] == 1)) { //--- Check sell condition
      sellArrowBuf[i] = high[i] + _Point * base_offset; //--- Place sell arrow
   }
}

Здесь мы проходим в цикле по каждому бару от начального индекса до общего количества доступных баров, рассчитывая исходную цену как среднее значение high, low и close для этого бара. Это значение используется как входной параметр для обоих осцилляторов WaveTrend.

Для сигнального WaveTrend на первом баре мы инициализируем экспоненциальную сглаженную среднюю непосредственно из исходной цены, рассчитываем абсолютную разность для буфера отклонения с помощью функции MathAbs , вычисляем знаменатель, умножая отклонение на 0.015, и задаем индекс канала, деля разность между исходной ценой и сглаженной средней на знаменатель, если он не равен нулю; иначе используем ноль. Затем мы устанавливаем обе основные линии равными значению индекса канала. Для последующих баров мы обновляем экспоненциальную сглаженную среднюю с помощью "CalcEMA", используя предыдущее значение, исходную цену и длину канала, пересчитываем отклонение еще одним вызовом "CalcEMA" по абсолютной разности от "MathAbs" с той же длиной, заново рассчитываем знаменатель, аналогично обновляем индекс канала, сглаживаем первую основную линию с помощью "CalcEMA" с использованием периода усреднения и вычисляем вторую основную линию, усредняя последние значения первой линии за период скользящей средней через вложенный цикл, который накапливает значения, выполняет деление и пропускает некорректные индексы.

Затем мы получаем сигнальную гистограмму как разность между двумя основными сигнальными линиями, отмечаем бычье пересечение, если первая линия находится выше второй и не находилась выше на предыдущем баре (или если это первый бар), и отмечаем медвежье пересечение, если первая линия ниже второй и ранее не была ниже. Повторяя аналогичный процесс для трендового WaveTrend с использованием той же исходной цены, мы обрабатываем первый бар, одинаково инициализируя экспоненциальное сглаживание, отклонение через "MathAbs", знаменатель, индекс канала и обе основные линии, но для последующих баров применяем параметры тренда: обновление через "CalcEMA" с длинами трендового канала и периода усреднения, а также усреднение второй линии по длине трендовой скользящей средней во вложенном цикле. Мы рассчитываем трендовую гистограмму как разность между ее основными линиями, устанавливаем флаг бычьего тренда в 1, если первая линия выше второй, и флаг медвежьего тренда в 1, если она ниже.

Если окрашивание свечей включено, мы копируем open, high, low и close бара в соответствующие буферы и назначаем цветовой индекс 0 для бычьих трендов или 1 для медвежьих. Наконец, мы сбрасываем буферы стрелок покупки и продажи в EMPTY_VALUE, затем размещаем стрелку покупки ниже low на величину базового смещения, умноженного на _Point , если произошло бычье пересечение сигнала и при этом трендовый фильтр не используется либо тренд является бычьим. Аналогично мы размещаем стрелку продажи выше high, если произошло медвежье пересечение при совпадающих условиях. После компиляции получаем следующий результат.

ИТОГОВЫЙ ТЕСТОВЫЙ GIF

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


Бэктестинг

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

GIF БЭКТЕСТИНГА


Заключение

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

В следующей части мы улучшим индикатор расширенными визуальными элементами, включая сигнальные блоки, полупрозрачные области для визуализации тренда, настраиваемые метки покупки/продажи и встроенное отображение take-profit и stop-loss для более эффективного управления рисками, как показано ниже. Следите за продолжением.

СРАВНЕНИЕ БУДУЩИХ УЛУЧШЕНИЙ

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

Прикрепленные файлы |
MQL5 Bootstrap (I): Набор переиспользуемых функций для работы с позициями и ордерами MQL5 Bootstrap (I): Набор переиспользуемых функций для работы с позициями и ордерами
В статье представлен компактный вспомогательный слой MQL5 для типовых торговых операций. Он включает функции проверки наличия позиций, счётчики позиций, вспомогательные функции для группового закрытия позиций, а также функции получения наиболее недавно открытой или самой старой позиции по символу, magic-номеру или типу. Интеграция демонстрируется на простом советнике с пересечением SMA. В результате советники становятся чище, уменьшается количество различий в реализации между проектами и упрощается сопровождение кода.
Моделирование рынка: Position View (VIII) Моделирование рынка: Position View (VIII)
В предыдущей статье мы рассмотрели, как можно реализовать индикатор позиции для закрытия открытой позиции непосредственно с графика, взаимодействуя с доступным на нем объектом. После завершения и проверки работы первого механизма мы приступили к внесению изменений, чтобы обеспечить возможность удаления линий тейк-профита и стоп-лосса для открытой позиции. Однако, поскольку необходимые изменения требовали подробных объяснений, в той же статье я показал только те изменения, которые необходимо было внести в советник, но всё ещё требовалось показать изменения, которые необходимо было внести в индикатор позиции.
Алгоритм бактериальной эволюции — Bacterial Evolutionary Algorithm (BEA) Алгоритм бактериальной эволюции — Bacterial Evolutionary Algorithm (BEA)
Статья посвящена бактериальному эволюционному алгоритму Нава — Фурухаси (BEA) и его двум ключевым операторам: посегментной бактериальной мутации для локальной оптимизации и горизонтальному переносу генов для обмена удачными фрагментами решений. Мы реализуем BEA в каркасе C_AO как конечный автомат под фиксированный бюджет оценок, тестируем на Hilly, Forest и Megacity и поясняем терминологическую развилку с моделью Нумаоки.
Оптимизация трендовой торговли: Торговля по направлению тренда и с учётом его силы Оптимизация трендовой торговли: Торговля по направлению тренда и с учётом его силы
Это специализированный трендовый советник, который выполняет краткосрочный и долгосрочный анализ, принимает торговые решения и исполняет сделки на основе общего тренда и его силы. В статье подробно рассматривается советник, специально разработанный для терпеливых и дисциплинированных трейдеров, способных последовательно придерживаться выбранного торгового направления, открывать сделки и удерживать позиции только тогда, когда рынок движется уверенно и по тренду, не меняя часто своего направленного взгляда на рынок, особенно против тренда, пока не будут достигнуты цели по тейк-профиту.