English
preview
Адаптивный индикатор Malaysian Engulfing (Часть 2): Оптимизированный диапазон баров ретеста

Адаптивный индикатор Malaysian Engulfing (Часть 2): Оптимизированный диапазон баров ретеста

MetaTrader 5 — Примеры |
58 1
Chukwubuikem Okeke
Chukwubuikem Okeke

Введение

У вас уже есть работающий детектор Malaysian Engulfing и процедура подтверждения ретеста, но надёжному масштабированию мешает одна практическая проблема: диапазон баров ретеста нельзя «задать один раз» для всех рынков и таймфреймов. На одном символе фиксированный диапазон захватывает допустимые откаты; на другом — пропускает сетапы или допускает шумовые ложные подтверждения. По мере изменения волатильности, импульса и структурного ритма ручная настройка превращается в процесс проб и ошибок, а эффективность стратегии становится нестабильной.

В этой статье добавляется адаптивный слой на основе данных, который объективно выбирает диапазон ретеста с учётом текущего контекста. Вместо того чтобы гадать, мы оцениваем исторические сетапы с помощью MFE (максимальное благоприятное движение цены) и MAE (максимальное неблагоприятное движение цены), измеряемых по единому правилу входа, оцениваем кандидатные расстояния ретеста по формуле avgMFE − avgMAE с помощью ограниченного полного перебора по одному параметру и выбираем оптимальные значения для каждого символа и таймфрейма. На выходе получаем воспроизводимый результат: optimalBullishRetestRange и optimalBearishRetestRange, а также сводку выполнения, которые можно валидировать и применять автоматически.

Адаптивный слой

К этому моменту индикатор уже успешно выявляет паттерны Malaysian Engulfing и отслеживает их поведение при откате с использованием фиксированного диапазона ретеста. Хотя этот подход работает в контролируемых условиях, он быстро теряет эффективность на реальных рынках, где волатильность, импульс и структурный ритм различаются между символами и таймфреймами. Статический параметр становится узким местом: он либо слишком узкий, чтобы захватывать допустимые ретесты, либо слишком широкий, что вносит шум и ложные подтверждения.

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

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

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

Каждый отработанный сетап Malaysian Engulfing можно разложить на согласованный набор измеримых характеристик, которые составляют основу нашего анализа MFE/MAE и последующей оптимизации:

Структура

Рис. 1. Адаптивный слой — структура

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

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

Реализация в MQL5

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

Шаг 1: директивы препроцессора

На этом этапе определяются метаданные и константы времени компиляции — такие как версия программы (VERSION "2.00"), идентификаторы имён, свойства и подключаемые библиотеки, — чтобы индикатор был корректно настроен, версионирован и пригоден для повторного использования ещё до компиляции.

//+------------------------------------------------------------------+
//|                      Malaysian Engulfing - Self Optimizing.mq5   |
//|                                             © 2026, ChukwuBuikem |
//|                             https://www.mql5.com/en/users/bikeen |
//+------------------------------------------------------------------+
#property copyright "© 2026, ChukwuBuikem"
#property link      "https://www.mql5.com/en/users/bikeen"

#define VERSION "2.00"

#property version   VERSION
#property indicator_chart_window
#property indicator_plots 0

#include <ChartObjects\ChartObjectsShapes.mqh>

#define PROG_NAME "Malaysian Engulfing - Self Optimizing"
#define ZONE_BULL PROG_NAME + "BullishEngulfing"
#define ZONE_BEAR PROG_NAME + "BearishEngulfing"

Шаг 2: Структуры данных

Структура `st_Engulfer`, которая удобно объединяет всю ключевую информацию о сетапе «поглощения», была расширена и теперь включает показатели MFE, MAE и диапазон баров ретеста (retestBars), как показано на рис. 1. При этом конструктор гарантирует, что каждый новый экземпляр инициализируется безопасными значениями по умолчанию, обеспечивая согласованное отслеживание.

//--- Data Structure
struct st_Engulfer
  {
   ENUM_SYSTEM_STATE  state;
   datetime          time, retestTime;
   double            high, low;
   double   mfe,// Max favorable excursion
            mae;// Max adverse excursion
   int      retestBars;// Bars until retest

   //--- Constructor
                     st_Engulfer(): state(SEARCH_STATE), time(0), retestTime(0),
                                    high(EMPTY_VALUE), low(EMPTY_VALUE), mfe(EMPTY_VALUE),
                                    mae(EMPTY_VALUE), retestBars(INT_MIN) {}
  };

Шаг 3: Входные параметры (настраиваемые параметры)

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

//--- Configurable parameters
input group "+== Optimization Settings ==+"
input datetime startDate =  D'2025.06.01';    // Analysis start date
input datetime endDate =  D'2025.07.18';      // Analysis end date
input int barsRetestRange = 50;               //Retest search horizon (bars)
input int maxLookahead = 20;                  //Forward window (bars) for MFE/MAE
input group "+== Runtime settings ==+"
input color bullishZoneColor = clrGreen;      //Bullish retest zone color
input color bearishZoneColor = clrRed;        //Bearish retest zone color
input int wickThreshold = 35;                 //Minimum wick ratio (%)

Шаг 4: Глобальные переменные

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

//--- Global variables
int start = -1;
CChartObjectRectangle rect;
st_Engulfer bullishEngulfer, bearishEngulfer;
MqlRates rates[];
st_Engulfer bullishSetups[], bearishSetups[];
int bullishRetestIndex = -1, bearishRetestIndex = -1;
int optimalBullishRetestRange = -1, optimalBearishRetestRange = -1;
datetime dateStart = 0, dateEnd = 0;

Шаг 5: Вспомогательные функции

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

  • Валидация ретеста

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

//+------------------------------------------------------------------+
//|              Adaptive Layer                                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|                Bullish retest validation                         |
//+------------------------------------------------------------------+
int getBullishRetestIndex(const int engIndex, const MqlRates &prices[],
                          const double engHigh, const double engLow, int &engRetest)
  {
//---
   int barIndex = -1;
   for(int w = 1; w <= barsRetestRange && (barIndex = engIndex - w) >= 0; w++)
     {
      //--- Check break
      if(prices[barIndex].close <= engLow)
         return -1;
      //--- Validate pullback
      if((prices[barIndex].low <= engHigh && prices[barIndex].close > engHigh)
         || (prices[barIndex].low <= engLow && prices[barIndex].close > engLow))
        {
         //--- Check lower wick ratio
         double candleRange = prices[barIndex].high - prices[barIndex].low;
         double ratio = (MathMin(prices[barIndex].open, prices[barIndex].close) - prices[barIndex].low) / candleRange;
         ratio = (NormalizeDouble(ratio, 2) * 100);
         //--- Utilize run-time wick parameter
         if(ratio >= wickThreshold)
           {
            engRetest = engIndex - barIndex;
            return barIndex;
           }
         return -1;
        }
     }
   return -1;
  }
//+------------------------------------------------------------------+
//|                Bearish retest validation                         |
//+------------------------------------------------------------------+
int getBearishRetestIndex(const int engIndex, const MqlRates &prices[],
                          const double engHigh, const double engLow, int &engRetest)
  {
//---
   int barIndex = -1;
   for(int w = 1; w <= barsRetestRange && (barIndex = engIndex - w) >= 0; w++)
     {
      //--- Check break
      if(prices[barIndex].close >= engHigh)
         return -1;
      //--- Validate pullback
      if((prices[barIndex].high >= engHigh && prices[barIndex].close < engHigh)
         || (prices[barIndex].high >= engLow && prices[barIndex].close < engLow))
        {
         //--- Check upper wick ratio
         double candleRange = prices[barIndex].high - prices[barIndex].low;
         double ratio = (prices[barIndex].high - MathMax(prices[barIndex].open, prices[barIndex].close)) / candleRange;
         ratio = (NormalizeDouble(ratio, 2) * 100);
         //--- Utilize run-time wick parameter
         if(ratio >= wickThreshold)
           {
            engRetest = engIndex - barIndex;
            return barIndex;
           }
         return -1;
        }
     }
   return -1;
  }
  • Расчёт MFE/MAE

Эта функция оценивает, насколько далеко цена прошла в благоприятном направлении (MFE) и в неблагоприятном для сетапа направлении (MAE) после ретеста: она сканирует заданное количество баров вперёд от точки входа, обновляет максимальное благоприятное движение цены и максимальное неблагоприятное движение цены, а затем преобразует результаты в пункты для стандартизированного сравнения сетапов.

//+------------------------------------------------------------------+
//|                   Compute MFE and MAE                            |
//+------------------------------------------------------------------+
void computeExcursions(const int retestIndex, const MqlRates &prices[],
                       const bool buySetup, st_Engulfer &destEng)
  {
//---
   double entry = NormalizeDouble(prices[retestIndex - 1].open, _Digits);// Entry price
   double mfe = 0, mae = 0;
   double favorableMove = 0, adverseMove = 0;
   int index = -1;

   for(int w = 1; w <= maxLookahead && (index = retestIndex - w) >= 0; w++)
     {
      if(buySetup)
        {
         favorableMove = NormalizeDouble(prices[index].high - entry, _Digits);
         adverseMove = NormalizeDouble(entry - prices[index].low, _Digits);

         if(favorableMove > mfe)
            mfe = favorableMove;
         if(adverseMove > mae)
            mae = adverseMove;
        }
      else
        {
         favorableMove = entry - prices[index].low;
         adverseMove = prices[index].high - entry;

         if(favorableMove > mfe)
            mfe = favorableMove;
         if(adverseMove > mae)
            mae = adverseMove;
        }

     }
//--- Convert to points
   destEng.mae = NormalizeDouble((mae / _Point), _Digits);
   destEng.mfe = NormalizeDouble((mfe / _Point), _Digits);
  }

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

  • Основной механизм оптимизации

Здесь механизм выполняет полный перебор всех возможных диапазонов ретеста, группируя исторические сетапы по значению retestBars, вычисляя средние MFE и MAE для каждой группы и оценивая их по разности «вознаграждение–риск» (avgMFE - avgMAE), чтобы выявить наиболее благоприятную со статистической точки зрения конфигурацию.

//+------------------------------------------------------------------+
//|                 MFE and MAE analysis engine                      |
//+------------------------------------------------------------------+
int getOptimalRetestRange(st_Engulfer &eng[])
  {
//--- Best results tracking variable
   double bestScore = -DBL_MAX;
   int bestRange = 1;//--- Fallback

//--- Define variables / accumulators
   double totalMAE = 0, totalMFE = 0;
   double avgMAE = 0, avgMFE = 0;
   double score = 0;
   int count = 0;

//--- Try every possible retest bars value
   for(int r = 1; r <= barsRetestRange; r++)
     {
      //--- Initialize accumulators
      totalMFE = 0;
      totalMAE = 0;
      count = 0;
      //--- Scan dataset
      for(int w = 0; w < ArraySize(eng); w++)
        {
         //--- Group data
         if(eng[w].retestBars == r)
           {
            //--- Accumulate values for this group
            totalMFE += eng[w].mfe;
            totalMAE += eng[w].mae;
            count++;
           }
        }
      //--- Handle empty groups: Prevents division by zero
      if(count < 1)
         continue;
      //--- Compute averages
      avgMFE = totalMFE / count;
      avgMAE = totalMAE / count;

      //--- Edge: Compute score (exhaustive search))
      score = avgMFE - avgMAE;
      //--- Select best candidate
      if(score > bestScore)
        {
         bestScore = score;
         bestRange = r;
        }
     }
//--- Return best result
   return bestRange;
  }

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

  • Формирование адаптивной модели
Данная процедура выполняет проход по историческим данным, чтобы выявить сетапы бычьего и медвежьего поглощения с подтвержденными ретестами. Она агрегирует показатели MFE/MAE (с помощью функции computeExcursions()), а затем выполняет оптимизацию полным перебором для поиска оптимальных параметров ретеста для сетапов на покупку и продажу.
//+------------------------------------------------------------------+
//|                 Core adaptive layer engine                       |
//+------------------------------------------------------------------+
void buildAdaptiveModel(const MqlRates &prices[], st_Engulfer &buySetup[], st_Engulfer &sellSetup[],
                        int &optimalBuyParameter, int &optimalSellParameter)
  {
//---
   int index = -1;
   for(int w = ArraySize(prices) - (barsRetestRange + 2); (index = w + barsRetestRange) >= 0 && w >= 0 ; w--)
     {
      //--- Detect a perfect bullish engulfing pattern with valid retest
      if(prices[index + 1].close < prices[index + 1].open && prices[index].close > prices[index].open
         && prices[index].open <= prices[index + 1].close && prices[index].close > prices[index + 1].high
         && (bullishRetestIndex = getBullishRetestIndex(index, prices, prices[index + 1].high, prices[index + 1].low,
                                  bullishEngulfer.retestBars)) > -1)
        {
         //--- Resize array and append values
         if(ArrayResize(buySetup, ArraySize(buySetup) + 1))
           {
            computeExcursions(bullishRetestIndex, prices, true, bullishEngulfer);
            bullishEngulfer.time = prices[index].time;
            bullishEngulfer.high = prices[index + 1].high;
            bullishEngulfer.low = prices[index + 1].low;
            bullishEngulfer.retestTime = prices[bullishRetestIndex].time;
            buySetup[ArraySize(buySetup) - 1] = bullishEngulfer;
           }
        }
      //--- Detect a perfect bearish engulfing pattern with valid retest
      if(prices[index + 1].close > prices[index + 1].open && prices[index].close < prices[index].open
         && prices[index].open >= prices[index + 1].close && prices[index].close < prices[index + 1].low
         && (bearishRetestIndex = getBearishRetestIndex(index, prices, prices[index + 1].high, prices[index + 1].low,
                                  bearishEngulfer.retestBars)) > -1)
        {
         //--- Resize array and append values
         if(ArrayResize(sellSetup, ArraySize(sellSetup) + 1))
           {
            computeExcursions(bearishRetestIndex, prices, false, bearishEngulfer);
            bearishEngulfer.time = prices[index].time;
            bearishEngulfer.high = prices[index + 1].high;
            bearishEngulfer.low = prices[index + 1].low;
            bearishEngulfer.retestTime = prices[bearishRetestIndex].time;
            sellSetup[ArraySize(sellSetup) - 1] = bearishEngulfer;
           }
        }
     }
//--- Set optimal parameters
   optimalBuyParameter = getOptimalRetestRange(buySetup);
   optimalSellParameter = getOptimalRetestRange(sellSetup);
  }
  • Сводка выполнения

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

//+------------------------------------------------------------------+
//|                   Run-time summary display                       |
//+------------------------------------------------------------------+
void showRunSummary(const datetime sDate, const datetime eDate)
  {
   Print("==========================================");
   PrintFormat("[RUN] %s v%s", PROG_NAME, VERSION);

//--- Context
   PrintFormat("[DATA] Symbol: %s (%s) | Period: %s → %s",
               _Symbol,  EnumToString(_Period),
               TimeToString(sDate, TIME_DATE),
               TimeToString(eDate, TIME_DATE));

   PrintFormat("[RESULT] Optimal Retest Range (Buy):  %d bars", optimalBullishRetestRange);
   PrintFormat("[RESULT] Optimal Retest Range (Sell): %d bars", optimalBearishRetestRange);

   Print("==========================================");
  }
  • Ограничение диапазона дат

Пользовательские входные параметры необходимо проверять, чтобы избежать сбоев в работе. На этом этапе переданные входные параметры (startDate, endDate) сначала заносятся в глобальное состояние (dateStart, dateEnd). Затем диапазон дат проверяется и ограничивается по доступным историческим данным с помощью функции SeriesInfoInteger, что гарантирует, что окно анализа остается в допустимых пределах, а все внесенные корректировки фиксируются в журнале.

//+------------------------------------------------------------------+
//|                      Date input clamping                         |
//+------------------------------------------------------------------+
void clampDateRange()
  {
//---
   dateEnd = endDate;
   dateStart = startDate;

   datetime firstDate = (datetime)SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_FIRSTDATE);
   datetime lastDate = (datetime)SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_LASTBAR_DATE);
//--- Attempt to clamp dates
   if(dateStart < firstDate)
     {
      PrintFormat("[ADJUSTMENT]: Analysis start date -> %s (history start)",
                  TimeToString(firstDate));
      dateStart = firstDate;
     }
   if(dateEnd < firstDate)
     {
      MqlDateTime dt;
      TimeToStruct(firstDate, dt);
      dt.year += 1;
      dateEnd = StructToTime(dt);// Fall back: first date + one year
      PrintFormat("[ADJUSTMENT]: Analysis end date  -> %s ",
                  TimeToString(dateEnd));
     }

   if(dateEnd > lastDate)
     {
      PrintFormat("[ADJUSTMENT]: Analysis end date  -> %s (last bar)",
                  TimeToString(lastDate));
      dateEnd = lastDate;
     }
  }
//+------------------------------------------------------------------+

Шаг 6: Инициализация (OnInit)

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

//+------------------------------------------------------------------+
//|                 Core adaptive engine                             |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Validate and clamp inputs (if necessary)
   if(startDate <= 0 || endDate <= 0)
     {
      Print("Invalid date: zero or uninitialized");
      return INIT_PARAMETERS_INCORRECT;
     }
   if(startDate >= endDate)
     {
      PrintFormat("[INPUT ERROR]: Analysis start (%s) must be < end date (%s)",
                  TimeToString(startDate), TimeToString(endDate));
      return(INIT_PARAMETERS_INCORRECT);
     }
   clampDateRange();
   if(barsRetestRange <= 0)
     {
      PrintFormat("[INPUT ERROR]: Retest search horizon (%d) less than Zero(0) ", barsRetestRange);
      return(INIT_PARAMETERS_INCORRECT);
     }
   if((barsRetestRange * 3) >= (int)SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_BARS_COUNT))
     {
      PrintFormat("[INPUT ERROR]: Retest search horizon (%d) too large for available bars ",
                  barsRetestRange);
      return(INIT_PARAMETERS_INCORRECT);
     }
   while(!SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_SYNCHRONIZED) && !IsStopped())
     {
      Sleep(100);
     }
//--- Copy data and build adaptive model
   ArraySetAsSeries(rates, true);
   if(CopyRates(_Symbol, PERIOD_CURRENT, dateStart, dateEnd, rates) > 0)
     {
      buildAdaptiveModel(rates, bullishSetups, bearishSetups, optimalBullishRetestRange, optimalBearishRetestRange);
      ArrayPrint(bearishSetups);//--- Observation
      Print("Optimal bearish retest range: ", optimalBearishRetestRange);
     }
   return(INIT_SUCCEEDED);
  }

Наблюдение

Хотя реализация все еще находится в стадии активной разработки, индикатор можно установить на график в реальном времени для наблюдения. Используя функции ArrayPrint() и Print() в функции OnInit(), мы можем просмотреть вычисленные структуры данных после инициализации. При установке на график XAUUSD с настройками по умолчанию функция OnInit() выполняет валидацию, синхронизацию и адаптивное моделирование. Затем он выводит bearishSetups в журнал «Эксперты».

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

Адаптивный слой — наблюдение

Рис. 2. Адаптивный слой — наблюдение

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

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

//+------------------------------------------------------------------+
//|                 Core adaptive engine                             |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Validate and clamp inputs (if necessary)
  . . . . . . 
   if(CopyRates(_Symbol, PERIOD_CURRENT, dateStart, dateEnd, rates) > 0)
     {
      buildAdaptiveModel(rates, bullishSetups, bearishSetups, optimalBullishRetestRange, optimalBearishRetestRange);
      showRunSummary(dateStart, dateEnd);
     }
   return(INIT_SUCCEEDED);
  }

Шаг 7: Основной движок (OnCalculate())

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

//+------------------------------------------------------------------+
//|             Core iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//---
   if(prev_calculated != rates_total && prev_calculated > 0)
     {
      start = (prev_calculated == 0) ? 1 : prev_calculated - 1;
      for(int w = start; w < rates_total - 1 && !IsStopped(); w++)
        {
         //--- Search state
         if(bullishEngulfer.state == SEARCH_STATE)
           {
            if(isBullishEngulfing(w, open, high, low, close))
              {
               bullishEngulfer.state = FOUND_STATE;
               bullishEngulfer.time = time[w - 1];
               bullishEngulfer.high = high[w - 1];
               bullishEngulfer.low = low[w - 1];
               bullishEngulfer.retestTime = time[w] + (PeriodSeconds() * optimalBullishRetestRange);// Set retest time window
               return rates_total;
              }
           }
         if(bearishEngulfer.state == SEARCH_STATE)
           {
            if(isBearishEngulfing(w, open, high, low, close))
              {
               bearishEngulfer.state = FOUND_STATE;
               bearishEngulfer.time = time[w - 1];
               bearishEngulfer.high = high[w - 1];
               bearishEngulfer.low = low[w - 1];
               bearishEngulfer.retestTime = time[w] + (PeriodSeconds() * optimalBearishRetestRange); // Set retest time window
               return rates_total;
              }
           }
         //--- Found state
         . . . . 
        }
     }
   return(rates_total);
  }

Заключительный тест

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

Адаптивный слой — тест инициализации

Рис. 3. Адаптивный слой — тест инициализации

Заключение

Мы внедрили адаптивное, проверяемое улучшение во фреймворк Malaysian Engulfing. Каждый обнаруженный сетап представлен фиксированным набором свойств (время формирования, максимум/минимум зоны, время ретеста, расстояние до бара ретеста) и оценивается путем сканирования фиксированного окна вперед для расчета MFE и MAE от стандартизированной точки входа (цены открытия после ретеста). Сетапы группируются по retestBars, а их оценка рассчитывается по формуле avgMFE − avgMAE. Ограниченный полный перебор по одному параметру позволяет определить оптимальное расстояние для ретеста отдельно для сетапов на покупку и на продажу.

На практике реализация на MQL5 вычисляет значения optimalBullishRetestRange и optimalBearishRetestRange в функции OnInit (в историческом окне, заданном пользователем) и применяет их в функции OnCalculate, чтобы управлять валидацией ретеста во время выполнения. Этот подход прост, прозрачен и эффективен: он заменяет ручной подбор параметров воспроизводимым выбором, основанным на данных и привязанным к выбранному символу, таймфрейму и диапазону дат. Результаты отображаются в сводке выполнения и доступны для проверки в массивах, что позволяет легко их верифицировать и выполнять дальнейшие итерации.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Bryan John Aldridge
Bryan John Aldridge | 17 мая 2026 в 07:24

Спасибо за этот очень интересный подход к адаптивной настройке параметров. Мне особенно нравится, что его можно применять к любому индикатору (или даже к набору индикаторов).

Что нужно сделать, чтобы применить тот же подход (то есть накопление показателей MAE/MFE по нескольким сделкам и выбор оптимального набора параметров) к советнику?

Нейронная сеть на практике: Перцептрон Нейронная сеть на практике: Перцептрон
В этой статье перцептрон представлен как основа нейронной сети, а также подробно описана его реализация на MQL5. Мы объясним, что такое функции активации и их производные, в чём разница между прямым распространением и обратным распространением, а также как используется функция потерь наименьших квадратов и градиент. Вы научитесь обучать веса и смещения, проверять результат с помощью тестовых скриптов и настраивать скорость обучения для достижения стабильной сходимости.
Нейронная сеть на практике: Градиент Нейронная сеть на практике: Градиент
В статье объясняется, почему и как используется градиент при обучении перцептрона, исходя из метода наименьших квадратов и правила цепочки для получения частных производных. Мы покажем реализацию вычисления градиента в классе C_Neuron на языке MQL5 и проверим её на примерах с 1 и 2 входами. Вы научитесь настраивать веса и смещение с помощью градиентного спуска, а также подготовитесь к прямому и обратному распространению.
Нейросети в трейдинге: Диффузионная генерация торговых планов (Вычислительная основа) Нейросети в трейдинге: Диффузионная генерация торговых планов (Вычислительная основа)
Продолжаем торговую адаптацию фреймворка DPCC и рассматриваем семь OpenCL-кернелов для коррекции торгового плана. На основе диагностики нарушений определяем порядок изменения действий, формируем варианты поправок и повторно проверяем их последствия на заданных ценовых сценариях. Разбираем сравнение предложений, выбор улучшения и контроль бюджета проверок. Итоговые статусы и диагностика позволяют отличить успешную коррекцию от завершения поиска с оставшимися нарушениями.
Создание пользовательского тикового графика в MQL5 Создание пользовательского тикового графика в MQL5
Узнайте, как реализовать в MQL5 тиковый график, в котором каждый бар строится из фиксированного количества тиков, а не по времени. В статье рассматриваются создание и настройка пользовательского символа, получение тиков в режиме реального времени, формирование значений OHLC и передача данных с помощью функции CustomRatesUpdate. Такой подход позволяет получить свечи, формируемые по рыночной активности, которые точнее отражают интенсивность рынка и краткосрочный импульс для точного внутридневного анализа.