English 日本語
preview
Знакомство с языком MQL5 (Часть 23): Автоматизация торговли на пробое диапазона открытия рынка

Знакомство с языком MQL5 (Часть 23): Автоматизация торговли на пробое диапазона открытия рынка

MetaTrader 5Трейдинг |
345 3
Israel Pelumi Abioye
Israel Pelumi Abioye

Введение

И снова приветствуем вас в Части 23 серии "Знакомство с языком MQL5"! В этой статье я расскажу вам, как автоматизировать стратегию пробоя диапазона открытия (ORB) с помощью языка MQL5. Цель состоит в том, чтобы обучить вас языку MQL5 на основе проектов, в удобной для начинающих манере, а в том, чтобы обещать прибыль или рекламировать какую-либо торговую стратегию. Работа с реальными примерами, такими как приведенные в данной статье, поможет вам развить ваши навыки владения языком MQL5 полезным образом, предоставляя вам опыт практической работы с такими понятиями, как распознавание пробойных диапазонов, исполнение автоматизированных ордеров и программное управление сделками.

В этой статье будет объяснена стратегия пробоя диапазона открытия (ORB), и будет показано, как использовать язык MQL5 для ее автоматизации. Вы научитесь задавать условия торговли, фиксировать диапазон открытия и настраивать свой советник для автоматической торговли на пробоях. Чтобы убедиться, что входы происходят только в течение определенного рыночного окна, мы также рассмотрим, как использовать временную логику для регулирования того, когда разрешено исполнять сделки. К концу статьи вы будете точно знать, как использовать MetaTrader 5, чтобы превратить эту классическую торговую стратегию в полностью автоматизированную торговую систему.


Пробой диапазона открытия (ORB)

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

Первые пять, пятнадцать, тридцать или шестьдесят минут сессии являются типичными вариантами длительности диапазона открытия. Более короткие диапазоны дают больше сигналов, но также создают больше шума, отслеживая наиболее раннюю волатильность. Более длинные диапазоны, хотя они генерируют меньше сигналов и являются более плавными, часто имеют более высокое качество. Выберите диапазон, который соответствует активу и периоду торговли.  Вы можете использовать первые 15 или 30 минут для внутридневных стратегий.

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

Аналогия

Рассмотрим сценарий, в котором рынок открывается ровно в 9:30 утра. Вашим основным фокусом становится свеча на M15, которая начинает формироваться в этот момент. Вы записываете максимум и минимум свечи после ее закрытия, и они становятся вашим диапазоном открытия.

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

Проще говоря, вы определяете границы в первые пятнадцать минут после открытия рынка, а затем следите за пробоем вашего диапазона открытия (ORB), который происходит, когда цена пробивает эти уровни.

Figure 1. ORB


Как работает советник

Перед началом программной реализации вам сначала необходимо понять работу советника, подробно ознакомившись с выполняемой им процедурой. Первый шаг – определить, что рынок открывается в 9:30 утра (по времени сервера). Советник будет внимательно ожидать формирования первой 15-минутной свечи, которая начинает формироваться ровно в 9:30. Советник выявит диапазон открытия, отметив максимум и минимум свечи после ее закрытия и отрисовав линии по ним.

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

Figure 2. Buy Logic

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

Figure 3. Sell Logic


Выявление времени открытия рынка

Следующим шагом после того, как мы получили полное понимание того, как работает советник, является определение времени открытия рынка. В качестве примера, предположим, что рынок открывается в 9:30 по серверному времени. Мы требуем, чтобы советник всегда определял это конкретное время автоматически. Если ваш советник работает на VPS, и вы не хотите вручную обновлять дату каждый день, вам нужен способ изолировать только часть, отвечающую за время дня, поскольку значения времени в языке MQL5 очень точные, так как включают год, месяц, день, час, минуту и секунду. Таким образом, программа сможет автоматически выявлять день, месяц и год каждой новой торговой сессии.

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

Пример:

string open_time_string = "9:30";
datetime open_time;

ulong chart_id = ChartID();
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

   ObjectsDeleteAll(chart_id);

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

   open_time = StringToTime(open_time_string);

   Comment("OPEN TIME: ",open_time);

   ObjectCreate(chart_id,"OPEN TIME",OBJ_VLINE,0,open_time,0);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_COLOR,clrBlue);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_WIDTH,2);

  }

Вывод:

Figure 4. Open Time

Пояснение:

Время открытия рынка сначала сохраняется как простая строка в строковой переменной open_time_string = "9:30";. Это всего лишь представление времени открытия, которое удобочитаемо для людей, и которое вы хотите, чтобы советник распознал. Затем это время будет сохранено в формате, который язык MQL5 может использовать для вычислений и построения графических объектов, в переменной datetime open_time;.

Программа преобразует строку в допустимое значение типа datetime. Хотя тип datetime в MQL5 содержит полную дату и время, время автоматически устанавливается на 9:30 текущего дня, когда используется такая строка. Это гарантирует, что советник сможет рассчитывать время открытия каждый день без явного обновления даты.

Затем советник рисует вертикальную линию на графике в точное время открытия рынка. Эта вертикальная линия служит визуальным маркером начала диапазона открытия.


Выявление максимума и минимума первой 15-минутной свечи

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

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

Пример:
string open_time_string = "9:30";
datetime open_time;

string open_time_bar_close_string = "9:45";
datetime open_time_bar_close;

ulong chart_id = ChartID();

double m15_high[];
double m15_low[];
double m15_close[];
double m15_open[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

   ObjectsDeleteAll(chart_id);

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

   open_time = StringToTime(open_time_string);

   Comment("OPEN TIME: ",open_time);

   ObjectCreate(chart_id,"OPEN TIME",OBJ_VLINE,0,open_time,0);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_COLOR,clrBlue);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_WIDTH,2);

   open_time_bar_close = StringToTime(open_time_bar_close_string);

   if(TimeCurrent() >= open_time_bar_close)
     {

      CopyHigh(_Symbol,PERIOD_M15,open_time,1,m15_high);
      CopyLow(_Symbol,PERIOD_M15,open_time,1,m15_low);
      CopyClose(_Symbol,PERIOD_M15,open_time,1,m15_close);
      CopyOpen(_Symbol,PERIOD_M15,open_time,1,m15_open);

      ObjectCreate(chart_id,"High",OBJ_TREND,0,open_time,m15_high[0],TimeCurrent(),m15_high[0]);
      ObjectSetInteger(chart_id,"High",OBJPROP_COLOR,clrBlue);
      ObjectSetInteger(chart_id,"High",OBJPROP_WIDTH,2);

      ObjectCreate(chart_id,"Low",OBJ_TREND,0,open_time,m15_low[0],TimeCurrent(),m15_low[0]);
      ObjectSetInteger(chart_id,"Low",OBJPROP_COLOR,clrBlue);
      ObjectSetInteger(chart_id,"Low",OBJPROP_WIDTH,2);

     }

  }

Вывод:

Figure 5. Range High and Low

Пояснение:

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

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

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


Использование пятиминутных графиков для обнаружения пробоев диапазона открытия

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

Пример:
double m5_high[];
double m5_low[];
double m5_close[];
double m5_open[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   ArraySetAsSeries(m5_high,true);
   ArraySetAsSeries(m5_low,true);
   ArraySetAsSeries(m5_close,true);
   ArraySetAsSeries(m5_open,true);

//---
   return(INIT_SUCCEEDED);
  }
if(TimeCurrent() >= open_time_bar_close)
  {

   CopyHigh(_Symbol,PERIOD_M15,open_time,1,m15_high);
   CopyLow(_Symbol,PERIOD_M15,open_time,1,m15_low);
   CopyClose(_Symbol,PERIOD_M15,open_time,1,m15_close);
   CopyOpen(_Symbol,PERIOD_M15,open_time,1,m15_open);

   ObjectCreate(chart_id,"High",OBJ_TREND,0,open_time,m15_high[0],TimeCurrent(),m15_high[0]);
   ObjectSetInteger(chart_id,"High",OBJPROP_COLOR,clrBlue);
   ObjectSetInteger(chart_id,"High",OBJPROP_WIDTH,2);

   ObjectCreate(chart_id,"Low",OBJ_TREND,0,open_time,m15_low[0],TimeCurrent(),m15_low[0]);
   ObjectSetInteger(chart_id,"Low",OBJPROP_COLOR,clrBlue);
   ObjectSetInteger(chart_id,"Low",OBJPROP_WIDTH,2);

  }

CopyHigh(_Symbol,PERIOD_M5,1,5,m5_high);
CopyLow(_Symbol,PERIOD_M5,1,5,m5_low);
CopyClose(_Symbol,PERIOD_M5,1,5,m5_close);
CopyOpen(_Symbol,PERIOD_M5,1,5,m5_open);

if(TimeCurrent() >= open_time_bar_close && m5_close[0] > m15_high[0] && m5_close[1] < m15_high[0])
  {

//BUY

  }

if(TimeCurrent() >= open_time_bar_close && m5_close[0] < m15_low[0] && m5_close[1] > m15_low[0])
  {

//SELL

  }

Пояснение:

В этом разделе программы объявлены четыре массива. В этих массивах хранятся цены максимума, цены минимума, цены закрытия и цены открытия свечей на таймфрейме M5. Самая последняя свеча представлена индексом 0, а более ранние свечи представлены более высокими индексами. Каждый массив содержит последовательность ценовых данных, которые могут быть извлечены по индексу.

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

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

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

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

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

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

Figure 6. Gap


Исполнение сделок

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

Пример:
#include <Trade/Trade.mqh>
CTrade trade;
int MagicNumber = 533930;  // Unique Number
input double RRR= 2; // RRR
input double lot_size = 0.2;
double ask_price;
double take_profit;
datetime lastTradeBarTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   ArraySetAsSeries(m5_high,true);
   ArraySetAsSeries(m5_low,true);
   ArraySetAsSeries(m5_close,true);
   ArraySetAsSeries(m5_open,true);

   trade.SetExpertMagicNumber(MagicNumber);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

   ObjectsDeleteAll(chart_id);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   open_time = StringToTime(open_time_string);

   Comment("OPEN TIME: ",open_time);

   ObjectCreate(chart_id,"OPEN TIME",OBJ_VLINE,0,open_time,0);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_COLOR,clrBlue);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_WIDTH,2);

   open_time_bar_close = StringToTime(open_time_bar_close_string);

   if(TimeCurrent() >= open_time_bar_close)
     {

      CopyHigh(_Symbol,PERIOD_M15,open_time,1,m15_high);
      CopyLow(_Symbol,PERIOD_M15,open_time,1,m15_low);
      CopyClose(_Symbol,PERIOD_M15,open_time,1,m15_close);
      CopyOpen(_Symbol,PERIOD_M15,open_time,1,m15_open);

      ObjectCreate(chart_id,"High",OBJ_TREND,0,open_time,m15_high[0],TimeCurrent(),m15_high[0]);
      ObjectSetInteger(chart_id,"High",OBJPROP_COLOR,clrBlue);
      ObjectSetInteger(chart_id,"High",OBJPROP_WIDTH,2);

      ObjectCreate(chart_id,"Low",OBJ_TREND,0,open_time,m15_low[0],TimeCurrent(),m15_low[0]);
      ObjectSetInteger(chart_id,"Low",OBJPROP_COLOR,clrBlue);
      ObjectSetInteger(chart_id,"Low",OBJPROP_WIDTH,2);
     }

   CopyHigh(_Symbol,PERIOD_M5,1,5,m5_high);
   CopyLow(_Symbol,PERIOD_M5,1,5,m5_low);
   CopyClose(_Symbol,PERIOD_M5,1,5,m5_close);
   CopyOpen(_Symbol,PERIOD_M5,1,5,m5_open);

   datetime currentBarTime = iTime(_Symbol, PERIOD_M5, 0);
   ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

   if(TimeCurrent() >= open_time_bar_close && m5_close[0] > m15_high[0] && m5_close[1] < m15_high[0] && m5_close[0] > m5_open[0] && currentBarTime != lastTradeBarTime)
     {

      //BUY
      take_profit = MathAbs(ask_price + ((ask_price - m15_low[0]) * RRR));
      trade.Buy(lot_size,_Symbol,ask_price,m15_low[0],take_profit);
      lastTradeBarTime = currentBarTime;

     }

   if(TimeCurrent() >= open_time_bar_close && m5_close[0] < m15_low[0] && m5_close[1] > m15_low[0] && m5_close[0] < m5_open[0] && currentBarTime != lastTradeBarTime)
     {

      //SELL
      take_profit = MathAbs(ask_price - ((m15_high[0] - ask_price) * RRR));
      trade.Sell(lot_size,_Symbol,ask_price,m15_high[0],take_profit);
      lastTradeBarTime = currentBarTime;
     }
  }

Figure 7. Multiple Breakouts

Пояснение:

Импортируя библиотеку для торговли, программа получает возможность использовать встроенные торговые функции для открытия, изменения и закрытия ордеров. Все действия, касающиеся торговли, затем обрабатываются объектом trade. MagicNumber – это специальный идентификационный номер, который каждый советник использует для обозначения и контроля своих сделок, даже когда на одном счете работают несколько автоматизированных систем.

Lot_size и RRR (соотношение риска и вознаграждения) – это параметры, определяемые пользователям. Lot_size регулирует объем сделки, а RRR устанавливает, во сколько раз тейк-профит должен превышать стоп-лосс. Переменные ask_price, take_profit и lastTradeBarTime помогают в управлении исполнением сделок, храня текущую цену Ask, рассчитанный тейк-профит и время последней сделки, чтобы избежать множественных входов на одной свече.

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

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

Пример:
int open_to_current;
bool isBreakout = false;
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

   open_time = StringToTime(open_time_string);

   ObjectCreate(chart_id,"OPEN TIME",OBJ_VLINE,0,open_time,0);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_COLOR,clrBlue);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_WIDTH,2);

   open_time_bar_close = StringToTime(open_time_bar_close_string);

   if(TimeCurrent() >= open_time_bar_close)
     {

      CopyHigh(_Symbol,PERIOD_M15,open_time,1,m15_high);
      CopyLow(_Symbol,PERIOD_M15,open_time,1,m15_low);
      CopyClose(_Symbol,PERIOD_M15,open_time,1,m15_close);
      CopyOpen(_Symbol,PERIOD_M15,open_time,1,m15_open);

      ObjectCreate(chart_id,"High",OBJ_TREND,0,open_time,m15_high[0],TimeCurrent(),m15_high[0]);
      ObjectSetInteger(chart_id,"High",OBJPROP_COLOR,clrBlue);
      ObjectSetInteger(chart_id,"High",OBJPROP_WIDTH,2);

      ObjectCreate(chart_id,"Low",OBJ_TREND,0,open_time,m15_low[0],TimeCurrent(),m15_low[0]);
      ObjectSetInteger(chart_id,"Low",OBJPROP_COLOR,clrBlue);
      ObjectSetInteger(chart_id,"Low",OBJPROP_WIDTH,2);

      open_to_current = Bars(_Symbol,PERIOD_M5,open_time_bar_close,TimeCurrent());

      CopyHigh(_Symbol,PERIOD_M5,1,open_to_current,m5_high);
      CopyLow(_Symbol,PERIOD_M5,1,open_to_current,m5_low);
      CopyClose(_Symbol,PERIOD_M5,1,open_to_current,m5_close);
      CopyOpen(_Symbol,PERIOD_M5,1,open_to_current,m5_open);

     }

   datetime currentBarTime = iTime(_Symbol, PERIOD_M5, 0);
   ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

   if(TimeCurrent() >= open_time_bar_close && m5_close[0] > m15_high[0] && m5_close[1] < m15_high[0] && m5_close[0] > m5_open[0] && currentBarTime != lastTradeBarTime && isBreakout == false)
     {

      //BUY
      take_profit = MathAbs(ask_price + ((ask_price - m15_low[0]) * RRR));
      trade.Buy(lot_size,_Symbol,ask_price,m15_low[0],take_profit);
      lastTradeBarTime = currentBarTime;

     }

   if(TimeCurrent() >= open_time_bar_close && m5_close[0] < m15_low[0] && m5_close[1] > m15_low[0] && m5_close[0] < m5_open[0] && currentBarTime != lastTradeBarTime && isBreakout == false)
     {

      //SELL
      take_profit = MathAbs(ask_price - ((m15_high[0] - ask_price) * RRR));
      trade.Sell(lot_size,_Symbol,ask_price,m15_high[0],take_profit);
      lastTradeBarTime = currentBarTime;

     }

   if(TimeCurrent() >= open_time_bar_close)
     {
      for(int i = 0; i < open_to_current; i++)
        {
         if(i + 1  < open_to_current)
           {
            if((m5_close[i] > m15_high[0] && m5_close[i + 1] < m15_high[0]) || (m5_close[i] < m15_low[0] && m5_close[i + 1] > m15_low[0]))
              {
               isBreakout = true;
               break;
              }
           }
        }
     }

   if(TimeCurrent() < open_time)
     {
      isBreakout = false;
     }

   Comment(isBreakout);

  }

Вывод:

Figure 8. Single Breakout

Пояснение:

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

Чтобы советник исполнял только одну сделку за сессию, используется переменная isBreakout (как флаг). Когда этому флагу вначале присвоено значение false, система может исполнить пробойную сделку. Когда выявляется действительный пробой, будь то бычий или медвежий, код внутри цикла присваивает переменной isBreakout значение true, что предотвращает любые дальнейшие сделки. Это помогает соблюдать требование о том, что советник должен каждый день выполнять максимум одну пробойную сделку.

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

Наконец, раздел if(TimeCurrent() < open_time) сбрасывает isBreakout к значению false перед началом нового торгового дня, так что советник будет готов обнаружить пробой следующего дня и торговать по нему.

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

Пример:

#include <Trade/Trade.mqh>
CTrade trade;
int MagicNumber = 533930;  // Unique Number
input double RRR= 2; // RRR
input double lot_size = 0.2;
input int pos_num = 2; // Number of Positions to Open 
if(TimeCurrent() >= open_time_bar_close && TimeCurrent() <= close_time && m5_close[0] > m15_high[0] && m5_close[1] < m15_high[0] && m5_close[0] > m5_open[0] && currentBarTime != lastTradeBarTime && isBreakout == false)
  {

//BUY
   take_profit = MathAbs(ask_price + ((ask_price - m15_low[0]) * RRR));

   for(int i = 0; i < pos_num; i++)  // open 3 trades
     {
      trade.Buy(lot_size,_Symbol,ask_price,m15_low[0],take_profit);
     }
   lastTradeBarTime = currentBarTime;
  }

if(TimeCurrent() >= open_time_bar_close && m5_close[0] < m15_low[0] && m5_close[1] > m15_low[0] && m5_close[0] < m5_open[0] && currentBarTime != lastTradeBarTime && isBreakout == false)
  {

//SELL
   take_profit = MathAbs(ask_price - ((m15_high[0] - ask_price) * RRR));

   for(int i = 0; i < pos_num; i++)  // open 3 trades
     {
      trade.Sell(lot_size,_Symbol,ask_price,m15_high[0],take_profit);
     }

   lastTradeBarTime = currentBarTime;
  }

if(TimeCurrent() >= open_time_bar_close)
  {
   for(int i = 0; i < open_to_current; i++)
     {
      if(i + 1  < open_to_current)
        {
         if((m5_close[i] > m15_high[0] && m5_close[i + 1] < m15_high[0]) || (m5_close[i] < m15_low[0] && m5_close[i + 1] > m15_low[0]))
           {
            isBreakout = true;
            break;
           }
        }
     }
  }

Пояснение:

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

Программа использует цикл для открытия нескольких сделок в соответствии с указанным пользователем числом. Цикл повторяется указанное количество раз (в зависимости от входного значения), открывая новую сделку с идентичными критериями входа, стоп-лоссом и тейк-профитом. Например, если переменная имеет значение 2, то после подтверждения действительного пробоя программа исполняет две сделки подряд.

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

 Пример:

open_time = StringToTime(open_time_string);
close_time = StringToTime(close_time_string);
ObjectCreate(chart_id,"OPEN TIME",OBJ_VLINE,0,open_time,0);
ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_COLOR,clrBlue);
ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_STYLE,STYLE_DASH);
ObjectSetInteger(chart_id,"OPEN TIME",OBJPROP_WIDTH,2);

ObjectCreate(chart_id,"CLOSE TIME",OBJ_VLINE,0,close_time,0);
ObjectSetInteger(chart_id,"CLOSE TIME",OBJPROP_COLOR,clrRed);
ObjectSetInteger(chart_id,"CLOSE TIME",OBJPROP_STYLE,STYLE_DASH);
ObjectSetInteger(chart_id,"CLOSE TIME",OBJPROP_WIDTH,2);
if(TimeCurrent() >= open_time_bar_close && TimeCurrent() <= close_time && m5_close[0] > m15_high[0] && m5_close[1] < m15_high[0] && m5_close[0] > m5_open[0] && currentBarTime != lastTradeBarTime && isBreakout == false)
  {

//BUY
   take_profit = MathAbs(ask_price + ((ask_price - m15_low[0]) * RRR));

   for(int i = 0; i < pos_num; i++)  // open 3 trades
     {
      trade.Buy(lot_size,_Symbol,ask_price,m15_low[0],take_profit);
     }
   lastTradeBarTime = currentBarTime;
  }

if(TimeCurrent() >= open_time_bar_close && TimeCurrent() <= close_time && m5_close[0] < m15_low[0] && m5_close[1] > m15_low[0] && m5_close[0] < m5_open[0] && currentBarTime != lastTradeBarTime && isBreakout == false)
  {

//SELL
   take_profit = MathAbs(ask_price - ((m15_high[0] - ask_price) * RRR));

   for(int i = 0; i < pos_num; i++)  // open 3 trades
     {
      trade.Sell(lot_size,_Symbol,ask_price,m15_high[0],take_profit);
     }

   lastTradeBarTime = currentBarTime;
  }

if(TimeCurrent() >= open_time_bar_close)
  {
   for(int i = 0; i < open_to_current; i++)
     {
      if(i + 1  < open_to_current)
        {
         if((m5_close[i] > m15_high[0] && m5_close[i + 1] < m15_high[0]) || (m5_close[i] < m15_low[0] && m5_close[i + 1] > m15_low[0]))
           {
            isBreakout = true;
            break;
           }
        }
     }
  }

if(TimeCurrent() < open_time)
  {
   isBreakout = false;
  }

// Comment(isBreakout);

for(int i = 0; i < PositionsTotal(); i++)
  {
   ulong ticket = PositionGetTicket(i);

   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber  && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
     {

      if(TimeCurrent() >= close_time)
        {
         // Close the position
         trade.PositionClose(ticket);
        }
     }
  }

Вывод:

Figure 9. Open and Close Time

Пояснение:

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

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

Примечание

Стратегия в этой статье полностью проектно-ориентированная и предназначена для обучения читателей языку MQL5 через практическое применение в реальном мире. Этот метод не гарантирует получение прибыли в реальной торговле.

 

Заключение

В этой статье мы рассмотрели автоматизацию метода пробоя диапазона открытия (ORB) с помощью языка MQL5. Мы создали советник, который распознает пробои между несколькими таймфреймами и автоматически совершает сделки в соответствии с установленными указаниями. Кроме того, мы разработали специальное торговое окно, чтобы прекращать сделки после 15:00 по серверному времени, и внедрили ограничения на торговлю, чтобы ограничить количество входов на пробой. Чтобы обеспечить дисциплинированное исполнение сделок и эффективное управление рисками, советник также закрывает все открытые позиции после завершения торгового окна. Этот методичный подход позволяет создавать более сложные и надежные автоматизированные торговые системы.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Mustafa Nail Sertoglu
Mustafa Nail Sertoglu | 15 окт. 2025 в 14:07
Спасибо, что поделились своими торговыми идеями ORB, но, пожалуйста, проверьте условия продажи/покупки, кажется, что они обратны или противоречат вашим определениям (я также проверю их еще раз). В комментариях говорится о ПРОДАЖЕ, но над ORB?
Mustafa Nail Sertoglu
Mustafa Nail Sertoglu | 15 окт. 2025 в 14:16
Mustafa Nail Sertoglu #:
Спасибо, что поделились своими торговыми идеями ORB, но, пожалуйста, проверьте ваши условия SELL / BUY, кажется, что они REVERSE или противоречат вашим определениям (я также проверю это снова)
может быть сигнал ПОКУПКИ "НЕ ВЫПОЛНЕН ВСЕМИ УСЛОВИЯМИ" даже цена спроса/предложения xauusd - ПОКУПАТЬ через ORB выше верхней линии ORB?
Israel Pelumi Abioye
Israel Pelumi Abioye | 15 окт. 2025 в 14:31
Mustafa Nail Sertoglu #:
Спасибо, что поделились своими торговыми идеями ORB, но, пожалуйста, проверьте условия SELL / BUY, кажется, что они REVERSE или противоречат вашим определениям (я также проверю это снова).
Здравствуйте, спасибо за добрые слова. Условия полностью соответствуют условиям, указанным в статье, никаких ошибок нет.
Машинное обучение и Data Science (Часть 34): Разложение временных рядов, раскрываем саму суть фондового рынка Машинное обучение и Data Science (Часть 34): Разложение временных рядов, раскрываем саму суть фондового рынка
В мире, переполненном шумными и непредсказуемыми данными, выявление значимых закономерностей может быть непростой задачей. В этой статье мы рассмотрим сезонное разложение (seasonal decomposition) — мощный аналитический метод, который помогает разделить данные на ключевые компоненты: тренд, сезонные закономерности и шум. Разбив данные на такие составляющие, мы можем выявить скрытые закономерности и работать с более чистой и понятной информацией.
Нейросети в трейдинге: Разностное моделирование рыночной микроструктуры (Окончание) Нейросети в трейдинге: Разностное моделирование рыночной микроструктуры (Окончание)
В статье подробно разбирается практическая реализация идей фреймворка EDCFlow средствами MQL5 и их проверка на реальных исторических данных. Показано, как нейросетевая модель формирует внутреннее представление рыночной среды, работает с корреляциями признаков и принимает торговые решения без ручных правил. Результаты тестирования раскрывают не только потенциал подхода, но и его слабые места, честно обозначая границы применимости и направления дальнейшего развития.
Нейросети в трейдинге: Пространственно-управляемая агрегация рыночных событий (STFlow) Нейросети в трейдинге: Пространственно-управляемая агрегация рыночных событий (STFlow)
Статья знакомит с фреймворком STFlow, который способен формировать устойчивое совместное представление текущего состояния рынка и динамики последних событий, обеспечивая высокую чувствительность к микроимпульсам при сохранении стабильности обработки. Реализован базовый модуль ICE, который аккумулирует потоки цены и событий, создавая надёжный фундамент для дальнейшей агрегации и анализа.
От новичка до эксперта: Периоды на рынке Форекс От новичка до эксперта: Периоды на рынке Форекс
Каждый рыночный период имеет начало и конец, при каждом закрытии цена определяет его настроение — так же, как и при любой свечной сессии. Понимание этих ориентиров позволяет нам оценить преобладающее настроение рынка, определяя, какие силы контролируют ситуацию - бычьи или медвежьи. В настоящем обсуждении мы делаем важный шаг вперед, разрабатывая новую функцию в Market Periods Synchronizer, которая визуализирует сессии рынка Форекс для помощи в принятии более обоснованных торговых решений. Этот инструмент может быть особенно эффективным для определения в режиме реального времени, какая сторона — быки или медведи — доминирует на сессии. Давайте исследуем эту концепцию и раскроем те идеи, которые она дает.