English 中文 Deutsch 日本語
preview
Разработка инструментария для анализа движения цен (Часть 21): Поиск разворотов рыночной структуры

Разработка инструментария для анализа движения цен (Часть 21): Поиск разворотов рыночной структуры

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

Содержание


Введение

Представленный здесь советник решает одну из самых сложных задач в трейдинге — проблему ложных сигналов разворота. Рваное, флетовое движение цены сбивает с толку простые алгоритмы поиска разворотов, из-за чего трейдеры ловят в серию "пил". Детектор разворота рыночной структуры Market Structure Flip Detector решает эту задачу, преобразуя ATR в фильтр на основе количества баров. Он игнорирует незначительные колебания, фиксирует только валидные максимумы и минимумы, а затем сигнализирует о медвежьем развороте, когда более высокий максимум сменяется более низким максимумом, либо о бычьем развороте, когда более низкий минимум превращается в более высокий минимум. В статье мы увидим, как:

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

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


Польза инструмента

На флетовых рынках ложные сигналы могут составлять более половины всех триггеров на основе разворотов. Результат — пилы и отрицательное математическое ожидание. Глубину разворота можно регулировать, если привязать фильтр свинг-точек к индикатору Average True Range (ATR), который был разработан в 1978 году Уайлдером для более точного измерения волатильности по сравнению с простым диапазоном high-low. Когда ATR растет в периоды всплесков волатильности, фильтр расширяется и игнорирует мелкие хаотичные колебания. Когда рынок успокаивается, он сужается, позволяя фиксировать реальные развороты.

Истинный диапазон (True Range, TR) для каждого бара определяется как:

Формула TR


ATR — это n-периодное простое скользящее среднее TR (по умолчанию n = 14).

Преобразуем ATR в глубину по количеству баров d:

ATR

где Point — минимальный ценовой шаг инструмента, m — множитель ATR, а f — коэффициент ослабления. 

Медвежий разворот возникает тогда, когда мы сначала фиксируем более высокий максимум, а затем видим, что следующий свинг-максимум опускается ниже этого пика. Бычий — зеркально. Если предположить, что доходности подчиняются нормальному распределению с нулевым средним и дисперсией, назовем это "квадрат волатильности", то вероятность того, что один конкретный бар окажется максимальным в интервале длиной "два раза глубина плюс один" бар, равна просто единице, деленной на это же значение "дважды глубина + 1". Поскольку мы напрямую связываем глубину с рыночной волатильностью, оценивая волатильность как ATR, деленный на квадратный корень из половины π, мы напрямую управляем частотой ложных сигналов. На практике это позволяет подобрать такие параметры, при которых уровень шума удерживается примерно на уровне 5%.

Исследования TradeStation показали, что пивот-окна на основе ATR сокращают количество шумовых сделок примерно на 40% и увеличивают чистую прибыль примерно на 22% за 5 лет на данных S&P 500. По данным QuantifiedStrategies.com, развороты с фильтром по ATR повышают долю успешных сделок примерно с 35% до 58% и увеличивают среднее соотношение прибыль/риск с ~1,1 до ~1,8 на тестах по EURUSD и фьючерсам ES. Отзывы сообщества также подчеркивают, что инструменты для поиска разворотов с ATR-окном хорошо согласуются с институциональными пробоями ордер-флоу, особенно на таймфреймах 1H и 4H.


План действий (краткий обзор)

Данный советник отфильтровывает рыночный шум, преобразуя текущее значение ATR в гибкое "окно глубины": более широкое в условиях высокой волатильности и более узкое на спокойных рынках, после чего валидирует каждый закрывшийся бар (его максимум или минимум) относительно соседних баров в пределах этого окна. Он запоминает последние два подтвержденных свинг-максимума и минимума и отслеживает простой флаг направления, который переключается в состояние up, когда новый максимум превышает предыдущий, либо в состояние down, когда новый минимум опускается ниже предшествующего. Когда рыночная структура разворачивается — то есть в восходящем состоянии последний максимум оказывается ниже предыдущего (медвежий разворот), либо в нисходящем состоянии последний минимум оказывается выше предыдущего (бычий разворот), — советник размещает на графике цветную стрелку, подписывает оба разворота, обновляет информацию на панели и при необходимости запускает звуковые или push-уведомления. Это позволит видеть только реальные развороты типа из более высокого максимума в более низкий или из более низкого минимума в более высокий.

Медвежий разворот

Восходящий тренд определяется, когда цена формирует два последовательных более высоких максимума. После этого советник ищет свинг-максимум ниже предыдущего свинг-максимума. Свинг-максимум определяется как наивысшая точка в окне, образованном индикатором Average True Range (ATR). Когда советник находится в состоянии "up" и обнаруживает такой более низкий максимум, он помечает соответствующий бар красной стрелкой с подписью "LH". Помимо визуального обозначения, советник генерирует уведомление и записывает медвежий разворот, сигнализируя о том, что продавцы начинают перехватывать контроль.

Медвежий разворот

Бычий разворот

Нисходящий тренд устанавливается, когда цена фиксирует два последовательных более низких минимума. Затем советник определяет свинг-минимум, который превышает предыдущий свинг-минимум. Свинг-минимум определяется как наинизшая точка в окне, основанном на показателях ATR. Когда советник находится в состоянии "down" и обнаруживает такой более высокий минимум, он отображает на соответствующем баре зеленую стрелку с подписью "HL". Кроме того, он создает уведомление и записывает бычий разворот, указывая на потенциальное возвращение интереса со стороны покупателей.

Бычий разворот


Структура советника

При начале работы с любым файлом MQL5 мы добавляем директивы #property, чтобы задать базовые параметры. Здесь мы включаем строгую компиляцию, чтобы компилятор отлавливал небезопасные приведения типов или устаревшие вызовы. Также указываем авторские права, ссылку и версию — кто написал код, где найти дополнительную информацию и какая это версия. Эти строки не влияют на логику — они служат для идентификации файла.

#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

Далее добавляем параметры, которые пользователи смогут настраивать. Первые три входных параметра определяют, как именно мы будем находить развороты с помощью ATR.

  • InpAtrPeriod — задает количество баров, используемых для расчета ATR. Короткий период (например, 7) реагирует быстро, но может показывать больше шума. Более длинный период (скажем, 21) сглаживает всплески, но может быть немного запаздывающим.
  • InpAtrMultiplier — преобразует диапазон ATR в минимальную ширину сдвига. При значении 1.0 требуется движение величиной в один ATR, чтобы зафиксировать разворот. Увеличение до 1.5 или 2.0 делает фильтр более избирательным.
  • InpAtrLoosenFactor — масштабирует эту ширину в диапазоне от 0 до 1. Коэффициент 0.5 уменьшает требование вдвое, благодаря чему развороты появляются раньше, что может быть полезно при низкой волатильности.

Затем мы настраиваем компоновку графика.

  • InpAutoShift — резервирует пустые бары справа при появлении новых баров.
  • InpShiftBars — задает количество оставляемых пустых баров (по умолчанию пять).

Такое смещение предотвращает наложение стрелок, подписей и панели статистики на ценовой график.

Далее у нас два способа оповещений:

  • InpEnableSound — советник будет воспроизводить WAV-файл при каждом развороте.
  • InpSoundFile — название звукового файла в папке Sounds терминала MetaTrader 5.
  • InpEnablePush — отправляет push-уведомление в мобильное приложение MetaTrader 5.

Это позволяет настроить звуковое оповещение на компьютере, push-уведомление на телефоне или использовать оба варианта.

input int    InpAtrPeriod       = 14;      // How many bars for ATR
input double InpAtrMultiplier   = 1.0;     // Scale ATR into bar‑depth
input double InpAtrLoosenFactor = 0.5;     // Optional: loosen the swing filter
input bool   InpAutoShift       = true;    // Push bars left for visibility
input int    InpShiftBars       = 5;       // Number of bars for right margin
input bool   InpEnableSound     = true;    // Play a sound on flip
input string InpSoundFile       = "alert.wav";
input bool   InpEnablePush      = false;   // Send push notifications

При запуске советника функция OnInit сначала проверяет InpAutoShift и, если сдвиг включен, вызывает ChartSetInteger с параметрами CHART_SHIFT и InpShiftBars, чтобы сдвинуть новые бары влево и зарезервировать чистое пространство для аннотаций. Затем она запрашивает хэндл встроенного индикатора ATR через функцию iATR, сохраняет его в atrHandle и немедленно завершает инициализацию с INIT_FAILED, если хэндл некорректен. После этого создается угловая метка (OBJ_LABEL с именем panelName), которая закрепляется в левом верхнем углу графика, смещается на 10 пикселей по горизонтали и вертикали, получает размер шрифта 10 и желтый цвет, после чего OnInit возвращает INIT_SUCCEEDED. Это подтверждает что доступ к данным ATR и панель статистики готовы к работе в OnTick.

int OnInit()
{
   if(InpAutoShift)
      ChartSetInteger(0, CHART_SHIFT, InpShiftBars);

   atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE)
      return INIT_FAILED;

   ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, panelName, OBJPROP_CORNER,    CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE,  10);
   ObjectSetInteger(0, panelName, OBJPROP_COLOR,     clrYellow);

   return INIT_SUCCEEDED;
}

При удалении советника или закрытии MetaTrader 5 функция OnDeinit удаляет все созданные стрелки и текстовые объекты, удаляет статистическую метку и освобождает хэндл ATR. Это предотвращает мусор графика и освобождает ресурсы.

void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, -1, OBJ_ARROW);
   ObjectsDeleteAll(0, -1, OBJ_TEXT);
   ObjectDelete(0, panelName);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
}

В обработчике OnTick? чтобы не выполнять логику поиска пивотов на каждом новом тике, вводим переменную типа datetime lastBar. Мы получаем thisBar через iTime(...,1), что возвращает время открытия последнего закрытого бара, и сравниваем его с lastBar. Если время не изменилось, значит мы все еще внутри той же свечи. Если же thisBar изменился, обновляем lastBar и продолжаем выполнение.

Затем мы вызываем CopyBuffer для хэндла индикатора ATR, извлекая только последнее значение ATR. Если произошла ошибка, выходим, чтобы не работать с некорректными данными. Когда у нас есть валидный ATR, вычисляем "глубину" свинга. Для этого переводим единицы ATR в количество баров. Делим ATR на минимальный ценовой шаг (SYMBOL_POINT) и умножаем результат на InpAtrMultiplier и InpAtrLoosenFactor, после чего принудительно задаем минимум в один бар с помощью MathMax. В результате получается динамическая глубина, которая увеличивается при всплесках волатильности (требуя более крупных движений для фиксации разворотов) и уменьшается на спокойном рынке (разрешая более узкие свинги), прежде чем передать управление функциям поиска разворотов.

void OnTick()
{
   static datetime lastBar=0;
   datetime thisBar = iTime(_Symbol,_Period,1);
   if(thisBar == lastBar) return;
   lastBar = thisBar;

   double atrBuf[];
   if(CopyBuffer(atrHandle, 0, 1, 1, atrBuf) <= 0) return;
   double atr = atrBuf[0];

   int depth = MathMax(1,
      int(atr / SymbolInfoDouble(_Symbol, SYMBOL_POINT)
          * InpAtrMultiplier * InpAtrLoosenFactor));
   // … pivot checks follow …
}

После определения глубины запускаем два простых цикла для проверки разворотов. IsSwingHigh(1, depth) проверяет, что ни один бар в пределах depth баров слева и справа не превышает кандидатный максимум. IsSwingLow выполняет аналогичную проверку для минимумов. Когда мы находим новый максимум или минимум, сдвигаем lastHigh в prevHigh (аналогично для минимумов) и сохраняем временную метку. Отслеживание двух разворотов (предыдущего и текущего) позволяет затем сравнить их между собой.

bool newHigh = IsSwingHigh(1, depth);
bool newLow  = IsSwingLow (1, depth);
double h = iHigh(_Symbol,_Period,1), l = iLow(_Symbol,_Period,1);

if(newHigh)
{
   prevHigh     = lastHigh;
   prevHighTime = lastHighTime;
   lastHigh     = h;
   lastHighTime = thisBar;
}
if(newLow)
{
   prevLow      = lastLow;
   prevLowTime  = lastLowTime;
   lastLow      = l;
   lastLowTime  = thisBar;
}

После фиксации разворотов обновляем structState, отражая тренд: более высокий максимум устанавливает состояние 1 (возможен будущий медвежий разворот), более низкий минимум — состояние 2 (возможен будущий бычий разворот). Затем проверяем сам факт разворота: в состоянии 1, если новый максимум ниже предыдущего, это медвежий разворот; в состоянии 2, если новый минимум выше предыдущего, это бычий разворот. При срабатывании разворота вызываются функции отрисовки и уведомлений, а счетчики обновляются.

// Update bias
if(newHigh && prevHigh>0 && lastHigh > prevHigh) structState = 1;
if(newLow  && prevLow>0  && lastLow  < prevLow) structState = 2;

// Bearish flip
if(newHigh && structState==1 && lastHigh < prevHigh)
{
   PlotArrow(...);  PlotLabel(...);  Notify(...);
   if(countBear>0) sumBearInterval += (lastHighTime - prevLowTime)/60.0;
   countBear++;
}

// Bullish flip
if(newLow && structState==2 && lastLow > prevLow)
{
   PlotArrow(...);  PlotLabel(...);  Notify(...);
   if(countBull>0) sumBullInterval += (lastLowTime - prevHighTime)/60.0;
   countBull++;
}

Вся отрисовка на графике реализована в двух вспомогательных функциях — PlotArrow и PlotLabel. Внутри каждой функции мы сначала вызываем ObjectFind(0, name), который ищет существующий объект по его уникальному имени. Эта операция выполняется за время O(n) относительно количества объектов, но на современных компьютерах это достаточно быстро для периодических проверок на каждом баре. Если объект не найден (ObjectFind возвращает –1), создаем его ровно один раз с помощью ObjectCreate. При этом выбираем соответствующий тип объекта (стрелка для PlotArrow, текстовая метка для PlotLabel).

Затем настраиваем свойства: для стрелок задаем OBJPROP_ARROWCODE для выбора нужного символа (например, код Wingdings 234 для красной стрелки вниз) и OBJPROP_COLOR для определения цвета. Для меток устанавливаем OBJPROP_TEXT с подписью (например, LH или HL), а также смещения и размер шрифта. Таким образом мы не вызываем лишний раз функцию ObjectCreate, благодаря чему не страдает производительность и предотвращаются утечки памяти, которые возникли бы при накоплении сотен или тысяч одинаковых объектов. Также каждая метка разворота имеет собственный идентификатор, благодаря чему можно будет изменять, например, OBJPROP_ZORDER (приоритет отрисовки) или удалять объект по имени, не затрагивая другие элементы графика.

void PlotArrow(string name, datetime t, double price, int code, color c)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_ARROW, 0, t, price);
      ObjectSetInteger(0, name, OBJPROP_ARROWCODE, code);
      ObjectSetInteger(0, name, OBJPROP_COLOR, c);
   }
}
// PlotLabel is identical, but creates OBJ_TEXT and sets OBJPROP_TEXT.

После каждого бара мы пересобираем метку panelName, в которой отображаем:

  • текущую глубину пивота;
  • общее количество бычьих и медвежьих разворотов;
  • среднее время (в минутах) между разворотами (после накопления как минимум двух).

Это позволяет быстро оценить, как часто происходят пробои структуры при выбранных настройках ATR.

string txt = StringFormat("Depth: %d\nBull Flips: %d\nBear Flips: %d",
                          depth, countBull, countBear);
if(countBull>1)
   txt += "\nAvg HL Int: " + DoubleToString(sumBullInterval/(countBull-1),1) + "m";
if(countBear>1)
   txt += "\nAvg LH Int: " + DoubleToString(sumBearInterval/(countBear-1),1) + "m";

ObjectSetString(0, panelName, OBJPROP_TEXT, txt);

Наконец, функция Notify(msg) объединяет все методы оповещений в одном месте. Мы всегда вызываем Alert(msg) для всплывающего окна MetaTrader 5, а затем, в зависимости от настроек, воспроизводим звук (PlaySound) или отправляем push-уведомление (SendNotification). Функции уведомления собраны в одном месте, и в будущем можно будет легко добавить уведомления по e-mail или webhook-интеграции.

void Notify(string msg)
{
   Alert(msg);
   if(InpEnableSound) PlaySound(InpSoundFile);
   if(InpEnablePush)  SendNotification(msg);
}


Исходный код

//+------------------------------------------------------------------+
//|                                 Market Structure Flip Detector EA|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

//--- user inputs
input int    InpAtrPeriod       = 14;      // ATR lookback
input double InpAtrMultiplier   = 1.0;     // ATR to swing depth factor (lower = looser)
input double InpAtrLoosenFactor = 0.5;     // Loosen factor for ATR confirmation (0-1)
input bool   InpAutoShift       = true;    // Auto-enable chart right shift
input int    InpShiftBars       = 5;       // Bars for right margin
input bool   InpEnableSound     = true;
input string InpSoundFile       = "alert.wav";
input bool   InpEnablePush      = false;

//--- global vars
string   panelName = "FlipPanel";
int      atrHandle;
int      structState = 0;
double   prevHigh=0, lastHigh=0;
datetime prevHighTime=0, lastHighTime=0;
double   prevLow=0, lastLow=0;
datetime prevLowTime=0, lastLowTime=0;
int      countBull=0, countBear=0;
double   sumBullInterval=0, sumBearInterval=0;

//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpAutoShift)
      ChartSetInteger(0, CHART_SHIFT, InpShiftBars);

   atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE)
      return(INIT_FAILED);

   ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, panelName, OBJPROP_CORNER,    CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE,  10);
   ObjectSetInteger(0, panelName, OBJPROP_COLOR,     clrYellow);
   ObjectSetInteger(0, panelName, OBJPROP_BACK,      false);
   ObjectSetInteger(0, panelName, OBJPROP_ZORDER,    1);

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, -1, OBJ_ARROW);
   ObjectsDeleteAll(0, -1, OBJ_TEXT);
   ObjectDelete(0, panelName);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
  }

//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime lastBar=0;
   datetime thisBar = iTime(_Symbol,_Period,1);
   if(thisBar==lastBar)
      return;
   lastBar=thisBar;

   double atrBuf[];
   if(CopyBuffer(atrHandle,0,1,1,atrBuf)<=0)
      return;
   double atr = atrBuf[0];

// loosen ATR confirmation by InpAtrLoosenFactor (0-1)
   double rawDepth = atr/SymbolInfoDouble(_Symbol,SYMBOL_POINT)*InpAtrMultiplier;
   int depth = MathMax(1, (int)(rawDepth * InpAtrLoosenFactor));

   bool newHigh=false,newLow=false;
   double h=iHigh(_Symbol,_Period,1), l=iLow(_Symbol,_Period,1);

   if(IsSwingHigh(1,depth))
     {
      prevHigh = lastHigh;
      prevHighTime = lastHighTime;
      lastHigh = h;
      lastHighTime = thisBar;
      newHigh = true;
     }
   if(IsSwingLow(1,depth))
     {
      prevLow = lastLow;
      prevLowTime = lastLowTime;
      lastLow = l;
      lastLowTime = thisBar;
      newLow = true;
     }

   double off = SymbolInfoDouble(_Symbol,SYMBOL_POINT)*10;

// Bearish Flip: Lower High after a Higher High
   if(newHigh && structState==1 && prevHigh>0 && lastHigh<prevHigh)
     {
      // signal arrow and label at current LH
      PlotArrow("Bear_"+IntegerToString((int)lastHighTime), lastHighTime, lastHigh, 234, clrRed);
      PlotLabel("LH_"+IntegerToString((int)lastHighTime), lastHighTime, lastHigh+off, "LH", clrRed);
      // label the previous LH used for comparison
      PlotLabel("PrevLH_"+IntegerToString((int)prevHighTime), prevHighTime, prevHigh+off, "LH_prev", clrRed);
      Notify("Bearish Flip (LH) at "+TimeToString(lastHighTime,TIME_DATE|TIME_MINUTES));
      if(countBear>0)
         sumBearInterval += (lastHighTime-prevLowTime)/60.0;
      countBear++;
     }

// Bullish Flip: Higher Low after a Lower Low
   if(newLow && structState==2 && prevLow>0 && lastLow>prevLow)
     {
      // signal arrow and label at current HL
      PlotArrow("Bull_"+IntegerToString((int)lastLowTime), lastLowTime, lastLow, 233, clrLime);
      PlotLabel("HL_"+IntegerToString((int)lastLowTime), lastLowTime, lastLow-off, "HL", clrLime);
      // label the previous HL used for comparison
      PlotLabel("PrevHL_"+IntegerToString((int)prevLowTime), prevLowTime, prevLow-off, "HL_prev", clrLime);
      Notify("Bullish Flip (HL) at "+TimeToString(lastLowTime,TIME_DATE|TIME_MINUTES));
      if(countBull>0)
         sumBullInterval += (lastLowTime-prevHighTime)/60.0;
      countBull++;
     }

// update structure state
   if(newHigh && prevHigh>0 && lastHigh>prevHigh)
      structState = 1;
   if(newLow  && prevLow>0  && lastLow <prevLow)
      structState = 2;

// update panel stats
   string txt = "Depth: "+IntegerToString(depth)+"\n";
   txt += "Bull Flips: "+IntegerToString(countBull)+"\n";
   txt += "Bear Flips: "+IntegerToString(countBear);
   if(countBull>1)
      txt += "\nAvg HL Int: "+DoubleToString(sumBullInterval/(countBull-1),1)+"m";
   if(countBear>1)
      txt += "\nAvg LH Int: "+DoubleToString(sumBearInterval/(countBear-1),1)+"m";
   ObjectSetString(0, panelName, OBJPROP_TEXT, txt);
  }

//+------------------------------------------------------------------+
bool IsSwingHigh(int shift,int depth)
  {
   double p = iHigh(_Symbol,_Period,shift);
   for(int i=shift-depth; i<=shift+depth; i++)
      if(i>=0 && iHigh(_Symbol,_Period,i) > p)
         return false;
   return true;
  }

//+------------------------------------------------------------------+
bool IsSwingLow(int shift,int depth)
  {
   double p = iLow(_Symbol,_Period,shift);
   for(int i=shift-depth; i<=shift+depth; i++)
      if(i>=0 && iLow(_Symbol,_Period,i) < p)
         return false;
   return true;
  }

//+------------------------------------------------------------------+
void PlotArrow(string nm,datetime t,double price,int code,color c)
  {
   if(ObjectFind(0,nm) < 0)
     {
      ObjectCreate(0, nm, OBJ_ARROW, 0, t, price);
      ObjectSetInteger(0, nm, OBJPROP_ARROWCODE, code);
      ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH, 2);
     }
  }

//+------------------------------------------------------------------+
void PlotLabel(string nm,datetime t,double price,string txt,color c)
  {
   if(ObjectFind(0,nm) < 0)
     {
      ObjectCreate(0, nm, OBJ_TEXT, 0, t, price);
      ObjectSetString(0, nm, OBJPROP_TEXT, txt);
      ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
      ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, 10);
     }
  }

//+------------------------------------------------------------------+
void Notify(string msg)
  {
   Alert(msg);
   if(InpEnableSound)
      PlaySound(InpSoundFile);
   if(InpEnablePush)
      SendNotification(msg);
  }
//+------------------------------------------------------------------+


Результаты

Ниже представлены результаты тестировании в условиях реального рынка и на истории.

Реальный рынок

Результаты на реальном рынке

На графике выше советник сначала нашел свинг-максимум, добавил подпись "LH_prev", то есть найдены два последовательных более высоких максимума и сформирована восходящая структура. Через несколько баров найден очередной свинг-максимум, который не превосходит предыдущий пик — этот более низкий максимум в рамках восходящего тренда. Поэтому советник рисует красную стрелку и метку "LH" на соответствующем баре. Этот сигнал медвежьего разворота указывает на ослабление бычьего импульса и предупреждает о возможном начале нисходящего движения.

GIF работы на реальном рынке

Ниже я привел GIF-анимацию с демонстрацией работы советника на EURUSD. На закрытии минутных свечей советник отслеживает последовательные минимумы в поисках свинг-минимума, превышающего предыдущую впадину. Когда появляется более высокий минимум, советник рисует зеленую стрелку HL, отмечая бычий разворот. В это же время обновляется панель — здесь она показывает 12 бычьих разворотов, 1 медвежий разворот и средний интервал HL в 108,0 минуты, — отражая обновленную статистику. Н апримере показан переход от нисходящей структуры к потенциальному восходящему движению.

Результаты на реальном рынке

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

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

Таймфрейм 5 минут

Тип сигнала Всего сигналов Положительные сигналы Win Rate
Sell 56 39 70%
Buy 53 44 83%

Таймфрейм 15 минут

Тип сигнала Всего сигналов Положительные сигналы Win Rate
Sell 7 5 71%
Buy 14 9 64%

Видим, что детектор разворота рыночной структуры стабильно генерирует прибыльные сигналы, особенно на младших таймфреймах. Сетапы на продажу демонстрируют уровень попадания 70% и выше, что подчеркивает эффективность инструмента. Это значительный шаг вперед в автоматизации анализа работы по ценовому действию (price action) и приближает нас к полностью системному торговому инструментарию.



Заключение

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

Дата Название инструмента  Описание Версия  Обновления  Примечания
01/10/24 Chart Projector Скрипт для наложения эффекта призрака на движение цены за предыдущий день. 1.0 Первоначальная версия Инструмент номер 1
18/11/24 Analytical Comment Предоставляет информацию за предыдущий день в табличном формате, а также прогнозирует будущее направление рынка. 1.0 Первоначальная версия Инструмент номер 2
27/11/24 Analytics Master Регулярное обновление рыночных показателей каждые два часа  1.01 Вторая версия Инструмент номер 3
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  Панель с кнопкfvb для анализа  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 
17/02/25  Parabolic Stop and Reverse (PSAR)  Автоматизация стратегии PSAR 1.0 Первоначальная версия  Инструмент номер 14
20/02/25  Скрипт Quarters Drawer  Нанесение уровней четвертей на график  1.0  Первоначальная версия  Инструмент номер 15 
27/02/25  Intrusion Detector Обнаружение и оповещение о достижении ценой уровней четвертей 1.0   Первоначальная версия Инструмент номер 16 
27/02/25  TrendLoom Tool  Панель мультитаймфреймового анализа 1.0 Первоначальная версия Инструмент номер 17
11/03/25  Quarters Board  Панель с кнопками для включения или отключения квартальных уровней  1.0  Первоначальная версия Инструмент номер 18
26/03/25  ZigZag Analyzer  Построение линий тренда с помощью индикатора ZigZag  1.0  Первоначальная версия  Инструмент номер 19 
10/04/25  Correlation Pathfinder Построение графиков корреляции валютных курсов с использованием библиотек Python. 1.0 Первоначальная версия  Инструмент номер 20 
 23/04/25  Market Structure Flip Detector Tool Поиск разворотов рыночной структуры 1.0  Первоначальная версия  Tool number 21

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

Прикрепленные файлы |
Flip_Detector.mq5 (14.5 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Alexander P.
Alexander P. | 7 мая 2025 в 11:08
bool IsSwingHigh( int shift, int depth)
  {
   double p = iHigh ( _Symbol , _Period ,shift);
   for ( int i= shift-depth; i<=shift+depth; i++)
       if (i>= 0 && iHigh ( _Symbol , _Period ,i) > p)
         return false ;
   return true ;
  }

Здравствуйте, я не понимаю, почему вы пишете int i=shift-depth, разве нельзя было просто использовать int i=0 ?

Не могли бы вы объяснить это? Спасибо.

Конвейеры обработки данных (пайплайны) в MQL5 Конвейеры обработки данных (пайплайны) в MQL5
В этой статье рассмотрим ключевой этап подготовки данных для машинного обучения, который быстро приобретает все большее значение. Конвейеры предварительной обработки данных. По сути, это упрощенная последовательность этапов преобразования данных, на которых происходит подготовка исходных данных перед их передачей в модель. Какой бы неинтересной она ни показалась непосвященным на первый взгляд, такая «стандартизация данных» не только экономит время обучения и затраты на выполнение, но и в значительной степени способствует более качественному обобщению. В этой статье сосредоточимся на некоторых функциях предварительной обработки SCIKIT-LEARN и, хотя мы не будем использовать Мастер MQL5, вернемся к нему в последующих статьях.
Нейросети в трейдинге: Гибридные модели прогнозирования с управляемой смесью распределений (Окончание) Нейросети в трейдинге: Гибридные модели прогнозирования с управляемой смесью распределений (Окончание)
В статье представлена адаптированная реализация фреймворка Lattice для анализа и прогнозирования рыночной динамики на реальных данных. Показано, как интеграция низко- и высокочастотных модулей, архетипов и адаптивного суммирования обеспечивает устойчивость модели и точность прогнозов.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Оптимизация Роем Жуков — Beetle Swarm Optimization (BSO) Оптимизация Роем Жуков — Beetle Swarm Optimization (BSO)
Рассматриваем гибрид BAS+PSO (BSO), где BAS добавляет локальный сигнал направления, а PSO обеспечивает обмен лучшими решениями в рое. Приведены математическая модель, псевдокод, реализация класса на MQL5 и результаты тестирования в типовом стенде. Материал позволяет воспроизвести алгоритм, настроить параметры и понять, как трёхкратные оценки за итерацию отражаются на эффективности.