English 中文 Español Deutsch 日本語 Português
preview
Введение в MQL5 (Часть 11): Руководство для начинающих по работе со встроенными индикаторами в MQL5 (II)

Введение в MQL5 (Часть 11): Руководство для начинающих по работе со встроенными индикаторами в MQL5 (II)

MetaTrader 5Индикаторы |
566 4
Israel Pelumi Abioye
Israel Pelumi Abioye

Введение

Представляю вашему вниманию очередную статью из нашей серии, посвященной MQL5. Сегодня мы обсудим очень важную тему, которая, я уверен, еще и будет очень полезной и интересной. В предыдущей части мы изучали, как использовать встроенные индикаторы MQL5. В частности мы разобрали индикатор индекса относительной силы RSI. Мы также реализовали проект, который на практике показывает, как включить RSI в торговую стратегию. В нашем новом проекте мы будем использовать сразу три индикатора: RSI, стохастический осциллятор и скользящую среднюю (Moving Average). В процессе реализации мы рассмотрим концепцию обнаружения скрытых расхождений/дивергенций. В частности, разберем, как распознавать скрытые бычьи и медвежьи дивергенции.

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

В этой статье мы поговорим о следующем:

  • Как анализировать минимумы и максимумы RSI в соотношении с движением цены, чтобы выявлять скрытые бычьи и медвежьи дивергенции.
  • Как создавать советники по нескольким индикаторам ( в нашем примере RSI, MA и Стохастик).
  • Как распознавать и визуализировать скрытые расхождения прямо на графике.
  • Как реализовать риск-менеджмент по размеру позиции (лот) на основе допустимого риска в деньгах и расстояния до стоп-лосса, учитывая неравномерный размер свечей.
  • Как автоматизировать сделки с заранее заданным соотношением риск/прибыль (Risk-Reward Ratio, RRR) при получении сигналов расхождения.


1. Создание проекта

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

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

1.1.1. Скрытая бычья дивергенция

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

Рисунок 1. Скрытая бычья дивергенция

1.1.2. Скрытая медвежья дивергенция

Здесь RSI формирует более высокий максимум, а цена показывает более низкий максимум. Это сигнализирует о возможном скором снижении цены.

Рисунок 2. Скрытая медвежья дивергенция

1.1.3. Логика открытия сделки на продажу (Sell)

Фильтр тренда:

  • Текущая цена должна находиться ниже 200 EMA, что подтверждает медвежий тренд.

Определение последнего значимого максимума RSI:

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

Отмечаем соответствующую цену:

  • На ценовом графике (в режиме линии) проводим горизонтальную линию на уровне соответствующей цены (C-PRICE) этого максимума RSI.

Поиск скрытой медвежьей дивергенции:

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

Подтверждение Стохастиком:

  • После выполнения всех условий ждем, когда Стохастик пересечет уровень перекупленности (например, 80) вверх, а затем опустится ниже его обратно в течение 11 баров.
  • Если подтверждение не появляется в течение 11 баров — сделку не открывам.

Установка SL и TP:

  • SL (Stop Loss) на последнем локальном максимуме.
  • TP (Take Profit) определяется на основе заданного параметра Risk-Reward Ratio (RRR).

1.1.4. Логика открытия сделки на покупку (Buy)

Фильтр тренда:

  • Убедимся, что текущая цена находится выше 200 EMA.

Определим последний значимый минимум RSI:

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

Отмечаем соответствующую цену:

  • На линейном ценовом графике проведем горизонтальную линию на уровне соответствующей цены (C-PRICE) этого минимума RSI.

Поиск скрытой бычьей дивергенции:

  • Дождемся, когда RSI сформирует новый минимум ниже предыдущего уровня RSI
  • Соответствующий минимум цены должен быть выше своей горизонтальной линии (C-PRICE).

Подтверждение Стохастиком:

  • После выполнения всех условий ждем, когда Стохастик опустится ниже уровня перепроданности (например, 20), а затем поднимется выше него обратно в течение 11 баров.
  • Если подтверждение не появляется в течение 11 баров — сделку не открывам.

Установка SL и TP:

  • SL (Stop Loss) на последнем локальном минимуме.
  • TP (Take Profit) определяется на основе заданного параметра Risk-Reward Ratio (RRR).


2. Настройка свойств индикаторов и получение данных

2.1. Задаем параметры индикаторов

На этом этапе мы настроим параметры индикаторов, которые будут использоваться в проекте — Стохастика, RSI и Скользящего среднего. Параметры определяют, как именно индикаторы будут работать.

Аналогия

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

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

Примеры:
//DECLEAR INDICATOR HANDLES
int ma_handle;
int rsi_handle;
int stoch_handle;

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

//INDICATOR PROPERTIES
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE);
   rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);
   stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH);

   return(INIT_SUCCEEDED);
  }

Пояснения:

Представьте, что вы находитесь в мастерской и готовитесь к работе над проектом, где у вас есть три специальных инструмента: рулетка (Moving Average), увеличительное стекло (RSI) и строительный уровень (Стохастик). Перед началом нужно убедиться, что все инструменты исправны и правильно настроены. Нельзя же просто достать их из ящика и сразу приступить к работе, не проверив!

Объявление хэндлов индикаторов (подготовка инструментов)

int ma_handle;
int rsi_handle;
int stoch_handle;

Это можно сравнить с тем, как вы размечаете шкафчики в своей мастерской, чтобы каждый инструмент имел свое место. Каждый инструмент должен быть правильно «прописан», чтобы вы могли быстро найти и использовать его, когда потребуется.

  • ma_handle — для рулетки (скользящая средняя).
  • rsi_handle — для лупы (RSI).
  • stoch_handle — для уровня (стохастик).

Настройка инструментов (инициализация индикаторов)

Настройка iMA (Moving Average)

ma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE);

Теперь представьте, что вы объясняете своему помощнику: «Нужно настроить рулетку для проекта». Вы объясняете, как именно ею пользоваться:

  • _Symbol: сообщает помощнику, что именно вы измеряете — актив, акцию или валютную пару (например, EUR/USD). То есть вы объясняете, что именно измеряете своей рулеткой.
  • PERIOD_CURRENT: определяет, с каким таймфреймом вы работаете. Это как длина рулетки — вы можете измерять день, неделю или месяц. PERIOD_CURRENT означает текущий таймфрейм графика.
  • 200: длина измерения — количество свечей, охватываемых скользящей средней. То есть вы просите помощника использовать рулетку, которая «заглядывает» на 200 периодов назад, чтобы оценить общий тренд.
  • 0: смещение. Показывает, насколько нужно сдвинуть измерение вправо или влево. Здесь 0 означает отсутствие сдвига — вы измеряете текущее значение и предыдущие 200 баров напрямую.
  • MODE_EMA: тип используемой скользящей средней. В данном случае это экспоненциальная (EMA). Она чувствительнее и быстрее реагирует на изменения цены, чем простая (SMA). Этот инструмент может адаптировать к новым данным.
  • PRICE_CLOSE: точка отсчета. В нашем случае измерение проводится по ценам закрытия, что аналогично калибровке инструмента под конкретный тип данных.

Рисунок 3. Свойства MA

Настройка iRSI (Relative Strength Index)

rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);

Теперь перейдем к настройке лупы (RSI). Указываем, Как именно его использовать:

  • _Symbol: тот же принцип — указывает, с каким активом вы работаете.
  • PERIOD_CURRENT: как и в случае со скользящей средней используем текущий таймфрейм графика.
  • 14: период RSI. Можно сказать, что это степень «увеличение» лупы — анализируем 14 последних свечей, чтобы оценить относительную силу актива и выявить дивергенции.
  • PRICE_CLOSE: как и в скользящей средней, рассчитываем RSI по ценам закрытия. Для расчета используется конец каждого периода, чтобы получить точные данные.

Рисунок 4. Свойства RSI

Настройка iStochastic (Стохастик)

stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH);

  • _Symbol: указывает, какой актив анализируется.
  • PERIOD_CURRENT: текущий таймфрейм графика.
  • 5, 3 и 3: параметры Стохастика:
  • 5 — период %K: определяет чувствительность индикатора к изменению цены. Он анализирует последние 5 периодов и показывает, где находится цена относительно своего диапазона.
  • 3 — период %D: сглаженная версия %K, которая помогает получить более четкий сигнал. 
  • 3 — период замедления: дополнительное сглаживание, уменьшающее чувствительность к мелким колебаниям и уменьшающее количество ложных сигналов.
  • MODE_SMA: метод усреднения для сглаживания значений %K и %D — простая скользящая средняя (SMA). SMA — надежный и понятный способ сглаживания данных.
  • STO_LOWHIGH: способ расчета стохастика — по минимумам и максимумам (Low/High), а не по ценам закрытия. Анализируем диапазон движения цены (нижний минимум и высокий максимум) в каждом периоде.

Рисунок 5. Свойства осциллятора

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


3. Получение данных индикаторов и свечей

3.1. Получаем данные индикаторов

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

Пример:

//DECLEAR INDICATOR HANDLES
int ma_handle;
int rsi_handle;
int stoch_handle;

//DECLEAR DOUBLE VARIABLES THAT STORES INDICATORS DATA
double ma_buffer[];
double rsi_buffer[];
double stoch_buffer_k[], stoch_buffer_d[];

//DECLEAR VARIABLES TO STORE CANDLE DATA
double open[];
double close[];
double high[];
double low[];
datetime time[];

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

//INDICATOR PROPERTIES
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE);
   rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);
   stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH);

//START FROM THE LATEST CANDLE ON THE CHART
   ArraySetAsSeries(ma_buffer,true);
   ArraySetAsSeries(rsi_buffer,true);
   ArraySetAsSeries(stoch_buffer_k,true);
   ArraySetAsSeries(stoch_buffer_d,true);

//START FROM THE LATEST CANDLE ON THE CHART
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(time,true);

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

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

//COPY INDICATOR'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART
   CopyBuffer(ma_handle,0,1,30,ma_buffer);
   CopyBuffer(stoch_handle,0,1,30,stoch_buffer_k);
   CopyBuffer(stoch_handle,1,1,30,stoch_buffer_d);
   CopyBuffer(rsi_handle,0,1,30,rsi_buffer);

//COPY CANDLE'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART
   CopyOpen(_Symbol,PERIOD_CURRENT,1,30,open);
   CopyClose(_Symbol,PERIOD_CURRENT,1,30,close);
   CopyHigh(_Symbol,PERIOD_CURRENT,1,30,high);
   CopyLow(_Symbol,PERIOD_CURRENT,1,30,low);
   CopyTime(_Symbol,PERIOD_CURRENT,1,30,time);

  }

Пояснения:

Объявляем переменные типа double для хранения данных индикаторов

Перед тем как сохранять данные индикаторов, необходимо объявить массивы, в которых эти данные будут храниться. Поскольку наши индикаторы (Стохастик, RSI и Скользящая средняя) возвращают числовые значения, тип этих массивов должен быть double. ma_buffer[] — хранит значения Скользящей средней (Moving Average), а rsi_buffer[] содержит значения RSI. stoch_buffer_k[] и stoch_buffer_d[] — предназначены для хранения линий %K и %D стохастика. Изначально эти массивы пусты, затем они заполняются значениями индикаторов, полученных с графика.

Начинаем с последней свечи на графике

Используем функцию ArraySetAsSeries(), чтобы расположить элементы массивов таким образом, чтобы самые последние данные находились в начале каждого массива. Аргумент true в функции указывает на то, что массивы нужно заполнять, начиная с наиболее свежих значений. Это важно, потому что по умолчанию массивы в MQL5 заполняются от старых данных к новым, а нам необходимо работать с актуальными значениями в первую очередь. С таким порядком индексации проще анализировать данные, а логика принятия торговых решений становится более удобной и наглядной.

Копируем данные индикаторов в массивы

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

  • Для скользящей средней данные копируются из ma_handle в массив ma_buffer[].
  • Для Стохастика получаем значения обеих линий — %K и %D, они записываются в массивы stoch_buffer_k[] и stoch_buffer_d[].
  • Для RSI данные копируются в массив rsi_buffer[].

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

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

3.2. Данные свечей

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

Примеры:

double open[];
double close[];
double high[];
double low[];
datetime time[];

Объявляем массивы open[], close[], high[], low[] и time[], которые содержат соответственно цены открытия, закрытия, максимума, минимума и время формирования каждой свечи. Эти данные позволяют анализировать движение цены и сопоставлять его с полученной информацией индикаторов.

ArraySetAsSeries(open,true);
ArraySetAsSeries(close,true);
ArraySetAsSeries(high,true);
ArraySetAsSeries(low,true);
ArraySetAsSeries(time,true);
Чтобы последние данные свечей всегда находились в начале массива, используется функция ArraySetAsSeries(). Она перестраивает массивы так, что самая свежая свеча оказывается первой, а более старые идут далее. Это обеспечивает согласованность структуры данных между ценовыми массивами и массивами индикаторов.
CopyOpen(_Symbol,PERIOD_CURRENT,1,30,open);
CopyClose(_Symbol,PERIOD_CURRENT,1,30,close);
CopyHigh(_Symbol,PERIOD_CURRENT,1,30,high);
CopyLow(_Symbol,PERIOD_CURRENT,1,30,low);
CopyTime(_Symbol,PERIOD_CURRENT,1,30,time);

Затем функции CopyOpen(), CopyClose(), CopyHigh(), CopyLow() и CopyTime() заполняют массивы реальными рыночными данными. Мы получаем данные за последние 30 свечей, начиная со второй, чтобы данные свечей точно соответствовали значениям индикаторов. Такая синхронизация нужна для корректности анализа и точности сигналов советника.


4. Определяем Последние максимумы и минимумы RSI и сопоставляем с ценовыми данными

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

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

4.1. Поиск скрытой бычьей дивергенции

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

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

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

Пример:
//DECLEAR INDICATOR HANDLES
int ma_handle;
int rsi_handle;
int stoch_handle;

//DECLEAR DOUBLE VARIABLES THAT STORES INDICATORS DATA
double ma_buffer[];
double rsi_buffer[];
double stoch_buffer_k[], stoch_buffer_d[];

//DECLEAR VARIABLES TO STORE CANDLE DATA
double open[];
double close[];
double high[];
double low[];
datetime time[];

//DECLEAR VARIBLE ROR RSI LOW AND CORESPONDING  CANDLE
double   rsi_low_value;          // Stores the RSI value at the identified low point.
double   corresponding_low_value; // Stores the price (closing value) corresponding to the RSI low.
double   corresponding_low_ma;    // Stores the Moving Average value at the same point.
datetime rsi_low_time;           // Stores the timestamp of the RSI low.

//DECLEAR VARIABLE FOR THE MINIMUM CORRESPONDING PRICE VALUE
int  minimum_value_low;

//DECLEAR VARIBLE ROR NEW RSI LOW AND CORESPONDING  CANDLE
double new_rsi_low_value;
datetime new_rsi_low_time;
double new_corresponding_low_value;

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

//INDICATOR PROPERTIES
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE);
   rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);
   stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH);

//START FROM THE LATEST CANDLE ON THE CHART
   ArraySetAsSeries(ma_buffer,true);
   ArraySetAsSeries(rsi_buffer,true);
   ArraySetAsSeries(stoch_buffer_k,true);
   ArraySetAsSeries(stoch_buffer_d,true);

//START FROM THE LATEST CANDLE ON THE CHART
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(time,true);

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

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

//COPY INDICATOR'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART
   CopyBuffer(ma_handle,0,1,30,ma_buffer);
   CopyBuffer(stoch_handle,0,1,30,stoch_buffer_k);
   CopyBuffer(stoch_handle,1,1,30,stoch_buffer_d);
   CopyBuffer(rsi_handle,0,1,30,rsi_buffer);

//COPY CANDLE'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART
   CopyOpen(_Symbol,PERIOD_CURRENT,1,30,open);
   CopyClose(_Symbol,PERIOD_CURRENT,1,30,close);
   CopyHigh(_Symbol,PERIOD_CURRENT,1,30,high);
   CopyLow(_Symbol,PERIOD_CURRENT,1,30,low);
   CopyTime(_Symbol,PERIOD_CURRENT,1,30,time);

//LOOP THROUGH THE LAST 30 BARS ON THE CHART
   for(int i = 0; i < 30; i++)
     {
      //PREVENT ARRAY OUT OF RANGE ERROR
      if((i+1 < 30) && (i+2 < 30) && (i < 30) && (i+3 < 30) && (i+4 < 30))
        {
         //LOGIC TO IDENTIFY THE LATEST RSI LOW
         if(rsi_buffer[i+4] > rsi_buffer[i+3] && rsi_buffer[i+2] > rsi_buffer[i+3] && rsi_buffer[i+1] > rsi_buffer[i+2] && rsi_buffer[i] > rsi_buffer[i+1])
           {
            //GETTING LATEST RSI LOW, CORRESPONDING CANDLE LOW, CORRESPONDING MA VALUE, and RSI TIME
            rsi_low_value = rsi_buffer[i+3];
            corresponding_low_value = close[i+3];
            corresponding_low_ma = ma_buffer[i+3];
            rsi_low_time = time[i+3];

            break;
           }

        }
     }

//TOTAL NUMBERS OF BARS FROM THE LAST SIGNIFICANT RSI LOW
   int total_bars_2 = 0;
   total_bars_2 = Bars(_Symbol,PERIOD_CURRENT,rsi_low_time, time[0]);

//MINIMUM CLOSE PRICE FROM THE LAST SIGNIFICANT RSI LOW
   minimum_value_low = ArrayMinimum(close,0,total_bars_2);

   if(corresponding_low_value > corresponding_low_ma && close[0] > ma_buffer[0] && close[minimum_value_low] >= corresponding_low_value)
     {
      //CREATE LINES TO MARK RSI AND CORRESPONDING CANDLE LOW
      ObjectCreate(ChartID(),"RSI LOW VALUE",OBJ_TREND,1,rsi_low_time,rsi_low_value,TimeCurrent(),rsi_low_value);
      ObjectCreate(ChartID(),"C-CANDLE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,TimeCurrent(),corresponding_low_value);
      //SETTING OBJECTS COLOUR
      ObjectSetInteger(ChartID(),"RSI LOW VALUE",OBJPROP_COLOR,clrBlack);
      ObjectSetInteger(ChartID(),"C-CANDLE",OBJPROP_COLOR,clrBlack);

      //CREATE TWO LINES TO CONNECT RSI LOW AND THE CORRESPONDING PRICE ON THE CHART
      ObjectCreate(ChartID(),"C-CANDLE LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,rsi_low_time,0);
      ObjectCreate(ChartID(),"RSI LOW LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,rsi_low_time,100);
      //SETTING OBJECTS COLOUR
      ObjectSetInteger(ChartID(),"C-CANDLE LINE",OBJPROP_COLOR,clrBlack);
      ObjectSetInteger(ChartID(),"RSI LOW LINE",OBJPROP_COLOR,clrBlack);

      //CREATE TEXTS TO MART RSI LOW AND CORRESPONDING PRICE (C-PRICE)
      ObjectCreate(ChartID(),"C-CANDLE TEXT",OBJ_TEXT,0,TimeCurrent(),corresponding_low_value);
      ObjectSetString(ChartID(),"C-CANDLE TEXT",OBJPROP_TEXT,"C-PRICE");
      ObjectCreate(ChartID(),"RSI LOW TEXT",OBJ_TEXT,1,TimeCurrent(),rsi_low_value);
      ObjectSetString(ChartID(),"RSI LOW TEXT",OBJPROP_TEXT,"RSI LOW");
      //SETTING TEXT COLOUR
      ObjectSetInteger(ChartID(),"C-CANDLE TEXT",OBJPROP_COLOR,clrBlack);
      ObjectSetInteger(ChartID(),"RSI LOW TEXT",OBJPROP_COLOR,clrBlack);

      //19 LOGIC TO GET THE NEW RSI LOW
      if(rsi_buffer[0] > rsi_buffer[1] && rsi_buffer[1] < rsi_buffer[2] && rsi_buffer[1] < rsi_low_value && close[1] > corresponding_low_value)
        {
         new_rsi_low_value = rsi_buffer[1];
         new_rsi_low_time = time[1];
         new_corresponding_low_value = close[1];

        }

      //CONDITION FOR HIDDEN BULLISH DIVERGENCE
      if(rsi_low_value > new_rsi_low_value && corresponding_low_value < new_corresponding_low_value)
        {
         ObjectCreate(ChartID(),"RSI LOW TREND LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,new_rsi_low_time,new_rsi_low_value);
         ObjectCreate(ChartID(),"L",OBJ_TEXT,1,rsi_low_time,rsi_low_value);
         ObjectSetString(ChartID(),"L",OBJPROP_TEXT,"L");
         ObjectSetInteger(ChartID(),"L",OBJPROP_FONTSIZE,15);

         ObjectSetInteger(ChartID(),"RSI LOW TREND LINE",OBJPROP_COLOR,clrBlack);
         ObjectSetInteger(ChartID(),"L",OBJPROP_COLOR,clrBlack);

         ObjectCreate(ChartID(),"LL",OBJ_TEXT,1,new_rsi_low_time,new_rsi_low_value);
         ObjectSetString(ChartID(),"LL",OBJPROP_TEXT,"LL");
         ObjectSetInteger(ChartID(),"LL",OBJPROP_FONTSIZE,15);

         ObjectSetInteger(ChartID(),"LL",OBJPROP_COLOR,clrBlack);

         //for candle
         ObjectCreate(ChartID(),"C-CANDLE TREND LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,new_rsi_low_time,new_corresponding_low_value);
         ObjectCreate(ChartID(),"CL",OBJ_TEXT,0,rsi_low_time,corresponding_low_value);
         ObjectSetString(ChartID(),"CL",OBJPROP_TEXT,"L");
         ObjectSetInteger(ChartID(),"CL",OBJPROP_FONTSIZE,15);
         ObjectSetInteger(ChartID(),"C-CANDLE TREND LINE",OBJPROP_COLOR,clrBlack);
         ObjectSetInteger(ChartID(),"CL",OBJPROP_COLOR,clrBlack);

         ObjectCreate(ChartID(),"CLL",OBJ_TEXT,0,new_rsi_low_time,new_corresponding_low_value);
         ObjectSetString(ChartID(),"CLL",OBJPROP_TEXT,"HL");
         ObjectSetInteger(ChartID(),"CLL",OBJPROP_FONTSIZE,15);
         ObjectSetInteger(ChartID(),"CLL",OBJPROP_COLOR,clrBlack);

        }

     }

//LOGIC TO DELETE THE OBJECTS WHEN ITS NO LONGER RELEVEANT
   else
      if((close[0] < ma_buffer[0]) || (close[minimum_value_low] < corresponding_low_value))
        {

         ObjectDelete(ChartID(),"RSI LOW VALUE");
         ObjectDelete(ChartID(),"C-CANDLE");
         ObjectDelete(ChartID(),"C-CANDLE LINE");
         ObjectDelete(ChartID(),"RSI LOW LINE");
         ObjectDelete(ChartID(),"C-CANDLE TEXT");
         ObjectDelete(ChartID(),"RSI LOW TEXT");
         ObjectDelete(ChartID(),"RSI LOW TREND LINE");
         ObjectDelete(ChartID(),"L");
         ObjectDelete(ChartID(),"LL");
         ObjectDelete(ChartID(),"C-CANDLE TREND LINE");
         ObjectDelete(ChartID(),"CL");
         ObjectDelete(ChartID(),"RSI LOW TEXT");
         ObjectDelete(ChartID(),"CLL");

        }

}

Пояснения:

Потенциальные дивергенции можно выявить с помощью переменной rsi_low_value, которая фиксирует наименьшее значение RSI в заданном диапазоне. Переменная corresponding_low_value хранит цену закрытия свечи, в которой возник этот минимум RSI. Это обеспечивает совместимость с линейным графиком, так как линии на нем строятся по ценам закрытия. При переходе на линейный график такая синхронизация делает точки RSI и соответствующие ценовые уровни логичными и визуально согласованными. Чтобы определить, находится ли цена выше или ниже скользящей средней (MA), используется переменная corresponding_low_ma, которая хранит значение с той же точки. Наконец, переменная rsi_low_time сохраняет точное время, когда был найден минимум RSI — это позволяет строить объекты на графике, такие как линии тренда или подписи, привязанные к конкретным свечам.

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

  1. Код анализирует последние 30 баров в поисках значимого минимума RSI.
  2. Ключевое условие:
    • Значение RSI в точке i+3 должно сначала снизиться (медвежье движение с i+4 до i+3).
    • Затем должны последовать три последовательных повышения (i+3 → i+2 → i+1 → i).
  3. Как только этот паттерн обнаружен, сохраняется значение RSI в минимуме, соответствующая цена закрытия, значение MA и временная метка.

Такой минимум отражает значимое изменение импульса, когда RSI падает, а затем начинает восстанавливаться — это важный разворотный моменто.

Почему используется close[i+3]?

Если вы используете линейный график, линия на нем строится по ценам закрытия. Благодаря привязке минимумов RSI с ценами закрытия мы получаем правильное выравнивание всех отметок (например, линий тренда) на графике. Это повышает наглядность и визуальную согласованность анализа.

if(corresponding_low_value > corresponding_low_ma && close[0] > ma_buffer[0] && close[minimum_value_low] >= corresponding_low_value)

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

  • corresponding_low_value > corresponding_low_ma — цена в точке минимума RSI находится выше скользящей средней. Это указывает на то, что цена остается выше средней, что подтверждает сохранение восходящего тренда.
  • close[0] > ma_buffer[0] — текущая цена закрытия (самая последняя свеча) выше значения MA, что дополнительно подтверждает активное восходящее движение.
  • close[minimum_value_low] >= corresponding_low_value — цена в последнем значимом минимуме остается на уровне или выше предыдущего минимума. Это означает формирование более высоких минимумов, что характерно для скрытой бычьей дивергенции.

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

//CREATE LINES TO MARK RSI AND CORRESPONDING PRICE
ObjectCreate(ChartID(),"RSI LOW VALUE",OBJ_TREND,1,rsi_low_time,rsi_low_value,TimeCurrent(),rsi_low_value);
ObjectCreate(ChartID(),"C-CANDLE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,TimeCurrent(),corresponding_low_value);
//SETTING OBJECTS COLOUR
ObjectSetInteger(ChartID(),"RSI LOW VALUE",OBJPROP_COLOR,clrBlack);
ObjectSetInteger(ChartID(),"C-CANDLE",OBJPROP_COLOR,clrBlack);

//CREATE TWO LINES TO CONNECT RSI LOW AND THE CORRESPONDING PRICE ON THE CHART
ObjectCreate(ChartID(),"C-CANDLE LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,rsi_low_time,0);
ObjectCreate(ChartID(),"RSI LOW LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,rsi_low_time,100);
//SETTING OBJECTS COLOUR
ObjectSetInteger(ChartID(),"C-CANDLE LINE",OBJPROP_COLOR,clrBlack);
ObjectSetInteger(ChartID(),"RSI LOW LINE",OBJPROP_COLOR,clrBlack);

//CREATE TEXTS TO MART RSI LOW AND CORRESPONDING PRICE (C-PRICE)
ObjectCreate(ChartID(),"C-CANDLE TEXT",OBJ_TEXT,0,TimeCurrent(),corresponding_low_value);
ObjectSetString(ChartID(),"C-CANDLE TEXT",OBJPROP_TEXT,"C-PRICE");
ObjectCreate(ChartID(),"RSI LOW TEXT",OBJ_TEXT,1,TimeCurrent(),rsi_low_value);
ObjectSetString(ChartID(),"RSI LOW TEXT",OBJPROP_TEXT,"RSI LOW");
//SETTING TEXT COLOUR
ObjectSetInteger(ChartID(),"C-CANDLE TEXT",OBJPROP_COLOR,clrBlack);
ObjectSetInteger(ChartID(),"RSI LOW TEXT",OBJPROP_COLOR,clrBlack);

Здесь мы создаем визуальные элементы для обозначения минимума RSI и соответствующей ему цены. На графике рисуются линии тренда, отмечающие RSI low и соответствующую цену (C-PRICE) в той же точке. Эти линии, подписанные как "RSI LOW VALUE" и "C-CANDLE", рисуются черным цветом для визуальной четкости. Они помогают визуально связать минимум RSI с соответствующей точкой цены, что облегчает поиск дивергенций.

Далее создаются дополнительные линии, которые соединяют минимум RSI и соответствующую цену по вертикали и по времени. "C-CANDLE LINE" соединяет цену на минимуме RSI с текущем временем, а "RSI LOW LINE" продолжает минимум RSI вертикально на графике. Они служат ориентирами для анализа динамики RSI и цены и помогают увидеть расхождения между ними. Линии тренда рисуются черным цветом, чтобы обеспечить единообразие и их четкую видимость на фоне графика.

Также создаются текстовые метки для обозначения минимума RSI и соответствующей цены (минимум свечи). "RSI LOW" и "C-PRICE" обозначают уровни RSI и цены прямо на графике. Эти элементы также рисуются черным для удобства восприятия. Эти графические объекты делают анализ технических сигналов более наглядным и помогают выявлять ключевые уровни для поиска скрытых дивергенций.

Результат:

Рисунок 6. Минимум RSI и соответствующая цена

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

Логика для нового минимума RSI:


Для нового минимума RSI:

  1. RSI должен показать одно снижение (медвежье движение на одном баре).
  2. За ним следует одно повышение (начало восстановления RSI).
if(rsi_buffer[0] > rsi_buffer[1] && rsi_buffer[1] < rsi_buffer[2] && rsi_buffer[1] < rsi_low_value && close[1] > corresponding_low_value)
  {
  new_rsi_low_value = rsi_buffer[1];
  new_rsi_low_time = time[1];
  new_corresponding_low_value = close[1];

  }

Первая часть кода объявляет переменные для хранения данных о новом минимуме RSI и соответствующей цене. new_rsi_low_value — значение нового минимума RSI (для сравнения с предыдущим минимумом), new_rsi_low_time — время, когда этот минимум произошёл (для отображения на графике). new_corresponding_low_value — цена закрытия, соответствующая этому новому минимуму RSI.

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

if(rsi_low_value > new_rsi_low_value && corresponding_low_value < new_corresponding_low_value)

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

//FOR RSI
ObjectCreate(ChartID(),"RSI LOW TREND LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,new_rsi_low_time,new_rsi_low_value);
ObjectCreate(ChartID(),"L",OBJ_TEXT,1,rsi_low_time,rsi_low_value);
ObjectSetString(ChartID(),"L",OBJPROP_TEXT,"L");
ObjectSetInteger(ChartID(),"L",OBJPROP_FONTSIZE,15);
ObjectSetInteger(ChartID(),"RSI LOW TREND LINE",OBJPROP_COLOR,clrBlack);
ObjectSetInteger(ChartID(),"L",OBJPROP_COLOR,clrBlack);
ObjectCreate(ChartID(),"LL",OBJ_TEXT,1,new_rsi_low_time,new_rsi_low_value);
ObjectSetString(ChartID(),"LL",OBJPROP_TEXT,"LL");
ObjectSetInteger(ChartID(),"LL",OBJPROP_FONTSIZE,15);
ObjectSetInteger(ChartID(),"LL",OBJPROP_COLOR,clrBlack);

//FOR CORRESPONDING PRICE
ObjectCreate(ChartID(),"C-CANDLE TREND LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,new_rsi_low_time,new_corresponding_low_value);
ObjectCreate(ChartID(),"CL",OBJ_TEXT,0,rsi_low_time,corresponding_low_value);
ObjectSetString(ChartID(),"CL",OBJPROP_TEXT,"L");
ObjectSetInteger(ChartID(),"CL",OBJPROP_FONTSIZE,15);
ObjectSetInteger(ChartID(),"C-CANDLE TREND LINE",OBJPROP_COLOR,clrBlack);
ObjectSetInteger(ChartID(),"CL",OBJPROP_COLOR,clrBlack);
ObjectCreate(ChartID(),"CLL",OBJ_TEXT,0,new_rsi_low_time,new_corresponding_low_value);
ObjectSetString(ChartID(),"CLL",OBJPROP_TEXT,"HL");
ObjectSetInteger(ChartID(),"CLL",OBJPROP_FONTSIZE,15);
ObjectSetInteger(ChartID(),"CLL",OBJPROP_COLOR,clrBlack);

После того как скрытая бычья дивергенция выявлена, следующий блок кода графически обозначает ключевые аспекты на графике. В первой серии действий основной акцент делается на RSI. Первый значимый минимум RSI и последующий более низкий минимум отображаются с помощью линий тренда и текстовых меток. Код строит линию тренда между новым, более низким минимумом RSI (new_rsi_low_value) и исходным минимумом RSI (rsi_low_value). Эта линия помогает трейдерам визуально определить нисходящий тренд RSI. Также добавляются подписи "L" и "LL" — "L" обозначает первоначальный минимум, а "LL" — более низкий минимум.

Следующие действия связаны с соответствующими ценовыми уровнями. Аналогично RSI, создаются линии тренда и текстовые метки, соединяющие первый ценовой минимум (corresponding_low_value) с новым ценовым минимумом (new_corresponding_low_value). Цена в этом случае должна образовать более высокий минимум — обязательно для определения скрытой бычьей дивергенции. Линия тренда соединяет эти два уровня, а подписи L и HL обозначают первый значимый минимум и более высокий минимум цены соответственно. Эти визуальные элементы подтверждают, что цена формирует ожидаемый более высокий минимум, тогда как RSI показывает более низкий минимум — классический сигнал скрытой бычьей дивергенции.

Функция ObjectDelete() отвечает за удаление визуальных объектов с графика, когда условия для скрытой бычьей дивергенции больше не выполняются. Например, если текущая цена закрытия опускается ниже скользящей средней (ma_buffer[0]) или минимальная цена с момента последнего значимого минимума RSI оказывается ниже соответствующего ценового минимума (corresponding_low_value), все ранее нарисованные линии и метки удаляются. График остается чистым, а вы можете сосредоточиться на актуальных и значимых рыночных сигналах.

Результат:

Рисунок 7. Минимумы и новые минимумы

4.2. Поиск скрытой медвежьей дивергенции

Скрытая медвежья дивергенция возникает, когда RSI формирует более высокий максимум, а цена — более низкий максимум, сигнализируя о потенциальном снижении, несмотря на временный рост цены. Для выявления такого паттерна определяется последний максимум RSI по следующей логике: максимум должен возникнуть после как минимум одного бычьего движения, за которым следуют три последовательных медвежьих движения, что указывает на значимый пик импульса. После идентификации максимума RSI извлекаются соответствующая цена и значение Скользящей средней (MA), чтобы убедиться в согласованности с преобладающим медвежьим трендом. Для корректной скрытой медвежьей дивергенции необходимо, чтобы максимум цены оставался ниже MA, подтверждая нисходящую направленность.

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

Пример:

//DECLEAR VARIBLE ROR RSI HIGH AND CORESPONDING  PRICE
double   rsi_high_value;
double   corresponding_high_value;
double   corresponding_high_ma;
datetime rsi_high_time;

//DECLEAR VARIABLE FOR THE MAXIMUM CORRESPONDING PRICE VALUE
int maximum_value_high;

// DECLEAR VARIBLE ROR NEW RSI HIGH AND CORESPONDING PRICE
double new_rsi_high_value;
datetime new_rsi_high_time;
double new_corresponding_high_value;
//LOOP THROUGH THE LAST 30 BARS ON THE CHART
for(int i = 0; i < 30; i++)
  {
//PREVENT ARRAY OUT OF RANGE ERROR
   if((i+1 < 30) && (i+2 < 30) && (i < 30) && (i+3 < 30) && (i+4 < 30))
     {
      //LOGIC TO IDENTIFY THE LATEST RSI HIGH
      if(rsi_buffer[i+4] < rsi_buffer[i+3] && rsi_buffer[i+2] < rsi_buffer[i+3] && rsi_buffer[i+1] < rsi_buffer[i+2] && rsi_buffer[i] < rsi_buffer[i+1])
        {
         //GETTING LATEST RSI HIGH, CORRESPONDING PRICE, CORRESPONDING MA VALUE, and RSI TIME
         rsi_high_value = rsi_buffer[i+3];
         corresponding_high_value = close[i+3];
         corresponding_high_ma = ma_buffer[i+3];
         rsi_high_time = time[i+3];

         break;
        }

     }
  }

//TOTAL NUMBERS OF BARS FROM THE LAST SIGNIFICANT RSI HIGH
int total_bars_3 = 0;
total_bars_3 = Bars(_Symbol,PERIOD_CURRENT,time[0],rsi_high_time);

//MAXIMUM CLOSE PRICE FROM THE LAST SIGNIFICANT RSI LOW
maximum_value_high = ArrayMaximum(close,0,total_bars_3);

if(corresponding_high_value < corresponding_high_ma && close[0] < ma_buffer[0] && close[maximum_value_high] <= corresponding_high_value)
  {
//CREATE LINES TO MARK RSI AND CORRESPONDING PRICE
   ObjectCreate(ChartID(),"RSI HIGH VALUE",OBJ_TREND,1,rsi_high_time,rsi_high_value,TimeCurrent(),rsi_high_value);
   ObjectCreate(ChartID(),"C-CANDLE HIGH",OBJ_TREND,0,rsi_high_time,corresponding_high_value,TimeCurrent(),corresponding_high_value);
//SETTING OBJECTS COLOUR
   ObjectSetInteger(ChartID(),"RSI HIGH VALUE",OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(ChartID(),"C-CANDLE HIGH",OBJPROP_COLOR,clrBlack);

//CREATE TWO LINES TO CONNECT RSI HIGH AND THE CORRESPONDING PRICE ON THE CHART
   ObjectCreate(ChartID(),"C-CANDLE LINE HIGH",OBJ_TREND,0,rsi_high_time,corresponding_high_value,rsi_high_time,0);
   ObjectCreate(ChartID(),"RSI HIGH LINE",OBJ_TREND,1,rsi_high_time,rsi_high_value,rsi_high_time,100);
//SETTING OBJECTS COLOUR
   ObjectSetInteger(ChartID(),"C-CANDLE LINE HIGH",OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(ChartID(),"RSI HIGH LINE",OBJPROP_COLOR,clrBlack);

//CREATE TEXTS TO MART RSI HIGH AND CORRESPONDING PRICE (C-PRICE)
   ObjectCreate(ChartID(),"C-CANDLE TEXT HIGH",OBJ_TEXT,0,TimeCurrent(),corresponding_high_value);
   ObjectSetString(ChartID(),"C-CANDLE TEXT HIGH",OBJPROP_TEXT,"C-PRICE");
   ObjectCreate(ChartID(),"RSI HIGH TEXT",OBJ_TEXT,1,TimeCurrent(),rsi_high_value);
   ObjectSetString(ChartID(),"RSI HIGH TEXT",OBJPROP_TEXT,"RSI HIGH");

//SETTING TEXT COLOUR
   ObjectSetInteger(ChartID(),"C-CANDLE TEXT HIGH",OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(ChartID(),"RSI HIGH TEXT",OBJPROP_COLOR,clrBlack);

//LOGIC TO GET THE NEW RSI HIGH
   if(rsi_buffer[0] < rsi_buffer[1] && rsi_buffer[1] > rsi_buffer[2] && rsi_buffer[1] > rsi_high_value && close[1] < corresponding_high_value)
     {

      new_rsi_high_value = rsi_buffer[1];
      new_rsi_high_time = time[1];
      new_corresponding_high_value = close[1];

     }

   if(rsi_high_value < new_rsi_high_value && corresponding_high_value > new_corresponding_high_value)
     {
      //for rsi
      ObjectCreate(ChartID(),"RSI HIGH TREND LINE",OBJ_TREND,1,rsi_high_time,rsi_high_value,new_rsi_high_time,new_rsi_high_value);
      ObjectCreate(ChartID(),"H",OBJ_TEXT,1,rsi_high_time,rsi_high_value);
      ObjectSetString(ChartID(),"H",OBJPROP_TEXT,"H");
      ObjectSetInteger(ChartID(),"H",OBJPROP_FONTSIZE,15);
      ObjectSetInteger(ChartID(),"RSI HIGH TREND LINE",OBJPROP_COLOR,clrBlack);
      ObjectSetInteger(ChartID(),"H",OBJPROP_COLOR,clrBlack);
      ObjectCreate(ChartID(),"HH",OBJ_TEXT,1,new_rsi_high_time,new_rsi_high_value);
      ObjectSetString(ChartID(),"HH",OBJPROP_TEXT,"HH");
      ObjectSetInteger(ChartID(),"HH",OBJPROP_FONTSIZE,15);
      ObjectSetInteger(ChartID(),"HH",OBJPROP_COLOR,clrBlack);

      //for candle
      ObjectCreate(ChartID(),"C-CANDLE TREND LINE HIGH",OBJ_TREND,0,rsi_high_time,corresponding_high_value,new_rsi_high_time,new_corresponding_high_value);
      ObjectCreate(ChartID(),"CH",OBJ_TEXT,0,rsi_high_time,corresponding_high_value);
      ObjectSetString(ChartID(),"CH",OBJPROP_TEXT,"H");
      ObjectSetInteger(ChartID(),"CH",OBJPROP_FONTSIZE,15);
      ObjectSetInteger(ChartID(),"C-CANDLE TREND LINE HIGH",OBJPROP_COLOR,clrBlack);
      ObjectSetInteger(ChartID(),"CH",OBJPROP_COLOR,clrBlack);
      ObjectCreate(ChartID(),"CLH",OBJ_TEXT,0,new_rsi_high_time,new_corresponding_high_value);
      ObjectSetString(ChartID(),"CLH",OBJPROP_TEXT,"LH");
      ObjectSetInteger(ChartID(),"CLH",OBJPROP_FONTSIZE,15);
      ObjectSetInteger(ChartID(),"CLH",OBJPROP_COLOR,clrBlack);
     }

  }

//LOGIC TO DELETE THE OBJECTS WHEN ITS NO LONGER RELEVEANT
else
   if((close[0] > ma_buffer[0]) || (close[maximum_value_high] > corresponding_high_value))
     {

      ObjectDelete(ChartID(),"RSI HIGH VALUE");
      ObjectDelete(ChartID(),"C-CANDLE HIGH");
      ObjectDelete(ChartID(),"C-CANDLE LINE HIGH");
      ObjectDelete(ChartID(),"RSI HIGH LINE");
      ObjectDelete(ChartID(),"C-CANDLE TEXT HIGH");
      ObjectDelete(ChartID(),"RSI HIGH TEXT");
      ObjectDelete(ChartID(),"RSI HIGH TREND LINE");
      ObjectDelete(ChartID(),"H");
      ObjectDelete(ChartID(),"HH");
      ObjectDelete(ChartID(),"C-CANDLE TREND LINE HIGH");
      ObjectDelete(ChartID(),"CH");
      ObjectDelete(ChartID(),"CLH");

     }

Пояснения:

Код предназначен для определения скрытой медвежьей дивергенции, используя максимумы RSI и соответствующие максимумы цен, по логике, аналогичной разделу 4.1 (скрытая бычья дивергенция). Поиск скрытой бычьей дивергенции. Большая часть логики уже была объяснена в пункте 4.1. Основное отличие — фокус на медвежьем сценарии, а не на бычьем. Для этого работаем с максимумами RSI, а не минимумами.

Первая часть кода объявляет переменные rsi_high_value — значение максимума RSI, corresponding_high_value — соответствующий ценовой максимум, corresponding_high_ma — значение MA. Алгоритм находит последний максимум RSI, проходя последние 30 баров и применяя условие: одно бычье движение, за которым следуют три медвежьих. После нахождения максимума RSI извлекаются соответствующие цена и MA, а также фиксируется временная метка (rsi_high_time). Валидация проверяет, что ценовой максимум находится ниже MA, подтверждая медвежий тренд.

Рисунок 8. RSI и соответствующий максимум

Затем определяется новый максимум RSI, при котором RSI формирует более высокий максимум, а цена — более низкий максимум (LH). Это удовлетворяет условиям скрытой медвежьей дивергенции, сигнализируя об ослаблении бычьего импульса. Если условие выполнено, создаются линии тренда и метки для визуального обозначения RSI (H и HH) и соответствующих ценовых максимумов (H и LH) на графике. Они помогают трейдерам быстрее определять потенциальные скрытые точки медвежьей дивергенции.

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

Рисунок 9. Максимумы и новые максимумы


5. Подключение стохастика и исполнение сделок

Стохастический осциллятор используется как финальный инструмент подтверждения, после того как мы определили скрытые бычьи и медвежьи дивергенции с помощью RSI и MA. Осциллятор позволяет убедиться, что дивергенция совпадает с изменениями рыночного импульса, повышая точность входа в сделку. Для обоих типов дивергенций подтверждение основывается на поведении осциллятора относительно уровней перекупленности (80) и перепроданности (20), а также на строгих правилах тайминга и управления риском.

5.1. Подключение Стохастического осциллятора

После подтверждения скрытой бычьей дивергенции RSI и движением цены, мы отслеживаем, чтобы стохастик упал ниже перепроданности (20), а затем вновь пересек этот уровень снизу вверх. Такое пересечение должно произойти в течение 11 баров после выявления дивергенции для подтверждения сигнала. При подтверждении открывается покупка, SL устанавливается на последний локальный минимум, а TP — по заранее заданному соотношению риска к прибыли (RRR).

Пример:

if(total_bars < 11)
  {
   if(stoch_buffer_k[0] < 20 && stoch_buffer_k[1] > 20 && currentBarTime != lastTradeBarTime && totalPositions < 1)
     {
      //
     }
  }

Пояснения:

Код подтверждает скрытую бычью дивергенцию с помощью стохастического осциллятора. Код проверяет количество баров с момента минимума RSI (new_rsi_low_time) с помощью функции Bars() и убеждается, что сигнал остается действительным в пределах 11 баров. В течение этого времени проверяется, пересекла ли линия %K осциллятора уровень 20 снизу вверх (stoch_buffer_k[1] < 20 и stoch_buffer_k[0] > 20). Это подтверждает потенциальный восходящий импульс, усиливая бычий сигнал. Если оба условия выполняются, код выполняет логику открытия сделки, подтверждая скрытую бычью дивергенцию.

Аналогично, для скрытой медвежьей дивергенции осциллятор проверяется на пересечение уровня 80 сверху вниз, также в пределах 11 баров. При подтверждении открывается сделка на покупку, TP рассчитывается по заданному RRR, а SL ставится на последний локальный максимум. Как видите, сделки совершаются с управлением рисками и с анализом импульса.

Пример:

int total_bars = Bars(_Symbol,PERIOD_CURRENT,new_rsi_high_time,TimeCurrent());

if(total_bars < 11)
  {
   if(stoch_buffer_k[0] < 80 && stoch_buffer_k[1] > 80)
     {
      //
     }
  }

Пояснения:

Переменная total_bars определяет количество баров между текущим временем (TimeCurrent()) и временем нового максимума RSI (new_rsi_high_time), чтобы удостовериться, что сигнал еще активен. Затем код определяет, опустилась ли линия %K стохастического осциллятора (stoch_buffer_k) ниже порога перекупленности (80) на текущем баре (stoch_buffer_k[0] < 80) и выше ли него на предыдущем баре (stoch_buffer_k[1] > 80). После проверки условий код выполняет необходимую логику входа в сделку.

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

5.2.1. Локальный минимум и максимум

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

Для установки стоп-лосса (SL) необходимо определить последний локальный максимум и минимум. Минимум используется для стоп-лосса для скрытой бычьей дивергенции, а максимум — для стоп-лосса для скрытой медвежьей дивергенции. Порог риска устанавливается в зависимости от предыдущего поведения цены.

Пример:

double last_high;
double last_low;

//LOGIC FOR LAST HIGH
for(int i = 0; i < 30; i++)
  {
   if(i < 30 && i+1 < 30)
     {
      if(close[i] < open[i] && close[i+1] > open[i+1])
        {

         last_high = MathMax(high[i],high[i+1]);

         break;
        }
     }

  }

//LOGIC FOR LAST LOW
for(int i = 0; i < 30; i++)
  {
   if(i < 30 && i+1 < 30)
     {
      if(close[i] > open[i] && close[i+1] < open[i+1])
        {

         last_low = MathMin(low[i],low[i+1]);

         break;
        }
     }
  }
  }

Пояснения:

Для определения последних максимумов и минимумов анализируются предыдущие 30 баров. Высший уровень (last_high) определяется через медвежью свечу, за которой следует бычья, а низший (last_low) — через бычью свечу, за которой следует медвежья. Эти значения критичны для корректного управления риском.

5.2.2. Управление исполнением сделок и предотвращение овертрейдинга

Система разрешает только одну открытую позицию одновременно, чтобы избежать переторговли и избыточного риска. Код проверяет наличие открытых позиций перед созданием новой. Это предотвратит исполнение сделок по каждому сигналу. Для управления сделками используется библиотека #include <Trade/Trade.mqh>, предоставляющая доступ к классу CTrade.

Пример:

#include <Trade/Trade.mqh>
CTrade trade;
input int MagicNumber   = 9097; //MAGIC NUMBER

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

//SET MAGIC NUMBER
   trade.SetExpertMagicNumber(MagicNumber);

   return(INIT_SUCCEEDED);
  }

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

// Getting total positions
   int totalPositions = 0;

   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(ChartID()))
        {
         totalPositions++;
        }
     }
  }

Пояснения:

Код начинается с включения библиотеки Trade.mqh, которая дает доступ к торговым функциям в MetaTrader. Создается объект CTrade trade. Затем определяется MagicNumber (в данном случае 9097) для уникальной идентификации сделок этого советника. В OnInit() применяется SetExpertMagicNumber(MagicNumber), чтобы советник распознавал свои сделки при открытии, изменении или закрытии.

В функции OnTick() проверяется общее количество открытых позиций для текущего символа функцией PositionsTotal(). Она получает номер тикета каждой позиции с помощью PositionGetTicket(i) и проверяет, совпадает ли магический номер позиции с магическим номером советника, с помощью PositionGetInteger(POSITION_MAGIC). Каждая позиция проверяется на совпадение magic number и символа. если оба условия выполняются, увеличивается счетчик totalPositions, что гарантирует, что одновременно открыта только одна позиция для данного советника.

5.2.3. Управление рисками

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

Пример:

//INPUTS
input double risk_amount = 20; // $ PER TRADE
input double rrr = 4; //RRR
input int MagicNumber   = 9097; //MAGIC NUMBER

double take_profit;
double points_risk;
double lot_size;
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//BULLISH HIDDEN DIVERGENCE
   if(stoch_buffer_k[0] > 20 && stoch_buffer_k[1] < 20 && currentBarTime != lastTradeBarTime && totalPositions < 1)
     {
      take_profit  = ((ask_price - last_low) * rrr) + ask_price;
      points_risk = ask_price - low[0];
      lot_size = CalculateLotSize(_Symbol, risk_amount, points_risk);

      trade.Buy(lot_size,_Symbol,ask_price,last_low,take_profit);
      lastTradeBarTime = currentBarTime;

     }

//BEARISH HIDDEN DIVERGENCE
   if(stoch_buffer_k[0] < 80 && stoch_buffer_k[1] > 80 && currentBarTime != lastTradeBarTime && totalPositions < 1)
     {
      take_profit  = MathAbs(((last_high - ask_price) * rrr) - ask_price);
      points_risk = high[0] - ask_price;
      lot_size = CalculateLotSize(_Symbol, risk_amount, points_risk);

      trade.Sell(lot_size,_Symbol,ask_price,last_high,take_profit);
      lastTradeBarTime = currentBarTime;

     }

  }

//+-----------------------------------------------------------------------+
//| Function to calculate the lot size based on risk amount and stop loss |
//+-----------------------------------------------------------------------+
double CalculateLotSize(string symbol, double riskAmount, double stopLossPips)
  {
// Get symbol information
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);

// Calculate pip value per lot
   double pipValuePerLot = tickValue / point;

// Calculate the stop loss value in currency
   double stopLossValue = stopLossPips * pipValuePerLot;

// Calculate the lot size
   double lotSize = riskAmount / stopLossValue;

// Round the lot size to the nearest acceptable lot step
   double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   lotSize = MathFloor(lotSize / lotStep) * lotStep;

// Ensure the lot size is within the allowed range
   double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);

   return lotSize;
  }

Пояснения:

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

Для открытия сделки на покупку линия %K стохастического осциллятора должна подняться выше уровня 20 после того, как опускалась ниже него. Сделка выполняется только если нет открытых позиций (totalPositions < 1) и текущая свеча отличается от последней свечи, по которой была выполнена сделка. Стоп-лосс (SL) устанавливается на последнем локальном минимуме, а тейк-профит (TP) рассчитывается с использованием RRR и расстояния между ценой ask и минимумом swing low. Размер лота определяется через функцию CalculateLotSize, исходя из суммы риска и расстояния до стоп-лосса. После выполнения сделки переменная lastTradeBarTime обновляется, чтобы предотвратить повторное открытие сделки на той же свече.

Рисунок 10. Покупка (BUY)

Для открытия сделки на продажу линия %K стохастика должна сначала находиться выше уровня 80 (перекупленность), а затем пересечь его сверху вниз. Сделка выполняется только если нет открытых позиций и если это новая свеча. Тейк-профит (TP) определяется с использованием RRR и разницы между ценой ask и последним swing high (last_high). Стоп-лосс (SL) устанавливается на последнем локальном максимуме swing high. Размер лота вычисляется через CalculateLotSize. Чтобы исключить дублирование сделок, после выполнения продажи обновляется lastTradeBarTime.

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

Рисунок 11. Продажа (SELL)


Заключение

В этой статье мы рассмотрели, как находить скрытые бычьи и медвежьи дивергенции с MQL5 и как по ним торговать. Для поиска паттернов мы использовали индикаторы Moving Average, RSI и Stochastic Oscillator. Так же мы уделили внимание управлению рисками, исполнению сделок и автоматизации — все, что необходимо трейдеру для эффективного поиска паттернов на рынке. Этот материал представлен в образовательных целях для изучения основ поиска расхождений и системного подхода к торговле. Для тестирования подхода на практике к статье приложен подробный исходный код с комментариями. Его можно использовать как готовый ориентир для реализации описанных концепций.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Oluwatosin Mary Babalola
Oluwatosin Mary Babalola | 6 февр. 2025 в 19:12
Спасибо.
Israel Pelumi Abioye
Israel Pelumi Abioye | 6 февр. 2025 в 19:42
Oluwatosin Mary Babalola #:
Спасибо.
Пожалуйста.
Jee_Pee
Jee_Pee | 18 февр. 2025 в 15:52
Здравствуйте, Израиль, спасибо, что уделяете так много времени понятным проектам, особенно мне нравятся ваши аналогии, вы выполняете свое обещание "делать все маленькими шагами". Спасибо, я собираюсь начать проект сейчас (11), и я очень взволнован, я дам вам знать, Джон.
Israel Pelumi Abioye
Israel Pelumi Abioye | 18 февр. 2025 в 17:23
Jee_Pee проектам, особенно мне нравятся ваши аналогии, вы выполняете свое обещание "делать все маленькими шагами". Спасибо, я собираюсь начать проект сейчас (11), и я очень взволнован, я дам вам знать, Джон.

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

Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (I) Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (I)
В этом обсуждении рассматриваются проблемы, возникающие при работе с большими базами кодов. Мы рассмотрим лучшие практики организации кода в MQL5 и реализуем практический подход для повышения читаемости и масштабируемости исходного кода нашей панели торгового администратора. Кроме того, мы начнем разработку повторно используемых компонентов кода, которые потенциально могут принести пользу другим разработчикам при создании алгоритмов. Присоединяйтесь к обсуждению.
Выборочные методы MCMC — Алгоритм Метрополиса-Гастингса Выборочные методы MCMC — Алгоритм Метрополиса-Гастингса
Алгоритм Метрополиса-Гастингса — фундаментальный метод Монте-Карло по схеме марковских цепей (MCMC), широко применяемый для аппроксимации апостериорных распределений в байесовском выводе. Статья описывает теоретические основы алгоритма, реализацию класса MHSampler на MQL5 и примеры применения с анализом полученных выборок.
Автоматизация торговых стратегий на MQL5 (Часть 17): Освоение стратегии скальпинга Grid-Mart с динамической информационной панелью Автоматизация торговых стратегий на MQL5 (Часть 17): Освоение стратегии скальпинга Grid-Mart с динамической информационной панелью
В настоящей статье мы рассмотрим стратегию скальпинга Grid-Mart, автоматизировав ее на MQL5 с помощью динамической информационной панели для получения информации о торговле в режиме реального времени. Мы подробно описываем логику мартингейла на основе сетки, а также функции управления рисками. Мы также проводим тестирование на истории и развертывание для обеспечения надежной работы.
Автоматизация торговых стратегий на MQL5 (Часть 5): Разработка стратегии Adaptive Crossover RSI Trading Suite Автоматизация торговых стратегий на MQL5 (Часть 5): Разработка стратегии Adaptive Crossover RSI Trading Suite
В этой статье мы разработаем систему Adaptive Crossover RSI Trading Suite, которая использует пересечения скользящих средних с периодами 14 и 50 в качестве сигналов, подтверждаемых фильтром RSI с периодом 14. Система включает в себя фильтр торговых дней, стрелки сигналов с пояснениями и дашборд для мониторинга в реальном времени. Такой подход обеспечивает точность и адаптивность автоматической торговли.