Автоматизация торговых стратегий в MQL5 (Часть 29): Создание системы торговли по гармоническому паттерну "Гартли" на основе Price Action
Введение
В своей предыдущей статье (Часть 28) мы создали систему паттерна "Летучая мышь" в MetaQuotes Language 5 (MQL5). В ней определялись бычьи и медвежьи паттерны “Летучая мышь” с помощью точных уровней Фибоначчи. В Части 29 мы создадим программу для паттерна "Гартли", которая определяет бычьи и медвежьи гармонические паттерны "Гартли" с использованием точек разворота и определенных коррекций Фибоначчи, открывая сделки с динамически определяемой точкой входа и многоуровневыми целями по прибыли. Она дополнена визуальными треугольниками, линиями тренда и метками для четкого отображения паттернов. В статье рассмотрим следующие темы:
- Изучение структуры гармонического паттерна "Гартли"
- Реализация средствами MQL5
- Тестирование на истории
- Заключение
В итоге у вас будет тщательно продуманная стратегия в MQL5 для торговли на основе гармонического паттерна "Гартли", готовая к настройке. Перейдём к реализации!
Изучение структуры гармонического паттерна "Гартли"
Паттерн "Гартли" — это гармонический паттерн, определяемый пятью ключевыми экстремумами — X, A, B, C и D — и существует в двух формах: бычий паттерн и медвежий паттерн. Каждый из них предназначен для выявления высоковероятных зон разворота с помощью точных уровней Фибоначчи. При бычьем паттерне "Гартли" структура образует последовательность "минимум-максимум-минимум-максимум-минимум", где точка X - свинг-лоу, точка A - свинг-хай, точка B - свинг-лоу (коррекция приблизительно 0,618 от XA), точка C - свинг-хай (продление от 0,382 до 0,886 от AB) и точка D - свинг-лоу (коррекция 0,786 от XA, расположена выше X). Напротив, медвежий паттерн "Гартли" формирует последовательность "максимум-минимум-максимум-минимум-максимум", где точка X является свинг-хай, точка A — свинг-лоу, точка B — свинг-хай, точка C — свинг-лоу, а точка D — свинг-хай (коррекция 0,786 от XA и располагается ниже X). Ниже представлены визуализированные типы паттернов.
Бычий гармонический паттерн "Гартли":

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

Для выявления паттернов ниже приведен наш подход:
- Определение волны XA: Первоначальное движение из точки X в точку A устанавливает направление паттерна (вверх для бычьего паттерна и вниз для медвежьего) и служит ориентиром для последующих расчётов по уровням Фибоначчи.
- Создание волны "AB": Точка B должна откатиться примерно на 0,618 длины XA, указывая на значительную, но контролируемую коррекцию первоначального импульса.
- Анализ волны "BC": Этот отрезок должен находиться в диапазоне от 0,382 до 0,886 длины AB, создавая контрдвижение, предшествующее финальной зоне разворота.
- Настройка волны "CD": Заключительный отрезок должен корректироваться на 0,786 длины отрезка XA, отмечая потенциальную зону разворота в точке D, где паттерн завершается и генерируется торговый сигнал.
Применяя эти геометрические критерии и критерии, основанные на коэффициентах Фибоначчи, наша торговая система будет систематически выявлять валидные паттерны "Гартли" в ценовых данных. Как только паттерн будет подтвержден, система визуализирует образование на графике с помощью треугольников, линий тренда и меток для точек X, A, B, C и D, а также уровней сделок для входа и тейк-профитов. Это позволит автоматически открывать сделки в точке D с рассчитанным стоп-лоссом и многоуровневыми зонами тейк-профита, используя прогностические свойства паттерна для выявления рыночных разворотов. Приступим к реализации!
Реализация средствами MQL5
Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку "Индикаторы" (Indicators), перейдите на вкладку "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нам нужно будет объявить некоторые глобальные переменные, которые будем использовать во всей программе.
//+------------------------------------------------------------------+ //| Gartley 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 Gartley 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 //--------------------------------------------------------------------------- //--- Gartley pattern definition: // //--- Bullish Gartley: //--- 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.618*(A-X); Legs within specified ranges; D at 0.786 retracement. // //--- Bearish Gartley: //--- 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.618*(X-A); Legs within specified ranges; D at 0.786 retracement. //--------------------------------------------------------------------------- //--- 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>" и создадим экземпляр объекта 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_lockedPatternX" значением 0 для сохранения времени пивота X для подтверждения паттерна. Закладываем основную структуру для обнаружения и торговли на паттернах "Гартли". Для визуализации можно использовать функции для рисования линий, меток и треугольников.
//+------------------------------------------------------------------+ //| 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), определенного тремя точками с параметрами времени ("t1", "t2", "t3") и ценами ("p1", "p2", "p3"). Установим OBJPROP_COLOR в указанный цвет, "OBJPROP_STYLE" в значение STYLE_SOLID, "OBJPROP_WIDTH" в заданную ширину, "OBJPROP_FILL" для включения или выключения заливки и "OBJPROP_BACK" для установки положения фона или переднего плана с помощью ObjectSetInteger функции.
Затем перейдём к реализации функции "DrawTrendLine", которая создает линию тренда (OBJ_TREND) между двумя точками. Далее реализуем функцию "DrawDottedLine", которая рисует горизонтальную пунктирную линию ("OBJ_TREND") по заданной цене от "t1" до "t2". Наконец, разработаем функцию "DrawTextEx", которая создает текстовую метку (OBJ_TEXT) в координатах ("t", "p"), установим "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 и сохраним. При выводе структуры пивота получим следующий массив данных.

С помощью этих данных мы можем выделить пивотные точки, и если у нас будет достаточно пивотов, сможем проанализировать и обнаружить паттерны. Вот логика, которую мы реализуем для достижения этой цели.
//--- 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 Gartley pattern is found bool patternFound = false; //--- Initialize pattern type string patternType = ""; //--- 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.618 * diff; //--- Check if actual B is within tolerance of the ideal position if(MathAbs(B.price - idealB) <= Tolerance * diff) { //--- Calculate the AB leg length double AB = B.price - A.price; //--- Calculate the BC leg length double BC = B.price - C.price; //--- Verify that BC is within the acceptable Fibonacci range if((BC >= 0.382 * AB) && (BC <= 0.886 * AB)) { //--- Calculate the retracement double retrace = D.price - A.price; //--- Verify that the retracement is within tolerance of 0.786 and that D is below X if(MathAbs(retrace - 0.786 * diff) <= Tolerance * diff && (D.price < X.price)) { patternFound = true; patternType = "Bearish"; } } } } } //--- 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.618 * diff; //--- Check if actual B is within tolerance of the ideal position if(MathAbs(B.price - idealB) <= Tolerance * diff) { //--- Calculate the AB leg length double AB = A.price - B.price; //--- Calculate the BC leg length double BC = C.price - B.price; //--- Verify that BC is within the acceptable Fibonacci range if((BC >= 0.382 * AB) && (BC <= 0.886 * AB)) { //--- Calculate the retracement double retrace = A.price - D.price; //--- Verify that the retracement is within tolerance of 0.786 and that D is above X if(MathAbs(retrace - 0.786 * diff) <= Tolerance * diff && (D.price > X.price)) { patternFound = true; patternType = "Bullish"; } } } } }
Сначала определим общее количество пивотов с помощью "ArraySize(pivots)", которые хранятся в "pivotCount". Выйдем, если найдено менее 5 пивотов, сбросив значения "g_patternFormationBar" и "g_lockedPatternX" на -1 и 0, поскольку для паттерна "Гартли" требуются точки X, A, B, C и D. Затем извлечем последние пять пивотов из массива "pivots", присвоим "X" (самые ранние), "A", "B", "C" и "D" (самые поздние) для формирования структуры паттерна.
Затем проверим наличие медвежьего паттерна "Гартли" (максимум X, минимум A, максимум B, минимум C, максимум D), вычислив разницу в длине XA ("X.price - A.price"). Убедимся, что она положительная, и вычислим идеальную точку B как "A.price + 0.618 * diff", и подтвердим, что B находится в пределах "Tolerance * diff" с помощью MathAbs. Затем проверим BC (от 0,382 до 0,886 отрезка AB) и подтвердим коррекцию AD (0,786 отрезка XA с D ниже X). Установим для параметра "patternFound" значение true, а для "patternType" - "Bearish", если паттерн действителен. Наконец проверим наличие бычьего паттерна "Гартли" (минимум X, максимум A, минимум B, максимум C, минимум D), рассчитаем XA как "A.price - X.price", убедимся в его положительном значении. Проверим, что B находится на уровне коррекции 0,618, BC — в пределах диапазона 0,382 - 0,886 от AB, а AD — на уровне 0,786 длины XA, при этом D находится выше X. Установим "patternFound" в значение true, а "patternType" в значение "Bullish", если паттерн действителен. Если паттерн обнаружен, можно перейти к его визуализации на графике.
//--- If a valid Gartley pattern is detected if(patternFound) { //--- Print a message indicating the pattern type and detection time Print(patternType, " Gartley 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 = "GA_" + 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); }
Если действительный паттерн обнаружен ("patternFound" в значении true), регистрируем обнаружение с помощью функции Print, выводя параметр "patternType" ("Bullish" или "Bearish") и время пивота D, отформатированное с помощью TimeToString, включая дату, минуты и секунды. Далее перейдем к созданию уникального идентификатора "signalPrefix", объединяя "GA_" с "X.time", преобразованным в строку с помощью IntegerToString функции. Затем для "triangleColor" установим синий цвет для бычьих паттернов или красный для медвежьих. Наконец вызовем "DrawTriangle" дважды для визуализации паттерна: сначала с целью нарисовать треугольник XAB, соединяющий X, A и B. Затем с целью нарисовать треугольник BCD, соединяющий B, C и D. Используем "signalPrefix" с суффиксами "_Triangle1" и "_Triangle2", соответствующие показатели времени и цен пивотов, "triangleColor", шириной 2. А также включим заливку и отображение фона с помощью флагов со значением 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); //--- 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 Gartley" : "Bearish Gartley"); 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", чтобы соединить ключевые пивотные точки: XA, AB, BC, CD, XB и BD, соединяя ключевые пивотные точки по времени и цене (например, "X.time", "X.price"), установим OBJPROP_COLOR в значение black, "OBJPROP_WIDTH" в значение 2 и "OBJPROP_STYLE" в значение "STYLE_SOLID" с помощью ObjectSetInteger для выделения отрезков паттерна. Затем мы получаем размер символа в пунктах с помощью функции "SymbolInfoDouble(_Symbol, SYMBOL_POINT)" и вычисляем смещение в 15 пунктов для позиционирования меток. Определим Y-координаты ("textY_X", "textY_A", "textY_B", "textY_C", "textY_D") путем добавления или вычитания смещения на основе того, является ли каждый пивот максимумом ("isHigh" true) или минимумом свинга, чтобы разместить метки выше максимумов или ниже минимумов.
Затем мы используем "DrawTextEx" для создания текстовых меток для пивотов X, A, B, C и D с "signalPrefix" и суффиксами типа "_Text_X", отображающие соответствующую букву, расположенную во времени пивота и скорректированную по координате Y. Для привязки используется "clrBlack", размер шрифта 11 и статус пивота "isHigh". Наконец вычислим центральную позицию метки в точке "centralTime" как середину между "X.time" и "B.time", а также "centralPrice" в точке "D.price", создавая текстовый объект с помощью ObjectCreate с именем "signalPrefix + '_Text_Center'". Установим OBJPROP_TEXT в значение "Bullish Gartley" или "Bearish Gartley" на основе "patternType", и настроим "OBJPROP_COLOR" в значение "clrBlack", "OBJPROP_FONTSIZE" - на 11, OBJPROP_FONT на "Arial Bold" и "OBJPROP_ALIGN" на "ALIGN_CENTER" с помощью ObjectSetString и "ObjectSetInteger". Эта логика обеспечивает полное визуальное представление структуры и типа паттерна на графике. При запуске программы получим следующий результат.

На изображении видно, что мы добавили края и метки на паттерн, сделав его более наглядным. Следующий шаг — определить торговые уровни паттерна
//--- 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, tradeDiff; //--- 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; //--- Calculate the total distance to be covered by the 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 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);
Чтобы определить и визуализировать торговые уровни для обнаруженного паттерна, установим значение "lineStart" на время пивота D ("D.time"), а "lineEnd" - на два периода вперед, используя "PeriodSeconds(_Period) * 2". Объявим переменные "entryPriceLevel", "TP1Level", "TP2Level", "TP3Level" и "tradeDiff" для расчетов сделок. Затем, для бычьего паттерна ("patternType == 'Bullish'"), установим "entryPriceLevel" равным текущей цене Ask с помощью SymbolInfoDouble, "TP3Level" равным цене пивота C, вычислим "tradeDiff" как "TP3Level - entryPriceLevel", а "TP1Level" и "TP2Level" вычислим как одну треть и две трети от "tradeDiff", прибавленные к "entryPriceLevel". Для медвежьего паттерна используем цену Bid, установим "TP3Level" равным цене точки C, вычислим "tradeDiff" как "entryPriceLevel - TP3Level", а "TP1Level" и "TP2Level" вычислим, вычитая одну треть и две трети от разницы между ценами сделок.
Далее, используя функцию "DrawDottedLine", нарисуем четыре пунктирные горизонтальные линии: линию входа на уровне "entryPriceLevel" пурпурного цвета и линии тейк-профита на уровнях "TP1Level" (глубокий лесной зеленый), "TP2Level" (зеленый) и "TP3Level" (темно-зеленый), от "lineStart" до "lineEnd". Наконец, установим значение "labelTime" равным "lineEnd" плюс половина периода, создадим текстовые метки с ценами, отформатированными с помощью DoubleToString (например, "BUY (price)" or "SELL (price)" для входа, "TP1 (price)" и т. д.). Используем "DrawTextEx" для отрисовки этих меток в "labelTime" с соответствующими цветами, размером шрифта 11 и привязкой над уровнями цен, обеспечив наглядную визуализацию уровней входа в сделку по паттерну Гартли и тейк-профита. После компиляции получаем следующий результат.
Медвежий паттерн:

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

На изображениях видно, что мы корректно нанесли торговые уровни. На этом остаётся лишь открыть реальные позиции.
//--- 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 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 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 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, "Gartley 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 diffTrade = entryPriceTrade - TP2Level; stopLoss = entryPriceTrade + diffTrade * 3; takeProfit = TP2Level; tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Gartley 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 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 Gartley pattern is detected, reset the pattern lock variables g_patternFormationBar = -1; g_lockedPatternX = 0; }
На этом этапе мы завершаем реализацию логики в обработчике OnTick, управляя открытием сделок и подтверждением паттерна для обнаруженного паттерна. Сначала получим индекс текущего бара с помощью функции "Bars(_Symbol, _Period) - 1" и сохраним его в "currentBarIndex". Затем, если ни один паттерн не заблокирован ("g_patternFormationBar == -1"), установим "g_patternFormationBar" в "currentBarIndex", сохраняем время пивота X в "g_lockedPatternX" с помощью "X.time", регистрируем обнаружение, указывающее на ожидание подтверждения. Завершаем работу. Далее, если паттерн все еще находится на формирующем баре ("currentBarIndex == g_patternFormationBar"), считаем, что паттерн ещё перерисовывается и выходим, чтобы избежать преждевременной торговли.
Наконец, если сформировался новый бар ("currentBarIndex > g_patternFormationBar") и пивот X совпадает с "g_lockedPatternX", подтверждаем паттерн, выводим в лог, обновляем "g_patternFormationBar" и проверяем, разрешена ли торговля и нет ли открытых позиций с помощью PositionSelect Для бычьего паттерна установим "entryPriceTrade" равным цене Ask, рассчитаем "diffTrade" как "TP2Level - entryPriceTrade", установим "stopLoss" в три раза больше этого значения расстояния ниже. Установим "takeProfit" равным "TP2Level" и откроем сделку на покупку с помощью "obj_Trade.Buy", используя "LotSize" и "Gartley Signal", регистрируем успешное или неудачное выполнение. Для медвежьего паттерна используем цену Bid, установим "stopLoss" в три раза выше и откроем сделку на продажу с помощью "obj_Trade.Sell". Если торговля запрещена или позиция уже существует, в журнал выводится сообщение о том, что новая сделка не выполняется. Если паттерн меняется, обновим сохранение и ждем. Если паттерн не найден, сбросим глобальные переменные. После компиляции получаем следующий результат.
Медвежий сигнал:

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

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

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

Заключение
В заключение отметим, что нами разработана система паттерна "Гартли" в MQL5. Она использует ценовое движение для выявления бычьих и медвежьих гармонических паттернов "Гартли" с точными уровнями Фибоначчи, автоматизирует сделки с расчетными точками входа, стоп-лосса и многоуровневыми точками тейк-профита, а также визуализирует паттерны с помощью графических объектов, таких как треугольники и линии тренда.
Отказ от ответственности: Содержание настоящей статьи предназначено только для целей обучения. Торговля сопряжена со значительными финансовыми рисками, а волатильность рынка может привести к убыткам. Тщательное тестирование на истории и управление рисками имеют решающее значение перед внедрением этой программы в реальных условиях рынка.
Используя представленные концепции и методы реализации, можно адаптировать эту систему паттерна "Гартли" к своему стилю торговли, улучшая свои алгоритмические стратегии. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19111
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
- Неполное определение Гартли: код проверяет только то, что точка D находится около 0,786 от XA. В гармоническом трейдинге ключевой идеей является потенциальная зона разворота (PRZ), которая требует слияния нескольких соотношений (например, 0,786 XA вместе с 1,27-1,618 BC). Без этого возникает множество ложных паттернов.
- Правила Stop Loss / Take Profit: здесь они определяются как простые доли движения к C. В гармоничной торговле стоп обычно размещается за пределами X, а цели основываются на ретрейсментах Фибоначчи от AD. Если этого не делать, то соотношение риск/вознаграждение становится произвольным.
Низкочастотные количественные стратегии в MetaTrader 5: (Часть 2) Бэктестинг lead/lag-анализа в SQL и MetaTrader 5
Архитектура машинного обучения для MetaTrader 5 (Часть 8): Байесовская оптимизация гиперпараметров с Purged Cross-Validation и ранним отсечением испытаний
Разработка инструментария для анализа Price Action (Часть 45): Создание динамической панели для анализа уровней в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования