English 中文 Español Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 8): Создание советника с помощью гармонических паттернов Butterfly

Автоматизация торговых стратегий на MQL5 (Часть 8): Создание советника с помощью гармонических паттернов Butterfly

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

Введение

В предыдущей статье (Часть 7) мы разработали советник по сеточной торговле на MetaQuotes Language 5 (MQL5) с использованием динамического масштабирования лотов для оптимизации риска и прибыли. Теперь, в Части 8, мы сосредоточимся на гармонической модели Butterfly — разворотной установке, которая использует точные соотношения Фибоначчи для определения потенциальных точек разворота рынка. Такой подход не только помогает определить четкие сигналы входа и выхода, но и улучшает вашу торговую стратегию за счет автоматизированной визуализации и исполнения. В настоящей статье мы рассмотрим следующее:

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

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


План стратегии

Паттерн Butterfly представляет собой точную геометрическую формацию, определяемую пятью ключевыми точками колебания или опорными точками — X, A, B, C и D — и бывает двух основных типов: медвежий паттерн и бычий паттерн. При медвежьем типе паттерна Butterfly структура образует последовательность «максимум-минимум-максимум-минимум-максимум», где пивот X является максимумом колебания, пивот A — минимумом колебания, пивот B — максимумом колебания, пивот C — минимумом колебания и пивот D — максимумом колебания (при этом D расположена выше X). И наоборот, бычий тип паттерна Butterfly формируется в последовательности «минимум-максимум-минимум-максимум-минимум», при этом пивот X является минимумом колебания, а пивот D опускается ниже X. Ниже приведены визуализированные типы паттернов.

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

BEARISH

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

BULLISH

Для выявления паттернов ниже будет представлен наш структурированный подход:

  • Определение стороны "XA": Начальное перемещение от пивота X к A установит наше исходное расстояние для паттерна.
  • Создание стороны "AB»: Для обоих типов паттернов пивот B в идеале должен возникать примерно на уровне коррекции 78,6% движения XA, подтверждая, что цена изменила значительную часть первоначального движения.
  • Анализ стороны "BC": Эта сторона должна выполнить возврат от 38,2% до 88,6% расстояния XA, обеспечивая стабильную консолидацию перед финальным движением.
  • Настройка стороны "CD": Заключительный отрезок должен составлять от 127% до 161,8% хода XA, завершая паттерн и указывая на точку разворота.

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


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

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

//+------------------------------------------------------------------+
//|                        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 Butterfly 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 XA move)  
input double LotSize      = 0.01;   //--- Lot size for new orders  
input bool   AllowTrading = true;   //--- Enable or disable trading

//---------------------------------------------------------------------------  
//--- Butterfly pattern definition:  
//  
//--- Bullish Butterfly:  
//---   Pivots (X-A-B-C-D): X swing high, A swing low, B swing high, C swing low, D swing high.  
//---   Normally XA > 0; Ideal B = A + 0.786*(X-A); Legs within specified ranges.  
//  
//--- Bearish Butterfly:  
//---   Pivots (X-A-B-C-D): X swing low, A swing high, B swing low, C swing high, D swing low.  
//---   Normally XA > 0; Ideal B = A - 0.786*(A-X); Legs within specified ranges.  
//---------------------------------------------------------------------------

//--- 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_lockedPatternX      = 0;   //--- The key X pivot time for the locked pattern

Здесь мы включаем библиотеку "Trade\Trade.mqh" для доступа к торговым функциям и создаем объект "obj_Trade" для исполнения ордеров. Определяем входные параметры, такие как "PivotLeft" и "PivotRight" для определения точек колебания, "Tolerance" для проверки гармонического отношения, "LotSize" для объема сделки и "AllowTrading" для включения или отключения сделок.

Чтобы отслеживать структуру рынка, мы используем структуру "Pivot", определяемую с помощью struct, в которой хранятся "time", "price" и "isHigh" (значение true для максимумов колебания, значение false - для минимумов). Эти пивоты сохраняются в глобальном динамическом массиве "pivots[]", для исторической справки. Наконец, мы определяем глобальные переменные "g_patternFormationBar" и "g_lockedPatternX", чтобы предотвратить дублирование сделок путем фиксации в обнаруженном паттерне. Далее мы можем определить функции, которые помогут нам визуализировать паттерны на графике.

//+------------------------------------------------------------------+  
//| 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);  
   }  
}  

Определяем набор вспомогательных функций для визуализации структуры ценового действия путем рисования треугольников, линий тренда, пунктирных линий и текстовых меток на графике. Эти функции помогут определить ключевые точки, направления тренда и потенциальные уровни разворота. Функция "DrawTriangle" создает треугольный объект, соединяющий три ценовые точки. Сначала она использует функцию ObjectCreate для определения объекта типа OBJ_TRIANGLE, затем назначает цвет, ширину, а также свойства заливки, используя функцию ObjectSetInteger. Эта функция будет полезна для обозначения гармонических формаций и паттернов ценового действия.

Функция "DrawTrendLine" графически строит линии тренда между двумя ценовыми точками, помогая определить структуру паттерна. Она создает линию тренда с помощью функции ObjectCreate  типа OBJ_TREND , а затем настраивает ее цвет, ширину и стиль. Функция "DrawDottedLine" поможет провести горизонтальную пунктирную линию на указанном уровне цен между двумя временными точками. Это будет полезно для обозначения уровней входа и выхода, обеспечивая визуальное выделение ключевых ценовых зон. Функция устанавливает для стиля линии значение STYLE_DOT для дифференциации. Функция "DrawTextEx" размещает текстовые метки в определенных точках разворота. Она присваивает метке название, устанавливает её цвет, размер шрифта и выравнивание, а также размещает её либо выше, либо ниже уровня цены в зависимости от того, является ли она максимумом или минимумом колебаний. Это помогает аннотировать ключевые уровни пивота для лучшего распознавания паттернов.

Вооружившись этими переменными и функциями, мы можем перейти к обработчику событий 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;  

}

Чтобы гарантировать, что программа исполняет логику только на новых барах во избежание избыточных вычислений, мы используем статическую переменную "lastBarTime" для хранения временной метки последнего обработанного бара. Для каждого тика получаем время последнего подтвержденного бара, используя функцию iTime. Если полученное время совпадает с "lastBarTime", мы завершаем работу раньше, используя оператор return, чтобы избежать повторной обработки. В противном случае мы обновляем "lastBarTime", чтобы пометить новый бар как обработанный. Можем приступить к подготовке массива хранения к приему данных для обработки.

//--- 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;  
   }  
}  

Здесь мы определяем точки разворота максимумов и минимумов колебаний на графике, анализируя исторические ценовые данные. Сначала мы сбрасываем массив "pivots" с помощью функции ArrayResize, чтобы обеспечить свежий анализ. Затем получаем общее количество баров с помощью функции Bars и определяем диапазон для определения пивотов, обеспечивая достаточное количество баров слева и справа для сравнения.

Далее используем for loop для перебора баров от "end-1" до "start", предполагая, что каждый бар может быть потенциальным пивотом. Мы извлекаем максимум и минимум бара, используя функции iHigh и iLow. Затем сравниваем текущий бар с окружающими его барами в диапазоне "PivotLeft" и "PivotRight". Если какой-либо бар в этом диапазоне имеет более высокий максимум, текущий бар не является максимумом колебаний; если какой-либо бар имеет более низкий минимум, он не является минимумом колебаний. Если бар соответствует критериям пивота, создаем структуру "Pivot", сохраняем его "время" с помощью функции iTime, устанавливаем его "цену" в зависимости от того, является ли он максимумом или минимумом, и определяем его тип (true для максимума колебания, false для минимума колебания). Наконец, изменяем размер массива "pivots", используя ArrayResize и добавляем указанный пивот. При выводе этих данных с помощью функции ArrayPrint, получаем следующий результат.

STORED DATA TO THE ARRAY

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

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

//--- Extract the last five pivots as X, A, B, C, and D  
Pivot X = pivots[pivotCount - 5];  
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 Butterfly pattern is found  
bool patternFound = false;  
//--- Check for the high-low-high-low-high (Bearish reversal) structure  
if(X.isHigh && (!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) {  
   //--- Calculate the difference between pivot X and A  
   double diff = X.price - A.price;  
   //--- Ensure the difference is positive  
   if(diff > 0) {  
      //--- Calculate the ideal position for pivot B based on Fibonacci ratio  
      double idealB = A.price + 0.786 * diff;  
      //--- Check if actual B is within tolerance of the ideal position  
      if(MathAbs(B.price - idealB) <= Tolerance * diff) {  
         //--- Calculate the BC leg length  
         double BC = B.price - C.price;  
         //--- Verify that BC is within the acceptable Fibonacci range  
         if((BC >= 0.382 * diff) && (BC <= 0.886 * diff)) {  
            //--- Calculate the CD leg length  
            double CD = D.price - C.price;  
            //--- Verify that CD is within the acceptable Fibonacci range and that D is above X  
            if((CD >= 1.27 * diff) && (CD <= 1.618 * diff) && (D.price > X.price))  
               patternFound = true;  
         }  
      }  
   }  
}  

Здесь мы проверяем, присутствует ли гармонический паттерн Butterfly, анализируя последние пять идентифицированных точек разворота. Сначала определяем общее количество пивотов, используя функцию ArraySize. Если существует менее пяти пивотов, сбрасываем переменные блокировки паттерна ("g_patternFormationBar" и "g_lockedPatternX") и выходим из функции OnTick, чтобы избежать сигналов false. Затем мы извлекаем последние пять пивотов и обозначаем их как "X", "A", "B", "C" и "D", следуя геометрической структуре паттерна. Затем мы инициализируем флаг "patternFound" как false, чтобы отслеживать, выполнены ли условия для допустимого паттерна Butterfly.

В отношении медвежьего разворотного паттерна проверяем последовательность разворотных максимумов и минимумов: "X" (максимум), "A" (минимум), "B" (максимум), "C" (минимум) и "D" (максимум). Если эта структура верна, мы вычисляем разницу отрезков "XA" и используем отношения Фибоначчи для проверки ожидаемых позиций "B", "C" и "D". Пивот "B" должен находиться вблизи уровня коррекции "0,786" отрезка "XA", "BC" должен находиться между "0,382" и "0,886" отрезка "XA", а "CD" должен находиться между "1,27" и "1,618" отрезка "XA", гарантируя, что "D" находится над "Х". Если все эти условия выполнены, подтверждаем паттерн, устанавливая для параметра "patternFound" значение true. Точно также мы поступаем и в отношении бычьего паттерна.

//--- Check for the low-high-low-high-low (Bullish reversal) structure  
if((!X.isHigh) && A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) {  
   //--- Calculate the difference between pivot A and X  
   double diff = A.price - X.price;  
   //--- Ensure the difference is positive  
   if(diff > 0) {  
      //--- Calculate the ideal position for pivot B based on Fibonacci ratio  
      double idealB = A.price - 0.786 * diff;  
      //--- Check if actual B is within tolerance of the ideal position  
      if(MathAbs(B.price - idealB) <= Tolerance * diff) {  
         //--- Calculate the BC leg length  
         double BC = C.price - B.price;  
         //--- Verify that BC is within the acceptable Fibonacci range  
         if((BC >= 0.382 * diff) && (BC <= 0.886 * diff)) {  
            //--- Calculate the CD leg length  
            double CD = C.price - D.price;  
            //--- Verify that CD is within the acceptable Fibonacci range and that D is below X  
            if((CD >= 1.27 * diff) && (CD <= 1.618 * diff) && (D.price < X.price))  
               patternFound = true;  
         }  
      }  
   }  
}  

Если паттерн найден, можно приступить к его отображению на графике.

//--- Initialize a string to store the type of pattern detected  
string patternType = "";  
//--- If a valid pattern is found, determine its type based on the relationship between D and X  
if(patternFound) {  
   if(D.price > X.price)  
      patternType = "Bearish";  //--- Bearish Butterfly indicates a SELL signal  
   else if(D.price < X.price)  
      patternType = "Bullish";  //--- Bullish Butterfly indicates a BUY signal  
}  

//--- If a valid Butterfly pattern is detected  
if(patternFound) {  
   //--- Print a message indicating the pattern type and detection time  
   Print(patternType, " Butterfly 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 = "BF_" + IntegerToString(X.time);  
   
   //--- Choose triangle color based on the pattern type  
   color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed;  
   
   //--- Draw the first triangle connecting pivots X, A, and B  
   DrawTriangle(signalPrefix+"_Triangle1", X.time, X.price, A.time, A.price, B.time, B.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);  
   
   //--- Draw boundary trend lines connecting the pivots for clarity  
   DrawTrendLine(signalPrefix+"_TL_XA", X.time, X.price, A.time, A.price, clrBlack, 2, STYLE_SOLID);  
   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);  
   DrawTrendLine(signalPrefix+"_TL_XB", X.time, X.price, B.time, B.price, clrBlack, 2, STYLE_SOLID);  
   DrawTrendLine(signalPrefix+"_TL_BD", B.time, B.price, D.time, D.price, clrBlack, 2, STYLE_SOLID);  
}

Здесь мы завершаем определение паттерна Butterfly, классифицируя его как бычий или медвежий паттерн и визуально отмечая его на графике. Сначала мы инициализируем строку "patternType", чтобы сохранить данные о том, является ли обнаруженный паттерн "бычьим" или «медвежьим". Если значение "patternFound" равно true, сравниваем пивот "D" с пивотом "X", используя свойство "price". Если "D" выше, чем "X", классифицируем его как "медвежий" паттерн, что сигнализирует о потенциальной возможности продажи. И наоборот, если "D" ниже, чем "X", классифицируем его как "бычий" паттерн, что сигнализирует о потенциальной возможности покупки.

Как только паттерн обнаружен, выводим сообщение, используя функцию Print , чтобы зарегистрировать тип паттерна и время обнаружения. Уникальный "signalPrefix" генерируется с использованием функции IntegerToString и "X.time", чтобы гарантировать, что каждый паттерн содержит отдельные графические объекты. Затем используем функцию "DrawTriangle", чтобы выделить две треугольные секции, образующих паттерн Butterfly. Треугольники окрашены в clrBlue для бычьих паттернов и в "clrRed" для медвежьих паттернов. Первый треугольник соединяет пивоты "X", "A" и "B", в то время как второй соединяет пивоты "B", "C" и "D".

Для дальнейшего улучшения визуализации используем функцию "DrawTrendLine" для создания сплошных черных линий тренда, соединяющих ключевые точки разворота: "XA", "AB", "BC", "CD", "XB" и "BD". Эти линии обеспечивают четкую структуру для определения гармонического паттерна и его симметрии. После компиляции и запуска получаем следующие результаты.

PATTERN DRAWN ON CHART

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

//--- 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_X = (X.isHigh ? X.price + offset : X.price - offset);  
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_X", "X", X.time, textY_X, clrBlack, 11, X.isHigh);  
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 X and B  
datetime centralTime = (X.time + B.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 Butterfly" : "Bearish Butterfly");  
   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);  
}  

Здесь мы добавляем текстовые метки, чтобы отметить паттерн Butterfly на графике. Сначала используем функцию SymbolInfoDouble, чтобы получить значение SYMBOL_POINT для символа и вычислить "смещение" для позиционирования текста. Метки для пивотов ("X", "A", "B", "C", "D") располагаются выше или ниже в зависимости от того, являются ли они максимумами или минимумами. Используем функцию "DrawTextEx", чтобы разместить эти метки черным шрифтом размером 11. Центральная метка "Bullish Butterfly" или "Bearish Butterfly" создается посередине между "X" and "B" с помощью ObjectCreate, ObjectSetString и ObjectSetInteger для настройки текста, цвета, размера шрифта и выравнивания для обеспечения четкости. Вот что мы имеем после запуска программы.

PATTERN WITH LABELS

Поскольку теперь у нас есть метки, можно перейти к добавлению уровней входа и выхода.

//--- Define start and end times for drawing horizontal dotted lines for obj_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, tradeDiff;  
//--- Calculate obj_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;  
   //--- Calculate the total distance to be covered by the obj_Trade  
   tradeDiff = TP3Level - entryPriceLevel;  
   //--- Set TP1 at one-third of the total move  
   TP1Level = entryPriceLevel + tradeDiff/3;  
   //--- Set TP2 at two-thirds of the total move  
   TP2Level = entryPriceLevel + 2*tradeDiff/3;  
} 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;  
   //--- Calculate the total distance to be covered by the obj_Trade  
   tradeDiff = entryPriceLevel - TP3Level;  
   //--- Set TP1 at one-third of the total move  
   TP1Level = entryPriceLevel - tradeDiff/3;  
   //--- Set TP2 at two-thirds of the total move  
   TP2Level = entryPriceLevel - 2*tradeDiff/3;  
}  

//--- 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);  

Здесь мы рассчитываем уровни входа в сделку и тейк-профита (TP) на основе обнаруженного паттерна. Начинаем с использования функции PeriodSeconds для определения продолжительности построения горизонтальных торговых уровней. Затем используем функцию SymbolInfoDouble для получения входной цены, применяя SYMBOL_ASK для покупки и SYMBOL_BID для продажи. Устанавливаем TP3, используя переменную "C.price", и вычисляем общий торговый диапазон. Мы вычисляем TP1 и TP2, деля этот диапазон на трети. Используем функцию "DrawDottedLine", чтобы нарисовать уровни входа и уровни TP различными цветами. Затем определяем подходящую временную координату метки, используя функцию PeriodSeconds для лучшего позиционирования. Создаем метку входа, используя функцию DoubleToString, чтобы точно отформатировать цену. Наконец, применяем функцию "DrawTextEx" для отображения меток входа и меток TP на графике. После компиляции получаем следующий результат.

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

COMPLETE BEARISH PATTERN

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

COMPLETE 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_lockedPatternX = X.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 obj_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 X pivot  
   if(g_lockedPatternX == X.time) {  
      Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening obj_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;  
         //--- For a Bullish pattern, execute a BUY obj_Trade  
         if(patternType=="Bullish") {  //--- BUY signal  
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK);  
            double diffTrade = TP2Level - entryPriceTrade;  
            stopLoss = entryPriceTrade - diffTrade * 3;  
            takeProfit = TP2Level;  
            tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Butterfly Signal");  
            if(tradeResult)  
               Print("Buy order opened successfully.");  
            else  
               Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription());  
         }  
         //--- For a Bearish pattern, execute a SELL obj_Trade  
         else if(patternType=="Bearish") {  //--- SELL signal  
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_BID);  
            double diffTrade = entryPriceTrade - TP2Level;  
            stopLoss = entryPriceTrade + diffTrade * 3;  
            takeProfit = TP2Level;  
            tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Butterfly 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 obj_Trade  
         Print("A position is already open for ", _Symbol, ". No new obj_Trade executed.");  
      }  
   }  
   else {  
      //--- If the pattern has changed, update the lock with the new formation bar and X pivot  
      g_patternFormationBar = currentBarIndex;  
      g_lockedPatternX = X.time;  
      Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation.");  
      return;  
   }  
}  
}  
else {  
//--- If no valid Butterfly pattern is detected, reset the pattern lock variables  
g_patternFormationBar = -1;  
g_lockedPatternX = 0;  
}  

Этот раздел управляет фиксацией паттернов и исполнением сделок. Сначала определяем индекс текущего бара с помощью функции Bars и присваиваем ему значение "currentBarIndex". Если ни один паттерн не зафиксирован, на что указывает "g_patternFormationBar" == -1, присваиваем значение "currentBarIndex" для "g_patternFormationBar" и сохраняем время пивота X в "g_lockedPatternX", выводя сообщение с помощью функции "Print", о том, что паттерн обнаружен и ожидает подтверждения. Если обнаруженный паттерн все еще формируется на том же баре, используем функцию Print для отображения сообщения о том, что паттерн перерисовывается, и сделка не совершается.

Если текущий бар выходит за пределы зафиксированного формирующего бара, проверяем, остается ли паттерн действительным, сравнивая "g_lockedPatternX" с текущим временем пивота X. Если он совпадает, подтверждаем паттерн и готовимся к исполнению сделки. Перед размещением ордера используем функцию PositionSelect, чтобы убедиться в отсутствии существующей позиции и проверить "AllowTrading". Если "бычий" паттерн подтверждается, мы получаем запрашиваемую цену, используя функцию SymbolInfoDouble с помощью SYMBOL_ASK, рассчитываем стоп-лосс и тейк-профит на основе "TP2Level" и исполняем ордер на покупку, используя функцию "obj_Trade.Buy". Если сделка прошла успешно, используем функцию "Print" для отображения сообщения с подтверждением. В противном случае используем функцию "obj_Trade.ResultRetcodeDescription" для вывода причины неудачи.

Для "медвежьего" паттерна мы получаем цену bid с помощью функции SymbolInfoDouble , используя SYMBOL_BID, вычисляем торговые уровни и исполняем ордер на продажу с помощью функции "obj_Trade.Sell", выводя соответствующие сообщения об успешном выполнении или неудаче с помощью функции Print . Если позиция уже существует, новая сделка не исполняется, а сообщение выводится с помощью функции "Print". Если зафиксированный пивот X изменяется, обновляем "g_patternFormationBar" и "g_lockedPatternX", указывая, что паттерн изменился и ожидает подтверждения. Если допустимый паттерн не обнаружен, сбрасываем "g_patternFormationBar" и "g_lockedPatternX", чтобы снять предыдущие блокировки.

После компиляции получаем следующий результат.

CONFIRMED PATTERN

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


Тестирование на истории и оптимизация

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

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

GRAPH

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

REPORT

Полугодовой период тестирования на 5-минутном графике, в ходе которого было совершено 65 сделок, показывает, что паттерн Butterfly встречается редко, и чем больше процент допуска, тем больше количество сигналов.


Заключение

В заключение, мы успешно разработали советник (EA) на MQL5, который с высокой точностью распознает гармонический паттерн Butterfly и торгует им. Используя распознавание паттернов, проверку пивотов и автоматизированное исполнение сделок, мы создали систему, которая динамично адаптируется к рыночным условиям.

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

Применяя эти методы, вы сможете усовершенствовать свои навыки торговли по гармоническим паттернам, усовершенствовать свой технический анализ и усовершенствовать свои алгоритмические торговые стратегии. Желаем удачи в вашем торговом путешествии!

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

Прикрепленные файлы |
От начального до среднего уровня: События (I) От начального до среднего уровня: События (I)
Учитывая всё, что ,было показано до настоящего момента, я думаю, что теперь мы можем начать реализовывать некое приложение для запуска какого-либо символа непосредственно на графике. Однако сначала нам нужно поговорить о довольно запутанном понятии для новичков, а именно о том, что приложения, разработанные на MQL5 и предназначенные для отображения на графике, создаются не так, как мы видели до сих пор. В этой статье мы начнем разбираться в этом немного лучше.
Как опубликовать код в CodeBase: Практическое руководство Как опубликовать код в CodeBase: Практическое руководство
В статье рассмотрим на реальных примерах процесс публикации различных типов программ для терминала в Библиотеке исходных кодов на языке MQL5.
Создание вероятностного рыночно-нейтрального робота на основе распределения доходностей Создание вероятностного рыночно-нейтрального робота на основе распределения доходностей
Рыночно-нейтральная торговая стратегия на основе эмпирического распределения доходностей представляет альтернативу классическим методам технического анализа, заменяя прогнозирование направления цены статистическим размещением ордеров в точках вероятного достижения. Статья подробно разбирает математический аппарат расчета перцентилей, алгоритмы взвешивания объемов позиций по вероятности срабатывания и механизмы адаптации к изменению рыночных условий через экспирацию сетки. Приводится полная реализация на MQL5.
Нейросети в трейдинге: Обучение глубоких спайкинговых моделей (SEW-ResNet) Нейросети в трейдинге: Обучение глубоких спайкинговых моделей (SEW-ResNet)
Приглашаем к знакомству с фреймворком SEW-ResNet, который позволяет строить глубокие спайковые модели без проблем деградации и с эффективным управлением градиентами. В этой статье мы демонстрируем, как реализовать базовый спайковый нейрон и его алгоритмы средствами MQL5.