English Deutsch 日本語
preview
Разработка индикатора Market Memory Zones: Зоны вероятного возврата цены

Разработка индикатора Market Memory Zones: Зоны вероятного возврата цены

MetaTrader 5 — Примеры |
336 1
Hlomohang John Borotho
Hlomohang John Borotho

Содержание:

  1. Введение
  2. Обзор индикатора
  3. Начало работы
  4. Демонстрация работы индикатора Market Memory Zones
  5. Заключение


Введение

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

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


Обзор индикатора: Market Memory Zones

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

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

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


Начало работы

//+------------------------------------------------------------------+
//|                                            MarketMemoryZones.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/en/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/johnhlomohang/"
#property version   "1.00"
#property indicator_chart_window
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_TIMEFRAMES AnalysisTF = PERIOD_CURRENT;  // Analysis Timeframe
input int             LookbackBars = 200;           // Bars to analyze
input double          MinCandleSizeATR = 1.5;       // Min candle size (ATR multiplier)
input bool            ShowDisplacementZones = true; // Show displacement zones
input bool            ShowUnfilledAreas = true;     // Show unfilled price areas
input bool            ShowStructureZones = true;    // Show structure transition zones
input bool            ShowLiquidityZones = true;    // Show liquidity sweep origins
input bool            FilterByVolume = false;       // Require volume spike
input int             MaxActiveZones = 15;          // Max zones to show
input bool            ExtendZonesForward = true;    // Extend zones forward
input int             ZoneOpacity = 20;             // Zone opacity (0-100)

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

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

//+------------------------------------------------------------------+
//| Zone Types                                                       |
//+------------------------------------------------------------------+
enum ZONE_TYPE
{
   ZONE_DISPLACEMENT,     // Large impulsive candles
   ZONE_UNFILLED,         // Price inefficiency gaps
   ZONE_STRUCTURE,        // Structure transition
   ZONE_LIQUIDITY         // Liquidity sweep origin
};

//+------------------------------------------------------------------+
//| Zone Structure                                                   |
//+------------------------------------------------------------------+
struct MemoryZone
{
   datetime          startTime;
   datetime          endTime;
   double            high;
   double            low;
   ZONE_TYPE         type;
   color             zoneColor;
   bool              isActive;
   int               creationBar;
   double            strength;      // 0-1 strength rating
   
   void Reset()
   {
      startTime = 0;
      endTime = 0;
      high = 0.0;
      low = 0.0;
      type = ZONE_DISPLACEMENT;
      zoneColor = clrNONE;
      isActive = false;
      creationBar = 0;
      strength = 0.5;
   }
   
   bool IsPriceInZone(double price)
   {
      return price >= low && price <= high;
   }
   
   double GetZoneMiddle()
   {
      return (high + low) / 2.0;
   }
   
   bool IsZoneMitigated(double currentHigh, double currentLow)
   {
      // Zone is considered mitigated if price has traded through entire zone
      return (currentLow <= low && currentHigh >= high);
   }
};

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
MemoryZone zones[];
int zoneCount = 0;
double atrBuffer[];
datetime lastProcessedBar = 0;
string indicatorName;
double currentAtrValue = 0.0;

// Colors for different zone types
color displacementColor = C'0,100,255';    // Blue
color unfilledColor = C'255,128,0';        // Orange
color structureColor = C'128,0,255';       // Purple
color liquidityColor = C'255,50,50';       // Red

В этом разделе кода определены различные классификации зон, используемые индикатором Market Memory Zones. Перечисление ZONE_TYPE предоставляет четкий и структурированный способ маркировки каждой зоны в соответствии с ее происхождением, включая зоны смещения, образованные импульсными свечами, незаполненные ценовые зоны, зоны структурных переходов и источники импульса после снятия ликвидности. Использование перечислений (enum) повышает читаемость кода и гарантирует, что каждая зона будет последовательно идентифицирована, обработана и визуализирована в соответствии с ее конкретной рыночной функцией.

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

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

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   indicatorName = "MMZ_" + IntegerToString(GetTickCount());
   
   // Set indicator properties
   IndicatorSetString(INDICATOR_SHORTNAME, "Market Memory Zones");
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   
   // Initialize ATR buffer
   ArrayResize(atrBuffer, LookbackBars);
   ArrayInitialize(atrBuffer, 0.0);
   
   // Initialize zones array
   ArrayResize(zones, MaxActiveZones * 2); // Double for safety
   for(int i = 0; i < ArraySize(zones); i++)
      zones[i].Reset();
   
   // Calculate initial ATR
   currentAtrValue = iATR(_Symbol, AnalysisTF, 14);
   
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Clean up all zone objects
   ObjectsDeleteAll(0, "MMZ_");
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   // Check if we have enough bars
   if(rates_total < LookbackBars) return 0;
   
   // Process only on new bar
   if(prev_calculated > 0 && prev_calculated == rates_total)
      return rates_total;
   
   // Set array as series
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(tick_volume, true);
   
   // Update ATR value
   currentAtrValue = iATR(_Symbol, AnalysisTF, 14);
   
   // Detect zones starting from the most recent data
   DetectMemoryZones(rates_total, time, open, high, low, close, tick_volume);
   
   // Update and draw zones
   UpdateZones(rates_total, time, high[0], low[0]);
   
   return rates_total;
}

Этот раздел кода отвечает за управление жизненным циклом индикатора Market Memory Zones, начиная с функции OnInit(). В процессе инициализации генерируется уникальное имя индикатора для предотвращения конфликтов между объектами, а также устанавливаются ключевые свойства индикатора, такие как имя отображения и точность цены. Затем выделяются и инициализируются буфер ATR и массив зон, что обеспечивает чистоту использования памяти до начала каких-либо вычислений. Структура каждой зоны сбрасывается до состояния по умолчанию, и на основе выбранного временного интервала анализа рассчитывается начальное значение ATR, которое впоследствии будет использоваться для проверки смещения и силы зоны.

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

//+------------------------------------------------------------------+
//| Detect All Memory Zones                                          |
//+------------------------------------------------------------------+
void DetectMemoryZones(int rates_total, const datetime &time[], const double &open[],
                      const double &high[], const double &low[], const double &close[],
                      const long &tick_volume[])
{
   // Clear old zones
   for(int i = 0; i < zoneCount; i++)
      zones[i].Reset();
   zoneCount = 0;
   
   // Calculate average volume for volume filter
   double avgVolume = CalculateAverageVolume(tick_volume, rates_total, 20);
   
   // Start from older bars to newer (but leave room for analysis)
   int startBar = MathMin(LookbackBars, rates_total - 10);
   
   for(int i = startBar; i >= 3; i--)  // Start from 3 to have previous bars for context
   {
      // 1. Check for Displacement Zones
      if(ShowDisplacementZones && zoneCount < MaxActiveZones)
      {
         if(IsDisplacementCandle(i, high, low, open, close, tick_volume, avgVolume))
         {
            AddDisplacementZone(i, time, high, low, open, close);
         }
      }
      
      // 2. Check for Unfilled Price Areas
      if(ShowUnfilledAreas && zoneCount < MaxActiveZones)
      {
         if(IsUnfilledArea(i, high, low, open, close))
         {
            AddUnfilledZone(i, time, high, low, open, close);
         }
      }
      
      // 3. Check for Structure Transition Zones
      if(ShowStructureZones && zoneCount < MaxActiveZones && i > 5)
      {
         if(IsStructureTransition(i, high, low))
         {
            AddStructureZone(i, time, high, low);
         }
      }
      
      // 4. Check for Liquidity Sweep Origins
      if(ShowLiquidityZones && zoneCount < MaxActiveZones && i > 1)
      {
         if(IsLiquiditySweep(i, high, low, close, open))
         {
            AddLiquidityZone(i, time, high, low, close);
         }
      }
      
      // Break if we reached max zones
      if(zoneCount >= MaxActiveZones) break;
   }
}

//+------------------------------------------------------------------+
//| Check for Displacement Candle                                    |
//+------------------------------------------------------------------+
bool IsDisplacementCandle(int bar, const double &high[], const double &low[],
                         const double &open[], const double &close[],
                         const long &tick_volume[], double avgVolume)
{
   // 1. Check minimum candle size (ATR-based)
   double candleSize = high[bar] - low[bar];
   double minSize = currentAtrValue * MinCandleSizeATR;
   
   if(candleSize < minSize) return false;
   
   // 2. Check for minimal overlap with prior candle
   double overlapWithPrev = CalculateOverlap(high[bar], low[bar], high[bar+1], low[bar+1]);
   if(overlapWithPrev > 0.3) return false; // More than 30% overlap
   
   // 3. Check for minimal overlap with next candle
   if(bar > 0)
   {
      double overlapWithNext = CalculateOverlap(high[bar], low[bar], high[bar-1], low[bar-1]);
      if(overlapWithNext > 0.3) return false;
   }
   
   // 4. Volume spike confirmation (optional)
   if(FilterByVolume)
   {
      double volumeRatio = (double)tick_volume[bar] / avgVolume;
      if(volumeRatio < 1.5) return false; // Less than 150% of average
   }
   
   // 5. Check if it's an impulsive candle (strong directional move)
   bool isBullishImpulse = (close[bar] > open[bar]) && ((close[bar] - open[bar]) > candleSize * 0.6);
   bool isBearishImpulse = (close[bar] < open[bar]) && ((open[bar] - close[bar]) > candleSize * 0.6);
   
   return (isBullishImpulse || isBearishImpulse);
}

//+------------------------------------------------------------------+
//| Check for Unfilled Price Area                                    |
//+------------------------------------------------------------------+
bool IsUnfilledArea(int bar, const double &high[], const double &low[],
                   const double &open[], const double &close[])
{
   // Look for Fair Value Gap pattern (3-candle formation)
   if(bar < 2) return false;
   
   // Pattern: Middle candle has range not overlapped by candles before and after
   // Bullish FVG
   if(low[bar-1] > high[bar] && low[bar-2] > high[bar])
   {
      return true;
   }
   
   // Bearish FVG
   if(high[bar-1] < low[bar] && high[bar-2] < low[bar])
   {
      return true;
   }
   
   // Alternative: Large candle followed by small overlapping candles
   double candle1Size = high[bar] - low[bar];
   double candle2Size = high[bar-1] - low[bar-1];
   
   if(candle1Size > currentAtrValue && candle2Size < currentAtrValue * 0.5)
   {
      // Check if candle2 didn't fill much of candle1's range
      double fillRatio = CalculateOverlap(high[bar], low[bar], high[bar-1], low[bar-1]);
      if(fillRatio < 0.3) return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| Check for Structure Transition                                   |
//+------------------------------------------------------------------+
bool IsStructureTransition(int bar, const double &high[], const double &low[])
{
   // Need at least 5 bars to determine structure
   if(bar < 5) return false;
   
   // Detect swing points
   bool isSwingHigh = IsSwingHigh(bar, high, 3);
   bool isSwingLow = IsSwingLow(bar, low, 3);
   
   if(!isSwingHigh && !isSwingLow) return false;
   
   // Check for structure break
   if(isSwingHigh)
   {
      // Check if breaking previous structure (Higher High to Lower High)
      double prevSwingHigh = FindPrevSwingHigh(bar, high, 3);
      if(prevSwingHigh > 0 && high[bar] < prevSwingHigh)
      {
         return true; // Lower High formed
      }
   }
   
   if(isSwingLow)
   {
      // Check if breaking previous structure (Lower Low to Higher Low)
      double prevSwingLow = FindPrevSwingLow(bar, low, 3);
      if(prevSwingLow > 0 && low[bar] > prevSwingLow)
      {
         return true; // Higher Low formed
      }
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| Check for Liquidity Sweep                                        |
//+------------------------------------------------------------------+
bool IsLiquiditySweep(int bar, const double &high[], const double &low[], const double &close[], const double &open[])
{


   if(bar < 2) return false;
   
   // Bullish liquidity sweep: Sweeps previous low then closes above
   if(low[bar] < low[bar+1] && close[bar] > low[bar+1] && close[bar] > close[bar+1])
   {
      // Check for reversal pattern
      if(close[bar] > open[bar] && (close[bar] - open[bar]) > (high[bar] - low[bar]) * 0.5)
      {
         return true;
      }
   }
   
   // Bearish liquidity sweep: Sweeps previous high then closes below
   if(high[bar] > high[bar+1] && close[bar] < high[bar+1] && close[bar] < close[bar+1])
   {
      // Check for reversal pattern
      if(close[bar] < open[bar] && (open[bar] - close[bar]) > (high[bar] - low[bar]) * 0.5)
      {
         return true;
      }
   }
   
   return false;
}

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

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

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

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

//+------------------------------------------------------------------+
//| Add Displacement Zone                                            |
//+------------------------------------------------------------------+
void AddDisplacementZone(int bar, const datetime &time[], const double &high[], 
                        const double &low[], const double &open[], const double &close[])
{
   if(zoneCount >= MaxActiveZones) return;
   
   zones[zoneCount].startTime = time[bar];
   zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar];
   zones[zoneCount].high = high[bar];
   zones[zoneCount].low = low[bar];
   zones[zoneCount].type = ZONE_DISPLACEMENT;
   zones[zoneCount].zoneColor = displacementColor;
   zones[zoneCount].isActive = true;
   zones[zoneCount].creationBar = bar;
   
   // Calculate strength based on candle size relative to ATR
   double candleSize = high[bar] - low[bar];
   zones[zoneCount].strength = MathMin(candleSize / (currentAtrValue * 2), 1.0);
   
   // Adjust zone boundaries for better visualization
   double range = zones[zoneCount].high - zones[zoneCount].low;
   zones[zoneCount].high += range * 0.05;  // Add 5% padding
   zones[zoneCount].low -= range * 0.05;
   
   zoneCount++;
}

//+------------------------------------------------------------------+
//| Add Unfilled Zone                                                |
//+------------------------------------------------------------------+
void AddUnfilledZone(int bar, const datetime &time[], const double &high[], 
                    const double &low[], const double &open[], const double &close[])
{
   if(zoneCount >= MaxActiveZones) return;
   
   // For FVG pattern (3-candle)
   if(bar >= 2)
   {
      // Bullish FVG
      if(low[bar-1] > high[bar] && low[bar-2] > high[bar])
      {
         zones[zoneCount].startTime = time[bar];
         zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar-1];
         zones[zoneCount].high = MathMax(high[bar-1], high[bar-2]);
         zones[zoneCount].low = low[bar];
      }
      // Bearish FVG
      else if(high[bar-1] < low[bar] && high[bar-2] < low[bar])
      {
         zones[zoneCount].startTime = time[bar];
         zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar-1];
         zones[zoneCount].high = high[bar];
         zones[zoneCount].low = MathMin(low[bar-1], low[bar-2]);
      }
      
      zones[zoneCount].type = ZONE_UNFILLED;
      zones[zoneCount].zoneColor = unfilledColor;
      zones[zoneCount].isActive = true;
      zones[zoneCount].creationBar = bar;
      zones[zoneCount].strength = 0.7;
      
      zoneCount++;
   }
}

//+------------------------------------------------------------------+
//| Add Structure Zone                                               |
//+------------------------------------------------------------------+
void AddStructureZone(int bar, const datetime &time[], const double &high[], const double &low[])
{
   if(zoneCount >= MaxActiveZones) return;
   
   zones[zoneCount].startTime = time[bar];
   zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar];
   
   if(IsSwingHigh(bar, high, 3))
   {
      zones[zoneCount].high = high[bar];
      zones[zoneCount].low = high[bar] - (high[bar] - low[bar]) * 0.5; // 50% of candle range
   }
   else if(IsSwingLow(bar, low, 3))
   {
      zones[zoneCount].low = low[bar];
      zones[zoneCount].high = low[bar] + (high[bar] - low[bar]) * 0.5; // 50% of candle range
   }
   
   zones[zoneCount].type = ZONE_STRUCTURE;
   zones[zoneCount].zoneColor = structureColor;
   zones[zoneCount].isActive = true;
   zones[zoneCount].creationBar = bar;
   zones[zoneCount].strength = 0.8;
   
   zoneCount++;
}

//+------------------------------------------------------------------+
//| Add Liquidity Zone                                               |
//+------------------------------------------------------------------+
void AddLiquidityZone(int bar, const datetime &time[], const double &high[], 
                     const double &low[], const double &close[])
{
   if(zoneCount >= MaxActiveZones) return;
   
   zones[zoneCount].startTime = time[bar];
   zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar];
   
   // Determine sweep direction
   if(low[bar] < low[bar+1]) // Bullish sweep
   {
      zones[zoneCount].high = high[bar];
      zones[zoneCount].low = low[bar+1]; // The level that was swept
   }
   else if(high[bar] > high[bar+1]) // Bearish sweep
   {
      zones[zoneCount].high = high[bar+1]; // The level that was swept
      zones[zoneCount].low = low[bar];
   }
   
   zones[zoneCount].type = ZONE_LIQUIDITY;
   zones[zoneCount].zoneColor = liquidityColor;
   zones[zoneCount].isActive = true;
   zones[zoneCount].creationBar = bar;
   zones[zoneCount].strength = 0.9; // Liquidity sweeps are high probability
   
   zoneCount++;
}

//+------------------------------------------------------------------+
//| Update and Draw Zones                                            |
//+------------------------------------------------------------------+
void UpdateZones(int rates_total, const datetime &time[], double currentHigh, double currentLow)
{
   // First, remove all old objects
   ObjectsDeleteAll(0, "MMZ_");
   
   // Draw active zones
   for(int i = 0; i < zoneCount; i++)
   {
      if(!zones[i].isActive) continue;
      
      // Check if zone is still valid (not fully mitigated)
      if(zones[i].IsZoneMitigated(currentHigh, currentLow))
      {
         zones[i].isActive = false;
         continue;
      }
      
      // Draw zone as rectangle
      DrawZone(zones[i], i);
   }
   
   // Draw legend
   DrawLegend();
}

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

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

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

//+------------------------------------------------------------------+
//| Draw Zone as Rectangle                                           |
//+------------------------------------------------------------------+
void DrawZone(MemoryZone &zone, int index)
{
   string objName = StringFormat("MMZ_%s_%d_%d", EnumToString(zone.type), (int)zone.startTime, index);
   
   // Create rectangle
   if(ObjectCreate(0, objName, OBJ_RECTANGLE, 0, zone.startTime, zone.high, zone.endTime, zone.low))
   {
      // Set properties
      ObjectSetInteger(0, objName, OBJPROP_COLOR, zone.zoneColor);
      ObjectSetInteger(0, objName, OBJPROP_BACK, true);
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
      
      // Set opacity based on strength and input
      int alpha = (int)(zone.strength * ZoneOpacity);
      alpha = MathMax(alpha, 10);
      alpha = MathMin(alpha, 80);
      
      color zoneColorWithAlpha = ColorToARGB(zone.zoneColor, alpha);
      ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColorWithAlpha);
      
      // Add tooltip
      string tooltip = StringFormat("MMZ Type: %s\nStrength: %.1f\nRange: %.5f - %.5f",
                                    GetZoneTypeName(zone.type),
                                    zone.strength,
                                    zone.low,
                                    zone.high);
      ObjectSetString(0, objName, OBJPROP_TOOLTIP, tooltip);
   }
}

//+------------------------------------------------------------------+
//| Draw Legend                                                      |
//+------------------------------------------------------------------+
void DrawLegend()
{
   int yPos = 20;
   int xPos = 20;
   
   if(ShowDisplacementZones)
   {
      CreateLabel("Legend_Displacement", "• Displacement Zones", xPos, yPos, displacementColor);
      yPos += 20;
   }
   
   if(ShowUnfilledAreas)
   {
      CreateLabel("Legend_Unfilled", "• Unfilled Areas", xPos, yPos, unfilledColor);
      yPos += 20;
   }
   
   if(ShowStructureZones)
   {
      CreateLabel("Legend_Structure", "• Structure Transitions", xPos, yPos, structureColor);
      yPos += 20;
   }
   
   if(ShowLiquidityZones)
   {
      CreateLabel("Legend_Liquidity", "• Liquidity Sweeps", xPos, yPos, liquidityColor);
      yPos += 20;
   }
}

void CreateLabel(string name, string text, int x, int y, color clr)
{
   ObjectCreate(0, "MMZ_" + name, OBJ_LABEL, 0, 0, 0);
   ObjectSetString(0, "MMZ_" + name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, "MMZ_" + name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, "MMZ_" + name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "MMZ_" + name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, "MMZ_" + name, OBJPROP_FONTSIZE, 10);
   ObjectSetString(0, "MMZ_" + name, OBJPROP_FONT, "Arial");
   ObjectSetInteger(0, "MMZ_" + name, OBJPROP_HIDDEN, true);
}

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

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


Демонстрация работы индикатора Market Memory Zones



Заключение

Резюмируем: разработка индикатора Market Memory Zones заключалась в создании системного метода определения ключевых областей на графике, куда цена с высокой вероятностью может вернуться. Мы разработали несколько типов зон — зоны смещения, незаполненные ценовые зоны, зоны структурных переходов и зоны источника импульса после снятия ликвидности, каждая из которых отражает специфическое поведение рынка (например, импульсивные движения, проявления неэффективности, структурные сдвиги и определение целевых уровней ликвидности). Внедрив функции обнаружения, определив надежную структуру MemoryZone и управляя зонами с помощью оценок силы и визуальных свойств, мы создали индикатор, который не только автоматически обнаруживает эти зоны, но и четко отображает их на графике с помощью прямоугольников, всплывающих подсказок и легенды для более удобного восприятия.

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Andre Luiz Bravo De Barros
Andre Luiz Bravo De Barros | 29 янв. 2026 в 02:49
отлично сработано
Возможности Мастера MQL5, которые вам нужно знать (Часть 96): Использование вейвлетного порогового преобразования и сети LSTM в пользовательском классе управления капиталом Возможности Мастера MQL5, которые вам нужно знать (Часть 96): Использование вейвлетного порогового преобразования и сети LSTM в пользовательском классе управления капиталом
В этой статье рассматривается пользовательский класс Мастера MQL5 для управления капиталом. Пользовательский класс CMoneyWaveletLSTM создается объединением алгоритма вейвлетного порогового преобразования и сети LSTM. Как и в других статьях серии, разработанную модель можно тестировать с советниками, собранными в Мастере MQL5 и настраиваемыми с использованием различных классов трейлинг-стопов и входных сигналов. Как и в предыдущих статьях, в качестве сигнала входа используются встроенный класс Envelopes и класс RSI.
Возможности Мастера MQL5, которые вам нужно знать (Часть 94): Использование выборки из резервуара и линейной регрессии в пользовательском классе трейлинг-стопа Возможности Мастера MQL5, которые вам нужно знать (Часть 94): Использование выборки из резервуара и линейной регрессии в пользовательском классе трейлинг-стопа
В этой статье мы переходим к пользовательской реализации класса для Мастера MQL5, посвященной трейлинг-стопам. Пользовательский класс CTrailingReservoirLinReg создается путем объединения алгоритма выборки из резервуара и сети линейной регрессии. Как и в других статьях серии, это решение можно тестировать с советниками, собранными Мастером MQL5 и настраиваемыми с различными сигналами входа и классами управления капиталом.
От начального до среднего уровня: Перегрузка операторов (II) От начального до среднего уровня: Перегрузка операторов (II)
Поначалу эта статья может показаться довольно запутанной из-за материала, который я собираюсь в ней изложить. Тем не менее я постарался изложить всё как можно проще и доступнее. Надеюсь, вы поймёте то, что я здесь покажу, и это когда-нибудь вам пригодится.
Моделирование рынка: Position View (XIX) Моделирование рынка: Position View (XIX)
Один из вопросов, который доставлял мне наибольший дискомфорт, заключается в том, что класс C_ElementsTrade содержит код для обращения к позициям. Не воспринимайте это как ошибку, потому что на самом деле это не так. Однако из-за этого некоторые задачи, которые нам предстоит выполнить позже, становятся более рискованными с точки зрения ошибок. Вся работа по реализации индикатора позиции велась с расчетом на его использование в сервисе репликации/моделирования. Однако при запуске в этой среде у нас не будет никакого доступа к реальной позиции. Следовательно, любой вызов библиотеки MQL5, предназначенный для получения данных о позиции, не окажет никакого влияния на код.