Автоматизация торговых стратегий в MQL5 (Часть 32): Создание системы распознавания гармонического паттерна "5 Drives" с использованием Price Action
Введение
В предыдущей статье (Часть 31), мы разработали систему распознавания графического паттерна "3 Drives" на MetaQuotes Language 5 (MQL5), которая определяет бычьи и медвежьи гармонические паттерны "3 Drives" с использованием коэффициентов Фибоначчи, автоматизируя сделки с настраиваемыми уровнями стоп-лосса и тейк-профита, визуализируемыми с помощью графических объектов, таких как треугольники и линии тренда. В Части 32 мы создадим систему паттерна "5 Drives", которая определяет бычьи и медвежьи гармонические паттерны "5 Drives" с использованием точек разворота и определенных уровней коррекции Фибоначчи и расширений, обеспечивая совершение сделок с гибкими возможностями входа, стоп-лосса и многоуровневой фиксации прибыли, дополненными визуальными треугольниками, линиями тренда и метками для наглядного представления паттернов. В статье рассмотрим следующие темы:
- Изучение структуры гармонического паттерна "5 Drives"
- Реализация средствами MQL5
- Тестирование на истории
- Заключение
В итоге у вас будет стабильная стратегия в MQL5 для торговли на основе гармонического паттерна "5 Drives", готовая к настройке. Перейдем к реализации!
Изучение структуры гармонического паттерна "5 Drives"
Паттерн "5 Drives (5-0)" представляет собой структуру гармонической торговли, определяемую шестью ключевыми точками разворота — A, B, C, D, E и F; паттерн может быть как бычьим, так и медвежьим. Паттерн используется для определения зон разворота посредством последовательности движений и коррекций с определенными коэффициентами коррекций Фибоначчи. При бычьем паттерне "5 Drives" структура образует последовательность "максимум-минимум-максимум-минимум-максимум-минимум", где A - это свинг-хай, B - свинг-лоу, C - свинг-хай, D - свинг-лоу, E - свинг-хай и F - свинг-лоу (выше B), при этом отрезки AB и CD составляют 1,13–1,618 от предыдущего движения, BC и DE — 1,618–2,24, а CD равен AB при коррекции 0,5; медвежий паттерн меняет эту последовательность на противоположную, когда F находится ниже B. Вот визуализация этих паттернов:
Медвежий гармонический паттерн "5 Drives":

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

Наш подход включает в себя обнаружение этих точек разворота в заданном диапазоне баров, проверку волн паттерна в соответствии с критериями Фибоначчи, визуализацию структуры A-B-C-D-E-F с помощью графических объектов, таких как треугольники и линии тренда, и открытие сделок в точке F с настраиваемым стоп-лоссом (на основе Фибоначчи или фиксированный) и настраиваемыми уровнями тейк-профита (одна треть, две трети или уровень точки разворота E), чтобы использовать ожидаемый разворот цены. Приступим к реализации!
Реализация средствами MQL5
Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в "Навигатор", выберите папку Indicators, кликните "Создать" и следуйте инструкциям для создания файла. Нам необходимо объявить некоторые глобальные переменные, которые мы будем использовать на протяжении всей программы.
//+------------------------------------------------------------------+ //| 5 Drives 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 5 Drives 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 //--- Enumeration for TP levels enum ENUM_TAKE_PROFIT_LEVEL { TP1 = 1, // One-third of the move to E TP2 = 2, // Two-thirds of the move to E TP3 = 3 // Pivot E Price }; //--- Enumeration for SL types enum ENUM_STOP_LOSS_TYPE { SL_FIBO = 1, // Fibonacci Extension SL_FIXED = 2 // Fixed Points }; //--- 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 move) input double LotSize = 0.01; // Lot size for new orders input bool AllowTrading = true; // Enable or disable trading input ENUM_TAKE_PROFIT_LEVEL TakeProfitLevel = TP2; // Take Profit Level input ENUM_STOP_LOSS_TYPE StopLossType = SL_FIBO; // Stop Loss Type input double SL_FiboExtension = 1.618; // Fibonacci Extension for SL input double SL_FixedPoints = 50; // Fixed Points for SL (in points) //--------------------------------------------------------------------------- //--- Structure for a pivot point struct Pivot { datetime time; //--- Bar time of the pivot double price; //--- Pivot price (High for swing high, low for swing low) bool isHigh; //--- True if swing high; false if swing low }; //--- Global dynamic array for storing pivots in chronological order Pivot pivots[]; //--- Declare a dynamic array to hold identified pivot points //--- Global variables to lock in a pattern (avoid trading on repaint) int g_patternFormationBar = -1; //--- Bar index where the pattern was formed (-1 means none) datetime g_lockedPatternA = 0; //--- The key A pivot time for the locked pattern //--- Global array to track traded patterns (using A.time as identifier) datetime tradedPatterns[];
Чтобы заложить основу для паттерна 5 Drives, сначала подключим библиотеку "<Trade\Trade.mqh>" и создадим экземпляр "obj_Trade" в качестве объекта CTrade для управления торговыми операциями, такими как открытие сделок на покупку и продажу. Затем определим перечисления "ENUM_TAKE_PROFIT_LEVEL" (TP1 для одной трети, TP2 для двух третей, TP3 для цены точки разворота E) и "ENUM_STOP_LOSS_TYPE" (SL_FIBO для расширения Фибоначчи, SL_FIXED для фиксированных точек) для гибких настроек торговли и установим входные параметры: "PivotLeft" и "PivotRight" задаются равными 5 барам и используются для определения точек разворота, "Tolerance" равно 0,10 для отклонения от уровней Фибоначчи, "LotSize" равно 0,01, "AllowTrading" равно значению true, "TakeProfitLevel" равно значению TP2, "StopLossType" равно значению SL_FIBO, "SL_FiboExtension" равно 1,618 и "SL_FixedPoints" равно 50. Возможно, вам нужно будет оптимизировать эти параметры в зависимости от валютной пары, которой вы торгуете.
Далее определим структуру "Pivot" с параметрами "time", "price" и "isHigh" для хранения точек разворота цены, объявим "pivots" как динамический массив и инициализируем глобальные переменные "g_patternFormationBar" значением -1, "g_lockedPatternA" значением 0 для сохранения времени разворота А и "tradedPatterns" как массив для отслеживания торговых паттернов с использованием времени А. Эта структура служит основой для обнаружения и торговли паттернами "5 Drives". Для визуализации можно использовать функции для рисования линий, меток и треугольников.
//+------------------------------------------------------------------+ //| 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); } }
Перейдем к реализации функций визуализации с целью создания четких графических представлений гармонического паттерна "5 Drives" и его торговых уровней. Сначала мы создадим функцию "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") по заданной цене. Наконец, разработаем функцию "DrawTextEx", которая создает текстовую метку (OBJ_TEXT) в координатах ("t", "p") с помощью "ObjectCreate". Установим "OBJPROP_TEXT" в указанный текст, "OBJPROP_COLOR", "OBJPROP_FONTSIZE" и OBJPROP_FONT в "Arial Bold" с помощью ObjectSetString и "ObjectSetInteger". Закрепим текст выше для свинг-хай или ниже для свинг-лоу на основе "isHigh" с помощью "OBJPROP_ANCHOR" и центрируя текст с помощью свойства "OBJPROP_ALIGN". Теперь перейдём к обработчику событий OnTick и попытаемся определить точки разворота, которые позже сможем использовать для распознавания паттернов. Вот логика, которую мы используем для достижения этой цели.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Declare a static variable to store the time of the last processed bar static datetime lastBarTime = 0; //--- Get the time of the current confirmed bar datetime currentBarTime = iTime(_Symbol, _Period, 1); //--- If the current bar time is the same as the last processed, exit if(currentBarTime == lastBarTime) return; //--- Update the last processed bar time lastBarTime = currentBarTime; //--- Clear the pivot array for fresh analysis ArrayResize(pivots, 0); //--- Get the total number of bars available on the chart int barsCount = Bars(_Symbol, _Period); //--- Define the starting index for pivot detection (ensuring enough left bars) int start = PivotLeft; //--- Define the ending index for pivot detection (ensuring enough right bars) int end = barsCount - PivotRight; //--- Loop through bars from 'end-1' down to 'start' to find pivot points for(int i = end - 1; i >= start; i--) { //--- Assume current bar is both a potential swing high and swing low bool isPivotHigh = true; bool isPivotLow = true; //--- Get the high and low of the current bar double currentHigh = iHigh(_Symbol, _Period, i); double currentLow = iLow(_Symbol, _Period, i); //--- Loop through the window of bars around the current bar for(int j = i - PivotLeft; j <= i + PivotRight; j++) { //--- Skip if the index is out of bounds if(j < 0 || j >= barsCount) continue; //--- Skip comparing the bar with itself if(j == i) continue; //--- If any bar in the window has a higher high, it's not a swing high if(iHigh(_Symbol, _Period, j) > currentHigh) isPivotHigh = false; //--- If any bar in the window has a lower low, it's not a swing low if(iLow(_Symbol, _Period, j) < currentLow) isPivotLow = false; } //--- If the current bar qualifies as either a swing high or swing low if(isPivotHigh || isPivotLow) { //--- Create a new pivot structure Pivot p; //--- Set the pivot's time p.time = iTime(_Symbol, _Period, i); //--- Set the pivot's price depending on whether it is a high or low p.price = isPivotHigh ? currentHigh : currentLow; //--- Set the pivot type (true for swing high, false for swing low) p.isHigh = isPivotHigh; //--- Get the current size of the pivots array int size = ArraySize(pivots); //--- Increase the size of the pivots array by one ArrayResize(pivots, size + 1); //--- Add the new pivot to the array pivots[size] = p; } } }
Здесь мы реализуем начальную часть логики функции OnTick для обнаружения точек разворота, что крайне важно для выявления паттерна. Сначала объявим статическую переменную "lastBarTime", инициализированную значением 0, чтобы отслеживать последний обработанный бар и сравнивать с "currentBarTime", полученной из iTime при сдвиге 1 для текущего символа и периода. Во избежание избыточной обработки, если значение не изменилось, завершаем работу, также обновим "lastBarTime" при обнаружении нового бара. Затем очистим массив "pivots" с помощью ArrayResize для обеспечения нового анализа. Далее получим общее количество баров с помощью Bars, установим диапазон поиска точек разворота с помощью "start" в качестве "PivotLeft" и "end" в качестве общего количества баров минус "PivotRight", и выполним перебор по выбранным барам.
Для каждого бара предположим, что это свинг-хай ("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 six pivots are found, the pattern cannot be formed if(pivotCount < 6) { //--- Reset pattern lock variables g_patternFormationBar = -1; g_lockedPatternA = 0; //--- Exit the OnTick function return; } //--- Extract the last six pivots as A, B, C, D, E, F Pivot A = pivots[pivotCount - 6]; Pivot B = pivots[pivotCount - 5]; Pivot C = pivots[pivotCount - 4]; Pivot D = pivots[pivotCount - 3]; Pivot E = pivots[pivotCount - 2]; Pivot F = pivots[pivotCount - 1]; //--- Initialize a flag to indicate if a valid 5 Drives pattern is found bool patternFound = false; //--- Initialize pattern type string patternType = ""; //--- Check for the high-low-high-low-high-low (Bullish reversal) structure if(A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh) && E.isHigh && (!F.isHigh)) { //--- Calculate XA length double XA_length = C.price - B.price; //--- Calculate AB length double AB_length = C.price - D.price; //--- Check AB extension of XA if(AB_length >= 1.13 * XA_length && AB_length <= 1.618 * XA_length) { //--- Calculate BC length double BC_length = E.price - D.price; //--- Check BC extension of AB if(BC_length >= 1.618 * AB_length && BC_length <= 2.24 * AB_length) { //--- Calculate CD length double CD_length = E.price - F.price; //--- Check 50% retracement of BC double ideal_retrace = 0.5 * BC_length; if(MathAbs(CD_length - ideal_retrace) <= Tolerance * BC_length) { //--- Check reciprocal AB=CD if(MathAbs(CD_length - AB_length) <= Tolerance * AB_length) { //--- Additional conditions: higher high at E, D above B if(E.price > C.price && F.price > B.price) { patternFound = true; patternType = "Bullish"; } } } } } } //--- Check for the low-high-low-high-low-high (Bearish reversal) structure if((!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh && (!E.isHigh) && F.isHigh) { //--- Calculate XA length double XA_length = B.price - C.price; //--- Calculate AB length double AB_length = D.price - C.price; //--- Check AB extension of XA if(AB_length >= 1.13 * XA_length && AB_length <= 1.618 * XA_length) { //--- Calculate BC length double BC_length = D.price - E.price; //--- Check BC extension of AB if(BC_length >= 1.618 * AB_length && BC_length <= 2.24 * AB_length) { //--- Calculate CD length double CD_length = F.price - E.price; //--- Check 50% retracement of BC double ideal_retrace = 0.5 * BC_length; if(MathAbs(CD_length - ideal_retrace) <= Tolerance * BC_length) { //--- Check reciprocal AB=CD if(MathAbs(CD_length - AB_length) <= Tolerance * AB_length) { //--- Additional conditions: lower low at E, D below B if(E.price < C.price && F.price < B.price) { patternFound = true; patternType = "Bearish"; } } } } } }
Сначала определим общее количество точек разворота с помощью "ArraySize(pivots)", которые хранятся в "pivotCount". Выйдем, если найдено менее 6 точек разворота, сбросив значения "g_patternFormationBar" и "g_lockedPatternA" на -1 и 0, поскольку для паттерна "5 Drives" требуются точки A, B, C, D, E и F.
Затем извлечем последние шесть точек разворота из массива "pivots", присвоим "A" (самая ранняя точка), "B", "C", "D", "E" и "F" (самая поздняя точка). Далее, для бычьего паттерна (максимум A, минимум B, максимум C, минимум D, максимум E, минимум F) вычислим длину XA ("С.price - B.price"), длину AB ("C.price - D.price"), чтобы убедиться, что она простирается от 1,13 до 1,618 XA, длину BC ("E.price - D.price") от 1,618 до 2,24 AB и длину CD ("E.price - F.price"), чтобы откатиться на 0,5 BC и сравняться с AB в пределах "Tolerance", подтверждая, что E — более высокий максимум, чем C, а F — выше B, устанавив "patternFound" в true и "patternType" в Bullish. Наконец для медвежьего паттерна (минимум A, максимум B, минимум C, максимум D, минимум Е, максимум F), используем аналогичные проверки для XA ("B.price - C.price"), AB ("D.price - C.price"), BC ("D.price - E.price") и CD ("F.price - E.price"), гарантируя, что E ниже минимума C, а F ниже B, устанавливая "patternFound" в true и "patternType" в Bearish. Если паттерн найден, можно приступить к его визуализации на графике.
//--- If a valid 5 Drives pattern is detected if(patternFound) { //--- Print a message indicating the pattern type and detection time Print(patternType, " 5 Drives pattern detected at ", TimeToString(F.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- Create a unique prefix for all graphical objects related to this pattern string signalPrefix = "5D_" + IntegerToString(A.time); //--- Choose triangle color based on the pattern type color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed; //--- Draw the first triangle connecting pivots B, C, D DrawTriangle(signalPrefix+"_Triangle1", B.time, B.price, C.time, C.price, D.time, D.price, triangleColor, 2, true, true); //--- Draw the second triangle connecting pivots D, E, F DrawTriangle(signalPrefix+"_Triangle2", D.time, D.price, E.time, E.price, F.time, F.price, triangleColor, 2, true, true); }
Теперь визуализируем обнаруженные паттерны на графике. Сначала, если действительный паттерн обнаружен ("patternFound" в значении true), выводим в лог обнаружение с помощью функции Print, выводим параметр "patternType" ("Bullish" или "Bearish") и время точки разворота F, отформатированное с помощью TimeToString, включая дату, минуты и секунды. Далее перейдем к созданию уникального идентификатора "signalPrefix", объединяя "5D_" с "A.time", преобразованным в строку с помощью функции IntegerToString для обеспечения определенного именования объектов графика.
Затем для "triangleColor" установим синий цвет для бычьих паттернов или красный для медвежьих, чтобы различать их визуально. Наконец дважды вызовем "DrawTriangle" для визуализации паттерна: сначала с целью нарисовать треугольник BCD, соединяющий точки разворота B, C и D. Затем - с целью нарисовать треугольник DEF, соединяющий точки разворота D, E и F, используя "signalPrefix" с суффиксами "_Triangle1" и "_Triangle2", соответствующие показатели времени и цен точек разворота, "triangleColor", ширину 2, а также включив заливку и отображение фона с помощью флагов со значением true. Получим следующий результат.

На изображении видно, что мы можем корректно отобразить и визуализировать обнаруженный паттерн. Теперь добавим линии тренда, чтобы полностью показать контур паттерна, а также добавим к ним метку для облегчения идентификации уровней.
//--- Draw boundary trend lines connecting the pivots for clarity DrawTrendLine(signalPrefix+"_TL_AB", A.time, A.price, B.time, B.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_BC", B.time, B.price, C.time, C.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_CD", C.time, C.price, D.time, D.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_DE", D.time, D.price, E.time, E.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_EF", E.time, E.price, F.time, F.price, clrBlack, 2, STYLE_SOLID); //--- Retrieve the symbol's point size to calculate offsets for text positioning double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); //--- Calculate an offset (15 points) for positioning text above or below pivots double offset = 15 * point; //--- Determine the Y coordinate for each pivot label based on its type double textY_A = (A.isHigh ? A.price + offset : A.price - offset); double textY_B = (B.isHigh ? B.price + offset : B.price - offset); double textY_C = (C.isHigh ? C.price + offset : C.price - offset); double textY_D = (D.isHigh ? D.price + offset : D.price - offset); double textY_E = (E.isHigh ? E.price + offset : E.price - offset); double textY_F = (F.isHigh ? F.price + offset : F.price - offset); //--- Draw text labels for each pivot with appropriate anchoring DrawTextEx(signalPrefix+"_Text_A", "A", A.time, textY_A, clrBlack, 11, A.isHigh); DrawTextEx(signalPrefix+"_Text_B", "B", B.time, textY_B, clrBlack, 11, B.isHigh); DrawTextEx(signalPrefix+"_Text_C", "C", C.time, textY_C, clrBlack, 11, C.isHigh); DrawTextEx(signalPrefix+"_Text_D", "D", D.time, textY_D, clrBlack, 11, D.isHigh); DrawTextEx(signalPrefix+"_Text_E", "E", E.time, textY_E, clrBlack, 11, E.isHigh); DrawTextEx(signalPrefix+"_Text_F", "F", F.time, textY_F, clrBlack, 11, F.isHigh); //--- Calculate the central label's time as the midpoint between pivots A and D datetime centralTime = (A.time + D.time) / 2; //--- Set the central label's price at pivot F's price double centralPrice = F.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 5 Drives" : "Bearish 5 Drives"); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_COLOR, clrBlack); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_FONTSIZE, 11); ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_FONT, "Arial Bold"); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_ALIGN, ALIGN_CENTER); }
Далее улучшаем визуализацию обнаруженных паттернов, добавляя подробные графические объекты для четкого отображения структуры паттерна. Сначала нарисуем пять сплошных линий тренда, используя "DrawTrendLine" с уникальным "signalPrefix", чтобы соединить ключевые точки разворота: AB, BC, CD, DE и EF, каждый из которых использует значения времени и цены точки разворота (например, "A.time", "A.price"), установим OBJPROP_COLOR на "clrBlack", "OBJPROP_WIDTH" на 2, а "OBJPROP_STYLE" - на "STYLE_SOLID" с ObjectSetInteger для отображения контуров паттерна. Затем мы получаем размер символа в пунктах с помощью функции "SymbolInfoDouble(_Symbol, SYMBOL_POINT)" и вычисляем смещение в 15 пунктов для позиционирования меток. Определим Y-координаты ("textY_A", "textY_B", "textY_C", "textY_D", "textY_E", "textY_F") путем добавления или вычитания смещения на основе того, является ли каждая точка разворота максимумом ("isHigh" true) или минимумом свинга, чтобы разместить метки выше максимумов или ниже минимумов.
Затем мы используем "DrawTextEx" для создания текстовых меток для точек разворота A, B, C, D, E и F с "signalPrefix" и суффиксами типа "_Text_A", отображающими соответствующую букву, расположенную в момент времени соответствующей точки разворота и скорректированную по координате Y. Для привязки используется "clrBlack", размер шрифта 11 и статус точки разворота "isHigh". Наконец вычислим центральную позицию метки в точке "centralTime" как середину между "A.time" и "D.time", а также "centralPrice" в точке "F.price", создавая текстовый объект с помощью "ObjectCreate" с именем "signalPrefix + '_Text_Center'". Установим OBJPROP_TEXT в значение "Bullish 5 Drives" или "Bearish 5 Drives" на основе "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 = F.time; datetime lineEnd = F.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 E's price TP3Level = E.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 E's price TP3Level = E.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" на время точки разворота F ("F.time"), а "lineEnd" - на два периода вперед, используя "PeriodSeconds(_Period) * 2". Объявим переменные "entryPriceLevel", "TP1Level", "TP2Level" и "TP3Level" для расчетов сделок. Затем для бычьего паттерна ("patternType == 'Bullish'") установим "entryPriceLevel" на текущую цену ask с "SymbolInfoDouble", "TP3Level" равным цене точки разворота E, рассчитаем "tradeDiff" как "TP3Level - entryPriceLevel", а "TP1Level" и "TP2Level" - как одну треть и две трети "tradeDiff", добавленного к "entryPriceLevel". Для медвежьего паттерна используем цену bid, установим "TP3Level" на цену E, рассчитаем "tradeDiff" как "entryPriceLevel - TP3Level", а "TP1Level" и "TP2Level" - вычитая одну треть и две трети от разницы между ценами сделок.
Далее, используя функцию "DrawDottedLine", нарисуем четыре пунктирные горизонтальные линии: линию входа на уровне "entryPriceLevel" пурпурного цвета и линии тейк-профита на уровнях "TP1Level" (глубокий лесной зеленый), "TP2Level" (зеленый) и "TP3Level" (темно-зеленый), от "lineStart" до "lineEnd". Наконец, установим значение "labelTime" равным "lineEnd" плюс половина периода, создадим текстовые метки с ценами, отформатированными с помощью функции DoubleToString (например, "BUY (price)" или "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_lockedPatternA = A.time; //--- Print a message that the pattern is detected and waiting for confirmation Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar."); return; } //--- If still on the same formation bar, the pattern is considered to be repainting if(currentBarIndex == g_patternFormationBar) { Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No trade yet."); return; } //--- If we are on a new bar compared to the locked formation if(currentBarIndex > g_patternFormationBar) { //--- Check if the locked pattern still corresponds to the same A pivot if(g_lockedPatternA == A.time) { Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening trade on bar ", currentBarIndex, "."); //--- Update the pattern formation bar to the current bar g_patternFormationBar = currentBarIndex; //--- Only proceed with trading if allowed and if there is no existing position if(AllowTrading && !PositionSelect(_Symbol)) { //--- Check if this pattern has already been traded bool alreadyTraded = false; for(int k = 0; k < ArraySize(tradedPatterns); k++) { if(tradedPatterns[k] == A.time) { alreadyTraded = true; break; } } if(alreadyTraded) { Print("This pattern has already been traded. No new trade executed."); return; } double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0; point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); bool tradeResult = false; //--- Select TP level based on user input switch(TakeProfitLevel) { case TP1: takeProfit = TP1Level; break; case TP2: takeProfit = TP2Level; break; case TP3: takeProfit = TP3Level; break; default: takeProfit = TP2Level; // Fallback to TP2 } //--- Calculate SL based on user-selected method if(patternType=="Bullish") { //--- BUY signal entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(StopLossType == SL_FIBO) { double third_drive = E.price - F.price; stopLoss = F.price - (SL_FiboExtension - 1.0) * third_drive; } else { // SL_FIXED stopLoss = entryPriceTrade - SL_FixedPoints * point; } // Ensure SL is below entry for BUY if(stopLoss >= entryPriceTrade) { stopLoss = entryPriceTrade - 10 * point; } tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "5 Drives 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); if(StopLossType == SL_FIBO) { double third_drive = F.price - E.price; stopLoss = F.price + (SL_FiboExtension - 1.0) * third_drive; } else { // SL_FIXED stopLoss = entryPriceTrade + SL_FixedPoints * point; } // Ensure SL is above entry for SELL if(stopLoss <= entryPriceTrade) { stopLoss = entryPriceTrade + 10 * point; } tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "5 Drives Signal"); if(tradeResult) Print("Sell order opened successfully."); else Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); } //--- If trade was successful, mark the pattern as traded if(tradeResult) { int size = ArraySize(tradedPatterns); ArrayResize(tradedPatterns, size + 1); tradedPatterns[size] = A.time; } } else { //--- If a position is already open, do not execute a new trade Print("A position is already open for ", _Symbol, ". No new trade executed."); } } else { //--- If the pattern has changed, update the lock with the new formation bar and A pivot g_patternFormationBar = currentBarIndex; g_lockedPatternA = A.time; Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation."); return; } } else { //--- If no valid 5 Drives pattern is detected, reset the pattern lock variables g_patternFormationBar = -1; g_lockedPatternA = 0; }
На завершающем этапе реализуем подтверждение паттерна и открытие сделок. Сначала получим индекс текущего бара с помощью функции "Bars(_Symbol, _Period) - 1" и сохраним его в "currentBarIndex". Затем, если ни один паттерн не сохранен ("g_patternFormationBar == -1"), установим "g_patternFormationBar" в "currentBarIndex", сохраним время точки разворота А в "g_lockedPatternA" с помощью "A.time", регистрируем обнаружение с помощью Print, указывая на ожидание подтверждения. Завершаем работу.
Далее, если паттерн все еще находится на формирующем баре ("currentBarIndex == g_patternFormationBar"), считаем, что паттерн ещё перерисовывается и выходим, чтобы избежать преждевременной торговли. Наконец, если сформировался новый бар ("currentBarIndex > g_patternFormationBar") и точка разворота А совпадает с "g_lockedPatternA", подтверждаем паттерн, выводим в лог, обновляем "g_patternFormationBar" и проверяем, разрешена ли торговля с помощью "AllowTrading" и нет ли открытых позиций с помощью функции PositionSelect. Проверяем, что паттерн не торговался, проверяя "tradedPatterns", выберем уровень тейк-профита ("TP1Level", "TP2Level" или "TP3Level") на основе "TakeProfitLevel", рассчитаем стоп-лосс, используя "SL_FIBO" ("F.price ± (SL_FiboExtension - 1.0) * third_drive") или "SL_FIXED" ("entryPriceTrade ± SL_FixedPoints * point"). Убедимся, что стоп-лосс действителен, откроем сделку на покупку или продажу с помощью "obj_Trade.Buy" или "obj_Trade.Sell", используя "LotSize" и "5 Drives Signal", зарегистрируем успех или неудачу, а также добавим паттерн в массив "tradedPatterns" как уже отработанный. Если торговля запрещена, позиция уже существует или паттерн торговался, в лог выводится сообщение об отсутствии сделки. Если паттерн меняется, обновляем сохранение и ждем. Если паттерн не найден, сбрасываем глобальные переменные. После компиляции получаем следующий результат.
Медвежий сигнал:

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

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

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

Заключение
В заключение отметим, что нами разработана система паттерна "5 Drives" в MQL5. Она использует ценовое движение для выявления бычьих и медвежьих гармонических паттернов "5 Drives" с точными уровнями коррекции и расширения Фибоначчи, автоматизирует сделки с расчетными точками входа, стоп-лосса и многоуровневыми точками тейк-профита, а также визуализирует паттерны с помощью графических объектов, таких как треугольники и линии тренда.
Внимание: все содержание настоящей статьи предназначено только для целей обучения и не предназначена для других целей. Торговля связана со значительными финансовыми рисками, а рыночная волатильность может привести к убыткам. Тщательное тестирование на исторических данных и внимательное управление рисками имеют решающее значение перед внедрением этой программы на реальных рынках.
Используя представленные концепции и методы реализации, вы можете адаптировать эту систему паттернов «5 Drives» к своему стилю торговли, улучшив свои алгоритмические стратегии. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19463
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Алгоритм бактериальной эволюции Нумаоки для быстрой адаптации (NBE)
Встраивание торговой дисциплины в код (Часть 6): Построение единого фреймворка дисциплины на MQL5
Разработка самовосстанавливающегося советника в MQL5 (Часть 1): Архитектура постоянного хранения состояния сделки
Создание индикатора ZigZag со статистикой волн в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования