Разработка инструментария для анализа Price Action (Часть 69): Обнаружение паттерна "флаг" в MQL5
Содержание
Введение
Визуально распознавать паттерны "флаг" задним числом довольно просто. В реальном времени для обнаружения нужна структурированная проверка по правилам, поскольку мешают рыночный шум, неоднородные консолидации и преждевременные пробои. Без измеримых правил для силы флагштока, глубины отката, длительности консолидации и подтверждения пробоя автоматический детектор может пропускать корректные сетапы. Он также может выдавать множество слабых или перекрывающихся паттернов. Поэтому необходима структурированная модель обнаружения. Она должна оценивать признаки продолжения по воспроизводимым количественным критериям – нормализации волатильности, порогам отката, ограничениям консолидации и подтверждению пробоя, – а не по субъективной визуальной интерпретации.
В этой статье мы разрабатываем детектор паттерна "флаг" на языке MQL5 специально для анализа и визуализации графиков в реальном времени в MetaTrader 5. Реализация выявляет бычьи и медвежьи формации "флаг", объединяя проверку флагштока, нормализованного по ATR, фильтрацию откатов, анализ консолидации, подтверждение пробоя и контроль перекрытия в единую схему обнаружения. После подтверждения корректной структуры индикатор автоматически строит флагшток, наклонный канал, затененную зону консолидации, стрелку пробоя и метки прямо на графике, а также при необходимости выдает алерты о новых обнаруженных паттернах.
Знакомство с паттерном "флаг" и структурой рынка
Что такое паттерн "флаг"?
Паттерн "флаг" – это структура продолжения, которая формируется в период временного рыночного равновесия после сильного импульсного движения. Он отражает паузу в направленном импульсе, а не разворот, и обычно характеризуется сжатием волатильности, контролируемым откатом и последующим продолжением движения после пробоя в сторону исходного тренда.
Эта структура отражает короткий период рыночного равновесия после сильного импульса. Покупатели или продавцы ненадолго ослабляют давление, цена сжимается в узком диапазоне, а волатильность снижается, прежде чем импульс возобновится. Паттерны "флаг" широко рассматриваются как паттерны продолжения, поскольку показывают, что доминирующее направление рынка сохраняется, несмотря на временную паузу. Как только цена пробивает границу консолидации, импульс часто возобновляется в направлении исходного тренда.
Существует два основных типа паттернов "флаг": бычий флаг и медвежий флаг. Бычий флаг формируется после сильного восходящего импульса и представляет собой временную нисходящую консолидацию перед продолжением движения вверх. Медвежий флаг формируется после сильного нисходящего импульса и развивается как временная консолидация с наклоном вверх перед продолжением движения вниз.
Ниже мы подробно рассмотрим обе формации, включая их структурное поведение и динамику продолжения.
Бычьи и медвежьи паттерны "флаг"
Паттерн "бычий флаг"
Бычий флаг формируется после сильного восходящего импульса (флагштока), за которым следует контролируемая фаза нисходящей или боковой консолидации.

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

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

Фаза консолидации обычно меньше самого флагштока и, как правило, развивается в относительно узком диапазоне. Именно эта сжатая структура и придает паттерну характерный вид флага на графике.
Динамика формирования
Паттерны "флаг" формируются через последовательность рыночных событий, отражающих импульс, временное равновесие и продолжение движения:
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). Индикатор успешно выявил образцовый паттерн "медвежий флаг". Хорошо видны:
- сильный нисходящий флагшток;
- зона боковой консолидации (флаг);
- стрелка пробоя, подтверждающая продолжение нисходящего тренда.
После сигнала сформировались две четкие точки входа на продажу.
Итоги тестирования
Эти тесты на разных инструментах (EURUSD и Step Index) и таймфреймах подтверждают, что индикатор эффективно обнаруживает как бычьи, так и медвежьи паттерны "флаг". Флагшток, зоны консолидации и сигналы пробоя отображаются четко, обеспечивая надежное распознавание паттернов. Убедительные результаты ручного тестирования на исторических данных, в сочетании с положительными результатами в условиях реального рынка демонстрируют надежность индикатора и подтверждают его точность в разных рыночных условиях.
Заключение
В этой статье была рассмотрена одна из ключевых проблем торговли по Price Action: сложность стабильного выявления бычьих и медвежьих паттернов "флаг" в условиях реального рынка. Из-за рыночного шума, неравномерных консолидаций и преждевременных пробоев надежное распознавание в реальном времени традиционно опиралось на субъективную визуальную оценку. Цель заключалась в создании структурированного, основанного на правилах решения на языке MQL5, способного автоматически распознавать качественные формации "флаг" и четко отображать их на графике.
Именно это и обеспечивает индикатор Flag_Pattern_Detector. В нем реализована полная процедура обнаружения, включающая проверку силы флагштока с нормализацией по ATR, фильтрацию откатов, анализ структуры консолидации, внутренние проверки импульса и подтверждение пробоя. Индикатор автоматически:
- сканирует сильные импульсные флагштоки на основе множителей ATR;
- проверяет зоны консолидации по длительности, глубине и внутреннему поведению;
- строит четкие наклонные каналы, выделяет зоны консолидации, а также отображает флагштоки и стрелки пробоя;
- предотвращает наложение и дублирование паттернов, сохраняя ясность графика;
- обеспечивает алерты в реальном времени и эффективно работает на разных инструментах и таймфреймах.
Flag_Pattern_Detector преобразует субъективную интерпретацию графика в структурированную вычислительную модель распознавания паттернов продолжения. Объединяя нормализацию волатильности, структурную проверку и графическое отображение в реальном времени, система обеспечивает последовательный и масштабируемый подход к обнаружению бычьих и медвежьих паттернов "флаг" в разных рыночных условиях. Это делает его не только торговым инструментом, но и основой для дальнейшего развития алгоритмического распознавания паттернов.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22503
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Управление позициями: Безопасный пирамидинг с единым стопом в MQL5
Рыночные секреты Ларри Уильямса (Часть 15): Торговля разворотами по паттерну Hidden Smash Day с учетом рыночного контекста
Торговые инструменты на MQL5 (Часть 32): Перекрестие, лупа и режим измерения
Торговые инструменты на MQL5 (Часть 31): Создание интерактивной палитры инструментов в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования