Автоматизация торговых стратегий на MQL5 (Часть 8): Создание советника с помощью гармонических паттернов Butterfly
Введение
В предыдущей статье (Часть 7) мы разработали советник по сеточной торговле на MetaQuotes Language 5 (MQL5) с использованием динамического масштабирования лотов для оптимизации риска и прибыли. Теперь, в Части 8, мы сосредоточимся на гармонической модели Butterfly — разворотной установке, которая использует точные соотношения Фибоначчи для определения потенциальных точек разворота рынка. Такой подход не только помогает определить четкие сигналы входа и выхода, но и улучшает вашу торговую стратегию за счет автоматизированной визуализации и исполнения. В настоящей статье мы рассмотрим следующее:
В результате вы получите полностью функциональный советник, способный обнаруживать и торговать по гармоническим паттернам Butterfly. Начнём!
План стратегии
Паттерн Butterfly представляет собой точную геометрическую формацию, определяемую пятью ключевыми точками колебания или опорными точками — X, A, B, C и D — и бывает двух основных типов: медвежий паттерн и бычий паттерн. При медвежьем типе паттерна Butterfly структура образует последовательность «максимум-минимум-максимум-минимум-максимум», где пивот X является максимумом колебания, пивот A — минимумом колебания, пивот B — максимумом колебания, пивот C — минимумом колебания и пивот D — максимумом колебания (при этом D расположена выше X). И наоборот, бычий тип паттерна Butterfly формируется в последовательности «минимум-максимум-минимум-максимум-минимум», при этом пивот X является минимумом колебания, а пивот D опускается ниже X. Ниже приведены визуализированные типы паттернов.
Медвежий гармонический паттерн Butterfly:

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

Для выявления паттернов ниже будет представлен наш структурированный подход:
- Определение стороны "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, получаем следующий результат.

С помощью этих данных мы можем выделить точки разворота, и если у нас будет достаточно пивотом, сможем проанализировать и обнаружить паттерны. Вот логика, которую мы реализуем для достижения этой цели.
//--- 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". Эти линии обеспечивают четкую структуру для определения гармонического паттерна и его симметрии. После компиляции и запуска получаем следующие результаты.

На изображения видно, что мы можем как идентифицировать паттерн, так и визуализировать его. Затем можно приступить к нанесению меток, чтобы улучшить его визуальную четкость.
//--- 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 для настройки текста, цвета, размера шрифта и выравнивания для обеспечения четкости. Вот что мы имеем после запуска программы.

Поскольку теперь у нас есть метки, можно перейти к добавлению уровней входа и выхода.
//--- 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 на графике. После компиляции получаем следующий результат.
Медвежий паттерн:

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

На изображениях видно, что можно идентифицировать оба паттерна и корректно их построить. Что нам теперь нужно сделать, так это дождаться подтверждений после японской свечи. И если паттерн все еще существует, это означает, что он не перерисовывался, поэтому можно приступить к открытию соответствующих позиций с уровня входа. Вот логика, которую мы реализуем для достижения этой цели.
//--- 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", чтобы снять предыдущие блокировки.
После компиляции получаем следующий результат.

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

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

Полугодовой период тестирования на 5-минутном графике, в ходе которого было совершено 65 сделок, показывает, что паттерн Butterfly встречается редко, и чем больше процент допуска, тем больше количество сигналов.
Заключение
В заключение, мы успешно разработали советник (EA) на MQL5, который с высокой точностью распознает гармонический паттерн Butterfly и торгует им. Используя распознавание паттернов, проверку пивотов и автоматизированное исполнение сделок, мы создали систему, которая динамично адаптируется к рыночным условиям.
Отказ от ответственности: Содержание настоящей статьи предназначено только для целей обучения. Торговля сопряжена со значительным финансовым риском, а рыночные условия могут быть непредсказуемыми. Хотя описываемая стратегия обеспечивает структурированный подход к гармонической торговле, она не гарантирует прибыльность. Перед внедрением этой программы в реальной среде необходимо провести всестороннее тестирование на истории и выполнить надлежащий контроль рисков.
Применяя эти методы, вы сможете усовершенствовать свои навыки торговли по гармоническим паттернам, усовершенствовать свой технический анализ и усовершенствовать свои алгоритмические торговые стратегии. Желаем удачи в вашем торговом путешествии!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17223
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От начального до среднего уровня: События (I)
Как опубликовать код в CodeBase: Практическое руководство
Создание вероятностного рыночно-нейтрального робота на основе распределения доходностей
Нейросети в трейдинге: Обучение глубоких спайкинговых моделей (SEW-ResNet)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ознакомьтесь с новой статьей: Автоматизация торговых стратегий на MQL5 (часть 8): Построение эксперта с помощью гармонических паттернов "Бабочка".
Автор: Аллан Мунене Мутирия
Ознакомьтесь с новой статьей: Автоматизация торговых стратегий на MQL5 (часть 8): Построение эксперта с помощью гармонических паттернов "Бабочка".
Автор: Аллан Мунене Мутирия