English Deutsch 日本語
preview
Создание советника на MQL5 на основе стратегии Прорыва дневного диапазона (Daily Range Breakout)

Создание советника на MQL5 на основе стратегии Прорыва дневного диапазона (Daily Range Breakout)

MetaTrader 5Трейдинг | 3 июня 2025, 07:57
192 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В настоящей статье мы рассмотрим, как создать советника (EA) на языке MetaQuotes Language 5 (MQL5) на основе стратегии Прорыва дневного диапазона. Поскольку трейдеры постоянно ищут эффективные решения для автоматической торговли, стратегия прорыва дневного диапазона предлагает систематический подход, который позволяет извлекать выгоду из движения цен за пределами определенного диапазона, что делает ее привлекательной для форекс-трейдеров в MetaTrader 5.

Мы начнем с изложения фундаментальных принципов стратегии Прорыв дневного диапазона, обеспечивающих прочную основу для ее реализации в автоматической торговле. Далее мы подробно рассмотрим особенности определения условий прорыва и установления точек входа и выхода. Далее расскажем о процессе написания кода на MQL5, выделив основные функции и логику, лежащие в основе стратегии. Кроме того, обсудим важность бэк-тестирования и оптимизации программы для обеспечения ее эффективности в условиях торговли. Темы, которые мы рассмотрим в настоящей статье:

  1. Понимание стратегии Прорыв дневного диапазона
  2. Схема советника
  3. Реализация стратегии Прорыв дневного диапазона на MQL5
  4. Бэк-тестирование и оптимизация
  5. Заключение

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


Понимание стратегии Прорыв дневного диапазона

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

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

DAILY RANGE BREAKOUT ILLUSTRATION

Для достижения максимальной эффективности эта стратегия используется либо на 1-часовом, либо на 4-часовом графике. Когда трейдеры используют этот шаблон на этих таймфреймах, они часто могут уловить более крупные и значимые колебания цены. Это происходит потому, что стратегия в основном свободна от шума, присутствующего на более низких таймфреймах. Прежде чем совершать сделки во время Лондонской и Нью-Йоркской сессий, для определения дневного диапазона стратегия прорыва обычно использует ценовое движение с азиатской сессии. Стратегии прорыва обычно имеют проблему подачи ложных сигналов, и Прорыв дневного диапазона не является исключением. Таким образом, как и в случае с любой торговой стратегией, при использовании Прорыва дневного диапазона жизненно важно управлять рисками. Для сохранения обоснованности риска, размещайте свои стоп-лоссы чуть ниже последнего минимума колебания для длинных позиций и выше последнего максимума колебания для коротких сделок. Это и будет нашей стратегией. Она управляет рисками с помощью стоп-лосса, размещаемого выше или ниже последнего максимума или минимума колебания, в зависимости от обстоятельств. Вот еще одна иллюстрация логики стоп-лосса.

STOP LOSS ENTRY

Стратегия Прорыва дневного диапазона выгодна в нескольких отношениях. Во-первых, её простота делает её подходящим выбором как для начинающих, так и для опытных трейдеров. Во-вторых, она использует определенные уровни, что удерживает трейдеров от принятия слишком большого количества произвольных решений. То, как этот метод торговли воздействует на рынок, позволяет получить четкую картину до и после каждой дневной сессии. Утром торговая активность на рынке может быть ограничена определенным "диапазоном". Затем, после закрытия утренней сессии, "пробитие" либо верхней, либо нижней линии диапазона становится возможным сигналом для входа в сделку на следующий день. В следующем разделе мы более подробно опишем наши торговые параметры, предоставив четкую схему со всеми конкретными деталями.


Схема советника

Прорыв верхнего уровня диапазона: Условие покупки

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

ПРОРЫВ ВЕРХНЕГО УРОВНЯ ДИАПАЗОНА

Прорыв нижнего уровня диапазона: Условие продажи

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

ПРОРЫВ НИЖНЕГО УРОВНЯ ДИАПАЗОНА

Эти наглядные изображения схемы стратегии будут полезны при реализации нами этих торговых условий на MQL5 и послужат ориентиром для написания точных правил входа и выхода.


Реализация стратегии Прорыв дневного диапазона на MQL5

После изучения всех теорий о торговой стратегии Прорыва дневного диапазона давайте автоматизируем теорию и создадим советника (EA) на MetaQuotes Language 5 (MQL5) для  MetaTrader 5.

Для создания советника (EA), в вашем терминале MetaTrader 5 перейдите на вкладку «Инструменты» и выберите языковой редактор MetaQuotes или просто нажмите клавишу F4 на клавиатуре. Кроме того, вы можете щелкнуть иконку IDE (интегрированная среда разработки) на панели инструментов. Откроется среда разработки на  MetaQuotes Language Editor , которая позволяет писать торговых роботов, технические индикаторы, скрипты и библиотеки функций.

Открыть MetaEditor

После открытия MetaEditor, на панели инструментов выберите "Файл" - "Новый файл" или нажмите CTRL + N, чтобы создать новый документ. Также вы можете нажать на иконку "Создать" в панели инструментов. Откроется окно Мастера MQL.

CREATE NEW EA

В открывшемся Мастере выберите Советник (шаблон) и нажмите Далее.

Мастер MQL

В общих свойствах укажите имя файла вашего советника. Чтобы указать или создать папку, если она не существует, используйте обратную косую черту перед именем советника. Например, по умолчанию указана папка «Experts\». Это значит, что наш советник будет создан в папке Experts. Остальные разделы довольно просты, но вы можете перейти по ссылке в нижней части Мастера, чтобы узнать детали.

ИМЯ НОВОГО СОВЕТНИКА

После указания имени файла советника нажмите "Далее" > "Далее" > "Готово". Теперь мы готовы к воплощению стратегии в коде.

Во-первых, начнем с определения некоторых метаданных о советнике (EA). Сюда относятся: название советника, информация об авторских правах и ссылка на веб-сайт MetaQuotes. Мы также указываем версию советника, которая установлена на "1.00".

//+------------------------------------------------------------------+
//|                          Daily Range Breakout Expert Advisor.mq5 |
//|      Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                                     https://forexalg0-trader.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://forexalg0-trader.com"
#property description "Daily Range Breakout Expert Advisor"
#property version   "1.00"

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

METADATA INFORMATION

Во-первых, мы включаем торговый экземпляр, используя  #include  в начале исходного кода. Он даст нам доступ к классу CTrade, который мы будем использовать для создания торгового объекта. Это очень важно, так как он необходимо нам для открытия сделок.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

Препроцессор заменит строку  #include <Trade/Trade.mqh> содержимым файла Trade.mqh. Угловые скобки указывают, что файл Trade.mqh будет взят из стандартного каталога (обычно это terminal_installation_directory\MQL5\Include). Текущий каталог не включен в поиск. Строку можно разместить в любом месте программы, но обычно все включения размещаются в начале исходного кода для лучшей структуры кода и удобства ссылок. Благодаря разработчикам MQL5 объявление объекта obj_Trade класса  CTrade  предоставит нам легкий доступ к методам, содержащимся в этом классе.

Класс CTRADE

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

double maximum_price = -DBL_MAX;      //--- Initialize the maximum price with the smallest possible value
double minimum_price = DBL_MAX;       //--- Initialize the minimum price with the largest possible value
datetime maximum_time, minimum_time;  //--- Declare variables to store the time of the highest and lowest prices
bool isHaveDailyRange_Prices = false; //--- Boolean flag to check if daily range prices are extracted
bool isHaveRangeBreak = false;        //--- Boolean flag to check if a range breakout has occurred

Здесь мы объявляем несколько важных переменных для отслеживания ключевых ценовых данных и обработки прорывов диапазона в торговой логике. Сначала мы инициализируем две переменные double, "maximum_price" и "minimum_price", в которых будут храниться самые высокие и самые низкие цены, найденные за определенный период. "maximum_price" установлена на -DBL_MAX, наименьшее возможное двойное значение, гарантирующее, что любая встречаемая цена будет выше и заменит это начальное значение. Аналогично, мы устанавливаем "minimum_price" для DBL_MAX, максимально возможное двойное значение, гарантируя, что любая более низкая цена заменит его в качестве минимального значения.

Мы также объявляем две переменные datetime, "maximum_time" и "minimum_time", для хранения точных временных отрезков, когда возникают максимальные и минимальные цены. Это поможет нам позже, если нам понадобится указать конкретные моменты, когда были достигнуты эти уровни цен.

Кроме того, объявлены две переменные bool для обработки логики, связанной с ценовыми диапазонами и прорывами. Первая, "isHaveDailyRange_Prices", инициализируется до «false» и служит флагом, указывающим, были ли успешно определены цены дневного диапазона (т.е. максимальные и минимальные). Вторая, "isHaveRangeBreak", также инициализируется до «false», действует как флаг, указывающий на то, произошел ли прорыв, означающий, что цена вышла за пределы дневного диапазона. Кроме того, мы визуально представим диапазоны на графике. Таким образом, нам понадобятся имена для диапазонов, и мы также можем объявить их здесь.

#define RECTANGLE_PREFIX "RANGE RECTANGLE " //--- Prefix for naming range rectangles
#define UPPER_LINE_PREFIX "UPPER LINE "     //--- Prefix for naming upper range line
#define LOWER_LINE_PREFIX "LOWER LINE "     //--- Prefix for naming lower range line

Здесь мы определяем три директивы препроцессора, которые создают префиксы для именования различных графических объектов, связанных с торговым диапазоном. Мы используем директиву #define RECTANGLE_PREFIX "RANGE RECTANGLE ", чтобы установить непротиворечивое соглашение об именовании в отношении прямоугольников, представляющих торговый диапазон, что упрощает идентификацию этих объектов на графике и управление ими. Аналогично, #define UPPER_LINE_PREFIX "UPPER LINE " создает префикс специально для верхней границы диапазона, в то время как LOWER_LINE_PREFIX "LOWER LINE " служит той же цели для нижней границы. Используя эти префиксы, мы гарантируем, что все графические объекты, относящиеся к диапазону, имеют систематизированные названия, что помогает сохранить ясность и организованность кода, особенно когда на графике может присутствовать несколько объектов.

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

}

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

   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

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

Далее вычисляем время для "sixAM", добавляя шесть часов, представленных в виде 6 * 3600, где 3600 - это количество секунд в часе (то есть 1 час, умноженный на 60 минут, умноженных на 60 секунд), к переменной "midnight". Это позволяет нам определять время для ежедневного анализа после открытия рынка, что облегчает наш анализ ценового движения, начиная с ранних часов торгового дня.

Наконец, мы устанавливаем переменную "scanBarTime", чтобы указать время сканирования для следующего бара после "sixAM". Мы достигаем этого, динамически добавляя дополнительный бар к текущему времени сканирования в 6 AM, чтобы при сканировании также учитывался бар 6 AM. Цифра 1 обозначает количество баров для сканирования, а функция PeriodSeconds автоматически преобразует текущий период графика в секунды. Например, у нас может быть 1-часовой график, что означает, что мы переводим 1 час в секунды и умножаем секунды на 1 столбец, что обычно дает 3600 секунд, а затем добавляем их к 6 AM, получая бар 7 AM. В целом, эти статические переменные имеют решающее значение для реализации нашей основанной на времени логики, в рамках торговой стратегии.

Далее, мы также можем объявить переменные, определяющие наши допустимые диапазоны прорыва во времени, если прорыв происходит через 7 часов или в определенное время, например, в 13:00, мы не считаем какой-либо сигнал действительным и, таким образом, ждем настройки следующего дня.

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+5) * 3600; //--- Set the end of valid breakout time to 11 AM

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

Вторая переменная, "validBreakTime_end", вычисляется путем добавления (6 + 5) * 3600 к переменной "midnight". Это выражение указывает на окончание нашего действительного периода прорыва, который соответствует 11 часам утра. Устанавливая этот временной интервал, мы создаем четкое окно, в течение которого будем оценивать условия прорыва, обеспечивая, что наши торговые решения будут основаны на изменениях цен, которые происходят в пределах этого определенного диапазона. После всего этого мы все готовы приступить к нашей логике. Первое, что нам нужно учитывать, это то, что мы хотим проверять настройки каждый день, поэтому нам потребуется логика, которая идентифицирует новый день.

   if (isNewDay()){
        //---

   }

Мы используем оператор if для проверки наличия нового дня, и если он действительно существует, мы исполняем фрагмент кода внутри него. Для проверки наличия нового дня мы используем пользовательскую логическую функцию "isNewDay". Её логика представлена ниже:

bool isNewDay() {
   //--- Flag to indicate if a new day has started
   bool newDay = false;
   
   //--- Structure to hold the current date and time
   MqlDateTime Str_DateTime;
   
   //--- Convert the current time to a structured format
   TimeToStruct(TimeCurrent(), Str_DateTime);
   
   //--- Static variable to store the previous day
   static int prevDay = 0;
   
   //--- Get the current day from the structured time
   int currDay = Str_DateTime.day;
   
   //--- If the previous day is the same as the current day, we're still on the same day
   if (prevDay == currDay) {
      newDay = false;
   }
   //--- If the current day differs from the previous one, we have a new day
   else if (prevDay != currDay) {
      //--- Print a message indicating the new day
      Print("WE HAVE A NEW DAY WITH DATE ", currDay);
      
      //--- Update the previous day to the current day
      prevDay = currDay;
      
      //--- Set the flag to true, indicating a new day has started
      newDay = true;
   }
   
   //--- Return whether a new day has started
   return (newDay);
}

Здесь мы определяем логическую функцию "isNewDay", которая отвечает за определение того, начался ли новый день в нашей торговой стратегии. Мы инициализируем логическую переменную "newDay" значением "false", которое служит флагом, указывающим на то, начался ли новый день. Для отслеживания текущей даты и времени создаем структуру типа MqlDateTime под названием "Str_DateTime". Мы используем функцию TimeToStruct для преобразования текущего времени, полученного из текущего времени, в структурированный формат, заполняя структуру "Str_DateTime" соответствующей информацией о дате и времени.

Далее мы объявляем статическую целочисленную переменную "prevDay", инициализированную до нуля, которая хранит день последней записанной даты. Затем извлекаем текущий день из структуры "Str_DateTime", присваивая его целочисленной переменной "currDay".

Сравниваем "prevDay" с "currDay". Если они равны, это означает, что мы все еще находимся в пределах одного и того же дня, и устанавливаем для "NewDay" значение "false". И наоборот, если "prevDay" отличается от ""currDay,", мы понимаем, что начался новый день. В этом случае выводим сообщение, указывающее на переход на новый день с помощью функции Print, обновляя переменную "prevDay" значением "currDay". Затем устанавливаем флаг "newDay" на значение "true", подтверждая, что новый день начался. Наконец, функция возвращает значение флага "NewDay", позволяя нам использовать эту информацию в нашей торговой логике, чтобы определить, нужно ли предпринимать какие-либо действия в связи с началом нового дня.

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

      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+5) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day

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

Затем мы пересчитываем время на 6 утра, добавляя 6 часов, представленное как "6 * 3600", к недавно установленной переменной "midnight". Это дает нам опорную точку для начала торговой сессии утром. После этого мы устанавливаем значение "scanBarTime" равным одному бару после 6 утра, добавляя продолжительность одного периода, полученную с помощью функции PeriodSeconds, чтобы привести наши расчеты в соответствие с текущим периодом графика.

Затем переходим к обновлению действительных интервалов времени прорыва, установив "validBreakTime_start" для недавно рассчитанного "scanBarTime". Эта корректировка указывает на начальную точку для рассмотрения потенциальных прорывов в течение торгового дня. Мы также установили "validBreakTime_end" равным 11 утра, рассчитав его как "midnight + (6 + 5) * 3600", чтобы у нас была четкая конечная точка для оценки прорыва. Кроме того, мы сбрасываем значения "maximum_price" и "minimum_price", чтобы отслеживать движение цен на новый день, инициализируя "maximum_price" до -DBL_MAX (минимально возможное значение), а "minimum_price" - до DBL_MAX (максимально возможное значение). Такой сброс позволяет точно фиксировать самые высокие и самые низкие цены в течение дня.

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

   if (isNewBar()){
        //---

   }

Здесь мы по-прежнему используем оператор if в сочетании с функцией "isNewBar" для реализации логики генерации нового бара. Адаптированный функциональный код показан ниже в виде фрагмента кода.

bool isNewBar() {
   //--- Static variable to hold the previous number of bars
   static int prevBars = 0;
   
   //--- Get the current number of bars on the chart
   int currBars = iBars(_Symbol, _Period);
   
   //--- If the number of bars hasn't changed, return false
   if (prevBars == currBars) return (false);
   
   //--- Update the previous bar count with the current one
   prevBars = currBars;
   
   //--- Return true if a new bar has been formed
   return (true);
}

Мы начинаем с объявления статической переменной под названием "prevBars", которая хранит предыдущее количество баров, отображаемых на графике. Ключевое слово static гарантирует, что переменная сохраняет свое значение между вызовами функций, что позволяет нам эффективно отслеживать изменения в количестве баров. Далее получаем текущее число баров на графике, используя функцию iBars, где _Symbol представляет торговый инструмент, а _Period ссылается на таймфрейм графика. Эта функция возвращает общее количество баров, доступных в данный момент для указанного инструмента и периода.

Затем мы сравниваем текущее количество баров, хранящееся в переменной "currBars", с предыдущим количеством баров, "prevBars". Если эти два значения равны, это означает, что с момента последней проверки не было сформировано ни одного нового бара, поэтому мы возвращаем "false", чтобы указать, что все еще находимся на том же баре. Если значения отличаются, это означает, что создан новый бар, побуждая нас обновить "prevBars" значением "currBars". Наконец, мы возвращаем значение "true", сигнализируя о том, что новый бар действительно сформирован. Далее, внутри функции, нам нужно обработать данные при формировании нового бара, уделяя особое внимание определенному временному условию для извлечения ценовых данных.

      //--- If a new bar has been formed, process the data
      datetime currentBarTime = iTime(_Symbol,_Period,0); //--- Get the time of the current bar
      
      if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){
         //--- If it's time to scan and the daily range is not yet extracted
         Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); //--- Log the extraction process
         int total_bars = int((sixAM - midnight)/PeriodSeconds(_Period)) + 1; //--- Calculate total bars between midnight and 6 AM
         Print("Total Bars for scan = ",total_bars); //--- Log the total number of bars for scanning
         int highest_price_bar_index = -1;   //--- Variable to store the bar index of the highest price
         int lowest_price_bar_index = -1;    //--- Variable to store the bar index of the lowest price

         //--- 

      }

Сначала мы объявляем переменную "currentBarTime", используя функцию iTime, которая извлекает время текущего бара на графике. Это помогает нам определить, находимся ли мы в конкретной точке в момент времени в течение дня, когда нам надо обработать определенные ценовые данные. Далее мы проверяем два условия в операторе "if". Сначала проверяем, совпадает ли время текущего бара со временем сканирования бара, которое является назначенным временем, которое мы планируем проанализировать (в данном случае установим на 6 утра). Во-вторых, мы проверяем, не были ли еще извлечены цены дневного диапазона, проверяя, что флаг "isHaveDailyRange_Prices" имеет значение false. Если оба условия верны, это означает, что мы находимся в нужном моменте и необходимо извлечь данные о ценовом диапазоне.

Затем регистрируем сообщение, используя функцию Print, чтобы указать, что доступно достаточно данных для баров и что начнется процесс извлечения. Это помогает отследить, когда и почему запускается процесс во время выполнения. Мы приступаем к расчету общего количества баров с полуночи до 6 утра, что имеет решающее значение для определения ценового диапазона за этот период. Функция PeriodSeconds определяет продолжительность каждого бара, и мы делим разницу во времени между "sixAM" и "midnight" на эту продолжительность, чтобы вычислить общее количество баров. Чтобы убедиться, что включены все бары из этого диапазона, добавляем 1.

Наконец, выводим общее количество баров для сканирования с помощью другой функции Print, а затем объявляем две переменные: "highest_price_bar_index" и "lowest_price_bar_index". Мы инициализируем эти переменные до значения -1 и будем использовать их для хранения индекса баров, содержащих самые высокие и самые низкие цены соответственно в пределах наблюдаемого диапазона. Эта настройка подготавливает нас к извлечению ценовых данных из этих конкретных баров. После запуска программы мы получили следующие результаты.

BARS SCAN CONFIRMATION

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

         for (int i=1; i<=total_bars ; i++){ //--- Loop through all bars within the defined time range
            double open_i = open(i);         //--- Get the opening price of the i-th bar
            double close_i = close(i);       //--- Get the closing price of the i-th bar
            
            double highest_price_i = (open_i > close_i) ? open_i : close_i; //--- Determine the highest price between open and close
            double lowest_price_i = (open_i < close_i) ? open_i : close_i;  //--- Determine the lowest price between open and close
            
            if (highest_price_i > maximum_price){
               //--- If the current highest price is greater than the recorded maximum price
               maximum_price = highest_price_i; //--- Update the maximum price
               highest_price_bar_index = i;     //--- Update the index of the highest price bar
               maximum_time = time(i);          //--- Update the time of the highest price
            }
            if (lowest_price_i < minimum_price){
               //--- If the current lowest price is lower than the recorded minimum price
               minimum_price = lowest_price_i;  //--- Update the minimum price
               lowest_price_bar_index = i;      //--- Update the index of the lowest price bar
               minimum_time = time(i);          //--- Update the time of the lowest price
            }
         }

Для извлечения данных мы просматриваем все бары в течение определенного периода времени (с полуночи до 6 утра), чтобы определить самые высокие и самые низкие цены. Цель состоит в том, чтобы найти максимальные и минимальные цены, которые возникли в пределах этого диапазона, и зафиксировать время, когда они имели место. Мы начинаем с настройки цикла for с помощью оператора "for (int i=1; i<=total_bars ; i++)". Этот оператор означает, что цикл проходит через каждый бар, начиная с первого (индекс 1) и заканчивая "total_bars", который ранее был рассчитан для представления количества баров между полуночью и 6 часами утра. Переменная "i" представляет индекс каждого бара в цикле.

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

//--- Utility functions to retrieve price and time data for a given bar index
double open(int index){return (iOpen(_Symbol,_Period,index));}   //--- Get the opening price
double high(int index){return (iHigh(_Symbol,_Period,index));}   //--- Get the highest price
double low(int index){return (iLow(_Symbol,_Period,index));}     //--- Get the lowest price
double close(int index){return (iClose(_Symbol,_Period,index));} //--- Get the closing price
datetime time(int index){return (iTime(_Symbol,_Period,index));} //--- Get the time of the bar

Далее мы используем троичную операцию, чтобы определить самую высокую и самую низкую цены для каждого бара. Оператор "double highest_price_i = (open_i > close_i) ? open_i : close_i;" проверяет, превышает ли цена открытия цену закрытия. Если это так, то цена открытия устанавливается как самая высокая цена для данного бара. В противном случае цена закрытия становится самой высокой. Аналогично, "double lowest_price_i = (open_i < close_i) ? open_i : close_i;" сравнивает цены открытия и закрытия, чтобы определить самую низкую цену для бара.

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

  • Если максимальная цена для этого выбранного бара превышает зарегистрированную максимальную цену, мы обновляем максимальную цену до этого нового значения. Мы также сохраняем индекс этого бара в "highest_price_bar_index" и записываем время этого бара с помощью функции "time", которая извлекает время, связанное с i-м баром. Это позволяет нам отслеживать, когда была установлена самая высокая цена.
  • Если "lowest_price_i" ниже, чем записанная "minimum_price", мы обновляем "minimum_price" до этого нового значения. Мы также сохраняем индекс этого бара в "highest_price_bar_index" и записываем время этого бара в "minimum_time" с помощью функции "time".

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

         //--- Log the maximum and minimum prices, along with their respective bar indices and times
         Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time);
         Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);

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

PRICE LEVELS

На рисунке мы видим, что наши максимальные цены находятся на 7-м баре, данные которого из лога составляют 0,6548, что соответствует цене открытия в окне данных. Его время - полночь, как показано на шкале перекрестия времени и даты по оси x. Таким образом, мы можем быть уверены, что у нас есть ежедневные цены, и мы можем использовать их для дальнейшего анализа. Однако нам больше не нужно проводить анализ в течение дня, так как мы уже собрали необходимые данные. Таким образом, мы можем установить наш логический флаг для переменной отслеживания цен равным true и дождаться следующего дня, чтобы снова получить данные о ценах.

         isHaveDailyRange_Prices = true; //--- Set the flag indicating daily range prices have been extracted

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

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE A RECTANGLE                             |
//+------------------------------------------------------------------+
void create_Rectangle(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   //--- Check if the object already exists by finding it on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create a rectangle object using the defined parameters: name, type, and coordinates
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the rectangle (start point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the rectangle (start point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the rectangle (end point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the rectangle (end point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Enable the fill property for the rectangle, making it filled
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);
      
      //--- Set the color for the rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the rectangle to not appear behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);

      //--- Redraw the chart to reflect the new changes
      ChartRedraw(0);
   }
}

Здесь мы создаем функцию типа void - "create_Rectangle", которая будет обрабатывать создание прямоугольного объекта на графике в MetaTrader. Функция принимает шесть параметров: "objName" (имя объекта), "time1" и "price1" (координаты первого угла прямоугольника), "time2" и "price2" (координаты противоположного угла) и "clr" (цвет прямоугольника). В этой функции мы сначала проверяем, существует ли уже объект с заданным именем на графике, используя функцию ObjectFind. Если объект не найден (т.е. он возвращает значение меньше 0), мы переходим к созданию прямоугольника.

Затем мы вызываем функцию ObjectCreate для создания прямоугольного объекта, предоставляя необходимые параметры: идентификатор графика (для текущего графика установлено значение 0), имя объекта, тип объекта (OBJ_RECTANGLE) и координаты (определенные как "time1, price1" и "time2, price2").

Далее мы используем функции ObjectSetInteger и ObjectSetDouble, чтобы задать отдельные свойства прямоугольника:

  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1)" устанавливает время для первого угла (начальной точки) прямоугольника.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1)" устанавливает цену для первого угла (начальной точки) прямоугольника.
  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2)" устанавливает время для второго угла (конечной точки) прямоугольника.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2)" устанавливает цену для второго угла (конечной точки) прямоугольника.

Мы также включаем свойство fill (заливка) для прямоугольника, используя метод OBJPROP_FILL, который делает прямоугольник визуально заполненным на графике, а не просто контуром. После этого мы устанавливаем цвет прямоугольника, используя метод OBJPROP_COLOR, применяя указанный цвет ("clr"), переданный в функцию. Далее прямоугольник настраивается так, чтобы он отображался перед другими объектами, путем отключения свойства OBJPROP_BACK. Наконец, мы вызываем функцию ChartRedraw, чтобы обновить график, гарантируя, что вновь созданный прямоугольник сразу же отобразится на графике. Следующая функция, которую нам нужно определить, предназначена для создания линий на графике, чтобы мы могли использовать их для отображения  диапазона времени начала и окончания.

//+------------------------------------------------------------------+
//|      FUNCTION TO CREATE A TREND LINE                             |
//+------------------------------------------------------------------+
void create_Line(string objName, datetime time1, double price1, datetime time2, double price2, int width, color clr, string text) {
   //--- Check if the line object already exists by its name
   if (ObjectFind(0, objName) < 0) {
      //--- Create a trendline object with the specified parameters
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Set the width for the line
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, width);
      
      //--- Set the color of the trendline
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the trendline to not be behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      //--- Retrieve the current chart scale
      long scale = 0;
      if(!ChartGetInteger(0, CHART_SCALE, 0, scale)) {
         //--- Print an error message if unable to retrieve the chart scale
         Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED");
      }
      //--- Set a default font size based on the chart scale
      int fontsize = 11;
      if (scale == 0) { fontsize = 5; }
      else if (scale == 1) { fontsize = 6; }
      else if (scale == 2) { fontsize = 7; }
      else if (scale == 3) { fontsize = 9; }
      else if (scale == 4) { fontsize = 11; }
      else if (scale == 5) { fontsize = 13; }
      
      //--- Define the description text to appear near the right price
      string txt = " Right Price";
      string objNameDescr = objName + txt;
      
      //--- Create a text object next to the line to display the description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time2, price2);
      
      //--- Set the color for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize);
      
      //--- Anchor the text to the left of the line
      ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT);
      
      //--- Set the text content to display the specified string
      ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + text);
      
      //--- Set the font of the text to "Calibri"
      ObjectSetString(0, objNameDescr, OBJPROP_FONT, "Calibri");
      
      //--- Redraw the chart to reflect the changes
      ChartRedraw(0);
   }
}

Здесь мы создаем другую функцию типа void - "create_Line" и также передаем ей необходимые параметры. Функция принимает восемь параметров: "objName" (имя линейного объекта), "time1" и "price1" (координаты начальной точки), "time2" и "price2" (координаты конечной точки), "width" (толщина линии), "clr" (цвет линии) и "text" (описание, которое будет отображаться рядом с линией тренда). Мы начинаем с проверки того, существует ли линия тренда на графике, используя ObjectFind. Если объект trendline с указанным именем не существует (возвращает значение меньше 0), мы переходим к созданию линии.

Для создания линии тренда мы используем функцию ObjectCreate, которая определяет тип объекта как OBJ_TREND и присваивает начальные ("time1, price1") и конечные ("time2, price2") координаты линии тренда.

Затем мы используем ObjectSetInteger и ObjectSetDouble , чтобы назначить свойства как начальной, так и конечной точкам линии:

  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1)" устанавливает время первой точки.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1)" устанавливает цену первой точки.
  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2)" устанавливает время второй точки.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2)" устанавливает цену второй точки.

Далее мы задаем ширину линии, используя свойство OBJPROP_WIDTH, которое определяет толщину линии, а затем задаем цвет линии. Затем мы следим за тем, чтобы линия отображалась перед другими объектами, установив свойство OBJPROP_BACK на значение false, что означает, что линия тренда не будет отображаться за другими элементами графика.

Чтобы улучшить отображение линии тренда, мы извлекаем текущий масштаб графика, используя ChartGetInteger. Если успешно получим масштаб, используем его для установки размера шрифта для описательного текста, который будет отображаться рядом с линией. Исходя из масштаба диаграммы, мы соответствующим образом настраиваем размер шрифта, по умолчанию он равен 11. Затем определяем описательную метку "Right Price" (Правильная цена), которая будет размещена рядом с линией тренда, и генерируем название объекта для этой метки, добавляя "txt" к исходному названию объекта, формируя "objNameDescr".

Затем мы создаем текстовый объект с помощью функции ObjectCreate, помещая его в конец линии ("time2, price2") и устанавливая различные свойства:

  • "ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr)" устанавливает цвет текста в соответствии с цветом линии тренда.
  • "ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize)" устанавливает размер шрифта на основе ранее рассчитанного значения.
  • "ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT)" закрепляет текст слева от линии.
  • "ObjectSetString(0, objNameDescr, OBJPROP_TEXT, ' ' + text)" устанавливает фактическое текстовое содержимое в значение параметра "text", передаваемого в функцию.
  • "ObjectSetString(0, objNameDescr, OBJPROP_FONT, 'Calibri')" устанавливает шрифт текста на "Calibri" для удобства чтения.

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

         //--- Create visual elements to represent the daily range
         create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); //--- Create a rectangle for the daily range
         create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,3,clrBlack,DoubleToString(maximum_price,_Digits)); //--- Draw upper range line
         create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,3,clrRed,DoubleToString(minimum_price,_Digits));   //--- Draw lower range line

После компиляции кода и запуска программы получим следующие сведения.

FIRST VISUAL PLOT

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

   //--- Get the close price and time of the previous bar
   double barClose = close(1); 
   datetime barTime = time(1);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
   }

Здесь мы проверяем, превышает ли цена закрытия предыдущего бара дневную максимальную цену, что указывает на прорыв верхнего диапазона. Сначала извлекаем цену закрытия и время предыдущего бара с помощью пользовательских функций "close" и "time", сохраняя эти значения в "barClose" и "barTime" соответственно. Это позволяет нам ориентироваться на цену закрытия и время анализируемого бара.

Затем выполняем серию проверок, чтобы подтвердить, произошел ли прорыв. Мы проверяем, превышает ли значение "barClose" значение "maximum_price", чтобы убедиться, что цена закрытия превышает самую высокую цену, зарегистрированную за день. Мы также проверяем, что цены дневного диапазона получены с помощью флага "isHaveDailyRange_Prices", и подтверждаем, что ранее с помощью флага "!isHaveRangeBreak» прорывов не обнаружено. Кроме того, мы гарантируем, что прорыв происходит в пределах допустимого окна прорыва, проверяя, находится ли "barTime" между "validBreakTime_start" и "validBreakTime_end".

Если все условия выполнены, мы регистрируем событие прорыва, выводя сообщение о том, что цена закрытия преодолела верхний диапазон. Затем устанавливаем для параметра "isHaveRangeBreak" значение true, отмечая, что обнаружен прорыв. Наконец, вызываем функцию "drawBreakPoint", чтобы визуально отметить этот прорыв на графике. Функция использует время бара, цену закрытия, размер маркера, цвет и приоритет для отображения визуального представления о прорыве. Ниже представлена логика работы функции, которая аналогична предыдущим функциям.

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE AN ARROW                                |
//+------------------------------------------------------------------+
void drawBreakPoint(string objName, datetime time, double price, int arrCode, color clr, int direction) {
   //--- Check if the arrow object already exists on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create an arrow object with the specified time, price, and arrow code
      ObjectCreate(0, objName, OBJ_ARROW, 0, time, price);
      
      //--- Set the arrow's code (symbol)
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      
      //--- Set the color for the arrow
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the arrow
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 12);
      
      //--- Set the anchor position for the arrow based on the direction
      if (direction > 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      
      //--- Define a text label for the break point
      string txt = " Break";
      string objNameDescr = objName + txt;
      
      //--- Create a text object for the break point description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time, price);
      
      //--- Set the color for the text description
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, 12);
      
      //--- Adjust the text anchor based on the direction of the arrow
      if (direction > 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
   }
   //--- Redraw the chart to reflect the new objects
   ChartRedraw(0);
}

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

   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
   }

После компиляции получаем следующий результат.

Прорыв нижнего уровня

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

Прорыв верхнего уровня

Операция прошла успешно. Мы также видим, что у нас действительно есть прорыв на верхнем уровне, как и ожидалось. Следующее, что нам нужно сделать, - это открыть позиции сразу после появления этих прорывов, и на этом все.

   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*2);
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*2);
   }

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

SELL BREAK TRADE

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


Бэк-тестирование и оптимизация

После завершения реализации следующим важным шагом является тщательное тестирование советника (EA), чтобы оценить его работу и оптимизировать параметры. Эффективное тестирование гарантирует, что стратегия работает должным образом в различных рыночных условиях, сводя к минимуму риск возникновения непредвиденных проблем во время торговли. Здесь мы будем использовать Тестер Стратегий MetaTrader 5 для проведения бэк-тестирования и оптимизации, чтобы найти наилучшие возможные входные значения для нашей стратегии.

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

enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions};

input int r2r = 2;
input int hoursValidity = 5;
input trade_direction direction_of_trade = Default_Trade_Directions;

Здесь мы определяем перечисление и инициализируем некоторые входные переменные, которые будем использовать для управления торговым поведением и параметрами стратегии. Сначала объявляем enum под названием "trade_direction", которое определяет два возможных значения: "Default_Trade_Directions" и "Invert_Trade_Directions". Enum (перечисление) - это определяемый пользователем тип данных в MQL5, позволяющий присваивать имена целочисленным константам, делая код более читаемым и простым в управлении. В этом случае "trade_direction" поможет контролировать, следует ли сделка торговому направлению по умолчанию или направление меняется на противоположное в зависимости от конкретных условий.

Далее определяем три входные переменные, позволяющие пользователю изменять значения непосредственно из настроек советника, при этом не редактируя сам код. Однако они будут более полезны при оптимизации программы. Первая переменная - "r2r", которая по умолчанию имеет значение 2, и мы будем использовать ее для управления соотношением риска и прибыли в стратегии. Ключевое слово input указывает на то, что эта переменная может быть изменена пользователем извне. Второй входной параметр - "hoursValidity", инициализируемый со значением по умолчанию равным 5. Эта переменная будет определять, насколько долго торговые условия для прорыва или сигналы о прорыве остаются действительными, имея в виду - часов.

Наконец, третий входной параметр - это "direction_of_trade", который имеет тип "trade_direction" (определенное нами ранее перечисление). По умолчанию мы устанавливаем значение "Default_Trade_Directions", но пользователь может изменить его на "Invert_Trade_Directions", если надо, чтобы сделка совершалась в противоположном направлении. Эти входные данные обеспечивают гибкость при выборе направления торговли без изменения основной логики советника. Имея это в виду, нам просто нужно заменить соответствующие статические параметры в коде и добавить динамический аспект.

   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Set the end of valid breakout time to 11 AM
   
   if (isNewDay()){
      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day
   }

   //---

   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
      }
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
   }

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

ВХОДНЫЕ ДАННЫЕ ДЛЯ ОПТИМИЗАЦИИ

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

BACKTEST GIF

Операция прошла успешно! Можем сделать вывод, что программа сработала так, как ожидалось. Фрагмент окончательного исходного кода, отвечающий за создание и реализацию стратегии Прорыва дневного диапазона, выглядит следующим образом:
//+------------------------------------------------------------------+
//|                          Daily Range Breakout Expert Advisor.mq5 |
//|      Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                                     https://forexalg0-trader.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://forexalg0-trader.com"
#property description "Daily Range Breakout Expert Advisor"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions};

input int r2r = 2;
input int hoursValidity = 5;
input trade_direction direction_of_trade = Default_Trade_Directions;

double maximum_price = -DBL_MAX;  //--- Initialize the maximum price with the smallest possible value
double minimum_price = DBL_MAX;   //--- Initialize the minimum price with the largest possible value
datetime maximum_time, minimum_time; //--- Declare variables to store the time of the highest and lowest prices
bool isHaveDailyRange_Prices = false; //--- Boolean flag to check if daily range prices are extracted
bool isHaveRangeBreak = false;        //--- Boolean flag to check if a range breakout has occurred

#define RECTANGLE_PREFIX "RANGE RECTANGLE " //--- Prefix for naming range rectangles
#define UPPER_LINE_PREFIX "UPPER LINE "     //--- Prefix for naming upper range line
#define LOWER_LINE_PREFIX "LOWER LINE "     //--- Prefix for naming lower range line

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   //--- Initialization code can be placed here if needed
   
   //---
   return(INIT_SUCCEEDED); //--- Return successful initialization
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   //--- Deinitialization code can be placed here if needed
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   //--- 
   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Set the end of valid breakout time to 11 AM
   
   if (isNewDay()){
      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day
   }
   
   if (isNewBar()){
      //--- If a new bar has been formed, process the data
      datetime currentBarTime = iTime(_Symbol,_Period,0); //--- Get the time of the current bar
      
      if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){
         //--- If it's time to scan and the daily range is not yet extracted
         Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); //--- Log the extraction process
         int total_bars = int((sixAM - midnight)/PeriodSeconds(_Period)) + 1; //--- Calculate total bars between midnight and 6 AM
         Print("Total Bars for scan = ",total_bars); //--- Log the total number of bars for scanning
         int highest_price_bar_index = -1;   //--- Variable to store the bar index of the highest price
         int lowest_price_bar_index = -1;    //--- Variable to store the bar index of the lowest price
   
         for (int i=1; i<=total_bars ; i++){ //--- Loop through all bars within the defined time range
            double open_i = open(i);         //--- Get the opening price of the i-th bar
            double close_i = close(i);       //--- Get the closing price of the i-th bar
            
            double highest_price_i = (open_i > close_i) ? open_i : close_i; //--- Determine the highest price between open and close
            double lowest_price_i = (open_i < close_i) ? open_i : close_i;  //--- Determine the lowest price between open and close
            
            if (highest_price_i > maximum_price){
               //--- If the current highest price is greater than the recorded maximum price
               maximum_price = highest_price_i; //--- Update the maximum price
               highest_price_bar_index = i;     //--- Update the index of the highest price bar
               maximum_time = time(i);          //--- Update the time of the highest price
            }
            if (lowest_price_i < minimum_price){
               //--- If the current lowest price is lower than the recorded minimum price
               minimum_price = lowest_price_i;  //--- Update the minimum price
               lowest_price_bar_index = i;      //--- Update the index of the lowest price bar
               minimum_time = time(i);          //--- Update the time of the lowest price
            }
         }
         //--- Log the maximum and minimum prices, along with their respective bar indices and times
         Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time);
         Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);
         
         //--- Create visual elements to represent the daily range
         create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); //--- Create a rectangle for the daily range
         create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,3,clrBlack,DoubleToString(maximum_price,_Digits)); //--- Draw upper range line
         create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,3,clrRed,DoubleToString(minimum_price,_Digits));   //--- Draw lower range line
         
         isHaveDailyRange_Prices = true; //--- Set the flag indicating daily range prices have been extracted
      }
   }
   
   //--- Get the close price and time of the previous bar
   double barClose = close(1); 
   datetime barTime = time(1);
   
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
      }
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
   }
}

//--- Utility functions to retrieve price and time data for a given bar index
double open(int index){return (iOpen(_Symbol,_Period,index));}   //--- Get the opening price
double high(int index){return (iHigh(_Symbol,_Period,index));}   //--- Get the highest price
double low(int index){return (iLow(_Symbol,_Period,index));}     //--- Get the lowest price
double close(int index){return (iClose(_Symbol,_Period,index));} //--- Get the closing price
datetime time(int index){return (iTime(_Symbol,_Period,index));} //--- Get the time of the bar

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE A RECTANGLE                             |
//+------------------------------------------------------------------+
void create_Rectangle(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   //--- Check if the object already exists by finding it on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create a rectangle object using the defined parameters: name, type, and coordinates
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the rectangle (start point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the rectangle (start point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the rectangle (end point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the rectangle (end point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Enable the fill property for the rectangle, making it filled
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);
      
      //--- Set the color for the rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the rectangle to not appear behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);

      //--- Redraw the chart to reflect the new changes
      ChartRedraw(0);
   }
}
//+------------------------------------------------------------------+
//|      FUNCTION TO CREATE A TREND LINE                             |
//+------------------------------------------------------------------+
void create_Line(string objName, datetime time1, double price1, datetime time2, double price2, int width, color clr, string text) {
   //--- Check if the line object already exists by its name
   if (ObjectFind(0, objName) < 0) {
      //--- Create a trendline object with the specified parameters
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Set the width for the line
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, width);
      
      //--- Set the color of the trendline
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the trendline to not be behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      //--- Retrieve the current chart scale
      long scale = 0;
      if(!ChartGetInteger(0, CHART_SCALE, 0, scale)) {
         //--- Print an error message if unable to retrieve the chart scale
         Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED");
      }
      //--- Set a default font size based on the chart scale
      int fontsize = 11;
      if (scale == 0) { fontsize = 5; }
      else if (scale == 1) { fontsize = 6; }
      else if (scale == 2) { fontsize = 7; }
      else if (scale == 3) { fontsize = 9; }
      else if (scale == 4) { fontsize = 11; }
      else if (scale == 5) { fontsize = 13; }
      
      //--- Define the description text to appear near the right price
      string txt = " Right Price";
      string objNameDescr = objName + txt;
      
      //--- Create a text object next to the line to display the description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time2, price2);
      
      //--- Set the color for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize);
      
      //--- Anchor the text to the left of the line
      ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT);
      
      //--- Set the text content to display the specified string
      ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + text);
      
      //--- Set the font of the text to "Calibri"
      ObjectSetString(0, objNameDescr, OBJPROP_FONT, "Calibri");
      
      //--- Redraw the chart to reflect the changes
      ChartRedraw(0);
   }
}

bool isNewBar() {
   //--- Static variable to hold the previous number of bars
   static int prevBars = 0;
   
   //--- Get the current number of bars on the chart
   int currBars = iBars(_Symbol, _Period);
   
   //--- If the number of bars hasn't changed, return false
   if (prevBars == currBars) return (false);
   
   //--- Update the previous bar count with the current one
   prevBars = currBars;
   
   //--- Return true if a new bar has been formed
   return (true);
}

bool isNewDay() {
   //--- Flag to indicate if a new day has started
   bool newDay = false;
   
   //--- Structure to hold the current date and time
   MqlDateTime Str_DateTime;
   
   //--- Convert the current time to a structured format
   TimeToStruct(TimeCurrent(), Str_DateTime);
   
   //--- Static variable to store the previous day
   static int prevDay = 0;
   
   //--- Get the current day from the structured time
   int currDay = Str_DateTime.day;
   
   //--- If the previous day is the same as the current day, we're still on the same day
   if (prevDay == currDay) {
      newDay = false;
   }
   //--- If the current day differs from the previous one, we have a new day
   else if (prevDay != currDay) {
      //--- Print a message indicating the new day
      Print("WE HAVE A NEW DAY WITH DATE ", currDay);
      
      //--- Update the previous day to the current day
      prevDay = currDay;
      
      //--- Set the flag to true, indicating a new day has started
      newDay = true;
   }
   
   //--- Return whether a new day has started
   return (newDay);
}
//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE AN ARROW                                |
//+------------------------------------------------------------------+
void drawBreakPoint(string objName, datetime time, double price, int arrCode, color clr, int direction) {
   //--- Check if the arrow object already exists on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create an arrow object with the specified time, price, and arrow code
      ObjectCreate(0, objName, OBJ_ARROW, 0, time, price);
      
      //--- Set the arrow's code (symbol)
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      
      //--- Set the color for the arrow
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the arrow
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 12);
      
      //--- Set the anchor position for the arrow based on the direction
      if (direction > 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      
      //--- Define a text label for the break point
      string txt = " Break";
      string objNameDescr = objName + txt;
      
      //--- Create a text object for the break point description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time, price);
      
      //--- Set the color for the text description
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, 12);
      
      //--- Adjust the text anchor based on the direction of the arrow
      if (direction > 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
   }
   //--- Redraw the chart to reflect the new objects
   ChartRedraw(0);
}

Результаты бэк-тестирования:

РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ НА ИСТОРИИ

График бэк-тестирования:

ГРАФИК БЭК-ТЕСТИРОВАНИЯ

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


Заключение

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

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

Отказ от ответственности: Информация, содержащаяся в статье, приведена исключительно в образовательных целях. Она предназначена для того, чтобы дать представление о создании советника (EA) на основе стратегии Прорыв дневного диапазона и должна служить основой для разработки более продвинутых систем с дальнейшей оптимизацией и тестированием. Описанные стратегии и методы не гарантируют каких-либо результатов торговли. Вы используете этот контент на свой страх и риск. Прежде чем применять какое-либо автоматическое торговое решение, проводите тщательное тестирование и учитывайте потенциальные рыночные условия.

Стратегия протестирована с помощью Тестера стратегий MetaTrader 5, что позволило нам оценить ее эффективность и внести коррективы, чтобы она работала еще лучше в наших торговых условиях. Удачного программирования и успешной торговли!

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Stanislav Korotky
Stanislav Korotky | 21 окт. 2024 в 14:59

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

Также я не увидел кода для обработки часовых поясов, потому что в зависимости от смещения GMT на вашем сервере, "утренний флэт", который определяет диапазон для прорыва, должен отслеживаться, начиная не с полуночи 00:00 обязательно, а со смещением.

Allan Munene Mutiiria
Allan Munene Mutiiria | 22 окт. 2024 в 11:03
Stanislav Korotky #:

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

Также я не увидел кода для обработки часовых поясов, потому что в зависимости от смещения GMT на вашем сервере, "утренний флэт", который определяет диапазон для прорыва, должен отслеживаться, начиная не с полуночи 00:00 обязательно, а со смещением.

Спасибо за ответ. Это зависит от используемой стратегии. В нашем случае это Daily Range Breakout, по сути, работающая с полуночи до 6 утра, что, конечно же, может быть скорректировано. Спасибо.

Возможности Мастера MQL5, которые вам нужно знать (Часть 44): Технический индикатор Average True Range (ATR) Возможности Мастера MQL5, которые вам нужно знать (Часть 44): Технический индикатор Average True Range (ATR)
Осциллятор ATR — очень популярный индикатор, используемый в качестве индикатора волатильности, особенно на валютных рынках, где данные об объемах скудны. Как и в случае с предыдущими индикаторами, мы рассмотрим паттерны и поделимся стратегиями и отчетами о тестировании.
От начального до среднего уровня: Объединение (I) От начального до среднего уровня: Объединение (I)
В данной статье мы рассмотрим, что такое объединение. Здесь, с помощью экспериментов, мы проанализируем первые конструкции, в которых можно использовать объединение. Однако то, что будет показано здесь, - лишь основная часть единого набора концепций и информации, которые будут рассмотрены в следующих статьях. Представленные здесь материалы предназначены только для обучения. Ни в коем случае нельзя рассматривать это приложение как окончательное, цели которого будут иные, кроме изучения представленных концепций.
Торгуем опционы без опционов (Часть 1): Основы теории и эмуляция через базовые активы Торгуем опционы без опционов (Часть 1): Основы теории и эмуляция через базовые активы
Статья описывает вариант эмуляции опционов через базовый актив, реализованный на языке программирования MQL5. Сравниваются преимущества и недостатки выбранного подхода с реальными биржевыми опционами на примере срочного рынка ФОРТС московской биржи MOEX и криптобиржи Bybit.
Индикатор CAPM модели на рынке Forex Индикатор CAPM модели на рынке Forex
Адаптация классической модели CAPM для валютного рынка Forex в MQL5. Индикатор рассчитывает ожидаемую доходность и премию за риск на основе исторической волатильности. Показатели возрастают на пиках и впадинах, отражая фундаментальные принципы ценообразования. Практическое применение для контртрендовых и трендовых стратегий с учетом динамики соотношения риска и доходности в реальном времени. Включает математический аппарат и техническую реализацию.