English Deutsch 日本語
preview
Автоматизация торговых стратегий в MQL5 (Часть 28): Создание гармонического паттерна "Летучая мышь" на основе Price Action с визуальной обратной связью

Автоматизация торговых стратегий в MQL5 (Часть 28): Создание гармонического паттерна "Летучая мышь" на основе Price Action с визуальной обратной связью

MetaTrader 5Торговые системы |
58 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В своей предыдущей статье (Часть 27) мы создали систему паттерна "Краб" в MetaQuotes Language 5 (MQL5). В ней используются гармонические паттерны для определения пивотных точек с высокой вероятностью с помощью точных коэффициентов Фибоначчи. Она автоматизирует сделки и визуализирует паттерны с помощью графических объектов. В Части 28 мы создадим систему паттерна "Летучая мышь", которая определяет бычьи и медвежьи гармонические паттерны "летучая мышь" с использованием пивотных точек и уровней коррекции Фибоначчи. Программа открывает сделки с расчетными точками входа, стоп-лосса и многоуровневого тейк-профита. Она дополнена визуальными треугольниками, линиями тренда и метками для четкого отображения паттернов. В статье рассмотрим следующие темы:

  1. Изучение структуры гармонического паттерна "Летучая мышь"
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

В итоге у вас будет стабильная стратегия в MQL5 для торговли на основе гармонических паттернов "Летучая мышь", готовая к настройке. Перейдём к реализации!


Изучение структуры гармонического паттерна "Летучая мышь"

Паттерн "Летучая мышь" — это гармоническая торговая формация, определяемая пятью ключевыми точками колебания цены — X, A, B, C и D - и существующая в двух формах: бычий паттерн и медвежий паттерн. Обе разновидности используются для выявления потенциальных разворотов рынка по Фибоначчи. При бычьем паттерне "Летучая мышь" структура образует последовательность "минимум-максимум-минимум-максимум-минимум", где точка X - свинг-лоу, точка A - свинг-хай, точка B - свинг-лоу (коррекция приблизительно 0,5 от XA), точка C - свинг-хай (продление от 0,382 до 0,886 от AB) и точка D - свинг-лоу (расширение 0,886 от XA, расположена выше X). Напротив, медвежий паттерн "Летучая мышь" формирует последовательность "максимум-минимум-максимум-минимум-максимум", где точка X является свинг-хай, точка A — свинг-лоу, точка B — свинг-хай, точка C — свинг-лоу, а точка D — свинг-хай (расширение 0,886 от XA и располагается ниже X). Ниже представлены визуализированные типы паттернов.

Бычий гармонический паттерн "Летучая мышь":

BULLISH BAT PATTERN

Медвежий гармонический паттерн "Летучая мышь":

BEARISH BAT PATTERN

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

  • Определение волны XA: Первоначальное движение из точки X в точку A устанавливает направление паттерна (вверх для бычьего тренда, вниз - для медвежьего) и служит основным ориентиром для расчётов по уровням Фибоначчи.
  • Создание волны "AB": Точка B должна откатиться примерно на 0,5 длины XA, указывая на умеренную коррекцию первоначального импульса.
  • Анализ волны "BC": Эта сторона должна находиться в диапазоне от 0,382 до 0,886 длины AB, создавая контрдвижение, которое подготавливает финальную зону разворота.
  • Настройка волны "CD": Заключительная волна должна продолжаться на 0,886 длины XA, отмечая потенциальную зону разворота в точке D, где паттерн завершается и генерируется торговый сигнал.

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


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

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

//+------------------------------------------------------------------+
//|                                              Bat 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 Bat 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

//---------------------------------------------------------------------------  
//--- Bat pattern definition:  
//  
//--- Bullish Bat:  
//---   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.5*(A-X); Legs within specified ranges.  
//  
//--- Bearish Bat:  
//---   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.5*(X-A); Legs within specified ranges.  
//---------------------------------------------------------------------------

//--- Structure for a pivot point  
struct Pivot {  
   datetime time;   //--- Bar time of the pivot  
   double   price;  //--- Pivot price (High for swing high, low for swing low)  
   bool     isHigh; //--- True if swing high; false if swing low  
};  

//--- Global dynamic array for storing pivots in chronological order  
Pivot pivots[];     //--- Declare a dynamic array to hold identified pivot points

//--- Global variables to lock in a pattern (avoid trading on repaint)  
int      g_patternFormationBar = -1;  //--- Bar index where the pattern was formed (-1 means none)  
datetime g_lockedPatternX      = 0;   //--- The key X pivot time for the locked pattern
bool     g_tradeTaken          = false; //--- Flag to indicate if a trade has been taken for the current pattern

Чтобы заложить основу для того, чтобы система паттерна "Летучая мышь" автоматизировала торговлю на основе гармонического паттерна "Летучая мышь", сначала подключим библиотеку "<Trade\Trade.mqh>" и создадим экземпляр объекта CTrade под названием "obj_Trade" для управления торговыми операциями, такими как открытие сделок на покупку и продажу. Затем перейдём к определению пяти входных параметров для обеспечения пользовательской настройки: Значения параметров "PivotLeft" и "PivotRight" равны 5 барам каждый, чтобы установить диапазон ретроспективного анализа для определения пивотов, "Tolerance" равно 0,10 для допустимого отклонения от уровней Фибоначчи, "LotSize" равно 0,01 для объема торговли, а значение "AllowTrading" равное true, чтобы включить автоматическую торговлю.

Далее определим структуру "Pivot" с параметрами "time" (datetime), "price" (double) и "isHigh" (bool) для хранения точек разворота цены, объявим "pivots" как динамический массив для хранения этих точек и инициализируем глобальные переменные "g_patternFormationBar" значением -1 для отслеживания формирования паттернов, "g_lockedPatternX" значением 0 для сохранения времени пивота X и установим значение параметра "g_tradeTaken" в false, чтобы предотвратить множественные сделки по одному и тому же паттерну. Тем самым закладываем основу советника для обнаружения и торговли на паттернах "Летучая мышь". Для визуализации можно использовать функции для рисования линий, меток и треугольников.

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

PIVOTS DATA

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

//--- 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;  
   g_tradeTaken = false;  
   //--- 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 Bat pattern is found  
bool patternFound = false;  
//--- Check for the high-low-high-low-high (Bearish reversal) structure  
if(X.isHigh && (!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) {  
   //--- Calculate the difference between pivot X and A  
   double diff = X.price - A.price;  
   //--- Ensure the difference is positive  
   if(diff > 0) {  
      //--- Calculate the ideal position for pivot B based on Fibonacci ratio  
      double idealB = A.price + 0.5 * 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 extension  
            double extension = D.price - A.price;  
            //--- Verify that the extension is within tolerance of 0.886 and that D is below X  
            if(MathAbs(extension - 0.886 * diff) <= Tolerance * diff && (D.price < X.price))  
               patternFound = true;  
         }  
      }  
   }  
}  
//--- Check for the low-high-low-high-low (Bullish reversal) structure  
if((!X.isHigh) && A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) {  
   //--- Calculate the difference between pivot A and X  
   double diff = A.price - X.price;  
   //--- Ensure the difference is positive  
   if(diff > 0) {  
      //--- Calculate the ideal position for pivot B based on Fibonacci ratio  
      double idealB = A.price - 0.5 * 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 extension  
            double extension = A.price - D.price;  
            //--- Verify that the extension is within tolerance of 0.886 and that D is above X  
            if(MathAbs(extension - 0.886 * diff) <= Tolerance * diff && (D.price > X.price))  
               patternFound = true;  
         }  
      }  
   }  
}

Здесь мы продолжим выявлять бычьи и медвежьи паттерны, используя точные критерии, основанные на коэффициентах Фибоначчи. Сначала определим общее количество пивотов с помощью "ArraySize(pivots)", которые хранятся в "pivotCount". Выйдем, если найдено менее 5 пивотов, сбросив значения "g_patternFormationBar", "g_lockedPatternX" и "g_tradeTaken" на -1, 0 и false соответственно, поскольку для паттерна "Летучая мышь" требуются точки 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.5 * diff", и подтвердим, что B находится в пределах "Tolerance * diff" с помощью MathAbs. Затем проверим BC (от 0,382 до 0,886 волны AB) и подтвердим расширение AD (0,886 волны XA с D ниже X). Установим для параметра "patternFound" значение true, если все условия выполнены. Наконец проверим наличие бычьего паттерна "Летучая мышь" (минимум X, максимум A, минимум B, максимум C, минимум D), рассчитаем XA как "A.price - X.price", убедимся в его положительном значении. Проверим, что B находится на уровне коррекции 0,5, BC — в пределах от 0,382 до 0,886 от AB, а AD — на уровне 0,886 длины XA, при этом D находится выше X. Установим "patternFound" в значение true, если паттерн действителен. Это обеспечивает точное обнаружение паттернов "Летучая мышь" для дальнейшей визуализации и торговли. Если паттерн обнаружен, можно перейти к его визуализации на графике.

//--- If a valid Bat pattern is detected  
if(patternFound) {  
   //--- Print a message indicating the pattern type and detection time  
   Print(patternType, " Bat 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 = "BAT_" + 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), мы инициализируем "patternType" пустой строкой и установим его значение как "Bearish", если "D.price" меньше "X.price" (что указывает на сигнал к продаже), или "Bullish", если больше (что указывает на сигнал к покупке). Затем регистрируем обнаружение с помощью функции Print, выводя "patternType" и время пивота D, отформатированное с помощью TimeToString, включая дату, минуты и секунды. Далее создадим уникальный идентификатор "signalPrefix", объединяя "BAT_" с "X.time", преобразованным в строку с помощью IntegerToString функции. Наконец установим "triangleColor" в синий цвет для бычьих или красный для медвежьих паттернов и вызовем "DrawTriangle" дважды: сначала с целью нарисовать треугольник XAB, соединяющий X, A и B. Затем с целью нарисовать треугольник BCD, соединяющий B, C и D. Используем "signalPrefix" с суффиксами "_Triangle1" и "_Triangle2", соответствующие показатели времени и цен пивотов, "triangleColor", шириной 2. А также включим заливку и отображение фона с помощью флагов со значением true. Эта логика обеспечит наглядную визуализацию структуры паттерна "Летучая мышь", что поможет принимать обоснованные торговые решения. Ниже представлен результат.

TRIANGLES SET

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

//--- 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 Bat" : "Bearish Bat");  
   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 в значение "clrBlack", "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 Bat" или "Bearish Bat" на основе "patternType", и настроим "OBJPROP_COLOR" в значение "clrBlack", "OBJPROP_FONTSIZE" - на 11, OBJPROP_FONT на "Arial Bold" и "OBJPROP_ALIGN" на "ALIGN_CENTER" с помощью ObjectSetString и "ObjectSetInteger". Эта логика обеспечивает полное визуальное представление структуры и типа паттерна на графике. При запуске программы получим следующий результат.

PATTERN WITH LABELS AND EDGES

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

//--- Define start and end times for drawing horizontal dotted lines for obj_Trade levels  
datetime lineStart = D.time;  
datetime lineEnd = D.time + PeriodSeconds(_Period)*2;  

//--- Declare variables for entry price and take profit levels  
double entryPriceLevel, TP1Level, TP2Level, TP3Level, tradeDiff;  
//--- Calculate obj_Trade levels based on whether the pattern is Bullish or Bearish  
if(patternType=="Bullish") { //--- Bullish → BUY signal  
   //--- Use the current ASK price as the entry  
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_ASK);  
   //--- Set TP3 at pivot C's price  
   TP3Level = C.price;  
   //--- Calculate the total distance to be covered by the obj_Trade  
   tradeDiff = TP3Level - entryPriceLevel;  
   //--- Set TP1 at one-third of the total move  
   TP1Level = entryPriceLevel + tradeDiff/3;  
   //--- Set TP2 at two-thirds of the total move  
   TP2Level = entryPriceLevel + 2*tradeDiff/3;  
} else { //--- Bearish → SELL signal  
   //--- Use the current BID price as the entry  
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_BID);  
   //--- Set TP3 at pivot C's price  
   TP3Level = C.price;  
   //--- Calculate the total distance to be covered by the obj_Trade  
   tradeDiff = entryPriceLevel - TP3Level;  
   //--- Set TP1 at one-third of the total move  
   TP1Level = entryPriceLevel - tradeDiff/3;  
   //--- Set TP2 at two-thirds of the total move  
   TP2Level = entryPriceLevel - 2*tradeDiff/3;  
}  

//--- Draw dotted horizontal lines to represent the entry and TP levels  
DrawDottedLine(signalPrefix+"_EntryLine", lineStart, entryPriceLevel, lineEnd, clrMagenta);  
DrawDottedLine(signalPrefix+"_TP1Line", lineStart, TP1Level, lineEnd, clrForestGreen);  
DrawDottedLine(signalPrefix+"_TP2Line", lineStart, TP2Level, lineEnd, clrGreen);  
DrawDottedLine(signalPrefix+"_TP3Line", lineStart, TP3Level, lineEnd, clrDarkGreen);  

//--- Define a label time coordinate positioned just to the right of the dotted lines  
datetime labelTime = lineEnd + PeriodSeconds(_Period)/2;  

//--- Construct the entry label text with the price  
string entryLabel = (patternType=="Bullish") ? "BUY (" : "SELL (";  
entryLabel += DoubleToString(entryPriceLevel, _Digits) + ")";  
//--- Draw the entry label on the chart  
DrawTextEx(signalPrefix+"_EntryLabel", entryLabel, labelTime, entryPriceLevel, clrMagenta, 11, true);  

//--- Construct and draw the TP1 label  
string tp1Label = "TP1 (" + DoubleToString(TP1Level, _Digits) + ")";  
DrawTextEx(signalPrefix+"_TP1Label", tp1Label, labelTime, TP1Level, clrForestGreen, 11, true);  

//--- Construct and draw the TP2 label  
string tp2Label = "TP2 (" + DoubleToString(TP2Level, _Digits) + ")";  
DrawTextEx(signalPrefix+"_TP2Label", tp2Label, labelTime, TP2Level, clrGreen, 11, true);  

//--- Construct and draw the TP3 label  
string tp3Label = "TP3 (" + DoubleToString(TP3Level, _Digits) + ")";  
DrawTextEx(signalPrefix+"_TP3Label", tp3Label, labelTime, TP3Level, clrDarkGreen, 11, true);

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

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

BEARISH PATTERN

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

BULLISH PATTERN

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

//--- Retrieve the index of the current bar  
int currentBarIndex = Bars(_Symbol, _Period) - 1;  
//--- If no pattern has been previously locked, lock the current pattern formation  
if(g_patternFormationBar == -1) {  
   g_patternFormationBar = currentBarIndex;  
   g_lockedPatternX = X.time;  
   //--- Print a message that the pattern is detected and waiting for confirmation  
   Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar.");  
   return;  
}  
//--- If still on the same formation bar, the pattern is considered to be repainting  
if(currentBarIndex == g_patternFormationBar) {  
   Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No obj_Trade yet.");  
   return;  
}  
//--- If we are on a new bar compared to the locked formation  
if(currentBarIndex > g_patternFormationBar) {  
   //--- Check if the locked pattern still corresponds to the same X pivot  
   if(g_lockedPatternX == X.time) {  
      Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening obj_Trade on bar ", currentBarIndex, ".");  
      //--- Update the pattern formation bar to the current bar  
      g_patternFormationBar = currentBarIndex;  
      //--- Only proceed with trading if allowed and if there is no existing position  
      if(AllowTrading && !PositionSelect(_Symbol) && !g_tradeTaken) {  
         double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0;  
         point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);  
         bool tradeResult = false;  
         //--- For a Bullish pattern, execute a BUY obj_Trade  
         if(patternType=="Bullish") {  //--- BUY signal  
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK);  
            double diffTrade = TP2Level - entryPriceTrade;  
            stopLoss = entryPriceTrade - diffTrade * 3;  
            takeProfit = TP2Level;  
            tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Bat Signal");  
            if(tradeResult)  {
               Print("Buy order opened successfully.");  
               g_tradeTaken = true;
            } else  
               Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription());  
         }  
         //--- For a Bearish pattern, execute a SELL obj_Trade  
         else if(patternType=="Bearish") {  //--- SELL signal  
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_BID);  
            double diffTrade = entryPriceTrade - TP2Level;  
            stopLoss = entryPriceTrade + diffTrade * 3;  
            takeProfit = TP2Level;  
            tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Bat Signal");  
            if(tradeResult)  {
               Print("Sell order opened successfully.");  
               g_tradeTaken = true;
            } else  
               Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription());  
         }  
      }  
      else {  
         //--- If a position is already open, do not execute a new obj_Trade  
         Print("A position is already open for ", _Symbol, ". No new obj_Trade executed.");  
      }  
   }  
   else {  
      //--- If the pattern has changed, update the lock with the new formation bar and X pivot  
      g_patternFormationBar = currentBarIndex;  
      g_lockedPatternX = X.time;  
      g_tradeTaken = false;  
      Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation.");  
      return;  
   }  
}  
else {  
   //--- If no valid Bat pattern is detected, reset the pattern lock variables  
   g_patternFormationBar = -1;  
   g_lockedPatternX = 0;  
   g_tradeTaken = false;  
}

На этом этапе мы завершаем реализацию логики в обработчике 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" и проверяем, разрешена ли торговля с помощью "AllowTrading" и нет ли открытых позиций с помощью PositionSelect, а также ни одна сделка не была совершена ("g_tradeTaken" false). Для бычьего паттерна установим "entryPriceTrade" равным цене Ask, рассчитаем "diffTrade" как "TP2Level - entryPriceTrade", установим "stopLoss" в три раза больше этого значения расстояния ниже, установим "takeProfit" равным "TP2Level" и откроем сделку на покупку с помощью "obj_Trade.Buy", используя "LotSize" и "Bat Signal", регистрируем успешное или неудачное выполнение и установим "g_tradeTaken" в значение true. Для медвежьего паттерна используем цену Bid, установим "stopLoss" в три раза выше и откроем сделку на продажу с помощью "obj_Trade.Sell". Если торговля запрещена, позиция уже существует или сделка исполнена, в журнал выводится сообщение о том, что новая сделка не выполняется. Если паттерн меняется, обновим сохранение и ждем. Если паттерн не найден, сбросим глобальные переменные. После компиляции получаем следующий результат.

Медвежий сигнал:

BEARISH SIGNAL

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

BULLISH SIGNAL

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


Тестирование на истории

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

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

График

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

REPORT


Заключение

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

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

Используя представленные концепции и методы реализации, можно адаптировать эту систему паттерна "Летучая мышь" к своему стилю торговли, улучшая свои алгоритмические стратегии. Удачной торговли! 

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

Прикрепленные файлы |
Bat_Pattern_EA.mq5 (50.85 KB)
Тестер стратегий для Python и MetaTrader 5 (Часть 05): Тестер стратегий для нескольких символов и таймфреймов Тестер стратегий для Python и MetaTrader 5 (Часть 05): Тестер стратегий для нескольких символов и таймфреймов
В этой статье представлен совместимый с MetaTrader 5 рабочий процесс бэктестинга, масштабируемый на разные символы и таймфреймы. Мы используем HistoryManager для параллельного сбора данных, синхронизации баров и тиков со всех таймфреймов и запуска изолированных по символам обработчиков OnTick в потоках. Вы узнаете, как режимы моделирования влияют на скорость и точность, когда стоит полагаться на данные терминала, как уменьшить операции ввода-вывода с помощью событийных обновлений и как собрать полноценного мультивалютного торгового робота.
Переосмысливаем классические стратегии (Часть 15): Стратегия пробоя диапазона предыдущего дня Переосмысливаем классические стратегии (Часть 15): Стратегия пробоя диапазона предыдущего дня
Трейдеры-люди уже давно работали на финансовых рынках до появления компьютеров, разработав практические правила, которыми они руководствовались при принятии решений. В этой статье мы вновь рассмотрим хорошо известную стратегию пробоя, чтобы проверить, может ли такая рыночная логика, усвоенная на опыте, конкурировать с систематическими методами. Наши результаты показывают, что, хотя первоначальная стратегия обеспечивала высокую точность, она страдала от нестабильности и слабого контроля рисков. Совершенствуя этот подход, мы продемонстрируем, как инсайты дискреционных трейдеров можно адаптировать в более надежные алгоритмические торговые стратегии.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Тестер стратегий для Python и MetaTrader 5 (Часть 04): Основы работы тестера Тестер стратегий для Python и MetaTrader 5 (Часть 04): Основы работы тестера
В этой увлекательной статье мы создадим своего первого торгового робота в симуляторе и запустим тестирование стратегии, напоминающее работу тестера стратегий MetaTrader 5, а затем сравним результат, полученный в пользовательской симуляции, с результатом в нашем любимом терминале.