Разработка инструментария для анализа движения цен (Часть 19): ZigZag Analyzer
Введение
Линии тренда лежат в основе технического анализа и работы с ценовым действием (т.н. price action). Это самая основная база, которой пользуются трейдеры на рынках форекс, криптовалют, сырьевых товаров, акций и деривативов. Основная задача линий тренда, при условии, что они построены корректно, — выявлять рыночные тенденции и помогать трейдерам находить потенциальные возможности.
При этом они одинаково полезны как для дневных трейдеров, так и для тех, кто торгует на более коротких интервалах. В рамках этой серии по разработке инструментария для анализа ценового действия представляю вашему вниманию инструмент под названием ZigZag Analyzer. Он строит линии тренда с использованием индикатора ZigZag для выявления точек разворота, которые и служат основой для построения трендовых линий. Язык MQL5предлагает все необходимое для автоматизации торговых систем и создания продвинутых инструментов, такие как ZigZag Analyzer, которые адаптируются к рыночным условиям и помогают принимать решения в реальном времени.
Сначала мы рассмотрим сами линии тренда, затем — индикатор ZigZag. Будет представлен алгоритм системы и код на MQL5. В конце проанализируем результаты и сделаем выводы. Содержание:
Линии тренда
Линия тренда — это наклонная линия, соединяющая значимые ценовые точки, как правило более высокие минимумы или более низкие максимумы, и проецируемая в будущее в виде уровня поддержки или сопротивления. Она указывает на вероятное направление и импульс ценового движения. Построение линии тренда начинается с определения общего направления рынка. Восходящий тренд характеризуется последовательностью более высоких максимумов и более высоких минимумов, а нисходящий тренд — более низких максимумов и более низких минимумов. Для примера рассмотрим график с нисходящей структурой. Линия тренда на нем соединяет понижающихся максимумов, на которых происходит смена направлений. В фазе консолидации цен формирование нескольких линий тренда может выявлять паттерны, усиливающие торговые сигналы. Графически это можно представить так:

Рис 1. Линии тренда
График иллюстрирует два ключевых понятия: поддержку и сопротивление. Уровень поддержки — это область, где нисходящее движение цены часто замедляется или останавливается, поскольку покупатели становятся достаточно активными, чтобы поглотить давление продаж. При использовании линий тренда поддержка обычно отображается восходящими линиями, соединяющими важные минимумы. Напротив, уровень сопротивления — это область, где восходящее движение цены часто приостанавливается из-за усиленного давления продаж. В контексте линий тренда сопротивление представлено нисходящими линиями, соединяющими значимые максимумы. Трейдеры часто строят линии тренда на нескольких временных горизонтах, чтобы определить, куда движется рынок (вверх или вниз) в краткосрочном, среднесрочном или долгосрочном масштабе.Ценность линий тренда для технического анализа заключается в том, что они помогают участникам рынка оценить общее направление движения цены. Они соединяют значимые максимумы или минимумы за выбранный временной период и наглядно показывают, движется ли рынок вверх, вниз или находится в боковом диапазоне. Это особенно полезно для трейдеров и краткосрочных инвесторов, которые при принятии решений используют, в том числе, данные о тренде.
Индикатор ZigZag
Хотя точное происхождение индикатора ZigZag документально не подтверждено, некоторые источники приписывают его разработку Биллу Вульфу. Это трейдер S&P 500 и создатель Волн Вульфа — метода, отчасти схожего с волнами Эллиотта, но имеющего собственные принципы построения графиков. Волны Вульфа состоят из пяти волн, иллюстрирующих движение спроса и предложения к равновесной цене (ссылка). Индикатор ZigZag выделяет значимые ценовые развороты, превышающие заданный порог, обычно выраженный в процентах. Когда ценовое колебание превышает этот порог, индикатор наносит новую точку и проводит прямую линию от предыдущей точки, отфильтровывая при этом мелкие колебания. Благодаря этому основные тренды становятся более заметными на различных таймфреймах.
Трейдеры могут настраивать процентный порог (например, 4% или 5%), чтобы принимать во внимание большее или меньшее количество ценовых колебаний в зависимости от волатильности актива или стиля торговли. Эта же гибкость позволяет настроить, как индикатор 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)

Рис 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Инженерия признаков с Python и MQL5 (Часть IV): Распознавание свечных паттернов с помощью UMAP-регрессии
Искусство ведения логов (Часть 6): Сохранение логов в базу данных
Улучшенная оптимизация сталкивающихся тел — Enhanced Colliding Bodies Optimization (ECBO)
Нейросети в трейдинге: Потоковые модели с остаточной высокочастотной адаптацией (ResFlow)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Я нахожу этот индикатор очень интересным. Он бы очень помог мне в моем техническом анализе, так как я трачу на него больше всего времени, чтобы найти правильный тренд, поддержку и сопротивление. Я скачала код, он скомпилировался правильно, но когда я добавила его на график, никакой информации не отражается. Может, я что-то делаю не так? Я снова прикрепил видео. С уважением.
>Здравствуйте, Кристина, приветствую вас.
Я нахожу этот индикатор очень интересным. Он бы очень помог мне в моем техническом анализе, так как я трачу на него больше всего времени, чтобы найти правильный тренд, поддержку и сопротивление. Я скачала код, он скомпилировался правильно, но когда я добавила его на график, никакой информации не отражается. Может, я что-то делаю не так? Я снова прикрепил видео. С уважением.
Диего Эррера
>Здравствуйте, Кристина, приветствую вас.
Я нахожу этот индикатор очень интересным. Он бы очень помог мне в моем техническом анализе, так как я трачу на него больше всего времени, чтобы найти правильный тренд, поддержку и сопротивление. Я скачала код, он скомпилировался правильно, но когда я добавила его на график, никакой информации не отражается. Может, я что-то делаю не так? Я снова прикрепил видео. С уважением.
>Здравствуйте, Кристина, приветствую вас.
Я нахожу этот индикатор очень интересным. Он бы очень помог мне в моем техническом анализе, так как я трачу на него больше всего времени, чтобы найти правильный тренд, поддержку и сопротивление. Я скачала код, он скомпилировался правильно, но когда я добавила его на график, никакой информации не отражается. Может, я что-то делаю не так? Я снова прикрепил видео. С уважением.
попробуйте это