English Deutsch 日本語
preview
Автоматизация торговых стратегий в MQL5 (Часть 30): Создание гармонического паттерна AB=CD на основе Price Action с визуализацией

Автоматизация торговых стратегий в MQL5 (Часть 30): Создание гармонического паттерна AB=CD на основе Price Action с визуализацией

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

Введение

В своей предыдущей статье (Часть 29) мы создали систему распознавания паттерна "Гартли" в MetaQuotes Language 5 (MQL5). В ней реализовано определение бычьих и медвежьих паттернов гармонических паттернов "Гартли" с помощью точных уровней Фибоначчи, автоматизация сделок с помощью рассчитанных уровней входа, стоп-лосса и тейк-профита, а также визуализация паттернов с помощью графических объектов, таких как треугольники и линии тренда. В Части 30 мы внедрим систему распознавания паттерна AB=CD. В то время как система "Гартли" основана на обнаружении специфических многозвенных гармонических структур, определяемых несколькими уровнями Фибоначчи, система AB=CD ориентирована на выявление паттернов, формирующихся при обнаружении двух эквивалентных ценовых сегментов (AB и CD) с помощью точек разворота и определенных коэффициентов коррекции и расширения. Это приводит к более простой, но динамичной идентификации паттернов. Система AB=CD автоматизирует открытие сделок с использованием динамических входов и многоуровневых целевых показателей тейк-профита. Это улучшает визуализацию с помощью треугольников, линий тренда и меток для четкого представления паттернов. В статье рассмотрим следующие темы:

  1. Изучение структуры гармонического паттерна AB=CD
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

В итоге у вас будет стабильная стратегия в MQL5 для торговли на основе гармонического паттерна AB=CD, готовая к настройке. Перейдём к реализации!


Изучение структуры гармонического паттерна AB=CD

Паттерн AB=CD представляет собой структуру гармонической торговли, которая определяет потенциальные зоны разворота посредством четырех ключевых точек разворота — A, B, C и D. Она существует в бычьей и медвежьей формах, использует уровни Фибоначчи для точного определения торговых сетапов с высокой вероятностью. При бычьем паттерне AB=CD структура образует последовательность максимум-минимум-максимум-минимум, где A - это свинг-хай, B - свинг-лоу, C - свинг-хай и D - свинг-лоу (ниже B), при этом отрезки AB и CD равны по длине или связаны коэффициентами коррекции и расширения Фибоначчи; медвежий паттерн AB=CD следует последовательности минимум-максимум-минимум-максимум, где D находится выше B. Вот визуализация паттернов:

Медвежий гармонический паттерн AB=CD:

BEARISH HARMONIC AB=CD PATTERN

Бычий гармонический паттерн AB=CD:

BULLISH HARMONIC AB=CD PATTERN

Наш подход включает в себя обнаружение этих точек разворота в заданном диапазоне баров, проверку паттерна путем обеспечения коррекции отрезка BC от 0,382 до 0,886 от AB, а также расширения отрезка CD в диапазоне 1,13 - 2,618 от BC, визуализацию паттерна с помощью графических объектов, таких как треугольники и линии тренда, для большей наглядности. Кроме того, подход включает в себя открытие сделок в точке D с рассчитанным стоп-лоссом и несколькими уровнями тейк-профита на основе коррекций Фибоначчи для получения прибыли из ожидаемых разворотов. Приступим к реализации!


Реализация средствами MQL5

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

//+------------------------------------------------------------------+
//|                                              ABCD Pattern EA.mq5 |
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property description "This EA trades based on AB=CD Strategy"
#property strict

//--- Include the trading library for order functions
#include <Trade\Trade.mqh> //--- Include Trade library
CTrade obj_Trade; //--- Instantiate a obj_Trade object

//--- Input parameters for user configuration
input int PivotLeft = 5;          // Number of bars to the left for pivot check
input int PivotRight = 5;         // Number of bars to the right for pivot check
input double Tolerance = 0.10;    // Allowed deviation (10% of AB move)
input double LotSize = 0.01;      // Lot size for new orders
input bool AllowTrading = true;   // Enable or disable trading
//---------------------------------------------------------------------------

//--- Structure for a pivot point
struct Pivot {
   datetime time; //--- Bar time of the pivot
   double price;  //--- Pivot price (High for swing high, low for swing low)
   bool isHigh;   //--- True if swing high; false if swing low
};
//--- Global dynamic array for storing pivots in chronological order
Pivot pivots[]; //--- Declare a dynamic array to hold identified pivot points
//--- Global variables to lock in a pattern (avoid trading on repaint)
int g_patternFormationBar = -1; //--- Bar index where the pattern was formed (-1 means none)
datetime g_lockedPatternA = 0;  //--- The key A pivot time for the locked pattern

Чтобы заложить основу для паттерна AB=CD, сначала подключим библиотеку "<Trade\Trade.mqh>" и создадим экземпляр объекта CTrade под названием "obj_Trade" для управления торговыми операциями, такими как открытие сделок на покупку и продажу. Затем перейдём к определению пяти входных параметров для обеспечения пользовательской настройки: Значения параметров "PivotLeft" и "PivotRight" равны 5 барам каждый, чтобы установить диапазон ретроспективного анализа для поиска точек разворота. "Tolerance" равно 0,10, чтобы разрешить отклонение в 10% в уровнях Фибоначчи, "LotSize" равно 0,01 для объема торговли, а значение "AllowTrading" равно true для включения автоматической торговли.

Далее определим структуру "Pivot" с параметрами "time" (datetime), "price" (double) и "isHigh" (bool) для хранения точек разворота цены, объявим "pivots" как динамический массив для хранения этих точек и инициализируем глобальные переменные "g_patternFormationBar" значением -1 для отслеживания бара, где формируется паттерн и "g_lockedPatternA" значением 0 для сохранения времени точки разворота А для подтверждения паттерна. Обратим внимание на использование A вместо X для соответствия фокусу внимания паттерна AB=CD на отрезках AB и CD. Данная схема создает базовую основу для обнаружения и торговли по паттернам AB=CD. Для визуализации можно использовать функции для рисования линий, меток и треугольников.

//+------------------------------------------------------------------+
//| Helper: Draw a filled triangle                                   |
//+------------------------------------------------------------------+
void DrawTriangle(string name, datetime t1, double p1, datetime t2, double p2, datetime t3, double p3, color cl, int width, bool fill, bool back) {
   //--- Attempt to create a triangle object with three coordinate points
   if(ObjectCreate(0, name, OBJ_TRIANGLE, 0, t1, p1, t2, p2, t3, p3)) {
      //--- Set the triangle's color
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);
      //--- Set the triangle's line style to solid
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      //--- Set the line width of the triangle
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
      //--- Determine if the triangle should be filled
      ObjectSetInteger(0, name, OBJPROP_FILL, fill);
      //--- Set whether the object is drawn in the background
      ObjectSetInteger(0, name, OBJPROP_BACK, back);
   }
}
//+------------------------------------------------------------------+
//| Helper: Draw a trend line                                        |
//+------------------------------------------------------------------+
void DrawTrendLine(string name, datetime t1, double p1, datetime t2, double p2, color cl, int width, int style) {
   //--- Create a trend line object connecting two points
   if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) {
      //--- Set the trend line's color
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);
      //--- Set the trend line's style (solid, dotted, etc.)
      ObjectSetInteger(0, name, OBJPROP_STYLE, style);
      //--- Set the width of the trend line
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   }
}
//+------------------------------------------------------------------+
//| Helper: Draw a dotted trend line |
//+------------------------------------------------------------------+
void DrawDottedLine(string name, datetime t1, double p, datetime t2, color lineColor) {
   //--- Create a horizontal trend line at a fixed price level with dotted style
   if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p, t2, p)) {
      //--- Set the dotted line's color
      ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
      //--- Set the line style to dotted
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
      //--- Set the line width to 1
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   }
}
//+------------------------------------------------------------------+
//| Helper: Draw anchored text label (for pivots)                    |
//| If isHigh is true, anchor at the bottom (label appears above);   |
//| if false, anchor at the top (label appears below).               |
//+------------------------------------------------------------------+
void DrawTextEx(string name, string text, datetime t, double p, color cl, int fontsize, bool isHigh) {
   //--- Create a text label object at the specified time and price
   if(ObjectCreate(0, name, OBJ_TEXT, 0, t, p)) {
      //--- Set the text of the label
      ObjectSetString(0, name, OBJPROP_TEXT, text);
      //--- Set the color of the text
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);
      //--- Set the font size for the text
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontsize);
      //--- Set the font type and style
      ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold");
      //--- Anchor the text depending on whether it's a swing high or low
      if(isHigh)
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      else
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP);
      //--- Center-align the text
      ObjectSetInteger(0, name, OBJPROP_ALIGN, ALIGN_CENTER);
   }
}

Перейдём к реализации функций визуализации с целью создания четких графических представлений гармонического паттерна AB=CD и его торговых уровней. Сначала мы создадим функцию "DrawTriangle", которая использует ObjectCreate для рисования заполненного треугольника (OBJ_TRIANGLE), определяемого тремя точками с параметрами времени ("t1", "t2", "t3") и ценами ("p1", "p2", "p3"). Установим OBJPROP_COLOR в указанный цвет, "OBJPROP_STYLE" в значение STYLE_SOLID, "OBJPROP_WIDTH" в заданную ширину, "OBJPROP_FILL" для включения или выключения заливки и "OBJPROP_BACK" для установки положения фона или переднего плана с помощью ObjectSetInteger функции.

Затем перейдём к реализации функции "DrawTrendLine", которая создает линию тренда (OBJ_TREND) между двумя точками. Наконец, разработаем функцию "DrawTextEx", которая создает текстовую метку (OBJ_TEXT) в координатах ("t", "p») с помощью "ObjectCreate". Установим "OBJPROP_TEXT" в указанный текст, "OBJPROP_COLOR", "OBJPROP_FONTSIZE" и OBJPROP_FONT в "Arial Bold" с помощью ObjectSetString и "ObjectSetInteger". Закрепим текст выше для свинг-хай или ниже для свинг-лоу на основе "isHigh" с помощью OBJPROP_ANCHOR и центрируя текст с помощью свойства "OBJPROP_ALIGN". Теперь можно перейти к обработчику OnTickи попытаться определить точки разворота, которые можно использовать позже для распознавания паттернов. Вот логика, которую мы используем для достижения этой цели.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Declare a static variable to store the time of the last processed bar
   static datetime lastBarTime = 0;
   //--- Get the time of the current confirmed bar
   datetime currentBarTime = iTime(_Symbol, _Period, 1);
   //--- If the current bar time is the same as the last processed, exit
   if(currentBarTime == lastBarTime)
      return;
   //--- Update the last processed bar time
   lastBarTime = currentBarTime;
  
   //--- Clear the pivot array for fresh analysis
   ArrayResize(pivots, 0);
   //--- Get the total number of bars available on the chart
   int barsCount = Bars(_Symbol, _Period);
   //--- Define the starting index for pivot detection (ensuring enough left bars)
   int start = PivotLeft;
   //--- Define the ending index for pivot detection (ensuring enough right bars)
   int end = barsCount - PivotRight;
  
   //--- Loop through bars from 'end-1' down to 'start' to find pivot points
   for(int i = end - 1; i >= start; i--) {
      //--- Assume current bar is both a potential swing high and swing low
      bool isPivotHigh = true;
      bool isPivotLow = true;
      //--- Get the high and low of the current bar
      double currentHigh = iHigh(_Symbol, _Period, i);
      double currentLow = iLow(_Symbol, _Period, i);
      //--- Loop through the window of bars around the current bar
      for(int j = i - PivotLeft; j <= i + PivotRight; j++) {
         //--- Skip if the index is out of bounds
         if(j < 0 || j >= barsCount)
            continue;
         //--- Skip comparing the bar with itself
         if(j == i)
            continue;
         //--- If any bar in the window has a higher high, it's not a swing high
         if(iHigh(_Symbol, _Period, j) > currentHigh)
            isPivotHigh = false;
         //--- If any bar in the window has a lower low, it's not a swing low
         if(iLow(_Symbol, _Period, j) < currentLow)
            isPivotLow = false;
      }
      //--- If the current bar qualifies as either a swing high or swing low
      if(isPivotHigh || isPivotLow) {
         //--- Create a new pivot structure
         Pivot p;
         //--- Set the pivot's time
         p.time = iTime(_Symbol, _Period, i);
         //--- Set the pivot's price depending on whether it is a high or low
         p.price = isPivotHigh ? currentHigh : currentLow;
         //--- Set the pivot type (true for swing high, false for swing low)
         p.isHigh = isPivotHigh;
         //--- Get the current size of the pivots array
         int size = ArraySize(pivots);
         //--- Increase the size of the pivots array by one
         ArrayResize(pivots, size + 1);
         //--- Add the new pivot to the array
         pivots[size] = p;
      }
   }
}

Здесь мы реализуем начальную часть логики OnTick функции. Сначала объявим статическую переменную "lastBarTime", инициализированную значением 0, чтобы отслеживать последний обработанный бар и сравнивать с "currentBarTime", полученной из iTime при сдвиге 1 для текущего символа и периода. Во избежание избыточной обработки, если значение не изменилось завершаем работу, также обновим "lastBarTime" при обнаружении нового бара. Затем очистим массив "pivots" с помощью ArrayResize для обеспечения нового анализа. Далее получим общее количество баров с помощью Bars, установим диапазон поиска точек разворота с помощью "start" в качестве "PivotLeft" и "end" в качестве общего количества баров минус "PivotRight". Выполним перебор баров от "end - 1" до "start".

Для каждого бара предположим, что это свинг-хай ("isPivotHigh" равно true) и свинг-лоу ("isPivotLow" равно true), получаем его максимальную и минимальную цены с помощью "iHigh" и "iLow". Затем подтвердим точку разворота путем проверки окружающих баров в пределах "PivotLeft" и "PivotRight" с помощью iHigh и iLow. Аннулируем точку разворота, если у какого-либо соседнего бара более высокий максимум или более низкий минимум. Наконец, если бар удовлетворяет условиям точки разворота, создадим структуру "Pivot", установим для нее "time" с помощью параметра "iTime", "price" на максимум или минимум на основе параметра "isPivotHigh" и флага "isHigh". Затем добавим ее в массив "pivots" с помощью ArrayResize и сохраним. При выводе структуры точки разворота получим следующий массив данных.

PIVOTS DATA

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

//--- Determine the total number of pivots found
int pivotCount = ArraySize(pivots);
//--- If fewer than four pivots are found, the pattern cannot be formed
if(pivotCount < 4) {
   //--- Reset pattern lock variables
   g_patternFormationBar = -1;
   g_lockedPatternA = 0;
   //--- Exit the OnTick function
   return;
}

//--- Extract the last four pivots as A, B, C, and D
Pivot A = pivots[pivotCount - 4];
Pivot B = pivots[pivotCount - 3];
Pivot C = pivots[pivotCount - 2];
Pivot D = pivots[pivotCount - 1];

//--- Initialize a flag to indicate if a valid AB=CD pattern is found
bool patternFound = false;
//--- Initialize pattern type
string patternType = "";
double used_retr = 0.0;
double used_ext = 0.0;
//--- Check for the high-low-high-low (Bullish reversal) structure
if(A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) {
   //--- Calculate the difference between pivot A and B
   double diff = A.price - B.price;
   //--- Ensure the difference is positive
   if(diff > 0) {
      //--- Calculate the BC leg length
      double BC = C.price - B.price;
      double retrace = BC / diff;
      //--- Calculate the CD leg length
      double CD = C.price - D.price;
      double extension = CD / BC;
      //--- Define fib ratios
      double fib_retr[] = {0.382, 0.5, 0.618, 0.786, 0.886};
      double fib_ext[] = {2.618, 2.0, 1.618, 1.272, 1.13};
      bool valid = false;
      for(int k = 0; k < ArraySize(fib_retr); k++) {
         if(MathAbs(retrace - fib_retr[k]) <= Tolerance && MathAbs(extension - fib_ext[k]) <= Tolerance) {
            valid = true;
            used_retr = fib_retr[k];
            used_ext = fib_ext[k];
            break;
         }
      }
      if(valid && (D.price < B.price)) {
         patternFound = true;
         patternType = "Bullish";
      }
   }
}
//--- Check for the low-high-low-high (Bearish reversal) structure
if((!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) {
   //--- Calculate the difference between pivot B and A
   double diff = B.price - A.price;
   //--- Ensure the difference is positive
   if(diff > 0) {
      //--- Calculate the BC leg length
      double BC = B.price - C.price;
      double retrace = BC / diff;
      //--- Calculate the CD leg length
      double CD = D.price - C.price;
      double extension = CD / BC;
      //--- Define fib ratios
      double fib_retr[] = {0.382, 0.5, 0.618, 0.786, 0.886};
      double fib_ext[] = {2.618, 2.0, 1.618, 1.272, 1.13};
      bool valid = false;
      for(int k = 0; k < ArraySize(fib_retr); k++) {
         if(MathAbs(retrace - fib_retr[k]) <= Tolerance && MathAbs(extension - fib_ext[k]) <= Tolerance) {
            valid = true;
            used_retr = fib_retr[k];
            used_ext = fib_ext[k];
            break;
         }
      }
      if(valid && (D.price > B.price)) {
         patternFound = true;
         patternType = "Bearish";
      }
   }
}

Сначала определим общее количество точек разворота с помощью "ArraySize(pivots)", которые хранятся в "pivotCount". Выйдем, если найдено менее 4 точек разворота, сбросив значения "g_patternFormationBar" и "g_lockedPatternA" на -1 и 0, поскольку для паттерна AB=CD требуются точки A, B, C и D. Затем извлечем последние четыре точки разворота из массива "pivots", присвоим "A" (самые первые), "B", "C" и "D" (самые последние) для формирования структуры паттерна.

Затем проверим наличие бычьего паттерна AB=CD (максимум A, минимум B, максимум C, минимум D), вычисляя разницу отрезка AB ("A.price - B.price"). Проверим, положительна ли она, вычислим длину отрезка BC ("C.price - B.price") и коэффициент коррекции относительно AB, вычислим длину отрезка CD ("C.price - D.price") и его коэффициента расширения относительно BC. Определим коэффициенты коррекции Фибоначчи (0.382, 0.5, 0.618, 0.786, 0.886) и коэффициенты расширения (2.618, 2.0, 1.618, 1.272, 1.13) и проверим, находятся ли оба коэффициента в пределах "Tolerance", обеспечивая при этом, что "D.price < B.price". Установим для "patternFound" значение true, для "patternType" - значение "Bullish" и сохраним совпадающие "used_retr" и "used_ext". Наконец проверим наличие медвежьего паттерна AB=CD (минимум A, максимум B, минимум C, максимум D), используя аналогичные вычисления для AB ("B.price - A.price"), BC ("B.price - C.price") и CD ("D.price - C.price"). Проверим соответствие тем же уровням Фибоначчи и обеспечим, что "D.price > B.price". Установим для параметра "patternFound" значение true, а для параметра "patternType" значение "Bearish", если оно действительно. Если паттерн найден, мы можем приступить к его визуализации на графике.

//--- If a valid AB=CD pattern is detected
if(patternFound) {
   //--- Print a message indicating the pattern type and detection time
   Print(patternType, " AB=CD pattern detected at ", TimeToString(D.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS));
  
   //--- Create a unique prefix for all graphical objects related to this pattern
   string signalPrefix = "AB_" + IntegerToString(A.time);
  
   //--- Choose triangle color based on the pattern type
   color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed;
  
   //--- Draw the first triangle connecting pivots A, B, and C
   DrawTriangle(signalPrefix+"_Triangle1", A.time, A.price, B.time, B.price, C.time, C.price,
                triangleColor, 2, true, true);
   //--- Draw the second triangle connecting pivots B, C, and D
   DrawTriangle(signalPrefix+"_Triangle2", B.time, B.price, C.time, C.price, D.time, D.price,
                triangleColor, 2, true, true);
}

Для визуализации паттерна, если действительный паттерн обнаружен ("patternFound" в значении true), выводим в лог обнаружение с помощью функции Print. Выводим параметр "patternType" ("Bullish" или "Bearish") и время точки разворота D, отформатированное с помощью TimeToString, включая дату, минуты и секунды. Далее перейдем к созданию уникального идентификатора "signalPrefix", объединяя "AB_" с "A.time", преобразованным в строку с помощью функции IntegerToString для обеспечения уникального именования объектов графика.

Затем для "triangleColor" установим синий цвет для бычьих паттернов или красный для медвежьих, чтобы различать их визуально. Наконец вызовем "DrawTriangle" дважды для визуализации паттерна: сначала с целью нарисовать треугольник ABC, соединяющий точки разворота A, B и C. Затем с целью нарисовать треугольник BCD, соединяющий точки разворота B, C и D. Используем "signalPrefix" с суффиксами "_Triangle1" и "_Triangle2", соответствующие показатели времени и цен точек разворота, "triangleColor", шириной 2. А также включим заливку и отображение фона с помощью флагов со значением true. Получим следующий результат.

TRIANGLE SET

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

//--- Draw boundary trend lines connecting the pivots for clarity
DrawTrendLine(signalPrefix+"_TL_AB", A.time, A.price, B.time, B.price, clrBlack, 2, STYLE_SOLID);
DrawTrendLine(signalPrefix+"_TL_BC", B.time, B.price, C.time, C.price, clrBlack, 2, STYLE_SOLID);
DrawTrendLine(signalPrefix+"_TL_CD", C.time, C.price, D.time, D.price, clrBlack, 2, STYLE_SOLID);

//--- Retrieve the symbol's point size to calculate offsets for text positioning
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
//--- Calculate an offset (15 points) for positioning text above or below pivots
double offset = 15 * point;

//--- Determine the Y coordinate for each pivot label based on its type
double textY_A = (A.isHigh ? A.price + offset : A.price - offset);
double textY_B = (B.isHigh ? B.price + offset : B.price - offset);
double textY_C = (C.isHigh ? C.price + offset : C.price - offset);
double textY_D = (D.isHigh ? D.price + offset : D.price - offset);

//--- Draw text labels for each pivot with appropriate anchoring
DrawTextEx(signalPrefix+"_Text_A", "A", A.time, textY_A, clrBlack, 11, A.isHigh);
DrawTextEx(signalPrefix+"_Text_B", "B", B.time, textY_B, clrBlack, 11, B.isHigh);
DrawTextEx(signalPrefix+"_Text_C", "C", C.time, textY_C, clrBlack, 11, C.isHigh);
DrawTextEx(signalPrefix+"_Text_D", "D", D.time, textY_D, clrBlack, 11, D.isHigh);

//--- Calculate the central label's time as the midpoint between pivots A and C
datetime centralTime = (A.time + C.time) / 2;
//--- Set the central label's price at pivot D's price
double centralPrice = D.price;
//--- Create the central text label indicating the pattern type
if(ObjectCreate(0, signalPrefix+"_Text_Center", OBJ_TEXT, 0, centralTime, centralPrice)) {
   ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_TEXT,
      (patternType=="Bullish") ? "Bullish AB=CD" : "Bearish AB=CD");
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_FONTSIZE, 11);
   ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_FONT, "Arial Bold");
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_ALIGN, ALIGN_CENTER);
}

Далее улучшаем визуализацию обнаруженных паттернов, добавляя подробные графические объекты для четкого отображения структуры паттерна. Сначала нарисуем три сплошные линии тренда, используя "DrawTrendLine" с уникальным "signalPrefix", чтобы соединить ключевые точки разворота: AB, BC и CD, каждый из которых использует значения времени и цены точки разворота (например, "A.time", "A.price"), установим OBJPROP_COLOR в значение "clrBlack", "OBJPROP_WIDTH" в значение 2 и "OBJPROP_STYLE" в значение "STYLE_SOLID" с помощью ObjectSetInteger для отображения контуров паттерна. Затем получим размер символа в пунктах с помощью функции SymbolInfoDouble(_Symbol, SYMBOL_POINT) и вычислим смещение в 15 пунктов для позиционирования меток. Определим Y-координаты ("textY_A", "textY_B", "textY_C", "textY_D") путем прибавления или вычитания смещения на основе того, является ли каждая точка разворота максимумом ("isHigh" равно true) или минимумом свинга, чтобы разместить метки выше максимумов или ниже минимумов.

Затем мы используем "DrawTextEx" для создания текстовых меток для точек разворота A, B, C и D с "signalPrefix" и суффиксами типа "_Text_A", отображающими соответствующую букву, расположенную в параметре времени точки разворота и скорректированную по координате Y. Для привязки используется "clrBlack", размер шрифта 11 и статус точки разворота "isHigh". Наконец вычислим центральную позицию метки в точке "centralTime" как середину между "A.time" и "C.time", а также "centralPrice" в точке "D.price", создавая текстовый объект с помощью ObjectCreate с именем "signalPrefix + '_Text_Center'". Установим OBJPROP_TEXT в значение "Bullish AB=CD" или "Bearish AB=CD" на основе "patternType". Настроим "OBJPROP_COLOR" в значение "clrBlack", "OBJPROP_FONTSIZE" - на 11, OBJPROP_FONT на "Arial Bold" и "OBJPROP_ALIGN" на "ALIGN_CENTER" с помощью ObjectSetString и "ObjectSetInteger". Этим мы обеспечим полное визуальное представление структуры и типа паттерна на графике. После запуска программы получаем следующую визуализацию.

PATTERN WITH LABELS AND EDGES

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

//--- Define start and end times for drawing horizontal dotted lines for trade levels
datetime lineStart = D.time;
datetime lineEnd = D.time + PeriodSeconds(_Period)*2;

//--- Declare variables for entry price and take profit levels
double entryPriceLevel, TP1Level, TP2Level, TP3Level;
//--- Calculate pattern range (CD length)
double patternRange = (patternType=="Bullish") ? (C.price - D.price) : (D.price - C.price);
//--- Calculate trade levels based on whether the pattern is Bullish or Bearish
if(patternType=="Bullish") { //--- Bullish → BUY signal
   //--- Use the current ASK price as the entry
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   //--- Set TP3 at pivot C's price
   TP3Level = C.price;
   //--- Set TP1 at 0.382 fib retrace from D to C
   TP1Level = D.price + 0.382 * patternRange;
   //--- Set TP2 at 0.618 fib retrace from D to C
   TP2Level = D.price + 0.618 * patternRange;
} else { //--- Bearish → SELL signal
   //--- Use the current BID price as the entry
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   //--- Set TP3 at pivot C's price
   TP3Level = C.price;
   //--- Set TP1 at 0.382 fib retrace from D to C
   TP1Level = D.price - 0.382 * patternRange;
   //--- Set TP2 at 0.618 fib retrace from D to C
   TP2Level = D.price - 0.618 * patternRange;
}

//--- Draw dotted horizontal lines to represent the entry and TP levels
DrawDottedLine(signalPrefix+"_EntryLine", lineStart, entryPriceLevel, lineEnd, clrMagenta);
DrawDottedLine(signalPrefix+"_TP1Line", lineStart, TP1Level, lineEnd, clrForestGreen);
DrawDottedLine(signalPrefix+"_TP2Line", lineStart, TP2Level, lineEnd, clrGreen);
DrawDottedLine(signalPrefix+"_TP3Line", lineStart, TP3Level, lineEnd, clrDarkGreen);

//--- Define a label time coordinate positioned just to the right of the dotted lines
datetime labelTime = lineEnd + PeriodSeconds(_Period)/2;

//--- Construct the entry label text with the price
string entryLabel = (patternType=="Bullish") ? "BUY (" : "SELL (";
entryLabel += DoubleToString(entryPriceLevel, _Digits) + ")";
//--- Draw the entry label on the chart
DrawTextEx(signalPrefix+"_EntryLabel", entryLabel, labelTime, entryPriceLevel, clrMagenta, 11, true);

//--- Construct and draw the TP1 label
string tp1Label = "TP1 (" + DoubleToString(TP1Level, _Digits) + ")";
DrawTextEx(signalPrefix+"_TP1Label", tp1Label, labelTime, TP1Level, clrForestGreen, 11, true);

//--- Construct and draw the TP2 label
string tp2Label = "TP2 (" + DoubleToString(TP2Level, _Digits) + ")";
DrawTextEx(signalPrefix+"_TP2Label", tp2Label, labelTime, TP2Level, clrGreen, 11, true);

//--- Construct and draw the TP3 label
string tp3Label = "TP3 (" + DoubleToString(TP3Level, _Digits) + ")";
DrawTextEx(signalPrefix+"_TP3Label", tp3Label, labelTime, TP3Level, clrDarkGreen, 11, true);

Чтобы определить и визуализировать торговые уровни, установим значение "lineStart" на время точки разворота D ("D.time"), а "lineEnd" - на два периода вперед, используя "PeriodSeconds(_Period) * 2". Объявим переменные "entryPriceLevel", "TP1Level", "TP2Level" и "TP3Level" для расчетов сделок. Затем вычислим "patternRange" как длину отрезка CD ("C.price - D.price" для бычьего паттерна, "D.price - C.price" для медвежьего). Для бычьего паттерна установим "entryPriceLevel" равным цене Ask с помощью SymbolInfoDouble, "TP3Level" равным цене C, "TP1Level" равным "D.price + 0.382 * patternRange" и "TP2Level" равным "D.price + 0.618 * patternRange". Для медвежьего паттерна мы используем цену Bid, установим "TP3Level" равным цене C, "TP1Level" равным "D.price - 0.382 * patternRange" и "TP2Level" равным "D.price - 0.618 * patternRange".

Далее, используя функцию "DrawDottedLine", нарисуем четыре пунктирные горизонтальные линии: линию входа на уровне "entryPriceLevel" пурпурного цвета и линии тейк-профита на уровнях "TP1Level" (глубокий лесной зеленый), "TP2Level" (зеленый) и "TP3Level" (темно-зеленый), от "lineStart" до "lineEnd". Наконец, установим значение "labelTime" равным "lineEnd" плюс половина периода, создадим текстовые метки с ценами, отформатированными с помощью DoubleToString (например, "BUY (price)" or "SELL (price)" для входа, "TP1 (price)" и т. д.). Используем "DrawTextEx" для отрисовки этих меток в "labelTime" с соответствующими цветами, размером шрифта 11 и привязкой над уровнями цен. После компиляции получаем следующий результат.

Медвежий паттерн:

BEARISH PATTERN

Бычий паттерн:

BULLISH PATTERN

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

//--- Retrieve the index of the current bar
int currentBarIndex = Bars(_Symbol, _Period) - 1;
//--- If no pattern has been previously locked, lock the current pattern formation
if(g_patternFormationBar == -1) {
   g_patternFormationBar = currentBarIndex;
   g_lockedPatternA = A.time;
   //--- Print a message that the pattern is detected and waiting for confirmation
   Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar.");
   return;
}
//--- If still on the same formation bar, the pattern is considered to be repainting
if(currentBarIndex == g_patternFormationBar) {
   Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No trade yet.");
   return;
}
//--- If we are on a new bar compared to the locked formation
if(currentBarIndex > g_patternFormationBar) {
   //--- Check if the locked pattern still corresponds to the same A pivot
   if(g_lockedPatternA == A.time) {
      Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening trade on bar ", currentBarIndex, ".");
      //--- Update the pattern formation bar to the current bar
      g_patternFormationBar = currentBarIndex;
      //--- Only proceed with trading if allowed and if there is no existing position
      if(AllowTrading && !PositionSelect(_Symbol)) {
         double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0;
         point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
         bool tradeResult = false;
         //--- Determine next extension for SL
         double next_ext = 0.0;
         if(MathAbs(used_ext - 1.13) < 0.05) next_ext = 1.272;
         else if(MathAbs(used_ext - 1.272) < 0.05) next_ext = 1.618;
         else if(MathAbs(used_ext - 1.618) < 0.05) next_ext = 2.0;
         else if(MathAbs(used_ext - 2.0) < 0.05) next_ext = 2.618;
         else if(MathAbs(used_ext - 2.618) < 0.05) next_ext = 3.618;
         else next_ext = used_ext * 1.618; // fallback
         //--- For a Bullish pattern, execute a BUY trade
         if(patternType=="Bullish") { //--- BUY signal
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double BC_leg = C.price - B.price;
            stopLoss = C.price - next_ext * BC_leg;
            if(stopLoss > D.price) stopLoss = D.price - 10 * point;
            takeProfit = TP2Level;
            tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "AB=CD Signal");
            if(tradeResult)
               Print("Buy order opened successfully.");
            else
               Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription());
         }
         //--- For a Bearish pattern, execute a SELL trade
         else if(patternType=="Bearish") { //--- SELL signal
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double BC_leg = B.price - C.price;
            stopLoss = C.price + next_ext * BC_leg;
            if(stopLoss < D.price) stopLoss = D.price + 10 * point;
            takeProfit = TP2Level;
            tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "AB=CD Signal");
            if(tradeResult)
               Print("Sell order opened successfully.");
            else
               Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription());
         }
      }
      else {
         //--- If a position is already open, do not execute a new trade
         Print("A position is already open for ", _Symbol, ". No new trade executed.");
      }
   }
   else {
      //--- If the pattern has changed, update the lock with the new formation bar and A pivot
      g_patternFormationBar = currentBarIndex;
      g_lockedPatternA = A.time;
      Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation.");
      return;
   }
}
}
else {
   //--- If no valid AB=CD pattern is detected, reset the pattern lock variables
   g_patternFormationBar = -1;
   g_lockedPatternA = 0;
}

На этом этапе мы завершаем реализацию логики в обработчике OnTick для обнаружения паттернов, управляя открытием сделок и подтверждением паттерна в отношении обнаруженного гармонического паттерна AB=CD. Сначала получим индекс текущего бара с помощью функции "Bars(_Symbol, _Period) - 1" и сохраним его в "currentBarIndex". Затем, если ни один паттерн не сохранен ("g_patternFormationBar == -1"), установим "g_patternFormationBar" в "currentBarIndex", сохраняем время точки разворота А в "g_lockedPatternA" с помощью "A.time", регистрируем обнаружение, указывающее на ожидание подтверждения. Завершаем работу. Далее, если паттерн все еще находится на формирующем баре ("currentBarIndex == g_patternFormationBar"), выходим, чтобы избежать преждевременной торговли.

Наконец, если сформировался новый бар ("currentBarIndex > g_patternFormationBar") и пивот А совпадает с "g_lockedPatternA", подтверждаем паттерн, выводим в лог, обновляем "g_patternFormationBar" и проверяем, разрешена ли торговля с помощью "AllowTrading" и нет ли открытых позиций с помощью PositionSelect. Определим следующее расширение Фибоначчи ("next_ext") для стоп-лосса на основе "used_ext" (например, от 1,13 до 1,272, до 3,618 или резервного значения "used_ext * 1,618"). Для бычьего паттерна установим "entryPriceTrade" равным цене Ask, рассчитаем "BC_leg" как "C.price - B.price", установим "stopLoss" в значение "C.price - next_ext * BC_leg" (скорректированное до "D.price - 10 * point" если находится выше D). Установим "takeProfit" равным "TP2Level" и откроем сделку на покупку с помощью "obj_Trade.Buy", используя "LotSize" и "AB=CD Signal", выводим в лог успешное или неудачное выполнение. Для медвежьего паттерна используем цену Bid, расчитаем "BC_leg" как "B.price - C.price". Установим "stopLoss" равным "C.price + next_ext * BC_leg" (скорректированное до "D.price + 10 * point" если находится ниже D). Откроем сделку на продажу с помощью "obj_Trade.Sell". Если торговля запрещена или позиция уже существует, в журнал выводится сообщение о том, что новая сделка не выполняется. Если паттерн меняется, обновим сохранение и ждем. Если паттерн не найден, сбросим глобальные переменные. После компиляции получаем следующий результат.

Медвежий сигнал:

BEARISH SIGNAL

Бычий сигнал:

BULLISH SIGNAL

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


Тестирование на истории

После тщательного тестирования на истории мы получили следующие результаты.

График тестирования на истории:

График

Отчет о тестировании на истории:

REPORT


Заключение

В заключение отметим, что нами разработана система паттерна AB=CD в MQL5. Она использует ценовое движение для выявления бычьих и медвежьих гармонических паттернов AB=CD с точными уровнями коррекции и расширения Фибоначчи, автоматизирует сделки с расчетными точками входа, стоп-лосса и многоуровневыми точками тейк-профита. Также визуализирует паттерны с помощью графических объектов, таких как треугольники и линии тренда.

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

Используя представленные концепции и методы реализации, можно адаптировать эту систему паттерна AB=CD к своему стилю торговли, улучшая свои алгоритмические стратегии. Удачной торговли! 

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Louis Wetzel
Louis Wetzel | 9 апр. 2026 в 18:13

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

Это мое мнение.

Луис Б. Ветцель

Allan Munene Mutiiria
Allan Munene Mutiiria | 9 апр. 2026 в 18:57
Louis Wetzel #:

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

Это мое мнение.

Луис Б. Ветцель

Здравствуйте. Спасибо за отзыв. Представленные идеи предназначены для автоматизации вышеупомянутой торговой стратегии. Может быть, что-то было неясно или непонятно, что требует уточнения? Спасибо.
Разработка торговой стратегии: Метод Butterfly Oscillator Разработка торговой стратегии: Метод Butterfly Oscillator
В этой статье мы продемонстрировали, как можно преобразовать увлекательную математическую концепцию Butterfly Curve («кривая-бабочка») в практичный торговый инструмент. Мы разработали индикатор Butterfly Oscillator и создали на его основе базовую торговую стратегию. Эта стратегия эффективно сочетает уникальные циклические сигналы осциллятора с традиционным подтверждением тренда на основе скользящих средних, формируя системный подход к выявлению потенциальных точек входа на рынок.
Разработка инструментария для анализа Price Action (Часть 46): Создание интерактивного советника по уровням коррекции Фибоначчи с интеллектуальной визуализацией на MQL5 Разработка инструментария для анализа Price Action (Часть 46): Создание интерактивного советника по уровням коррекции Фибоначчи с интеллектуальной визуализацией на MQL5
Инструменты Фибоначчи – одни из наиболее популярных инструментов технического анализа. В этой статье мы создадим советник Interactive Fibonacci EA, который строит уровни коррекции и расширения, динамически реагирующие на движение цены, выдает алерты в реальном времени, аккуратно оформляет линии и показывает бегущую строку в стиле новостной ленты. Еще одно важное преимущество этого советника – гибкость: вы можете вручную ввести на графике значения максимума (A) и минимума (B) движения, что дает точный контроль над анализируемым рыночным диапазоном.
Архитектура машинного обучения для MetaTrader 5 (Часть 12): Калибровка вероятностей для финансового машинного обучения Архитектура машинного обучения для MetaTrader 5 (Часть 12): Калибровка вероятностей для финансового машинного обучения
Классификаторы на основе деревьев обычно чрезмерно уверены: истинные доли выигрышей около 0,55 отображаются как 0,65–0,80 и завышают размеры позиций и доли Келли. В этой статье представлены afml.calibration и CalibratorCV, которые генерируют прогнозы вне обучающего фолда (OOF) через PurgedKFold и подбирают изотоническую регрессию или масштабирование Платта. Мы определяем оценку Брайера, ECE и MCE, а также показываем диагностику, которая прослеживает некалиброванность до размеров позиций, реализованного P&L и распределений Sharpe по путям CPCV для поддержки торговли без утечек и с корректным размером позиции.
Архитектура машинного обучения для MetaTrader 5 (Часть 11): Критерий Келли, интеграция правил проп-фирмы и динамический бэктест по CPCV Архитектура машинного обучения для MetaTrader 5 (Часть 11): Критерий Келли, интеграция правил проп-фирмы и динамический бэктест по CPCV
Сигнал определения размера позиции из Части 10 скорректирован с учётом одновременности сигналов, но не учитывает соотношение выигрыша и проигрыша, не реагирует на жёсткий бюджет просадки и не валидируется по комбинаторным путям. В этой статье рассматриваются три дополнения: двухэтапная архитектура, в которой множитель выплат Келли применяется поверх get_signal, сохраняя коррекцию перекрытия активных меток и добавляя асимметрию выигрыша/проигрыша; слой интеграции с проп-фирмой, который непрерывно калибрует параметр сигмоиды w по оставшемуся бюджету просадки в правилах FundedNext Stellar 2-Step; и CPCV-фреймворк бэктеста, который моделирует заново инициализированное состояние счёта по всем путям φ[N, k], формируя распределение коэффициента Шарпа и выполняя аудит PBO.