Адаптивный индикатор Malaysian Engulfing (Часть 2): Оптимизированный диапазон баров ретеста
Введение
У вас уже есть работающий детектор 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 — значение диапазона ретеста в барах, дающее наивысшую оценку, — которое представляет оптимально сбалансированный диапазон, где благоприятное движение цены стабильно перевешивает неблагоприятное движение по всему набору данных.
- Формирование адаптивной модели
//+------------------------------------------------------------------+ //| 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейронная сеть на практике: Перцептрон
Нейронная сеть на практике: Градиент
Нейросети в трейдинге: Диффузионная генерация торговых планов (Вычислительная основа)
Создание пользовательского тикового графика в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Спасибо за этот очень интересный подход к адаптивной настройке параметров. Мне особенно нравится, что его можно применять к любому индикатору (или даже к набору индикаторов).
Что нужно сделать, чтобы применить тот же подход (то есть накопление показателей MAE/MFE по нескольким сделкам и выбор оптимального набора параметров) к советнику?