English
preview
Разработка инструментария для анализа Price Action (Часть 69): Обнаружение паттерна "флаг" в MQL5

Разработка инструментария для анализа Price Action (Часть 69): Обнаружение паттерна "флаг" в MQL5

MetaTrader 5Примеры |
55 0
Christian Benjamin
Christian Benjamin

Содержание


Введение

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

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


Знакомство с паттерном "флаг" и структурой рынка

Что такое паттерн "флаг"?

Паттерн "флаг" – это структура продолжения, которая формируется в период временного рыночного равновесия после сильного импульсного движения. Он отражает паузу в направленном импульсе, а не разворот, и обычно характеризуется сжатием волатильности, контролируемым откатом и последующим продолжением движения после пробоя в сторону исходного тренда.

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

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

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

Бычьи и медвежьи паттерны "флаг"

Паттерн "бычий флаг"

Бычий флаг формируется после сильного восходящего импульса (флагштока), за которым следует контролируемая фаза нисходящей или боковой консолидации. 

Эта консолидация отражает временную фиксацию прибыли и поглощение ликвидности перед возможным продолжением роста после пробоя сопротивления.

Паттерн "медвежий флаг"

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

Эта фаза отражает краткосрочное рыночное равновесие и поглощение ликвидности перед продолжением снижения после пробоя поддержки.

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

Распознавание паттерна "флаг"

Хотя паттерны "флаг" могут различаться по виду, обычно в них есть три основных элемента:

  • сильное импульсное движение, формирующее флагшток;
  • контролируемая консолидация, наклоненная против исходного тренда или движущаяся вбок;
  • пробой, возобновляющий движение в направлении исходного импульса.

Flag Pattern

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

Динамика формирования

Паттерны "флаг" формируются через последовательность рыночных событий, отражающих импульс, временное равновесие и продолжение движения:

1. Сильное направленное движение

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

2. Временная консолидация

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

3. Сжатие волатильности

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

4. Пробой и продолжение тренда

Если доминирующее рыночное давление сохраняется, цена в итоге выходит из зоны консолидации в направлении исходного тренда, завершая структуру продолжения паттерна "флаг".


Реализация на MQL5

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

Свойства и настройка индикатора

Этот индикатор настроен на работу в основном окне графика, поэтому все визуальные элементы строятся прямо в основном окне графика без лишних переходов между окнами. Он не генерирует стандартные индикаторные буферы и построения, поскольку его основная функция – графическая. В реализации установлены параметры _indicator_buffers = 0 и _indicator_plots = 0, что отключает отрисовку индикаторов по умолчанию и обеспечивает полностью графическое отображение. Вместо этого все визуальные элементы паттерна – такие как трендовые линии, прямоугольники, стрелки пробоя и поясняющие метки, – строятся динамически во время выполнения.

#property copyright "Copyright 2026, Christian Benjamin."
#property link      "https://www.mql5.com/ru/users/lynnchris"
#property version   "1.0"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

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

Пользовательские параметры и варианты настройки

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

//--- input parameters
input int      LookbackBars      =2000;        // Bars to scan backward (first load only)
input double   MinPoleATR        =1.0;         // Minimum flagpole size (ATR multiple)
input double   MaxRetracePercent =61.8;        // Max retracement of flag (% of pole)
input int      MinFlagBars       =4;           // Minimum flag duration
input int      MaxFlagBars       =0;           // Max flag duration (0 = no limit)
input bool     DebugMode         =true;        // Print detected flags to Experts tab
input color    BullFlagColor     =clrDodgerBlue;
input color    BearFlagColor     =clrTomato;

//--- alert parameters
input bool     EnableAlerts      =true;        // Popup alert on new flag
input bool     EnableSound       =false;       // Play a sound file
input string   SoundFile         ="alert.wav"; // Sound file name (terminal/Sounds)
input bool     EnableNotification=false;       // Push notification to mobile
input bool     EnableEmail       =false;       // Send email alert

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

Дополнительные параметры включают отладочный вывод и настраиваемые цвета для бычьих и медвежьих флагов, улучшающие читаемость. Индикатор также предоставляет гибкую систему алертов через EnableAlerts, EnableSound, EnableNotification и EnableEmail, благодаря которой трейдеры могут получать уведомления в реальном времени при обнаружении новых паттернов "флаг", быстрее принимать решения и своевременно реагировать на изменения рынка.

Параметры системы выстроены так, чтобы обеспечить строгую структурную фильтрацию ценового движения. Объединяя пороги на основе волатильности (множители ATR) с процентными ограничениями на откат, модель гарантирует, что корректными флагштоками будут считаться только статистически значимые импульсные движения. Это снижает чувствительность к шуму и повышает надежность паттерна в разных рыночных условиях.

Структуры для управления паттернами

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

//--- structures
struct DrawnFlag
  {
   int               poleStart;
   int               poleEnd;
   int               flagStart;
   int               flagEnd;
   datetime          startTime;
   datetime          endTime;
   bool              isBull;
  };

struct ActiveFlag
  {
   int               poleStart;
   int               poleEnd;
   int               flagStart;
   int               lastUpdate;
   bool              isBull;
   double            poleHigh;
   double            poleLow;
   double            poleLength;
   double            extreme;
   int               pullbacks;
   int               pushes;
   datetime          poleStartTime;
   datetime          poleEndTime;
  };

DrawnFlag   drawnFlags[];
ActiveFlag  activeFlags[];
int         atrHandle;
double      atrBuffer[];

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

Инициализация – настройка ресурсов

При загрузке индикатора вызывается функция OnInit(), которая инициализирует необходимые ресурсы и хэндлы. Она создает хэндл индикатора Average True Range (ATR) с помощью функции iATR(), которая предоставляет меру волатильности, критически важную для проверки значимости потенциальных флагштоков. Если создать этот хэндл не удается, процесс инициализации останавливается, чтобы предотвратить ошибки во время выполнения.

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   atrHandle=iATR(_Symbol,PERIOD_CURRENT,20);
   if(atrHandle==INVALID_HANDLE)
      return INIT_FAILED;

   if(DebugMode)
      Print("Flag Detector v1.0 loaded on ",_Symbol);

   return INIT_SUCCEEDED;
  }

Кроме того, если включен режим отладки, во вкладке "Эксперты" выводится сообщение, подтверждающее успешную загрузку индикатора. На этом этапе графические объекты, такие как трендовые линии, прямоугольники, стрелки и метки, еще не создаются – они будут динамически формироваться во время обнаружения паттернов. Такая настройка ресурсов обеспечивает эффективность, корректное управление ресурсами и готовность к обнаружению паттернов в реальном времени.

Деинициализация и очистка ресурсов

Когда индикатор удаляется с графика или терминал закрывается, выполняется функция OnDeinit(), освобождающая ресурсы и удаляющая графические объекты. Правильная деинициализация особенно важна для графических индикаторов, поскольку во время работы динамически создается множество графических объектов и хэндлов индикаторов.

//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(atrHandle!=INVALID_HANDLE)
      IndicatorRelease(atrHandle);

   ObjectsDeleteAll(0,"Flag_");
   ArrayFree(drawnFlags);
   ArrayFree(activeFlags);
  }

Хэндл ATR освобождается с помощью IndicatorRelease() для освобождения ресурсов. Затем графические объекты, созданные индикатором, удаляются с помощью ObjectsDeleteAll() с префиксом "Flag_", что в MQL5 является штатным способом удаления объектов по префиксу имени на текущем графике.

Основной расчет (OnCalculate()) – логика обнаружения паттернов

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

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

Как только обнаруживается потенциальный флагшток, код затем выполняет поиск вперед по барам, чтобы найти развивающееся в противоположном направлении движение, указывающее на возможное формирование паттерна "флаг". Затем код проверяет, что откат от максимума или минимума флагштока не превышает максимальный процент, заданный параметром MaxRetracePercent.

//+------------------------------------------------------------------+
//| OnCalculate – Main Processing Loop                               |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t prev_calculated,
                const datetime &time[],
                const double   &open[],
                const double   &high[],
                const double   &low[],
                const double   &close[],
                const long     &tick_volume[],
                const long     &volume[],
                const int32_t  &spread[])
  {
   if(CopyBuffer(atrHandle,0,0,rates_total,atrBuffer)!=rates_total)
      return 0;

   if(prev_calculated==0)
     {
      //--- first run: scan for completed historical flags
      ScanHistoricalFlags(rates_total,open,high,low,close,time);

      //--- scan for active (unfinished) flags
      int activeScanStart=MathMax(0,rates_total-LookbackBars);
      for(int i=activeScanStart; i<=rates_total-MinFlagBars-1; i++)
        {
         int  moveStart,moveEnd;
         bool isBullMove;
         if(FindThreeBarMove(i,rates_total,open,close,atrBuffer,moveStart,moveEnd,isBullMove))
            TryAddActiveFlag(moveStart,moveEnd,isBullMove,high,low,time,rates_total);
        }
     }
   else
     {
      int newBars=rates_total-prev_calculated;
      if(newBars>0)
        {
         //--- update existing active flags
         for(int i=ArraySize(activeFlags)-1; i>=0; i--)
           {
            bool remove=false;
            for(int bar=prev_calculated; bar<rates_total; bar++)
              {
               if(UpdateActiveFlag(i,high,low,close,time,bar,rates_total))
                 {
                  remove=true;
                  break;
                 }
              }
            if(remove)
               RemoveActiveFlag(i);
           }

         //--- scan recent bars for new flagpoles
         int newPoleScanStart=MathMax(0,rates_total-5);
         for(int i=newPoleScanStart; i<=rates_total-3; i++)
           {
            int  moveStart,moveEnd;
            bool isBullMove;
            if(FindThreeBarMove(i,rates_total,open,close,atrBuffer,moveStart,moveEnd,isBullMove))
               TryAddActiveFlag(moveStart,moveEnd,isBullMove,high,low,time,rates_total);
           }
        }
     }

   return rates_total;
  }

После завершения первоначального исторического сканирования (prev_calculated == 0) индикатор переходит в режим обработки в реальном времени, используя параметр prev_calculated. В этом режиме выполняются инкрементальные обновления вместо полного пересчета графика, что в MQL5 является штатным подходом к эффективной обработке индикаторов. В режиме реального времени система обновляет активные паттерны, проверяет новые бары на наличие пробоя или признаков отмены сценария и сканирует только самый свежий ценовой участок в поиске новых флагштоков. Это позволяет индикатору быстро реагировать на изменения без лишних повторных вычислений.

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

В MQL5 prev_calculated — это предыдущее возвращаемое значение OnCalculate(), и платформа использует его именно для экономичного пересчета только тех баров, которые изменились.

Ветвь реального времени выполняет три ключевые операции:

  • Обновление активных флагов

Существующие активные паттерны, хранящиеся в activeFlags[], непрерывно обновляются с помощью функции UpdateActiveFlag(). Каждый новый бар проверяется на продолжение отката, признаки отмены сценария или подтверждение пробоя.

  • Обнаружение пробоя и очистка

Когда условие пробоя подтверждается, паттерн сразу строится на графике с помощью DrawSlantedPattern(), заносится в drawnFlags[] и при необходимости удаляется из массива активного отслеживания.

  • Повторное микросканирование флагштоков (только последние бары)

Система выполняет быстрое сканирование последних баров (примерно пяти), чтобы обнаружить новые формирующиеся флагштоки. Это позволяет быстро выявлять формирующиеся паттерны без повторения полного исторического анализа.

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

Чтобы обеспечить корректность паттерна, дополнительно проверяется, соответствует ли его длительность ограничениям по минимальному и максимальному числу баров флага (MinFlagBars и MaxFlagBars). Для подтверждения внутренней согласованности анализируется количество откатов и импульсных продвижений внутри паттерна.

Вспомогательные функции и визуализация

DrawSlantedPattern()

Функция DrawSlantedPattern() создает на графике полную визуальную структуру подтвержденного паттерна "флаг". Она рисует флагшток, строит канал консолидации, выделяет зону паттерна, размещает стрелку пробоя и добавляет текстовую метку, чтобы сетап было легко распознать с первого взгляда.

Канал консолидации строится по динамически выбранным максимумам и минимумам внутри области флага. Для бычьих паттернов "флаг" канал наклоняется вниз после импульсного роста, а для медвежьих – вверх после снижения. Благодаря этому структура следует за рынком, а не подгоняется под фиксированный шаблон.

//+------------------------------------------------------------------+
//| Draw completed flag pattern                                      |
//+------------------------------------------------------------------+
void DrawSlantedPattern(int poleStart,int poleEnd,int flagStart,int flagEnd,bool isBull,
                        const double &high[],const double &low[],const datetime &time[])
  {
//--- Build unique object name prefix for this pattern
   string prefix="Flag_"+IntegerToString(poleStart)+"_"+IntegerToString(poleEnd)+"_";
//--- Remove any previous drawings with the same prefix (to avoid clutter)
   ObjectsDeleteAll(0,prefix);

   int      endConsol=MathMax(flagStart,flagEnd-1);
   datetime startTime=time[flagStart];
   datetime endTime  =time[endConsol];
   color    clr      =isBull ? BullFlagColor : BearFlagColor;
   string   typeStr  =isBull ? "Bullish Flag" : "Bearish Flag";

//--- 1. Draw the flagpole as a solid line from the start to the end of the impulsive move
   ObjectCreate(0,prefix+"pole",OBJ_TREND,0,
                time[poleStart],isBull ? low[poleStart] : high[poleStart],
                time[poleEnd],  isBull ? high[poleEnd]  : low[poleEnd]);
   ObjectSetInteger(0,prefix+"pole",OBJPROP_COLOR,clr);
   ObjectSetInteger(0,prefix+"pole",OBJPROP_WIDTH,2);
   ObjectSetInteger(0,prefix+"pole",OBJPROP_RAY_RIGHT,false);

//--- 2. Construct a slanted parallel channel that encloses the consolidation
//--- The channel slope is estimated from two anchor points inside the consolidation
   int    half=flagStart+(endConsol-flagStart)/2;
   double upperStart,upperEnd,lowerStart,lowerEnd;

//--- Bull flag: channel slopes downward
   if(isBull)
     {
      //--- Find the highest high in the first half and second half for slope calculation
      int    idxHighFirst=flagStart;
      int    idxHighSecond=half+1;
      double highFirst   =high[flagStart];
      double highSecond  =(half+1<=endConsol) ? high[half+1] : high[flagStart];

      for(int i=flagStart+1; i<=half && i<=endConsol; i++)
        {
         if(high[i]>highFirst)
           {
            highFirst   =high[i];
            idxHighFirst=i;
           }
        }

      for(int i=half+2; i<=endConsol; i++)
        {
         if(high[i]>highSecond)
           {
            highSecond   =high[i];
            idxHighSecond=i;
           }
        }

      //--- If the second high is equal to or above the first, force a slight downward tilt
      if(highSecond>=highFirst)
         highSecond=highFirst-(highFirst-FindMinInRange(flagStart,endConsol,low))*0.1;

      //--- Calculate the slope of the upper line
      double slope=0;
      if(time[idxHighSecond]!=time[idxHighFirst])
         slope=(highSecond-highFirst)/(double)(time[idxHighSecond]-time[idxHighFirst]);

      //--- Extend the upper line across the whole consolidation period
      upperStart=highFirst+slope*(startTime-time[idxHighFirst]);
      upperEnd  =highFirst+slope*(endTime  -time[idxHighFirst]);

      //--- Find the lowest low to anchor the lower channel line
      double lowestLow=FindMinInRange(flagStart,endConsol,low);
      int    idxLowest=flagStart;
      for(int i=flagStart+1; i<=endConsol; i++)
        {
         if(low[i]<lowestLow)
           {
            lowestLow=low[i];
            idxLowest=i;
           }
        }

      //--- Create the lower line parallel to the upper line
      lowerStart=lowestLow+slope*(startTime-time[idxLowest]);
      lowerEnd  =lowestLow+slope*(endTime  -time[idxLowest]);

      //--- Push the lower line down if any low falls below it (ensures all bars are inside)
      for(int i=flagStart; i<=endConsol; i++)
        {
         double ratio  =(double)(time[i]-startTime)/(double)(endTime-startTime);
         double lineVal=lowerStart+(lowerEnd-lowerStart)*ratio;
         if(low[i]<lineVal)
           {
            double diff=lineVal-low[i]+_Point;
            lowerStart-=diff;
            lowerEnd  -=diff;
           }
        }
     }
//--- Bear flag: channel slopes upward
   else
     {
      //--- Find the lowest low in the first and second halves for slope calculation
      int    idxLowFirst=flagStart;
      int    idxLowSecond=half+1;
      double lowFirst   =low[flagStart];
      double lowSecond  =(half+1<=endConsol) ? low[half+1] : low[flagStart];

      for(int i=flagStart+1; i<=half && i<=endConsol; i++)
        {
         if(low[i]<lowFirst)
           {
            lowFirst   =low[i];
            idxLowFirst=i;
           }
        }

      for(int i=half+2; i<=endConsol; i++)
        {
         if(low[i]<lowSecond)
           {
            lowSecond   =low[i];
            idxLowSecond=i;
           }
        }

      //--- Force a slight upward tilt if the second low is equal to or below the first
      if(lowSecond<=lowFirst)
         lowSecond=lowFirst+(FindMaxInRange(flagStart,endConsol,high)-lowFirst)*0.1;

      //--- Calculate slope of the lower line
      double slope=0;
      if(time[idxLowSecond]!=time[idxLowFirst])
         slope=(lowSecond-lowFirst)/(double)(time[idxLowSecond]-time[idxLowFirst]);

      //--- Extend the lower line across the consolidation
      lowerStart=lowFirst+slope*(startTime-time[idxLowFirst]);
      lowerEnd  =lowFirst+slope*(endTime  -time[idxLowFirst]);

      //--- Find the highest high to anchor the upper channel line
      double highestHigh=FindMaxInRange(flagStart,endConsol,high);
      int    idxHighest=flagStart;
      for(int i=flagStart+1; i<=endConsol; i++)
        {
         if(high[i]>highestHigh)
           {
            highestHigh=high[i];
            idxHighest =i;
           }
        }

      //--- Upper line parallel to lower line
      upperStart=highestHigh+slope*(startTime-time[idxHighest]);
      upperEnd  =highestHigh+slope*(endTime  -time[idxHighest]);

      //--- Push the upper line up if any high sticks out
      for(int i=flagStart; i<=endConsol; i++)
        {
         double ratio  =(double)(time[i]-startTime)/(double)(endTime-startTime);
         double lineVal=upperStart+(upperEnd-upperStart)*ratio;
         if(high[i]>lineVal)
           {
            double diff=high[i]-lineVal+_Point;
            upperStart+=diff;
            upperEnd  +=diff;
           }
        }
     }

//--- 3. Draw the upper and lower channel lines (dashed and bold)
   ObjectCreate(0,prefix+"upper",OBJ_TREND,0,startTime,upperStart,endTime,upperEnd);
   ObjectSetInteger(0,prefix+"upper",OBJPROP_COLOR,clr);
   ObjectSetInteger(0,prefix+"upper",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,prefix+"upper",OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(0,prefix+"upper",OBJPROP_RAY_RIGHT,false);

   ObjectCreate(0,prefix+"lower",OBJ_TREND,0,startTime,lowerStart,endTime,lowerEnd);
   ObjectSetInteger(0,prefix+"lower",OBJPROP_COLOR,clr);
   ObjectSetInteger(0,prefix+"lower",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,prefix+"lower",OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(0,prefix+"lower",OBJPROP_RAY_RIGHT,false);

//--- 4. Fill the channel area with a lightened rectangle
   double rectHigh=MathMax(MathMax(upperStart,upperEnd),MathMax(lowerStart,lowerEnd));
   double rectLow =MathMin(MathMin(upperStart,upperEnd),MathMin(lowerStart,lowerEnd));
   color  fillClr =ColorLighter(clr,70);
   ObjectCreate(0,prefix+"rect",OBJ_RECTANGLE,0,startTime,rectHigh,endTime,rectLow);
   ObjectSetInteger(0,prefix+"rect",OBJPROP_COLOR,fillClr);
   ObjectSetInteger(0,prefix+"rect",OBJPROP_FILL,true);
   ObjectSetInteger(0,prefix+"rect",OBJPROP_BACK,true);  
   ObjectSetInteger(0,prefix+"rect",OBJPROP_WIDTH,1);

//--- 5. Place a breakout arrow at the end of the flag
   if(isBull)
      ObjectCreate(0,prefix+"arrow",OBJ_ARROW_UP,  0,time[flagEnd],low[flagEnd] -10*_Point);
   else
      ObjectCreate(0,prefix+"arrow",OBJ_ARROW_DOWN,0,time[flagEnd],high[flagEnd]+10*_Point);

   ObjectSetInteger(0,prefix+"arrow",OBJPROP_COLOR,clr);
   ObjectSetInteger(0,prefix+"arrow",OBJPROP_WIDTH,2);

//--- 6. Add a simple text label in the middle of the channel
   int    midIdx    =(flagStart+endConsol)/2;
//--- Place label slightly above the channel (bull) or below (bear)
   double labelPrice=isBull ? rectHigh+(rectHigh-rectLow)*0.15 : rectLow-(rectHigh-rectLow)*0.15;
   ObjectCreate(0,prefix+"label",OBJ_TEXT,0,time[midIdx],labelPrice);
   ObjectSetString(0,prefix+"label",OBJPROP_TEXT,    typeStr);
   ObjectSetInteger(0,prefix+"label",OBJPROP_COLOR,   clr);
   ObjectSetInteger(0,prefix+"label",OBJPROP_FONTSIZE,9);
   ObjectSetInteger(0,prefix+"label",OBJPROP_BACK,    true);
  }

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

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

IsTooClose() и RecordDrawnFlag()

Функция IsTooClose() не позволяет отрисовать один и тот же флаг более одного раза, проверяя, совпадают ли вновь обнаруженные начальный и конечный бары с ранее зафиксированным паттерном. Это позволяет не перегружать график и избегать повторной разметки одного и того же сетапа.

//+------------------------------------------------------------------+
//| Store confirmed pattern                                          |
//+------------------------------------------------------------------+
void RecordDrawnFlag(int poleStart,
                     int poleEnd,
                     int flagStart,
                     int flagEnd,
                     datetime startTime,
                     datetime endTime,
                     bool isBull)
  {
   int sz = ArraySize(drawnFlags);
   ArrayResize(drawnFlags, sz + 1);

   drawnFlags[sz].poleStart = poleStart;
   drawnFlags[sz].poleEnd   = poleEnd;
   drawnFlags[sz].flagStart  = flagStart;
   drawnFlags[sz].flagEnd    = flagEnd;
   drawnFlags[sz].startTime  = startTime;
   drawnFlags[sz].endTime    = endTime;
   drawnFlags[sz].isBull     = isBull;
  }

Функция RecordDrawnFlag() сохраняет координаты подтвержденного паттерна после подтверждения пробоя. Сохраненные данные впоследствии используются для предотвращения повторного обнаружения и для сохранения завершенных паттернов для дальнейшего анализа.

//+------------------------------------------------------------------+
//| Try to add an active flag                                        |
//+------------------------------------------------------------------+
bool TryAddActiveFlag(int poleStart,
                      int poleEnd,
                      bool isBull,
                      const double &high[],
                      const double &low[],
                      const datetime &time[],
                      int rates_total)

Управление активными паттернами

TryAddActiveFlag() и RemoveActiveFlag()

Функция TryAddActiveFlag() анализирует недавно обнаруженный флагшток и решает, следует ли отслеживать его как активный паттерн. Она проверяет, не была ли структура уже зафиксирована, применяет ограничения по откату и длительности, а также оценивает внутреннее поведение консолидации перед добавлением сетапа в активное отслеживание.

//+------------------------------------------------------------------+
//| Update active pattern                                            |
//+------------------------------------------------------------------+
bool UpdateActiveFlag(int index,
                      const double &high[],
                      const double &low[],
                      const double &close[],
                      const datetime &time[],
                      int newBar,
                      int rates_total)

Если пробой подтверждается, паттерн окончательно отображается на графике, срабатывают алерты (если они включены), а завершенный сетап записывается в массив DrawnFlag. Недействительные или устаревшие паттерны удаляются с помощью функции RemoveActiveFlag(), чтобы в активном отслеживании оставались только актуальные формации.

//+------------------------------------------------------------------+
//| Remove active flag                                               |
//+------------------------------------------------------------------+
void RemoveActiveFlag(int index)

  • RemoveActiveFlag() удаляет недействительные или завершенные сетапы из активного отслеживания, чтобы под наблюдением оставались только актуальные паттерны.

Алерты и информирование пользователя

//+------------------------------------------------------------------+
//| Alert manager                                                    |
//+------------------------------------------------------------------+
void DoAlert(string msg,
             bool playSound,
             bool pushNote,
             bool sendMail)
  {
   //--- Check if alerts are enabled
   if(!EnableAlerts)
      return;

   //--- Display popup alert
   Alert(msg);

   //--- Play notification sound
   if(playSound)
      PlaySound(SoundFile);

   //--- Send push notification
   if(pushNote)
      SendNotification(msg);

   //--- Send email notification
   if(sendMail)
      SendMail("Flag Detector Alert", msg);
  }

Функция DoAlert() отвечает за уведомления в соответствии с настройками пользователя. Она может показывать всплывающие алерты, воспроизводить звук, отправлять мобильное уведомление или письмо по электронной почте с сигналом при подтверждении корректности паттерна.

//+------------------------------------------------------------------+
//| Lighten a color                                                  |
//+------------------------------------------------------------------+
color ColorLighter(color clr, double percent)
  {
   percent = MathMax(0, MathMin(100, percent));

   double factor = percent / 100.0;

   uchar r = (uchar)((clr >> 16) & 0xFF);
   uchar g = (uchar)((clr >> 8)  & 0xFF);
   uchar b = (uchar)(clr & 0xFF);

   r = (uchar)(r + (255 - r) * factor);
   g = (uchar)(g + (255 - g) * factor);
   b = (uchar)(b + (255 - b) * factor);

   return (color)((r << 16) | (g << 8) | b);
  }

Кроме того, вспомогательная функция ColorLighter() формирует более светлый цвет заливки для ненавязчивого выделения областей паттерна, улучшая читаемость и не перекрывая исходные ценовые данные. Эти механизмы генерации алертов и цветового выделения работают эффективно, но ненавязчиво, помогая трейдерам вовремя замечать важные изменения в развитии паттернов.


Результаты

После успешной компиляции индикатора в MetaEditor следующим важным шагом становится проверка его корректности и эффективности. Такую проверку следует проводить на различных валютных парах (например, на основных, второстепенных и экзотических) и на нескольких таймфреймах – от M1 до MN – в разных рыночных условиях, чтобы убедиться в надежности и устойчивости решения.

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

Тестирование на исторических данных

GIF-анимация ниже показывает работу индикатора во время тестирования на EURUSD M5.

Индикатор успешно обнаруживает и четко отображает как бычьи, так и медвежьи паттерны "флаг". Как видно на GIF, флагшток, зона консолидации и стрелки пробоя четко обозначены. В большинстве случаев обнаруженные паттерны дают результативные сигналы, что видно при прокрутке исторического ценового движения на графике в ходе тестирования на исторических данных.

Работа в реальном времени

На изображении выше показан другой пример тестирования на Step Index (M1). Индикатор успешно выявил образцовый паттерн "медвежий флаг". Хорошо видны:

  1. сильный нисходящий флагшток;
  2. зона боковой консолидации (флаг);
  3. стрелка пробоя, подтверждающая продолжение нисходящего тренда.

После сигнала сформировались две четкие точки входа на продажу.

Итоги тестирования

Эти тесты на разных инструментах (EURUSD и Step Index) и таймфреймах подтверждают, что индикатор эффективно обнаруживает как бычьи, так и медвежьи паттерны "флаг". Флагшток, зоны консолидации и сигналы пробоя отображаются четко, обеспечивая надежное распознавание паттернов. Убедительные результаты ручного тестирования на исторических данных, в сочетании с положительными результатами в условиях реального рынка демонстрируют надежность индикатора и подтверждают его точность в разных рыночных условиях.


Заключение

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

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

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

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

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

Прикрепленные файлы |
Управление позициями: Безопасный пирамидинг с единым стопом в MQL5 Управление позициями: Безопасный пирамидинг с единым стопом в MQL5
В этой статье представлен CPyramidEngine – переиспользуемый класс на языке MQL5, который добавляет в любой советник дисциплинированное пирамидирование и требует для интеграции всего около шести изменений в коде. Движок обеспечивает соблюдение трех ограничений: размеры лотов должны строго уменьшаться, единый стоп – сдвигаться после каждого добавления, а каждая модификация – проходить проверку на уровне брокера. В статье разбираются типичные сценарии отказа наивных реализаций и показывается, как по мере добавления позиций сохранять общий риск по счету измеримым и контролируемым.
Рыночные секреты Ларри Уильямса (Часть 15): Торговля разворотами по паттерну Hidden Smash Day с учетом рыночного контекста Рыночные секреты Ларри Уильямса (Часть 15): Торговля разворотами по паттерну Hidden Smash Day с учетом рыночного контекста
Создадим советник MQL5, который автоматизирует развороты Hidden Smash Day Ларри Уильямса. Он считывает подтвержденные сигналы из пользовательского индикатора, применяет фильтры рыночного контекста (включая проверку направления по Supertrend и необязательные правила торговых дней) и управляет риском с помощью моделей стоп-лосса на основе структуры бара Smash или ATR, а также фиксированного или риск-ориентированного размера позиции. В результате получается воспроизводимая система, готовая к тестированию и расширению.
Торговые инструменты на MQL5 (Часть 32): Перекрестие, лупа и режим измерения Торговые инструменты на MQL5 (Часть 32): Перекрестие, лупа и режим измерения
В этой статье мы расширяем палитру инструментов, добавляя прецизионное перекрестие для графиков MQL5: ретикул с делениями, линии на всю ширину и всю высоту графика с метками осей, а также круговую лупу для отображения увеличенных свечей. Режим измерения двойным щелчком добавляет якорные маркеры, диагональный соединитель и плавающую метку с количеством баров, расстоянием в пипсах и разницей цен. Детали реализации включают менеджер перекрестия, одиннадцать слоев холста (canvas), алгоритм Брезенхема для рисования линий и поведение с учётом темы оформления: элементы перекрестия скрываются при наведении на боковую или выдвижную панель.
Торговые инструменты на MQL5 (Часть 31): Создание интерактивной палитры инструментов в MQL5 Торговые инструменты на MQL5 (Часть 31): Создание интерактивной палитры инструментов в MQL5
Мы превращаем боковую панель "Палитра инструментов" из статической оболочки в интерактивную систему MQL5. В статье реализованы выдвижные панели для каждой категории, обработчик событий графика, механизм рисования с несколькими щелчками мыши (инструменты с одним, двумя и тремя щелчками), а также взаимодействие с мышью, включая перетаскивание, изменение размера нижнего края, прокрутку, состояния при наведении курсора и переключение тем в реальном времени. Вы сможете выбирать инструмент и размещать объекты графика непосредственно из палитры для анализа.