English
preview
От новичка до эксперта: Торговля по RSI с учетом структуры рынка

От новичка до эксперта: Торговля по RSI с учетом структуры рынка

MetaTrader 5Примеры |
105 0
Clemence Benjamin
Clemence Benjamin

Содержание



Введение

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

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

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

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

  1. Алгоритмически определяет допустимые трендовые каналы.
  2. Разумно интерпретирует поведение RSI в контексте этих каналов.
  3. Выполняет сделки и управляет ими на основе предопределенной и основанной на правилах логики.

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

Понимание концепции и общих структур

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

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

Анатомия надежного флага

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

A bullish Flag

Установка бычьего флага

Медвежий флаг: зеркальное отражение с нисходящим трендом. За крутым спуском флагштока вниз следует восходящий или прямоугольный канал консолидации.

A bearish Flag

Установка медвежьего флага

За пределами пробоя: граница дивергенции

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

В отношении бычьего флага:

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

Сигнал дивергенции (наше слияние при входе): Когда цена достигает своего 3-го или 4-го более низкого минимума (LL) вблизи нижней границы поддержки канала, RSI формирует более высокий минимум (HL). Эта дивергенция указывает на то, что нисходящий импульс продаж в рамках консолидации исчерпывается именно в области логической поддержки. Более масштабный восходящий тренд вот-вот возобновится.

В отношении медвежьего флага:

Структурный контекст: цена совершает серию более высоких максимумов (HH), по мере того, как поднимается в границах направленного вверх канала.

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

Showing RSI divergence and Structural Relationship

Отображение слияния каналов дивергенции при установке медвежьего флага

Почему это слияние трансформирует торговлю:

Эта основанная на дивергенции логика напрямую устраняет основной недостаток как простых правил пробоя, так и базовых сигналов RSI о перекупленности/перепроданности:

  1. Предотвращает "ловлю падающего ножа": Перепроданный RSI на уровне поддержки канала является обычным явлением, но не надежным сигналом на покупку. Однако бычья дивергенция RSI на той же самой поддержке свидетельствует о том, что давление продаж ухудшается, превращая простой "уровень отскока" в "точку разворота" с высокой вероятностью.
  2. Обеспечивает превосходное управление рисками: Вход, спровоцированный дивергенцией на границе канала, допускает логически жесткий стоп-лосс (размещаемый сразу за последним колебательным минимумом/максимумом дивергенции). Соотношение прибыли к риску значительно улучшено по сравнению со входом на пробое.
  3. Ищет более входы с высоким уровнем подтверждения: Вместо того чтобы ждать, пока ценовое движение завершит пробой (и часто это лучшая часть движения), мы действуем в соответствии с сигналом импульса, предшествующим этому пробою и предсказывает его.

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

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

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



Реализация

Успешное преобразование торговой концепции в надежную автоматизированную систему требует методичного модульного подхода. В то время как обнаружение дивергенции RSI в сочетании с подтверждением структуры на основе канала теоретически кажется простым, практическая реализация представляет значительную техническую сложность. Чтобы эффективно справиться с этой сложностью, я спроектировал решение в виде двух независимых, но дополняющих друг друга модулей:
  1. Индикатор RSI Divergence: Автономный технический индикатор, который идентифицирует и визуально отмечает паттерны расхождения на ценовых графиках.
  2. Советник Equidistant Channel (равноудаленный канал): Автоматический торговый ассистент, который определяет и рисует структуру каналов в режиме реального времени.

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

Реализация следует за систематической прогрессией. Сначала я подробно расскажу о пошаговом построении индикатора RSI Divergence, объяснив логику определения пивота, проверки дивергенции и визуального представления сигнала. Далее я продемонстрирую алгоритм советника Channel Placer для определения и построения равноудаленных каналов на ценовых структурах. Когда эти инструменты работают согласованно, трейдеры получают своевременные предупреждения о расхождениях в рамках подтвержденных каналов, превращая субъективный технический анализ в механическую, основанную на правилах методологию торговли, подкрепленную алгоритмической точностью.

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

 Индикатор RSI Divergence Detector

Раздел 1: Установка и конфигурация индикаторного фреймворка

Основа нашего индикатора RSI Divergence Detector начинается с создания надлежащей системы индикаторов MetaTrader 5. Начнем с обязательных свойств индикатора, определяющих, как инструмент взаимодействует с торговой платформой. Директивы #property в строках 1-22 настраивают основные метаданные: информацию об авторских правах, отслеживание версий и, самое главное, параметры визуального представления.

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

Цветовые и стилевые настройки (строки 11-17) создают интуитивно понятный визуальный язык: DodgerBlue обозначает линию RSI, orange - пивот с максимальной ценой предыдущего периода, а DeepSkyBlue - пивот с минимальной ценой предыдущего периода, что позволяет сразу же визуально различать различные типы точек ценовой структуры.

//+------------------------------------------------------------------+
//|                                        RSIDivergenceDetector.mq5 |
//|                                    Copyright 2025, MetaQuotes Ltd|
//|                                            https://www.mql5.com  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description "RSI Divergence Detector with Main Chart Arrows"
#property description "Shows divergence arrows on main chart, stores pivot values"

#property indicator_separate_window
#property indicator_buffers 7
#property indicator_plots   3
#property indicator_color1  clrDodgerBlue      // RSI line
#property indicator_color2  clrOrange          // RSI High Pivots
#property indicator_color3  clrDeepSkyBlue     // RSI Low Pivots
#property indicator_width1  2
#property indicator_width2  3
#property indicator_width3  3
#property indicator_label1  "RSI"
#property indicator_label2  "RSI High Pivot"
#property indicator_label3  "RSI Low Pivot"

Раздел 2: Конфигурация пользователя и входные параметры

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

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

Особенно важен параметр InpRequireRSIBreak (строка 40), который реализует запрошенную вами логику подтверждения — гарантирует, что RSI действительно пробьет предыдущий уровень пивота, прежде чем сигнализировать о дивергенции. Это добавляет уровень проверки, который предотвращает преждевременные сигналы и повышает надежность.

//--- Input parameters
input int                InpRSIPeriod     = 14;            // RSI Period
input ENUM_APPLIED_PRICE InpRSIPrice      = PRICE_CLOSE;   // RSI Applied Price
input int                InpPivotStrength = 3;             // Pivot Strength (bars on each side)
input double             InpOverbought    = 70.0;          // Overbought Level
input double             InpOversold      = 30.0;          // Oversold Level
input bool               InpShowRegular   = true;          // Show Regular Divergences
input bool               InpShowHidden    = true;          // Show Hidden Divergences
input color              InpBullishColor  = clrLimeGreen;  // Bullish divergence arrow color
input color              InpBearishColor  = clrRed;        // Bearish divergence arrow color
input int                InpArrowSize     = 3;             // Arrow size on chart
input bool               InpAlertOnDivergence = true;      // Alert on divergence
input bool               InpSendNotification = false;      // Send notification
input bool               InpRequireRSIBreak = true;        // Require RSI to break pivot line
input double             InpMinDivergenceStrength = 2.0;   // Minimum RSI divergence strength
input int                InpMaxPivotDistance = 100;        // Max bars between pivots for divergence
input double             InpArrowOffsetPct = 0.3;          // Arrow offset percentage (0.3 = 30%)

Раздел 3: Проектирование структуры данных и управление памятью

В строках 44-67 реализуем архитектуру core data, используя пользовательскую структуру RSI_PIVOT. Такая структура представляет собой важное проектное решение: вместо того чтобы полагаться на простые массивы, в которых смешиваются различные типы данных, мы создаем выделенный тип данных, который инкапсулирует всю необходимую информацию о каждой точке разворота.

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

Конструктор (строки 59-62) обеспечивает правильную инициализацию, предотвращая распространенные ошибки программирования с неинициализированными переменными. Мы поддерживаем динамический массив rsiPivots[] для хранения этих структур, а pivotCount отслеживает текущее количество допустимых записей. Такой подход обеспечивает как целостность данных, так и эффективное использование памяти, поскольку мы можем легко добавлять, удалять или выполнять поиск по данным разворотов без сложного управления индексом.

//--- Indicator buffers
double BufferRSI[];
double BufferRSIHighPivot[];
double BufferRSILowPivot[];
double BufferRSIHigh[];
double BufferRSILow[];
double BufferPivotHigh[];
double BufferPivotLow[];

//--- Global variables
int rsiHandle;
datetime lastAlertTime;
string indicatorPrefix = "RSI_DIV_";

//--- Structures for storing pivot data
struct RSI_PIVOT
{
   int barIndex;
   double value;
   double price;
   datetime time;
   bool isHigh;
   int strength;
   bool isConfirmed;
   
   // Constructor to initialize values
   RSI_PIVOT() : barIndex(-1), value(0.0), price(0.0), time(0), 
                 isHigh(false), strength(0), isConfirmed(false) {}
};

RSI_PIVOT rsiPivots[];
int pivotCount = 0;

Раздел 4: Инициализация и настройка ресурсов

Функция OnInit() (строки 70-125) выполняет критическую фазу настройки при загрузке индикатора. Строки 74-80 устанавливают связь между нашими расчетными буферами и системой отображения индикатора с помощью функции SetIndexBuffer().

Каждый буфер получает определенную роль: BufferRSI хранит рассчитанные значения RSI, в то время как BufferRSIHighPivot и BufferRSILowPivot содержат визуальные маркеры для точек разворота.

Линии 83-94 настраивают поведение графика: основная линия RSI использует DRAW_LINE, в то время как точки разворота используют DRAW_ARROW с кодом символа 159 (квадратный символ). Мы создаем хэндл для встроенного индикатора RSI в строке 103 с помощью iRSI(), обеспечивающей эффективный доступ к предварительно рассчитанным значениям RSI без необходимости заново изобретать велосипед.

В строках 112-115 задаются визуальные ориентиры для уровней перекупленности/перепроданности, а в строке 121 вызывается функция CleanChartObjects() для удаления любых остаточных графических элементов от предыдущих запусков индикатора, обеспечивая чистое начальное состояние.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, BufferRSI, INDICATOR_DATA);
   SetIndexBuffer(1, BufferRSIHighPivot, INDICATOR_DATA);
   SetIndexBuffer(2, BufferRSILowPivot, INDICATOR_DATA);
   SetIndexBuffer(3, BufferRSIHigh, INDICATOR_DATA);
   SetIndexBuffer(4, BufferRSILow, INDICATOR_DATA);
   SetIndexBuffer(5, BufferPivotHigh, INDICATOR_DATA);
   SetIndexBuffer(6, BufferPivotLow, INDICATOR_DATA);
   
   //--- Set plot properties
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE);
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_ARROW);
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_ARROW);
   
   //--- Set arrow codes for RSI pivot points
   PlotIndexSetInteger(1, PLOT_ARROW, 159);  // Square dot for high pivots
   PlotIndexSetInteger(2, PLOT_ARROW, 159);  // Square dot for low pivots
   
   //--- Set empty values for arrow buffers
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   
   //--- Set indicator labels
   IndicatorSetString(INDICATOR_SHORTNAME, "RSI Divergence (" + string(InpRSIPeriod) + ")");
   IndicatorSetInteger(INDICATOR_DIGITS, 2);
   
   //--- Create RSI handle
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, InpRSIPrice);
   if(rsiHandle == INVALID_HANDLE)
   {
      Print("Failed to create RSI handle");
      return(INIT_FAILED);
   }
   
   //--- Set overbought/oversold levels
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpOversold);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, InpOverbought);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR, 0, clrSilver);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR, 1, clrSilver);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE, 0, STYLE_DOT);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE, 1, STYLE_DOT);
   
   lastAlertTime = 0;
   
   //--- Clean any existing objects from previous runs
   CleanChartObjects();
   
   return(INIT_SUCCEEDED);
}

Раздел 5: Основной обработчик расчетов и обработка данных

Функция OnCalculate() (строки 128-169) служит основным циклом обработки, вызываемым при каждом обновлении тика и бара. Мы начинаем с проверки в строке 130, чтобы убедиться в наличии достаточного количества исторических данных для значимых вычислений.

Строки 133-142 реализуют интеллектуальную логику пересчета: если это первый запуск (prev_calculated == 0), инициализируем наш пивотный массив и сбрасываем счетчики; в противном случае, продолжаем с того места, где остановились, оптимизируя эффективность за счет исключения избыточных вычислений. В строке 145 извлекаем значения RSI с помощью функции CopyBuffer() из нашего хэндла RSI — это демонстрирует надлежащее использование API технических индикаторов MetaTrader 5 для доступа к данным.

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

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < InpRSIPeriod + 10) return(0);
   
   int start;
   if(prev_calculated == 0)
   {
      start = InpRSIPeriod + InpPivotStrength;
      ArrayResize(rsiPivots, rates_total);
      pivotCount = 0;
      CleanChartObjects();
   }
   else
   {
      start = prev_calculated - 1;
   }
   
   //--- Get RSI values
   if(CopyBuffer(rsiHandle, 0, 0, rates_total, BufferRSI) <= 0)
      return(0);
   
   //--- Find RSI pivots
   FindRSIPivots(rates_total, start, high, low, time);
   
   //--- Detect divergences and draw arrows on main chart
   DetectDivergences(rates_total, start, high, low, close, time);
   
   //--- Clean old pivot data
   CleanOldPivots(rates_total);
   
   return(rates_total);
}

Раздел 6: Реализация алгоритма обнаружения пивотов

Функция FindRSIPivots() (строки 172-218) реализует основную логику идентификации точки поворота. Строки 175-176 удаляют предыдущие маркеры разворота из буферов отображения, гарантируя, что мы показываем только текущие, релевантные пивоты.

Цикл в строках 178-216 повторяется по барам, применяя критерии обнаружения пивотов. Вспомогательные функции IsHighPivot() и IsLowPivot() (строки 221-272) содержат фактическую логику определения: чтобы бар можно было квалифицировать как high pivot (пивот с максимальной ценой предыдущего периода), его значение RSI должно превышать указанное количество баров с обеих сторон (контролируется с помощью InpPivotStrength).

При обнаружении действительного пивота (строки 183-215), заполняем новую структуру RSI_PIVOT всеми соответствующими данными: индекс бара для справки, значение RSI для сравнения, уровень цены для анализа дивергенции, временная метка для отслеживания, а также классификация типов. Флагу isConfirmed изначально присвоено значение false, что позволяет нашей логике обнаружения дивергенции отмечать пивоты, как только они привели к действительному сигналу.

//+------------------------------------------------------------------+
//| Find RSI pivots function                                         |
//+------------------------------------------------------------------+
void FindRSIPivots(int rates_total, int start, const double &high[], const double &low[], const datetime &time[])
{
   // Clear pivot buffers
   ArrayFill(BufferRSIHighPivot, start, rates_total - start, EMPTY_VALUE);
   ArrayFill(BufferRSILowPivot, start, rates_total - start, EMPTY_VALUE);
   
   for(int i = start; i < rates_total - InpPivotStrength; i++)
   {
      //--- Check for RSI high pivot
      if(IsHighPivot(i, BufferRSI, InpPivotStrength))
      {
         if(pivotCount < ArraySize(rsiPivots))
         {
            rsiPivots[pivotCount].barIndex = i;
            rsiPivots[pivotCount].value = BufferRSI[i];
            rsiPivots[pivotCount].price = high[i];
            rsiPivots[pivotCount].time = time[i];
            rsiPivots[pivotCount].isHigh = true;
            rsiPivots[pivotCount].strength = InpPivotStrength;
            rsiPivots[pivotCount].isConfirmed = false;
            pivotCount++;
            
            BufferRSIHighPivot[i] = BufferRSI[i];
         }
      }
      
      //--- Check for RSI low pivot
      if(IsLowPivot(i, BufferRSI, InpPivotStrength))
      {
         if(pivotCount < ArraySize(rsiPivots))
         {
            rsiPivots[pivotCount].barIndex = i;
            rsiPivots[pivotCount].value = BufferRSI[i];
            rsiPivots[pivotCount].price = low[i];
            rsiPivots[pivotCount].time = time[i];
            rsiPivots[pivotCount].isHigh = false;
            rsiPivots[pivotCount].strength = InpPivotStrength;
            rsiPivots[pivotCount].isConfirmed = false;
            pivotCount++;
            
            BufferRSILowPivot[i] = BufferRSI[i];
         }
      }
   }
}

Раздел 7: Вспомогательные функции для определения пивота

Функции IsHighPivot() и IsLowPivot() содержат точную логику для определения точек колебания в значениях RSI. Эти функции реализуют то, что трейдеры обычно называют определением «силы пивота».

Чтобы бар можно было квалифицировать как «high pivot» (линии 221-246), его значение RSI должно быть больше указанного количества предыдущих баров (если смотреть слева) И больше указанного количества следующих баров (если смотреть справа).

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

//+------------------------------------------------------------------+
//| Check if bar is a high pivot                                     |
//+------------------------------------------------------------------+
bool IsHighPivot(int index, double &buffer[], int strength)
{
   if(index < strength || index >= ArraySize(buffer) - strength)
      return false;
   
   double pivotValue = buffer[index];
   
   // Check left side
   for(int i = 1; i <= strength; i++)
   {
      if(buffer[index - i] > pivotValue)
         return false;
   }
   
   // Check right side
   for(int i = 1; i <= strength; i++)
   {
      if(buffer[index + i] > pivotValue)
         return false;
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| Check if bar is a low pivot                                      |
//+------------------------------------------------------------------+
bool IsLowPivot(int index, double &buffer[], int strength)
{
   if(index < strength || index >= ArraySize(buffer) - strength)
      return false;
   
   double pivotValue = buffer[index];
   
   // Check left side
   for(int i = 1; i <= strength; i++)
   {
      if(buffer[index - i] < pivotValue)
         return false;
   }
   
   // Check right side
   for(int i = 1; i <= strength; i++)
   {
      if(buffer[index + i] < pivotValue)
         return false;
   }
   
   return true;
}

Раздел 8: Логика обнаружения дивергенций и генерация сигналов

Функция DetectDivergences() (строки 275-373) представляет собой интеллектуальную основу нашего индикатора. После проверки наличия достаточного количества пивотных данных в строке 277 мы реализуем структуру вложенного цикла (строки 280-372), которая сравнивает все возможные пивотные пары.

В строке 283 применяется практичный фильтр: пивоты, расположенные слишком далеко друг от друга (превышающие InpMaxPivotDistance), игнорируются, поскольку отдаленные корреляции часто отражают разные фазы рынка. Затем логика разделяется на две основные ветви: сравнения с high pivot для медвежьих дивергенций (строки 287-322) и сравнения с low pivot для бычьих расхождений (строки 323-372).

Для каждого типа дивергенции проверяем как обычные, так и скрытые множества, применяя специальные функции Check...Divergence(), инкапсулирующие точные критерии соотношения цена/RSI. При обнаружении действительной дивергенции строки 294-298 (для медвежьего тренда) и 330-334 (для бычьего тренда) рассчитывают соответствующее расположение стрелок, используя процентное отклонение от текущего ценового диапазона, обеспечивая, чтобы стрелки были четко видны, не затеняя движение цены.

//+------------------------------------------------------------------+
//| Detect divergences between price and RSI                         |
//+------------------------------------------------------------------+
void DetectDivergences(int rates_total, int start, const double &high[], const double &low[], 
                      const double &close[], const datetime &time[])
{
   if(pivotCount < 4) return;
   
   // Check all pivot pairs for divergence
   for(int i = pivotCount - 1; i >= 0; i--)
   {
      for(int j = i - 1; j >= 0; j--)
      {
         // Skip if pivots are too far apart
         if(rsiPivots[i].barIndex - rsiPivots[j].barIndex > InpMaxPivotDistance)
            continue;
         
         // Check if both are high pivots
         if(rsiPivots[i].isHigh && rsiPivots[j].isHigh)
         {
            // Check for regular bearish divergence
            if(InpShowRegular && CheckBearishDivergence(rsiPivots[i], rsiPivots[j], rates_total))
            {
               // Check RSI break confirmation if required
               if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, true))
               {
                  double arrowPrice = high[rsiPivots[i].barIndex];
                  double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex];
                  double offset = range * InpArrowOffsetPct;
                  
                  DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], 
                                arrowPrice + offset, false, "Bearish");
                  TriggerAlert("Regular Bearish Divergence detected!", time[rsiPivots[i].barIndex]);
                  // Mark as confirmed to avoid duplicate signals
                  rsiPivots[i].isConfirmed = true;
                  rsiPivots[j].isConfirmed = true;
               }
            }
            // Check for hidden bearish divergence
            else if(InpShowHidden && CheckHiddenBearishDivergence(rsiPivots[i], rsiPivots[j]))
            {
               if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, true))
               {
                  double arrowPrice = high[rsiPivots[i].barIndex];
                  double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex];
                  double offset = range * InpArrowOffsetPct;
                  
                  DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], 
                                arrowPrice + offset, false, "Bearish(H)");
                  TriggerAlert("Hidden Bearish Divergence detected!", time[rsiPivots[i].barIndex]);
                  rsiPivots[i].isConfirmed = true;
                  rsiPivots[j].isConfirmed = true;
               }
            }
         }
         // Check if both are low pivots
         else if(!rsiPivots[i].isHigh && !rsiPivots[j].isHigh)
         {
            // Check for regular bullish divergence
            if(InpShowRegular && CheckBullishDivergence(rsiPivots[i], rsiPivots[j], rates_total))
            {
               // Check RSI break confirmation if required
               if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, false))
               {
                  double arrowPrice = low[rsiPivots[i].barIndex];
                  double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex];
                  double offset = range * InpArrowOffsetPct;
                  
                  DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], 
                                arrowPrice - offset, true, "Bullish");
                  TriggerAlert("Regular Bullish Divergence detected!", time[rsiPivots[i].barIndex]);
                  rsiPivots[i].isConfirmed = true;
                  rsiPivots[j].isConfirmed = true;
               }
            }
            // Check for hidden bullish divergence
            else if(InpShowHidden && CheckHiddenBullishDivergence(rsiPivots[i], rsiPivots[j]))
            {
               if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, false))
               {
                  double arrowPrice = low[rsiPivots[i].barIndex];
                  double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex];
                  double offset = range * InpArrowOffsetPct;
                  
                  DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], 
                                arrowPrice - offset, true, "Bullish(H)");
                  TriggerAlert("Hidden Bullish Divergence detected!", time[rsiPivots[i].barIndex]);
                  rsiPivots[i].isConfirmed = true;
                  rsiPivots[j].isConfirmed = true;
               }
            }
         }
      }
   }
}

Раздел 9: Функции проверки и подтверждения дивергенций

Функции проверки CheckBearishDivergence(), CheckBullishDivergence(), CheckHiddenBearishDivergence() и CheckHiddenBullishDivergence() (строки 376-445) реализуют точные математические определения каждого типа дивергенции.

Каждая функция выполняется по определенному шаблону: проверка параметров, проверка соотношения цен, проверка соотношения RSI, проверка силы и проверка статуса подтверждения.

Например, функция CheckBearishDivergence() в строках 376-395 требует следующее: 1) более новый пивот имеет более высокую цену, чем более старый пивот, 2) более новый пивот имеет более низкое значение RSI (по крайней мере, на величину InpMinDivergenceStrength), 3) абсолютная разница соответствует минимальным требованиям к силе и 4) ни один из пивотов ранее не был подтвержден при другой дивергенции. Эти строгие критерии предотвращают ложные сигналы и гарантируют, что только значимые дивергенции активируют оповещения.

//+------------------------------------------------------------------+
//| Check for regular bearish divergence                             |
//+------------------------------------------------------------------+
bool CheckBearishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2, int rates_total)
{
   // pivot1 is newer, pivot2 is older
   if(pivot1.barIndex <= pivot2.barIndex) return false;
   if(pivot1.barIndex >= rates_total - 1 || pivot2.barIndex >= rates_total - 1) return false;
   
   // Price makes higher high
   bool priceHigher = pivot1.price > pivot2.price;
   
   // RSI makes lower high
   bool rsiLower = pivot1.value < pivot2.value - InpMinDivergenceStrength;
   
   // Ensure there's enough divergence strength
   bool enoughStrength = MathAbs(pivot2.value - pivot1.value) >= InpMinDivergenceStrength;
   
   // Not already confirmed
   bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed;
   
   return priceHigher && rsiLower && enoughStrength && notConfirmed;
}

//+------------------------------------------------------------------+
//| Check for regular bullish divergence                             |
//+------------------------------------------------------------------+
bool CheckBullishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2, int rates_total)
{
   // pivot1 is newer, pivot2 is older
   if(pivot1.barIndex <= pivot2.barIndex) return false;
   if(pivot1.barIndex >= rates_total - 1 || pivot2.barIndex >= rates_total - 1) return false;
   
   // Price makes lower low
   bool priceLower = pivot1.price < pivot2.price;
   
   // RSI makes higher low
   bool rsiHigher = pivot1.value > pivot2.value + InpMinDivergenceStrength;
   
   // Ensure there's enough divergence strength
   bool enoughStrength = MathAbs(pivot1.value - pivot2.value) >= InpMinDivergenceStrength;
   
   // Not already confirmed
   bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed;
   
   return priceLower && rsiHigher && enoughStrength && notConfirmed;
}

//+------------------------------------------------------------------+
//| Check for hidden bearish divergence                              |
//+------------------------------------------------------------------+
bool CheckHiddenBearishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2)
{
   // Price makes lower high
   bool priceLower = pivot1.price < pivot2.price;
   
   // RSI makes higher high
   bool rsiHigher = pivot1.value > pivot2.value + InpMinDivergenceStrength;
   
   // Not already confirmed
   bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed;
   
   return priceLower && rsiHigher && notConfirmed;
}

//+------------------------------------------------------------------+
//| Check for hidden bullish divergence                              |
//+------------------------------------------------------------------+
bool CheckHiddenBullishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2)
{
   // Price makes higher low
   bool priceHigher = pivot1.price > pivot2.price;
   
   // RSI makes lower low
   bool rsiLower = pivot1.value < pivot2.value - InpMinDivergenceStrength;
   
   // Not already confirmed
   bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed;
   
   return priceHigher && rsiLower && notConfirmed;
}

Раздел 10: Подтверждение пробоя RSI и проверка сигнала

Функция CheckRSIBreak() (строки 448-478) реализует указанный вами дополнительный уровень проверки — гарантируя, что RSI действительно пробьет предыдущий уровень пивота, прежде чем считать дивергенцию подтвержденной.

Эта функция решает распространенную проблему преждевременных сигналов дивергенции, которые возникают до того, как импульс действительно изменится. Строки 456-458 открывают окно ретроспективного анализа (10 баров) после нового пивота, во время которого мы отслеживаем состояние пробоя.

Для медвежьих дивергенций (строки 464-467) мы проверяем, опускается ли RSI ниже значения старшего пивота; для бычьих дивергенций (строки 469-472) мы проверяем, поднимается ли RSI выше значения старшего пивота. Этот этап подтверждения превращает индикатор из простого детектора паттернов в валидатор изменения импульса, что значительно повышает надежность сигнала.

//+------------------------------------------------------------------+
//| Check if RSI has broken the previous pivot level                 |
//+------------------------------------------------------------------+
bool CheckRSIBreak(RSI_PIVOT &olderPivot, RSI_PIVOT &newerPivot, int rates_total, bool isBearish)
{
   // For bearish: Check if RSI has broken below the older pivot's value
   // For bullish: Check if RSI has broken above the older pivot's value
   
   if(newerPivot.barIndex >= rates_total - 1) return false;
   
   // Look for break in recent bars after newer pivot
   int lookbackBars = 10;
   int startBar = newerPivot.barIndex + 1;
   int endBar = MathMin(rates_total - 1, newerPivot.barIndex + lookbackBars);
   
   for(int i = startBar; i <= endBar; i++)
   {
      if(isBearish)
      {
         // Bearish: RSI should break below older pivot value
         if(BufferRSI[i] < olderPivot.value)
            return true;
      }
      else
      {
         // Bullish: RSI should break above older pivot value
         if(BufferRSI[i] > olderPivot.value)
            return true;
      }
   }
   
   return false;
}

Раздел 11: Визуальное представление сигналов и управление графиками

Функция DrawChartArrow() (строки 481-529) обрабатывает визуальное отображение сигналов дивергенции на основном ценовом графике. Строки 483-484 генерируют уникальные имена объектов с использованием временной метки, что позволяет нам независимо управлять отдельными парами стрелок/меток. Строки 487-488 очищают все существующие объекты с одинаковыми именами, предотвращая дублирование при пересчете индикатора.

Затем функция разветвляется в зависимости от направления сигнала: бычьи сигналы используют объекты OBJ_ARROW_BUY (строки 491-505), в то время как медвежьи сигналы используют объекты OBJ_ARROW_SELL (строки 507-522). Каждая стрелка имеет настраиваемый стиль (цвет из InpBullishColor/InpBearishColor, размер из InpArrowSize) и включает текстовую метку, показывающую тип дивергенции.

Функция CleanChartObjects() (строки 532-542) н обеспечивает систематическую очистку всех объектов, созданных с помощью индикатора, поддерживая чистоту графика.

//+------------------------------------------------------------------+
//| Draw arrow on main chart                                         |
//+------------------------------------------------------------------+
void DrawChartArrow(int barIndex, datetime time, double price, bool isBullish, string labelText)
{
   string arrowName = indicatorPrefix + "Arrow_" + IntegerToString(time);
   string labelName = indicatorPrefix + "Label_" + IntegerToString(time);
   
   // Remove existing objects with same name
   ObjectDelete(0, arrowName);
   ObjectDelete(0, labelName);
   
   if(isBullish)
   {
      // Draw bullish arrow (up arrow)
      if(!ObjectCreate(0, arrowName, OBJ_ARROW_BUY, 0, time, price))
      {
         Print("Failed to create arrow object: ", GetLastError());
         return;
      }
      
      ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpBullishColor);
      ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize);
      ObjectSetInteger(0, arrowName, OBJPROP_BACK, false);
      
      // Draw label
      if(!ObjectCreate(0, labelName, OBJ_TEXT, 0, time, price))
         return;
      
      ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);
      ObjectSetInteger(0, labelName, OBJPROP_COLOR, InpBullishColor);
      ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
   }
   else
   {
      // Draw bearish arrow (down arrow)
      if(!ObjectCreate(0, arrowName, OBJ_ARROW_SELL, 0, time, price))
      {
         Print("Failed to create arrow object: ", GetLastError());
         return;
      }
      
      ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpBearishColor);
      ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize);
      ObjectSetInteger(0, arrowName, OBJPROP_BACK, false);
      
      // Draw label
      if(!ObjectCreate(0, labelName, OBJ_TEXT, 0, time, price))
         return;
      
      ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);
      ObjectSetInteger(0, labelName, OBJPROP_COLOR, InpBearishColor);
      ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
   }
   
   ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_CENTER);
   ObjectSetInteger(0, labelName, OBJPROP_BACK, false);
}

Раздел 12: Функции очистки и система оповещения

Функция CleanChartObjects() (строки 532-542) и функция OnDeinit() (строки 572-582) отвечают за управление критически важными ресурсами. Логика очистки пивотов поддерживает непрерывное отображение последних 500 пивотов, отбрасывая более старшие, чтобы предотвратить переполнение памяти при расширенном анализе графиков. Когда индикатор удален, OnDeinit() освобождает хэндл индикатора RSI с помощью функции IndicatorRelease() и очищает все объекты графика.

Функция TriggerAlert() реализует тщательно продуманную систему оповещения, сочетающую в себе осведомленность о сигналах и управление усталостью от уведомлений. Строка 554 соответствует предпочтениям пользователя в отношении оповещений.

В строке 557 реализована фильтрация по времени с использованием lastAlertTime, предотвращающая повторные оповещения для одного и того же бара, что часто вызывает раздражение в торговых индикаторах. При выполнении условий строка 562 запускает стандартное звуковое оповещение MetaTrader 5, а строки 565-568 дополнительно отправляют уведомления платформы, если они включены.

//+------------------------------------------------------------------+
//| Clean chart objects                                              |
//+------------------------------------------------------------------+
void CleanChartObjects()
{
   int total = ObjectsTotal(0, 0, -1);
   for(int i = total - 1; i >= 0; i--)
   {
      string name = ObjectName(0, i, 0, -1);
      if(StringFind(name, indicatorPrefix, 0) != -1)
      {
         ObjectDelete(0, name);
      }
   }
}

//+------------------------------------------------------------------+
//| Clean old pivot data                                             |
//+------------------------------------------------------------------+
void CleanOldPivots(int rates_total)
{
   if(pivotCount > 500)  // Keep last 500 pivots maximum
   {
      int newCount = 250;
      for(int i = 0; i < newCount; i++)
      {
         rsiPivots[i] = rsiPivots[pivotCount - newCount + i];
      }
      pivotCount = newCount;
   }
}

//+------------------------------------------------------------------+
//| Trigger alert function                                           |
//+------------------------------------------------------------------+
void TriggerAlert(string message, datetime time)
{
   if(!InpAlertOnDivergence) return;
   
   // Avoid repeated alerts for same bar
   if(time <= lastAlertTime) return;
   
   lastAlertTime = time;
   
   // Play sound
   Alert(message + " at ", TimeToString(time, TIME_DATE|TIME_MINUTES));
   
   // Send notification if enabled
   if(InpSendNotification)
      SendNotification("RSI Divergence: " + Symbol() + " " + 
                       StringSubstr(EnumToString(_Period), 7) + 
                       " - " + message + " at " + TimeToString(time));
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Delete RSI handle
   if(rsiHandle != INVALID_HANDLE)
      IndicatorRelease(rsiHandle);
   
   // Clean up chart objects
   CleanChartObjects();
}

После тщательного изучения архитектуры и реализации детектора дивергенций RSI, полный исходный код доступен в приложении под статьей. После создания нашей базы технического индикатора переходим ко второму компоненту нашей торговой системы: разработке советника Equidistant Channel Auto-Placement. Этот автоматизированный инструмент дополнит наше определение дивергенции, выявляя структурные ценовые паттерны, и в конечном итоге создаст интегрированное алгоритмическое торговое решение. После реализации мы поделимся всесторонними результатами тестирования обоих проектов и разработаем стратегическую дорожную карту для дальнейшего развития и системной интеграции.

Советник Equidistant Channel Auto-Placement

Советник Equidistant Channel Auto-Placement EA создает надежную основу благодаря тщательно разработанной системе перечисления и всесторонним настраиваемым пользователем параметрам. Перечисление ENUM_CHANNEL_TYPE обеспечивает четкое семантическое различие между восходящими и нисходящими каналами, заменяя традиционную бычью/медвежью терминологию более описательными терминами, соответствующими торговым установкам: восходящие каналы (более высокие минимумы) указывают на возможности продажи, в то время как нисходящие каналы (более низкие максимумы) предполагают возможности покупки. Это изменение терминологии представляет собой важное дизайнерское решение, которое приводит терминологию канала в соответствие с реальным поведением трейдеров, а не с абстрактными техническими концепциями.

Раздел 1: Архитектурная основа и фреймворк пользовательской конфигурации

Система входных параметров реализует сложный интерфейс настройки, сочетающий автоматизацию с управлением пользователем. Каждый параметр служит определенной цели в алгоритме определения канала: LookbackBars определяет объем исторических данных, SwingStrength контролирует чувствительность определения точки колебания, в то время как MinTouchesPerLine вводит новое требование проверки нескольких касаний цены на каждой линии канала — функцию, значительно снижающую количество ложных сигналов. Параметр TouchTolerancePips демонстрирует внимание к реальным торговым условиям, учитывая незначительные отклонения цен, которые в противном случае могли бы привести к нарушению подлинной структуры каналов.

enum ENUM_CHANNEL_TYPE
{
   CHANNEL_NONE,
   CHANNEL_RISING,   // Higher lows - Sell setups (formerly bullish)
   CHANNEL_FALLING   // Lower highs - Buy setups (formerly bearish)
};

input bool   EnableRisingChannels   = true;
input bool   EnableFallingChannels  = true;
input int    LookbackBars           = 150;
input int    SwingStrength          = 2;
input bool   ShowChannel            = true;
input color  RisingChannelColor     = clrRed;
input color  FallingChannelColor    = clrLimeGreen;
input int    ChannelWidth           = 1;
input int    MinChannelLengthBars   = 15;
input double MinChannelHeightPct    = 0.5;
input bool   AlertOnNewChannel      = true;
input int    MinTouchesPerLine      = 2;
input double TouchTolerancePips     = 5.0;
input int    MaxExtensionBars       = 50;
input bool   ExtendLeft             = true;
input bool   ExtendRight            = false;

Раздел 2: Архитектура управления состоянием и инициализации

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

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

datetime lastBarTime = 0;
string currentChannelName = "";
string channelPrefix = "SmartCh_";
bool channelFound = false;
ENUM_CHANNEL_TYPE currentChannelType = CHANNEL_NONE;
datetime lastAlertTime = 0;
int channelStartBar = -1;
int channelEndBar = -1;

int OnInit()
{
   DeleteAllChannels();
   Print("Smart Single Channel EA initialized");
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // Optional: Delete channel on exit
   // DeleteAllChannels();
}

Раздел 3: Интеллектуальная, управляемая событиями обработка 

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

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

void OnTick()
{
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if(currentBarTime <= lastBarTime) return;
   lastBarTime = currentBarTime;
   
   int barShift = iBarShift(_Symbol, _Period, currentBarTime);
   if(barShift % 3 != 0) return;
   
   FindAndDrawSingleChannel();
}

Раздел 4: Логика обнаружения и проверки основных каналов

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

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

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

void FindAndDrawSingleChannel()
{
   bool risingFound = false;
   int risePoint1 = -1, risePoint2 = -1;
   double riseSlope = 0;
   int riseTouchCount = 0;
   
   if(EnableRisingChannels)
      risingFound = FindRisingChannel(risePoint1, risePoint2, riseSlope, riseTouchCount);
   
   bool fallingFound = false;
   int fallPoint1 = -1, fallPoint2 = -1;
   double fallSlope = 0;
   int fallTouchCount = 0;
   
   if(EnableFallingChannels)
      fallingFound = FindFallingChannel(fallPoint1, fallPoint2, fallSlope, fallTouchCount);
   
   bool drawChannel = false;
   int point1 = -1, point2 = -1;
   double slope = 0;
   ENUM_CHANNEL_TYPE newType = CHANNEL_NONE;
   int touchCount = 0;
   
   if(risingFound && fallingFound)
   {
      if(risePoint1 > fallPoint1 || riseTouchCount > fallTouchCount + 1)
      {
         drawChannel = true;
         point1 = risePoint1;
         point2 = risePoint2;
         slope = riseSlope;
         newType = CHANNEL_RISING;
         touchCount = riseTouchCount;
      }
      else
      {
         drawChannel = true;
         point1 = fallPoint1;
         point2 = fallPoint2;
         slope = fallSlope;
         newType = CHANNEL_FALLING;
         touchCount = fallTouchCount;
      }
   }
   else if(risingFound && riseTouchCount >= MinTouchesPerLine * 2)
   {
      drawChannel = true;
      point1 = risePoint1;
      point2 = risePoint2;
      slope = riseSlope;
      newType = CHANNEL_RISING;
      touchCount = riseTouchCount;
   }
   else if(fallingFound && fallTouchCount >= MinTouchesPerLine * 2)
   {
      drawChannel = true;
      point1 = fallPoint1;
      point2 = fallPoint2;
      slope = fallSlope;
      newType = CHANNEL_FALLING;
      touchCount = fallTouchCount;
   }
   
   if(drawChannel && touchCount >= MinTouchesPerLine * 2)
   {
      bool isNewChannel = (!channelFound) || 
                         (currentChannelType != newType) ||
                         (MathAbs(point1 - channelStartBar) > 10) ||
                         (TimeCurrent() - lastAlertTime > 3600);
      
      if(isNewChannel)
      {
         DeleteAllChannels();
         
         if(DrawChannel(point1, point2, slope, newType))
         {
            channelFound = true;
            currentChannelType = newType;
            channelStartBar = point1;
            channelEndBar = point2;
            
            if(AlertOnNewChannel && (TimeCurrent() - lastAlertTime > 3600))
            {
               string typeStr = (newType == CHANNEL_RISING) ? "Rising (Sell Setup)" : "Falling (Buy Setup)";
               string message = StringFormat("%s channel detected on %s %s. %d touches confirmed.", 
                           typeStr, Symbol(), PeriodToString(_Period), touchCount);
               Alert(message);
               lastAlertTime = TimeCurrent();
            }
         }
      }
   }
   else
   {
      if(channelFound && !IsChannelStillValid())
      {
         DeleteAllChannels();
         channelFound = false;
         currentChannelType = CHANNEL_NONE;
      }
   }
}

Раздел 5: Расширенное определение канала с помощью проверки касаний

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

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

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

bool FindRisingChannel(int &point1, int &point2, double &slope, int &touchCount)
{
   int swingLows[];
   FindSwingLows(swingLows, SwingStrength, LookbackBars);
   
   if(ArraySize(swingLows) < 2) return false;
   
   int bestPoint1 = -1, bestPoint2 = -1;
   double bestScore = -1;
   int bestTouches = 0;
   
   for(int i = 0; i < ArraySize(swingLows) - 1; i++)
   {
      for(int j = i + 1; j < ArraySize(swingLows); j++)
      {
         int low1 = swingLows[i];
         int low2 = swingLows[j];
         
         if(iLow(NULL, 0, low1) <= iLow(NULL, 0, low2)) continue;
         if(MathAbs(low1 - low2) < MinChannelLengthBars) continue;
         
         double low1Price = iLow(NULL, 0, low1);
         double low2Price = iLow(NULL, 0, low2);
         
         double barDiff = MathAbs(low1 - low2);
         slope = (low1Price - low2Price) / barDiff;
         
         if(slope < 0.00005) continue;
         
         double channelHeight = CalculateChannelHeight(low1, low2, true);
         int touches = CountChannelTouches(low1, low2, slope, low2Price, channelHeight, true);
         
         double recencyScore = 100.0 - (low1 * 100.0 / LookbackBars);
         double touchScore = touches * 25.0;
         double heightScore = (channelHeight / SymbolInfoDouble(_Symbol, SYMBOL_POINT)) / 100.0;
         
         double totalScore = recencyScore + touchScore + heightScore;
         
         if(totalScore > bestScore && touches >= MinTouchesPerLine * 2)
         {
            bestScore = totalScore;
            bestPoint1 = low1;
            bestPoint2 = low2;
            bestTouches = touches;
         }
      }
   }
   
   if(bestScore > 0)
   {
      point1 = bestPoint1;
      point2 = bestPoint2;
      slope = (iLow(NULL, 0, bestPoint1) - iLow(NULL, 0, bestPoint2)) / MathAbs(bestPoint1 - bestPoint2);
      touchCount = bestTouches;
      return true;
   }
   
   return false;
}

Раздел 6: Математические основы геометрии каналов

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

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

Функция CountChannelTouches() вводит проверку на основе допусков, учитывающую реальное поведение цен в тех случаях, когда точные касания линий встречаются редко. Расчет допуска демонстрирует профессиональное внимание к масштабированию для конкретного инструмента, обеспечивая согласованное поведение различных символов и значений пипсов. Двухконтурная структура отдельно проверяет прикосновения к границам обоих каналов, предоставляя подробную диагностическую информацию о целостности канала.

double CalculateChannelHeight(int point1, int point2, bool isRising)
{
   double maxHeight = 0;
   int startBar = MathMin(point1, point2);
   int endBar = MathMax(point1, point2);
   
   double price1 = (isRising) ? iLow(NULL, 0, point1) : iHigh(NULL, 0, point1);
   double price2 = (isRising) ? iLow(NULL, 0, point2) : iHigh(NULL, 0, point2);
   
   double slope = (price1 - price2) / (point1 - point2);
   double intercept = price1 - slope * point1;
   
   for(int bar = startBar; bar <= endBar; bar++)
   {
      double currentPrice = (isRising) ? iHigh(NULL, 0, bar) : iLow(NULL, 0, bar);
      double baseLinePrice = slope * bar + intercept;
      double deviation = MathAbs(currentPrice - baseLinePrice);
      
      if(deviation > maxHeight) maxHeight = deviation;
   }
   
   double minHeight = (isRising) ? iLow(NULL, 0, startBar) * MinChannelHeightPct / 100.0 : 
                                   iHigh(NULL, 0, startBar) * MinChannelHeightPct / 100.0;
   
   return MathMax(maxHeight, minHeight);
}

int CountChannelTouches(int point1, int point2, double slope, double basePrice, double height, bool isRising)
{
   int touches = 0;
   int startBar = MathMin(point1, point2);
   int endBar = MathMax(point1, point2);
   
   double tolerance = TouchTolerancePips * SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 10;
   
   for(int bar = startBar; bar <= endBar; bar++)
   {
      double currentPrice = (isRising) ? iLow(NULL, 0, bar) : iHigh(NULL, 0, bar);
      double baseLinePrice = slope * (bar - point2) + basePrice;
      
      if(MathAbs(currentPrice - baseLinePrice) <= tolerance) touches++;
   }
   
   for(int bar = startBar; bar <= endBar; bar++)
   {
      double currentPrice = (isRising) ? iHigh(NULL, 0, bar) : iLow(NULL, 0, bar);
      double parallelLinePrice = slope * (bar - point2) + basePrice + (isRising ? height : -height);
      
      if(MathAbs(currentPrice - parallelLinePrice) <= tolerance) touches++;
   }
   
   return touches;
}

Раздел 7: Механизм определения точки поворота

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

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

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

void FindSwingLows(int &swingPoints[], int strength, int lookback)
{
   ArrayResize(swingPoints, 0);
   
   for(int i = strength; i < MathMin(lookback, Bars(NULL, 0) - strength); i++)
   {
      bool isSwingLow = true;
      double currentLow = iLow(NULL, 0, i);
      
      for(int left = 1; left <= strength && isSwingLow; left++)
         if(iLow(NULL, 0, i - left) < currentLow) isSwingLow = false;
      
      if(isSwingLow)
         for(int right = 1; right <= strength && isSwingLow; right++)
            if(iLow(NULL, 0, i + right) < currentLow) isSwingLow = false;
      
      if(isSwingLow)
      {
         int size = ArraySize(swingPoints);
         ArrayResize(swingPoints, size + 1);
         swingPoints[size] = i;
      }
   }
   
   ArraySort(swingPoints);
}

Раздел 8: Профессиональная визуализация каналов с контролируемым расширением

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

Функция демонстрирует профессиональный расчет координат, преобразующий математические параметры каналов в визуальные элементы. Логика расширения реализует контролируемое управление границами: ExtendLeft предоставляет исторический контекст, в то время как ExtendRight намеренно отключен, чтобы предотвратить чрезмерное проецирование каналов на пустое пространство графика — прямой ответ на отзывы тестирования о том, что каналы отображаются "слишком далеко от текущей цены".

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

bool DrawChannel(int point1, int point2, double slope, ENUM_CHANNEL_TYPE type)
{
   if(!ShowChannel) return false;
   
   DeleteAllChannels();
   
   datetime time1 = iTime(NULL, 0, point1);
   datetime time2 = iTime(NULL, 0, point2);
   
   double price1, price2;
   color channelColor;
   string channelLabel;
   bool isRising = (type == CHANNEL_RISING);
   
   if(isRising)
   {
      price1 = iLow(NULL, 0, point1);
      price2 = iLow(NULL, 0, point2);
      channelColor = RisingChannelColor;
      channelLabel = "Rising Channel (Sell)";
   }
   else
   {
      price1 = iHigh(NULL, 0, point1);
      price2 = iHigh(NULL, 0, point2);
      channelColor = FallingChannelColor;
      channelLabel = "Falling Channel (Buy)";
   }
   
   double channelHeight = CalculateChannelHeight(point1, point2, isRising);
   
   int extensionBars = MathMin(MaxExtensionBars, MathAbs(point1 - point2) / 2);
   datetime extendedTime1 = time1;
   datetime extendedTime2 = time2;
   
   if(ExtendLeft)
   {
      int extendBack = MathMin(extensionBars, point2);
      extendedTime2 = iTime(NULL, 0, point2 - extendBack);
   }
   
   if(ExtendRight)
   {
      int extendForward = MathMin(extensionBars, Bars(NULL, 0) - point1 - 1);
      extendedTime1 = iTime(NULL, 0, point1 + extendForward);
   }
   
   currentChannelName = channelPrefix + "Base";
   ObjectCreate(0, currentChannelName, OBJ_TREND, 0, extendedTime2, price2, time1, price1);
   ObjectSetInteger(0, currentChannelName, OBJPROP_COLOR, channelColor);
   ObjectSetInteger(0, currentChannelName, OBJPROP_WIDTH, ChannelWidth);
   ObjectSetInteger(0, currentChannelName, OBJPROP_RAY_RIGHT, false);
   ObjectSetInteger(0, currentChannelName, OBJPROP_RAY_LEFT, ExtendLeft);
   ObjectSetInteger(0, currentChannelName, OBJPROP_BACK, true);
   
   string parallelName = channelPrefix + "Parallel";
   double parallelPrice1 = price1 + (isRising ? channelHeight : -channelHeight);
   double parallelPrice2 = price2 + (isRising ? channelHeight : -channelHeight);
   
   ObjectCreate(0, parallelName, OBJ_TREND, 0, extendedTime2, parallelPrice2, time1, parallelPrice1);
   ObjectSetInteger(0, parallelName, OBJPROP_COLOR, channelColor);
   ObjectSetInteger(0, parallelName, OBJPROP_WIDTH, ChannelWidth);
   ObjectSetInteger(0, parallelName, OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, parallelName, OBJPROP_RAY_RIGHT, false);
   ObjectSetInteger(0, parallelName, OBJPROP_RAY_LEFT, ExtendLeft);
   ObjectSetInteger(0, parallelName, OBJPROP_BACK, true);
   
   string labelName = channelPrefix + "Label";
   ObjectCreate(0, labelName, OBJ_TEXT, 0, time1, isRising ? price1 + channelHeight * 1.1 : price1 - channelHeight * 1.1);
   ObjectSetString(0, labelName, OBJPROP_TEXT, channelLabel);
   ObjectSetInteger(0, labelName, OBJPROP_COLOR, channelColor);
   ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
   ObjectSetInteger(0, labelName, OBJPROP_BACK, true);
   
   return true;
}

Раздел 9: Проверка персистентности каналов и управление ресурсами

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

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

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

bool IsChannelStillValid()
{
   if(!channelFound) return false;
   
   int recentBars = 10;
   bool isRising = (currentChannelType == CHANNEL_RISING);
   
   for(int i = 0; i < recentBars; i++)
   {
      double high = iHigh(NULL, 0, i);
      double low = iLow(NULL, 0, i);
      
      if(isRising)
      {
         if(low > iLow(NULL, 0, channelStartBar) * 1.01) return false;
      }
      else
      {
         if(high < iHigh(NULL, 0, channelStartBar) * 0.99) return false;
      }
   }
   
   return true;
}

void DeleteAllChannels()
{
   int total = ObjectsTotal(0);
   for(int i = total - 1; i >= 0; i--)
   {
      string name = ObjectName(0, i);
      if(StringFind(name, channelPrefix) == 0)
         ObjectDelete(0, name);
   }
   currentChannelName = "";
}


Тестирование

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

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

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

Развёртывание детектора RSIDivergenceDetector

На приведенном ниже скриншоте показан советник Equidistant Channel Auto-Placement EA, тестируемый в тестере стратегий и демонстрирующий эффективность в точном определении и отрисовке структуры каналов. При использовании с детектором RSI Divergence Detector торговые сигналы могут быть проверены на соответствие этим структурным образованиям, что обеспечивает фреймворк множественного подтверждения для принятия решений об исполнении с более высокой степенью уверенности.

Testing the ECAP

Тестирование советника EquidistantChannel Auto-Placement EA в тестере стратегий


Заключение

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

Эти инструменты работают синергетически на одном графике без конфликтов — детектор RSI Divergence Detector функционирует как пользовательский индикатор, в то время как Equidistant Channel Auto-Placement работает как советник. Наш модульный подход к разработке позволил целенаправленно реализовывать и тестировать каждый компонент, сохраняя при этом четкое разделение задач.

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

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

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



Основные уроки

Основные уроки Описание
1. Архитектура модульной системы Разделять сложные системы на независимые, тестируемые модули (индикатор + советник), которые могут быть интегрированы позже.
2. Управление состоянием для контроля за оповещениями Реализовывать регулирование оповещений на основе времени (lastAlertTime), чтобы предотвратить непрерывную отправку оповещений и усталость от уведомлений.
3. Валидация - Первый паттерн проектирования Прежде чем сигнализировать о паттернах, требует многократного изменения цены и перерывов в подтверждении, отдавая приоритет надежности.
4. Интеллектуальное управление жизненным циклом объектов Использовать уникальные префиксы именования и систематическую очистку (OnDeinit), чтобы предотвратить накопление объектов графика.
5. Оптимизированная с точки зрения результата обработка событий Реализовывать регулирование обнаружения и обработки новых баров для балансировки скорости реагирования и эффективности вычислений.

Вложения

Имя исходного файла Версия Описание
RSIDivergenceDetector.mq5 1.00 Настраиваемый индикатор дивергенций RSI, обнаруживающий обычные / скрытые дивергенции, сохраняет значения пивота и отображает четкие стрелки на покупку / продажу на основном графике с настраиваемыми оповещениями и подтверждением пробоя RSI.
EquidistantChannelAuto-Placement.mq5 1.00 Интеллектуальный советник, который автоматически определяет и выстраивает единый допустимый равноудаленный канал с проверкой касаний, управляемыми расширениями и интеллектуальным регулированием предупреждений о растущих (на продажу) и падающих (на покупку) настройках.

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

Прикрепленные файлы |
Знакомство с языком MQL5 (Часть 26): Советник по зонам поддержки/сопротивления — выявление, проверка пробоя и вход Знакомство с языком MQL5 (Часть 26): Советник по зонам поддержки/сопротивления — выявление, проверка пробоя и вход
В этой статье вы научитесь созданию советника на языке MQL5, который автоматически определяет зоны поддержки и сопротивления и исполняет сделки на их основе. Вы узнаете, как запрограммировать своего советника так, чтобы он выявлял эти ключевые рыночные уровни, осуществлял мониторинг отскоков цены и принимал торговые решения без ручного вмешательства.
Нейросети в трейдинге: Возмущённые модели пространства состояний для анализа рыночной динамики (Энкодер) Нейросети в трейдинге: Возмущённые модели пространства состояний для анализа рыночной динамики (Энкодер)
В статье представлен практический подход к реализации модуля P-SSE для анализа потоков рыночных данных в реальном времени. Продуманное использование стека исторических состояний позволяет каждому срезу рынка обрабатываться лишь один раз, исключая дублирование вычислений и ускоряя онлайн-анализ. Представленные решения обеспечивают высокую точность, устойчивость модели и эффективность обработки, делая фреймворк мощным инструментом для анализа микроимпульсов на финансовых рынках.
Машинное обучение и Data Science (Часть 35): NumPy в MQL5 – искусство создания сложных алгоритмов с меньшим объемом кода Машинное обучение и Data Science (Часть 35): NumPy в MQL5 – искусство создания сложных алгоритмов с меньшим объемом кода
Библиотека NumPy лежит в основе практически всех алгоритмов машинного обучения на языке программирования Python. В этой статье мы собираемся реализовать аналогичный модуль, содержащий набор всего сложного кода, который поможет нам создавать сложные модели и алгоритмы любого типа.
Знакомство с языком MQL5 (Часть 25): Создание советника для торговли по графическим объектам (II) Знакомство с языком MQL5 (Часть 25): Создание советника для торговли по графическим объектам (II)
В этой статье объясняется, как создать советник, который взаимодействует с графическими объектами, особенно с трендовыми линиями, чтобы выявлять потенциальные пробои и развороты и торговать по ним. Вы узнаете, как советник подтверждает действительность сигналов, управляет частотой торговли и поддерживает согласованность с выбранными пользователем стратегиями.