English Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 15): Гармонический паттерн «Шифр» (Cypher) ценового действия с визуализацией

Автоматизация торговых стратегий на MQL5 (Часть 15): Гармонический паттерн «Шифр» (Cypher) ценового действия с визуализацией

MetaTrader 5Трейдинг |
491 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В нашей предыдущей статье (Часть 14) мы разработали стратегию лейеринга с использованием Индикатора схождения-расхождения скользящих средних (MACD) и Индикатора относительной силы (RSI) со статистическими методами для динамичного масштабирования позиций на трендовых рынках. Теперь, в части 15, мы сосредоточимся на автоматизации гармонического паттерна «Шифр» (Cypher), разворотного паттерна на основе Фибоначчи, с помощью советника (EA), который обнаруживает, визуализирует и торгует этой структурой на MetaQuotes Language 5 (MQL5). В статье рассмотрим следующие темы:

  1. Изучение архитектуры паттерна «Шифр»
  2. Реализация средствами MQL5
  3. Тестирование на истории и оптимизация
  4. Заключение

К концу настоящей статьи у вас будет полностью функциональная программа, которая распознает паттерны «Шифр», аннотирует графики с помощью четких визуальных элементов и точно исполняет сделки. Давайте погрузимся в работу!


Изучение архитектуры паттерна «Шифр»

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

Бычий Гармонический паттерн «Шифр» (Cypher):

BULLISH CYPHER

Медвежий Гармонический паттерн «Шифр» (Cypher):

BEARISH CYPHER

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

  • Определение стороны "XA": Начальное движение от точки X к точке A устанавливает контрольное расстояние для паттерна, задавая направление (вверх для медвежьего тренда, вниз для бычьего).
  • Создание стороны "AB": Для обоих типов паттернов, точка В должна выполнить возврат от 38,2% до 61,8% расстояния XA, подтверждая умеренную коррекцию первоначального движения.
  • Анализ стороны "BC": Эта сторона должна распространяться от 127,2% до 141,4% стороны AB, обеспечивая сильное встречное движение перед последней стороной.
  • Настройка стороны "CD": Последняя сторона должна выполнить обратный ход примерно 78,6% хода XC (от X до C), обозначая потенциальную зону разворота.

Применяя эти геометрические критерии и критерии, основанные на Фибоначчи, наша торговая система будет систематически выявлять достоверные паттерны «Шифр» в исторических ценовых данных. Как только паттерн будет подтвержден, программа визуализирует образование на графике с помощью аннотированных треугольников, линий тренда и меток для точек X, A, B, C и D, а также уровней сделок. Эта настройка позволяет автоматически совершать сделки на основе рассчитанных уровней входа, стоп-лосса и тейк-профита, используя возможности паттерна для прогнозирования разворотов рынка.


Реализация средствами 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 Cypher Strategy with visualization"
#property strict //--- Forces strict coding rules to catch errors early

//--- Include the Trade library from MQL5 to handle trading operations like buying and selling
#include <Trade\Trade.mqh>
//--- Create an instance (object) of the CTrade class to use for placing trades
CTrade obj_Trade;

//--- Input parameters let the user customize the EA without editing the code
input int    SwingHighCount    = 5;      // How many bars to check on the left to find a swing point (high or low)
input int    SwingLowCount     = 5;      // How many bars to check on the right to confirm a swing point
input double FibonacciTolerance = 0.10;  // Allowed error margin (10%) for Fibonacci ratios in the pattern
input double TradeVolume       = 0.01;   // Size of the trade (e.g., 0.01 lots is small for testing)
input bool   TradingEnabled    = true;   // True = EA can trade; False = only visualize patterns

//--- Define the Cypher pattern rules as a comment for reference
//--- Bullish Cypher: X (low), A (high), B (low), C (high), D (low)
//---   XA > 0; AB = 0.382-0.618 XA; BC = 1.272-1.414 AB; CD = 0.786 XC; D < X
//--- Bearish Cypher: X (high), A (low), B (high), C (low), D (high)
//---   XA > 0; AB = 0.382-0.618 XA; BC = 1.272-1.414 AB; CD = 0.786 XC; D > X

//--- Define a structure (like a custom data type) to store swing point info
struct SwingPoint {  
   datetime TimeOfSwing;    //--- When the swing happened (date and time of the bar)
   double   PriceAtSwing;   //--- Price at the swing (high or low)
   bool     IsSwingHigh;    //--- True = swing high; False = swing low
};  

//--- Create a dynamic array to hold all detected swing points
SwingPoint SwingPoints[];

Здесь мы приступаем к реализации торговой системы паттерн «Шифр» на языке MetaQuotes Language 5, включив библиотеку "Trade.mqh" для обеспечения торговых операций и создав экземпляр класса "CTrade" с именем "obj_Trade" для исполнения сделок.

Определяем входные параметры — "SwingHighCount" и "SwingLowCount" (оба по 5) для определения точки колебания, "FibonacciTolerance" (0,10) для определения гибкости соотношения Фибоначчи, "TradeVolume" (0,01 лота) для определения размера сделки и "TradingEnabled" (true) для переключения торговли, что обеспечит возможность осуществлять пользовательские настройки.

Структура "SwingPoint" определена с помощью "TimeOfSwing" (datetime), "PriceAtSwing" (double) и "IsSwingHigh" (boolean) для хранения сведений о точках колебания, а динамический массив "SwingPoints" содержит все обнаруженные точки колебания для анализа шаблона. 

Далее мы можем определить функции, которые помогут нам визуализировать паттерны на графике.

//+------------------------------------------------------------------+
//| Helper: Draw a filled triangle                                   |  
//+------------------------------------------------------------------+  
//--- Function to draw a triangle on the chart to highlight pattern segments
void DrawTriangle(string TriangleName, datetime Time1, double Price1, datetime Time2, double Price2, datetime Time3, double Price3, color LineColor, int LineWidth, bool FillTriangle, bool DrawBehind) {  
   //--- Create a triangle object using three points (time, price) on the chart
   if(ObjectCreate(0, TriangleName, OBJ_TRIANGLE, 0, Time1, Price1, Time2, Price2, Time3, Price3)) {  
      ObjectSetInteger(0, TriangleName, OBJPROP_COLOR, LineColor);      //--- Set the triangle’s color (e.g., blue or red)
      ObjectSetInteger(0, TriangleName, OBJPROP_STYLE, STYLE_SOLID);    //--- Use a solid line style
      ObjectSetInteger(0, TriangleName, OBJPROP_WIDTH, LineWidth);      //--- Set the line thickness
      ObjectSetInteger(0, TriangleName, OBJPROP_FILL, FillTriangle);    //--- Fill the triangle with color if true
      ObjectSetInteger(0, TriangleName, OBJPROP_BACK, DrawBehind);      //--- Draw behind candles if true
   }  
}  

Здесь мы реализуем функцию "DrawTriangle" для улучшения визуализации паттерна «Шифр», рисуя треугольник с заливкой на графике MetaTrader 5, выделяя определенные сегменты паттерна для лучшего понимания трейдером. Функция принимает несколько параметров для определения внешнего вида и положения треугольника: "TriangleName" (строка) предоставляет уникальный идентификатор для объекта, "Time1", "Time2" и "Time3" (datetime) задают временные координаты трех вершин треугольника, а "Price1", "Price2" и "Price3" (double) задают соответствующие уровни цен.

Дополнительные параметры включают "LineColor" (color) для определения цвета контура (например, синий для бычьих паттернов, красный - для медвежьих), "LineWidth" (int) для задания толщины границ треугольника, "FillTriangle" (bool) для определения того, будет ли треугольник иметь цветную заливку, а также "DrawBehind" (bool) позволяет контролировать, отображается ли треугольник за свечами графика, чтобы избежать искажения ценовых данных.

В рамках этой функции мы используем функцию ObjectCreate для создания треугольного объекта на графике, с указанием типа объекта как OBJ_TRIANGLE и передачей предоставленных временных и ценовых координат для трех точек. Функция проверяет, прошло ли создание объекта успешно, прежде чем приступить к настройке его свойств.

Если создание завершается успешно, вызываем функцию ObjectSetInteger несколько раз, чтобы задать атрибуты треугольника: OBJPROP_COLOR присваивает значение "LineColor", "OBJPROP_STYLE" имеет значение "STYLE_SOLID" для сплошного контура, "OBJPROP_WIDTH" применяет значение "LineWidth", "OBJPROP_FILL" использует логическое значение "FillTriangle" для включения или отключения заливки, а OBJPROP_BACK использует логическое значение "DrawBehind" чтобы гарантировать, что треугольник появляется позади свечей при значении true.

Теперь мы можем определить остальные вспомогательные функции с помощью той же логики.

//+------------------------------------------------------------------+  
//| Helper: Draw a trend line                                        |  
//+------------------------------------------------------------------+  
//--- Function to draw a straight line between two points on the chart
void DrawTrendLine(string LineName, datetime StartTime, double StartPrice, datetime EndTime, double EndPrice, color LineColor, int LineWidth, int LineStyle) {  
   //--- Create a trend line object connecting two points (start time/price to end time/price)
   if(ObjectCreate(0, LineName, OBJ_TREND, 0, StartTime, StartPrice, EndTime, EndPrice)) {  
      ObjectSetInteger(0, LineName, OBJPROP_COLOR, LineColor);      //--- Set the line color
      ObjectSetInteger(0, LineName, OBJPROP_STYLE, LineStyle);      //--- Set line style (e.g., solid or dashed)
      ObjectSetInteger(0, LineName, OBJPROP_WIDTH, LineWidth);      //--- Set line thickness
      ObjectSetInteger(0, LineName, OBJPROP_BACK, true);           
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw a dotted trend line                                 |  
//+------------------------------------------------------------------+  
//--- Function to draw a horizontal dotted line (e.g., for entry or take-profit levels)
void DrawDottedLine(string LineName, datetime StartTime, double LinePrice, datetime EndTime, color LineColor) {  
   //--- Create a horizontal line from start time to end time at a fixed price
   if(ObjectCreate(0, LineName, OBJ_TREND, 0, StartTime, LinePrice, EndTime, LinePrice)) {  
      ObjectSetInteger(0, LineName, OBJPROP_COLOR, LineColor);      //--- Set the line color
      ObjectSetInteger(0, LineName, OBJPROP_STYLE, STYLE_DOT);      //--- Use dotted style
      ObjectSetInteger(0, LineName, OBJPROP_WIDTH, 1);              //--- Thin line
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw anchored text label (for pivots and levels)         |  
//+------------------------------------------------------------------+  
//--- Function to place text labels (e.g., "X" or "TP1") on the chart
void DrawTextLabel(string LabelName, string LabelText, datetime LabelTime, double LabelPrice, color TextColor, int FontSize, bool IsAbove) {  
   //--- Create a text object at a specific time and price
   if(ObjectCreate(0, LabelName, OBJ_TEXT, 0, LabelTime, LabelPrice)) {  
      ObjectSetString(0, LabelName, OBJPROP_TEXT, LabelText);          //--- Set the text to display
      ObjectSetInteger(0, LabelName, OBJPROP_COLOR, TextColor);        //--- Set text color
      ObjectSetInteger(0, LabelName, OBJPROP_FONTSIZE, FontSize);      //--- Set text size
      ObjectSetString(0, LabelName, OBJPROP_FONT, "Arial Bold");       //--- Use bold Arial font
      //--- Position text below if it’s a high point, above if it’s a low point
      ObjectSetInteger(0, LabelName, OBJPROP_ANCHOR, IsAbove ? ANCHOR_BOTTOM : ANCHOR_TOP);  
      ObjectSetInteger(0, LabelName, OBJPROP_ALIGN, ALIGN_CENTER);     //--- Center the text
   }  
}  

Здесь мы реализуем функцию "DrawTrendLine", чтобы нарисовать прямую линию, соединяющую паттерн «Шифр» с точками поворота на графике, используя параметры "LineName" (string), "StartTime", "EndTime" (datetime), "StartPrice", "EndPrice" (double), "LineColor" (color), "LineWidth" (int) и "LineStyle" (int). Используем функцию ObjectCreate для создания линии "OBJ_TREND" и, в случае успешного выполнения, задаем "OBJPROP_COLOR", "OBJPROP_STYLE", "OBJPROP_WIDTH" и "OBJPROP_BACK" (true) с помощью "ObjectSetInteger" для видимости за свечами.

Функция "DrawDottedLine" рисует горизонтальную пунктирную линию для торговых уровней, используя "LineName" (string), "StartTime", "EndTime" (datetime), "LinePrice" (double) и "LineColor" (color). Создаем объект OBJ_TREND с помощью "ObjectCreate" по фиксированной цене и устанавливаем значение "OBJPROP_COLOR", "OBJPROP_STYLE" для "STYLE_DOT", а для "OBJPROP_WIDTH" значение 1, используя ObjectSetInteger в качестве незаметного маркера.

Функция "DrawTextLabel" помещает текстовые метки для точек колебания или торговых уровней, используя "LabelName", "LabelText" (string), "LabelTime" (datetime), "LabelPrice" (double), "TextColor" (color), "FontSize" (int) и "IsAbove" (bool). Создаем объект OBJ_TEXT с помощью "ObjectCreate" и используем "ObjectSetString" для "OBJPROP_TEXT" и "OBJPROP_FONT" ("Arial Bold"), а также "ObjectSetInteger" для "OBJPROP_COLOR", "OBJPROP_FONTSIZE", "OBJPROP_ANCHOR" ("ANCHOR_BOTTOM" или "ANCHOR_TOP") и "OBJPROP_ALIGN" (ALIGN_CENTER) для обеспечения прозрачных аннотаций.

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
//--- Main function that runs every time a new price tick arrives
void OnTick() {  
   //--- Use a static variable to track the last bar’s time so we only process new bars
   static datetime LastProcessedBarTime = 0;  
   //--- Get the time of the second-to-last bar (latest complete bar)
   datetime CurrentBarTime = iTime(_Symbol, _Period, 1);  
   //--- If no new bar has formed, exit to avoid over-processing
   if(CurrentBarTime == LastProcessedBarTime)  
      return;  
   LastProcessedBarTime = CurrentBarTime;  //--- Update to the current bar
}

В обработчике событий OnTick, который служит основным обработчиком событий, который задействуется каждый раз, когда на платформе MetaTrader 5 появляется новый ценовой тик, объявляем статическую переменную "LastProcessedBarTime" для отслеживания временной метки последнего обработанного бара, гарантируя, что функция обрабатывает только новые бары для оптимизации эффективности.

Используя функцию iTime, извлекаем время предпоследнего бара (последнего полного бара) и сохраняем его в "CurrentBarTime". Затем сравниваем "CurrentBarTime" с "LastProcessedBarTime", чтобы проверить, сформировался ли новый бар. Если они равны, выходим из функции с помощью оператора return, чтобы избежать избыточной обработки.

При обнаружении нового бара обновляем "LastProcessedBarTime" на "CurrentBarTime", позволяя функции перейти к последующей логике для анализа ценовых данных и обнаружения паттернов «Шифр». Далее нам нужно определить переменные, которые помогут определить уровни точек колебания.

//--- Clear the SwingPoints array to start fresh each time
ArrayResize(SwingPoints, 0);  
//--- Get the total number of bars on the chart
int TotalBars = Bars(_Symbol, _Period);  
int StartBarIndex = SwingHighCount;         //--- Start checking swings after SwingHighCount bars
int EndBarIndex = TotalBars - SwingLowCount; //--- Stop before the last SwingLowCount bars

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

Чтобы сосредоточиться на соответствующих барах для определения колебаний, устанавливаем для параметра "StartBarIndex" значение "SwingHighCount", отмечая самый ранний бар для проверки колебаний, и вычисляем "EndBarIndex" как "TotalBars" минус "SwingLowCount", гарантируя, что остановимся перед последними несколькими барами, чтобы получить достаточно данных для подтверждения точек колебаний.

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

//--- Loop through bars to find swing highs and lows (swing points)
for(int BarIndex = EndBarIndex - 1; BarIndex >= StartBarIndex; BarIndex--) {  
   bool IsSwingHigh = true;   //--- Assume it’s a high until proven otherwise
   bool IsSwingLow = true;    //--- Assume it’s a low until proven otherwise
   double CurrentBarHigh = iHigh(_Symbol, _Period, BarIndex); //--- Get the high of this bar
   double CurrentBarLow = iLow(_Symbol, _Period, BarIndex);   //--- Get the low of this bar
   //--- Check bars to the left and right to confirm it’s a swing point
   for(int NeighborIndex = BarIndex - SwingHighCount; NeighborIndex <= BarIndex + SwingLowCount; NeighborIndex++) {  
      if(NeighborIndex < 0 || NeighborIndex >= TotalBars || NeighborIndex == BarIndex) //--- Skip invalid bars or current bar
         continue;  
      if(iHigh(_Symbol, _Period, NeighborIndex) > CurrentBarHigh) //--- If any bar is higher, not a high
         IsSwingHigh = false;  
      if(iLow(_Symbol, _Period, NeighborIndex) < CurrentBarLow)   //--- If any bar is lower, not a low
         IsSwingLow = false;  
   }  
   //--- If it’s a high or low, store it in the SwingPoints array
   if(IsSwingHigh || IsSwingLow) {  
      SwingPoint NewSwing;  
      NewSwing.TimeOfSwing = iTime(_Symbol, _Period, BarIndex); //--- Store the bar’s time
      NewSwing.PriceAtSwing = IsSwingHigh ? CurrentBarHigh : CurrentBarLow; //--- Store high or low price
      NewSwing.IsSwingHigh = IsSwingHigh;              //--- Mark as high or low
      int CurrentArraySize = ArraySize(SwingPoints);   //--- Get current array size
      ArrayResize(SwingPoints, CurrentArraySize + 1);  //--- Add one more slot
      SwingPoints[CurrentArraySize] = NewSwing;        //--- Add the swing to the array
   }  
}  

Здесь мы реализуем логику определения точки колебания, чтобы определить максимумы и минимумы колебания для паттерна «Шифр». Мы используем цикл for для перебора баров от "EndBarIndex - 1" до "StartBarIndex" в порядке убывания, при этом "BarIndex" отслеживает текущий бар. Для каждого бара инициализируем значения "IsSwingHigh" и "IsSwingLow" как true, предполагая, что бар является точкой колебания, пока это не будет опровергнуто, и извлекаем максимальную и низкую цены бара, используя функции iHigh и iLow, сохраняя их в "CurrentBarHigh" и "CurrentBarLow". Вложенный цикл for проверяет соседние бары от "BarIndex - SwingHighCount" до "BarIndex + SwingLowCount", используя "NeighborIndex" для пропуска недопустимых индексов или самого текущего бара, с помощью оператора continue.

Если максимум какого-либо соседнего бара превышает "CurrentBarHigh" или минимум падает ниже "CurrentBarLow" (через iHigh и iLow), мы устанавливаем значение "IsSwingHigh" или "IsSwingLow" равным false соответственно. Если какой-либо из них остается true, создаем экземпляр "SwingPoint" с именем "NewSwing", присваивая значение "TimeOfSwing" со временем бара из iTime, "PriceAtSwing" как "CurrentBarHigh" или "CurrentBarLow" на основании "IsSwingHigh" и "IsSwingHigh" соответственно.

Затем используем функцию "ArraySize", чтобы получить текущий размер "SwingPoints", увеличиваем его на единицу с помощью ArrayResize и сохраняем "NewSwing" в массиве "SwingPoints" с новым индексом, создавая коллекцию точек колебания для анализа паттернов. При выводе данных с помощью функции ArrayPrint(SwingPoints), мы получаем следующий результат.

STRUCTURED DATA OUTCOME

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

//--- Check if we have enough swing points (need 5 for Cypher: X, A, B, C, D)
int TotalSwingPoints = ArraySize(SwingPoints);  
if(TotalSwingPoints < 5)  
   return;  //--- Exit if not enough swing points

//--- Assign the last 5 swing points to X, A, B, C, D (most recent is D)
SwingPoint PointX = SwingPoints[TotalSwingPoints - 5];  
SwingPoint PointA = SwingPoints[TotalSwingPoints - 4];  
SwingPoint PointB = SwingPoints[TotalSwingPoints - 3];  
SwingPoint PointC = SwingPoints[TotalSwingPoints - 2];  
SwingPoint PointD = SwingPoints[TotalSwingPoints - 1];  

//--- Variables to track if we found a pattern and its type
bool PatternFound = false;  
string PatternDirection = "";  

//--- Check for Bearish Cypher pattern
if(PointX.IsSwingHigh && !PointA.IsSwingHigh && PointB.IsSwingHigh && !PointC.IsSwingHigh && PointD.IsSwingHigh) {  
   double LegXA = PointX.PriceAtSwing - PointA.PriceAtSwing;  //--- Calculate XA leg (should be positive)
   if(LegXA > 0) {  
      double LegAB = PointB.PriceAtSwing - PointA.PriceAtSwing; //--- AB leg
      double LegBC = PointB.PriceAtSwing - PointC.PriceAtSwing; //--- BC leg
      double LegXC = PointX.PriceAtSwing - PointC.PriceAtSwing; //--- XC leg
      double LegCD = PointD.PriceAtSwing - PointC.PriceAtSwing; //--- CD leg
      //--- Check Fibonacci rules and D > X for bearish
      if(LegAB >= 0.382 * LegXA && LegAB <= 0.618 * LegXA &&  
         LegBC >= 1.272 * LegAB && LegBC <= 1.414 * LegAB &&  
         MathAbs(LegCD - 0.786 * LegXC) <= FibonacciTolerance * LegXC && PointD.PriceAtSwing > PointX.PriceAtSwing) {  
         PatternFound = true;  
         PatternDirection = "Bearish";  
      }  
   }  
}  
//--- Check for Bullish Cypher pattern
else if(!PointX.IsSwingHigh && PointA.IsSwingHigh && !PointB.IsSwingHigh && PointC.IsSwingHigh && !PointD.IsSwingHigh) {  
   double LegXA = PointA.PriceAtSwing - PointX.PriceAtSwing;  //--- Calculate XA leg (should be positive)
   if(LegXA > 0) {  
      double LegAB = PointA.PriceAtSwing - PointB.PriceAtSwing; //--- AB leg
      double LegBC = PointC.PriceAtSwing - PointB.PriceAtSwing; //--- BC leg
      double LegXC = PointC.PriceAtSwing - PointX.PriceAtSwing; //--- XC leg
      double LegCD = PointC.PriceAtSwing - PointD.PriceAtSwing; //--- CD leg
      //--- Check Fibonacci rules and D < X for bullish
      if(LegAB >= 0.382 * LegXA && LegAB <= 0.618 * LegXA &&  
         LegBC >= 1.272 * LegAB && LegBC <= 1.414 * LegAB &&  
         MathAbs(LegCD - 0.786 * LegXC) <= FibonacciTolerance * LegXC && PointD.PriceAtSwing < PointX.PriceAtSwing) {  
         PatternFound = true;  
         PatternDirection = "Bullish";  
      }  
   }  
}  

Здесь мы продолжаем проверку паттерна «Шифр», проверяя наличие достаточных точек колебания и анализируя последние пять для формирования паттерна. Используем функцию ArraySize, чтобы определить количество элементов в массиве "SwingPoints", сохранив его в "TotalSwingPoints", и завершаем работу с оператором return, если "TotalSwingPoints" меньше 5, поскольку для паттерна «Шифр» требуется пять точек (X, A, B, C, D). Если точек достаточно, присваиваем последним пяти точкам колебания значения "PointX", "PointA", "PointB", "PointC" и "PointD" из массива "SwingPoints" с индексами от "TotalSwingPoints - 5" до "TotalSwingPoints - 1", где "PointD" - это самый последний.

Затем инициализируем "PatternFound" как значение false, чтобы отслеживать, обнаружен ли допустимый паттерн, и "PatternDirection" как пустую строку для хранения типа паттерна. Для проверки наличия медвежьего «Шифра», проверяем, что "PointX.IsSwingHigh" имеет значение true, "PointA.IsSwingHigh" - false, "PointB.IsSwingHigh" - true, "PointC.IsSwingHigh" - false и "PointD.IsSwingHigh" - true, обеспечивая последовательность максимум-минимум-максимум-минимум-максимум.

Если это значение равно true, мы вычисляем длины сторон: "LegXA" как "PointX.PriceAtSwing" минус "PointA.PriceAtSwing" (положительное для медвежьего), "LegAB" как "PointB.PriceAtSwing" минус "PointA.PriceAtSwing", "LegBC" как "PointB.PriceAtSwing" минус "PointC.PriceAtSwing", "LegXC" как "PointX.PriceAtSwing" минус "PointC.PriceAtSwing" и "LegCD" как "PointD.PriceAtSwing" минус "PointC.PriceAtSwing".

Проверяем соотношения Фибоначчи, чтобы убедиться, что "LegAB" составляет от 38,2% до 61,8% от "LegXA", "LegBC" — от 127,2% до 141,4% от "LegAB", "LegCD" находится в пределах ""FibonacciTolerance", равного 78,6% от "LegXC", используя функцию "MathAbs", и "PointD.PriceAtSwing" превышает "PointX.PriceAtSwing" — при выполнении всех условий для параметра "PatternFound" устанавливается значение true, а для параметра "PatternDirection" - значение "Медвежий".

Для бычьего «Шифра» проверяем противоположную последовательность: "PointX.IsSwingHigh" - false, "PointA.IsSwingHigh" - true, "PointB.IsSwingHigh" - false, "PointC.IsSwingHigh" - true и "PointD.IsSwingHigh" - false.

Рассчитываем "LegXA" как "PointA.PriceAtSwing" минус "PointX.PriceAtSwing" (положительное для бычьего), "LegAB" как "PointA.PriceAtSwing" минус "PointB.PriceAtSwing", "LegBC" как "PointC.PriceAtSwing" минус "PointB.PriceAtSwing", "LegXC" как "PointC.PriceAtSwing" минус "PointX.PriceAtSwing" и "LegCD" как "PointC.PriceAtSwing" минус "PointD.PriceAtSwing".

Применяются те же проверки Фибоначчи, когда "PointD.PriceAtSwing" меньше, чем "PointX.PriceAtSwing", при этом значение "PatternFound" изменяется на true, а значение "PatternDirection" - на "Bullish" (Бычий), если оно допустимо, что обеспечивает последующую визуализацию и логику торговли. Если паттерн найден, можно приступить к его отображению на графике.

//--- If a pattern is found, visualize it and trade
if(PatternFound) {  
   //--- Log the pattern detection in the Experts tab
   Print(PatternDirection, " Cypher pattern detected at ", TimeToString(PointD.TimeOfSwing, TIME_DATE|TIME_MINUTES));  
   
   //--- Create a unique prefix for all chart objects using D’s time
   string ObjectPrefix = "CY_" + IntegerToString(PointD.TimeOfSwing);  
   //--- Set triangle color: blue for bullish, red for bearish
   color TriangleColor = (PatternDirection == "Bullish") ? clrBlue : clrRed;  
   
   //--- **Visualization Steps**
   //--- 1. Draw two filled triangles to highlight the pattern
   DrawTriangle(ObjectPrefix + "_Triangle1", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, TriangleColor, 2, true, true);  
   DrawTriangle(ObjectPrefix + "_Triangle2", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, TriangleColor, 2, true, true);  
}

Переходим к обработке визуализации обнаруженного паттерна «Шифр», когда значение "PatternFound" равно true. Используем функцию Print для регистрации обнаружения паттерна на вкладке Experts, выводя "PatternDirection", за которым следует сообщение, указывающее, что был обнаружен паттерн «Шифр», а время отформатировано функцией TimeToString с использованием флагов "PointD.TimeOfSwing" и "TIME_DATE|TIME_MINUTES" для удобства чтения.

Чтобы упорядочить объекты диаграммы, создаем уникальный префикс "ObjectPrefix", объединяя "CY_" со строковым представлением "PointD.TimeOfSwing", полученным с помощью функции IntegerToString, гарантируя, что у объектов каждого паттерна будут разные имена. Затем устанавливаем "TriangleColor", используя троичный оператор, присваивая "clrBlue" для "бычьего" паттерна или "clrRed" для "медвежьего" паттерна на основе "PatternDirection".

Для визуализации вызываем функцию "DrawTriangle" дважды: сначала, чтобы нарисовать треугольник с именем "ObjectPrefix + '_Triangle1'", соединяющий "PointX", "PointA" и "PointB", используя их значения "TimeOfSwing" и "PriceAtSwing", а затем "ObjectPrefix + '_Triangle2'", соединяющий "PointB", "PointC" и "PointD", оба с "TriangleColor", шириной линии 2 и "true" для заливки и рисования за свечами, выделяя структуру паттерна на графике. Получаем промежуточный результат.

MAPPING OF THE CYPHER HARMONIC TRIANGLES

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

//--- 2. Draw six trend lines connecting the swing points
DrawTrendLine(ObjectPrefix + "_Line_XA", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_AB", PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_BC", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_CD", PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_XB", PointX.TimeOfSwing, PointX.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_BD", PointB.TimeOfSwing, PointB.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  

//--- 3. Draw labels for each swing point (X, A, B, C, D)
double LabelOffset = 15 * SymbolInfoDouble(_Symbol, SYMBOL_POINT); //--- Offset in points for label placement
DrawTextLabel(ObjectPrefix + "_Label_X", "X", PointX.TimeOfSwing, PointX.PriceAtSwing + (PointX.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointX.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_A", "A", PointA.TimeOfSwing, PointA.PriceAtSwing + (PointA.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointA.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_B", "B", PointB.TimeOfSwing, PointB.PriceAtSwing + (PointB.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointB.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_C", "C", PointC.TimeOfSwing, PointC.PriceAtSwing + (PointC.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointC.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_D", "D", PointD.TimeOfSwing, PointD.PriceAtSwing + (PointD.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointD.IsSwingHigh);  

//--- 4. Draw a central label to identify the pattern
datetime CenterTime = (PointX.TimeOfSwing + PointB.TimeOfSwing) / 2;  //--- Middle time between X and B
double CenterPrice = PointD.PriceAtSwing;                            //--- Place it at D’s price level
if(ObjectCreate(0, ObjectPrefix + "_Label_Center", OBJ_TEXT, 0, CenterTime, CenterPrice)) {  
   ObjectSetString(0, ObjectPrefix + "_Label_Center", OBJPROP_TEXT, "Cypher"); //--- Label as "Cypher"
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_COLOR, clrBlack);  
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_FONTSIZE, 11);  
   ObjectSetString(0, ObjectPrefix + "_Label_Center", OBJPROP_FONT, "Arial Bold");  
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_ALIGN, ALIGN_CENTER);  
}  

Продолжаем процесс визуализации, чтобы еще раз проиллюстрировать паттерн «Шифр» на графике. Вызываем функцию "DrawTrendLine" шесть раз, чтобы нарисовать сплошные черные линии, соединяющие точки поворота, каждая из которых имеет "ObjectPrefix" и уникальный суффикс (например, "_Line_XA"). Эти линии связывают "PointX" с "PointA", "PointA" с "PointB", "PointB" с "PointC", "PointC" с "PointD", "PointX" с "PointB" и "PointB" с "PointD", используя соответствующие значения "TimeOfSwing" и "PriceAtSwing" с шириной линии 2 и STYLE_SOLID для четкого определения структуры паттерна.

Затем добавляем текстовые метки для каждой точки поворота, вычисляя "LabelOffset" как 15-кратный размер панкта символа, полученный с помощью функции SymbolInfoDouble с помощью SYMBOL_POINT, чтобы расположить метки соответствующим образом. Вызываем функцию "DrawTextLabel" пять раз, чтобы поставить метку "PointX", "PointA", "PointB", "PointC" и "PointD" такими именами, как "ObjectPrefix + '_Label_X'" и текстом "X", "A", "B", "C", "D". Для каждой метки используются значения "TimeOfSwing" и "PriceAtSwing" точки, скорректированные с помощью параметра "LabelOffset" (добавляется, если значение "IsSwingHigh" равно true, вычитается, если значение "false"), при этом цвет "clrBlack", размер шрифта 11 и "IsSwingHigh" определяют расположение выше или ниже точки.

Наконец, создаем центральную метку для идентификации паттерна, вычисляя "CenterTime" как среднее значение "PointX.TimeOfSwing" и "PointB.TimeOfSwing" и устанавливаем для "CenterPrice" значение "PointD.PriceAtSwing". Используем функцию ObjectCreate для создания текстового объекта с именем "ObjectPrefix + '_Label_Center'" типа "OBJ_TEXT" в этих координатах. В случае успешного выполнения настраиваем его с помощью "ObjectSetString", чтобы установить "OBJPROP_TEXT" значение "Cypher", а "OBJPROP_FONT" на "Arial Bold", а также ObjectSetInteger, чтобы установить для "OBJPROP_COLOR" значение "clrBlack", "OBJPROP_FONTSIZE" - на 11, "OBJPROP_ALIGN" установим на значение "ALIGN_CENTER", четко отмечая паттерн на графике. После компиляции получаем следующий результат.

PATTERN WITH EDGES AND LABELS

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

//--- 5. Draw trade levels (entry, take-profits) as dotted lines
datetime LineStartTime = PointD.TimeOfSwing;                    //--- Start at D’s time
datetime LineEndTime = PointD.TimeOfSwing + PeriodSeconds(_Period) * 2; //--- Extend 2 bars to the right
double EntryPrice, StopLossPrice, TakeProfitPrice, TakeProfit1Level, TakeProfit2Level, TakeProfit3Level, TradeDistance;  
if(PatternDirection == "Bullish") {  
   EntryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Buy at current ask price
   StopLossPrice = PointX.PriceAtSwing;                //--- Stop-loss at X (below entry for bullish)
   TakeProfitPrice = PointC.PriceAtSwing;              //--- Take-profit at C (target level)
   TakeProfit3Level = PointC.PriceAtSwing;             //--- Highest TP at C
   TradeDistance = TakeProfit3Level - EntryPrice;      //--- Distance to TP3
   TakeProfit1Level = EntryPrice + TradeDistance / 3;  //--- First TP at 1/3 of the distance
   TakeProfit2Level = EntryPrice + 2 * TradeDistance / 3; //--- Second TP at 2/3
} else {  
   EntryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Sell at current bid price
   StopLossPrice = PointX.PriceAtSwing;                //--- Stop-loss at X (above entry for bearish)
   TakeProfitPrice = PointC.PriceAtSwing;              //--- Take-profit at C
   TakeProfit3Level = PointC.PriceAtSwing;             //--- Lowest TP at C
   TradeDistance = EntryPrice - TakeProfit3Level;      //--- Distance to TP3
   TakeProfit1Level = EntryPrice - TradeDistance / 3;  //--- First TP at 1/3
   TakeProfit2Level = EntryPrice - 2 * TradeDistance / 3; //--- Second TP at 2/3
}  

DrawDottedLine(ObjectPrefix + "_EntryLine", LineStartTime, EntryPrice, LineEndTime, clrMagenta); //--- Entry line
DrawDottedLine(ObjectPrefix + "_TP1Line", LineStartTime, TakeProfit1Level, LineEndTime, clrForestGreen); //--- TP1 line
DrawDottedLine(ObjectPrefix + "_TP2Line", LineStartTime, TakeProfit2Level, LineEndTime, clrGreen);      //--- TP2 line
DrawDottedLine(ObjectPrefix + "_TP3Line", LineStartTime, TakeProfit3Level, LineEndTime, clrDarkGreen);  //--- TP3 line

//--- 6. Draw labels for trade levels
datetime LabelTime = LineEndTime + PeriodSeconds(_Period) / 2; //--- Place labels further right
string EntryLabelText = (PatternDirection == "Bullish") ? "BUY (" : "SELL (";  
EntryLabelText += DoubleToString(EntryPrice, _Digits) + ")"; //--- Add price to label
DrawTextLabel(ObjectPrefix + "_EntryLabel", EntryLabelText, LabelTime, EntryPrice, clrMagenta, 11, true);  

string TP1LabelText = "TP1 (" + DoubleToString(TakeProfit1Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP1Label", TP1LabelText, LabelTime, TakeProfit1Level, clrForestGreen, 11, true);  

string TP2LabelText = "TP2 (" + DoubleToString(TakeProfit2Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP2Label", TP2LabelText, LabelTime, TakeProfit2Level, clrGreen, 11, true);  

string TP3LabelText = "TP3 (" + DoubleToString(TakeProfit3Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP3Label", TP3LabelText, LabelTime, TakeProfit3Level, clrDarkGreen, 11, true);  

Здесь продолжаем визуализировать торговые уровни для паттерна «Шифр», рисуя пунктирные линии и метки. Устанавливаем для "LineStartTime" значение "PointD.TimeOfSwing", а для "LineEndTime" - на два бара дальше, используя функцию PeriodSeconds, умноженную на 2, определяя временной диапазон для горизонтальных линий.

Для "бычьего" паттерна (когда "PatternDirection" является "Bullish") устанавливаем "EntryPrice" на текущую цену ask с помощью SymbolInfoDouble с помощью SYMBOL_ASK, "StopLossPrice" на "PointX.PriceAtSwing", "TakeProfitPrice" и "TakeProfit3Level" на "PointC.PriceAtSwing", вычисляем "TradeDistance" как "TakeProfit3Level" минус "EntryPrice" и вычисляем "TakeProfit1Level" и "TakeProfit2Level" как одну треть и две трети от "TradeDistance", добавленного к "EntryPrice".

Для медвежьего паттерна мы используем SYMBOL_BID для "EntryPrice", устанавливаем "StopLossPrice" и "TakeProfit3Level" аналогично, рассчитываем "TradeDistance" как "EntryPrice" минус "TakeProfit3Level" и вычитаем одну треть и две трети значения "TradeDistance" из "EntryPrice" для "TakeProfit1Level" и "TakeProfit2Level".

Затем вызываем функцию "DrawDottedLine" четыре раза, чтобы нарисовать горизонтальные линии: "ObjectPrefix + '_EntryLine'" в "EntryPrice" в "clrMagenta" и "ObjectPrefix + '_TP1Line'", "ObjectPrefix + '_TP2Line'", "ObjectPrefix + '_TP3Line'" в "TakeProfit1Level", "TakeProfit2Level" и "TakeProfit3Level" в "clrForestGreen", "clrGreen" и "clrDarkGreen", соответственно, от "LineStartTime" до "LineEndTime".

Для нанесения меток устанавливаем для "LabelTime" значение "LineEndTime" плюс длительность половины бара, используя "PeriodSeconds". Создаем "EntryLabelText" как "BUY" или ("SELL") на основе "PatternDirection", добавляя "EntryPrice", отформатированный с помощью "DoubleToString", с использованием "_Digits", и вызываем "DrawTextLabel" для "ObjectPrefix + '_EntryLabel'" в "EntryPrice" в "clrMagenta".

Аналогично определяем "TP1LabelText", "TP2LabelText" и "TP3LabelText" с отформатированными ценами "TakeProfit1Level", "TakeProfit2Level" и "TakeProfit3Level", вызывая "DrawTextLabel" для каждого из соответствующих уровней в "clrForestGreen", "clrGreen" и "clrDarkGreen", все с размером шрифта 11 и размещенными над ценой, что повышает ясность уровня сделки. Ниже представлен результат.

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

BEARISH

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

BULLISH

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

//--- **Trading Logic**
//--- Check if trading is allowed and no position is already open
if(TradingEnabled && !PositionSelect(_Symbol)) {  
   //--- Place a buy or sell order based on pattern type
   bool TradeSuccessful = (PatternDirection == "Bullish") ?  
      obj_Trade.Buy(TradeVolume, _Symbol, EntryPrice, StopLossPrice, TakeProfitPrice, "Cypher Buy") :  
      obj_Trade.Sell(TradeVolume, _Symbol, EntryPrice, StopLossPrice, TakeProfitPrice, "Cypher Sell");  
   //--- Log the result of the trade attempt
   if(TradeSuccessful)  
      Print(PatternDirection, " order opened successfully.");  
   else  
      Print(PatternDirection, " order failed: ", obj_Trade.ResultRetcodeDescription());  
}  

//--- Force the chart to update and show all drawn objects
ChartRedraw();  

Здесь мы реализуем торговую логику для исполнения сделок по паттерну «Шифр» при выполнении условий. Проверяем, имеет ли "TradingEnabled" значение true и не открыта ли какая-либо существующая позиция по текущему инструменту, используя функцию PositionSelect с помощью _Symbol, гарантируя, что сделки будут размещаться только тогда, когда это разрешено, и не будет конфликтующих позиций. Если оба условия выполнены, используем троичный оператор для размещения сделки на основе "PatternDirection": для "бычьего" паттерна вызываем функцию "obj_Trade.Buy" с параметрами "TradeVolume", _Symbol, "EntryPrice", "StopLossPrice", "TakeProfitPrice" и комментарием "Cypher Buy", в то время как для медвежьего паттерна вызываем "obj_Trade.Sell" с теми же параметрами, но комментарием "Cypher Sell", сохраняя результат в "TradeSuccessful".

Затем регистрируем результат, используя функцию Print, выводя "PatternDirection" и "ордер открыт успешно", если значение "TradeSuccessful" равно true, или "ордер не выполнен" с описанием ошибки из "obj_Trade.ResultRetcodeDescription", если значение равно false. Наконец, вызываем функцию ChartRedraw, чтобы принудительно обновить график в MetaTrader 5, гарантируя, что все нарисованные объекты, такие как треугольники, линии и метки будут сразу видны пользователю. 

Наконец, при удалении программы нам просто нужно удалить паттерны с графика.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
//--- Runs when the EA stops (e.g., removed from chart)
void OnDeinit(const int reason) {  
   //--- Remove all objects starting with "CY_" (our Cypher pattern objects)
   ObjectsDeleteAll(0, "CY_");  
}  

В обработчике событий OnDeinit используем функцию ObjectsDeleteAll, чтобы удалить все объекты графика с именами, начинающимися с префикса "CY_", гарантируя, что все связанные с паттерном «Шифр» визуализации, такие как треугольники, линии тренда и метки, будут удалены с графика, сохраняя чистое рабочее пространство, когда система больше не активна. После компиляции получаем следующий результат.

TRADE CONFIRMATION

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


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

Во время первоначального тестирования на истории мы выявили критическую проблему: система была склонна к перерисовыванию паттернов. Перерисовка происходила, когда на одном баре оказывалось, что паттерн «Шифр» является действительным, но изменялась или исчезала при поступлении новых ценовых данных, что приводило к ненадежным торговым сигналам. Эта проблема приводила к ложным срабатываниям, когда сделки совершались на основе паттернов, которые позже оказывались недействительными, что негативно влияло на результат. Пример того, что имеется в виду.

REPAINTED PATTERN

Чтобы решить эту проблему, нами реализован механизм блокировки паттерна, используя глобальные переменные "g_patternFormationBar" и "g_lockedPatternX", чтобы заблокировать паттерн при обнаружении и подтвердить его на следующем баре, гарантируя, что точка поворота X остается неизменной. Это исправление значительно сократило перерисовку, что подтверждается последующими тестами, показавшими более стабильное обнаружение паттернов и меньшее количество недействительных сделок. Вот фрагмент образца кода для блокировки паттерна, чтобы убедиться, что мы подождем, пока паттерн не станет стабильным, прежде чем торговать по нему.

//--- If the pattern has changed, update the lock
g_patternFormationBar = CurrentBarIndex;
g_lockedPatternX = PointX.TimeOfSwing;
Print("Cypher pattern has changed; updating lock on bar ", CurrentBarIndex, ". Waiting for confirmation.");
return;

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

ENHANCED PATTERN LOCK

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

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

График

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

REPORT


Заключение

В заключение, нами успешно разработан советник на MetaQuotes Language 5, который с высокой точностью распознает Гармонический паттерн «Шифр» (Cypher) и торгует им. Объединив определение точки поворота, проверку на основе Фибоначчи, всестороннюю визуализацию и механизм блокировки паттернов для предотвращения перерисовки, мы создали надежную систему, которая динамично адаптируется к рыночным условиям.

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

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Kyle Young Sangster
Kyle Young Sangster | 3 мая 2025 в 19:30
Отличная статья. Спасибо за проделанную работу. Не могли бы вы поделиться готовым файлом кода? Прикрепить его в конце статьи?
Большое спасибо
Allan Munene Mutiiria
Allan Munene Mutiiria | 5 мая 2025 в 07:04
Kyle Young Sangster #:
Отличная статья. Спасибо за проделанную работу. Не могли бы вы поделиться готовым файлом кода? Прикрепить его в конце статьи?
Большое спасибо

Добро пожаловать. Вы хоть проверили? Спасибо.

Muhammad Syamil Bin Abdullah
Muhammad Syamil Bin Abdullah | 7 июн. 2025 в 11:08
Спасибо, что поделились этой статьей. Полезный код для реализации других гармонических паттернов.
Allan Munene Mutiiria
Allan Munene Mutiiria | 7 июн. 2025 в 17:15
Muhammad Syamil Bin Abdullah #:
Спасибо, что поделились этой статьей. Полезный код для реализации других гармонических паттернов.

Конечно. Добро пожаловать.

От начального до среднего уровня: Struct (VI) От начального до среднего уровня: Struct (VI)
В данной статье мы рассмотрим, как можно приступить к реализации базы общего структурного кода. Цель - снизить нагрузку при программировании и использовать весь потенциал самого языка программирования, в данном случае MQL5.
Управление рисками (Часть 4): Завершение ключевых методов класса Управление рисками (Часть 4): Завершение ключевых методов класса
Эта статья — четвертая часть нашей серии статей об управлении рисками в MQL5, где мы продолжаем изучать продвинутые методы защиты и оптимизации торговых стратегий. Заложив важные основы в предыдущих статьях, теперь мы сосредоточимся на завершении всех оставшихся методов, которые были отложены в третьей части, включая функции для проверки достижения определенных уровней прибыли или убытков. Кроме того, в статье будут представлены новые ключевые события, обеспечивающие более точное и гибкое управление.
Нейросети в трейдинге: Адаптивное восприятие рыночной динамики (STE-FlowNet) Нейросети в трейдинге: Адаптивное восприятие рыночной динамики (STE-FlowNet)
Фреймворк STE-FlowNet открывает новый взгляд на анализ финансовых данных, реагируя на реальные события рынка, а не на фиксированные таймфреймы. Его архитектура сохраняет локальные и временные зависимости, позволяя отслеживать даже мелкие импульсы в динамике цен.
Нейросети в трейдинге: Спайково-семантический подход к пространственно-временной идентификации (Окончание) Нейросети в трейдинге: Спайково-семантический подход к пространственно-временной идентификации (Окончание)
S3CE-Net в нашей интерпретации ловко переводит рынок в язык событий и фиксирует ранние импульсы, которые традиционные индикаторы просто усредняют. STFS гарантирует устойчивость обучения — модель видит данные под разными углами и не переобучается на локальных аномалиях. SSAM-блоки и OpenCL-реализация дают практическую скорость и точность, а разделение режимов обучение/эксплуатация сохраняет ресурсы в продакшене.