Рыночные секреты Ларри Уильямса (Часть 11): Индикатор для обнаружения разворотов Smash Day
Введение
Попытка торговать каждый разворот Smash Day на графике может быстро привести к шуму, разочарованию и нестабильным результатам. Поначалу это кажется логичным. Резкий пробой, за которым сразу следует неудача, часто приводит к сильным движениям в противоположном направлении. Однако если торговать каждый сигнал без учета контекста, обычно возникает шум, разочарование и нестабильные результаты.
В своей книге Долгосрочные секреты краткосрочной торговли Ларри Уильямс делает важное замечание. Эти паттерны отражают моменты эмоционального перегрева, когда толпа бросается в то, что выглядит как пробой, но затем оказывается в ловушке из-за внезапного разворота. Такая эмоциональная ловушка сильна, но это не означает, что ее нужно торговать безоговорочно. Уильямс многократно подчеркивает, что лучшие результаты появляются тогда, когда такие паттерны совпадают с более широкой рыночной картиной, включая преобладающие тренды и другие подтверждающие условия.
Это создает практическую задачу для дискреционных трейдеров. Как быстро и объективно находить эти редкие, но значимые паттерны, не превращая их в жесткую автоматизированную систему, игнорирующую контекст?
Решение, представленное в этой статье, — это пользовательский индикатор MQL5 который визуализирует развороты smash day. Вместо автоматического открытия сделок он точно показывает, где сформировался подтвержденный паттерн и когда цена подтверждает разворот, пробивая уровень smash-бара. Для бычьих разворотов появляется четкая стрелка вверх, а для медвежьих разворотов — четкая стрелка вниз. С этого момента окончательное решение остается за человеком.
Преобразуя объективные правила паттерна Ларри Уильямса в четкие графические сигналы, этот индикатор убирает неопределенность из процесса распознавания, но при этом позволяет трейдерам применять собственные фильтры, такие как направление тренда, влияние новостей и структура рынка. Он служит мостом чистым анализом ценового движения (Price action) и осмысленной дискреционной торговлей, а не механическим «черным ящиком».
Быстрое распознавание разворотов Smash Day
Разворот Smash Day начинается с сильного и эмоционального импульса в одном направлении, который, как кажется, подтверждает пробой. Цена закрылась за пределами недавнего торгового диапазона, создавая впечатление, что импульс продолжится. Вместо продолжения рынок быстро теряет импульс и на следующей сессии разворачивается в другую сторону. Эта внезапная неудача ловит в ловушку тех, кто слишком поздно вошел в пробой и часто подпитывает резкое движение в противоположном направлении.
Бычий разворот Smash Day появляется после агрессивной распродажи. Smash-бар закрывается ниже минимумов одного или нескольких предыдущих баров, но при этом не является внешней свечой (outside bar). Эта деталь важна, потому что движение вызвано направленным давлением рынка, а не нерешительностью участников. Когда следующий бар поднимается выше максимума этого smash-бара, пробой вниз оказывается ложным, и подается сигнал разворота вверх.
Медвежий разворот Smash Day является зеркальным вариантом. Smash-бар закрывается выше максимумов нескольких предыдущих баров, не поглощая предыдущий диапазон. Когда следующий бар опускается ниже минимума этого smash-бара, пробой вверх оказывается несостоятельным, и запускается разворот вниз.
Ключевая идея проста. Движение, которое выглядит достаточно сильным для продолжения, внезапно не может удержать достигнутый уровень. Эта неудача создает возможность.
Полный набор правил и логика каждого условия были подробно рассмотрены в предыдущей статье этой серии, где был создан советник для автоматической торговли такими паттернами. В этой статье мы сосредоточимся на визуальном обнаружении. Используются те же объективные правила, но вместо открытия сделок индикатор отмечает график, когда подтверждается разворот, соответствующий условиям модели. Это позволяет мгновенно увидеть паттерн, оставляя окончательное торговое решение на усмотрение человека.
Преобразование паттернов Smash Day в понятные визуальные сигналы
Цель этого индикатора — не торговать вместо нас, а сделать важные моменты на графике максимально заметными. Вместо просмотра свечи за свечой в поисках только что возникшего разворота Smash Day индикатор непрерывно выполняет проверку и отмечает точный бар, на котором происходит подтверждение.
Когда подтверждается бычий разворот Smash Day, под smash-баром рисуется стрелка цвета Sea Green. Это происходит в момент, когда текущий формирующийся бар поднимается выше максимума этого smash-бара. Такой пробой вверх показывает, что давление продавцов предыдущего дня оказалось несостоятельным и цена пытается развернуться вверх.
Когда подтверждается медвежий разворот Smash Day, над smash-баром рисуется черная стрелка. Она появляется, как только текущий формирующийся бар опускается ниже минимума smash-бара. Пробой вниз показывает, что сильное покупательское давление не смогло удержаться, указывая на разворот цены вниз.
Эти стрелки выступают как мгновенные визуальные подсказки. Они выделяют точную свечу, на которой становится очевидной неудача пробоя. С этого момента исполнение сделки остается на усмотрение трейдера. Один трейдер может брать сигнал только тогда, когда он согласуется с более широким трендом. Другой может ждать новостной волатильности или определенного дня недели. Индикатор показывает событие, а трейдер оценивает контекст.
Ларри Уильямс также подчеркивал, что определение smash-бара должно быть объективным, но гибким. На практике это означает, что smash-бар должен закрыться за экстремумами выбранного числа предыдущих баров. Одни трейдеры предпочитают меньшее число анализируемых предыдущих баров, чтобы получать больше сигналов. Другие выбирают большее число анализируемых предыдущих баров, концентрируясь только на более сильных экстремумах. Чтобы поддержать оба подхода, индикатор содержит входные параметры, позволяющие свободно настраивать количество предыдущих баров для проверки.
Такой подход сохраняет строгую, формализованную логику, но при этом учитывает индивидуальный торговый стиль. Индикатор точно отвечает на один вопрос: только что был подтвержден разворот Smash Day, соответствующий условиям модели. Что произойдет дальше, определяется собственными фильтрами и суждением трейдера.
Чтобы визуализировать результат, ниже приведен скриншот индикатора, который мы будем создавать, работающего на реальном графике.

Пошаговое создание индикатора
Предварительные требования перед началом
Перед переходом к фактическому написанию кода нужны несколько базовых навыков, чтобы процесс проходил плавно. Предполагается знакомство с языком программирования MQL5 и его основными конструкциями, такими как переменные, функции, массивы и условная логика. Также предполагается предыдущий опыт работы с настольным терминалом MetaTrader 5, включая открытие графиков и подключение индикаторов.
Кроме того, разработка будет выполняться внутри MetaEditor, поэтому важно уверенно создавать новые исходные файлы, компилировать код и проверять ошибки, которые могут появиться во время компиляции. Когда эти основы освоены, мы можем уверенно перейти к созданию индикатора с нуля.
Работа параллельно с готовой версией
Полный и полностью рабочий исходный код индикатора приложен к этой статье под именем lwSmashDayReversalIndicator.mq5. Настоятельно рекомендуется писать код параллельно с руководством. Набор каждой строки и последующее сравнение результата с приложенным готовым файлом делают обучение практичным и предсказуемым. Если что-то не компилируется или работает не так, как ожидалось, приложенный исходник служит чистой контрольной точкой для быстрого исправления. Цель состоит не только в том, чтобы получить рабочий индикатор, но и в том, чтобы понять, как четко построена каждая его часть.
Создание базового файла индикатора
Первый практический шаг — открыть MetaEditor и создать новый исходный файл пользовательского индикатора. Имя файла может быть любым осмысленным. После создания пустого файла в него вставляется следующий базовый код.
//+------------------------------------------------------------------+ //| lwSmashDayReversalIndicator.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| Custom Indicator specific directives | //+------------------------------------------------------------------+ #property indicator_chart_window #property indicator_plots 2 #property indicator_buffers 2 //+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input int smashBuyLookbackBars = 1; input int smashSellLookbackBars = 1; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; //+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+ double buySmashArrowBuffer []; double sellSmashArrowBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { return(rates_total); } //+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrSeaGreen)){ Print("Error while setting bullish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
Этот начальный блок задает базовую структуру индикатора. Он еще не выполняет обнаружение паттернов. Вместо этого он подготавливает среду, на которую позже будет опираться логика.
Этот стартовый код можно разделить на несколько логических частей. У каждой части есть четкая цель, и каждая подготавливает индикатор к дальнейшему расширению.
Свойства индикатора
//+------------------------------------------------------------------+ //| lwSmashDayReversalIndicator.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| Custom Indicator specific directives | //+------------------------------------------------------------------+ #property indicator_chart_window #property indicator_plots 2 #property indicator_buffers 2
Раздел свойств определяет, как индикатор будет вести себя на графике. Индикатор рисуется непосредственно в основном окне графика, а не в отдельной панели. Зарезервированы два графических построения и два буфера. Позже эти буферы будут хранить ценовые значения, на которых появятся стрелки покупки и продажи. На этом этапе стиль отрисовки еще не задан. Буферы выступают как пустые контейнеры, ожидающие заполнения.
Пользовательские входные настройки
Объявляются две входные переменные.
//+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input int smashBuyLookbackBars = 1; input int smashSellLookbackBars = 1;
Эти значения управляют тем, сколько предыдущих баров должно быть пробито закрытием smash-бара. Сохранение их в виде входных параметров делает индикатор гибким. Тот же код позднее можно тестировать с разными уровнями строгости без изменения исходника.
Это отражает идею Ларри Уильямса о том, что сила паттерна зависит от того, насколько далеко назад рынок был вытолкнут к экстремуму.
Ссылка на таймфрейм
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
Переменная таймфрейма задается равной текущему периоду графика. Благодаря этому индикатор автоматически адаптируется к любому графику, к которому он прикреплен. Нет необходимости вручную выбирать таймфрейм в настройках индикатора. Поэтому все проверки цены будут соответствовать видимому графику.
Буферы индикатора
Объявляются два массива.
//+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+ double buySmashArrowBuffer []; double sellSmashArrowBuffer[];
Каждый массив позднее будет хранить ценовые уровни, на которых должны рисоваться стрелки. Когда значение записывается в буфер по определенному индексу бара, на этом баре появляется стрелка. Когда записывается пустое значение, ничего не рисуется. Эти буферы являются визуальным результатом работы индикатора.
Фаза инициализации
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } return(INIT_SUCCEEDED); }
Функция OnInit выполняется один раз при подключении индикатора к графику. Здесь внешний вид графика настраивается через вспомогательную функцию. Фон устанавливается белым, бычьи свечи окрашиваются в зеленый цвет, медвежьи свечи — в черный, а сетка скрывается. Это создает наглядный контрастный график, на котором будущие стрелки и линии будут хорошо видны во время тестирования. Если какая-либо настройка графика не выполняется, инициализация прекращается и выводится ошибка. Это не позволяет индикатору работать в частично настроенном состоянии.
Цикл расчета
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { return(rates_total); }
Функция OnCalculate — это место, где будет размещена вся будущая логика обнаружения. Пока она просто возвращает общее количество баров. Это означает, что индикатор активен, но еще не выполняет анализ. В последующих разделах эта функция будет расширена так, чтобы каждый новый тик и каждый новый бар могли обновлять буферы стрелок в реальном времени.
Вспомогательная функция настройки внешнего вида графика
//+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrSeaGreen)){ Print("Error while setting bullish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } return true; }
Функция ConfigureChartAppearance объединяет все визуальные настройки графика в одном месте. Вынос этой логики отдельно сохраняет инициализацию чистой и читаемой. Если позднее понадобятся визуальные корректировки, их можно будет изменить здесь, не затрагивая основную логику. Это также гарантирует, что график подготавливается одинаково каждый раз при подключении индикатора.
Хотя этот код еще не рисует стрелки, он является важной основой. Он определяет, сколько визуальных результатов будет формироваться, подготавливает для них память, обеспечивает читаемость графика и настраивает основной цикл расчета, в котором позже будет размещена логика обнаружения smash day. Начав с понятной и минимальной базы, каждую новую функцию можно добавлять пошагово и без путаницы.
Создание логики индикатора разворота Smash DayНа этом этапе индикатор уже имеет базовую структуру и пустые буферы. Следующая цель — подключить эти буферы к терминалу, а затем создать логику, которая будет заполнять их осмысленными значениями на нужных барах. Процесс состоит из трех простых шагов. Сначала мы убеждаемся, что исторических данных достаточно.
Во-вторых, мы привязываем наши массивы к буферам индикатора. В-третьих, пишем небольшие специализированные функции, которые обнаруживают паттерны и обновляют буферы как по истории, так и в реальном времени.
Проверка достаточности исторических данных
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Ensure there are enough historical bars available before running smash pattern detection int totalRates = iBars(_Symbol, timeframe); if(totalRates <= smashBuyLookbackBars || totalRates <= smashSellLookbackBars){ Print("There is not enough historical data!"); return INIT_FAILED; } return(INIT_SUCCEEDED); }
Перед началом обнаружения паттернов мы подтверждаем, что на графике достаточно баров для безопасного выполнения необходимых проверок по числу предыдущих баров. Если количество доступных баров меньше либо равно выбранной глубине просмотра, индикатор прекращает инициализацию. Это предотвращает выход индекса за пределы диапазона при обращении к более старым свечам. Такая простая защита гарантирует, что каждая последующая функция работает с корректными данными и избегает ошибок времени выполнения.
Связывание массивов с буферами индикатора
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Bind arrays to indicator buffers SetIndexBuffer(0, buySmashArrowBuffer, INDICATOR_DATA); SetIndexBuffer(1, sellSmashArrowBuffer, INDICATOR_DATA); return(INIT_SUCCEEDED); }
После проверки данных массивы, которые будут хранить значения стрелок, связываются с индексами буферов с помощью SetIndexBuffer внутри процедуры инициализации. Каждый индекс буфера представляет одно визуальное построение на графике. Нулевой буфер используется для стрелок покупки. Первый буфер используется для стрелок продажи. С этого момента запись ценового значения в элемент буфера разместит графический объект точно на соответствующем баре. Запись EMPTY_VALUE скроет его.
Обнаружение внешних свечей (outside bars)
Небольшая вспомогательная функция проверяет, полностью ли свеча поглощает диапазон предыдущей свечи.
//+-----------------------------------------------------------------------------+ //| Checks whether the bar at the given index fully engulfs the prior bar range | //+-----------------------------------------------------------------------------+ bool IsOutsideBar(double &open[], double &high[], double &low[], double &close[], int index){ double high0 = high[index]; double low0 = low [index]; double high1 = high[index - 1]; double low1 = low [index - 1]; return (high0 > high1 && low0 < low1); }
Внешняя свеча (outside bar) игнорируется при обнаружении smash, потому что она представляет расширение в обоих направлениях, а не четкий эмоциональный импульс в одну сторону. Ранний отбор таких баров делает последующую логику чище и объективнее. Эта функция становится общим строительным блоком как для бычьих, так и для медвежьих проверок smash.
Определение бычьего разворота Smash Day
//+-----------------------------------------------------------------------------------+ //| Detects a Smash Day Buy Reversal where the close breaks below multiple prior lows | //+-----------------------------------------------------------------------------------+ bool IsSmashDayBuyReversal(double &open[], double &high[], double &low[], double &close[], int index, int lookbackBars) { // Bar must not be an outside bar if(IsOutsideBar(open, high, low, close, index)){ return false; } double close1 = close[index]; double close2 = close[index + 1]; double high1 = high[index]; // Validate close breaks below N prior lows for(int i = index-1; i>=(index - lookbackBars); i--){ double priorLow = low[i]; if(close1 >= priorLow){ return false; } } return close2 > high1; }
Бычий разворот Smash Day обнаруживается при выполнении трех условий. Бар не должен быть внешней свечой. Его закрытие должно пробить минимумы выбранного числа более ранних баров. Следующий бар должен закрыться выше максимума smash-бара. Это показывает, что пробой вниз оказался ложным и покупатели вернули контроль. Когда все условия истинны, функция возвращает true, а минимум этого бара записывается в буфер покупки.
Определение медвежьего разворота Smash Day
//+-------------------------------------------------------------------------------------+ //| Detects a Smash Day Sell Reversal where the close breaks above multiple prior highs | //+-------------------------------------------------------------------------------------+ bool IsSmashDaySellReversal(double &open[], double &high[], double &low[], double &close[], int index, int lookbackBars) { // Bar must not be an outside bar if(IsOutsideBar(open, high, low, close, index)){ return false; } double close1 = close[index]; double close2 = close[index + 1]; double low1 = low [index]; // Validate close breaks above N prior highs for(int i = index-1; i>=(index - lookbackBars); i--){ double priorHigh = high[i]; if(close1 <= priorHigh){ return false; } } return close2 < low1; }
Медвежья логика зеркально повторяет бычью. Бар не должен быть внешней свечой. Его закрытие должно пробить максимумы более ранних баров. Следующий бар должен закрыться ниже минимума smash-бара. Это сигнализирует, что пробой вверх оказывается несостоятельным и продавцы перехватили контроль. При обнаружении максимум этого бара записывается в буфер продажи.
Сканирование исторических паттернов
Когда индикатор впервые прикрепляется к графику, он должен просканировать всю историю и отметить каждый smash-паттерн, соответствующий условиям модели. Эту задачу выполняют две функции, которые сканируют исторические данные и заполняют буферы по истории.
//+-------------------------------------------------------------------------------------------------+ //| Scans historical price data to identify buy-side smash day reversals and populate the buy arrow | //+-------------------------------------------------------------------------------------------------+ void MapBuySmashDayReversals(double &open[], double &high[], double &low[], double &close[]){ int totalRates = ArraySize(open); for(int i = smashBuyLookbackBars; i < totalRates - 1; i++){ if(IsSmashDayBuyReversal(open, high, low, close, i, smashBuyLookbackBars)){ buySmashArrowBuffer[i] = low[i]; }else{ buySmashArrowBuffer[i] = EMPTY_VALUE; } } } //+--------------------------------------------------------------------------------------------------------------------+ //| Scans historical price data to identify sell-side smash day reversals and populate the sell arrow and line buffers | //+--------------------------------------------------------------------------------------------------------------------+ void MapSelSmashDayReversals(double &open[], double &high[], double &low[], double &close[]){ int totalRates = ArraySize(open); for(int i = smashSellLookbackBars; i < totalRates - 1; i++){ if(IsSmashDaySellReversal(open, high, low, close, i, smashSellLookbackBars)){ sellSmashArrowBuffer[i] = high[i]; }else{ sellSmashArrowBuffer[i] = EMPTY_VALUE; } } }
Одна сканирует и заполняет буфер покупки. Другая сканирует и заполняет буфер продажи. Каждый бар проверяется с помощью функций обнаружения. Если найден подтвержденный паттерн, буфер получает экстремум свечи. В противном случае элемент буфера устанавливается в EMPTY_VALUE. Этот шаг сразу строит полную визуальную историю.
Обновление буферов в реальном времени
После первоначального сканирования истории индикатор должен оставаться отзывчивым по мере поступления новых цен. Используются два режима обновления. Когда открывается новый бар, предыдущий завершенный бар проверяется и фиксируется, если он формирует подтвержденный разворот Smash Day.
//+----------------------------------------------------------------------------------------------------------------+ //|Updates the buy arrow buffer in real time when a bullish smash reversal is detected on the latest completed bar | //+----------------------------------------------------------------------------------------------------------------+ void UpdateBuySmashArrowBufferOnTick (double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDayBuyReversal(open, high, low, close, rates_total - 2, smashBuyLookbackBars)){ buySmashArrowBuffer[rates_total - 2] = low[rates_total - 2]; }else{ buySmashArrowBuffer[rates_total - 2] = EMPTY_VALUE; } } //+-----------------------------------------------------------------------------------------------------------------+ //|Updates the sell arrow buffer in real time when a bearish smash reversal is detected on the latest completed bar | //+-----------------------------------------------------------------------------------------------------------------+ void UpdateSellSmashArrowBufferOnTick(double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDaySellReversal(open, high, low, close, rates_total - 2, smashSellLookbackBars)){ sellSmashArrowBuffer[rates_total - 2] = high[rates_total - 2]; }else{ sellSmashArrowBuffer[rates_total - 2] = EMPTY_VALUE; } }
Когда ценовые тики поступают внутри текущего бара, последний завершенный бар проверяется снова.
//+-----------------------------------------------------------------------------------------------------+ //|Updates the buy arrow buffer at the moment a new bar forms to mark confirmed bullish smash reversals | //+-----------------------------------------------------------------------------------------------------+ void UpdateBuySmashArrowBufferOnNewBar (double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDayBuyReversal(open, high, low, close, rates_total - 3, smashBuyLookbackBars)){ buySmashArrowBuffer[rates_total - 3] = low[rates_total - 3]; }else{ buySmashArrowBuffer[rates_total - 3] = EMPTY_VALUE; } } //+------------------------------------------------------------------------------------------------------+ //|Updates the sell arrow buffer at the moment a new bar forms to mark confirmed bearish smash reversals | //+------------------------------------------------------------------------------------------------------+ void UpdateSellSmashArrowBufferOnNewBar(double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDaySellReversal(open, high, low, close, rates_total - 3, smashSellLookbackBars)){ sellSmashArrowBuffer[rates_total - 3] = high[rates_total - 3]; }else{ sellSmashArrowBuffer[rates_total - 3] = EMPTY_VALUE; } }
Это позволяет стрелке появиться сразу после подтверждения, не дожидаясь еще одного бара. Эти небольшие функции обновления затрагивают только последний релевантный индекс. Это сохраняет расчет легким и быстрым.
Подготовка ценовых данных для безопасной обработки
Исходные ценовые массивы, предоставляемые платформой, доступны только для чтения. Чтобы свободно работать с ними, их значения копируются в локальные массивы.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { ... //--- Temporary buffers used to store price data for rendering custom colored candles double lwOpen []; double lwHigh []; double lwLow []; double lwClose[]; //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays ArrayCopy(lwOpen, open); ArrayCopy(lwHigh, high); ArrayCopy(lwLow, low); ArrayCopy(lwClose, close); return(rates_total); }
Порядок индексации также изменяется так, чтобы индекс ноль соответствовал самому старому бару, а последний индекс — самому новому бару. Все служебные функции предполагают именно такой прямой порядок. Этот подготовительный шаг обеспечивает согласованную индексацию и предотвращает изменение данных, принадлежащих платформе.
Первичная загрузка и обновления в реальном времени
Процедура расчета различает два момента. Когда индикатор впервые подключается, все исторические паттерны сканируются, а оба буфера инициализируются значением EMPTY_VALUE перед записью любых сигналов.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { ... //--- This block will be executed whenever the indicator is initially attached on a chart if(prev_calculated == 0){ ArrayInitialize(buySmashArrowBuffer, EMPTY_VALUE); ArrayInitialize(sellSmashArrowBuffer, EMPTY_VALUE); //--- MapBuySmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); MapSelSmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); } return(rates_total); }
После этого каждый новый бар и каждый входящий тик запускают только небольшие целевые обновления с помощью функций обновления в реальном времени.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { ... //--- This block is executed on new bar open if(prev_calculated != rates_total && prev_calculated != 0){ //--- UpdateBuySmashArrowBufferOnNewBar (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnNewBar(lwOpen, lwHigh, lwLow, lwClose, rates_total); } //--- This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ //--- UpdateBuySmashArrowBufferOnTick (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnTick(lwOpen, lwHigh, lwLow, lwClose, rates_total); } return(rates_total); }
Такое разделение сохраняет логику понятной и эффективной.
Ниже приведена итоговая форма функции OnCalculate после завершения разработки индикатора.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { ArraySetAsSeries(open, false); ArraySetAsSeries(high, false); ArraySetAsSeries(low, false); ArraySetAsSeries(close, false); //--- Temporary buffers used to store price data for rendering custom colored candles double lwOpen []; double lwHigh []; double lwLow []; double lwClose[]; //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays ArrayCopy(lwOpen, open); ArrayCopy(lwHigh, high); ArrayCopy(lwLow, low); ArrayCopy(lwClose, close); //--- This block will be executed whenever the indicator is initially attached on a chart if(prev_calculated == 0){ ArrayInitialize(buySmashArrowBuffer, EMPTY_VALUE); ArrayInitialize(sellSmashArrowBuffer, EMPTY_VALUE); //--- MapBuySmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); MapSelSmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); } //--- This block is executed on new bar open if(prev_calculated != rates_total && prev_calculated != 0){ //--- UpdateBuySmashArrowBufferOnNewBar (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnNewBar(lwOpen, lwHigh, lwLow, lwClose, rates_total); } //--- This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ //--- UpdateBuySmashArrowBufferOnTick (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnTick(lwOpen, lwHigh, lwLow, lwClose, rates_total); } return(rates_total); }
Эта функция служит основным вычислительным контуром индикатора, координируя все обнаружение паттернов и обновления буферов контролируемым и эффективным способом.
В начале каждого вызова ценовые массивы настраиваются на прямую индексацию вместо series. Это сделано намеренно, поскольку все вспомогательные функции предполагают прямую индексацию данных, где более старые бары идут первыми, а более новые — последними. Единая модель индексации предотвращает скрытые логические ошибки на последующих этапах обнаружения.
Затем создаются локальные рабочие массивы и заполняются копиями ценовых данных. Исходные массивы, предоставляемые терминалом, доступны только для чтения, поэтому копирование позволяет безопасно обрабатывать и повторно использовать данные в вспомогательных функциях без изменения исходных данных.
Когда индикатор впервые прикрепляется к графику, функция выполняет полное сканирование истории. Все буферы индикатора инициализируются пустыми значениями для обеспечения чистого стартового состояния, после чего каждый разворот, соответствующий условиям модели smash day в доступной истории обнаруживается и отмечается на графике. Это гарантирует, что график сразу заполняется всеми релевантными сигналами.
При последующих вызовах функция разделяет логику на два случая: сформировался ли новый бар и поступили ли новые ценовые данные внутри текущего бара. Когда открывается новый бар, подтвержденные развороты Smash Day по недавно закрытому бару оцениваются и записываются в буферы. Когда поступают только тиковые данные, функция проверяет подтверждение разворотов Smash Day в реальном времени и соответствующим образом обновляет самые последние значения буферов.
Такая структура гарантирует корректную обработку исторических паттернов, новых подтвержденных баров и движений цены в реальном времени без дублирования или пропущенных сигналов. Функция всегда возвращает общее количество рассчитанных баров, позволяя терминалу эффективно управлять инкрементальными обновлениями.
Отображение стрелок на графике.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Configure Graphic Plots PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_ARROW); PlotIndexSetInteger(0, PLOT_ARROW, 233); PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrSeaGreen); PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, +20); PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2); PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetString (0, PLOT_LABEL, "Smash Buy Bar"); PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_ARROW); PlotIndexSetInteger(1, PLOT_ARROW, 234); PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrBlack); PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, -20); PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 2); PlotIndexSetDouble (1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetString (1, PLOT_LABEL, "Smash Sell bar"); return(INIT_SUCCEEDED); }
Даже после заполнения буферов ничего не появится, пока терминалу не будет указано, как их рисовать. Каждый буфер настраивается как графическое построение стрелки. Стрелка цвета Sea Green рисуется под бычьими smash-барами. Черная стрелка рисуется над медвежьими smash-барами. Коды стрелок, цвета, ширина, вертикальный сдвиг и пустые значения определяются во время инициализации. Это превращает сырые данные буферов в сигналы, отображаемые на графике.
Назначение краткого имени индикатора
Назначается краткое отображаемое имя, чтобы индикатор было легко распознать на графике и в панели навигатора.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- General indicator configurations IndicatorSetString(INDICATOR_SHORTNAME, "Larry Williams Market Structure Indicator"); return(INIT_SUCCEEDED); }
Эта небольшая деталь повышает удобство использования и делает инструмент завершенным.
Итоговый результат
В конце этого процесса индикатор выполняет три ключевые задачи. Он сканирует прошлое и отмечает каждый разворот Smash Day, соответствующий условиям модели. Он отслеживает каждый новый бар и обновляет подтвержденные сигналы. Он реагирует на изменения цены в реальном времени и сразу показывает новые сигналы.
Все стрелки рисуются непосредственно на графике на тех самых барах, где происходят развороты. Это дает наглядный визуальный ориентир, который позднее можно сочетать с трендом, временем или другими фильтрами.
С такой основой концепция разворота smash day становится практическим визуальным инструментом, поддерживающим быстрые и объективные дискреционные решения.
Тестирование и проверка вывода индикатора
Когда логика индикатора полностью готова, следующий шаг — проверить, что все компилируется и отображается как ожидается. На этом этапе исходный код следует скомпилировать в MetaEditor. Если все предыдущие шаги были выполнены правильно, процесс компиляции должен завершиться без ошибок и предупреждений. Успешная компиляция подтверждает согласованность структуры индикатора, привязок буферов и расчетной логики.
Если возникают ошибки компиляции, самый эффективный подход — сравнить текущий исходный код с приложенной надежной опорной версией lwSmashDayReversalIndicator.mq5. Такое сравнение обычно позволяет легко обнаружить пропущенные строки, неправильно расположенные блоки или небольшие синтаксические ошибки, которые могли появиться во время реализации.
После успешной компиляции индикатор можно прикрепить к графику из терминала MetaTrader. В этот момент стрелки должны начать появляться в точках действительных разворотов smash day.
Чтобы визуализировать итоговый результат, в этом разделе приведен скриншот, показывающий индикатор, примененный к фондовому индексу NASDAQ 100 на часовом таймфрейме.

Стрелки четко отмечают бычьи и медвежьи развороты Smash Day прямо на ценовом графике, благодаря чему легко увидеть, как индикатор ведет себя в реальных рыночных условиях. На этом этап тестирования завершается, подтверждая, что индикатор работает как задумано.
Использование индикатора разворота Smash Day в торговой практике
После полной реализации и тестирования индикатора следующий шаг — понять, как его можно применять в практическом торговом процессе. Индикатор разработан как вспомогательный инструмент для принятия решений, а не как самостоятельная торговая система. Его основная роль — объективно выделять формирование паттернов Smash Day Reversal непосредственно на графике, позволяя трейдерам применять усмотрение и учитывать контекст перед действием.
Когда формируется бычий Smash Day Reversal, под smash-баром появляется стрелка цвета seaGreen после того, как цена подтверждает разворот пробоем максимума бара; аналогично, медвежий Smash Day Reversal отмечается черной стрелкой над smash-баром после подтверждения разворота пробоем вниз. Эти визуальные подсказки избавляют от необходимости вручную просматривать исторические свечи и привлекают внимание к потенциальным точкам разворота по мере их формирования.
На практике трейдеры могут сочетать эти сигналы с более широким рыночным контекстом. Одни могут предпочитать сделки только в направлении доминирующего тренда, а другие — использовать структуру старшего таймфрейма для фильтрации сигналов. Дополнительные факторы, такие как недавние новости, условия волатильности или время торговой сессии, могут дополнительно уточнять принятие решений. Индикатор остается нейтральным к этим фильтрам и последовательно, воспроизводимо показывает исходный паттерн.
Настраиваемые входные параметры количества предыдущих баров для проверки позволяют трейдерам регулировать строгость определения Smash Day. Меньшее число анализируемых предыдущих баров дает более частые сигналы, тогда как большее число баров выделяет только более сильные эмоциональные экстремумы. Такая гибкость делает индикатор подходящим для разных инструментов и торговых стилей без изменения его базовой логики.
Самое важное, что индикатор помогает превратить тонкий визуальный паттерн в четкий и объективный элемент графика. Тем самым он поддерживает дисциплинированный анализ, снижает эмоциональное смещение и позволяет трейдерам сосредоточиться на качестве исполнения, а не на поиске паттерна.
Заключение
В этой статье мы вышли за рамки теории и преобразовали концепции разворотов smash day Ларри Уильямса в практический визуальный торговый инструмент. Результатом стал пользовательский индикатор MQL5, который объективно определяет развороты smash day, выделяет точные бары, где возникают эмоциональные экстремумы, и отмечает конкретный момент, когда цена подтверждает разворот. Уже одно это устраняет значительную часть неопределенности, которая часто сопровождает дискреционную торговлю по паттернам.
Что еще важнее, индикатор был разработан для поддержки принятия решений, а не для их замены. Развороты smash day имеют наибольший смысл, когда оцениваются в контексте направления тренда, рыночной среды или временных факторов. Четко четко отображая эти паттерны на графике, индикатор позволяет спокойно и последовательно оценивать этот контекст, а не эмоционально реагировать в моменте.
Сам процесс разработки также служит шаблоном. Мы разобрали дискреционную рыночную идею, определили объективные правила и реализовали их пошагово с помощью чистого и модульного кода MQL5. Такой подход можно повторно использовать для изучения других ценовых паттернов, тестирования вариантов или создания дополнительных инструментов, соответствующих личным торговым убеждениям.
На этом этапе индикатор готов к дальнейшему изучению. Разные значения глубины просмотра, таймфреймы и рынки могут раскрыть новые наблюдения, которые раньше не были очевидны. Читателям рекомендуется экспериментировать, наблюдать, как эти паттерны ведут себя в разных условиях, и делиться своими выводами в комментариях к статье. Именно в процессе тестирования и наблюдения обнаруживаются реальные торговые преимущества.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21128
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Сеточный советник на клеточном автомате с онлайн-обучением в MQL5 (Часть III): Живой граф признаков
Моделирование рынка: Position View (V)
Разработка инструментария для анализа Price Action (Часть 66): Создание сканера паттерна "голова и плечи" с проверкой структуры на MQL5
От начального до среднего уровня: Произвольный доступ (I)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Можно ли разработать EA для этого индикатора?
Этот индикатор перерисовывается или имеет задержку?
Это не
Стрелки задерживаются