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

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

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

Реализация на MQL5
Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в окно «Навигатор», откройте папку Experts, щёлкните по ней правой кнопкой мыши, выберите «Новый файл» и следуйте указаниям мастера. После создания файла объявим входные параметры и глобальные переменные, которые будут использоваться во всей программе.
//+------------------------------------------------------------------+ //| RSI Hidden 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 "Price Divergence Filter" input bool Use_Price_Slope_Filter = false; // Использовать фильтр угла наклона для цены input double Price_Min_Slope_Degrees = 10.0; // Минимальный угол наклона цены в градусах (0 — отключить минимум) input double Price_Max_Slope_Degrees = 80.0; // Максимальный угол наклона цены в градусах (90 — отключить максимум) input group "RSI Divergence Filter" input bool Use_RSI_Slope_Filter = true; // Использовать фильтр угла наклона для RSI input double RSI_Min_Slope_Degrees = 1.0; // Минимальный угол наклона RSI в градусах (0 — отключить минимум) input double RSI_Max_Slope_Degrees = 89.0; // Максимальный угол наклона RSI в градусах (90 — отключить максимум) 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 bool Mark_Swings_On_Price = true; // Отмечать свинговые точки на ценовом графике input bool Mark_Swings_On_RSI = true; // Отмечать свинговые точки в подокне RSI 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 задаёт небольшой допуск при проверке чистоты дивергенции, как и в предыдущей версии.
В группе «Price Divergence Filter» параметр «Use_Price_Slope_Filter» по умолчанию равен false и позволяет при необходимости включить фильтрацию по углу наклона. Параметры «Price_Min_Slope_Degrees» и «Price_Max_Slope_Degrees» со значениями 10,0 и 80,0 задают допустимый диапазон углов наклона ценовой линии в градусах. Значение 0 отключает минимальное ограничение, а 90 — максимальное. Аналогично в группе «RSI Divergence Filter» параметр «Use_RSI_Slope_Filter» равен true, а «RSI_Min_Slope_Degrees» и «RSI_Max_Slope_Degrees» со значениями 1,0 и 89,0 задают диапазон углов наклона линии RSI. Остальные параметры совпадают с предыдущей версией системы для обычных дивергенций; в текущей версии добавлены только настройки фильтрации по углам наклона.
Наконец, объявим глобальные переменные: «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 Hidden 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, если дивергенция чистая } //+------------------------------------------------------------------+ //| Расчёт визуального угла | //+------------------------------------------------------------------+ double CalculateVisualAngle(long chart_id, int sub_window, datetime time1, double val1, datetime time2, double val2) { int x1 = 0, y1 = 0, x2 = 0, y2 = 0; //--- Инициализируем пиксельные координаты bool ok1 = ChartTimePriceToXY(chart_id, sub_window, time1, val1, x1, y1); //--- Преобразуем первую точку в координаты XY bool ok2 = ChartTimePriceToXY(chart_id, sub_window, time2, val2, x2, y2); //--- Преобразуем вторую точку в координаты XY if (!ok1 || !ok2 || x1 == x2) return 0.0; //--- Возвращаем ноль при ошибке преобразования или совпадении координат x double dx = (double)(x2 - x1); //--- Рассчитываем разность координат x double dy = (double)(y2 - y1); //--- Рассчитываем разность координат y if (dx == 0.0) return (dy > 0.0 ? -90.0 : 90.0); //--- Обрабатываем случай вертикальной линии double angle = MathArctan(-dy / dx) * 180.0 / M_PI; //--- Рассчитываем угол в градусах return MathAbs(angle); //--- Возвращаем абсолютное значение угла }
Функция «CleanDivergence» проверяет, что промежуточные значения RSI не пересекают линию между двумя свинговыми точками, то есть дивергенция остаётся «чистой». Она принимает значения RSI в точках «rsi1» и «rsi2», соответствующие сдвиги «shift1» и «shift2» (при этом «shift1» должен быть больше), ссылку на массив «rsi_data» и логический параметр «bearish», определяющий тип дивергенции. Сначала проверяется корректность сдвигов, затем перебираются бары от «shift2 + 1» до «shift1 - 1». Для каждого бара рассчитываются нормированный коэффициент положения «interp_factor» и линейно интерполированное значение «interp_rsi». Для медвежьего варианта функция возвращает false, если какое-либо значение «rsi_data[b]» превышает «interp_rsi + Tolerance»; для бычьего — если оно опускается ниже «interp_rsi - Tolerance». Если все проверки пройдены, функция возвращает true.
Затем создадим функцию «CalculateVisualAngle» для расчёта визуального угла наклона в градусах между двумя точками на графике. Этот угол используется при фильтрации дивергенций. Функция принимает идентификатор графика «chart_id», номер основного окна или подокна «sub_window», а также координаты двух точек: «time1», «val1», «time2» и «val2». Сначала пиксельные координаты «x1», «y1», «x2» и «y2» инициализируются нулём. Затем обе точки преобразуются из координат времени и цены в экранные координаты с помощью ChartTimePriceToXY. Если преобразование завершилось ошибкой или координаты x совпадают, функция возвращает 0,0. В противном случае рассчитываются разности «dx» и «dy». Для вертикальной линии возвращается угол −90,0 или 90,0 в зависимости от направления. В остальных случаях угол вычисляется функцией MathArctan по отношению «-dy / dx», переводится в градусы с использованием M_PI, после чего функция MathAbs возвращает его абсолютное значение. Теперь все вспомогательные функции готовы, и можно перейти к реализации логики в обработчике 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 rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Находим подокно RSI // Находим последний свинговый максимум 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 lower_high = Last_High_Price < Prev_High_Price; //--- Проверяем наличие более низкого максимума bool higher_rsi_high = Last_High_RSI > Prev_High_RSI; //--- Проверяем наличие более высокого максимума RSI int bars_diff = prev_high_bar - last_high_bar; //--- Рассчитываем количество баров между максимумами bool hidden_bear_div = false; //--- Инициализируем флаг скрытой медвежьей дивергенции double price_angle = 0.0; //--- Инициализируем угол ценовой линии double rsi_angle = 0.0; //--- Инициализируем угол линии RSI if (Prev_High_Price > 0.0 && lower_high && higher_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)) { //--- Проверяем чистую дивергенцию price_angle = CalculateVisualAngle(chart_id, 0, Prev_High_Time, Prev_High_Price, Last_High_Time, Last_High_Price); //--- Рассчитываем угол ценовой линии rsi_angle = CalculateVisualAngle(chart_id, rsi_window, Prev_High_Time, Prev_High_RSI, Last_High_Time, Last_High_RSI); //--- Рассчитываем угол линии RSI if ((!Use_Price_Slope_Filter || (price_angle >= Price_Min_Slope_Degrees && price_angle <= Price_Max_Slope_Degrees)) && (!Use_RSI_Slope_Filter || (rsi_angle >= RSI_Min_Slope_Degrees && rsi_angle <= RSI_Max_Slope_Degrees))) { //--- Проверяем фильтры угла наклона hidden_bear_div = true; //--- Устанавливаем флаг скрытой медвежьей дивергенции // Рисуем линии дивергенции string line_name = "DivLine_HiddenBear_" + 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); //--- Выводим на передний план if (rsi_window != -1) { //--- Проверяем, найдено ли подокно RSI string rsi_line = "DivLine_RSI_HiddenBear_" + 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); //--- Выводим на передний план } } } } // Рисуем подпись свинговой точки на ценовом графике, если включено if (Mark_Swings_On_Price) { //--- Проверяем, включены ли подписи свинговых точек на ценовом графике string swing_name = "SwingHigh_" + TimeToString(Last_High_Time); //--- Задаём имя подписи свингового максимума if (ObjectFind(chart_id, swing_name) < 0) { //--- Проверяем наличие подписи string high_label_text = " " + high_type + (hidden_bear_div ? " Hidden Bear Div " + DoubleToString(price_angle, 1) + "°" : ""); //--- Формируем текст подписи с углом при наличии дивергенции ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_High_Time, Last_High_Price); //--- Создаём подпись свингового максимума ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, high_label_text); //--- Задаём текст подписи 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); //--- Задаём размер шрифта подписи } } // Рисуем соответствующую подпись свинговой точки на RSI, если включено if (Mark_Swings_On_RSI && rsi_window != -1) { //--- Проверяем, включены ли подписи на RSI и найдено ли подокно string rsi_swing_name = "RSI_SwingHigh_" + TimeToString(Last_High_Time); //--- Задаём имя подписи свингового максимума RSI if (ObjectFind(chart_id, rsi_swing_name) < 0) { //--- Проверяем наличие подписи string high_label_text_rsi = " " + high_type + (hidden_bear_div ? " Hidden Bear Div " + DoubleToString(rsi_angle, 1) + "°" : ""); //--- Формируем текст подписи с углом RSI при наличии дивергенции ObjectCreate(chart_id, rsi_swing_name, OBJ_TEXT, rsi_window, Last_High_Time, Last_High_RSI); //--- Создаём подпись свингового максимума RSI ObjectSetString(chart_id, rsi_swing_name, OBJPROP_TEXT, high_label_text_rsi); //--- Задаём текст подписи ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_COLOR, Swing_High_Color); //--- Задаём цвет подписи ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Задаём точку привязки подписи ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Задаём размер шрифта подписи } } ChartRedraw(chart_id); //--- Перерисовываем график } }
В обработчике OnTick, который вызывается при поступлении каждого нового тика, используем статическую переменную «last_time» для хранения времени последнего обработанного бара. Время текущего бара получаем через iTime. Если оно не изменилось, завершаем функцию; в противном случае обновляем «last_time». Затем задаём количество анализируемых баров: 200. Объявляем массивы максимумов, минимумов, времени и значений RSI и заполняем их с помощью CopyHigh, CopyLow, CopyTime и CopyBuffer.
Преобразуем массивы в таймсерии с помощью ArraySetAsSeries, чтобы элементы располагались в обратном порядке — от новых к старым. Затем получаем идентификатор графика через «ChartID» и находим индекс подокна RSI с помощью «ChartWindowFind». Для поиска двух последних свинговых максимумов переменным «last_high_bar» и «prev_high_bar» присваиваем -1, после чего перебираем бары и вызываем «CheckSwingHigh», пока не будут найдены два подтверждённых максимума.
Если обнаружен новый свинговый максимум, то есть «time_data[last_high_bar]» больше «Last_High_Time», переносим текущие значения в переменные предыдущего максимума и обновляем «Last_High_Price», «Last_High_Time» и «Last_High_RSI». Тип максимума записывается в «high_type»: «H» по умолчанию, «HH» для более высокого максимума и «LH» для более низкого. Затем проверяем условия скрытой медвежьей дивергенции: цена должна сформировать более низкий максимум, RSI — более высокий максимум, а количество баров между точками должно находиться в диапазоне от «Min_Bars_Between» до «Max_Bars_Between». Флаг «hidden_bear_div» первоначально равен false, а переменные «price_angle» и «rsi_angle» — 0,0.
Если дивергенция проходит проверку на чистоту, рассчитываем «price_angle» с помощью «CalculateVisualAngle» в основном окне графика, используя время и цены максимумов, а «rsi_angle» — в подокне RSI по времени и значениям индикатора. Затем проверяем активные фильтры. Если фильтр цены отключён либо «price_angle» находится между «Price_Min_Slope_Degrees» и «Price_Max_Slope_Degrees», а фильтр RSI отключён либо «rsi_angle» находится между «RSI_Min_Slope_Degrees» и «RSI_Max_Slope_Degrees», устанавливаем флаг «hidden_bear_div» в true и строим линии скрытой дивергенции на ценовом графике и в подокне RSI. Для линий задаём цвет, ширину, стиль, отключаем продолжение в виде луча и выводим объекты на передний план. Торговые вызовы «CloseAll()» и «OpenSell()» добавляются далее отдельным шагом.
Если параметр «Mark_Swings_On_Price» равен true, проверяем наличие текстового объекта «SwingHigh_» с соответствующей временной меткой. Если объекта нет, создаём его с помощью «ObjectCreate» и формируем подпись из типа максимума и, при наличии дивергенции, отметки «Hidden Bear Div» с углом ценовой линии. Затем задаём цвет, привязку и размер шрифта. Аналогично, если включён параметр «Mark_Swings_On_RSI» и подокно RSI найдено, создаём подпись «RSI_SwingHigh_» со значением угла линии RSI. В завершение перерисовываем график функцией «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 hidden 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 hidden 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 higher_low = Last_Low_Price > Prev_Low_Price; //--- Проверяем наличие более высокого минимума bool lower_rsi_low = Last_Low_RSI < Prev_Low_RSI; //--- Проверяем наличие более низкого минимума RSI int bars_diff = prev_low_bar - last_low_bar; //--- Рассчитываем количество баров между минимумами bool hidden_bull_div = false; //--- Инициализируем флаг скрытой бычьей дивергенции double price_angle = 0.0; //--- Инициализируем угол ценовой линии double rsi_angle = 0.0; //--- Инициализируем угол линии RSI if (Prev_Low_Price > 0.0 && higher_low && lower_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)) { //--- Проверяем чистую дивергенцию price_angle = CalculateVisualAngle(chart_id, 0, Prev_Low_Time, Prev_Low_Price, Last_Low_Time, Last_Low_Price); //--- Рассчитываем угол ценовой линии rsi_angle = CalculateVisualAngle(chart_id, rsi_window, Prev_Low_Time, Prev_Low_RSI, Last_Low_Time, Last_Low_RSI); //--- Рассчитываем угол линии RSI if ((!Use_Price_Slope_Filter || (price_angle >= Price_Min_Slope_Degrees && price_angle <= Price_Max_Slope_Degrees)) && (!Use_RSI_Slope_Filter || (rsi_angle >= RSI_Min_Slope_Degrees && rsi_angle <= RSI_Max_Slope_Degrees))) { //--- Проверяем фильтры угла наклона hidden_bull_div = true; //--- Устанавливаем флаг скрытой бычьей дивергенции // Обнаружена скрытая бычья дивергенция — сигнал на покупку CloseAll(); //--- Закрываем все открытые позиции OpenBuy(); //--- Открываем позицию на покупку // Рисуем линии дивергенции string line_name = "DivLine_HiddenBull_" + 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); //--- Выводим на передний план if (rsi_window != -1) { //--- Проверяем, найдено ли подокно RSI string rsi_line = "DivLine_RSI_HiddenBull_" + 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); //--- Выводим на передний план } } } } // Рисуем подпись свинговой точки на ценовом графике, если включено if (Mark_Swings_On_Price) { //--- Проверяем, включены ли подписи свинговых точек на ценовом графике string swing_name = "SwingLow_" + TimeToString(Last_Low_Time); //--- Задаём имя подписи свингового минимума if (ObjectFind(chart_id, swing_name) < 0) { //--- Проверяем наличие подписи string low_label_text = " " + low_type + (hidden_bull_div ? " Hidden Bull Div " + DoubleToString(price_angle, 1) + "°" : ""); //--- Формируем текст подписи с углом при наличии дивергенции ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_Low_Time, Last_Low_Price); //--- Создаём подпись свингового минимума ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, low_label_text); //--- Задаём текст подписи 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); //--- Задаём размер шрифта подписи } } // Рисуем соответствующую подпись свинговой точки на RSI, если включено if (Mark_Swings_On_RSI && rsi_window != -1) { //--- Проверяем, включены ли подписи на RSI и найдено ли подокно string rsi_swing_name = "RSI_SwingLow_" + TimeToString(Last_Low_Time); //--- Задаём имя подписи свингового минимума RSI if (ObjectFind(chart_id, rsi_swing_name) < 0) { //--- Проверяем наличие подписи string low_label_text_rsi = " " + low_type + (hidden_bull_div ? " Hidden Bull Div " + DoubleToString(rsi_angle, 1) + "°" : ""); //--- Формируем текст подписи с углом RSI при наличии дивергенции ObjectCreate(chart_id, rsi_swing_name, OBJ_TEXT, rsi_window, Last_Low_Time, Last_Low_RSI); //--- Создаём подпись свингового минимума RSI ObjectSetString(chart_id, rsi_swing_name, OBJPROP_TEXT, low_label_text_rsi); //--- Задаём текст подписи ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_COLOR, Swing_Low_Color); //--- Задаём цвет подписи ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Задаём точку привязки подписи ObjectSetInteger(chart_id, rsi_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». Так трейлинг-стоп применяется только к подходящим позициям. Теперь вызовем функцию в обработчике тиков.
//+------------------------------------------------------------------+ //| Обработчик тиков советника | //+------------------------------------------------------------------+ void OnTick() { if (Enable_Trailing_Stop && PositionsTotal() > 0) { //--- Проверяем, включён ли трейлинг-стоп ApplyTrailingStop(); //--- Применяем трейлинг-стоп к позициям } static datetime last_time = 0; //--- Сохраняем время последнего обработанного бара datetime current_time = iTime(_Symbol, _Period, 0); //--- Получаем время текущего бара //--- ОСТАЛЬНАЯ ЛОГИКА }
Если есть открытые позиции и трейлинг-стоп включён, вызываем функцию «ApplyTrailingStop» на каждом тике. Результат показан ниже.

На этом основная логика завершена. Остаётся удалить графические объекты при снятии советника с графика, чтобы не оставлять лишние элементы.
//+------------------------------------------------------------------+ //| Функция деинициализации советника | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (RSI_Handle != INVALID_HANDLE) { //--- Проверяем действительность дескриптора RSI 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 ChartIndicatorDelete(chart_id, rsi_subwin, rsi_name); //--- Удаляем индикатор RSI с графика Print("RSI indicator removed from chart"); //--- Записываем удаление в журнал } IndicatorRelease(RSI_Handle); //--- Освобождаем дескриптор RSI } ObjectsDeleteAll(0, "DivLine_"); //--- Удаляем все объекты линий дивергенции ObjectsDeleteAll(0, "SwingHigh_"); //--- Удаляем все объекты свинговых максимумов ObjectsDeleteAll(0, "SwingLow_"); //--- Удаляем все объекты свинговых минимумов ObjectsDeleteAll(0, "RSI_SwingHigh_"); //--- Удаляем все объекты свинговых максимумов RSI ObjectsDeleteAll(0, "RSI_SwingLow_"); //--- Удаляем все объекты свинговых минимумов RSI Print("RSI Hidden Divergence EA deinitialized"); //--- Записываем деинициализацию в журнал }
Обработчик OnDeinit принимает целочисленный параметр «reason», указывающий причину деинициализации, и выполняет очистку при удалении советника с графика. Если «RSI_Handle» не равен INVALID_HANDLE, получаем идентификатор текущего графика через «ChartID», формируем имя индикатора RSI и находим его подокно с помощью «ChartWindowFind». Если подокно найдено, удаляем индикатор с графика функцией ChartIndicatorDelete, записываем сообщение в журнал, а затем освобождаем дескриптор вызовом IndicatorRelease.
После этого несколькими вызовами ObjectsDeleteAll удаляем линии дивергенции и подписи свинговых точек с префиксами «DivLine_», «SwingHigh_», «SwingLow_», «RSI_SwingHigh_» и «RSI_SwingLow_». В завершение через «Print» выводится сообщение о деинициализации. На этом реализация завершена; остаётся протестировать программу в тестере.
Бэктестирование
После тщательного бэктестирования получены следующие результаты.
График бэктеста:

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

Заключение
В результате мы разработали на MQL5 систему торговли по скрытым дивергенциям RSI. Стратегия определяет скрытые бычьи и медвежьи дивергенции по свинговым максимумам и минимумам с заданным параметром силы, проверяет чистоту формирования дивергенции в установленном диапазоне баров с учётом допуска и фильтрует сигналы по настраиваемым углам наклона ценовой линии и линии RSI. Советник открывает позиции фиксированным объёмом, использует опциональные уровни Stop Loss и Take Profit, заданные в пипсах (в исходной статье — pips; в коде расчёт выполняется через _Point), применяет трейлинг-стоп для динамического управления рисками и отображает цветные линии и подписи свинговых точек со значениями углов на ценовом графике и в подокне RSI.
Предупреждение: статья предназначена исключительно для образовательных целей. Торговля связана со значительными финансовыми рисками, а волатильность рынка может привести к убыткам. Перед использованием программы на реальном счёте необходимо провести тщательное бэктестирование и соблюдать строгие правила управления рисками.
Теперь у вас есть рабочая система для торговли по сигналам продолжения тренда на основе скрытых дивергенций RSI, готовая к дальнейшей оптимизации и адаптации в вашей торговой практике. Успешной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20157
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От начального до среднего уровня: Очереди, списки и деревья (III)
Изучение Стандартной библиотеки MQL5 (Часть 1): Интеграция и связывание компонентов
Нейросети в трейдинге: Долговременная память торговых сценариев (VLADriver-RAG)
Возможности Мастера MQL5, которые вам нужно знать (Часть 83): Использование паттернов стохастического осциллятора и FrAMA — поведенческие архетипы
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования