Разработка индикатора Market Memory Zones: Зоны вероятного возврата цены
Содержание:
Введение
Многим трейдерам сложно выделить действительно значимые для рынка ценовые уровни. Традиционные линии поддержки и сопротивления, запаздывающие индикаторы и субъективное построение зон часто приводят к загромождению графиков и противоречивым сигналам. В результате трейдеры часто реагируют на движения цен с опозданием, открывают сделки в зонах низкой вероятности успеха или ошибочно принимают рыночный шум за значимую рыночную структуру. Такая дилемма приводит к непоследовательности, чрезмерной торговле и отсутствию уверенности в принимаемых решениях.
Подход в основе индикатора 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Возможности Мастера MQL5, которые вам нужно знать (Часть 96): Использование вейвлетного порогового преобразования и сети LSTM в пользовательском классе управления капиталом
Возможности Мастера MQL5, которые вам нужно знать (Часть 94): Использование выборки из резервуара и линейной регрессии в пользовательском классе трейлинг-стопа
От начального до среднего уровня: Перегрузка операторов (II)
Моделирование рынка: Position View (XIX)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования