English 中文 Español Deutsch 日本語
preview
Разработка инструментария для анализа движения цен (Часть 13): RSI Sentinel

Разработка инструментария для анализа движения цен (Часть 13): RSI Sentinel

MetaTrader 5Торговые системы |
490 1
Christian Benjamin
Christian Benjamin

Содержание


Введение

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

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


Обзор стратегии

Дивергенция RSI

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

В контексте дивергенции RSI существуют два основных типа

1. Обычная дивергенция RSI

Регулярная дивергенция RSI обычно рассматривается как сигнал разворота. Это свидетельствует о том, что текущий тренд теряет силу и может вот-вот развернуться.

  • Обычная бычья дивергенция RSI

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

Бычье расхождение

Рис 1. Бычье расхождение

  • Обычная медвежья дивергенция RSI

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

Сигнал на продажу

Рис 2. Медвежья дивергенция

2. Скрытая дивергенция RSI

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

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

скрытая бычья

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

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

медвежья дивергенция

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

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

Тип дивергенции RSI Ценовое действие Действие RSI Тип сигнала Ожидание
Обычный бычий Low Low(LL) Higher Low(HL) Разворот вверх Нисходящий тренд к восходящему тренду
Обычный медвежий Higher High(HH)  Lower Higher(LH) Разворот вниз Восходящий тренд к нисходящему тренду
Скрытый бычий High Low(HL) Low Low(LL) Продолжение вверх Восходящий тренд продолжается
Скрытый медвежий Lower High (LH) High High(HH) Продолжение вниз Нисходящий тренд продолжается

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

Вот что он делает:

1. Сбор и подготовка данных

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

2. Определение точек колебания

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

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

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

5. Генерация визуальных и логарифмических сигналов

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


Код MQL5

//+--------------------------------------------------------------------+
//|                                                RSI Divergence.mql5 |
//|                                 Copyright 2025, Christian Benjamin |
//|                                               https://www.mql5.com |
//+--------------------------------------------------------------------+
#property copyright "2025, MetaQuotes Software Corp."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

//---- Input parameters
input int    InpRSIPeriod             = 14;    // RSI period
input int    InpSwingLeft             = 1;     // Bars to the left for swing detection (relaxed)
input int    InpSwingRight            = 1;     // Bars to the right for swing detection (relaxed)
input int    InpLookback              = 100;   // Number of bars to scan for divergence
input int    InpEvalBars              = 5;     // Bars after which to evaluate a signal
input int    InpMinBarsBetweenSignals = 1;     // Minimum bars between same-type signals (allows frequent re-entry)
input double InpArrowOffset           = 3.0;   // Arrow offset (in points) for display
input double InpMinSwingDiffPct       = 0.05;  // Lower minimum % difference to qualify as a swing
input double InpMinRSIDiff            = 1.0;   // Lower minimum difference in RSI between swing points

// Optional RSI threshold filter for bullish divergence (disabled by default)
input bool   InpUseRSIThreshold       = false; // If true, require earlier RSI swing to be oversold for bullish divergence
input double InpRSIOversold           = 30;    // RSI oversold level
input double InpRSIOverbought         = 70;    // RSI overbought level (if needed for bearish)

//---- Global variables
int      rsiHandle;         // Handle for the RSI indicator
double   rsiBuffer[];       // Buffer for RSI values
double   lowBuffer[];       // Buffer for low prices
double   highBuffer[];      // Buffer for high prices
double   closeBuffer[];     // Buffer for close prices
datetime timeBuffer[];       // Buffer for bar times
int      g_totalBars = 0;   // Number of bars in our copied arrays
datetime lastBarTime = 0;   // Time of last closed bar

//---- Structure to hold signal information
struct SignalInfo
  {
   string            type;         // e.g. "RegBearish Divergence", "HiddenBullish Divergence"
   int               barIndex;     // Bar index where the signal was generated
   datetime          signalTime;   // Time of the signal bar
   double            signalPrice;  // Price used for the signal (swing high for bearish, swing low for bullish)
  };

SignalInfo signals[];  // Global array to store signals

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
     {
      Print("Error creating RSI handle");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(rsiHandle != INVALID_HANDLE)
      IndicatorRelease(rsiHandle);
   EvaluateSignalsAndPrint();
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Process once per new closed candle (using bar1's time)
   datetime currentBarTime = iTime(_Symbol, _Period, 1);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

//--- Copy RSI data
   ArrayResize(rsiBuffer, InpLookback);
   ArraySetAsSeries(rsiBuffer, true);
   if(CopyBuffer(rsiHandle, 0, 0, InpLookback, rsiBuffer) <= 0)
     {
      Print("Error copying RSI data");
      return;
     }

//--- Copy price and time data
   ArrayResize(lowBuffer, InpLookback);
   ArrayResize(highBuffer, InpLookback);
   ArrayResize(closeBuffer, InpLookback);
   ArraySetAsSeries(lowBuffer, true);
   ArraySetAsSeries(highBuffer, true);
   ArraySetAsSeries(closeBuffer, true);
   if(CopyLow(_Symbol, _Period, 0, InpLookback, lowBuffer) <= 0 ||
      CopyHigh(_Symbol, _Period, 0, InpLookback, highBuffer) <= 0 ||
      CopyClose(_Symbol, _Period, 0, InpLookback, closeBuffer) <= 0)
     {
      Print("Error copying price data");
      return;
     }

   ArrayResize(timeBuffer, InpLookback);
   ArraySetAsSeries(timeBuffer, true);
   if(CopyTime(_Symbol, _Period, 0, InpLookback, timeBuffer) <= 0)
     {
      Print("Error copying time data");
      return;
     }

   g_totalBars = InpLookback;

//--- Identify swing lows and swing highs
   int swingLows[];
   int swingHighs[];
   int startIndex = InpSwingLeft;
   int endIndex   = g_totalBars - InpSwingRight;
   for(int i = startIndex; i < endIndex; i++)
     {
      if(IsSignificantSwingLow(i, InpSwingLeft, InpSwingRight))
        {
         ArrayResize(swingLows, ArraySize(swingLows) + 1);
         swingLows[ArraySize(swingLows) - 1] = i;
        }
      if(IsSignificantSwingHigh(i, InpSwingLeft, InpSwingRight))
        {
         ArrayResize(swingHighs, ArraySize(swingHighs) + 1);
         swingHighs[ArraySize(swingHighs) - 1] = i;
        }
     }

//--- Bearish Divergence (using swing highs)
   if(ArraySize(swingHighs) >= 2)
     {
      ArraySort(swingHighs); // ascending order: index 0 is most recent
      int recent   = swingHighs[0];
      int previous = swingHighs[1];

      // Regular Bearish Divergence: Price makes a higher high while RSI makes a lower high
      if(highBuffer[recent] > highBuffer[previous] &&
         rsiBuffer[recent] < rsiBuffer[previous] &&
         (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
        {
         Print("Regular Bearish Divergence detected at bar ", recent);
         DisplaySignal("RegBearish Divergence", recent);
        }
      // Hidden Bearish Divergence: Price makes a lower high while RSI makes a higher high
      else
         if(highBuffer[recent] < highBuffer[previous] &&
            rsiBuffer[recent] > rsiBuffer[previous] &&
            (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
           {
            Print("Hidden Bearish Divergence detected at bar ", recent);
            DisplaySignal("HiddenBearish Divergence", recent);
           }
     }

//--- Bullish Divergence (using swing lows)
   if(ArraySize(swingLows) >= 2)
     {
      ArraySort(swingLows); // ascending order: index 0 is most recent
      int recent   = swingLows[0];
      int previous = swingLows[1];

      // Regular Bullish Divergence: Price makes a lower low while RSI makes a higher low
      if(lowBuffer[recent] < lowBuffer[previous] &&
         rsiBuffer[recent] > rsiBuffer[previous] &&
         (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
        {
         // Optionally require the earlier swing's RSI be oversold
         if(!InpUseRSIThreshold || rsiBuffer[previous] <= InpRSIOversold)
           {
            Print("Regular Bullish Divergence detected at bar ", recent);
            DisplaySignal("RegBullish Divergence", recent);
           }
        }
      // Hidden Bullish Divergence: Price makes a higher low while RSI makes a lower low
      else
         if(lowBuffer[recent] > lowBuffer[previous] &&
            rsiBuffer[recent] < rsiBuffer[previous] &&
            (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
           {
            Print("Hidden Bullish Divergence detected at bar ", recent);
            DisplaySignal("HiddenBullish Divergence", recent);
           }
     }
  }

//+------------------------------------------------------------------------+
//| IsSignificantSwingLow: Determines if the bar at 'index' is a swing low |
//+------------------------------------------------------------------------+
bool IsSignificantSwingLow(int index, int left, int right)
  {
   double currentLow = lowBuffer[index];
// Check left side for a local minimum condition
   for(int i = index - left; i < index; i++)
     {
      if(i < 0)
         continue;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
     }
// Check right side for a local minimum condition
   for(int i = index + 1; i <= index + right; i++)
     {
      if(i >= g_totalBars)
         break;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
     }
   return true;
  }

//+--------------------------------------------------------------------------+
//| IsSignificantSwingHigh: Determines if the bar at 'index' is a swing high |
//+--------------------------------------------------------------------------+
bool IsSignificantSwingHigh(int index, int left, int right)
  {
   double currentHigh = highBuffer[index];
// Check left side for a local maximum condition
   for(int i = index - left; i < index; i++)
     {
      if(i < 0)
         continue;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
     }
// Check right side for a local maximum condition
   for(int i = index + 1; i <= index + right; i++)
     {
      if(i >= g_totalBars)
         break;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| DisplaySignal: Draws an arrow on the chart and records the signal|
//+------------------------------------------------------------------+
void DisplaySignal(string signalText, int barIndex)
  {
// Prevent duplicate signals on the same bar (or too close)
   for(int i = 0; i < ArraySize(signals); i++)
     {
      if(StringFind(signals[i].type, signalText) != -1)
         if(MathAbs(signals[i].barIndex - barIndex) < InpMinBarsBetweenSignals)
            return;
     }

// Update a "LatestSignal" label for regular signals.
   if(StringFind(signalText, "Reg") != -1)
     {
      string labelName = "LatestSignal";
      if(ObjectFind(0, labelName) == -1)
        {
         if(!ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0))
           {
            Print("Failed to create LatestSignal label");
            return;
           }
         ObjectSetInteger(0, labelName, OBJPROP_CORNER, 0);
         ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 10);
         ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 20);
         ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
        }
      ObjectSetString(0, labelName, OBJPROP_TEXT, signalText);
     }

// Create an arrow object for the signal.
   string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex);
   if(ObjectFind(0, arrowName) < 0)
     {
      int arrowCode = 0;
      double arrowPrice = 0.0;
      color arrowColor = clrWhite;
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

      if(StringFind(signalText, "Bullish") != -1)
        {
         arrowCode  = 233; // Wingdings up arrow
         arrowColor = clrLime;
         arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point);
        }
      else
         if(StringFind(signalText, "Bearish") != -1)
           {
            arrowCode  = 234; // Wingdings down arrow
            arrowColor = clrRed;
            arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point);
           }

      if(!ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeBuffer[barIndex], arrowPrice))
        {
         Print("Failed to create arrow object ", arrowName);
         return;
        }
      ObjectSetInteger(0, arrowName, OBJPROP_COLOR, arrowColor);
      ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode);
     }

// Record the signal for evaluation.
   SignalInfo sig;
   sig.type = signalText;
   sig.barIndex = barIndex;
   sig.signalTime = timeBuffer[barIndex];
   if(StringFind(signalText, "Bullish") != -1)
      sig.signalPrice = lowBuffer[barIndex];
   else
      sig.signalPrice = highBuffer[barIndex];
   ArrayResize(signals, ArraySize(signals) + 1);
   signals[ArraySize(signals) - 1] = sig;

   UpdateSignalCountLabel();
  }

//+------------------------------------------------------------------+
//| UpdateSignalCountLabel: Updates a label showing signal counts    |
//+------------------------------------------------------------------+
void UpdateSignalCountLabel()
  {
   int regCount = 0, hidCount = 0;
   for(int i = 0; i < ArraySize(signals); i++)
     {
      if(StringFind(signals[i].type, "Reg") != -1)
         regCount++;
      else
         if(StringFind(signals[i].type, "Hidden") != -1)
            hidCount++;
     }
   string countText = "Regular Signals: " + IntegerToString(regCount) +
                      "\nHidden Signals: " + IntegerToString(hidCount);
   string countLabel = "SignalCount";
   if(ObjectFind(0, countLabel) == -1)
     {
      if(!ObjectCreate(0, countLabel, OBJ_LABEL, 0, 0, 0))
        {
         Print("Failed to create SignalCount label");
         return;
        }
      ObjectSetInteger(0, countLabel, OBJPROP_CORNER, 0);
      ObjectSetInteger(0, countLabel, OBJPROP_XDISTANCE, 10);
      ObjectSetInteger(0, countLabel, OBJPROP_YDISTANCE, 40);
      ObjectSetInteger(0, countLabel, OBJPROP_COLOR, clrYellow);
     }
   ObjectSetString(0, countLabel, OBJPROP_TEXT, countText);
  }

//+--------------------------------------------------------------------+
//| EvaluateSignalsAndPrint: After backtesting, prints signal accuracy |
//+--------------------------------------------------------------------+
void EvaluateSignalsAndPrint()
  {
   double closeAll[];
   int totalBars = CopyClose(_Symbol, _Period, 0, WHOLE_ARRAY, closeAll);
   if(totalBars <= 0)
     {
      Print("Error copying complete close data for evaluation");
      return;
     }
   ArraySetAsSeries(closeAll, true);

   int totalEvaluated = 0, regTotal = 0, hidTotal = 0;
   int regEval = 0, hidEval = 0;
   int regCorrect = 0, hidCorrect = 0;

   for(int i = 0; i < ArraySize(signals); i++)
     {
      int evalIndex = signals[i].barIndex - InpEvalBars;
      if(evalIndex < 0)
         continue;
      double evalClose = closeAll[evalIndex];

      if(StringFind(signals[i].type, "Bullish") != -1)
        {
         if(StringFind(signals[i].type, "Reg") != -1)
           {
            regTotal++;
            regEval++;
            if(evalClose > signals[i].signalPrice)
               regCorrect++;
           }
         else
            if(StringFind(signals[i].type, "Hidden") != -1)
              {
               hidTotal++;
               hidEval++;
               if(evalClose > signals[i].signalPrice)
                  hidCorrect++;
              }
         totalEvaluated++;
        }
      else
         if(StringFind(signals[i].type, "Bearish") != -1)
           {
            if(StringFind(signals[i].type, "Reg") != -1)
              {
               regTotal++;
               regEval++;
               if(evalClose < signals[i].signalPrice)
                  regCorrect++;
              }
            else
               if(StringFind(signals[i].type, "Hidden") != -1)
                 {
                  hidTotal++;
                  hidEval++;
                  if(evalClose < signals[i].signalPrice)
                     hidCorrect++;
                 }
            totalEvaluated++;
           }
     }

   double overallAccuracy = (totalEvaluated > 0) ? (double)(regCorrect + hidCorrect) / totalEvaluated * 100.0 : 0.0;
   double regAccuracy = (regEval > 0) ? (double)regCorrect / regEval * 100.0 : 0.0;
   double hidAccuracy = (hidEval > 0) ? (double)hidCorrect / hidEval * 100.0 : 0.0;

   Print("----- Backtest Signal Evaluation -----");
   Print("Total Signals Generated: ", ArraySize(signals));
   Print("Signals Evaluated: ", totalEvaluated);
   Print("Overall Accuracy: ", DoubleToString(overallAccuracy, 2), "%");
   Print("Regular Signals: ", regTotal, " | Evaluated: ", regEval, " | Accuracy: ", DoubleToString(regAccuracy, 2), "%");
   Print("Hidden Signals:  ", hidTotal, " | Evaluated: ", hidEval, " | Accuracy: ", DoubleToString(hidAccuracy, 2), "%");
  }
//+------------------------------------------------------------------+


Разбор кода

1. Заголовок и входные параметры

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

Информация о файле и авторе


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

Директивы по управлению версиями и компиляции

Директивы #property устанавливают важные свойства, такие как номер версии и использование строгих правил компиляции (#property strict). Это помогает поддерживать согласованность и сокращать количество потенциальных ошибок во время разработки и развертывания. Далее следует раздел входных параметров, необходимый для настройки. Эти параметры позволяют вам или любому другому пользователю точно настроить поведение логики обнаружения дивергенций без изменения основного кода. Ниже приведены несколько основных моментов:

Параметры определения RSI и колебаний (swing)

  • InpRSIPeriod - период RSI.
  • InpSwingLeft и InpSwingRight - сколько полос с каждой стороны учитывается при обнаружении точек колебания. Изменение этих значений делает обнаружение колебаний более мягким или более строгим.

Настройки дивергенции и оценки сигнала

  • InpLookback - сколько баров в прошлом скрипт будет сканировать на предмет расхождений.
  • InpEvalBars - количество баров, которое необходимо выждать перед оценкой успешности сигнала.
  • InpMinBarsBetweenSignals - помогает избежать дублирования сигналов, обеспечивая минимальное расстояние между похожими сигналами.

Настройки отображения

  • InpArrowOffset - расстояние (в пунктах), на которое смещаются стрелки от точки колебания, что повышает визуальную ясность на графике.

Дополнительный пороговый фильтр RSI

  • InpUseRSIThreshold, InpRSIOversold и InpRSIOverbought обеспечивает дополнительный уровень сортировки. Это гарантирует, что в случае бычьей дивергенции более раннее колебание RSI находится в области перепроданности, если пользователь решит включить этот фильтр.

//+------------------------------------------------------------------+
//|                                           RSI Divergence.mql5    |
//|                               Copyright 2025, Christian Benjamin |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2025, MetaQuotes Software Corp."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

//---- Input parameters
input int    InpRSIPeriod             = 14;    // RSI period
input int    InpSwingLeft             = 1;     // Bars to the left for swing detection (relaxed)
input int    InpSwingRight            = 1;     // Bars to the right for swing detection (relaxed)
input int    InpLookback              = 100;   // Number of bars to scan for divergence
input int    InpEvalBars              = 5;     // Bars after which to evaluate a signal
input int    InpMinBarsBetweenSignals = 1;     // Minimum bars between same-type signals (allows frequent re-entry)
input double InpArrowOffset           = 3.0;   // Arrow offset (in points) for display
input double InpMinSwingDiffPct       = 0.05;  // Lower minimum % difference to qualify as a swing
input double InpMinRSIDiff            = 1.0;   // Lower minimum difference in RSI between swing points

// Optional RSI threshold filter for bullish divergence (disabled by default)
input bool   InpUseRSIThreshold       = false; // If true, require earlier RSI swing to be oversold for bullish divergence
input double InpRSIOversold           = 30;    // RSI oversold level
input double InpRSIOverbought         = 70;    // RSI overbought level (if needed for bearish)

2. Инициализация индикатора

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

  • Функция iRSI вызывается с необходимыми параметрами.
  • Реализована обработка ошибок для выявления любых сбоев при создании хэндла.
  • Инициализация гарантирует, что наш индикатор готов к сбору и анализу данных.
int OnInit()
{
   // Create the RSI indicator handle with the specified period
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
   {
      Print("Error creating RSI handle");
      return(INIT_FAILED);
   }
   return(INIT_SUCCEEDED);
}

3. Сбор данных о каждой новой свече

В функции OnTick() мы проверяем, закрылась ли новая свеча перед обработкой. Это гарантирует, что наш анализ всегда работает с полными данными. Затем мы копируем массивы значений RSI, минимумов, максимумов, закрытий и временных данных за настраиваемый период просмотра. Настройка массивов как серий гарантирует, что данные будут упорядочены, начиная с самого последнего столбца с индексом 0.

  • Код ждет закрытия следующей свечи, чтобы избежать обработки неполных данных.
  • Данные RSI и цен извлекаются с помощью таких функций, как CopyBuffer и CopyLow/High/Close/Time.
  • Использование ArraySetAsSeries сохраняет правильный порядок анализа временных рядов.

void OnTick()
{
   // Process only once per new closed candle by comparing bar times
   datetime currentBarTime = iTime(_Symbol, _Period, 1);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;
   
   // Copy RSI data for a given lookback period
   ArrayResize(rsiBuffer, InpLookback);
   ArraySetAsSeries(rsiBuffer, true);
   if(CopyBuffer(rsiHandle, 0, 0, InpLookback, rsiBuffer) <= 0)
   {
      Print("Error copying RSI data");
      return;
   }
   
   // Copy price data (lows, highs, closes) and time data for analysis
   ArrayResize(lowBuffer, InpLookback);
   ArrayResize(highBuffer, InpLookback);
   ArrayResize(closeBuffer, InpLookback);
   ArraySetAsSeries(lowBuffer, true);
   ArraySetAsSeries(highBuffer, true);
   ArraySetAsSeries(closeBuffer, true);
   if(CopyLow(_Symbol, _Period, 0, InpLookback, lowBuffer) <= 0 ||
      CopyHigh(_Symbol, _Period, 0, InpLookback, highBuffer) <= 0 ||
      CopyClose(_Symbol, _Period, 0, InpLookback, closeBuffer) <= 0)
   {
      Print("Error copying price data");
      return;
   }
   
   ArrayResize(timeBuffer, InpLookback);
   ArraySetAsSeries(timeBuffer, true);
   if(CopyTime(_Symbol, _Period, 0, InpLookback, timeBuffer) <= 0)
   {
      Print("Error copying time data");
      return;
   }
   
   g_totalBars = InpLookback;
   
   // (Further processing follows here...)
}

4. Обнаружение колебаний (определение минимумов и максимумов колебаний)

Прежде чем обнаружить расхождения, мы должны сначала определить существенные точки колебаний. Две вспомогательные функции, IsSignificantSwingLow и IsSignificantSwingHigh, используются для определения локальных минимумов и максимумов. Они делают это, сравнивая минимум или максимум бара с соседними барами в заданном окне и проверяя, что процентная разница соответствует установленному пороговому значению.
  • Функции проверяют как левую, так и правую часть текущего бара.
  • Они рассчитывают процентную разницу, чтобы гарантировать, что отмечаются только существенные колебания.
  • Такая сортировка снижает уровень шума, гарантируя, что наш анализ дивергенций будет сосредоточен на значимых движениях рынка.
bool IsSignificantSwingLow(int index, int left, int right)
{
   double currentLow = lowBuffer[index];
   // Check left side for local minimum condition
   for(int i = index - left; i < index; i++)
   {
      if(i < 0) continue;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   // Check right side for local minimum condition
   for(int i = index + 1; i <= index + right; i++)
   {
      if(i >= g_totalBars) break;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   return true;
}

bool IsSignificantSwingHigh(int index, int left, int right)
{
   double currentHigh = highBuffer[index];
   // Check left side for local maximum condition
   for(int i = index - left; i < index; i++)
   {
      if(i < 0) continue;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   // Check right side for local maximum condition
   for(int i = index + 1; i <= index + right; i++)
   {
      if(i >= g_totalBars) break;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   return true;
}

5. Обнаружение дивергенций: медвежья и бычья дивергенция

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

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

// --- Bearish Divergence (using swing highs)
if(ArraySize(swingHighs) >= 2)
{
   ArraySort(swingHighs); // Ensure ascending order: index 0 is most recent
   int recent   = swingHighs[0];
   int previous = swingHighs[1];
   
   // Regular Bearish Divergence: Price makes a higher high while RSI makes a lower high
   if(highBuffer[recent] > highBuffer[previous] &&
      rsiBuffer[recent] < rsiBuffer[previous] &&
      (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
   {
      Print("Regular Bearish Divergence detected at bar ", recent);
      DisplaySignal("RegBearish Divergence", recent);
   }
   // Hidden Bearish Divergence: Price makes a lower high while RSI makes a higher high
   else if(highBuffer[recent] < highBuffer[previous] &&
           rsiBuffer[recent] > rsiBuffer[previous] &&
           (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
   {
      Print("Hidden Bearish Divergence detected at bar ", recent);
      DisplaySignal("HiddenBearish Divergence", recent);
   }
}

// --- Bullish Divergence (using swing lows)
if(ArraySize(swingLows) >= 2)
{
   ArraySort(swingLows); // Ensure ascending order: index 0 is most recent
   int recent   = swingLows[0];
   int previous = swingLows[1];
   
   // Regular Bullish Divergence: Price makes a lower low while RSI makes a higher low
   if(lowBuffer[recent] < lowBuffer[previous] &&
      rsiBuffer[recent] > rsiBuffer[previous] &&
      (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
   {
      // Optionally require the earlier RSI swing to be oversold
      if(!InpUseRSIThreshold || rsiBuffer[previous] <= InpRSIOversold)
      {
         Print("Regular Bullish Divergence detected at bar ", recent);
         DisplaySignal("RegBullish Divergence", recent);
      }
   }
   // Hidden Bullish Divergence: Price makes a higher low while RSI makes a lower low
   else if(lowBuffer[recent] > lowBuffer[previous] &&
           rsiBuffer[recent] < rsiBuffer[previous] &&
           (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
   {
      Print("Hidden Bullish Divergence detected at bar ", recent);
      DisplaySignal("HiddenBullish Divergence", recent);
   }
}

6. Отображение и запись сигнала

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

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

void DisplaySignal(string signalText, int barIndex)
{
   // Prevent duplicate signals on the same or nearby bars
   for(int i = 0; i < ArraySize(signals); i++)
   {
      if(StringFind(signals[i].type, signalText) != -1)
         if(MathAbs(signals[i].barIndex - barIndex) < InpMinBarsBetweenSignals)
            return;
   }
     
   // Update a label for the latest regular signal
   if(StringFind(signalText, "Reg") != -1)
   {
      string labelName = "LatestSignal";
      if(ObjectFind(0, labelName) == -1)
      {
         if(!ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0))
         {
            Print("Failed to create LatestSignal label");
            return;
         }
         ObjectSetInteger(0, labelName, OBJPROP_CORNER, 0);
         ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 10);
         ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 20);
         ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
      }
      ObjectSetString(0, labelName, OBJPROP_TEXT, signalText);
   }
     
   // Create an arrow object to mark the signal on the chart
   string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex);
   if(ObjectFind(0, arrowName) < 0)
   {
      int arrowCode = 0;
      double arrowPrice = 0.0;
      color arrowColor = clrWhite;
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
          
      if(StringFind(signalText, "Bullish") != -1)
      {
         arrowCode  = 233; // Wingdings up arrow
         arrowColor = clrLime;
         arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point);
      }
      else if(StringFind(signalText, "Bearish") != -1)
      {
         arrowCode  = 234; // Wingdings down arrow
         arrowColor = clrRed;
         arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point);
      }
          
      if(!ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeBuffer[barIndex], arrowPrice))
      {
         Print("Failed to create arrow object ", arrowName);
         return;
      }
      ObjectSetInteger(0, arrowName, OBJPROP_COLOR, arrowColor);
      ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode);
   }
     
   // Record the signal details for later backtesting evaluation
   SignalInfo sig;
   sig.type = signalText;
   sig.barIndex = barIndex;
   sig.signalTime = timeBuffer[barIndex];
   if(StringFind(signalText, "Bullish") != -1)
      sig.signalPrice = lowBuffer[barIndex];
   else
      sig.signalPrice = highBuffer[barIndex];
   ArrayResize(signals, ArraySize(signals) + 1);
   signals[ArraySize(signals) - 1] = sig;
     
   UpdateSignalCountLabel();
}

Оценка тестирования на истории при деинициализации

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

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

void EvaluateSignalsAndPrint()
{
   double closeAll[];
   int totalBars = CopyClose(_Symbol, _Period, 0, WHOLE_ARRAY, closeAll);
   if(totalBars <= 0)
   {
      Print("Error copying complete close data for evaluation");
      return;
   }
   ArraySetAsSeries(closeAll, true);
   
   int totalEvaluated = 0, regTotal = 0, hidTotal = 0;
   int regEval = 0, hidEval = 0;
   int regCorrect = 0, hidCorrect = 0;
   
   for(int i = 0; i < ArraySize(signals); i++)
   {
      int evalIndex = signals[i].barIndex - InpEvalBars;
      if(evalIndex < 0)
         continue;
      double evalClose = closeAll[evalIndex];
      
      if(StringFind(signals[i].type, "Bullish") != -1)
      {
         if(StringFind(signals[i].type, "Reg") != -1)
         {
            regTotal++;
            regEval++;
            if(evalClose > signals[i].signalPrice)
               regCorrect++;
         }
         else if(StringFind(signals[i].type, "Hidden") != -1)
         {
            hidTotal++;
            hidEval++;
            if(evalClose > signals[i].signalPrice)
               hidCorrect++;
         }
         totalEvaluated++;
      }
      else if(StringFind(signals[i].type, "Bearish") != -1)
      {
         if(StringFind(signals[i].type, "Reg") != -1)
         {
            regTotal++;
            regEval++;
            if(evalClose < signals[i].signalPrice)
               regCorrect++;
         }
         else if(StringFind(signals[i].type, "Hidden") != -1)
         {
            hidTotal++;
            hidEval++;
            if(evalClose < signals[i].signalPrice)
               hidCorrect++;
         }
         totalEvaluated++;
      }
   }
     
   double overallAccuracy = (totalEvaluated > 0) ? (double)(regCorrect + hidCorrect) / totalEvaluated * 100.0 : 0.0;
   double regAccuracy = (regEval > 0) ? (double)regCorrect / regEval * 100.0 : 0.0;
   double hidAccuracy = (hidEval > 0) ? (double)hidCorrect / hidEval * 100.0 : 0.0;
     
   Print("----- Backtest Signal Evaluation -----");
   Print("Total Signals Generated: ", ArraySize(signals));
   Print("Signals Evaluated: ", totalEvaluated);
   Print("Overall Accuracy: ", DoubleToString(overallAccuracy, 2), "%");
   Print("Regular Signals: ", regTotal, " | Evaluated: ", regEval, " | Accuracy: ", DoubleToString(regAccuracy, 2), "%");
   Print("Hidden Signals:  ", hidTotal, " | Evaluated: ", hidEval, " | Accuracy: ", DoubleToString(hidAccuracy, 2), "%");
}


Тестирование и результаты

После успешной компиляции советника с помощью MetaEditor перетащите его на график для тестирования. Обязательно используйте демо-счет, чтобы не рисковать реальными деньгами. Вы также можете добавить индикатор RSI на свой график для легкого подтверждения сигналов при тестировании вашего советника. Для этого перейдите на вкладку "Индикаторы", выберите индикатор RSI в папке Panels и задайте желаемые параметры, убедившись, что они соответствуют параметрам вашего советника. Ниже показано, как добавить окно индикатора RSI на график MetaTrader 5. Вы также можете увидеть подтвержденный сигнал — обычную бычью дивергенцию на минутном таймфрейме.

Бычья конвергенция

Рис. 5. Настройка индикатора и результата теста 1

Ниже представлен еще один тест, который мы провели на Boom 500, подтвержденный как ценовым действием, так и индикатором RSI, показывающим сигнал на продажу.

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

Рис. 6. Результат теста 2

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

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

Рис. 7. Результат теста 3


Заключение

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

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

Дата Название инструмента  Описание Версия  Обновления  Примечания
01/10/24 Chart Projector Скрипт для наложения эффекта призрака на движение цены за предыдущий день. 1.0 Первоначальная версия Первый инструмент в Lynnchris Tools Chest
18/11/24 Analytical Comment Предоставляет информацию за предыдущий день в табличном формате, а также прогнозирует будущее направление рынка. 1.0 Первоначальная версия Второй инструмент в Lynnchris Tools Chest
27/11/24 Analytics Master Регулярное обновление рыночных показателей каждые два часа  1.01 Вторая версия Третий инструмент в Lynnchris Tools Chest
02/12/24 Analytics Forecaster  Регулярное обновление рыночных показателей каждые два часа с интеграцией с Telegram 1.1 Третья версия Инструмент номер 4
09/12/24 Volatility Navigator Советник анализирует рыночные условия с помощью полос Боллинджера, RSI и ATR. 1.0 Первоначальная версия Инструмент номер 5
19/12/24 Mean Reversion Signal Reaper  Анализирует рынок и генерирует сигналы, используя стратегию возврата к среднему  1.0  Первоначальная версия  Инструмент номер 6 
9/01/25  Signal Pulse  Анализирует несколько таймфреймов 1.0  Первоначальная версия  Инструмент номер 7 
17/01/25  Metrics Board  Панель с кнопками для анализа  1.0  Первоначальная версия Инструмент номер 8 
21/01/25 External Flow Аналитика с помощью внешних библиотек 1.0  Первоначальная версия Инструмент номер 9 
27/01/25 VWAP Взвешенная по объему средняя цена   1.3  Первоначальная версия  Инструмент номер 10 
02/02/25  Heikin Ashi  Сглаживание тренда и идентификация сигналов разворота  1.0  Первоначальная версия  Инструмент номер 11
04/02/25  FibVWAP  Генерация сигнала с помощью анализа Python  1.0  Первоначальная версия  Инструмент номер 12
14/02/25  RSI DIVERGENCE  Дивергенция цены и RSI  1.0  Первоначальная версия  Инструмент номер 13 

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

Прикрепленные файлы |
RSI_DIVERGENCE.mq5 (33.61 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
linfo2
linfo2 | 18 февр. 2025 в 20:05
Еще раз спасибо вам, Крис, за ваши хорошо продуманные и полезные статьи. Я очень ценю их как шаблон для модификации. Я также ценю ваши эрудированные мысли и практические реализации. У меня возникли проблемы с EvaluateSignalsAndPrint , написано, что функция возвращает Error Copying Compete close data for evaluation . Последняя ошибка - '4003'. Я не настолько умен, чтобы понять, почему при использовании переменной WHOLE_ARRAY функция не работает. Для меня, если я заменю 'WHOLE_ARRAY в copyclose на подсчет закрытых баров, я могу получить возврат 'Backtest Signal Evaluation' . BTW Я нахожусь в странном часовом поясе GMT +13 и иногда имею проблемы с локальными и серверными датами и временем, но, возможно, это может помочь кому-то.
Разработка инструментария для анализа движения цен (Часть 14): Parabolic Stop and Reverse Разработка инструментария для анализа движения цен (Часть 14): Parabolic Stop and Reverse
Использование технических индикаторов в анализе ценового движения — эффективный подход. Эти индикаторы часто выделяют ключевые уровни разворотов и коррекций, предоставляя ценную информацию о динамике рынка. В этой статье мы продемонстрируем разработку автоматизированного инструмента, который генерирует сигналы с использованием индикатора Parabolic SAR.
От новичка до эксперта: Индикатор Market Periods Synchronizer От новичка до эксперта: Индикатор Market Periods Synchronizer
В настоящем обсуждении мы представляем инструмент синхронизации таймфреймов от старших к младшим, предназначенный для решения проблемы анализа рыночных паттернов, охватывающих периоды старших таймфреймов. Встроенные маркеры периодов в MetaTrader 5 часто ограничены, жестки и их нелегко настроить для нестандартных таймфреймов. Наше решение использует язык MQL5 для разработки индикатора, обеспечивающего динамичный и наглядный способ выравнивания структур старших таймфреймов на графиках младших таймфреймов. Этот инструмент может быть очень полезен для детального анализа рынка. Чтобы узнать больше о его функциях и реализации, приглашаю вас присоединиться к обсуждению.
Разработка динамического советника на нескольких парах (Часть 3): Стратегии возврата к среднему и моментума Разработка динамического советника на нескольких парах (Часть 3): Стратегии возврата к среднему и моментума
В этой статье мы рассмотрим третью часть нашего пути в формулировании динамического мультипарного советника (Dynamic Multi-Pair Expert Advisor), сосредоточив внимание на интеграции стратегий торговли на основе возврата к среднему и моментума. Мы разберем, как обнаруживать и действовать при отклонениях цен от среднего (Z-оценка), а также как измерять моментум по нескольким валютным парам, чтобы определить направление торговли.
Нейросети в трейдинге: Агрегация движения по времени (Окончание) Нейросети в трейдинге: Агрегация движения по времени (Окончание)
Представляем фреймворк TMA — интеллектуальную систему, способную прогнозировать рыночную динамику с достаточной точностью. В этой статье мы собрали все компоненты в единую архитектуру и превратили её в полноценного торгового агента, который анализирует рынок и принимает решения в реальном времени.