English 中文 Deutsch 日本語
preview
Разработка инструментария для анализа движения цен (Часть 19): ZigZag Analyzer

Разработка инструментария для анализа движения цен (Часть 19): ZigZag Analyzer

MetaTrader 5Примеры |
173 5
Christian Benjamin
Christian Benjamin

Введение

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

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

Сначала мы рассмотрим сами линии тренда, затем — индикатор ZigZag. Будет представлен алгоритм системы и код на MQL5. В конце проанализируем результаты и сделаем выводы. Содержание:



Линии тренда

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

Восходящий тренд

Рис 1. Линии тренда

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



Индикатор ZigZag

Хотя точное происхождение индикатора ZigZag документально не подтверждено, некоторые источники приписывают его разработку Биллу Вульфу. Это трейдер S&P 500 и создатель Волн Вульфа — метода, отчасти схожего с волнами Эллиотта, но имеющего собственные принципы построения графиков. Волны Вульфа состоят из пяти волн, иллюстрирующих движение спроса и предложения к равновесной цене (ссылка). Индикатор ZigZag выделяет значимые ценовые развороты, превышающие заданный порог, обычно выраженный в процентах. Когда ценовое колебание превышает этот порог, индикатор наносит новую точку и проводит прямую линию от предыдущей точки, отфильтровывая при этом мелкие колебания. Благодаря этому основные тренды становятся более заметными на различных таймфреймах.

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

Индикатор Zigzag

Рисунок 2. Индикатор Zigzag

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



Алгоритм системы

1. Глобальные объявления и входные параметры

На начальном этапе мы задаем входные параметры и глобальные переменные, которые позволяют настраивать анализатор без необходимости углубляться в код. Определим основные параметры, включая таймфрейм графика и свойства индикатора ZigZag — глубину (depth), отклонение (deviation) и шаг (backstep). Эти настройки влияют на то, Как индикатор определяет значимые уровни разворота. Также здесь мы объявляем массивы для хранения данных индикатора ZigZag и записи времени и цены ключевых вершин.

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

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

// Input parameters
input ENUM_TIMEFRAMES InpTimeFrame    = PERIOD_CURRENT; // Timeframe to analyze
input int             ZZ_Depth         = 12;  // ZigZag depth
input int             ZZ_Deviation     = 5;   // ZigZag deviation
input int             ZZ_Backstep      = 3;   // ZigZag backstep
input int             LookBackBars     = 200; // Bars to search for pivots
input int             ExtendFutureBars = 100; // Bars to extend trendlines into the future

// Global indicator handle for ZigZag
int zzHandle;

// Arrays for ZigZag data and pivot storage
double   zzBuffer[];
datetime pivotTimes[];
double   pivotPrices[];
bool     pivotIsPeak[];

// Variable to detect new bars
datetime lastBarTime = 0;

  • InpTimeFrame — таймфрейм для анализа.
  • ZZ_Depth, ZZ_Deviation, ZZ_Backstep — параметры, влияющие на чувствительность и поведение индикатора ZigZag.

Массивы по типу zzBuffer хранят выходные данные индикатора ZigZag. Дополнительные массивы (pivotTimes, pivotPrices, pivotIsPeak) предназначены для хранения информации о каждом обнаруженном развороте. Переменная lastBarTime помогает определить, сформировался ли новый бар, чтобы анализ обновлялся только при необходимости.

2. Функция инициализации (OnInit)

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

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

int OnInit()
  {
   zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep);
   if(zzHandle == INVALID_HANDLE)
     {
      Print("Error creating ZigZag handle");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

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

3. Функция деинициализации (OnDeinit)

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

Очистка ресурсов: удаляет все созданные графические объекты с графика.

Освобождение хэндла: освобождает хэндл индикатора ZigZag с помощью функции IndicatorRelease.

void OnDeinit(const int reason)
  {
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");
   IndicatorRelease(zzHandle);
  }

4. Основная функция выполнения (OnTick)

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

void OnTick()
  {
   datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

   // Remove previous objects
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");

   if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0)
     {
      Print("Failed to copy ZigZag data");
      return;
     }
   ArraySetAsSeries(zzBuffer, true);

   DrawZigZagTrendlines();
   DrawSupportResistance();
  }

  • Проверка нового бара: использует функцию iTime для определения смены бара.
  • Обновление буфера: обновляет буфер zzBuffer последними данными индикатора ZigZag.
  • Обновление графика: очищает предыдущие графические объекты и готовит график для новых. Вызывает функции для построения линий тренда и уровней поддержки/сопротивления.

5. Построение линий тренда на основе индикатора ZigZag (DrawZigZagTrendlines)

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

1. Наклон (m)

Рис. 3. Формула регрессии

2. Пересечение (b)

Пересечение (b)

Рис 4. Пересечение

  • (t) представляет значения времени (независимая переменная).
  • (p) представляет значения цены (зависимая переменная).
  • (N) — количество точек данных, используемых в регрессии.

Получение точек разворота: проходит в цикле по буферу zzBuffer и собирает 10 максимумов и минимумов.
Исключение последнего разворота: отбрасывает самый последний свинг для снижения шума.
Регрессионный анализ: вычисляет наклон (m) и пересечение (b) по приведенным выше формулам.

void DrawZigZagTrendlines()
  {
   double highPrices[10], lowPrices[10];
   datetime highTimes[10], lowTimes[10];
   int highCount = 0, lowCount = 0;

   // Extract swing points from the ZigZag buffer
   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10)
           {
            highPrices[highCount] = zzBuffer[i];
            highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i);
            highCount++;
           }
         else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10)
              {
               lowPrices[lowCount] = zzBuffer[i];
               lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i);
               lowCount++;
              }
        }
     }

   // Exclude the most recent swing if possible
   int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount;
   int usedLowCount  = (lowCount  >= 4) ? lowCount - 1  : lowCount;

   double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0;
   bool validHigh = false, validLow = false;

   // Regression for highs
   if(usedHighCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedHighCount; i++)
        {
         double t = (double)highTimes[i];
         double p = highPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedHighCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mHigh = (N * sumTP - sumT * sumP) / denominator;
         bHigh = (sumP - mHigh * sumT) / N;
        }
      else
         bHigh = sumP / N;
      validHigh = true;
     }

   // Regression for lows
   if(usedLowCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedLowCount; i++)
        {
         double t = (double)lowTimes[i];
         double p = lowPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedLowCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mLow = (N * sumTP - sumT * sumP) / denominator;
         bLow = (sumP - mLow * sumT) / N;
        }
      else
         bLow = sumP / N;
      validLow = true;
     }

   // Define time limits for trendlines
   datetime pastTime   = iTime(_Symbol, InpTimeFrame, LookBackBars - 1);
   datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds();

   // Draw trendlines if both regressions are valid
   if(validHigh && validLow)
     {
      // When slopes have the same sign, use average slope for parallel lines
      if(mHigh * mLow > 0)
        {
         double mParallel = (mHigh + mLow) / 2.0;
         double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0];
         double bLowParallel  = lowPrices[0] - mParallel * (double)lowTimes[0];

         datetime highStartTime = pastTime;
         double highStartPrice  = mParallel * (double)highStartTime + bHighParallel;
         double highEndPrice    = mParallel * (double)futureTime + bHighParallel;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

         datetime lowStartTime = pastTime;
         double lowStartPrice  = mParallel * (double)lowStartTime + bLowParallel;
         double lowEndPrice    = mParallel * (double)futureTime + bLowParallel;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
      else // Draw separate trendlines if slopes differ
        {
         datetime highStartTime = pastTime;
         double highStartPrice  = mHigh * (double)highStartTime + bHigh;
         double highEndPrice    = mHigh * (double)futureTime + bHigh;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

         datetime lowStartTime = pastTime;
         double lowStartPrice  = mLow * (double)lowStartTime + bLow;
         double lowEndPrice    = mLow * (double)futureTime + bLow;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
     }
   else
     {
      // Draw only available regression if only one set of points is valid
      if(validHigh)
        {
         datetime highStartTime = pastTime;
         double highStartPrice  = mHigh * (double)highStartTime + bHigh;
         double highEndPrice    = mHigh * (double)futureTime + bHigh;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }
        }
      if(validLow)
        {
         datetime lowStartTime = pastTime;
         double lowStartPrice  = mLow * (double)lowStartTime + bLow;
         double lowEndPrice    = mLow * (double)futureTime + bLow;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
     }
  }

Построение линий тренда (если оба тренда валидны)

  • Одинаковые знаки наклонов: использует усредненный наклон для параллельных линий.
  • Разные знаки: строятся отдельные линии тренда. Использует функции ObjectCreate и ObjectSetInteger для размещения и стиля линий.

6. Построение уровней поддержки и сопротивления (DrawSupportResistance)

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

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

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

void DrawSupportResistance()
  {
   double confirmedHighs[10], confirmedLows[10];
   int confHighCount = 0, confLowCount = 0;

   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10)
           {
            confirmedHighs[confHighCount] = zzBuffer[i];
            confHighCount++;
           }
         else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10)
              {
               confirmedLows[confLowCount] = zzBuffer[i];
               confLowCount++;
              }
        }
     }

   int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount;
   int usedLowCount  = (confLowCount  >= 4) ? confLowCount - 1  : confLowCount;

   double majorResistance = -1e9, majorSupport = 1e9;
   double minorResistance = -1e9, minorSupport = 1e9;
   double tempHigh = -1e9, tempLow = -1e9;
   for(int i = 0; i < usedHighCount; i++)
     {
      if(confirmedHighs[i] > majorResistance)
        {
         tempHigh = majorResistance;
         majorResistance = confirmedHighs[i];
        }
      else if(confirmedHighs[i] > tempHigh)
           {
            tempHigh = confirmedHighs[i];
           }
     }
   if(tempHigh > -1e9)
      minorResistance = tempHigh;
   for(int i = 0; i < usedLowCount; i++)
     {
      if(confirmedLows[i] < majorSupport)
        {
         tempLow = majorSupport;
         majorSupport = confirmedLows[i];
        }
      else if(confirmedLows[i] < tempLow)
           {
            tempLow = confirmedLows[i];
           }
     }
   if(tempLow < 1e9)
      minorSupport = tempLow;

   if(usedHighCount > 0)
     {
      if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance))
         Print("Failed to create Major Resistance");
      else
         ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta);

      if(minorResistance > -1e9 && minorResistance < majorResistance)
        {
         if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance))
            Print("Failed to create Minor Resistance");
         else
            ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia);
        }
     }
   if(usedLowCount > 0)
     {
      if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport))
         Print("Failed to create Major Support");
      else
         ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua);

      if(minorSupport < 1e9 && minorSupport > majorSupport)
        {
         if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport))
            Print("Failed to create Minor Support");
         else
            ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue);
        }
     }
  }

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


MQL5-код

//+------------------------------------------------------------------+
//|                                               ZigZag Analyzer.mq5|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

// Input parameters
input ENUM_TIMEFRAMES InpTimeFrame    = PERIOD_CURRENT; // Timeframe to analyze
input int             ZZ_Depth         = 12;  // ZigZag depth
input int             ZZ_Deviation     = 5;   // ZigZag deviation
input int             ZZ_Backstep      = 3;   // ZigZag backstep
input int             LookBackBars     = 200; // Bars to search for pivots
input int             ExtendFutureBars = 100; // Bars to extend trendlines into the future

// Global indicator handle for ZigZag
int zzHandle;

// Arrays for ZigZag data and pivot storage
double   zzBuffer[];
datetime pivotTimes[];
double   pivotPrices[];
bool     pivotIsPeak[];

// Variable to detect new bars
datetime lastBarTime = 0;

//+------------------------------------------------------------------+
//| Initialization function                                          |
//+------------------------------------------------------------------+
int OnInit()
  {
   zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep);
   if(zzHandle == INVALID_HANDLE)
     {
      Print("Error creating ZigZag handle");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Deinitialization function                                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");
   IndicatorRelease(zzHandle);
  }

//+------------------------------------------------------------------+
//| Tick function                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

// Remove previous objects
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");

   if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0)
     {
      Print("Failed to copy ZigZag data");
      return;
     }
   ArraySetAsSeries(zzBuffer, true);

   DrawZigZagTrendlines();
   DrawSupportResistance();
  }

//+------------------------------------------------------------------+
//| Draw ZigZag-based Trendlines                                     |
//+------------------------------------------------------------------+
void DrawZigZagTrendlines()
  {
   double highPrices[10], lowPrices[10];
   datetime highTimes[10], lowTimes[10];
   int highCount = 0, lowCount = 0;

// Extract swing points from the ZigZag buffer
   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10)
           {
            highPrices[highCount] = zzBuffer[i];
            highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i);
            highCount++;
           }
         else
            if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10)
              {
               lowPrices[lowCount] = zzBuffer[i];
               lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i);
               lowCount++;
              }
        }
     }

// Exclude the most recent swing if possible
   int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount;
   int usedLowCount  = (lowCount  >= 4) ? lowCount - 1  : lowCount;

   double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0;
   bool validHigh = false, validLow = false;

// Regression for highs
   if(usedHighCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedHighCount; i++)
        {
         double t = (double)highTimes[i];
         double p = highPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedHighCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mHigh = (N * sumTP - sumT * sumP) / denominator;
         bHigh = (sumP - mHigh * sumT) / N;
        }
      else
         bHigh = sumP / N;
      validHigh = true;
     }

// Regression for lows
   if(usedLowCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedLowCount; i++)
        {
         double t = (double)lowTimes[i];
         double p = lowPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedLowCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mLow = (N * sumTP - sumT * sumP) / denominator;
         bLow = (sumP - mLow * sumT) / N;
        }
      else
         bLow = sumP / N;
      validLow = true;
     }

// Define time limits for trendlines
   datetime pastTime   = iTime(_Symbol, InpTimeFrame, LookBackBars - 1);
   datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds();

// Draw trendlines if both regressions are valid
   if(validHigh && validLow)
     {
      // When slopes have the same sign, use average slope
      if(mHigh * mLow > 0)
        {
         double mParallel = (mHigh + mLow) / 2.0;
         double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0];
         double bLowParallel  = lowPrices[0] - mParallel * (double)lowTimes[0];

         datetime highStartTime = pastTime;
         double highStartPrice  = mParallel * (double)highStartTime + bHighParallel;
         double highEndPrice    = mParallel * (double)futureTime + bHighParallel;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

         datetime lowStartTime = pastTime;
         double lowStartPrice  = mParallel * (double)lowStartTime + bLowParallel;
         double lowEndPrice    = mParallel * (double)futureTime + bLowParallel;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
      else
        {
         datetime highStartTime = pastTime;
         double highStartPrice  = mHigh * (double)highStartTime + bHigh;
         double highEndPrice    = mHigh * (double)futureTime + bHigh;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

         datetime lowStartTime = pastTime;
         double lowStartPrice  = mLow * (double)lowStartTime + bLow;
         double lowEndPrice    = mLow * (double)futureTime + bLow;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
     }
   else
     {
      if(validHigh)
        {
         datetime highStartTime = pastTime;
         double highStartPrice  = mHigh * (double)highStartTime + bHigh;
         double highEndPrice    = mHigh * (double)futureTime + bHigh;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }
        }
      if(validLow)
        {
         datetime lowStartTime = pastTime;
         double lowStartPrice  = mLow * (double)lowStartTime + bLow;
         double lowEndPrice    = mLow * (double)futureTime + bLow;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Draw Support and Resistance Levels                               |
//+------------------------------------------------------------------+
void DrawSupportResistance()
  {
   double confirmedHighs[10], confirmedLows[10];
   int confHighCount = 0, confLowCount = 0;

   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10)
           {
            confirmedHighs[confHighCount] = zzBuffer[i];
            confHighCount++;
           }
         else
            if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10)
              {
               confirmedLows[confLowCount] = zzBuffer[i];
               confLowCount++;
              }
        }
     }

   int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount;
   int usedLowCount  = (confLowCount  >= 4) ? confLowCount - 1  : confLowCount;

   double majorResistance = -1e9, majorSupport = 1e9;
   double minorResistance = -1e9, minorSupport = 1e9;
   double tempHigh = -1e9, tempLow = -1e9;
   for(int i = 0; i < usedHighCount; i++)
     {
      if(confirmedHighs[i] > majorResistance)
        {
         tempHigh = majorResistance;
         majorResistance = confirmedHighs[i];
        }
      else
         if(confirmedHighs[i] > tempHigh)
           {
            tempHigh = confirmedHighs[i];
           }
     }
   if(tempHigh > -1e9)
      minorResistance = tempHigh;
   for(int i = 0; i < usedLowCount; i++)
     {
      if(confirmedLows[i] < majorSupport)
        {
         tempLow = majorSupport;
         majorSupport = confirmedLows[i];
        }
      else
         if(confirmedLows[i] < tempLow)
           {
            tempLow = confirmedLows[i];
           }
     }
   if(tempLow < 1e9)
      minorSupport = tempLow;

   if(usedHighCount > 0)
     {
      if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance))
         Print("Failed to create Major Resistance");
      else
         ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta);

      if(minorResistance > -1e9 && minorResistance < majorResistance)
        {
         if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance))
            Print("Failed to create Minor Resistance");
         else
            ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia);
        }
     }
   if(usedLowCount > 0)
     {
      if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport))
         Print("Failed to create Major Support");
      else
         ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua);

      if(minorSupport < 1e9 && minorSupport > majorSupport)
        {
         if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport))
            Print("Failed to create Minor Support");
         else
            ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue);
        }
     }
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+



Результаты

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

Добавление индикатора

Рис. 5. Инициализация индикатора ZigZag

Далее показаны полученные результаты, при этом каждый рисунок сопровождается подробными пояснениями. На первом рисунке ниже показан результат, полученный после запуска инструмента Zig Zag Analyzer на индексе Step Index. Как видно, на графике корректно построены линии тренда. И нижняя линия, и верхняя линия имеют нисходящий наклон, что указывает на нисходящий тренд. Кроме того, на графиках построены основные и второстепенные уровни поддержки и сопротивления.

Линии тренда

Рис. 6. Результат на Step Index

Еще один скриншот с результатами анализа. На картинке показаны ключевые точки разворота, понижающиеся максимумы (lower highs) и понижающиеся минимумы (lower lows), используемые для построения линий тренда. Также отмечены основные и второстепенные уровни поддержки и сопротивления. Примечательно, что точки пересечения линий тренда с основными уровнями подчеркивают сильные зоны разворота. Наивысший понижающийся максимум совпадает с основным уровнем сопротивления, а самый низкий понижающийся минимум — с основным уровнем поддержки, где и происходит разворот цены.

Линии тренда

Рис. 7. Step Index

На данном графике USDCHF показаны построенные линии тренда. Хоть они и присутствуют на рынке, они не выглядят сильно доминирующими. Рынок соблюдает эти уровни поддержки и сопротивления, что подтверждает эффективность системы. Я выделил точки, в которых линии тренда соприкасаются с разворотом рынка.

Рис. 8. USDCHF

Далее можно оценить работу системы на индексе Volatility 25. Линии тренда также построены и соединяют точки разворота. Эти линии точно отражают движение рынка. Такая наглядность дополнительно подтверждает надежность системы в различных рыночных условиях. Полученные результаты подтверждают эффективность предложенного подхода.

Рис. 9. Volatility 25 Index


Заключение

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

Дата Название инструмента  Описание Версия  Обновления  Примечания
01/10/24 Chart Projector Скрипт для наложения эффекта призрака на движение цены за предыдущий день. 1.0 Первоначальная версия Инструмент номер 1
18/11/24 Analytical Comment Предоставляет информацию за предыдущий день в табличном формате, а также прогнозирует будущее направление рынка. 1.0 Первоначальная версия Инструмент номер 2
27/11/24 Analytics Master Регулярное обновление рыночных показателей каждые два часа  1.01 Вторая версия Инструмент номер 3
02/12/24 Analytics Forecaster  Регулярное обновление рыночных показателей каждые два часа с интеграцией с Telegram 1.1 Третья версия Инструмент номер 4
09/12/24 Volatility Navigator Советник анализирует рыночные условия с помощью полос Боллинджера, RSI и ATR. 1.0 Первоначальная версия Инструмент номер 5
19/12/24 Mean Reversion Signal Reaper  Анализирует рынок и генерирует сигналы, используя стратегию возврата к среднему  1.0  Первоначальная версия  Инструмент номер 6 
9/01/25  Signal Pulse  Анализирует несколько таймфреймов 1.0  Первоначальная версия  Инструмент номер 7 
17/01/25  Metrics Board  Панель с кнопками для анализа  1.0  Первоначальная версия Инструмент номер 8 
21/01/25 External Flow Аналитика с помощью внешних библиотек 1.0  Первоначальная версия Инструмент номер 9 
27/01/25 VWAP Взвешенная по объему средняя цена   1.3  Первоначальная версия  Инструмент номер 10 
02/02/25  Heikin Ashi  Сглаживание тренда и идентификация сигналов разворота  1.0  Первоначальная версия  Инструмент номер 11
04/02/25  FibVWAP  Генерация сигнала с помощью анализа Python  1.0  Первоначальная версия  Инструмент номер 12
14/02/25  RSI DIVERGENCE  Дивергенция цены и RSI  1.0  Первоначальная версия  Инструмент номер 13 
17/02/25  Parabolic Stop and Reverse (PSAR)  Автоматизация стратегии PSAR 1.0 Первоначальная версия  Инструмент номер 14
20/02/25  Скрипт Quarters Drawer  Нанесение уровней четвертей на график  1.0  Первоначальная версия  Инструмент номер 15 
27/02/25  Intrusion Detector Обнаружение и оповещение о достижении ценой уровней четвертей 1.0   Первоначальная версия Инструмент номер 16 
27/02/25  TrendLoom Tool Панель мультитаймфреймового анализа 1.0 Первоначальная версия Инструмент номер 17
11/03/25  Quarters Board  Панель с кнопками для включения/отключения уровней четвертей 1.0  Первоначальная версия Инструмент номер 18
26/03/25  ZigZag Analyzer  Построение линий тренда с помощью индикатора ZigZag  1.0  Первоначальная версия  Инструмент номер 19 

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

Прикрепленные файлы |
ZigZag_Analyzer.mq5 (13.44 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
diego herrera
diego herrera | 9 апр. 2025 в 21:28
>Здравствуйте, Кристина, приветствую вас.

Я нахожу этот индикатор очень интересным. Он бы очень помог мне в моем техническом анализе, так как я трачу на него больше всего времени, чтобы найти правильный тренд, поддержку и сопротивление. Я скачала код, он скомпилировался правильно, но когда я добавила его на график, никакой информации не отражается. Может, я что-то делаю не так? Я снова прикрепил видео. С уважением.
Christian Benjamin
Christian Benjamin | 12 апр. 2025 в 08:18
diego herrera #:
>Здравствуйте, Кристина, приветствую вас.

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

Диего Эррера

Christian Benjamin
Christian Benjamin | 12 апр. 2025 в 08:26
diego herrera #:
>Здравствуйте, Кристина, приветствую вас.

Я нахожу этот индикатор очень интересным. Он бы очень помог мне в моем техническом анализе, так как я трачу на него больше всего времени, чтобы найти правильный тренд, поддержку и сопротивление. Я скачала код, он скомпилировался правильно, но когда я добавила его на график, никакой информации не отражается. Может, я что-то делаю не так? Я снова прикрепил видео. С уважением.
Я полагаю, что проблема может быть связана с расположением индикатора ZigZag. Пожалуйста, попробуйте переместить его в основную папку индикаторов. Если проблема не исчезнет, пожалуйста, поделитесь логами MQL5 из вкладки "Эксперты" после запуска программы. Приносим извинения за задержку с ответом.
linfo2
linfo2 | 13 апр. 2025 в 21:48
Я думаю, что проблема в том, что индикатор zigazag не включен в загрузку, я не уверен, что Кристен имеет в виду, но вы можете использовать индикатор zigzag из примеров, если он вам нужен в качестве пользовательского индикатора.
Conor Mcnamara
Conor Mcnamara | 6 мая 2025 в 22:42
diego herrera #:
>Здравствуйте, Кристина, приветствую вас.

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

попробуйте это

int OnInit()
  {
   zzHandle = iCustom(_Symbol, InpTimeFrame, "Examples\\ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep);
   if(zzHandle == INVALID_HANDLE)
     {
      Print("Error creating ZigZag handle");
      return(INIT_FAILED);
     }
     
   ChartIndicatorAdd(0, 0, zzHandle);
   return(INIT_SUCCEEDED);
  }
Инженерия признаков с Python и MQL5 (Часть IV): Распознавание свечных паттернов с помощью UMAP-регрессии Инженерия признаков с Python и MQL5 (Часть IV): Распознавание свечных паттернов с помощью UMAP-регрессии
Методы уменьшения размерности широко используются для повышения производительности моделей машинного обучения. Мы рассмотрим относительно новый метод UMAP (Uniform Manifold Approximation and Projection) — приближение и проекция на равномерном многообразии. Эта новая методика разработана специально для решения проблемы артефактов и искажений в данных, которые присущи традиционным методам. UMAP — это эффективный метод уменьшения размерности, который позволяет группировать похожие свечные графики новым способом, снижая вероятность ошибок на данных, не входящих в выборку, и улучшая результаты торговли.
Искусство ведения логов (Часть 6): Сохранение логов в базу данных Искусство ведения логов (Часть 6): Сохранение логов в базу данных
В статье рассматривается использование баз данных для структурированного и масштабируемого хранения журналов событий. В ней рассматриваются основные понятия, ключевые операции, настройка и реализация обработчика баз данных на языке MQL5. В заключение, подтверждаются полученные результаты и подчеркиваются преимущества описанного подхода для оптимизации и эффективного мониторинга.
Улучшенная оптимизация сталкивающихся тел — Enhanced Colliding Bodies Optimization (ECBO) Улучшенная оптимизация сталкивающихся тел — Enhanced Colliding Bodies Optimization (ECBO)
В статье рассматривается алгоритм Colliding Bodies Optimization (CBO), основанный на физике одномерных столкновений тел. Базовая версия алгоритма не содержит настраиваемых параметров, что делает её простой. Поэтому за основу реализации была взята расширенная версия ECBO, дополненная памятью столкновений и механизмом кроссовера, что позволило алгоритму показать достойные результаты и занять место в рейтинговой таблице.
Нейросети в трейдинге: Потоковые модели с остаточной высокочастотной адаптацией (ResFlow) Нейросети в трейдинге: Потоковые модели с остаточной высокочастотной адаптацией (ResFlow)
Статья знакомит с фреймворком ResFlow, созданным для анализа временной динамики событийных потоков. Фреймворк сочетает низкочастотное моделирование трендов с высокочастотной корректировкой локальных колебаний. Ключевые достоинства — модульность, гибкость интеграции с разными алгоритмами и эффективное повышение временного разрешения без лишней нагрузки на модель.