English Deutsch 日本語
preview
Знакомство с языком MQL5 (Часть 17): Создание советников для разворотов тренда

Знакомство с языком MQL5 (Часть 17): Создание советников для разворотов тренда

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

Введение

И снова приветствуем вас в Части 17 серии "Знакомство с языком MQL5"! В этой части мы продолжим развивать все, что мы изучили до сих пор, используя наш фирменный проектный подход, чтобы помочь вам улучшить свои навыки в языке MQL5 через примеры из реальной жизни. В Части 16 мы сосредоточились на паттерне "голова и плечи": создали советник, который мог бы автоматически его обнаруживать, размещать сделки и даже визуализировать его на графике. Это был отличный способ научиться работать с графическими паттернами на языке MQL5.

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

В этой статье вы узнаете:

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

1. Интерпретация трендовых линий

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

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

1.1. Типы трендовых линий

1.1.1. Восходящая трендовая линия

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

Figure 1. Ascending Trend Line

1.1.2. Нисходящая трендовая линия

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

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

Figure 2 Descending Trend Line

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

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


2. Настройка проекта

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

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

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

2.1.1. Восходящая трендовая линия

2.1.1.1. Логика для покупки

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

Figure 3. Ascending Trend Line Buy Logic

2.1.1.2. Логика для продажи

  • Советник обнаружит два минимума свинга и проведет восходящую трендовую линию через них.
  • Он непрерывно отслеживает цену на предмет медвежьего пробоя ниже этой восходящей трендовой линии.
  • Как только цена пробьет трендовую линию вниз, советник начнет ждать ретеста – ситуации, когда цена возвращается к трендовой линии, касаясь ее снизу.
  • Если сразу после ретеста формируется медвежья свеча, и ее минимум находится ниже трендовой линии, советник считает это действительным пробоем и подтверждением.
  • После подтверждения медвежьего ретеста советник размещает ордер на продажу.
  • В качестве SL пользователь может указать некоторое расстояние в пунктах выше цены входа либо максимум свечи ретеста.
  • Пользователь также может указать TP как некоторое расстояние в пунктах ниже цены входа.

Figure 4. Ascending Trend Line Sell Logic

2.1.2. Нисходящая трендовая линия

2.1.2.1. Логика для покупки

  • Советник обнаружит два максимума свинга и проведет трендовую линию через них.
  • Он непрерывно отслеживает цену на предмет бычьего пробоя выше этой нисходящей трендовой линии.
  • Как только цена пробьет трендовую линию вверх, советник начнет ждать ретеста – ситуации, когда цена возвращается к пробитой трендовой линии, касаясь ее или приближаясь к ней сверху.
  • Если сразу после ретеста формируется бычья свеча, и ее максимум находится ниже трендовой линии, советник считает это действительным пробоем и подтверждением.
  • После подтверждения бычьего ретеста советник размещает ордер на покупку.
  • В качестве стоп-лосса (SL) пользователь может указать некоторое расстояние в пунктах ниже цены входа либо минимум свечи ретеста.
  • Пользователь также может указать тейк-профит (TP) как некоторое расстояние в пунктах выше цены входа.

Figure 5. Descending Trend Line Buy Logic

2.1.2.2. Логика для продажи

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

Figure 6.  Descending Trend Line Sell Logic

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


3. Выявление восходящей и нисходящей трендовой линии

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

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

3.1. Получение свечных данных

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

Пример:
// Timeframe to use for retrieving candlestick data (default is the current chart timeframe)
input ENUM_TIMEFRAMES time_frame = PERIOD_CURRENT;

// Number of past bars (candlesticks) to check
int bars_check = 500;

// Arrays to store candlestick data
double close_price[];   // Stores close prices
double open_price[];    // Stores open prices
double low_price[];     // Stores low prices
double high_price[];    // Stores high prices
datetime time_price[];  // Stores time data for each candle

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Set arrays as series so the newest bar is index 0 ( start from the latest bar)
   ArraySetAsSeries(close_price, true);
   ArraySetAsSeries(open_price, true);
   ArraySetAsSeries(low_price, true);
   ArraySetAsSeries(high_price, true);
   ArraySetAsSeries(time_price, true);

   return(INIT_SUCCEEDED);  // Signal that the EA initialized successfully
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Copy the latest candlestick data into the arrays
   CopyOpen(_Symbol, time_frame, 1, bars_check, open_price);     // Open prices
   CopyClose(_Symbol, time_frame, 1, bars_check, close_price);   // Close prices
   CopyLow(_Symbol, time_frame, 1, bars_check, low_price);       // Low prices
   CopyHigh(_Symbol, time_frame, 1, bars_check, high_price);     // High prices
   CopyTime(_Symbol, time_frame, 1, bars_check, time_price);     // Candle times

  }

Пояснение:

Чтобы создать советник, который реагирует на графические паттерны, необходимо собрать исторические свечные данные. Код хранит эту информацию в пяти массивах: open, close, high, low и time. Эти массивы служат основой для построения трендовой линии и обнаружения точек свинга. При значении по умолчанию PERIOD_CURRENT советник использует таймфрейм текущего графика. Пользователи могут выбрать таймфрейм, на котором должен работать советник (например, M1, H1 или D1) с помощью входной переменной time_frame. Переменная bars_check, которая в нашем случае имеет значение 500, определяет, сколько предыдущих свечей должно быть рассмотрено. Таким образом, игнорируя свечу, которая формируется в текущий момент, советник извлечет и проанализирует последние 500 завершенных свечей.

Каждый из этих массивов настраивается с помощью функции ArraySetAsSeries(..., true); внутри функции OnInit(). Поскольку она меняет порядок массива, этот шаг имеет решающее значение, потому что он делает индекс 0 связанным с самой последней завершенной свечой, индекс 1 – со свечой, которая была до нее, и так далее. Начиная с самой последней ценовой активности и двигаясь назад, этот метод индексирования соответствует тому, как трейдеры интуитивно изучают графики. 

Метод OnTick() выполняет основную часть работы по извлечению данных. Эта функция активируется на каждом тике или всякий раз, когда происходит изменение цены на рынке. С ее помощью советник извлекает свечные данные, помещая их в соответствующие массивы с помощью функций CopyOpen, CopyClose, CopyHigh, CopyLow и CopyTime. В то время как первая опция гарантирует, что советник будет избегать все еще формирующейся свечи (поскольку она может быстро измениться и не является надежной для обнаружения паттернов), вторым аргументом является выбранный таймфрейм. Полученная информация относится к последним bars_check завершенным свечам.

3.2. Выявление восходящей трендовой линии

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

Пример:

// Timeframe to use for retrieving candlestick data (default is the current chart timeframe)
input ENUM_TIMEFRAMES time_frame = PERIOD_CURRENT;
// Input to enable or disable drawing of the ascending trend line (true = allow drawing)
input bool allow_uptrend = true;
// Number of candles to look back when identifying swing lows for drawing the trend line
input int LookbackBars = 5;

// Number of past bars (candlesticks) to check
int bars_check = 500;

// Arrays to store candlestick data
double close_price[];   // Stores close prices
double open_price[];    // Stores open prices
double low_price[];     // Stores low prices
double high_price[];    // Stores high prices
datetime time_price[];  // Stores time data for each candle

double first_low;           // Price value of the first identified swing low
datetime first_low_time;    // Time when the first swing low occurred

double second_low;          // Price value of the second identified swing low
datetime second_low_time;   // Time when the second swing low occurred

string up_trend = "Up Trend";  // Label used to name the ascending trend line object on the chart

long chart_id = ChartID();     // Stores the current chart ID for referencing during object creation or manipulation

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Set arrays as series so the newest bar is index 0 ( start from the latest bar)
   ArraySetAsSeries(close_price, true);
   ArraySetAsSeries(open_price, true);
   ArraySetAsSeries(low_price, true);
   ArraySetAsSeries(high_price, true);
   ArraySetAsSeries(time_price, true);

   return(INIT_SUCCEEDED);  // Signal that the EA initialized successfully
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

   ObjectsDeleteAll(chart_id);

  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Copy the latest candlestick data into the arrays
   CopyOpen(_Symbol, time_frame, 1, bars_check, open_price);     // Open prices
   CopyClose(_Symbol, time_frame, 1, bars_check, close_price);   // Close prices
   CopyLow(_Symbol, time_frame, 1, bars_check, low_price);       // Low prices
   CopyHigh(_Symbol, time_frame, 1, bars_check, high_price);     // High prices
   CopyTime(_Symbol, time_frame, 1, bars_check, time_price);     // Candle times

// If the user allows drawing of ascending trend line
   if(allow_uptrend)
     {
      // First loop: Find the most recent swing low (first low)
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check if current point is a swing low
         if(IsSwingLow(low_price, i, LookbackBars))
           {
            // Store price and time of the first (latest) swing low
            first_low = low_price[i];
            first_low_time = time_price[i];
            break;  // Exit loop after finding the first swing low
           }
        }

      // Second loop: Find an earlier swing low that is lower than the first low
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check for earlier swing low that is lower and occurs before the first low
         if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
           {
            // Store price and time of the second (older) swing low
            second_low = low_price[i];
            second_low_time = time_price[i];
            break;  // Exit loop after finding the second swing low
           }
        }

      // Create an ascending trend line from the second low to the first low
      ObjectCreate(chart_id, up_trend, OBJ_TREND, 0, second_low_time, second_low, first_low_time, first_low);
      ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);  // Temporarily hide line on all timeframes

      // If the swing structure is valid (i.e., second low is lower than first)
      if(first_low > second_low && second_low > 0)
        {
         // Extend the trend line to the right
         ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);

         // Show the trend line on all timeframes
         ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

         // Set visual properties: color and thickness
         ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
         ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, 3);
        }
     }

  }

//+------------------------------------------------------------------+
//| FUNCTION FOR LOWS                                                |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low[], int index, int lookback)
  {

   for(int i = 1; i <= lookback; i++)
     {

      if(low[index] > low[index - i] || low[index] > low[index + i])
         return false;
     }
   return true;
  }

Вывод:

Figure 7. Identifying Ascending Trend Line

Пояснение:

Задачей функции IsSwingLow является нахождение минимумов свинга в ценовом ряде. Она принимает на вход три аргумента: массив минимумов (low[]), определенный индекс внутри этого массива (index) и значение интервала проверки lookback, которое указывает, сколько баров слева и справа следует проверить. Цикл for внутри метода выполняется от 1 до lookback. На каждой итерации в нем сравниваются минимум на текущем index с минимумами на index + i (справа) и index - i (слева).

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

Логика трендовой линии управляется двумя основными входными переменными. Первая переменная allow_uptrend представляет собой булево значение, которое позволяет пользователям выбирать, рисовать ли трендовую линию в восходящем порядке. Если у этой переменной установлено значение true, код попытается найти два минимума свинга и построить соединяющую их трендовую линию. Вторая входная переменная LookbackBars указывает, сколько свечей следует рассмотреть слева и справа от точки , чтобы определить, является ли она минимумом свинга. Функция IsSwingLow использует то же значение интервала проверки. Требование к минимуму свинга становится более чувствительным при более низком значении и более строгим при более высоком значении (требуя более резкого падения).

Ценовые значения двух самых последних минимумов свинга, найденных в свечных данных, сохранятся в переменных first_low и second_low. Значения времени, когда произошли такие минимумы свинга, сохранятся в переменных first_low_time и second_low_time, соответственно. Затем на основе этих пар "цена-время" будет проведена восходящая трендовая линия. ID текущего графика сохранится в переменной chart_id, а имя объекта трендовой линии (простая метка "Up Trend") сохранится в переменной up_trend. При создании или изменении графических объектов, таких как трендовые линии, требуется идентификатор графика для определения соответствующего контекста.

Советник начинает с поиска двух действительных минимумов свинга, когда allow_uptrend имеет значение true. Чтобы предотвратить доступ к элементам массива за пределами границ, первый цикл for проходит по ценовым данным от LookbackBars до bars_check - LookbackBars. Чтобы уточнить, является ли каждая из точек минимумом свинга, используется функция IsSwingLow. После определения последнего действительного минимума свинга, она записывает его цену и время в переменные first_low и first_low_time перед завершением цикла.

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

Затем код использует функцию ObjectCreate для построения восходящей трендовой линии после выявления обоих минимумов свинга. Используя их соответствующие временные метки, эта функция соединяет second_low и first_low на графике. Чтобы предотвратить слишком раннее проведение линии, она сначала скрывается на всех таймфреймах с помощью OBJ_NO_PERIODS. После подтверждения того, что структура корректная (first_low > second_low и second_low > 0), используется OBJPROP_RAY_RIGHT для продления линии вправо, что приводит к ее проекции в будущее. Для отображения линии на всех таймфреймах также используется OBJ_ALL_PERIODS.

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

3.3. Выявление нисходящей трендовой линии

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

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

Пример:
// Timeframe to use for retrieving candlestick data (default is the current chart timeframe)
input ENUM_TIMEFRAMES time_frame = PERIOD_CURRENT;
// Input to enable or disable drawing of the ascending trend line (true = allow drawing)
input bool allow_uptrend = true;
// Number of candles to look back when identifying swing lows for drawing the trend line
input int LookbackBars = 5;
// Input to enable or disable drawing of the descebding trend line (true = allow drawing)
input bool allow_downtrend = true;

// Number of past bars (candlesticks) to check
int bars_check = 500;

// Arrays to store candlestick data
double close_price[];   // Stores close prices
double open_price[];    // Stores open prices
double low_price[];     // Stores low prices
double high_price[];    // Stores high prices
datetime time_price[];  // Stores time data for each candle

double first_low;           // Price value of the first identified swing low
datetime first_low_time;    // Time when the first swing low occurred
double second_low;          // Price value of the second identified swing low
datetime second_low_time;   // Time when the second swing low occurred
string up_trend = "Up Trend";  // Label used to name the ascending trend line object on the chart
long chart_id = ChartID();     // Stores the current chart ID for referencing during object creation or manipulation

double first_high;          // Price value of the first identified swing high (latest high)
datetime first_high_time;   // Time when the first swing high occurred
double second_high;         // Price value of the second identified swing high (older high)
datetime second_high_time;  // Time when the second swing high occurred

string down_trend = "Down Trend";  // Label used to name the descending trend line object on the chart

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Set arrays as series so the newest bar is index 0 ( start from the latest bar)
   ArraySetAsSeries(close_price, true);
   ArraySetAsSeries(open_price, true);
   ArraySetAsSeries(low_price, true);
   ArraySetAsSeries(high_price, true);
   ArraySetAsSeries(time_price, true);

   return(INIT_SUCCEEDED);  // Signal that the EA initialized successfully
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

   ObjectsDeleteAll(chart_id);

  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Copy the latest candlestick data into the arrays
   CopyOpen(_Symbol, time_frame, 1, bars_check, open_price);     // Open prices
   CopyClose(_Symbol, time_frame, 1, bars_check, close_price);   // Close prices
   CopyLow(_Symbol, time_frame, 1, bars_check, low_price);       // Low prices
   CopyHigh(_Symbol, time_frame, 1, bars_check, high_price);     // High prices
   CopyTime(_Symbol, time_frame, 1, bars_check, time_price);     // Candle times

// If the user allows drawing of ascending trend line
   if(allow_uptrend)
     {
      // First loop: Find the most recent swing low (first low)
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check if current point is a swing low
         if(IsSwingLow(low_price, i, LookbackBars))
           {
            // Store price and time of the first (latest) swing low
            first_low = low_price[i];
            first_low_time = time_price[i];
            break;  // Exit loop after finding the first swing low
           }
        }

      // Second loop: Find an earlier swing low that is lower than the first low
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check for earlier swing low that is lower and occurs before the first low
         if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
           {
            // Store price and time of the second (older) swing low
            second_low = low_price[i];
            second_low_time = time_price[i];
            break;  // Exit loop after finding the second swing low
           }
        }

      // Create an ascending trend line from the second low to the first low
      ObjectCreate(chart_id, up_trend, OBJ_TREND, 0, second_low_time, second_low, first_low_time, first_low);
      ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);  // Temporarily hide line on all timeframes

      // If the swing structure is valid (i.e., second low is lower than first)
      if(first_low > second_low && second_low > 0)
        {
         // Extend the trend line to the right
         ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);

         // Show the trend line on all timeframes
         ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

         // Set visual properties: color and thickness
         ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
         ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, 3);
        }
     }

//
// Only proceed if drawing descending trend lines is enabled
   if(allow_downtrend)
     {
      // First loop: Find the most recent swing high (first high)
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check if the current bar is a swing high
         if(IsSwingHigh(high_price, i, LookbackBars))
           {
            // Store the price and time of this latest swing high
            first_high = high_price[i];
            first_high_time = time_price[i];

            break;  // Exit loop once the first swing high is found
           }
        }

      // Second loop: Find an earlier swing high that is higher than the first high and occurred before it
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check for earlier swing high that is higher and happened before the first one
         if(IsSwingHigh(high_price, i, LookbackBars) && high_price[i] > first_high && time_price[i] < first_high_time)
           {
            // Store the price and time of this older swing high
            second_high = high_price[i];
            second_high_time = time_price[i];

            break;  // Exit loop once the second swing high is found
           }
        }

      // Create a trend line object from the second swing high to the first swing high
      ObjectCreate(chart_id, down_trend, OBJ_TREND, 0, second_high_time, second_high, first_high_time, first_high);

      // Initially hide the trend line across all timeframes to avoid partial drawing
      ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);

      // Validate the swing structure:
      // The older swing high should be higher than the later swing high to confirm a descending trend line
      if(first_high < second_high && second_high > 0)
        {
         // Extend the trend line indefinitely to the right for better visual guidance
         ObjectSetInteger(chart_id, down_trend, OBJPROP_RAY_RIGHT, true);

         // Make the trend line visible on all chart timeframes
         ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

         // Set the trend line color to dark green for clear distinction
         ObjectSetInteger(chart_id, down_trend, OBJPROP_COLOR, clrDarkGreen);

         // Set the thickness of the trend line to 3 pixels for better visibility
         ObjectSetInteger(chart_id, down_trend, OBJPROP_WIDTH, 3);
        }
     }
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR LOWS                                                |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low[], int index, int lookback)
  {

   for(int i = 1; i <= lookback; i++)
     {

      if(low[index] > low[index - i] || low[index] > low[index + i])
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR HIGHS                                               |
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high[], int index, int lookback)
  {

   for(int i = 1; i <= lookback; i++)
     {
      if(high[index] < high[index - i] || high[index] < high[index + i])
         return false;
     }
   return true;
  }

Вывод:

Figure 8. Identifying Descending Trend Line

Пояснение:

Сравнивая максимальную цену бара с максимумами соседних баров в рамках заранее определенного периода, функция IsSwingHigh определяет, является ли текущий бар максимумом свинга. Бар возвращает true, подтверждая локальный пик, если его максимум больше, чем у любого из таких соседей; в противном случае он возвращает false. Можно включить или отключить отрисовку нисходящей трендовой линии на графике с помощью входной переменной allow_downtrend.

Код изначально ищет последний максимум свинга (first_high) и записывает его цену и время, чтобы определить нисходящую трендовую линию. Затем он ищет более ранний максимум свинга (second_high), который предшествовал первому и был выше. Трендовая линия, которая соединяет эти два места, называется "Down Trend." Нисходящая трендовая линия становится очевидной, когда недавний максимум свинга был ниже предшествующего максимума, что подтверждает нисходящий тренд. Она соединяет нисходящие максимумы, стилизована для видимости и проецируется в будущее на графике. Эта логика по сути противоположна восходящей трендовой линии, которая связывает восходящие минимумы для отображения восходящего тренда.


4. Исполнение сделок на основе пробоев трендовых линий и разворотов

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

4.1. Восходящие трендовые линии

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

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

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

Пример:
double t_line_value;   // Ascending trend line price level at the time of the most recent bar (not the ticking bar)
double t1_line_value;  // Ascending trend line price level at the time of the second most recent bar
double t2_line_value;  // Ascending trend line price level at the time of the third most recent bar
double t3_line_value;  // Ascending trend line price level at the time of the fourth most recent bar
if(allow_uptrend)
  {
// First loop: Find the most recent swing low (first low)
   for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      // Check if current point is a swing low
      if(IsSwingLow(low_price, i, LookbackBars))
        {
         // Store price and time of the first (latest) swing low
         first_low = low_price[i];
         first_low_time = time_price[i];
         break;  // Exit loop after finding the first swing low
        }
     }

// Second loop: Find an earlier swing low that is lower than the first low
   for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      // Check for earlier swing low that is lower and occurs before the first low
      if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
        {
         // Store price and time of the second (older) swing low
         second_low = low_price[i];
         second_low_time = time_price[i];
         break;  // Exit loop after finding the second swing low
        }
     }

// Create an ascending trend line from the second low to the first low
   ObjectCreate(chart_id, up_trend, OBJ_TREND, 0, second_low_time, second_low, first_low_time, first_low);
   ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);  // Temporarily hide line on all timeframes

// If the swing structure is valid (i.e., second low is lower than first)
   if(first_low > second_low && second_low > 0)
     {
      // Extend the trend line to the right
      ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);

      // Show the trend line on all timeframes
      ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

      // Set visual properties: color and thickness
      ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
      ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, 3);


      // Get the price values of the trend line at the corresponding times of the four most recent bars
      t_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[0], 0);   // Current bar
      t1_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[1], 0);  // One bar ago
      t2_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[2], 0);  // Two bars ago
      t3_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[3], 0);  // Three bars ago

      Comment("Ascending trend tine value for the last 4 Bars",
              "\nBar 0: ",  DoubleToString(t_line_value, _Digits),
              "\nBar 1: ", DoubleToString(t1_line_value, _Digits),
              "\nBar 2: ",  DoubleToString(t2_line_value, _Digits),
              "\nBar 3: ",  DoubleToString(t3_line_value, _Digits));

     }
  }

Вывод:

Figure 9. Trend Line Values

Пояснение:

Для получения значения трендовой линии используется функция ObjectGetValueByTime(), которая возвращает ценовой уровень заданного графического объекта (в данном случае – трендовой линии) в определенный момент времени. Это позволит нам определять расположение трендовой линии на момент закрытия каждой из последних свечей.

Для этого мы используем четыре отдельные переменные. Значение трендовой линии на баре 0, который является самым последним завершенным баром, но не тем, который продолжает формироваться (обычно его называют "тиковым баром"), сохраняется в переменной t_line_value. Уровень трендовой линии на баре 1, непосредственно предшествующем последнему бару, сохраняется в переменной t1_line_value. Значения трендовой линии для баров 2 и 3, то есть баров, закрывшихся два и три периода назад соответственно, аналогичным образом сохраняются в t2_line_value и t3_line_value.

Следует отметить, что хотя мы и называем time_price[0] "bar 0", на графике он фактически отображается как второй справа. Это связано с тем, что мы не используем в нашем анализе финальный бар, то, что самый правый, потому что он все еще формируется и не закрылся. То, как мы использовали функцию CopyTime() ранее в коде, когда копирование начиналось с индекса 1 и пропускало текущую (незакрытую) свечу для большей надежности данных, также соответствует этой схеме. Для целей проверки или отладки код выводит эти значения трендовой линии непосредственно на графике с помощью функции Comment(). Этот вывод позволяет вам наблюдать, как трендовая линия ведет себя в отношении самых последних баров, и отображается в верхнем левом углу окна графика. Это особенно полезно для уточнения того, является ли трендовая линия местом пробоя или разворота.

4.1.1. Исполнение сделки по восходящей трендовой линии

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

4.1.1.1. Разворот

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

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

Ниже приведены некоторые валидные условия разворота:

Отскок с фитилем и бычий разворот от восходящей трендовой линии:

Figure 10. Wick Rejection and Bullish Reversal

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

Касание фитилем медвежьей свечи, за которым следует бычье подтверждение:

Figure 11. Bearish Candle Wick Touch Followed by Bullish Confirmation

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

Несколько медвежьих закрытий перед подтверждением бычьего разворота:

Figure 12. Multiple Bearish Closes Before Bullish Reversal Confirmation

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

Разворот после ложного пробоя с немедленным бычьим подтверждением:

Figure 13. False Breakout

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

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

Недействительный разворот после подтвержденного пробоя:

Figure 14. Invalid Reversal after Confirmed Breakout

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

Предотвращение повторяющихся торговых сигналов при множественных разворотах:

Figure 15. Repetitive Trade Signals

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

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

Пример:

#include <Trade/Trade.mqh>
CTrade trade;
int MagicNumber = 532127;


// Timeframe to use for retrieving candlestick data (default is the current chart timeframe)
input ENUM_TIMEFRAMES time_frame = PERIOD_CURRENT;
// Input to enable or disable drawing of the ascending trend line (true = allow drawing)
input bool allow_uptrend = true;
// Number of candles to look back when identifying swing lows for drawing the trend line
input int LookbackBars = 5;

// Input to enable or disable drawing of the descebding trend line (true = allow drawing)
input bool allow_downtrend = true;
input bool allow_break_out = true;    // Enable or disable trade execution on trend line breakout (true = allow)
input bool allow_reversal = true;     // Enable or disable trade execution on trend line reversal (true = allow)
input double lot_size = 0.6;          // Lot size for each trade
input double sl_points = 10;          // Stop Loss in points from entry price
input double tp_points = 50;          // Take Profit in points from entry price

// Number of past bars (candlesticks) to check
int bars_check = 500;

// Arrays to store candlestick data
double close_price[];   // Stores close prices
double open_price[];    // Stores open prices
double low_price[];     // Stores low prices
double high_price[];    // Stores high prices
datetime time_price[];  // Stores time data for each candle

double first_low;           // Price value of the first identified swing low
datetime first_low_time;    // Time when the first swing low occurred

double second_low;          // Price value of the second identified swing low
datetime second_low_time;   // Time when the second swing low occurred
string up_trend = "Up Trend";  // Label used to name the ascending trend line object on the chart
long chart_id = ChartID();     // Stores the current chart ID for referencing during object creation or manipulation
double first_high;          // Price value of the first identified swing high (latest high)
datetime first_high_time;   // Time when the first swing high occurred
double second_high;         // Price value of the second identified swing high (older high)
datetime second_high_time;  // Time when the second swing high occurred
string down_trend = "Down Trend";  // Label used to name the descending trend line object on the chart
double t_line_value;   // Ascending trend line price level at the time of the most recent bar (not the ticking bar)
double t1_line_value;  // Ascending trend line price level at the time of the second most recent bar
double t2_line_value;  // Ascending trend line price level at the time of the third most recent bar
double t3_line_value;  // Ascending trend line price level at the time of the fourth most recent bar

// Time boundary used to limit lookback for valid reversal setups
datetime lookbackf_time;

datetime lastTradeBarTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Set arrays as series so the newest bar is index 0 ( start from the latest bar)
   ArraySetAsSeries(close_price, true);
   ArraySetAsSeries(open_price, true);
   ArraySetAsSeries(low_price, true);
   ArraySetAsSeries(high_price, true);
   ArraySetAsSeries(time_price, true);

   trade.SetExpertMagicNumber(MagicNumber);

   return(INIT_SUCCEEDED);  // Signal that the EA initialized successfully
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

   ObjectsDeleteAll(chart_id);

  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Copy the latest candlestick data into the arrays
   CopyOpen(_Symbol, time_frame, 1, bars_check, open_price);     // Open prices
   CopyClose(_Symbol, time_frame, 1, bars_check, close_price);   // Close prices
   CopyLow(_Symbol, time_frame, 1, bars_check, low_price);       // Low prices
   CopyHigh(_Symbol, time_frame, 1, bars_check, high_price);     // High prices
   CopyTime(_Symbol, time_frame, 1, bars_check, time_price);     // Candle times

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

// If the user allows drawing of ascending trend line
   if(allow_uptrend)
     {
      // First loop: Find the most recent swing low (first low)
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check if current point is a swing low
         if(IsSwingLow(low_price, i, LookbackBars))
           {
            // Store price and time of the first (latest) swing low
            first_low = low_price[i];
            first_low_time = time_price[i];
            lookbackf_time = time_price[i - 3];

            break;  // Exit loop after finding the first swing low
           }
        }

      // Second loop: Find an earlier swing low that is lower than the first low
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check for earlier swing low that is lower and occurs before the first low
         if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
           {
            // Store price and time of the second (older) swing low
            second_low = low_price[i];
            second_low_time = time_price[i];
            break;  // Exit loop after finding the second swing low
           }
        }

      // Create an ascending trend line from the second low to the first low
      ObjectCreate(chart_id, up_trend, OBJ_TREND, 0, second_low_time, second_low, first_low_time, first_low);
      ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);  // Temporarily hide line on all timeframes

      // If the swing structure is valid (i.e., second low is lower than first)
      if(first_low > second_low && second_low > 0)
        {
         // Extend the trend line to the right
         ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);

         // Show the trend line on all timeframes
         ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

         // Set visual properties: color and thickness
         ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
         ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, 3);

         // Get the price values of the trend line at the corresponding times of the four most recent bars
         t_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[0], 0);   // Current bar
         t1_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[1], 0);  // One bar ago
         t2_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[2], 0);  // Two bars ago
         t3_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[3], 0);  // Three bars ago

         // Number of bars between the valid bullish confirmation candle and current time
         int no_bars = 0;

         // Loop through the last 4 bars to check for reversal wick touch on the trend line
         for(int i = 0; i <= 3; i++)
           {
            // Condition: Wick of the candle touches below the trend line but opens above it (indicating a potential reversal zone)
            if(low_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0) &&
               open_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0))
              {
               // Check if there's a bullish confirmation candle after the wick touch (within or immediately after)
               for(int j = i; j >= 0; j--)
                 {
                  // Bullish candle that closed above the trend line
                  if(close_price[j] > open_price[j] &&
                     close_price[j] > ObjectGetValueByTime(chart_id, up_trend, time_price[j], 0))
                    {
                     // Count how many bars ago this confirmation occurred
                     no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
                     break;
                    }
                 }
               break; // Exit after first valid reversal zone is found
              }
           }

         // Check whether a similar wick touch (reversal) happened recently to avoid repeated signals
         bool prev_touch = false;

         if((low_price[1] < t1_line_value && close_price[1] > open_price[1]) ||  // Bar 1 had reversal wick and bullish body
            (low_price[2] < t2_line_value && close_price[2] > open_price[2]))    // Bar 2 had reversal wick and bullish body
           {
            prev_touch = true;  // Flag that a recent touch already occurred
           }

         // Final condition for executing a BUY trade on a reversal setup
         if(
            // One of the recent 4 bars touched and rejected the trend line (wick below, open above), AND
            ((low_price[0] < t_line_value && open_price[0] > t_line_value) ||
             (low_price[1] < t1_line_value && open_price[1] > t1_line_value) ||
             (low_price[2] < t2_line_value && open_price[2] > t2_line_value) ||
             (low_price[3] < t3_line_value && open_price[3] > t3_line_value))
            &&
            // Current candle must be bullish and close above the trend line
            (close_price[0] > open_price[0]) && close_price[0] > t_line_value
            &&
            // The bullish confirmation must occur within 3 bars
            (no_bars < 3)
            &&
            // No recent wick reversal signal already processed
            prev_touch == false
            &&
            // The signal must be more recent than the lookback time threshold
            (time_price[3] > lookbackf_time)
            &&
            // Reversal signals are allowed and this signal is not duplicated from the same bar
            (allow_reversal == true && currentBarTime != lastTradeBarTime)
         )
           {
            // Execute BUY trade with defined lot size, SL and TP
            trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
            lastTradeBarTime = currentBarTime; // Update last trade bar time to avoid duplicate signals
           }
        }
     }

//
// Only proceed if drawing descending trend lines is enabled
   if(allow_downtrend)
     {
      // First loop: Find the most recent swing high (first high)
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check if the current bar is a swing high
         if(IsSwingHigh(high_price, i, LookbackBars))
           {
            // Store the price and time of this latest swing high
            first_high = high_price[i];
            first_high_time = time_price[i];

            break;  // Exit loop once the first swing high is found
           }
        }

      // Second loop: Find an earlier swing high that is higher than the first high and occurred before it
      for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
        {
         // Check for earlier swing high that is higher and happened before the first one
         if(IsSwingHigh(high_price, i, LookbackBars) && high_price[i] > first_high && time_price[i] < first_high_time)
           {
            // Store the price and time of this older swing high
            second_high = high_price[i];
            second_high_time = time_price[i];

            break;  // Exit loop once the second swing high is found
           }
        }

      // Create a trend line object from the second swing high to the first swing high
      ObjectCreate(chart_id, down_trend, OBJ_TREND, 0, second_high_time, second_high, first_high_time, first_high);

      // Initially hide the trend line across all timeframes to avoid partial drawing
      ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);

      // Validate the swing structure:
      // The older swing high should be higher than the later swing high to confirm a descending trend line
      if(first_high < second_high && second_high > 0)
        {
         // Extend the trend line indefinitely to the right for better visual guidance
         ObjectSetInteger(chart_id, down_trend, OBJPROP_RAY_RIGHT, true);

         // Make the trend line visible on all chart timeframes
         ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

         // Set the trend line color to dark green for clear distinction
         ObjectSetInteger(chart_id, down_trend, OBJPROP_COLOR, clrDarkGreen);

         // Set the thickness of the trend line to 3 pixels for better visibility
         ObjectSetInteger(chart_id, down_trend, OBJPROP_WIDTH, 3);

        }
     }
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR LOWS                                                |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low[], int index, int lookback)
  {

   for(int i = 1; i <= lookback; i++)
     {

      if(low[index] > low[index - i] || low[index] > low[index + i])
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR HIGHS                                               |
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high[], int index, int lookback)
  {

   for(int i = 1; i <= lookback; i++)
     {
      if(high[index] < high[index - i] || high[index] < high[index + i])
         return false;
     }
   return true;
  }

Вывод:

Figure 16. Wick Reversal

Figure 17. False Breakout

figure 18. Wick Rejection and Bullish Reversal

Пояснение:

Чтобы получить доступ к классу CTrade, который используется в языке MQL5 для размещения сделок и управления ими, код сначала включает торговую библиотеку Trade.mqh. Также как функции Buy() и Sell() выполняют торговые операции, создается объект класса CTrade с именем trade. Чтобы отличить транзакции, исполняемые этим конкретным советником, от других, компьютер присваивает советнику уникальный MagicNumber. Для установки этого значения используется функция SetExpertMagicNumber().

Затем указывается ряд входных параметров. Это позволяет пользователю изменять поведение советника, не изменяя основной код. Например, чтобы регулировать, разрешены ли сделки в ситуациях нисходящего тренда, будь то на пробойных паттернах или в сетапах разворота, используются переключатели включения/выключения: allow_downtrend, allow_break_out и allow_reversal. Параметры sl_points и tp_points указывают расстояние в пунктах от цены входа до стоп-лосса и тейк-профита, соответственно, а параметр lot_size устанавливает размер каждой позиции.

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

int no_bars = 0;
for(int i = 0; i <= 3; i++)
  {
   if(low_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0) &&
      open_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0))
     {
      for(int j = i; j >= 0; j--)
        {
         if(close_price[j] > open_price[j] &&
            close_price[j] > ObjectGetValueByTime(chart_id, up_trend, time_price[j], 0))
           {
            no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
            break;
           }
        }
      break;
     }
  }

В последних четырех свечах (бары 0-3) проверяется, показала ли какая-либо из них поведение фитиля, которое могло бы указывать на сигнал разворота: минимум пробил трендовую линию, но цена открытия осталась выше нее. Это подразумевает, что произошел отскок от трендовой линии. Как только такая свеча будет идентифицирована, внутренний цикл начнет искать бычью подтверждающую свечу, которая закрылась выше как цены открытия, так и трендовой линии. Если такая свеча будет найдена, вызывается функция Bars(), чтобы определить, сколько баров назад произошло это подтверждение, и результат сохраняется в переменной no_bars.

bool prev_touch = false;
if((low_price[1] < t1_line_value && close_price[1] > open_price[1]) ||
   (low_price[2] < t2_line_value && close_price[2] > open_price[2])) {
    prev_touch = true;
}

Этот блок определяет, касались ли уже свечи 1 или 2 трендовой линии и закрылись ли они бычьими, чтобы предотвратить повторяющиеся или преждевременные предупреждения. Переменной prev_touch присваивается значение true, если хотя бы одно из двух условий истинно, что предполагает, что разворот мог произойти недавно, и что другой сигнал на текущем баре следует игнорировать.

if(
   ((low_price[0] < t_line_value && open_price[0] > t_line_value) ||
    (low_price[1] < t1_line_value && open_price[1] > t1_line_value) ||
    (low_price[2] < t2_line_value && open_price[2] > t2_line_value) ||
    (low_price[3] < t3_line_value && open_price[3] > t3_line_value))
   &&
   (close_price[0] > open_price[0]) && close_price[0] > t_line_value
   &&
   (no_bars < 3)
   &&
   prev_touch == false
   &&
   (time_price[3] > lookbackf_time)
   &&
   (allow_reversal == true && currentBarTime != lastTradeBarTime)
)
  {
   trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
   lastTradeBarTime = currentBarTime;
  }

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

Алгоритм проверяет, что недавно не был обнаружен сопоставимый сигнал касания фитиля, и следовательно значение prev_touch должно быть false, чтобы предотвратить повторные входы. Кроме того, он гарантирует, что сетап более актуален, чем указанное время lookbackf_time, что ограничивает диапазон сигналов, которые программа принимает как действительные.

Наконец, проверяет, что currentBarTime не равен lastTradeBarTime, алгоритм предотвращает дублирование и подтверждает, что сделки разворота разрешены (allow_reversal имеет значение true), и что никакая сделка еще не производилась на той же свече. Ордер на покупку с заданным размером лота, значениями стоп-лосса и тейк-профита исполняется, если все эти требования выполнены. Затем, чтобы предотвратить совершение дополнительных сделок на одной и той же свече, lastTradeBarTime обновляется до времени текущей свечи.

4.1.1.2. Пробой и ретест

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

Figure 19. Breakout and Retest

Пример:

// If the user allows drawing of ascending trend line
if(allow_uptrend)
  {
// First loop: Find the most recent swing low (first low)
   for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      // Check if current point is a swing low
      if(IsSwingLow(low_price, i, LookbackBars))
        {
         // Store price and time of the first (latest) swing low
         first_low = low_price[i];
         first_low_time = time_price[i];
         lookbackf_time = time_price[i - 3];

         break;  // Exit loop after finding the first swing low
        }
     }

// Second loop: Find an earlier swing low that is lower than the first low
   for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      // Check for earlier swing low that is lower and occurs before the first low
      if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
        {
         // Store price and time of the second (older) swing low
         second_low = low_price[i];
         second_low_time = time_price[i];
         break;  // Exit loop after finding the second swing low
        }
     }

// Create an ascending trend line from the second low to the first low
   ObjectCreate(chart_id, up_trend, OBJ_TREND, 0, second_low_time, second_low, first_low_time, first_low);
   ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);  // Temporarily hide line on all timeframes

// If the swing structure is valid (i.e., second low is lower than first)
   if(first_low > second_low && second_low > 0)
     {
      // Extend the trend line to the right
      ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);

      // Show the trend line on all timeframes
      ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

      // Set visual properties: color and thickness
      ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
      ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, 3);


      // Get the price values of the trend line at the corresponding times of the four most recent bars
      t_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[0], 0);   // Current bar
      t1_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[1], 0);  // One bar ago
      t2_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[2], 0);  // Two bars ago
      t3_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[3], 0);  // Three bars ago

      // Number of bars between the valid bullish confirmation candle and current time
      int no_bars = 0;

      // Loop through the last 4 bars to check for reversal wick touch on the trend line
      for(int i = 0; i <= 3; i++)
        {
         // Condition: Wick of the candle touches below the trend line but opens above it (indicating a potential reversal zone)
         if(low_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0) &&
            open_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0))
           {
            // Check if there's a bullish confirmation candle after the wick touch (within or immediately after)
            for(int j = i; j >= 0; j--)
              {
               // Bullish candle that closed above the trend line
               if(close_price[j] > open_price[j] &&
                  close_price[j] > ObjectGetValueByTime(chart_id, up_trend, time_price[j], 0))
                 {
                  // Count how many bars ago this confirmation occurred
                  no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
                  break;
                 }
              }
            break; // Exit after first valid reversal zone is found
           }
        }

      // Check whether a similar wick touch (reversal) happened recently to avoid repeated signals
      bool prev_touch = false;

      if((low_price[1] < t1_line_value && close_price[1] > open_price[1]) ||  // Bar 1 had reversal wick and bullish body
         (low_price[2] < t2_line_value && close_price[2] > open_price[2]))    // Bar 2 had reversal wick and bullish body
        {
         prev_touch = true;  // Flag that a recent touch already occurred
        }

      // Final condition for executing a BUY trade on a reversal setup
      if(
         // One of the recent 4 bars touched and rejected the trend line (wick below, open above), AND
         ((low_price[0] < t_line_value && open_price[0] > t_line_value) ||
          (low_price[1] < t1_line_value && open_price[1] > t1_line_value) ||
          (low_price[2] < t2_line_value && open_price[2] > t2_line_value) ||
          (low_price[3] < t3_line_value && open_price[3] > t3_line_value))
         &&
         // Current candle must be bullish and close above the trend line
         (close_price[0] > open_price[0]) && close_price[0] > t_line_value
         &&
         // The bullish confirmation must occur within 3 bars
         (no_bars < 3)
         &&
         // No recent wick reversal signal already processed
         prev_touch == false
         &&
         // The signal must be more recent than the lookback time threshold
         (time_price[3] > lookbackf_time)
         &&
         // Reversal signals are allowed and this signal is not duplicated from the same bar
         (allow_reversal == true && currentBarTime != lastTradeBarTime)
      )
        {
         // Execute BUY trade with defined lot size, SL and TP
         trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
         lastTradeBarTime = currentBarTime; // Update last trade bar time to avoid duplicate signals
        }

      //BREAKOUT AND RETEST

      // Flag to track whether a recent bearish wick rejection (touch) already occurred
      bool prev_touch2 = false;

      // Check the last 2 bars to see if a candle had its high wick above the trend line,
      // but closed bearishly below the open - indicating a possible rejection
      if((high_price[1] > t1_line_value && close_price[1] < open_price[1]) ||
         (high_price[2] > t2_line_value && close_price[2] < open_price[2] && open_price[2] < t2_line_value))
        {
         prev_touch2 = true; // Set flag to avoid duplicate signals
        }

      // Variable to store how many bars ago the bearish confirmation candle appeared
      int no_bars2 = 0;

      // Loop through the last 4 candles to detect a wick rejection of the trend line (retest)
      for(int i = 0; i <= 3; i++)
        {
         // Condition: Candle wick (high) goes above the trend line, but the open is below it
         if(high_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0) &&
            open_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], 0))
           {
            // Search backward from that bar for a bearish confirmation candle
            for(int j = i; j >= 0; j--)
              {
               // Bearish candle that also closed below the trend line
               if(close_price[j] < open_price[j] &&
                  close_price[j] < ObjectGetValueByTime(chart_id, up_trend, time_price[j], 0))
                 {
                  // Count bars between that confirmation and now
                  no_bars2 = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
                  break; // Exit inner loop
                 }
              }
            break; // Exit outer loop after first valid retest
           }
        }

      // Final conditions to confirm a breakout and retest sell setup:
      // 1. One of the last 4 candles had a wick above the trend line but opened below it
      // 2. Current candle is bearish and closed below the trend line
      // 3. There was no recent similar signal (prev_touch2 == false)
      // 4. The bearish confirmation occurred within the last 3 bars
      // 5. Breakout trades are allowed and this signal is not from the same bar as the last trade
      if(((high_price[1] >= t1_line_value && open_price[1] < t1_line_value) ||
          (high_price[2] >= t2_line_value && open_price[2] < t2_line_value) ||
          (high_price[3] >= t3_line_value && open_price[3] < t3_line_value) ||
          (high_price[0] >= t_line_value)) &&
         (close_price[0] < t_line_value && close_price[0] < open_price[0] && open_price[1] < t1_line_value) &&
         prev_touch2 == false &&
         (no_bars2 < 3) &&
         (allow_break_out == true && currentBarTime != lastTradeBarTime))
        {
         // All conditions met - place SELL trade with defined SL and TP
         trade.Sell(lot_size, _Symbol, ask_price, ask_price + sl_points, ask_price - tp_points);

         // Update timestamp to prevent duplicate signals from the same bar
         lastTradeBarTime = currentBarTime;
        }
     }
  }

Вывод:

Figure 20. Immediate Retest

Figure 21. Breakout and Retest

Пояснение:

Код начинается с объявления булевой переменной с именем prev_touch2, которая служит флагом для определения, произошел ли недавно медвежий отскок (ложный пробой). Цель этого флага заключается в том, чтобы предотвратить срабатывание алгоритма на множественные торговые сигналы от одного и того же сетапа, который уже был обработан. Это помогает уменьшить количество ложных сигналов и гарантирует, что советник реагирует только на новые, действительные сетапы.

Затем алгоритм ищет признаки медвежьего отскока на трендовой линии, анализируя две предыдущие свечи (high_price[1] и high_price[2]). В частности, он исследует, закрылся ли максимум свечи в конечном итоге ниже ее открытия, что указывает на медвежье давление и неспособность удержать пробой, или же она кратковременно пробила трендовую линию, что предполагает потенциальную попытку пробоя. Чтобы подтвердить отклонение, добавляется условие для второй свечи ([2]), чтобы убедиться, что цена открытия также находится ниже трендовой линии. Переменной prev_touch2 присваивается значение true, если выполнено любое из этих условий.

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

После этого код входит в цикл, который проходит по предыдущим четырем свечам. Цель в том, чтобы найти свечу, чей фитиль (максимум) пересек трендовую линию, но которая открылась ниже нее; это указывает на то, что цена пыталась пробить трендовую линию, но не смогла сделать это чисто. После нахождения такой свечи вложенный цикл производит обратный поиск от нее на предмет медвежьего подтверждения, а именно свечи, закрывшейся ниже как открытия, так и трендовой линии в соответствующий момент. Если это подтверждение обнаружено, применяется метод Bars() для определения, сколько баров назад возникла эта свеча, и результат сохраняется в no_bars2. Чтобы сэкономить вычислительные ресурсы, циклы завершаются преждевременно, как только обнаруживаются действительные сигналы.

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

  • одна из последних четырех свечей имела фитиль, который поднялся выше трендовой линии, но при этом открылась ниже нее;
  • текущая свеча медвежья и закрылась ниже как трендовой линии, так и своей цены открытия;
  • не было ранее отмеченного аналогичного сигнала (prev_touch2 == false);
  • недавно сформировалась медвежья свеча подтверждения (в пределах последних 3 баров);
  • сделка разрешена (allow_break_out == true), и она не дублируется с той же свечи (currentBarTime != lastTradeBarTime).

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

4.1.2. Исполнение сделки по нисходящей трендовой линии

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

Пример:

// Only proceed if drawing descending trend lines is enabled
if(allow_downtrend)
  {
// First loop: Find the most recent swing high (first high)
   for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      // Check if the current bar is a swing high
      if(IsSwingHigh(high_price, i, LookbackBars))
        {
         // Store the price and time of this latest swing high
         first_high = high_price[i];
         first_high_time = time_price[i];

         break;  // Exit loop once the first swing high is found
        }
     }

// Second loop: Find an earlier swing high that is higher than the first high and occurred before it
   for(int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      // Check for earlier swing high that is higher and happened before the first one
      if(IsSwingHigh(high_price, i, LookbackBars) && high_price[i] > first_high && time_price[i] < first_high_time)
        {
         // Store the price and time of this older swing high
         second_high = high_price[i];
         second_high_time = time_price[i];

         break;  // Exit loop once the second swing high is found
        }
     }

// Create a trend line object from the second swing high to the first swing high
   ObjectCreate(chart_id, down_trend, OBJ_TREND, 0, second_high_time, second_high, first_high_time, first_high);

// Initially hide the trend line across all timeframes to avoid partial drawing
   ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);

// Validate the swing structure:
// The older swing high should be higher than the later swing high to confirm a descending trend line
   if(first_high < second_high && second_high > 0)
     {
      // Extend the trend line indefinitely to the right for better visual guidance
      ObjectSetInteger(chart_id, down_trend, OBJPROP_RAY_RIGHT, true);

      // Make the trend line visible on all chart timeframes
      ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

      // Set the trend line color to dark green for clear distinction
      ObjectSetInteger(chart_id, down_trend, OBJPROP_COLOR, clrDarkGreen);

      // Set the thickness of the trend line to 3 pixels for better visibility
      ObjectSetInteger(chart_id, down_trend, OBJPROP_WIDTH, 3);

      //REVERSAL

      td_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[0],0);
      td1_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[1],0);
      td2_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[2],0);
      td3_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[3],0);

      int no_bars = 0;

      for(int i = 0; i <= 3; i++)
        {

         if(high_price[i] > ObjectGetValueByTime(chart_id,down_trend,time_price[i],0) && open_price[i] < ObjectGetValueByTime(chart_id,down_trend,time_price[i],0)
           )
           {

            for(int j = i; j >= 0; j--)
              {

               if(close_price[j] < open_price[j] && close_price[j] < ObjectGetValueByTime(chart_id,down_trend,time_price[j],0))
                 {

                  no_bars = Bars(_Symbol,time_frame,time_price[j],TimeCurrent());

                  break;

                 }

              }
            break;

           }

        }

      bool prev_touch = false;

      if((high_price[1] > td1_line_value && close_price[1] < open_price[1])
         ||
         (high_price[2] > td2_line_value && close_price[2] < open_price[2])
        )
        {

         prev_touch = true;

        }

      if(((high_price[1] >= td1_line_value && open_price[1] < td1_line_value) || (high_price[2] >= td2_line_value && open_price[2] < td2_line_value)
          || (high_price[3] >= td3_line_value && open_price[3] < td3_line_value) || (high_price[0] >= td_line_value))
         && (close_price[0] < td_line_value && close_price[0] < open_price[0] && open_price[1] < td1_line_value)
         && (no_bars < 3)
         && prev_touch == false
         && (allow_reversal == true  && currentBarTime != lastTradeBarTime)
        )
        {

         trade.Sell(lot_size,_Symbol,ask_price,ask_price + sl_points, ask_price - tp_points);
         lastTradeBarTime = currentBarTime;

        }

      //BREAKOUT AMD RETEST

      // Flag to track whether a recent bullish wick rejection (touch) already occurred
      bool prev_touch2 = false;

      // Check the last 2 candles for bullish rejection from below the descending trend line
      // A bullish rejection occurs when the low goes below the trend line but closes above the open (bullish candle)
      if((low_price[1] < td1_line_value && close_price[1] > open_price[1]) ||
         (low_price[2] < td2_line_value && close_price[2] > open_price[2] && open_price[2] > td2_line_value))
        {
         prev_touch2 = true; // Set flag to prevent duplicate signals from the same type of setup
        }

      // Variable to hold how many bars ago a bullish confirmation candle occurred after wick rejection
      int no_bars2 = 0;

      // Loop through the last 4 candles to detect a wick rejection of the descending trend line
      for(int i = 0; i <= 3; i++)
        {
         // Condition: Candle wick (low) goes below the trend line, but the open is above it
         if(low_price[i] < ObjectGetValueByTime(chart_id, down_trend, time_price[i], 0) &&
            open_price[i] > ObjectGetValueByTime(chart_id, down_trend, time_price[i], 0))
           {
            // Look backward for a bullish confirmation candle that closes above the trend line
            for(int j = i; j >= 0; j--)
              {
               if(close_price[j] > open_price[j] &&
                  close_price[j] > ObjectGetValueByTime(chart_id, down_trend, time_price[j], 0))
                 {
                  // Count how many bars ago that bullish confirmation happened
                  no_bars2 = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
                  break; // Exit inner loop once confirmation is found
                 }
              }
            break; // Exit outer loop after the first valid retest is processed
           }
        }

      // Final conditions to confirm a breakout or retest for a BUY setup on descending trend line:
      // 1. One of the last 4 candles had a wick below the trend line but opened above it
      // 2. Current candle is bullish and closed above the trend line
      // 3. A valid bullish confirmation occurred within the last 3 bars
      // 4. No recent similar touch detected (prev_touch2 == false)
      // 5. Candle timestamps are valid (not too far back)
      // 6. Breakout trading is allowed, and this bar is not the same as the last trade bar
      if(
         ((low_price[0] < td_line_value && open_price[0] > td_line_value) ||
          (low_price[1] < td1_line_value && open_price[1] > td1_line_value) ||
          (low_price[2] < td2_line_value && open_price[2] > td2_line_value) ||
          (low_price[3] < td3_line_value && open_price[3] > td3_line_value)) &&
         (close_price[0] > open_price[0]) && close_price[0] > td_line_value &&
         (no_bars2 < 3) &&
         prev_touch2 == false &&
         (time_price[3] > lookbackfd_time) &&
         (allow_break_out == true && currentBarTime != lastTradeBarTime)
      )
        {
         // All conditions met - place a BUY trade with defined SL and TP
         trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);

         // Update the last trade time to avoid repeated trades from the same bar
         lastTradeBarTime = currentBarTime;
        }
     }
  }

Вывод:

Figure 22. Descending Trend Line Breakout

Figure 23. Descending Trend Line Reversal

Пояснение:

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

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

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

Аналогичным образом, мы также удостоверяемся, что сетап актуален по времени, пробойные сделки разрешены, сигнал не исходит от той же свечи, что и предыдущая сделка, а также что не было зафиксировано недавних отскоков (prev_touch2). Советник использует указанный размер лота, уровни стоп-лосса и тейк-профита для исполнения сделки на покупку, если все эти критерии выполнены. Он также изменяет lastTradeBarTime, чтобы избежать дублирования сделок на основе одной и той же свечи.


Заключение

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
Dominic Michael Frehner
Dominic Michael Frehner | 5 июн. 2025 в 08:17
Спасибо за эту статью! Только хочу сообщить, что есть небольшая ошибка с расчетом стоплосса и тейкпрофита :-) Он не основан на пунктах. Попробуйте это на любой валютной паре (EURUSD, GBPUSD и т.д.)
Israel Pelumi Abioye
Israel Pelumi Abioye | 5 июн. 2025 в 13:16
Dominic Michael Frehner GBPUSD и т.д.)
Здравствуйте, Доминик.
Спасибо за добрые слова, это не ошибка. Например, вы можете решить использовать 0.0010 для 10 пунктов, это зависит от инструмента
Yaovi Inoussa Atchou
Yaovi Inoussa Atchou | 11 июл. 2025 в 13:30

Здравствуйте, брат

Может ли ваш Ea работать с деривативами на волатильность?

Celestine Nwakaeze
Celestine Nwakaeze | 28 окт. 2025 в 15:48
Спасибо за эту статью. Она очень познавательна. Да благословит вас Бог.
Israel Pelumi Abioye
Israel Pelumi Abioye | 28 окт. 2025 в 17:42
Celestine Nwakaeze #:
Спасибо за эту статью, она очень поучительна. Да благословит вас Господь.

Не за что, спасибо за добрые слова.


Квантовые вычисления и градиентный бустинг в торговле EUR/USD Квантовые вычисления и градиентный бустинг в торговле EUR/USD
Статья описывает практическую реализацию гибридной системы алгоритмического трейдинга, объединяющей квантовые вычисления (IBM Qiskit) и градиентный бустинг (CatBoost) для предсказания движения EUR/USD на часовом таймфрейме. Система извлекает четыре уникальных квантовых признака из вероятностного распределения по 256 состояниям через восемь кубитов, которые в комбинации с классическими индикаторами и дельта-кодированием временных категорий достигают точности 62% на 15,000 свечах.
Нейросети в трейдинге: Двусторонняя адаптивная временная корреляция (BAT) Нейросети в трейдинге: Двусторонняя адаптивная временная корреляция (BAT)
В статье представлен фреймворк BAT, обеспечивающий точное и адаптивное моделирование временной динамики. Используя двустороннюю временную корреляцию, BAT превращает последовательные изменения рыночных данных в структурированные, информативные представления. Модель сочетает высокую вычислительную эффективность с возможностью глубокой интеграции в торговые системы, позволяя выявлять как краткосрочные, так и долгосрочные паттерны движения.
Анализ нескольких символов с помощью Python и MQL5 (Часть 3): Треугольные курсы валют Анализ нескольких символов с помощью Python и MQL5 (Часть 3): Треугольные курсы валют
Трейдеры часто сталкиваются с просадками из-за ложных сигналов, а ожидание подтверждения может привести к упущенным возможностям. В этой статье представлена треугольная торговая стратегия, использующая цену серебра в долларах (XAGUSD) и евро (XAGEUR), а также обменный курс EURUSD для фильтрации шума. Используя межрыночные связи, трейдеры могут выявлять скрытые настроения и совершенствовать свои позиции в реальном времени.
Быстрая интеграция большой языковой модели и MetaTrader 5 (Часть II): Файнтьюн на реальных данных, бэктест и онлайн-торговля модели Быстрая интеграция большой языковой модели и MetaTrader 5 (Часть II): Файнтьюн на реальных данных, бэктест и онлайн-торговля модели
Статья описывает процесс файнтьюна языковой модели для трейдинга на основе реальных исторических данных из MetaTrader 5. Базовая модель, знающая лишь теоретический технический анализ, обучается на тысяче примеров реального поведения валютных пар (EURUSD, GBPUSD, USDCHF, USDCAD) за 180 дней. После обучения через Ollama модель начинает понимать специфику каждого инструмента.