Разработка инструментария для анализа Price Action (Часть 39): Автоматизация обнаружения BOS и ChOCh на MQL5
Введение
Приветствуем вас в Части 39 серии Разработка инструментария для анализа Price Action. В этой части мы создаем практическую систему на MQL5, которая помогает трейдерам, работающим по Price Action, лучше интерпретировать график, а не просто смотреть на свечи и пропускать смену структуры. Цель проста: превратить фрактальные опорные точки и правила структуры в надежные, не перерисовывающиеся сигналы, которым можно доверять и в реальной торговле, и при тестировании на исторических данных.
Мы используем фрактальные опорные точки в качестве надежных локальных якорей и выявляем два взаимодополняющих сигнала: ChOCh (смена характера), который сигнализирует, что рынок теряет прежнее направление – например, когда в восходящем тренде не формируется более высокий максимум или в нисходящем тренде не формируется более низкий минимум, – и BOS (пробой структуры), который подтверждает, что направление изменилось, когда цена решительно закрывается выше или ниже предыдущего максимума или минимума свинга. ChoCh следует рассматривать как раннее предупреждение, а BOS – как подтверждение.

Сочетание фракталов с ChOCh/BOS дает более чистые, не перерисовывающиеся якоря для анализа, раньше предупреждает о возможных разворотах и делает картину на нескольких таймфреймах яснее, помогая отфильтровывать шум на младших графиках. Эти же правила легко автоматизировать, вести журнал и тестировать на исторических данных – поэтому они хорошо подходят для советника.
В этой статье мы разберем проектирование алгоритма и полную реализацию на MQL5: сканирование фракталов на закрытых барах, безопасное для памяти хранение фракталов, безопасную отрисовку сохраняемых графических объектов и обработку событий, которая ведет журнал и выдает алерты по каждому подтвержденному BOS/ChoCh (на десктопных и мобильных устройствах, в том числе со звуком). В итоге у вас будет детектор профессионального уровня, который можно скомпилировать, протестировать и запустить.
Сначала мы разберем логику стратегии, затем перейдем к реализации на MQL5, алертам, параметрам логирования и уведомлений, тестированию и результатам, а в конце подведем итоги. Ниже приведено содержание.
- Введение
- Логика стратегии
- Реализация на MQL5
- Настройки алертов, логирования и уведомлений
- Тестирование и результаты
- Заключение
Логика стратегии
В этом разделе мы рассмотрим основную логику стратегии, лежащую в основе создаваемой нами системы. Этот подход основан на индикаторе фракталов и сигналах структуры, которые мы из него выводим. Если вы пропустили, в одной из предыдущих статей мы уже разбирали трендовую стратегию пробоя фрактала – фракталы универсальны: помимо пробоев, они дают надежные точки привязки для многих задач анализа Price Action. Здесь мы используем их для обнаружения смены характера (Change of Character, ChOCh) и пробоя структуры (Break of Structure, BOS).
Фрактальная опорная точка – это локальная точка разворота, которая формируется, когда максимум (или минимум) центрального бара выше (или ниже), чем у симметричного числа соседних баров. На практике, если длина окна задана как g_length = 2*p + 1, центральный бар считается бычьим фракталом, когда его максимум больше или равен каждому максимуму в окне, и медвежьим фракталом, когда его минимум меньше или равен каждому минимуму в окне. Фракталы дают стабильные, не перерисовывающиеся якоря, потому что для их определения нужно полное окно подтверждающих баров.
Пробой структуры (BOS) – это уверенное нарушение недавней структуры рынка: цена закрывается выше предыдущего максимума свинга (бычий BOS) или ниже предыдущего минимума свинга (медвежий BOS). BOS сигнализирует о том, что моментум рынка и его направление в краткосрочной и среднесрочной перспективе сместились в сторону пробоя. Именно такое подтверждение трейдеры обычно используют, чтобы присоединиться к движению в новом направлении.
Смена характера (ChOCh) – более ранний и мягкий сигнал того, что направление рынка меняется. Типичный пример – неспособность сформировать более высокий максимум в восходящем тренде или более низкий минимум в нисходящем тренде. ChOCh нужно рассматривать как предупреждение: он часто предшествует BOS, если последующее движение цены становится убедительным, и позволяет заранее подготовиться (подтянуть стопы, сократить риск или искать входы на разворот).
Чтобы избежать перерисовки, критически важно оценивать все сигналы только на основе закрытых баров. На практике это означает, что мы сравниваем закрытия завершенных баров (например, prevClose и curClose) с сохраненными уровнями фракталов; мы никогда не объявляем BOS или ChOCh на неподтвержденном, все еще формирующемся баре. Использование логики закрытых баров обеспечивает воспроизводимость событий и возможность тестирования на исторических данных.
Алгоритм намеренно сделан простым и детерминированным: он выявляет надежные фрактальные якоря на закрытых барах, отслеживает, пересекает ли цена эти якоря на закрытых барах, отмечает и отрисовывает каждое пробитие только один раз, а также формирует одну запись в журнале и один алерт для каждого подтвержденного события. Этот подход на закрытых барах предотвращает перерисовку и дает воспроизводимые сигналы, пригодные для тестирования на исторических данных.- Общая схема работы

- Дождаться нового закрытого бара (в качестве триггера использовать временную метку закрытого бара).
- Определить фрактал в центре симметричного окна длиной g_length = 2*p + 1.
- Если он найден, сохранить фрактал (время, цена) и присвоить флагу marked значение false.
- На каждом новом закрытом баре сравнивать prevClose и curClose с каждой сохраненной ценой фрактала, чтобы обнаруживать пересечения.
- Когда происходит пересечение (пробой на закрытом баре), отметить этот фрактал, отрисовать горизонтальную или трендовую линию и привязанную к нему метку, а затем сформировать одну запись в журнале и один алерт.
- Периодически удалять старые фракталы, чтобы массивы не росли без ограничений.
Основной псевдокод (кратко):
if new_closed_bar(): ScanForFractals() // detect & append new fractal anchors PruneFractals(maxKeepBars) // remove very old anchors for each fractal in stored_fractals: if not fractal.marked and crossed(prevClose, curClose, fractal.price): DrawBreak(fractal) LabelAndLog(fractal) fractal.marked = true
Реализация на MQL5
Заголовок и метаданные
В начале файла краткие комментарии содержат основную метаинформацию: имя файла, автор, сведения об авторских правах и справочные ссылки. Эти сведения полезны и для управления версиями, и как справочная информация при передаче файла другим людям или при повторном обращении к нему в будущем. Директивы #property определяют поведение при компиляции; в частности, #property strict включает более строгие проверки типов и API, что помогает выявлять тонкие ошибки уже на ранних этапах разработки. Директива #include <stdlib.mqh> подключает стандартную вспомогательную библиотеку, которая упрощает типовые задачи и делает основной код чище и удобнее для сопровождения. Если этой библиотеки у читателя нет, нужно установить ее или же удалить данную директиву, чтобы избежать ошибок компиляции.
//+------------------------------------------------------------------+ //| Fractal Reaction System.mq5| //| Copyright 2025, Christian Benjamin.| //| https://www.mql5.com/ru/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Christian Benjamin." #property link "https://www.mql5.com/ru/users/lynnchris" #property version "1.0" #property strict #include <stdlib.mqh>
Пользовательские входные параметры
Блок входных параметров задает параметры конфигурации, которые управляют поведением советника без перекомпиляции. AutoDetectLength позволяет автоматически выбирать подходящую длину фрактала по таймфрейму графика, а LengthInput – задавать ее вручную. Обратите внимание: для обнаружения фракталов нужен нечетный размер окна (например, 3, 5 или 7), чтобы в нем был один центральный бар с симметрично расположенными соседями. Параметры отображения, такие как ShowBull и ShowBear, а также настройки цвета (BullColor, BearColor) улучшают наглядность и ускоряют интерпретацию на нескольких графиках.
Параметры HorizontalRightBars и HorizontalLeftExtend определяют, насколько линии визуально продолжаются вправо или влево, что помогает оценивать актуальность уровней. DebugMode включает диагностическое логирование для разработки и тестирования, а MaxFractalHistoryBars ограничивает число хранимых исторических фракталов, не допуская неограниченного роста потребления памяти при длительной работе.
// User-configurable inputs input bool AutoDetectLength = false; input int LengthInput = 5; input bool ShowBull = true; input color BullColor = clrLime; input bool ShowBear = true; input color BearColor = clrRed; input int HorizontalRightBars = 0; input int HorizontalLeftExtend = 3; input bool DebugMode = false; input int MaxFractalHistoryBars = 2000;
Глобальные переменные и структуры данных
Глобальные переменные отвечают за состояние системы во время работы и за постоянное хранение данных. g_chart_id хранит идентификатор графика, чтобы все графические объекты были явно привязаны к нужному графику. g_length и p_half обозначают длину фрактального окна и его половину соответственно; эти значения вычисляются один раз при инициализации и затем используются повторно. ea_digits, ea_point и ea_point_pips нормализуют точность цены для разных брокеров и символов, обеспечивая единообразные смещения и корректное размещение меток. Система хранит бычьи и медвежьи фракталы в параллельных массивах (*_time[], *_price[], *_marked[]): временная метка и цена описывают каждый фрактал, а флаг marked предотвращает повторную обработку. Наконец, os_state описывает текущее направление рынка в логике системы: 0 – нейтральное, 1 – бычье, -1 – медвежье.
// Internal globals long g_chart_id; int g_length; int p_half; int ea_digits; double ea_point, ea_point_pips; datetime bull_time[]; double bull_price[]; bool bull_marked[]; datetime bear_time[]; double bear_price[]; bool bear_marked[]; int os_state = 0; // 0: none, 1: bullish, -1: bearish
Инициализация (OnInit)
OnInit() выполняет инициализацию и проверку входных параметров. Процедура получает идентификатор графика, определяет длину фрактала (автоматически или по значению пользователя), гарантирует минимально допустимое значение и следит за тем, чтобы она оставалась нечетной. Затем она вычисляет p_half и считывает точность символа, чтобы рассчитать размер пункта и пипса для точной отрисовки. Фрактальные массивы сбрасываются до пустого состояния. Когда DebugMode включен, функция выводит сообщение об инициализации с ключевыми значениями, что помогает проверить корректность конфигурации до начала работы.
int OnInit() { g_chart_id = ChartID(); // Determine fractal length if(AutoDetectLength) { if(_Period <= PERIOD_H1) g_length = 5; else if(_Period <= PERIOD_H4) g_length = 7; else if(_Period <= PERIOD_D1) g_length = 9; else g_length = 11; } else { g_length = LengthInput; } // Ensure odd length >=3 if(g_length < 3) g_length = 5; if((g_length % 2) == 0) g_length++; p_half = g_length / 2; // Get symbol info ea_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); ea_point = Point(); ea_point_pips = ea_point; if(ea_digits == 3 || ea_digits == 5) ea_point_pips = Point() * 10.0; // Clear fractal arrays ArrayResize(bull_time,0); ArrayResize(bull_price,0); ArrayResize(bull_marked,0); ArrayResize(bear_time,0); ArrayResize(bear_price,0); ArrayResize(bear_marked,0); if(DebugMode) PrintFormat("EA INIT: AutoDetect=%s LengthInput=%d g_length=%d p_half=%d chart=%d", AutoDetectLength ? "true" : "false", LengthInput, g_length, p_half, g_chart_id); return(INIT_SUCCEEDED); }
Очистка (OnDeinit)
OnDeinit() выполняет очистку, когда советник удаляется с графика. Она вызывает CleanupObjectsByPrefix, используя единый префикс, чтобы удалить все графические объекты, созданные советником (например, линии тренда и метки). Это предотвращает захламление графика оставшимися объектами и устраняет помехи для последующего анализа или работы других инструментов – важная деталь для решений профессионального уровня и демонстраций.
void OnDeinit(const int reason) { CleanupObjectsByPrefix("CHB_"); }
Основной цикл (OnTick)
OnTick() реализует модель "один запуск на каждый закрытый бар", что обеспечивает детерминированное поведение. Функция проверяет временную метку последнего закрытого бара и не выполняет никакой дальнейшей работы, если с момента предыдущего вызова бар не изменился. Когда обнаруживается новый закрытый бар, OnTick() вызывает три основные функции: ScanForFractals() для обнаружения новых подтвержденных фракталов, PruneFractals() для удаления записей старше заданного лимита истории и ProcessFractalCrosses() для проверки, пересекла ли цена какие-либо сохраненные уровни фракталов. Такая последовательность обеспечивает производительность и гарантирует, что все этапы обнаружения и обработки выполняются на закрытых барах, а это важно для воспроизводимого тестирования на исторических данных и надежной работы в реальном времени.
void OnTick() { static datetime last_checked = 0; datetime t = iTime(_Symbol, _Period, 1); if(t == last_checked) return; last_checked = t; ScanForFractals(); PruneFractals(MaxFractalHistoryBars); ProcessFractalCrosses(); }
Обнаружение фракталов (ScanForFractals)
ScanForFractals() сначала проверяет, хватает ли исторических данных, а затем анализирует центральный бар со сдвигом p_half, который соответствует середине окна фрактала. Она вызывает IsFractalHighAtShift() и IsFractalLowAtShift(), чтобы определить, является ли центральный бар бычьим фракталом или медвежьим фракталом. Эти вспомогательные функции выполняют строгие сравнения между центральным баром и его соседями. Когда обнаружен корректный фрактал и его еще нет в записях (защита от дублей по временной метке), процедура добавляет его временную метку и цену в соответствующие массивы и помечает его как необработанный, чтобы позже он мог участвовать в обнаружении пересечений.
void ScanForFractals() { int bars = iBars(_Symbol, _Period); if(bars <= g_length) return; int centerShift = p_half; if(centerShift >= bars) return; // High fractal detection if(IsFractalHighAtShift(centerShift)) { datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift); double p_fr = iHigh(_Symbol, _Period, centerShift); // Store if new bool exists = false; for(int i=0;i<ArraySize(bull_time);i++) if(bull_time[i]==t_fr) exists = true; if(!exists) { int n = ArraySize(bull_time); ArrayResize(bull_time, n+1); ArrayResize(bull_price, n+1); ArrayResize(bull_marked, n+1); bull_time[n] = t_fr; bull_price[n] = p_fr; bull_marked[n] = false; if(DebugMode) PrintFormat("FRAC_BULL DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr); } } // Low fractal detection if(IsFractalLowAtShift(centerShift)) { datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift); double p_fr = iLow(_Symbol, _Period, centerShift); // Store if new bool exists = false; for(int i=0;i<ArraySize(bear_time);i++) if(bear_time[i]==t_fr) exists = true; if(!exists) { int n = ArraySize(bear_time); ArrayResize(bear_time, n+1); ArrayResize(bear_price, n+1); ArrayResize(bear_marked, n+1); bear_time[n] = t_fr; bear_price[n] = p_fr; bear_marked[n] = false; if(DebugMode) PrintFormat("FRAC_BEAR DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr); } } }
Вспомогательные функции для проверки фракталов
Функции IsFractalHighAtShift() и IsFractalLowAtShift() проверяют, соответствует ли бар определению фрактала, перебирая симметричное окно вокруг центрального бара. Они возвращают false, если хотя бы один сосед нарушает условие доминирования центрального бара или если полное окно недоступно. Эти строгие проверки предотвращают преждевременные и ложные срабатывания и защищают от ошибок индексации в начале исторических данных или после смены таймфрейма. Для больших наборов данных стоит рассмотреть пакетное копирование исторических рядов с помощью CopyHigh/CopyLow , чтобы сократить количество повторных обращений к API на каждом баре и повысить производительность.
bool IsFractalHighAtShift(int shift) { int bars = iBars(_Symbol,_Period); int p = p_half; if(shift < 0 || shift >= bars) return false; double center = iHigh(_Symbol,_Period,shift); for(int k=-p; k<=p; k++) { if(k == 0) continue; int s = shift + k; if(s < 0 || s >= bars) return false; // incomplete window if(iHigh(_Symbol,_Period,s) > center) return false; } return true; }
Вспомогательная функция: проверка на медвежий фрактал
bool IsFractalLowAtShift(int shift) { int bars = iBars(_Symbol,_Period); int p = p_half; if(shift < 0 || shift >= bars) return false; double center = iLow(_Symbol,_Period,shift); for(int k=-p; k<=p; k++) { if(k == 0) continue; int s = shift + k; if(s < 0 || s >= bars) return false; if(iLow(_Symbol,_Period,s) < center) return false; } return true; }
Обработка пересечений (ProcessFractalCrosses)
ProcessFractalCrosses() преобразует сохраненные уровни фракталов в рабочие сигналы, проверяя, пересекло ли последнее подтвержденное закрытие какой-либо фрактальный уровень. Функция использует консервативный подход на закрытых барах: она сравнивает prevClose (предыдущий закрытый бар) и curClose (последний закрытый бар) и применяет CrossedOver() или CrossedUnder(), чтобы определить факт пересечения. Когда для необработанного фрактала обнаруживается пересечение, советник присваивает объекту уникальную метку, классифицирует событие как пробой структуры (BOS) или смену характера (ChOCh) по значению os_state, при необходимости отрисовывает линию пробоя и метку, обновляет os_state, помечает фрактал как обработанный и выдает алерты. Такая однократная обработка каждого фрактала делает поведение детерминированным и хорошо подходящим для тестирования на исторических данных.
void ProcessFractalCrosses() { double prevClose = iClose(_Symbol, _Period, 2); double curClose = iClose(_Symbol, _Period, 1); datetime curTime = (datetime)iTime(_Symbol, _Period, 1); // Process bullish fractals for(int i=0; i<ArraySize(bull_time); i++) { if(bull_marked[i]) continue; double level = bull_price[i]; if(CrossedOver(prevClose, curClose, level)) { datetime fr_time = bull_time[i]; string tag = "CHB_BULL_" + IntegerToString((int)fr_time); bool isChoCH = (os_state == -1); string niceName = isChoCH ? "Bull ChoCH" : "Bull BOS"; if(ShowBull) { DrawBreak(tag, fr_time, level, curTime, true); CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level + 3*ea_point, BullColor); } os_state = 1; bull_marked[i] = true; string msg = StringFormat("%s detected: %s %s at %s price=%s", niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits)); EmitLogAlert(msg); } } // Process bearish fractals for(int i=0; i<ArraySize(bear_time); i++) { if(bear_marked[i]) continue; double level = bear_price[i]; if(CrossedUnder(prevClose, curClose, level)) { datetime fr_time = bear_time[i]; string tag = "CHB_BEAR_" + IntegerToString((int)fr_time); bool isChoCH = (os_state == 1); string niceName = isChoCH ? "Bear ChoCH" : "Bear BOS"; if(ShowBear) { DrawBreak(tag, fr_time, level, curTime, false); CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level - 3*ea_point, BearColor); } os_state = -1; bear_marked[i] = true; string msg = StringFormat("%s detected: %s %s at %s price=%s", niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits)); EmitLogAlert(msg); } } }
Уведомления (EmitLogAlert)
EmitLogAlert() собирает всю логику уведомлений в одном месте. Она всегда выводит сообщение в лог на вкладке "Эксперты" для аудита и последующей проверки. При необходимости она показывает всплывающий алерт, отправляет push-уведомление на настроенный мобильный клиент MetaTrader и воспроизводит звуковой файл. Такой многоканальный механизм уведомлений гарантирует, что трейдеры будут вовремя получать сообщения независимо от обстоятельств. Если нужны push-уведомления, убедитесь, что в терминале настроен MetaQuotes ID.
void EmitLogAlert(const string msg) { Print(msg); if(EnableAlerts) Alert(msg); if(EnableNotifications) SendNotification(msg); if(EnableSound && StringLen(AlertSoundFile) > 0) PlaySound(AlertSoundFile); }
Функции отрисовки
DrawBreak(), CreateTrendLine() и CreateAnchoredLabel() управляют графическими объектами на графике. DrawBreak() вычисляет индексы баров для времени фрактала и пробоя с помощью iBarShift(), расширяет более старую сторону на HorizontalLeftExtend и при необходимости сдвигает более новую границу к текущему бару в соответствии с HorizontalRightBars. CreateTrendLine() создает линию тренда, привязанную к двум моментам времени, и применяет к ней визуальное оформление. CreateAnchoredLabel() размещает поясняющий текст по заданным времени и цене, со смещением на несколько пунктов для лучшей читаемости. Каждая процедура создания использует SafeDelete(), чтобы перед созданием новых объектов удалить конфликтующие объекты с тем же именем; это не дает графику засоряться и поддерживает согласованность его отображения.
void DrawBreak(const string tag, datetime fract_time, double fract_price, datetime break_time, bool bullish) { int barFr = iBarShift(_Symbol, _Period, fract_time, false); int barBreak = iBarShift(_Symbol, _Period, break_time, false); int bars = iBars(_Symbol, _Period); if(barFr == -1 || barBreak == -1) return; int older_shift = MathMax(barFr, barBreak); int newer_shift = MathMin(barFr, barBreak); // Extend left older_shift = MathMin(older_shift + HorizontalLeftExtend, bars - 1); // Extend right towards current bar if(HorizontalRightBars > 0) newer_shift = MathMax(newer_shift - HorizontalRightBars, 0); // Swap if necessary if(older_shift < newer_shift) { int tmp = older_shift; older_shift = newer_shift; newer_shift = tmp; } datetime tLeft = (datetime)iTime(_Symbol, _Period, older_shift); datetime tRight = (datetime)iTime(_Symbol, _Period, newer_shift); string lineName = tag + "_line"; CreateTrendLine(lineName, tLeft, fract_price, tRight, (bullish ? BullColor : BearColor), false); } void CreateAnchoredLabel(const string name, const string txt, datetime when, double price, color col) { SafeDelete(name); if(ObjectCreate(g_chart_id, name, OBJ_TEXT, 0, when, price)) { ObjectSetString(g_chart_id, name, OBJPROP_TEXT, txt); ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col); ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false); ObjectMove(g_chart_id, name, 0, when, price); } } void CreateTrendLine(const string name, datetime tLeft, double price, datetime tRight, color col, bool dashed=false) { SafeDelete(name); if(ObjectCreate(g_chart_id, name, OBJ_TREND, 0, tLeft, price, tRight, price)) { ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col); ObjectSetInteger(g_chart_id, name, OBJPROP_WIDTH, 2); ObjectSetInteger(g_chart_id, name, OBJPROP_STYLE, dashed ? STYLE_DASH : STYLE_SOLID); ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false); ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false); } } void SafeDelete(const string name) { if(ObjectFind(g_chart_id, name) >= 0) ObjectDelete(g_chart_id, name); }
Вспомогательные функции
CrossedOver() и CrossedUnder() выносят в отдельные функции условия пересечения на закрытии бара (prevClose <= level && curClose > level и обратное условие).
bool CrossedOver(double prevClose, double curClose, double level) { return (prevClose <= level && curClose > level); } bool CrossedUnder(double prevClose, double curClose, double level) { return (prevClose >= level && curClose < level); }
TimeframeToString() преобразует константы таймфрейма в удобочитаемые строки, чтобы сообщения в журнале были понятнее.
string TimeframeToString(int period) { switch(period) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; default: return IntegerToString(period); } }
CleanupObjectsByPrefix() удаляет все объекты с общим префиксом; функция перебирает список объектов в обратном порядке, чтобы избежать ошибок сдвига индексов при удалении.
void CleanupObjectsByPrefix(const string prefix) { long total = ObjectsTotal(g_chart_id); for(int i=total-1; i>=0; i--) { string name = ObjectName(g_chart_id, i); if(StringLen(name) >= StringLen(prefix) && StringSubstr(name, 0, StringLen(prefix)) == prefix) ObjectDelete(g_chart_id, name); } }
Удаление устаревших фракталов (PruneFractals)
PruneFractals() поддерживает производительность и стабильное потребление памяти, удаляя сохраненные фракталы старше порога MaxFractalHistoryBars . Процедура уплотняет массивы на месте с помощью указателя записи, а затем изменяет их размер, избегая лишних выделений памяти. Имейте в виду, что во время удаления iBarShift() вызывается для каждого сохраненного фрактала, поэтому слишком большой лимит хранения может увеличить время обработки. Выбор разумного значения по умолчанию, например 2000 баров, помогает сбалансировать глубину истории и эффективность выполнения.
void PruneFractals(int keepBars) { if(keepBars <=0) return; // Prune bullish fractals int nB = ArraySize(bull_time); if(nB > 0) { int write = 0; for(int i=0; i<nB; i++) { int sh = iBarShift(_Symbol, _Period, bull_time[i], false); if(sh != -1 && sh <= keepBars) { bull_time[write] = bull_time[i]; bull_price[write] = bull_price[i]; bull_marked[write] = bull_marked[i]; write++; } } if(write != nB) { ArrayResize(bull_time, write); ArrayResize(bull_price, write); ArrayResize(bull_marked, write); } } // Prune bearish fractals int nS = ArraySize(bear_time); if(nS > 0) { int write = 0; for(int i=0; i<nS; i++) { int sh = iBarShift(_Symbol, _Period, bear_time[i], false); if(sh != -1 && sh <= keepBars) { bear_time[write] = bear_time[i]; bear_price[write] = bear_price[i]; bear_marked[write] = bear_marked[i]; write++; } } if(write != nS) { ArrayResize(bear_time, write); ArrayResize(bear_price, write); ArrayResize(bear_marked, write); } } }
Этот советник представляет собой модульную систему профессионального уровня, которая определяет подтвержденные фракталы на закрытых барах, эффективно их хранит, обнаруживает подтвержденные пересечения для формирования сигналов BOS/ChOCh, наносит на график понятные пометки и выдает алерты по нескольким каналам. Каждый модуль спроектирован с учетом ясности, воспроизводимости и удобства сопровождения. В рабочем или учебном контексте стоит рекомендовать читателям тестировать систему в демосреде и включать DebugMode на этапах проверки.
Настройки алертов, логирования и уведомлений
Советник предлагает три настраиваемых канала оповещения, чтобы вы не пропустили событие: всплывающие алерты на десктопных устройствах, мобильные push-уведомления и звуковые оповещения. Используйте их вместе для максимального охвата или по отдельности, если нужна более тихая работа.
Настройки:
- EnableAlerts – всплывающие окна на рабочем столе через Alert() (срабатывают сразу и видны, пока терминал запущен). Полезно при активном мониторинге.
- EnableNotifications – мобильные push-уведомления через SendNotification() (для этого нужно указать свой MetaQuotes ID в Сервис → Настройки → Уведомления и включить уведомления в мобильном приложении MetaTrader).
- EnableSound – воспроизводит локальный звук терминала через PlaySound(filename); файл должен находиться в папке Sounds терминала, а звук в терминале не должен быть выключен.
Рекомендуемый формат сообщения алерта (четкий и пригодный для автоматического разбора):
Bull BOS detected: EURUSD H1 at 2025.08.01 14:00 price=1.12345 fr_time=2025.08.01 12:00
Указывайте символ, таймфрейм, тип события, время подтверждения, цену и при необходимости временную метку фрактала, чтобы упростить сверку.
Чтобы включить уведомления, укажите свой MetaQuotes ID в настройках терминала и убедитесь, что уведомления включены на мобильном устройстве.

Для звуковых алертов скопируйте файлы WAV или MP3 в папку Sounds терминала и убедитесь через Сервис → Настройки → События, что функция PlaySound работает корректно. Чтобы избежать спама, используйте массивы *_marked[] для защиты от дублирующихся алертов по одному и тому же фракталу и при необходимости введите короткий таймаут, например 30-120 секунд, для каждого символа в периоды высокой рыночной волатильности. Кроме того, для аудита и диагностики во время тестирования можно записывать события в CSV-файл – через FILE_COMMON или в каталог MQL5/Files терминала, – и обязательно сразу закрывать файл после записи, чтобы избежать его блокировки.
Тестирование и результаты
В этом разделе я покажу, как система проявила себя и в тестировании на исторических данных, и в реальном времени: по моим наблюдениям, результаты соответствуют целям проектирования и система ведет себя ожидаемо. Я покажу конфигурацию и метрики тестирования на исторических данных, кривую капитала и примеры сделок, которые иллюстрируют поведение детектора на практике, а также кратко разберу результаты в реальном времени по скриншотам и их сверке с тестированием на исторических данных. Наконец, я кратко подведу итоги по ограничениям и предложу следующие шаги для дальнейшей проверки.
Я провел тестирование на исторических данных по EURUSD и Step Index – в обоих случаях на таймфрейме H1, – и ниже показываю результаты. GIF-анимация ниже наглядно показывает и отмечает события ChOCh и BOS – аннотации точны, поэтому последовательность "предупреждение – подтверждение" легко прослеживается.
- Step Index H1 – фрактальная опорная точка (якорь) с аннотацией, ChOCh (не сформирован более высокий максимум) и Bull BOS (подтвержден на закрытии).

- EURUSD H1 – фрактальная опорная точка (якорь) с аннотацией, ChOCh (не сформирован более высокий максимум) и Bull BOS (подтвержден на закрытии).

Я также провел тестирование в реальном времени, чтобы подтвердить поведение системы, и инструмент работал именно так, как ожидалось. Скриншоты ниже показывают, как советник в реальном времени обнаруживает, помечает и заносит в журнал события ChOCh и BOS: алерты на рабочем столе и графические объекты появляются сразу на подтвержденном закрытом баре. Поведение в реальном времени совпало с сигналами тестирования на исторических данных по направлению, а в логах исполнения были видны только обычные эффекты реального рынка – спред и проскальзывание.
Тест в реальном времени – Volatility 75 (1s) Index M1: предупреждения ChOCh в реальном времени и подтверждения Bull BOS.
Этот скриншот тестирования в реальном времени показывает, как система использует фрактальные опорные точки для раннего выявления предупреждений ChOCh, а затем подтверждает смену структуры не перерисовывающимися маркерами BOS, наглядно подтверждая поведение детектора в реальном времени.
Тест в реальном времени – Step Index M1: предупреждения ChOCh в реальном времени и подтверждения Bull BOS
Советник сначала отмечает медвежью фазу через Bear ChOCh и последовательные уровни Bear BOS, затем подает сигнал Bull ChOCh, когда нисходящее движение выдыхается, и подтверждает бычий разворот несколькими сигналами Bull BOS – так в реальном времени проявляются ранний предупреждающий сигнал (ChOCh) и подтверждение без перерисовки (BOS).
Ниже приведены логи на вкладке "Эксперты" в MetaTrader 5.
2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.40 2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73 2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73 2025.09.03 10 : 20 :58.894 Fractal Reaction System (Step Index,M1) Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6 2025.09.03 10 : 20 :58.894 Fractal Reaction System (Step Index,M1) Alert: Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6 2025.09.03 10 : 20 :58.896 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3447.86
И в тестировании на исторических данных (EURUSD и Step Index, H1), и в реальном тестировании детектор фракталов работал именно так, как было задумано: фрактальные опорные точки давали стабильные, не перерисовывающиеся якоря, предупреждения ChOCh заранее показывали потерю прежнего направления, а подтверждения BOS надежно отмечали структурные сдвиги. Тестирование на исторических данных дало последовательные торговые события, совпадающие с аннотированными GIF-анимациями по схеме "предупреждение – подтверждение", а скриншоты из теста в реальном времени показывают тот же паттерн с поправкой только на ожидаемые рыночные эффекты – спред и проскальзывание.
Заключение
Инструмент Fractal Reaction System преобразует простые фрактальные опорные точки в надежные, не перерисовывающиеся сигналы структуры рынка: ChOCh (смена характера) – как раннее предупреждение, а BOS (пробой структуры) – как подтверждение. Представленный здесь советник безопасен с точки зрения памяти, для воспроизводимости оценивает сигналы только по закрытым барам, рисует на графике постоянные объекты структуры и ведет журнал и выдает алерты по каждому подтвержденному событию – и в тестировании на исторических данных, и в реальном времени он демонстрирует сопоставимое поведение. Его основные сильные стороны – прозрачность (проверяемые события), воспроизводимость (логика закрытых баров) и практичность (уведомления на десктопе, мобильных устройствах и через звук, плюс простая сверка по логам).
Этот инструмент – детектор сигналов, а не полноценный менеджер сделок: на фактический результат будут влиять такие факторы, как спред, проскальзывание и качество исполнения ордеров, а предупреждения ChOCh носят скорее информационный, чем предписывающий характер. Прежде чем рисковать капиталом, проверьте советник на своих инструментах и настройках брокера, изучите приложенный .set-файл для тестирования на исторических данных и журналы событий, а также подумайте о добавлении правил расчета размера позиции, фильтров старшего таймфрейма или механизма таймаута в соответствии со своим риск-профилем.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19365
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Статистический арбитраж на основе коинтегрированных акций (заключительная часть): Анализ данных с помощью специализированной БД
Разработка инструментария для анализа Price Action (Часть 38): VWAP на основе тикового буфера и модуль расчета дисбаланса на коротком окне
Разработка инструментария для анализа Price Action (Часть 40): ДНК-профиль рынка
Алгоритм Цветовой Гармонии — Color Harmony Algorithm (CHA)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте,
файл 'C:\Users\Administrator\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Include\stdlib_mq5.mqh' не найден Fractal_Reaction_System.mq5
Где я могу получить этот файл?
Крис
Здравствуйте,
файл 'C:\Users\Administrator\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Include\stdlib_mq5.mqh' не найден Fractal_Reaction_System.mq5
Где я могу получить этот файл?
Крис
Похоже на шаблон искусственного интеллекта. Возможно, используется для очистки кода. Агенты ИИ иногда навязывают здесь несовместимый синтаксис C/C++. Код компилируется, если удалить эту строку.
Для ясности, на самом деле в коде присутствует директива include:
#include <stdlib.mqh>Обратите внимание, что здесь нет подчеркивания.