Рыночные секреты Ларри Уильямса (Часть 12): Торговля разворотами Smash Day на основе контекста
Введение
Паттерны Smash Day привлекательны, но торговля ими без структуры часто приводит к шумным сигналам и ненужным убыткам. Паттерн может появляться часто, и без четких правил контекста, тайминга и срока действия становится трудно отделить значимые развороты от случайного движения цены. Ларри Уильямс неоднократно подчеркивал, что паттерны Smash Day не следует торговать изолированно, а нужно рассматривать как часть более широкого процесса принятия решений.
В этой статье решается указанная проблема: развороты Smash Day преобразуются в объективную систему на основе правил, которую можно автоматизировать и тестировать. Вместо опоры на субъективную визуальную оценку каждое решение выражается в измеримой и последовательной логике, которую можно оценивать на разных рынках и таймфреймах.
Материал предназначен для алгоритмических трейдеров и разработчиков MQL5, которые хотят автоматизировать, тестировать и объективно изучать паттерны Smash Day. Он также подойдет трейдерам, которые хотят понять, как идеи Ларри Уильямса можно перевести в точные правила входа и исполнения сделок без субъективного усмотрения.
В этой статье создается полноценный советник, который торгует разворотами Smash Day с опциональными контекстными фильтрами. Направление тренда можно учитывать с помощью индикатора Supertrend, торговлю можно ограничивать по дням недели, а каждому сетапу назначается фиксированное окно действия. Эти элементы предназначены для снижения шума, ограничения лишних сделок и для того, чтобы результаты тестирования было проще интерпретировать. К концу статьи паттерны Hidden Smash Day уже не рассматриваются как самостоятельные сигналы, а как часть структурированной системы, которую можно тестировать, изменять и расширять.
Правила стратегии советника Smash Day
Этот советник применяет концепцию разворота Smash Day Ларри Уильямса с помощью строгих, тестируемых правил. Цель состоит не в том, чтобы торговать каждый видимый паттерн, а в том, чтобы убрать неоднозначность и определить условия, которые можно последовательно оценивать на разных рынках и таймфреймах.
Паттерн Smash Day выступает триггером сигнала. Все дополнительные правила нужны для контроля тайминга, контекста и риска, чтобы результаты отражали структурированное принятие решений, а не случайное исполнение.
Определение паттерна Smash Day
Система поддерживает два типа паттернов: развороты Smash Day на покупку и на продажу. Покупочный сетап Smash Day определяется, когда бар закрывается ниже минимумов заданного пользователем количества предыдущих баров. Глубина ретроспективного окна настраивается и позволяет проверять, как разная интенсивность пробоя вниз влияет на развороты. Бар не должен быть баром внешнего диапазона (outside bar), поскольку он может расширяться в обе стороны и часто отражает скорее шум, чем направленное намерение.
Продажный сетап Smash Day следует той же логике в обратном направлении. Бар должен закрыться выше максимумов заданного пользователем количества предыдущих баров и также не должен быть внешним баром. После обнаружения бар Smash Day представляет потенциальную точку разворота, а не немедленную сделку.
Срок действия сетапа и контроль тайминга
Не предполагается, что сетапы Smash Day остаются действительными бесконечно. Каждому обнаруженному сетапу назначается ограниченный срок жизни, определяемый заданным пользователем количеством будущих баров. Если цена не запускает вход в пределах этого окна, сетап автоматически отбрасывается. Это отражает реальное поведение рынка, где разворотное давление со временем ослабевает, а поздние входы часто теряют свое преимущество. Это правило также позволяет объективно тестировать чувствительность к таймингу без изменения базового паттерна.
Условия входа и подтверждение
Сделка рассматривается только тогда, когда цена пробивает уровень Smash Day в ожидаемом направлении. Для покупочных разворотов цена должна подняться выше максимума бара Smash Day. Для продажных разворотов цена должна опуститься ниже минимума бара Smash Day. Поддерживаются два режима входа. Первый выполняет вход сразу при пересечении ценой уровня пробоя. Второй ожидает полного закрытия бара за пределами уровня. Это позволяет сравнивать более быстрое исполнение с подтвержденными входами, не меняя определение паттерна.
Контроль направления
Систему можно настроить на торговлю только покупочными сетапами, только продажными сетапами или обоими типами. Независимо от выбранного режима советник обеспечивает строгое правило: не более одной открытой позиции одновременно. Развороты Smash Day являются дискретными событиями, и разрешение нескольких позиций исказило бы причинно-следственную связь. Такая конструкция упрощает интерпретацию результатов во время тестирования.
Контекстные фильтры
Контекстная фильтрация является необязательной, но явно заданной. Фильтр Supertrend можно включить, чтобы согласовать сделки с доминирующим направлением рынка. Когда он активен, покупочные сетапы разрешены только при бычьих условиях Supertrend, а продажные сетапы — только при медвежьих условиях. Таймфрейм Supertrend, период ATR и множитель ATR настраиваются через входные параметры.
Также можно применить фильтр Trade Day of the Week. Сделки выполняются только в выбранные дни, что позволяет анализировать поведение, связанное с конкретными сессиями или календарными закономерностями. Оба фильтра можно включать и отключать независимо друг от друга.
Управление риском и сделками
Управление риском следует паттерну Smash Day. Для покупочных разворотов стоп-лосс размещается на минимуме бара Smash Day. Для продажных разворотов стоп-лосс размещается на максимуме бара Smash Day. Тейк-профит рассчитывается с использованием фиксированного соотношения риск/прибыль, заданного пользователем. Это делает выходы объективными и масштабируемыми на разных рынках.
Размер позиции можно задавать вручную или рассчитывать автоматически как процент от баланса счета. В автоматическом режиме размер позиции рассчитывается на основе расстояния до стоп-лосса, что обеспечивает стабильную рисковую экспозицию независимо от волатильности.
Что нового в этом советнике
Эта версия советника Smash Day расширяет предыдущие реализации за счет акцента на контроле тайминга, контекстной фильтрации и воспроизводимости. Самое значительное дополнение — механизм ограничения срока активности сетапа. Развороты Smash Day больше не рассматриваются как немедленные возможности. Каждый сетап остается активным только в течение заданного количества баров, после чего он автоматически становится недействительным, если вход не произошел. Это предотвращает позднее участие и позволяет структурированно тестировать затухание импульса.
Контекстная фильтрация расширена за счет опционального фильтра Supertrend. Теперь сделки можно ограничивать доминирующим направлением рынка с полным контролем таймфрейма Supertrend и параметров расчета. Это делает понятие контекста явным и тестируемым, а не дискреционным.
Фильтрация по времени остается доступной через механизм Trade Day of the Week. Сделки можно ограничивать конкретными днями без изменения основной логики, что позволяет проводить сфокусированные эксперименты.
Логика исполнения из предыдущих версий сохранена. Вход может происходить при пробое уровня или по подтвержденному закрытию бара. Контроль направления позволяет работать только с покупками, только с продажами или комбинировать оба режима. Управление риском поддерживает как фиксированный, так и процентный расчет размера позиции.
В совокупности эти дополнения превращают советник из исполнителя паттернов в структурированный исследовательский инструмент. Сигнал Smash Day остается триггером, но именно тайминг, контекст и риск теперь определяют, когда и как открываются сделки.
Пошаговое создание советника
В этом разделе мы начинаем практическое построение советника. Здесь цель состоит не в том, чтобы как можно быстрее перейти к торговой логике, а в том, чтобы создать чистую и надежную основу, которая будет поддерживать все последующие элементы. Каждый блок кода, представленный на этом этапе, существует по определенной причине, даже если эта причина станет полностью понятна только ниже по ходу разработки.
Прежде чем продолжить, уточним несколько предварительных требований. Предполагается знакомство с языком MQL5 и его базовым синтаксисом. Такие понятия, как переменные, функции, перечисления, циклы и стандартные библиотеки, уже должны быть понятны. Также предполагается предыдущий опыт работы с терминалом MetaTrader 5, включая запуск программ на графиках и проведение тестов. Наконец, требуется рабочее знание MetaEditor для создания, компиляции и отладки исходных файлов при необходимости.
Чтобы поддержать обучение на практике, полный исходный код готового советника прикреплен к этой статье как lwSmashDayTrendFilteredExpert.mq5. Параллельное написание кода по уроку и сравнение прогресса с финальной версией значительно упрощают понимание того, как каждая часть вписывается в общую систему.
После этого мы начинаем с создания нового пустого файла советника в MetaEditor и вставляем следующий начальный исходный код.
//+------------------------------------------------------------------+ //| lwSmashDayTrendFilteredExpert.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" #resource "\\Indicators\\supertrend.ex5" //+------------------------------------------------------------------+ //| Standard Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Custom Enumerations | //+------------------------------------------------------------------+ enum ENUM_TDW_MODE { TDW_ALL_DAYS, TDW_SELECTED_DAYS }; enum ENUM_SMASH_ENTRY_MODE{ ENTRY_ON_LEVEL_CROSS, ENTRY_ON_BAR_CLOSE }; enum ENUM_SMASH_TRADE_MODE { SMASH_TRADE_BUY_ONLY, SMASH_TRADE_SELL_ONLY, SMASH_TRADE_BOTH }; enum ENUM_LOT_SIZE_INPUT_MODE { MODE_MANUAL, MODE_AUTO }; //+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input ulong magicNumber = 254700680002; input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; input group "Smash Day Pattern Rules" input int smashBuyLookbackBars = 1; input int smashSellLookbackBars = 1; input int smashSetupValidityBars = 3; input group "Smash Day Entry Settings" input ENUM_SMASH_ENTRY_MODE smashEntryMode = ENTRY_ON_LEVEL_CROSS; input ENUM_SMASH_TRADE_MODE smashTradeMode = SMASH_TRADE_BOTH; input group "Supertrend configuration parameters" input bool useSupertrendFilter = false; input ENUM_TIMEFRAMES supertrendTimeframe = PERIOD_CURRENT; input int32_t supertrendAtrPeriod = 10; input double supertrendAtrMultiplier = 1.5; input group "TDW filters" input ENUM_TDW_MODE tradeDayMode = TDW_SELECTED_DAYS; input bool tradeSunday = false; input bool tradeMonday = true; input bool tradeTuesday = false; input bool tradeWednesday = false; input bool tradeThursday = false; input bool tradeFriday = false; input bool tradeSaturday = false; input group "Trade and Risk Management" input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO; input double riskPerTradePercent = 1.0; input double positionSize = 0.1; input double riskRewardRatio = 3.0; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Create a CTrade object to handle trading operations CTrade Trade; //--- To hep track current market prices for Buying (Ask) and Selling (Bid) double askPrice; double bidPrice; //--- To store current time datetime currentTime; //--- Supertrend values int supertrendIndicatorHandle; double upperBandValues[]; double lowerBandValues[]; //--- To help track new bar open datetime lastBarOpenTime; struct MqlSmashDayPatternState{ //--- Pattern detection flags bool hasBuySmashSetup; bool hasSellSmashSetup; //--- Reference breakout levels from smash bar double buyBreakoutLevel; double sellBreakoutLevel; //--- Pattern bar reference data datetime smashBarTime; //--- Entry status tracking bool entryPending; //--- Tracks the number of bars elapsed after the occurence of the smash bar int barsSinceSmash; //--- Stop-Loss levels double buyStopLoss; double sellStopLoss; }; MqlSmashDayPatternState smashState; //--- To store minutes data double closePriceMinutesData []; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Assign a unique magic number to identify trades opened by this EA Trade.SetExpertMagicNumber(magicNumber); // Initialize the Supertrend Indicator supertrendIndicatorHandle = iCustom(_Symbol, supertrendTimeframe, "::Indicators\\supertrend.ex5", supertrendAtrPeriod, supertrendAtrMultiplier); if(supertrendIndicatorHandle == INVALID_HANDLE){ Print("Error while initializing the Supertrend indicator", GetLastError()); return(INIT_FAILED); } //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar) ArraySetAsSeries(closePriceMinutesData, true); ArraySetAsSeries(upperBandValues, true); ArraySetAsSeries(lowerBandValues, true); //--- Initialize global variables lastBarOpenTime = 0; //--- Reset ZeroMemory(smashState); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- Release Supertrend if(supertrendIndicatorHandle != INVALID_HANDLE){ IndicatorRelease(supertrendIndicatorHandle); } //--- Notify why the program stopped running Print("Program terminated! Reason code: ", reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- Retrieve current market prices for trade execution askPrice = SymbolInfoDouble (_Symbol, SYMBOL_ASK); bidPrice = SymbolInfoDouble (_Symbol, SYMBOL_BID); currentTime = TimeCurrent(); //--- Get some minutes data if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){ Print("Error while copying minutes datas ", GetLastError()); return; } } //--- UTILITY FUNCTIONS //+------------------------------------------------------------------+
Этот код формирует структурный каркас системы.
Заголовок файла и идентификация программы
//+------------------------------------------------------------------+ //| lwSmashDayTrendFilteredExpert.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" #resource "\\Indicators\\supertrend.ex5"
Начальный раздел определяет имя программы, сведения об авторе и информацию о версии. Это стандартная практика в MQL5, которая помогает при долгосрочном сопровождении, особенно когда существует несколько итераций одного советника.
Индикатор Supertrend также встраивается как ресурс. Это гарантирует, что советник всегда сможет обращаться к индикатору внутри себя, не требуя отдельного подключения индикатора к графику. Поскольку фильтрация по тренду является ключевой функцией этой версии, доступность индикатора на таком уровне необходима.
Стандартные библиотеки и доступ к торговым операциям
Далее мы подключаем библиотеку Trade.
//+------------------------------------------------------------------+ //| Standard Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh>
Это предоставляет доступ к классу CTrade, который упрощает исполнение ордеров, обработку позиций и управление ошибками. Использование этой библиотеки сохраняет торговую логику читаемой и последовательной в разных частях системы.
На этом этапе сделки еще не размещаются. Мы только подготавливаем инструменты, которые позднее позволят выполнять безопасное и контролируемое исполнение.
Перечисления для понятной конфигурации
//+------------------------------------------------------------------+ //| Custom Enumerations | //+------------------------------------------------------------------+ enum ENUM_TDW_MODE { TDW_ALL_DAYS, TDW_SELECTED_DAYS }; enum ENUM_SMASH_ENTRY_MODE{ ENTRY_ON_LEVEL_CROSS, ENTRY_ON_BAR_CLOSE }; enum ENUM_SMASH_TRADE_MODE { SMASH_TRADE_BUY_ONLY, SMASH_TRADE_SELL_ONLY, SMASH_TRADE_BOTH }; enum ENUM_LOT_SIZE_INPUT_MODE { MODE_MANUAL, MODE_AUTO };
В начале файла объявляется несколько пользовательских перечислений. Эти перечисления определяют, как советник ведет себя при разных конфигурациях. Режим TDW управляет тем, разрешена ли торговля во все дни или только в выбранные дни. Режим входа Smash определяет, запускаются ли входы сразу при пробое уровня или только после подтверждения закрытием бара. Режим торговли Smash управляет направлением, позволяя работать только с покупками, только с продажами или с обоими направлениями. Режим размера лота определяет, задается ли размер позиции вручную или рассчитывается на основе риска. Использование перечислений вместо непосредственных числовых значений повышает читаемость и предотвращает ошибки настройки в дальнейшем.
Входные параметры пользователя
//+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input ulong magicNumber = 254700680002; input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; input group "Smash Day Pattern Rules" input int smashBuyLookbackBars = 1; input int smashSellLookbackBars = 1; input int smashSetupValidityBars = 3; input group "Smash Day Entry Settings" input ENUM_SMASH_ENTRY_MODE smashEntryMode = ENTRY_ON_LEVEL_CROSS; input ENUM_SMASH_TRADE_MODE smashTradeMode = SMASH_TRADE_BOTH; input group "Supertrend configuration parameters" input bool useSupertrendFilter = false; input ENUM_TIMEFRAMES supertrendTimeframe = PERIOD_CURRENT; input int32_t supertrendAtrPeriod = 10; input double supertrendAtrMultiplier = 1.5; input group "TDW filters" input ENUM_TDW_MODE tradeDayMode = TDW_SELECTED_DAYS; input bool tradeSunday = false; input bool tradeMonday = true; input bool tradeTuesday = false; input bool tradeWednesday = false; input bool tradeThursday = false; input bool tradeFriday = false; input bool tradeSaturday = false; input group "Trade and Risk Management" input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO; input double riskPerTradePercent = 1.0; input double positionSize = 0.1; input double riskRewardRatio = 3.0;
Раздел входных параметров представляет все настраиваемое поведение пользователю в структурированном и читаемом виде. Базовая входная информация задает magic number и рабочий таймфрейм. Правила паттерна Smash Day управляют тем, как выявляются разворотные паттерны и как долго сетап остается действительным после появления. Настройки входа определяют, как и когда запускаются сделки. Входные параметры Supertrend управляют тем, активна ли фильтрация по тренду и как рассчитывается тренд. Параметры TDW определяют, какие дни доступны для торговли. Параметры управления риском задают размер позиции и целевое соотношение риск/прибыль. На этом этапе ничего не исполняется. Эти входные параметры задают правила системы до того, как она начнет реагировать на рыночные данные.
Глобальные переменные и общее состояние
Далее объявляются глобальные переменные для хранения цен, времени, значений индикатора и внутреннего состояния.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Create a CTrade object to handle trading operations CTrade Trade; //--- To hep track current market prices for Buying (Ask) and Selling (Bid) double askPrice; double bidPrice; //--- To store current time datetime currentTime; //--- Supertrend values int supertrendIndicatorHandle; double upperBandValues[]; double lowerBandValues[]; //--- To help track new bar open datetime lastBarOpenTime; struct MqlSmashDayPatternState{ //--- Pattern detection flags bool hasBuySmashSetup; bool hasSellSmashSetup; //--- Reference breakout levels from smash bar double buyBreakoutLevel; double sellBreakoutLevel; //--- Pattern bar reference data datetime smashBarTime; //--- Entry status tracking bool entryPending; //--- Tracks the number of bars elapsed after the occurence of the smash bar int barsSinceSmash; //--- Stop-Loss levels double buyStopLoss; double sellStopLoss; }; MqlSmashDayPatternState smashState; //--- To store minutes data double closePriceMinutesData [];
Объект CTrade создается один раз и повторно используется для всех торговых операций. Цены Ask и Bid сохраняются для точности исполнения. Буферы Supertrend подготавливаются для хранения трендовых данных, полученных от индикатора.
Также вводится пользовательская структура для отслеживания состояния сетапа Smash Day. Эта структура хранит информацию об обнаруженных сетапах, уровнях пробоя, уровнях стоп-лосса и количестве баров, прошедших с момента формирования бара Smash. Такой подход сохраняет логику организованной и предотвращает разбросанные глобальные флаги.
Небольшой массив выделяется для хранения цен закрытия на минутном таймфрейме. Позднее он будет использоваться для точного обнаружения пересечения уровней.
Логика инициализации советника
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Assign a unique magic number to identify trades opened by this EA Trade.SetExpertMagicNumber(magicNumber); // Initialize the Supertrend Indicator supertrendIndicatorHandle = iCustom(_Symbol, supertrendTimeframe, "::Indicators\\supertrend.ex5", supertrendAtrPeriod, supertrendAtrMultiplier); if(supertrendIndicatorHandle == INVALID_HANDLE){ Print("Error while initializing the Supertrend indicator", GetLastError()); return(INIT_FAILED); } //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar) ArraySetAsSeries(closePriceMinutesData, true); ArraySetAsSeries(upperBandValues, true); ArraySetAsSeries(lowerBandValues, true); //--- Reset ZeroMemory(smashState); return(INIT_SUCCEEDED); }
Функция инициализации — это место, где советник подготавливается перед получением рыночных данных. Magic number назначается торговому объекту, чтобы надежно идентифицировать позиции, открытые этим советником. Индикатор Supertrend инициализируется с помощью iCustom и сразу проверяется. Если индикатор не удается загрузить, советник останавливается, чтобы избежать торговли без трендового контекста. Массивы настраиваются как таймсерии, чтобы индекс ноль всегда представлял самое последнее значение. Структура состояния Smash Day сбрасывается, чтобы обеспечить чистое начальное состояние.
В конце этого процесса советник полностью подготовлен, но все еще неактивен. Решения не принимаются до поступления тиков.
Деинициализация и очистка ресурсов
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- Release Supertrend if(supertrendIndicatorHandle != INVALID_HANDLE){ IndicatorRelease(supertrendIndicatorHandle); } //--- Notify why the program stopped running Print("Program terminated! Reason code: ", reason); }
Когда советник прекращает работу, вызывается функция деинициализации. Здесь мы освобождаем дескриптор индикатора Supertrend и записываем причину завершения. Это предотвращает утечки памяти и гарантирует, что ресурсы корректно возвращаются терминалу.
Каркас обработки тиков
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- Retrieve current market prices for trade execution askPrice = SymbolInfoDouble (_Symbol, SYMBOL_ASK); bidPrice = SymbolInfoDouble (_Symbol, SYMBOL_BID); currentTime = TimeCurrent(); //--- Get some minutes data if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){ Print("Error while copying minutes datas ", GetLastError()); return; } }
Функция обработки тиков представлена последней в базовом каркасе программы. На этом этапе она только получает текущие цены и последние минутные данные. Торговая логика пока не применяется. Такое разделение сделано намеренно. Сохраняя начальную функцию обработки тиков простой, мы создаем чистую точку входа для постепенного добавления логики без путаницы.
На этом этапе советник имеет прочную структурную основу. Вся конфигурация, управление состоянием, доступ к индикатору и подготовка данных уже находятся на месте. В следующих шагах эта структура будет расширена обнаружением паттернов, проверкой сетапов, фильтрацией по тренду и контролируемым исполнением сделок.
Каждая новая функция, добавленная с этого момента, будет напрямую связана с концепциями, уже введенными в этой основе.
Обнаружение новых баров и контроль дневного исполнения
Советник спроектирован так, чтобы принимать ключевые решения при открытии нового дневного бара. По этой причине первая вспомогательная функция, которую мы вводим, — надежный способ определить, что на выбранном таймфрейме сформировался новый бар. Мы начинаем с добавления вспомогательной функции, которая проверяет, изменилась ли временная метка самого последнего бара.
//+------------------------------------------------------------------+ //| Function to check if there's a new bar on a given chart timeframe| //+------------------------------------------------------------------+ bool IsNewBar(string symbol, ENUM_TIMEFRAMES tf, datetime &lastTm){ datetime currentTm = iTime(symbol, tf, 0); if(currentTm != lastTm){ lastTm = currentTm; return true; } return false; }
Эту функцию следует разместить в разделе вспомогательных функций исходного файла. Функция сравнивает время открытия текущего бара с сохраненным опорным временем. Если два значения различаются, сформировался новый бар, и опорное время обновляется. Этот простой механизм позволяет советнику выполнять дневную логику только один раз на бар, предотвращая повторные оценки на каждом тике.
Для поддержки этой логики вводится глобальная переменная datetime, в которой хранится последнее известное время открытия бара.
//--- To help track new bar open datetime lastBarOpenTime;
Эта переменная должна сохраняться между тиками, поэтому она объявляется глобально. Во время инициализации советника эта переменная явно устанавливается в ноль.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Initialize global variables lastBarOpenTime = 0; return(INIT_SUCCEEDED); }
Это гарантирует, что первый бар, обнаруженный после запуска, будет обработан как новый бар, обеспечивая корректное поведение с самого начала. Этот механизм становится основой всего потока исполнения, поскольку все обнаружение паттернов Smash и управление сетапами позднее будут зависеть от точного определения нового бара.
Фильтрация недействительных баров Smash с помощью обнаружения баров внешнего диапазона (outside bar)
Одно из ключевых проектных решений состоит в том, что бар Smash не должен быть баром внешнего диапазона (outside bar). Такие бары часто отражают избыточную волатильность и некачественное ценовое формирование, что противоречит смыслу разворотного сетапа Smash. Чтобы обеспечить это правило, мы вводим функцию, которая проверяет, полностью ли бар поглощает диапазон предыдущего бара.
//+-----------------------------------------------------------------------------+ //| Checks whether the bar at the given index fully engulfs the prior bar range | //+-----------------------------------------------------------------------------+ bool IsOutsideBar(string symbol, ENUM_TIMEFRAMES tf, int index){ double high0 = iHigh(symbol, tf, index); double low0 = iLow (symbol, tf, index); double high1 = iHigh(symbol, tf, index + 1); double low1 = iLow (symbol, tf, index + 1); return (high0 > high1 && low0 < low1); }
Функция сравнивает максимум и минимум текущего бара со значениями предыдущего бара. Если текущий бар расширяется как выше, так и ниже предыдущего бара, он классифицируется как бар внешнего диапазона и исключается из дальнейшей оценки. Эта функция намеренно сделана небольшой и сфокусированной. Она не обнаруживает паттерны Smash напрямую; вместо этого она действует как фильтр, на который опираются другие функции обнаружения. Изолируя эту логику, мы повышаем ясность и избегаем повторения кода.
Определение разворотных паттернов Smash Day
После фильтрации внешних баров мы можем определить логику выявления действительных разворотных паттернов Smash Day. Функция обнаружения покупочного разворота проверяет, пробивает ли закрытие бара Smash вниз настраиваемое количество предыдущих минимумов.
//+-----------------------------------------------------------------------------------+ //| Detects a Smash Day Buy Reversal where the close breaks below multiple prior lows | //+-----------------------------------------------------------------------------------+ bool IsSmashDayBuyReversal(string symbol, ENUM_TIMEFRAMES tf, int index, int lookbackBars) { // Bar must not be an outside bar if(IsOutsideBar(symbol, tf, index)) return false; double close1 = iClose(symbol, tf, index); // Validate close breaks below N prior lows for(int i = 2; i <= lookbackBars + 1; i++) { double priorLow = iLow(symbol, tf, i); if(close1 >= priorLow) return false; } return true; }
Этот период ретроспективного анализа управляется пользователем, что дает гибкость в степени строгости определения паттерна. Сначала функция проверяет, является ли бар внешним баром. Затем она проверяет, находится ли цена закрытия ниже каждого предыдущего минимума в пределах ретроспективного окна. Если какой-либо предыдущий минимум не пробит, паттерн отклоняется.
Функция обнаружения продажного разворота точно зеркалит эту логику, но в противоположном направлении.
//+-------------------------------------------------------------------------------------+ //| Detects a Smash Day Sell Reversal where the close breaks above multiple prior highs | //+-------------------------------------------------------------------------------------+ bool IsSmashDaySellReversal(string symbol, ENUM_TIMEFRAMES tf, int index, int lookbackBars) { // Bar must not be an outside bar if(IsOutsideBar(symbol, tf, index)) return false; double close1 = iClose(symbol, tf, index); // Validate close breaks above N prior highs for(int i = 2; i <= lookbackBars + 1; i++) { double priorHigh = iHigh(symbol, tf, i); if(close1 <= priorHigh) return false; } return true; }
Она проверяет, закрывается ли бар Smash выше ряда предыдущих максимумов. Эти две функции представляют базовую логику генерации сигналов. Каждое последующее торговое решение в советнике можно проследить до этих валидаторов паттерна.
Обновление и интерпретация данных Supertrend
Поскольку этот советник поддерживает фильтрацию по тренду, нам нужно получать и интерпретировать значения индикатора Supertrend. Вводится отдельная функция для получения последних значений верхней и нижней полос Supertrend с использованием дескриптора индикатора, созданного во время инициализации.
//+------------------------------------------------------------------+ //| Fetches recent Supertrend upper and lower band values | //+------------------------------------------------------------------+ void UpdateSupertrendBandValues(){ //--- Get a few Supertrend upper band values int copiedUpper = CopyBuffer(supertrendIndicatorHandle, 5, 0, 5, upperBandValues); if(copiedUpper == -1) { Print("Error while copying Supertrend upper band values: ", GetLastError()); return; } //--- Get a few Supertrend lower band values int copiedLower = CopyBuffer(supertrendIndicatorHandle, 6, 0, 5, lowerBandValues); if(copiedLower == -1) { Print("Error while copying Supertrend lower band values: ", GetLastError()); return; } if(copiedUpper < 5 || copiedLower < 5){ Print("Insufficient Supertrend indicator data!"); return; } }
Эта функция обновляет внутренние массивы, в которых хранится самый свежий вывод индикатора. Проверка данных выполняется сразу, чтобы убедиться, что получено достаточное количество значений. Это предотвращает ложные трендовые сигналы, вызванные неполными данными индикатора.
Затем определяются две небольшие вспомогательные функции для интерпретации состояния Supertrend.
//+------------------------------------------------------------------+ //| Returns true if Supertrend is currently in a bullish trend state | //+------------------------------------------------------------------+ bool IsSupertrendCurrentlyBullish(){ if(lowerBandValues[1] != EMPTY_VALUE){ return true; } return false; } //+------------------------------------------------------------------+ //| Returns true if Supertrend is currently in a bearish trend state | //+------------------------------------------------------------------+ bool IsSupertrendCurrentlyBearish(){ if(upperBandValues[1] != EMPTY_VALUE){ return true; } return false; }
Бычий тренд определяется, когда активна нижняя полоса, а медвежий тренд — когда активна верхняя полоса. Отделение получения данных от интерпретации сохраняет трендовую логику чистой и пригодной для повторного использования. Позднее логика входа просто спрашивает, является ли тренд бычьим или медвежьим, не заботясь о том, как было получено это решение.
Фильтрация по торговому дню недели
Ларри Уильямс часто подчеркивал важность тайминга и рыночного ритма. Чтобы отразить это, советник включает дополнительную фильтрацию по дню недели. Вспомогательная функция преобразует значение datetime в числовой день недели.
//+------------------------------------------------------------------------------------+ //| Returns the day of the week (0 = Sunday, 6 = Saturday) for the given datetime value| //+------------------------------------------------------------------------------------+ int TimeDayOfWeek(datetime time){ MqlDateTime timeStruct = {}; if(!TimeToStruct(time, timeStruct)){ Print("TimeDayOfWeek: TimeToStruct failed"); return -1; } return timeStruct.day_of_week; }
Затем это значение используется другой функцией, которая проверяет, разрешена ли торговля на основе пользовательских входных параметров.
//+-----------------------------------------------------------------------------------------------------+ //| Determines whether trading is permitted for the given datetime based on the selected trade-day mode | //+-----------------------------------------------------------------------------------------------------+ bool IsTradingDayAllowed(datetime time) { // Baseline mode: no filtering if(tradeDayMode == TDW_ALL_DAYS){ return true; } int day = TimeDayOfWeek(time); switch(day) { case 0: return tradeSunday; case 1: return tradeMonday; case 2: return tradeTuesday; case 3: return tradeWednesday; case 4: return tradeThursday; case 5: return tradeFriday; case 6: return tradeSaturday; } return false; }
Логика фильтрации поддерживает два режима. Торговля может быть разрешена либо во все дни, либо только в выбранные дни. Такая конструкция позволяет одному и тому же советнику поддерживать как дискреционные предпочтения по таймингу, так и системное тестирование. Эта логика намеренно изолирована, чтобы фильтрацию по дням можно было последовательно применять ко всем условиям входа далее в коде.
Обнаружение пересечений ценового уровня для тайминга входа
Одно из важных проектных решений в этом советнике — дать пользователям возможность выбирать, как запускаются входы. Одни трейдеры предпочитают входить сразу, когда цена пересекает уровень. Другие требуют подтверждения через закрытие бара за этим уровнем. Для поддержки первого варианта мы вводим функции обнаружения пересечения вверх и пересечения вниз.
//+------------------------------------------------------------------+ //| To detect a crossover at a given price level | //+------------------------------------------------------------------+ bool IsCrossOver(const double price, const double &closePriceMinsData[]){ if(closePriceMinsData[1] <= price && closePriceMinsData[0] > price){ return true; } return false; } //+------------------------------------------------------------------+ //| To detect a crossunder at a given price level | //+------------------------------------------------------------------+ bool IsCrossUnder(const double price, const double &closePriceMinsData[]){ if(closePriceMinsData[1] >= price && closePriceMinsData[0] < price){ return true; } return false; }
Эти функции работают с закрытыми данными минутного уровня и обнаруживают, когда цена пересекает заданный уровень снизу или сверху. Они простые, но точные, и гарантируют, что входы запускаются только один раз на событие пересечения. Эти утилиты напрямую интегрируются в логику входа и остаются неактивными, если пользователь не выбирает режим входа по уровню.
Ограничение: не более одной открытой позиции одновременно
Чтобы предотвратить чрезмерную экспозицию, советник рассчитан на удержание только одной открытой позиции одновременно. Две вспомогательные функции определены для проверки того, существует ли уже позиция buy или sell с заданным magic number.
//+------------------------------------------------------------------+ //| To verify whether this EA currently has an active buy position. | | //+------------------------------------------------------------------+ bool IsThereAnActiveBuyPosition(ulong magic){ for(int i = PositionsTotal() - 1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(ticket == 0){ Print("Error while fetching position ticket ", _LastError); continue; }else{ if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ return true; } } } return false; } //+------------------------------------------------------------------+ //| To verify whether this EA currently has an active sell position. | | //+------------------------------------------------------------------+ bool IsThereAnActiveSellPosition(ulong magic){ for(int i = PositionsTotal() - 1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(ticket == 0){ Print("Error while fetching position ticket ", _LastError); continue; }else{ if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ return true; } } } return false; }
Каждая функция просматривает все открытые позиции и возвращает true, если найдена совпадающая позиция. Перед открытием любой сделки выполняются эти проверки. Это гарантирует строгий контроль позиций и предотвращает случайное накопление сделок.
Расчет размера позиции на основе риска и проекция прибыли
Управление риском является центральным элементом системы. Отдельная функция рассчитывает размер позиции на основе фиксированного процента от баланса счета.
//+----------------------------------------------------------------------------------+ //| Calculates position size based on a fixed percentage risk of the account balance | //+----------------------------------------------------------------------------------+ double CalculatePositionSizeByRisk(double stopDistance){ double amountAtRisk = (riskPerTradePercent / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE); double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); double volume = amountAtRisk / (contractSize * stopDistance); return NormalizeDouble(volume, 2); }
Расчет использует расстояние до стоп-лосса и размер контракта, чтобы определить объем, соответствующий желаемому риску. Затем определяются отдельные функции для расчета уровней тейк-профита для покупок и продаж с использованием настраиваемого соотношения риск/прибыль.
//+--------------------------------------------------------------------------------------------------+ //| Computes the bullish take profit level based on entry price, stop loss, and risk to reward ratio | //+--------------------------------------------------------------------------------------------------+ double GetBuyTakeProfit(double entryPrice, double stopLoss){ double riskDistance = entryPrice - stopLoss; double rewardDistance = riskDistance * riskRewardRatio; rewardDistance = MathAbs(rewardDistance); return NormalizeDouble((entryPrice + rewardDistance), Digits()); } //+--------------------------------------------------------------------------------------------------+ //| Computes the bearish take profit level based on entry price, stop loss, and risk to reward ratio | //+--------------------------------------------------------------------------------------------------+ double GetSellTakeProfit(double entryPrice, double stopLoss){ double riskDistance = stopLoss - entryPrice; double rewardDistance = riskDistance * riskRewardRatio; rewardDistance = MathAbs(rewardDistance); return NormalizeDouble((entryPrice - rewardDistance), Digits()); }
Эти функции обеспечивают согласованность всех сделок и предотвращают жестко заданные цели по прибыли. Благодаря изоляции этих расчетов советник остается гибким и легко расширяемым.
Исполнение рыночных ордеров
Две функции исполнения обрабатывают ордера buy и sell.
//+------------------------------------------------------------------+ //| Function to open a market buy position | //+------------------------------------------------------------------+ bool OpenBuy(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(lotSizeMode == MODE_AUTO){ lotSize = CalculatePositionSizeByRisk(entryPrice - stopLoss); } if(!Trade.Buy(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; } //+------------------------------------------------------------------+ //| Function to open a market sell position | //+------------------------------------------------------------------+ bool OpenSel(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(lotSizeMode == MODE_AUTO){ lotSize = CalculatePositionSizeByRisk(stopLoss - entryPrice); } if(!Trade.Sell(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){ Print("Error while executing a market sell order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
Каждая функция поддерживает как ручной, так и автоматический расчет размера лота. Когда включен автоматический режим, размер позиции рассчитывается динамически с помощью функции на основе риска, определенной ранее. Ошибки исполнения четко записываются в журнал, чтобы помочь при отладке и тестировании. Эти функции служат финальным шагом торгового конвейера: они получают проверенные сигналы и безопасно исполняют сделки.
Объединение всего внутри функции обработки тиков
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- Retrieve current market prices for trade execution askPrice = SymbolInfoDouble (_Symbol, SYMBOL_ASK); bidPrice = SymbolInfoDouble (_Symbol, SYMBOL_BID); currentTime = TimeCurrent(); //--- Get some minutes data if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){ Print("Error while copying minutes datas ", GetLastError()); return; } //--- Update Supertrend UpdateSupertrendBandValues(); //--- Execute this block on new bar formation if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){ //--- if(!smashState.hasBuySmashSetup && !smashState.hasSellSmashSetup){ if(IsSmashDayBuyReversal(_Symbol, timeframe, 1, smashBuyLookbackBars)){ smashState.hasBuySmashSetup = true; smashState.buyBreakoutLevel = iHigh(_Symbol, timeframe, 1); smashState.smashBarTime = iTime(_Symbol, timeframe, 1); smashState.entryPending = true; smashState.buyStopLoss = iLow(_Symbol, timeframe, 1); } //--- if(IsSmashDaySellReversal(_Symbol, timeframe, 1, smashSellLookbackBars)){ smashState.hasSellSmashSetup = true; smashState.sellBreakoutLevel = iLow(_Symbol, timeframe, 1); smashState.smashBarTime = iTime(_Symbol, timeframe, 1); smashState.entryPending = true; smashState.sellStopLoss = iHigh(_Symbol, timeframe, 1); } }else{ smashState.barsSinceSmash = smashState.barsSinceSmash + 1; if(smashState.barsSinceSmash > smashSetupValidityBars){ ZeroMemory(smashState); } } //--- if(smashState.hasBuySmashSetup && smashState.entryPending){ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(smashTradeMode == SMASH_TRADE_BUY_ONLY || smashTradeMode == SMASH_TRADE_BOTH){ if(askPrice > smashState.buyBreakoutLevel){ if(useSupertrendFilter){ if(IsSupertrendCurrentlyBullish()){ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } } }else{ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } } } } } } //--- if(smashState.hasSellSmashSetup && smashState.entryPending){ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(smashTradeMode == SMASH_TRADE_SELL_ONLY || smashTradeMode == SMASH_TRADE_BOTH){ if(bidPrice < smashState.sellBreakoutLevel){ if(useSupertrendFilter){ if(IsSupertrendCurrentlyBearish()){ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } } }else{ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } } } } } } } //--- if(smashEntryMode == ENTRY_ON_LEVEL_CROSS){ //--- if(smashState.hasBuySmashSetup && smashState.entryPending){ // --- if(IsCrossOver(smashState.buyBreakoutLevel, closePriceMinutesData)){ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(smashTradeMode == SMASH_TRADE_BUY_ONLY || smashTradeMode == SMASH_TRADE_BOTH){ if(useSupertrendFilter){ if(IsSupertrendCurrentlyBullish()){ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } } }else{ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize); ZeroMemory(smashState); } } } } } } //--- if(smashState.hasSellSmashSetup && smashState.entryPending){ // --- if(IsCrossUnder(smashState.sellBreakoutLevel, closePriceMinutesData)){ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(smashTradeMode == SMASH_TRADE_SELL_ONLY || smashTradeMode == SMASH_TRADE_BOTH){ if(useSupertrendFilter){ if(IsSupertrendCurrentlyBearish()){ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } } }else{ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } }else{ OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize); ZeroMemory(smashState); } } } } } } } }
На этом этапе все необходимые компоненты уже готовы, и функция обработки тиков координирует работу всей стратегии. Далее при необходимости можно добавить визуальные элементы, чтобы упростить наблюдение за барами Smash и поведением цены в ходе тестирования.
Завершение этапа разработки
На этом этапе советник полностью построен и готов к тестированию. Полный исходный код прикреплен как lwSmashDayTrendFilteredExpert.mq5, что позволяет читателям изучить финальную реализацию без перегрузки статьи полным листингом кода. На этом этапе разработка завершается. Каждая введенная функция выполняет конкретную задачу и логически интегрируется с остальной системой.
Тестирование стратегии на исторических данных
Чтобы оценить, как советник ведет себя в реальных рыночных условиях, был проведен бэктест на исторических ценовых данных. Этот этап важен, потому что позволяет наблюдать характеристики эффективности, такие как прибыльность, стабильность и просадка, без какой-либо субъективной интерпретации.
Тест был выполнен на золоте с использованием символа XAUUSD. Был выбран дневной таймфрейм, чтобы сохранить соответствие исходной логике Smash Day, описанной Ларри Уильямсом. Период тестирования охватывал один полный календарный год — с 1 января 2025 года по 30 декабря 2025 года. Это дает широкую выборку рыночных условий в пределах одного года.
В этом запуске были включены только покупочные сетапы Smash Day. Это означает, что система была ограничена только длинными позициями, что позволило изолировать и оценить бычье поведение Smash Day без влияния коротких сделок. Размер позиции был установлен в автоматический режим, при котором каждая сделка рисковала ровно 1,0 процента баланса счета.

Начальный баланс счета был установлен на уровне 10000 долларов. К концу периода тестирования система получила общую чистую прибыль 832,97 доллара, что означает прирост счета чуть более чем на 8%. Зафиксированный процент прибыльных сделок составил 75,00 процента. Хотя значение прибыли может показаться скромным, кривая капитала рассказывает более важную историю.
Она остается плавной на протяжении всего периода тестирования и не демонстрирует резких просадок или нестабильных колебаний equity, что указывает на контролируемый риск и стабильную отработку сделок. Скриншот кривой капитала включен в этот раздел, чтобы визуально подтвердить такое поведение и упростить интерпретацию результатов с первого взгляда.

Чтобы упростить воспроизведение, вместе со статьей прикреплены два файла. Первый файл содержит конфигурацию среды тестера, а второй — точный набор входных параметров, использованный в этом тесте. С этими файлами те же условия тестирования можно воспроизвести без неоднозначности.
Этот тест представляет только одну конфигурацию и один рынок. Советник намеренно спроектирован гибким за счет входных параметров. Разные значения глубины ретроспективного окна, режимы входа, настройки риска и фильтры можно изменять для изучения альтернативных идей. Дополнительные эксперименты на других символах или таймфреймах могут выявить поведение, подходящее для других торговых стилей или предпочтений по риску.
Любые выводы, улучшения или наблюдения, обнаруженные в ходе независимого тестирования, ценны. Обмен такими идеями в комментариях к статье помогает расширить исследование за пределы одной реализации и превращает систему в совместный обучающий инструмент, а не в фиксированное решение.
Заключение
Эта статья была направлена на решение конкретной проблемы. Торговля паттернами Smash Day без контекста дает шумные сигналы и нестабильные результаты. Цель состояла в том, чтобы преобразовать паттерн в структурированную, тестируемую систему, подходящую для автоматизации.
Эта цель достигнута. В статье представлен полный набор объективных правил Smash Day, включая идентификацию паттерна, логику подтверждения, ограничения срока действия сетапа и контролируемое поведение входа. Опциональные контекстные фильтры, такие как направление Supertrend и выбор Trade Day of the Week, интегрированы непосредственно в систему и могут включаться или отключаться через входные параметры.
На практике читатель получает полностью функциональный советник MQL5, который автоматизирует развороты Smash Day точно в соответствии с заданным определением. Советник обеспечивает правило: не более одной открытой позиции одновременно, применяет последовательное управление риском и явно выносит все допущения в настраиваемые входные параметры. Прикрепленные файлы параметров и конфигурации позволяют воспроизводить результаты и проводить дальнейшее тестирование без изменения исходного кода.
Для алгоритмических трейдеров это готовый исследовательский инструмент для изучения поведения Smash Day в разных условиях. Для разработчиков MQL5 он демонстрирует, как дискреционные ценовые паттерны можно перевести в чистую, тестируемую логику. Самое важное, статья выполняет обещание, данное в начале: показывает понятный путь от изолированного паттерна к объективной, контекстно-ориентированной торговой системе, которую можно надежно тестировать и оценивать.
Вложения
| Имя файла | Описание |
|---|---|
| lwSmashDayTrendFilteredExpert.mq5 | Советник, разработанный в этой статье |
| configurations.ini | Для настройки среды тестера стратегий для тестового запуска в этой статье |
| parameters.set | Для настройки пользовательских входных переменных для теста, использованного в этой статье |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21228
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Торговые инструменты на MQL5 (Часть 29): Пошаговая анимация кривой-бабочки на Canvas
Разработка инструментария для анализа Price Action (Часть 66): Создание сканера паттерна "голова и плечи" с проверкой структуры на MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования