Автоматизация торговых стратегий на MQL5 (Часть 37): Поиск дивергенций и конвергенций RSI с визуализацией на графике
Введение
В нашей предыдущей статье (Часть 36), мы разработали торговую систему на основе зон спроса и предложения на языке MetaQuotes Language 5 (MQL5), которая определяла зоны спроса и предложения по диапазонам консолидации, подтверждала их импульсными движениями и открывала позиции при повторном тесте зон с подтверждением тренда и настраиваемыми параметрами риска. В Части 37 мы разработаем систему поиска обычных дивергенций RSI с визуализацией на графике. Система определяет обычные бычьи и медвежьи дивергенции между свинговыми точками цены и значениями индекса относительной силы (RSI), открывает позиции по сигналам с возможностью настройки параметров риск-менеджмента и добавляет на график визуальные элементы для более удобного анализа. Мы рассмотрим следующие темы:
К концу статьи у вас будет готовая к дальнейшей настройке стратегия MQL5 для торговли по обычным дивергенциям RSI. Приступим!
Принцип работы стратегии обычных дивергенций RSI
Стратегия обычных дивергенций RSI направлена на поиск возможных разворотов тренда по расхождениям между поведением цены и осциллятором индекса относительной силы (RSI), который измеряет силу ценового импульса. При бычьей дивергенции цена формирует более низкий минимум, а RSI — более высокий минимум, что указывает на ослабление нисходящего импульса и вероятный разворот вверх. При медвежьей дивергенции цена формирует более высокий максимум, а RSI — более низкий максимум, что указывает на ослабление восходящего импульса и возможный разворот вниз.
Для поиска этих моделей используются подтверждённые свинговые точки, определяемые по заданному числу баров. Чтобы считать дивергенцию «чистой», вводится допуск: промежуточные значения RSI не должны пересекать линию между двумя опорными точками. При обнаружении бычьей дивергенции открываем длинную позицию на закрытии подтверждающего бара, а при медвежьей дивергенции открываем короткую позицию на закрытии подтверждающего бара. Для управления риском используются заранее заданные уровни Stop Loss и Take Profit, а также динамический трейлинг-стоп. Ниже показаны возможные варианты формирования дивергенций.
Пример формирования бычьей дивергенции:

Пример формирования медвежьей дивергенции:

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

Реализация на MQL5
Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в окно «Навигатор», найдите папку Experts, нажмите кнопку «Создать» (New) и следуйте указаниям мастера. После создания файла объявим входные параметры и глобальные переменные, которые будут использоваться во всей программе.
//+------------------------------------------------------------------+ //| RSI Regular Divergence Convergence EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "RSI Settings" input int RSI_Period = 14; // Период RSI input ENUM_APPLIED_PRICE RSI_Applied = PRICE_CLOSE; // Применяемая цена RSI input group "Swing Settings" input int Swing_Strength = 5; // Количество баров для подтверждения свингового максимума/минимума input int Min_Bars_Between = 5; // Минимальное количество баров между свинговыми точками для дивергенции input int Max_Bars_Between = 50; // Максимальное количество баров между свинговыми точками для дивергенции input double Tolerance = 0.1; // Допуск для проверки «чистой» дивергенции input group "Trade Settings" input double Lot_Size = 0.01; // Фиксированный размер лота input int Magic_Number = 123456789; // Магический номер input double SL_Pips = 300.0; // Stop Loss в пунктах (pips); 0 — отключить input double TP_Pips = 300.0; // Take Profit в пунктах (pips); 0 — отключить input group "Trailing Stop Settings" input bool Enable_Trailing_Stop = true; // Включить трейлинг-стоп input double Trailing_Stop_Pips = 30.0; // Трейлинг-стоп в пунктах (pips) input double Min_Profit_To_Trail_Pips = 50.0; // Минимальная прибыль в пунктах (pips) для запуска трейлинг-стопа input group "Visualization" input color Bull_Color = clrGreen; // Цвет бычьей дивергенции input color Bear_Color = clrRed; // Цвет медвежьей дивергенции input color Swing_High_Color = clrRed; // Цвет подписей свинговых максимумов input color Swing_Low_Color = clrGreen; // Цвет подписей свинговых минимумов input int Line_Width = 2; // Толщина линии дивергенции input ENUM_LINE_STYLE Line_Style = STYLE_SOLID; // Стиль линии дивергенции input int Font_Size = 8; // Размер шрифта подписей свинговых точек //+------------------------------------------------------------------+ //| Дескрипторы индикаторов и торговый объект | //+------------------------------------------------------------------+ int RSI_Handle = INVALID_HANDLE; //--- Дескриптор индикатора RSI CTrade obj_Trade; //--- Торговый объект для управления позициями
Начнём с подключения торговой библиотеки директивой «#include<Trade\Trade.mqh>», которая предоставляет встроенные средства для управления позициями и ордерами. Затем объявим входные параметры, сгруппированные по категориям, для удобной настройки. В группе «RSI Settings» параметр «RSI_Period» равен 14 и задаёт период расчёта RSI, а «RSI_Applied» имеет значение PRICE_CLOSE чтобы рассчитывать индикатор по ценам закрытия. В группе «Swing Settings» параметр «Swing_Strength» равен 5 и задаёт количество баров для подтверждения свинговых максимумов и минимумов. «Min_Bars_Between» и «Max_Bars_Between» ограничивают расстояние между свингами диапазоном от 5 до 50 баров, а «Tolerance» со значением 0,1 задаёт небольшой допуск для проверки чистой дивергенции.
В группе «Trade Settings» параметр «Lot_Size» равен 0,01 и задаёт фиксированный объём позиции, «Magic_Number» со значением 123456789 служит идентификатором сделок, а «SL_Pips» и «TP_Pips» со значением 300,0 задают расстояния Stop Loss и Take Profit; при значении 0 соответствующий уровень отключается. В группе «Trailing Stop Settings» параметр «Enable_Trailing_Stop» включает трейлинг-стоп, «Trailing_Stop_Pips» задаёт расстояние 30 пунктов, а «Min_Profit_To_Trail_Pips» — минимальную прибыль 50 пунктов для его запуска. В группе «Visualization» задаются цвета линий и подписей, ширина и стиль линий дивергенции, а также размер шрифта.
Наконец, объявим глобальные переменные: «RSI_Handle», инициализированную значением «INVALID_HANDLE» для хранения дескриптора RSI, и «obj_Trade» — экземпляр класса CTrade для выполнения торговых операций. Далее объявим и инициализируем глобальные переменные свинговых точек.
//+------------------------------------------------------------------+ //| Переменные свинговых точек | //+------------------------------------------------------------------+ double Last_High_Price = 0.0; //--- Цена последнего свингового максимума datetime Last_High_Time = 0; //--- Время последнего свингового максимума double Prev_High_Price = 0.0; //--- Цена предыдущего свингового максимума datetime Prev_High_Time = 0; //--- Время предыдущего свингового максимума double Last_Low_Price = 0.0; //--- Цена последнего свингового минимума datetime Last_Low_Time = 0; //--- Время последнего свингового минимума double Prev_Low_Price = 0.0; //--- Цена предыдущего свингового минимума datetime Prev_Low_Time = 0; //--- Время предыдущего свингового минимума double Last_High_RSI = 0.0; //--- Значение RSI последнего свингового максимума double Prev_High_RSI = 0.0; //--- Значение RSI предыдущего свингового максимума double Last_Low_RSI = 0.0; //--- Значение RSI последнего свингового минимума double Prev_Low_RSI = 0.0; //--- Значение RSI предыдущего свингового минимума
В разделе «Переменные свинговых точек» объявим глобальные переменные для хранения последней и предыдущей свинговой точки отдельно для максимумов и минимумов. Переменные «Last_High_Price», «Last_High_Time», «Prev_High_Price» и «Prev_High_Time» хранят цены и время свинговых максимумов; аналогичные переменные с «Low» — данные свинговых минимумов. Значения RSI в этих точках сохраняются в «Last_High_RSI», «Prev_High_RSI», «Last_Low_RSI» и «Prev_Low_RSI». Все переменные инициализируются нулём и затем динамически обновляются при поиске дивергенций. Теперь инициализируем советник и индикатор RSI, а также найдём подокно RSI для последующей отрисовки объектов.
//+------------------------------------------------------------------+ //| Функция инициализации советника | //+------------------------------------------------------------------+ int OnInit() { RSI_Handle = iRSI(_Symbol, _Period, RSI_Period, RSI_Applied); //--- Создаём дескриптор RSI if (RSI_Handle == INVALID_HANDLE) { //--- Проверяем ошибку создания RSI Print("Failed to create RSI indicator"); //--- Выводим ошибку в журнал return(INIT_FAILED); //--- Возвращаем ошибку инициализации } long chart_id = ChartID(); //--- Получаем идентификатор текущего графика string rsi_name = "RSI(" + IntegerToString(RSI_Period) + ")"; //--- Формируем имя индикатора RSI int rsi_subwin = ChartWindowFind(chart_id, rsi_name); //--- Находим подокно RSI if (rsi_subwin == -1) { //--- Проверяем отсутствие подокна RSI if (!ChartIndicatorAdd(chart_id, 1, RSI_Handle)) { //--- Добавляем RSI в подокно графика Print("Failed to add RSI indicator to chart"); //--- Выводим ошибку в журнал } } obj_Trade.SetExpertMagicNumber(Magic_Number); //--- Задаём магический номер торгового объекта Print("RSI Divergence EA initialized"); //--- Выводим сообщение об успешной инициализации return(INIT_SUCCEEDED); //--- Возвращаем успешную инициализацию }
В обработчике OnInit сначала создадим дескриптор RSI с помощью iRSI, передав текущий символ, таймфрейм, период RSI и тип применяемой цены. Затем проверим, не равен ли «RSI_Handle» значению «INVALID_HANDLE». При ошибке выведем сообщение через «Print» и вернём «INIT_FAILED». После этого получим идентификатор текущего графика с помощью ChartID и сформируем имя индикатора RSI, объединив строку «RSI(» с периодом, преобразованным функцией IntegerToString .
Подокно RSI найдём с помощью ChartWindowFind по идентификатору графика и имени. Если подокно не найдено («rsi_subwin» == -1), добавим индикатор в подокно 1 через ChartIndicatorAdd. При ошибке выведем соответствующее сообщение. Затем вызовем «obj_Trade.SetExpertMagicNumber(Magic_Number)», чтобы присвоить позициям и торговым операциям советника уникальный идентификатор. В завершение выведем сообщение об успешной инициализации и вернём «INIT_SUCCEEDED». После запуска видим следующий результат.

После инициализации советника и добавления RSI в отдельное подокно необходимо находить и отображать свинговые точки, чтобы использовать их для определения дивергенций RSI. Для этого создадим вспомогательные функции.
//+------------------------------------------------------------------+ //| Проверка свингового максимума | //+------------------------------------------------------------------+ bool CheckSwingHigh(int bar, double& highs[]) { if (bar < Swing_Strength || bar + Swing_Strength >= ArraySize(highs)) return false; //--- Возвращаем false, если индекс бара выходит за допустимые границы double current = highs[bar]; //--- Получаем текущий максимум for (int i = 1; i <= Swing_Strength; i++) { //--- Перебираем соседние бары if (highs[bar - i] >= current || highs[bar + i] >= current) return false; //--- Возвращаем false, если это не свинговый максимум } return true; //--- Возвращаем true для свингового максимума } //+------------------------------------------------------------------+ //| Проверка свингового минимума | //+------------------------------------------------------------------+ bool CheckSwingLow(int bar, double& lows[]) { if (bar < Swing_Strength || bar + Swing_Strength >= ArraySize(lows)) return false; //--- Возвращаем false, если индекс бара выходит за допустимые границы double current = lows[bar]; //--- Получаем текущий минимум for (int i = 1; i <= Swing_Strength; i++) { //--- Перебираем соседние бары if (lows[bar - i] <= current || lows[bar + i] <= current) return false; //--- Возвращаем false, если это не свинговый минимум } return true; //--- Возвращаем true для свингового минимума }
Функция «CheckSwingHigh» принимает индекс бара и ссылку на массив максимумов и определяет, является ли указанный бар свинговым максимумом. Сначала она проверяет границы массива с учётом «Swing_Strength» через ArraySize чтобы избежать ошибок индексации. Затем получает текущий максимум и сравнивает его с соседними барами слева и справа в пределах «Swing_Strength». Если хотя бы один соседний максимум не ниже текущего, возвращается false; иначе — true.
Аналогично создадим функцию «CheckSwingLow» для минимумов. Она проверяет допустимость индекса и убеждается, что минимумы соседних баров выше текущего значения. True возвращается только для подтверждённого свингового минимума. Далее создадим функцию проверки чистой дивергенции RSI.
//+------------------------------------------------------------------+ //| Проверка чистой дивергенции | //+------------------------------------------------------------------+ bool CleanDivergence(double rsi1, double rsi2, int shift1, int shift2, double& rsi_data[], bool bearish) { if (shift1 <= shift2) return false; //--- Возвращаем false при некорректных сдвигах for (int b = shift2 + 1; b < shift1; b++) { //--- Перебираем бары между сдвигами double interp_factor = (double)(b - shift2) / (shift1 - shift2); //--- Рассчитываем коэффициент интерполяции double interp_rsi = rsi2 + interp_factor * (rsi1 - rsi2); //--- Рассчитываем интерполированное значение RSI if (bearish) { //--- Проверяем медвежью дивергенцию if (rsi_data[b] > interp_rsi + Tolerance) return false; //--- Возвращаем false, если RSI превышает линию с учётом допуска } else { //--- Проверяем бычью дивергенцию if (rsi_data[b] < interp_rsi - Tolerance) return false; //--- Возвращаем false, если RSI опускается ниже линии с учётом допуска } } return true; //--- Возвращаем true, если дивергенция чистая }
Функция «CleanDivergence» проверяет, что промежуточные значения RSI не пересекают линию между двумя свинговыми точками, то есть дивергенция остаётся чистой. Она принимает значения RSI в точках «rsi1» и «rsi2», соответствующие сдвиги «shift1» и «shift2», ссылку на массив «rsi_data» и логический параметр «bearish», определяющий тип дивергенции.
Сначала проверим корректность сдвигов, затем переберём промежуточные бары от «shift2 + 1» до «shift1 - 1». Для каждого бара рассчитываются коэффициент интерполяции «interp_factor» и линейно интерполированное значение «interp_rsi». При медвежьей дивергенции функция возвращает false, если «rsi_data[b]» выше «interp_rsi + Tolerance»; при бычьей — если значение ниже «interp_rsi - Tolerance». Если нарушений нет, возвращается true. Теперь необходимые функции готовы, и эту логику можно использовать в обработчике OnTick. Начнём с медвежьей дивергенции.
//+------------------------------------------------------------------+ //| Обработчик тиков советника | //+------------------------------------------------------------------+ void OnTick() { static datetime last_time = 0; //--- Сохраняем время последнего обработанного бара datetime current_time = iTime(_Symbol, _Period, 0); //--- Получаем время текущего бара if (current_time == last_time) return; //--- Выходим, если новый бар не появился last_time = current_time; //--- Обновляем последнее время int data_size = 200; //--- Задаём количество анализируемых баров double high_data[], low_data[], rsi_data[]; //--- Объявляем массивы максимумов, минимумов и RSI datetime time_data[]; //--- Объявляем массив времени CopyHigh(_Symbol, _Period, 0, data_size, high_data); //--- Копируем максимумы CopyLow(_Symbol, _Period, 0, data_size, low_data); //--- Копируем минимумы CopyTime(_Symbol, _Period, 0, data_size, time_data); //--- Копируем значения времени CopyBuffer(RSI_Handle, 0, 0, data_size, rsi_data); //--- Копируем значения RSI ArraySetAsSeries(high_data, true); //--- Преобразуем массив максимумов в таймсерию ArraySetAsSeries(low_data, true); //--- Преобразуем массив минимумов в таймсерию ArraySetAsSeries(time_data, true); //--- Преобразуем массив времени в таймсерию ArraySetAsSeries(rsi_data, true); //--- Преобразуем массив RSI в таймсерию long chart_id = ChartID(); //--- Получаем идентификатор текущего графика // Находим два последних свинговых максимума int last_high_bar = -1, prev_high_bar = -1; //--- Инициализируем индексы свинговых максимумов for (int b = 1; b < data_size - Swing_Strength; b++) { //--- Перебираем бары if (CheckSwingHigh(b, high_data)) { //--- Проверяем свинговый максимум if (last_high_bar == -1) { //--- Проверяем первый свинговый максимум last_high_bar = b; //--- Задаём индекс последнего максимума } else { //--- Найден второй свинговый максимум prev_high_bar = b; //--- Задаём индекс предыдущего максимума break; //--- Выходим из цикла } } } if (last_high_bar > 0 && time_data[last_high_bar] > Last_High_Time) { //--- Проверяем новый свинговый максимум Prev_High_Price = Last_High_Price; //--- Обновляем цену предыдущего максимума Prev_High_Time = Last_High_Time; //--- Обновляем время предыдущего максимума Last_High_Price = high_data[last_high_bar]; //--- Задаём цену последнего максимума Last_High_Time = time_data[last_high_bar]; //--- Задаём время последнего максимума Prev_High_RSI = Last_High_RSI; //--- Обновляем RSI предыдущего максимума Last_High_RSI = rsi_data[last_high_bar]; //--- Задаём RSI последнего максимума string high_type = "H"; //--- Задаём тип максимума по умолчанию if (Prev_High_Price > 0.0) { //--- Проверяем наличие предыдущего максимума high_type = (Last_High_Price > Prev_High_Price) ? "HH" : "LH"; //--- Задаём тип максимума } bool higher_high = Last_High_Price > Prev_High_Price; //--- Проверяем наличие более высокого максимума bool lower_rsi_high = Last_High_RSI < Prev_High_RSI; //--- Проверяем наличие более низкого максимума RSI int bars_diff = prev_high_bar - last_high_bar; //--- Рассчитываем количество баров между максимумами bool bear_div = false; //--- Инициализируем флаг медвежьей дивергенции if (Prev_High_Price > 0.0 && higher_high && lower_rsi_high && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Проверяем условия медвежьей дивергенции if (CleanDivergence(Prev_High_RSI, Last_High_RSI, prev_high_bar, last_high_bar, rsi_data, true)) { //--- Проверяем чистую дивергенцию bear_div = true; //--- Устанавливаем флаг медвежьей дивергенции // Рисуем линии дивергенции string line_name = "DivLine_Bear_" + TimeToString(Last_High_Time); //--- Задаём имя линии дивергенции ObjectCreate(chart_id, line_name, OBJ_TREND, 0, Prev_High_Time, Prev_High_Price, Last_High_Time, Last_High_Price); //--- Создаём линию ценовой дивергенции ObjectSetInteger(chart_id, line_name, OBJPROP_COLOR, Bear_Color); //--- Задаём цвет линии ObjectSetInteger(chart_id, line_name, OBJPROP_WIDTH, Line_Width); //--- Задаём ширину линии ObjectSetInteger(chart_id, line_name, OBJPROP_STYLE, Line_Style); //--- Задаём стиль линии ObjectSetInteger(chart_id, line_name, OBJPROP_RAY, false); //--- Отключаем луч ObjectSetInteger(chart_id, line_name, OBJPROP_BACK, false); //--- Выводим на передний план int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Находим подокно RSI if (rsi_window != -1) { //--- Проверяем, найдено ли подокно RSI string rsi_line = "DivLine_RSI_Bear_" + TimeToString(Last_High_Time); //--- Задаём имя линии дивергенции RSI ObjectCreate(chart_id, rsi_line, OBJ_TREND, rsi_window, Prev_High_Time, Prev_High_RSI, Last_High_Time, Last_High_RSI); //--- Создаём линию дивергенции RSI ObjectSetInteger(chart_id, rsi_line, OBJPROP_COLOR, Bear_Color); //--- Задаём цвет линии ObjectSetInteger(chart_id, rsi_line, OBJPROP_WIDTH, Line_Width); //--- Задаём ширину линии ObjectSetInteger(chart_id, rsi_line, OBJPROP_STYLE, Line_Style); //--- Задаём стиль линии ObjectSetInteger(chart_id, rsi_line, OBJPROP_RAY, false); //--- Отключаем луч ObjectSetInteger(chart_id, rsi_line, OBJPROP_BACK, false); //--- Выводим на передний план } } } // Рисуем подпись свинговой точки string swing_name = "SwingHigh_" + TimeToString(Last_High_Time); //--- Задаём имя подписи свингового максимума if (ObjectFind(chart_id, swing_name) < 0) { //--- Проверяем наличие подписи ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_High_Time, Last_High_Price); //--- Создаём подпись свингового максимума ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, " " + high_type + (bear_div ? " Bear Div" : "")); //--- Задаём текст подписи ObjectSetInteger(chart_id, swing_name, OBJPROP_COLOR, Swing_High_Color); //--- Задаём цвет подписи ObjectSetInteger(chart_id, swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Задаём точку привязки подписи ObjectSetInteger(chart_id, swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Задаём размер шрифта подписи } ChartRedraw(chart_id); //--- Перерисовываем график } }
В обработчике OnTick используем статическую переменную «last_time» для хранения времени последнего обработанного бара. Время текущего бара получаем через iTime и завершаем функцию, если оно не изменилось. Затем обновляем «last_time». Задаём количество анализируемых баров: параметр «data_size» равен 200, но при необходимости это значение можно изменить. Объявляем массивы «high_data», «low_data», «rsi_data» и «time_data» и заполняем их с помощью CopyHigh, «CopyLow», «CopyTime» и CopyBuffer, получая недавние максимумы, минимумы, временные метки и значения RSI. Затем преобразуем массивы в таймсерии с помощью ArraySetAsSeries и получаем «chart_id» через «ChartID» для последующей отрисовки объектов.
Для поиска последних свинговых максимумов переменным «last_high_bar» и «prev_high_bar» присваиваем -1, после чего перебираем бары и вызываем «CheckSwingHigh», пока не найдём два последних подтверждённых максимума. При появлении нового максимума обновляем предыдущие значения и записываем новые «Last_High_Price», «Last_High_Time» и «Last_High_RSI». Тип максимума хранится в «high_type»: «H» по умолчанию, «HH» для более высокого максимума и «LH» для более низкого.
Затем проверяем условия медвежьей дивергенции: предыдущий максимум должен существовать, цена должна сформировать более высокий максимум, RSI — более низкий максимум, а расстояние между точками должно находиться в пределах «Min_Bars_Between» и «Max_Bars_Between». После этого вызываем «CleanDivergence» в медвежьем режиме. Если дивергенция чистая, устанавливаем «bear_div» в true и создаём на основном графике линию «DivLine_Bear_» с помощью ObjectCreate соединяя предыдущий и последний максимумы. Цвет, ширина, стиль, отключение луча и вывод на передний план задаются через ObjectSetInteger .
Подокно RSI находим через «ChartWindowFind». Если оно найдено, создаём в нём аналогичную линию «DivLine_RSI_Bear_», соединяющую значения RSI. Затем проверяем наличие подписи «SwingHigh_» через «ObjectFind». Если объекта нет, создаём его в точке максимума, задаём текст с «high_type» и отметкой «Bear Div», цвет «Swing_High_Color», привязку «ANCHOR_LEFT_LOWER» и размер шрифта «Font_Size». После этого обновляем график функцией ChartRedraw. После компиляции видим следующий результат.

Теперь система определяет и отображает медвежью дивергенцию. Для торговли по этому сигналу нужно открыть позицию на продажу. Перед этим закроем все открытые позиции, чтобы не накапливать лишние позиции. При необходимости эту логику можно изменить. Создадим соответствующие вспомогательные функции.
//+------------------------------------------------------------------+ //| Открытие позиции на покупку | //+------------------------------------------------------------------+ void OpenBuy() { double ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Получаем текущую цену Ask double sl = (SL_Pips > 0) ? NormalizeDouble(ask_price - SL_Pips * _Point, _Digits) : 0; //--- Рассчитываем Stop Loss, если он включён double tp = (TP_Pips > 0) ? NormalizeDouble(ask_price + TP_Pips * _Point, _Digits) : 0; //--- Рассчитываем Take Profit, если он включён if (obj_Trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, Lot_Size, 0, sl, tp)) { //--- Пытаемся открыть позицию на покупку Print("Buy trade opened on bullish divergence"); //--- Записываем открытие покупки в журнал } else { //--- Обрабатываем ошибку открытия Print("Failed to open Buy: ", obj_Trade.ResultRetcodeDescription()); //--- Выводим ошибку в журнал } } //+------------------------------------------------------------------+ //| Открытие позиции на продажу | //+------------------------------------------------------------------+ void OpenSell() { double bid_price = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Получаем текущую цену Bid double sl = (SL_Pips > 0) ? NormalizeDouble(bid_price + SL_Pips * _Point, _Digits) : 0; //--- Рассчитываем Stop Loss, если он включён double tp = (TP_Pips > 0) ? NormalizeDouble(bid_price - TP_Pips * _Point, _Digits) : 0; //--- Рассчитываем Take Profit, если он включён if (obj_Trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, Lot_Size, 0, sl, tp)) { //--- Пытаемся открыть позицию на продажу Print("Sell trade opened on bearish divergence"); //--- Записываем открытие продажи в журнал } else { //--- Обрабатываем ошибку открытия Print("Failed to open Sell: ", obj_Trade.ResultRetcodeDescription()); //--- Выводим ошибку в журнал } } //+------------------------------------------------------------------+ //| Закрытие всех позиций | //+------------------------------------------------------------------+ void CloseAll() { for (int p = PositionsTotal() - 1; p >= 0; p--) { //--- Перебираем позиции в обратном порядке if (PositionGetTicket(p) > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic_Number) { //--- Проверяем параметры позиции obj_Trade.PositionClose(PositionGetTicket(p)); //--- Закрываем позицию } } }
Поскольку советник будет открывать позиции обоих направлений, создадим все необходимые функции. «OpenBuy» открывает длинную позицию при бычьей дивергенции. Хотя логика бычьего сигнала будет добавлена позже, функция уже понадобится. Сначала получаем текущую цену Ask через SymbolInfoDouble . Если «SL_Pips» больше нуля, Stop Loss рассчитывается как «ask_price - SL_Pips * _Point» и нормализуется по количеству знаков символа функцией NormalizeDouble; иначе устанавливается 0. Аналогично Take Profit рассчитывается как «ask_price + TP_Pips * _Point» или отключается нулевым значением. Позиция на покупку открывается через «obj_Trade.PositionOpen» с типом ORDER_TYPE_BUY, объёмом «Lot_Size» и рассчитанными SL и TP. Результат операции выводится через «Print», а при ошибке — описание из «obj_Trade.ResultRetcodeDescription».
Функция «OpenSell» аналогичным образом открывает короткую позицию по медвежьему сигналу. Она получает цену Bid, рассчитывает Stop Loss как «bid_price + SL_Pips * _Point», Take Profit как «bid_price - TP_Pips * _Point» и вызывает «obj_Trade.PositionOpen» с типом «ORDER_TYPE_SELL». Результат также записывается в журнал.
Наконец, реализуем функцию «CloseAll», которая закрывает существующие позиции перед открытием новых. Для этого перебираем позиции в обратном порядке от «PositionsTotal()- 1» до нуля. Для каждой позиции проверяем действительность тикета, соответствие текущему символу через «PositionGetString(POSITION_SYMBOL)» и магическому номеру «Magic_Number». Подходящие позиции закрываются через «obj_Trade.PositionClose». При появлении сигнала сначала вызывается «CloseAll», а затем функция открытия позиции соответствующего направления. Для медвежьей дивергенции добавим следующий код.
// Обнаружена медвежья дивергенция — сигнал на продажу CloseAll(); //--- Закрываем все открытые позиции OpenSell(); //--- Открываем позицию на продажу
После компиляции видим следующий результат.

Теперь советник может торговать по сигналу медвежьей дивергенции. Добавим аналогичную логику для бычьей дивергенции.
// Находим два последних свинговых минимума int last_low_bar = -1, prev_low_bar = -1; //--- Инициализируем индексы свинговых минимумов for (int b = 1; b < data_size - Swing_Strength; b++) { //--- Перебираем бары if (CheckSwingLow(b, low_data)) { //--- Проверяем свинговый минимум if (last_low_bar == -1) { //--- Проверяем первый свинговый минимум last_low_bar = b; //--- Задаём индекс последнего минимума } else { //--- Найден второй свинговый минимум prev_low_bar = b; //--- Задаём индекс предыдущего минимума break; //--- Выходим из цикла } } } if (last_low_bar > 0 && time_data[last_low_bar] > Last_Low_Time) { //--- Проверяем новый свинговый минимум Prev_Low_Price = Last_Low_Price; //--- Обновляем цену предыдущего минимума Prev_Low_Time = Last_Low_Time; //--- Обновляем время предыдущего минимума Last_Low_Price = low_data[last_low_bar]; //--- Задаём цену последнего минимума Last_Low_Time = time_data[last_low_bar]; //--- Задаём время последнего минимума Prev_Low_RSI = Last_Low_RSI; //--- Обновляем RSI предыдущего минимума Last_Low_RSI = rsi_data[last_low_bar]; //--- Задаём RSI последнего минимума string low_type = "L"; //--- Задаём тип минимума по умолчанию if (Prev_Low_Price > 0.0) { //--- Проверяем наличие предыдущего минимума low_type = (Last_Low_Price < Prev_Low_Price) ? "LL" : "HL"; //--- Задаём тип минимума } bool lower_low = Last_Low_Price < Prev_Low_Price; //--- Проверяем наличие более низкого минимума bool higher_rsi_low = Last_Low_RSI > Prev_Low_RSI; //--- Проверяем наличие более высокого минимума RSI int bars_diff = prev_low_bar - last_low_bar; //--- Рассчитываем количество баров между минимумами bool bull_div = false; //--- Инициализируем флаг бычьей дивергенции if (Prev_Low_Price > 0.0 && lower_low && higher_rsi_low && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Проверяем условия бычьей дивергенции if (CleanDivergence(Prev_Low_RSI, Last_Low_RSI, prev_low_bar, last_low_bar, rsi_data, false)) { //--- Проверяем чистую дивергенцию bull_div = true; //--- Устанавливаем флаг бычьей дивергенции // Обнаружена бычья дивергенция — сигнал на покупку CloseAll(); //--- Закрываем все открытые позиции OpenBuy(); //--- Открываем позицию на покупку // Рисуем линии дивергенции string line_name = "DivLine_Bull_" + TimeToString(Last_Low_Time); //--- Задаём имя линии дивергенции ObjectCreate(chart_id, line_name, OBJ_TREND, 0, Prev_Low_Time, Prev_Low_Price, Last_Low_Time, Last_Low_Price); //--- Создаём линию ценовой дивергенции ObjectSetInteger(chart_id, line_name, OBJPROP_COLOR, Bull_Color); //--- Задаём цвет линии ObjectSetInteger(chart_id, line_name, OBJPROP_WIDTH, Line_Width); //--- Задаём ширину линии ObjectSetInteger(chart_id, line_name, OBJPROP_STYLE, Line_Style); //--- Задаём стиль линии ObjectSetInteger(chart_id, line_name, OBJPROP_RAY, false); //--- Отключаем луч ObjectSetInteger(chart_id, line_name, OBJPROP_BACK, false); //--- Выводим на передний план int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Находим подокно RSI if (rsi_window != -1) { //--- Проверяем, найдено ли подокно RSI string rsi_line = "DivLine_RSI_Bull_" + TimeToString(Last_Low_Time); //--- Задаём имя линии дивергенции RSI ObjectCreate(chart_id, rsi_line, OBJ_TREND, rsi_window, Prev_Low_Time, Prev_Low_RSI, Last_Low_Time, Last_Low_RSI); //--- Создаём линию дивергенции RSI ObjectSetInteger(chart_id, rsi_line, OBJPROP_COLOR, Bull_Color); //--- Задаём цвет линии ObjectSetInteger(chart_id, rsi_line, OBJPROP_WIDTH, Line_Width); //--- Задаём ширину линии ObjectSetInteger(chart_id, rsi_line, OBJPROP_STYLE, Line_Style); //--- Задаём стиль линии ObjectSetInteger(chart_id, rsi_line, OBJPROP_RAY, false); //--- Отключаем луч ObjectSetInteger(chart_id, rsi_line, OBJPROP_BACK, false); //--- Выводим на передний план } } } // Рисуем подпись свинговой точки string swing_name = "SwingLow_" + TimeToString(Last_Low_Time); //--- Задаём имя подписи свингового минимума if (ObjectFind(chart_id, swing_name) < 0) { //--- Проверяем наличие подписи ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_Low_Time, Last_Low_Price); //--- Создаём подпись свингового минимума ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, " " + low_type + (bull_div ? " Bull Div" : "")); //--- Задаём текст подписи ObjectSetInteger(chart_id, swing_name, OBJPROP_COLOR, Swing_Low_Color); //--- Задаём цвет подписи ObjectSetInteger(chart_id, swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Задаём точку привязки подписи ObjectSetInteger(chart_id, swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Задаём размер шрифта подписи } ChartRedraw(chart_id); //--- Перерисовываем график }
Здесь используется та же логика, что и для медвежьей дивергенции, но с обратными условиями. Комментарии поясняют назначение операций. После компиляции видим следующий результат.

Как видно на изображении, советник также открывает позиции по бычьим дивергенциям. Далее добавим трейлинг-стоп для сопровождения прибыли. Для этого вынесем логику в отдельную функцию — так код останется модульным.
//+------------------------------------------------------------------+ //| Применение трейлинг-стопа к позициям | //+------------------------------------------------------------------+ void ApplyTrailingStop() { double point = _Point; //--- Получаем размер пункта инструмента for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Перебираем позиции в обратном порядке if (PositionGetTicket(i) > 0) { //--- Проверяем корректность тикета if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic_Number) { //--- Проверяем символ и магический номер double sl = PositionGetDouble(POSITION_SL); //--- Получаем текущий Stop Loss double tp = PositionGetDouble(POSITION_TP); //--- Получаем текущий Take Profit double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Получаем цену открытия ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Получаем тикет позиции if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Проверяем позицию на покупку double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Рассчитываем новый Stop Loss if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Проверяем условия трейлинг-стопа obj_Trade.PositionModify(ticket, newSL, tp); //--- Изменяем позицию, устанавливая новый Stop Loss } } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Проверяем позицию на продажу double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Рассчитываем новый Stop Loss if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Проверяем условия трейлинг-стопа obj_Trade.PositionModify(ticket, newSL, tp); //--- Изменяем позицию, устанавливая новый Stop Loss } } } } } }
Функция «ApplyTrailingStop» динамически изменяет Stop Loss открытых позиций после достижения заданного порога прибыли, фиксируя результат по мере движения рынка. Сначала присваиваем переменной «point» значение «_Point», затем перебираем все позиции в обратном порядке от «PositionsTotal() - 1» до нуля, чтобы безопасно изменять их без смещения индексов.
Для каждой позиции функция PositionGetTicket с параметром i должна вернуть действительный тикет больше нуля. Затем проверяем символ через PositionGetString с параметром «POSITION_SYMBOL» и сравниваем его с _Symbol, а также магический номер через «PositionGetInteger(POSITION_MAGIC)». Затем получаем текущие значения «sl», «tp», цену открытия «openPrice» и тикет позиции.
Если тип позиции, полученный через PositionGetInteger с параметром «POSITION_TYPE», равен POSITION_TYPE_BUY, рассчитываем «newSL» как текущую цену Bid минус «Trailing_Stop_Pips * point» и нормализуем значение. Если новый Stop Loss выше текущего, а прибыль превышает «Min_Profit_To_Trail_Pips * point», позиция изменяется через «obj_Trade.PositionModify» с сохранением Take Profit.
Для позиции на продажу, когда «POSITION_TYPE» равен POSITION_TYPE_SELL, новый Stop Loss рассчитывается как цена Ask плюс «Trailing_Stop_Pips * point». Если он ниже текущего Stop Loss, а прибыль превышает минимальный порог, позиция изменяется через «obj_Trade.PositionModify». Так трейлинг-стоп применяется только к подходящим позициям. Теперь вызовем функцию в обработчике тиков.
if (Enable_Trailing_Stop && PositionsTotal() > 0) { //--- Проверяем, включён ли трейлинг-стоп ApplyTrailingStop(); //--- Применяем трейлинг-стоп к позициям }
Если есть открытые позиции и трейлинг-стоп включён, вызываем функцию на каждом тике. Получаем следующий результат.

Основная логика завершена. Остаётся удалить графические объекты при снятии советника с графика, чтобы не оставлять лишние элементы.
//+------------------------------------------------------------------+ //| Функция деинициализации советника | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (RSI_Handle != INVALID_HANDLE) IndicatorRelease(RSI_Handle); //--- Освобождаем дескриптор RSI, если он действителен ObjectsDeleteAll(0, "DivLine_"); //--- Удаляем все линии дивергенции ObjectsDeleteAll(0, "SwingHigh_"); //--- Удаляем все объекты свинговых максимумов ObjectsDeleteAll(0, "SwingLow_"); //--- Удаляем все объекты свинговых минимумов Print("RSI Divergence EA deinitialized"); //--- Записываем деинициализацию в журнал }
В обработчике OnDeinit проверяем, что «RSI_Handle» не равен INVALID_HANDLE, после чего освобождаем ресурсы индикатора вызовом IndicatorRelease с аргументом «RSI_Handle». Затем удаляем графические объекты через ObjectsDeleteAll с префиксами «DivLine_», «SwingHigh_» и «SwingLow_». В завершение выводим подтверждающее сообщение через Print о завершении работы советника. На этом реализация завершена; далее перейдём к бэктестированию программы.
Бэктестирование
После тщательного бэктестирования получены следующие результаты.
График бэктеста:

Отчёт о бэктесте:

Заключение
В результате мы разработали систему поиска обычных дивергенций RSI на MQL5. Она определяет бычьи и медвежьи дивергенции по свинговым максимумам и минимумам с подтверждением силы, проверяет чистоту сигналов в заданном диапазоне баров с учётом допуска и открывает позиции фиксированным объёмом с необязательными Stop Loss, Take Profit и трейлинг-стопом.
Предупреждение: статья предназначена исключительно для образовательных целей. Торговля связана со значительными финансовыми рисками, а волатильность рынка может привести к убыткам. Перед использованием программы на реальном счёте необходимо провести тщательное бэктестирование и соблюдать строгий риск-менеджмент.
Теперь у вас есть рабочая основа для эффективной торговли по сигналам обычных дивергенций RSI, готовая к дальнейшей оптимизации. Успешной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20031
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Возможности Мастера MQL5, которые вам нужно знать (Часть 83): Использование паттернов стохастического осциллятора и FrAMA — поведенческие архетипы
Как построить мультивалютную netting-сетку с точным CatBoost и swap/carry-слоем в MQL5
Изучение Стандартной библиотеки MQL5 (Часть 1): Интеграция и связывание компонентов
Кооперация бионических алгоритмов — Co-Operation of Biology Related Algorithms (COBRA)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования