English Deutsch 日本語
preview
Автоматизация индикатора Market Memory Zones: Зоны, куда цена с высокой вероятностью вернется

Автоматизация индикатора Market Memory Zones: Зоны, куда цена с высокой вероятностью вернется

MetaTrader 5Примеры |
108 0
Hlomohang John Borotho
Hlomohang John Borotho

Содержание

  1. Введение
  2. Обзор автоматизации
  3. Приступим
  4. Результаты тестирования на исторических данных
  5. Заключение


Введение

В предыдущей статье был разработан индикатор Market Memory Zones, определяющий ключевые области, в которых цена демонстрировала выраженный направленный импульс – через смещение, изменение структуры или снятие ликвидности. Исходное ценовое движение было преобразовано в структурированные зоны, визуально обозначающие области возникновения дисбалансов, перехода контроля или событий, связанных с ликвидностью. Вместо произвольных уровней поддержки и сопротивления индикатор выделяет области, подтвержденные поведением цены: импульсным движением за пределы обычной волатильности, явными событиями изменения характера (CHoCH) и выраженным снятием ликвидности с последующим разворотом.

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


Обзор автоматизации

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

Displacement Zone

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

Structure Transition Zone

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

Liqudity Sweep


Приступим

//+------------------------------------------------------------------+
//|                                                     Auto MMZ.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ru/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/users/johnhlomohang/"
#property version   "1.00"

#include <Trade/Trade.mqh>
#include <Math/Stat/Math.mqh>
CTrade trade;

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_TIMEFRAMES HTF = PERIOD_H1;          // Higher Timeframe
input ENUM_TIMEFRAMES LTF = PERIOD_M5;          // Lower Timeframe
input ENUM_TIMEFRAMES ATF = PERIOD_M15;         // Analysis Timeframe (for zones)
input int             SwingBars = 3;            // Bars for swing detection

// Displacement Zone Parameters
input double          Displacement_ATR_Multiplier = 1.8;    // ATR multiplier for displacement
input double          Displacement_Body_Percent = 60.0;     // Minimum body percentage
input double          Displacement_Overlap_Max = 30.0;      // Maximum overlap with previous candle
input double          Displacement_Close_Extreme = 20.0;    // Close near extreme percentage

// Risk Management
input double          RiskPercent = 1.0;        // Risk per trade (%)
input int             StopLoss_Pips = 150;      // Stop Loss in pips
input double          RiskRewardRatio = 2.0;    // Risk/Reward ratio
input bool            UseDynamicSL = true;      // Use zone-based SL
input bool            UseStructureTP = true;    // Use structure for TP

// Trade Management
input int             MaxTradesPerDay = 10;     // Maximum trades per day
input bool            AllowConsecutiveTrades = true;  // Allow multiple trades in same direction
input double          TrailingStop_ATR = 1.5;   // ATR multiple for trailing stop

// Visualization
input bool            ShowZones = true;         // Show zone rectangles
input bool            ShowArrows = true;        // Show entry arrows
input bool            ShowLabels = true;        // Show trade labels
input bool            DebugMode = true;         // Show debug information

//+------------------------------------------------------------------+
//| Zone Structure                                                   |
//+------------------------------------------------------------------+
enum ZONE_TYPE
{
   ZONE_DISPLACEMENT,
   ZONE_STRUCTURE_TRANSITION,
   ZONE_LIQUIDITY_SWEEP
};

struct TradeZone
{
   datetime          time;
   double            high;
   double            low;
   double            mid;
   ZONE_TYPE         type;
   int               direction;      // 1 for bullish, -1 for bearish
   double            atr;            // ATR at time of zone formation
   datetime          expiry;         // Zone expiry time
   bool              active;
   bool              triggered;
   string            symbol;
   ENUM_TIMEFRAMES   tf;
   
   // Constructor to initialize with default values
   TradeZone()
   {
      Reset();
   }
   
   // Reset method
   void Reset()
   {
      time = 0;
      high = 0;
      low = 0;
      mid = 0;
      type = ZONE_DISPLACEMENT;
      direction = 0;
      atr = 0;
      expiry = 0;
      active = false;
      triggered = false;
      symbol = _Symbol;
      tf = PERIOD_CURRENT;
   }
};

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

Затем определяется перечисление ZONE_TYPE для классификации зон смещения, изменения структуры и снятия ликвидности. После него задается структурированная модель данных TradeZone, хранящая всю информацию о зоне: ценовые границы, направление, ATR в момент формирования, срок действия, статус активации и метаданные. Конструктор и метод Reset() обеспечивают для каждой зоны чистое контролируемое начальное состояние. Это создает структурированную и масштабируемую основу для управления несколькими динамическими зонами Market Memory Zones в советнике.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
SwingPoint htf_lastHigh, htf_prevHigh;
SwingPoint htf_lastLow, htf_prevLow;
SwingPoint atf_lastHigh, atf_prevHigh;
SwingPoint atf_lastLow, atf_prevLow;

TradeZone zones[100];
int zoneCount = 0;

TradeInfo activeTrades[10];
int activeTradeCount = 0;

datetime lastTradeDay = 0;
int tradesToday = 0;

double currentATR = 0;
int atrHandle = -1;

MqlDateTime today;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Initialize ATR indicator
   atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
   if(atrHandle == INVALID_HANDLE)
   {
      Print("Failed to create ATR indicator");
      return INIT_FAILED;
   }
   
   // Initialize swing points
   htf_lastHigh.Reset();
   htf_prevHigh.Reset();
   htf_lastLow.Reset();
   htf_prevLow.Reset();
   atf_lastHigh.Reset();
   atf_prevHigh.Reset();
   atf_lastLow.Reset();
   atf_prevLow.Reset();
   
   // Initialize zones array
   for(int i = 0; i < ArraySize(zones); i++)
      zones[i].Reset();
   zoneCount = 0;
   
   // Initialize active trades array
   for(int i = 0; i < ArraySize(activeTrades); i++)
      activeTrades[i].Reset();
   activeTradeCount = 0;
   
   // Initialize trade tracking
   lastTradeDay = 0;
   tradesToday = 0;
   
   // Set magic number for trades
   trade.SetExpertMagicNumber(12345);
   
   Print("EA Initialized. Zone Types: Displacement, Structure Transition, Liquidity Sweep");
   
   return INIT_SUCCEEDED;
}

В этом разделе задается основное глобальное состояние советника: объявляются переменные для отслеживания точек свинга на старшем и анализируемом таймфреймах, массивы для хранения до 100 обнаруженных торговых зон и до 10 активных сделок, а также переменные для управления дневными лимитами сделок и отслеживания волатильности на основе ATR. В функции OnInit() инициализируется хэндл индикатора ATR для расчетов волатильности, сбрасываются все структуры свингов для задания чистых начальных значений, очищаются и подготавливаются массивы зон и активных сделок, а также сбрасываются счетчики дневных сделок. В целом такая настройка обеспечивает запуск системы в упорядоченном состоянии с корректно инициализированными структурами данных, готовыми к обнаружению зон в реальном времени и исполнению сделок.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
   
   ObjectsDeleteAll(0, "ZONE_");
   ObjectsDeleteAll(0, "TRADE_");
   ChartRedraw();
   
   Print("EA Deinitialized");
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Update current ATR
   UpdateATR();
   
   // Check for new bars on analysis timeframe
   static datetime lastAtfBar = 0;
   datetime currentAtfBar = iTime(_Symbol, ATF, 0);
   
   if(currentAtfBar != lastAtfBar)
   {
      lastAtfBar = currentAtfBar;
      
      if(DebugMode) Print("New ATF bar: ", TimeToString(currentAtfBar));
      
      // Update market structure
      UpdateMarketStructure();
      
      // Detect new zones
      DetectDisplacementZones();
      DetectStructureTransitionZones();
      DetectLiquiditySweepZones();
      
      // Clean up expired zones
      CleanupZones();
   }
   
   // Check zone entries on every tick
   CheckZoneEntries();
   
   // Manage open positions
   ManageOpenTrades();
   
   // Update visualization
   if(ShowZones) UpdateVisualization();
}

//+------------------------------------------------------------------+
//| Update Market Structure                                          |
//+------------------------------------------------------------------+
void UpdateMarketStructure()
{
   // Update HTF structure
   UpdateSwingPoints(HTF, htf_lastHigh, htf_prevHigh, htf_lastLow, htf_prevLow);
   
   // Update Analysis TF structure
   UpdateSwingPoints(ATF, atf_lastHigh, atf_prevHigh, atf_lastLow, atf_prevLow);
   
   if(DebugMode && atf_lastHigh.price > 0 && atf_lastLow.price > 0)
   {
      Print("Market Structure - ATF High: ", atf_lastHigh.price, 
            " Low: ", atf_lastLow.price,
            " ATR: ", currentATR);
   }
}

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

Функция OnTick() служит оперативным ядром советника, координируя в реальном времени логику определения зон, исполнения сделок и управления ими. На каждом тике обновляется текущее значение ATR и проверяется, сформировался ли новый бар на анализируемом таймфрейме. Затем пересчитывается рыночная структура и выполняется поиск новых зон смещения, изменения структуры и снятия ликвидности. Истекшие зоны систематически очищаются, а условия входа непрерывно оцениваются на каждом тике для своевременного исполнения сделок. Открытые сделки активно сопровождаются, а графические элементы обновляются при включенной визуализации. Функция UpdateMarketStructure() поддерживает этот процесс, обновляя точки свинга на старшем и анализируемом таймфреймах. Благодаря этому советник остается синхронизированным с текущей рыночной структурой и волатильностью.

//+------------------------------------------------------------------+
//| Detect Displacement Zones                                        |
//+------------------------------------------------------------------+
void DetectDisplacementZones()
{
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, ATF, 0, 5, rates);
   
   if(copied < 2) return;
   
   // Check latest candle for displacement
   MqlRates candle = rates[0];
   MqlRates prevCandle = rates[1];
   
   // Calculate candle metrics
   double range = candle.high - candle.low;
   if(range <= 0) return;
   
   double body = MathAbs(candle.close - candle.open);
   double bodyPercent = (body / range) * 100;
   
   // Calculate overlap with previous candle
   double overlapHigh = MathMin(candle.high, prevCandle.high);
   double overlapLow = MathMax(candle.low, prevCandle.low);
   double overlap = (overlapHigh > overlapLow) ? overlapHigh - overlapLow : 0;
   double overlapPercent = (overlap / range) * 100;
   
   // Check close near extreme (for bullish or bearish)
   double closeToHigh = candle.high - candle.close;
   double closeToLow = candle.close - candle.low;
   double closePercent;
   
   if(candle.close > candle.open) // Bullish candle
      closePercent = (closeToHigh / range) * 100;
   else // Bearish candle
      closePercent = (closeToLow / range) * 100;
   
   // Debug information
   if(DebugMode)
   {
      Print("Checking displacement - Range: ", range, 
            " ATR*Mult: ", currentATR * Displacement_ATR_Multiplier,
            " Body%: ", bodyPercent, " Req: ", Displacement_Body_Percent,
            " Overlap%: ", overlapPercent, " Max: ", Displacement_Overlap_Max,
            " Close%: ", closePercent, " Max: ", Displacement_Close_Extreme);
   }
   
   // Check displacement conditions
   bool isDisplacement = 
      (range >= currentATR * Displacement_ATR_Multiplier) &&
      (bodyPercent >= Displacement_Body_Percent) &&
      (overlapPercent <= Displacement_Overlap_Max) &&
      (closePercent <= Displacement_Close_Extreme);
   
   if(isDisplacement)
   {
      // Check if similar zone already exists (within last 5 bars)
      bool zoneExists = false;
      for(int i = 0; i < zoneCount; i++)
      {
         if(zones[i].active && MathAbs(zones[i].time - candle.time) < PeriodSeconds(ATF) * 5)
         {
            zoneExists = true;
            break;
         }
      }
      
      if(!zoneExists)
      {
         // Create displacement zone
         TradeZone zone;
         zone.time = candle.time;
         zone.high = candle.high;
         zone.low = candle.low;
         zone.mid = (zone.high + zone.low) / 2.0;
         zone.type = ZONE_DISPLACEMENT;
         zone.direction = (candle.close > candle.open) ? 1 : -1;
         zone.atr = currentATR;
         zone.expiry = zone.time + (24 * 60 * 60);  // 24 hour expiry
         zone.active = true;
         zone.triggered = false;
         zone.symbol = _Symbol;
         zone.tf = ATF;
         
         // Add to zones array
         int index = zoneCount % 100;
         zones[index] = zone;
         zoneCount++;
         
         Print("Displacement Zone Detected: ", 
               (zone.direction == 1) ? "BULLISH" : "BEARISH",
               " | High: ", zone.high, " Low: ", zone.low,
               " | Time: ", TimeToString(zone.time));
      }
   }
}

//+------------------------------------------------------------------+
//| Detect Structure Transition Zones (CHoCH)                        |
//+------------------------------------------------------------------+
void DetectStructureTransitionZones()
{
   // Check for bullish CHoCH (downtrend to uptrend)
   if(atf_prevHigh.price > 0 && atf_lastHigh.price > atf_prevHigh.price &&
      atf_prevLow.price > 0 && atf_lastLow.price > atf_prevLow.price)
   {
      // Bullish transition - look for last bearish candle before break
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int startBar = MathMax(0, atf_lastHigh.barIndex - 10);
      int copied = CopyRates(_Symbol, ATF, startBar, 20, rates);
      
      if(DebugMode) Print("Checking Bullish CHoCH - PrevHigh: ", atf_prevHigh.price, 
                        " LastHigh: ", atf_lastHigh.price);
      
      for(int i = 0; i < copied; i++)
      {
         if(rates[i].close < rates[i].open)  // Bearish candle
         {
            CreateStructureTransitionZone(rates[i], 1);
            break;
         }
      }
   }
   
   // Check for bearish CHoCH (uptrend to downtrend)
   if(atf_prevLow.price > 0 && atf_lastLow.price < atf_prevLow.price &&
      atf_prevHigh.price > 0 && atf_lastHigh.price < atf_prevHigh.price)
   {
      // Bearish transition - look for last bullish candle before break
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int startBar = MathMax(0, atf_lastLow.barIndex - 10);
      int copied = CopyRates(_Symbol, ATF, startBar, 20, rates);
      
      if(DebugMode) Print("Checking Bearish CHoCH - PrevLow: ", atf_prevLow.price, 
                        " LastLow: ", atf_lastLow.price);
      
      for(int i = 0; i < copied; i++)
      {
         if(rates[i].close > rates[i].open)  // Bullish candle
         {
            CreateStructureTransitionZone(rates[i], -1);
            break;
         }
      }
   }
}

Функция DetectDisplacementZones() анализирует последнюю свечу на анализируемом таймфрейме и с помощью строгих количественных фильтров оценивает, является ли она подлинным импульсным смещением. Она вычисляет полный диапазон свечи, долю ее тела в диапазоне, перекрытие с предыдущей свечой и близость цены закрытия к экстремуму свечи. Эти показатели сравниваются с порогами на основе ATR и процентными порогами, чтобы подтвердить сильное направленное движение с низким перекрытием и высоким моментумом. Если выполнены все условия смещения и в недавнем периоде не существует аналогичной зоны, функция создает новый объект TradeZone, задает его ценовые границы, средний уровень зоны, направление, контекст ATR, срок действия и статус активации, а затем сохраняет его в массиве зон. Это гарантирует, что в качестве потенциальных зон возврата фиксируются только статистически значимые импульсные движения.

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

//+------------------------------------------------------------------+
//| Detect Liquidity Sweep Zones                                     |
//+------------------------------------------------------------------+
void DetectLiquiditySweepZones()
{
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, ATF, 0, 5, rates);
   
   if(copied < 3) return;
   
   // Check for buy-side liquidity sweep (sweep of lows)
   if(atf_lastLow.price > 0 && rates[1].low < atf_lastLow.price)
   {
      // Check if there's a bullish reversal after the sweep
      bool bullishReversal = rates[1].close > rates[1].open || 
                            (rates[0].close > rates[0].open && rates[0].close > rates[1].high);
      
      if(bullishReversal)
      {
         if(DebugMode) Print("Buy-side Liquidity Sweep detected - Swept Low: ", atf_lastLow.price,
                           " Sweep Candle Low: ", rates[1].low);
         
         CreateLiquiditySweepZone(rates[1], 1);  // Bullish reversal
      }
   }
   
   // Check for sell-side liquidity sweep (sweep of highs)
   if(atf_lastHigh.price > 0 && rates[1].high > atf_lastHigh.price)
   {
      // Check if there's a bearish reversal after the sweep
      bool bearishReversal = rates[1].close < rates[1].open || 
                            (rates[0].close < rates[0].open && rates[0].close < rates[1].low);
      
      if(bearishReversal)
      {
         if(DebugMode) Print("Sell-side Liquidity Sweep detected - Swept High: ", atf_lastHigh.price,
                           " Sweep Candle High: ", rates[1].high);
         
         CreateLiquiditySweepZone(rates[1], -1);  // Bearish reversal
      }
   }
}

//+------------------------------------------------------------------+
//| Create Structure Transition Zone                                 |
//+------------------------------------------------------------------+
void CreateStructureTransitionZone(MqlRates &candle, int direction)
{
   // Check if similar zone already exists (within last 5 bars)
   bool zoneExists = false;
   for(int i = 0; i < zoneCount; i++)
   {
      if(zones[i].active && zones[i].type == ZONE_STRUCTURE_TRANSITION &&
         MathAbs(zones[i].time - candle.time) < PeriodSeconds(ATF) * 5)
      {
         zoneExists = true;
         break;
      }
   }
   
   if(!zoneExists)
   {
      TradeZone zone;
      zone.time = candle.time;
      zone.high = candle.high;
      zone.low = candle.low;
      zone.mid = (zone.high + zone.low) / 2.0;
      zone.type = ZONE_STRUCTURE_TRANSITION;
      zone.direction = direction;
      zone.atr = currentATR;
      zone.expiry = zone.time + (12 * 60 * 60);  // 12 hour expiry
      zone.active = true;
      zone.triggered = false;
      zone.symbol = _Symbol;
      zone.tf = ATF;
      
      // Add to zones array
      int index = zoneCount % 100;
      zones[index] = zone;
      zoneCount++;
      
      Print("Structure Transition Zone Detected: ",
            (zone.direction == 1) ? "BULLISH (CHoCH)" : "BEARISH (CHoCH)",
            " | High: ", zone.high, " Low: ", zone.low,
            " | Time: ", TimeToString(zone.time));
   }
}

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

Функция CreateStructureTransitionZone() создает зону на основе CHoCH после определения структурного пробоя. Перед созданием новой зоны функция проверяет, существует ли в недавнем временном окне аналогичная активная зона изменения структуры. Это предотвращает дублирование. Если недавняя эквивалентная зона не найдена, инициализируется новый объект TradeZone на основе свечи, предшествующей структурному пробою. Ему задаются ценовые границы, срединная цена, направление (бычье или медвежье), контекст ATR и срок жизни 12 часов. Затем зона сохраняется в массиве зон с помощью механизма циклического индекса, активируется для мониторинга и записывается в журнал терминала. Благодаря этому каждое изменение структуры четко фиксируется и управляется в памяти советника.

//+------------------------------------------------------------------+
//| Check Zone Entries                                               |
//+------------------------------------------------------------------+
void CheckZoneEntries()
{
   // Reset daily trade count if new day
   TimeToStruct(TimeCurrent(), today);
   if(lastTradeDay != today.day)
   {
      tradesToday = 0;
      lastTradeDay = today.day;
      if(DebugMode) Print("New day started. Trade counter reset.");
   }
   
   // Check if we can take more trades today
   if(MaxTradesPerDay > 0 && tradesToday >= MaxTradesPerDay)
   {
      if(tradesToday == MaxTradesPerDay)
         Print("Maximum daily trades (", MaxTradesPerDay, ") reached for today.");
      return;
   }
   
   double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   for(int i = 0; i < zoneCount; i++)
   {
      if(!zones[i].active || zones[i].triggered) continue;
      
      // Check if zone is expired
      if(TimeCurrent() > zones[i].expiry)
      {
         zones[i].active = false;
         continue;
      }
      
      // Check BUY entries (bullish zones)
      if(zones[i].direction == 1)
      {
         // Check if price is returning to zone (using Ask for buys)
         if(currentAsk >= zones[i].low && currentAsk <= zones[i].high)
         {
            // Get LTF confirmation
            if(CheckLTFConfirmation(true))
            {
               Print("BUY Signal: Price in ", ZoneTypeToString(zones[i].type), 
                     " zone | Entry: ", currentAsk, " Zone: [", zones[i].low, "-", zones[i].high, "]");
               ExecuteZoneTrade(true, zones[i], currentAsk);
               zones[i].triggered = true;
               tradesToday++;
               break;  // Take only one trade per tick
            }
         }
      }
      // Check SELL entries (bearish zones)
      else if(zones[i].direction == -1)
      {
         // Check if price is returning to zone (using Bid for sells)
         if(currentBid >= zones[i].low && currentBid <= zones[i].high)
         {
            // Get LTF confirmation
            if(CheckLTFConfirmation(false))
            {
               Print("SELL Signal: Price in ", ZoneTypeToString(zones[i].type), 
                     " zone | Entry: ", currentBid, " Zone: [", zones[i].low, "-", zones[i].high, "]");
               ExecuteZoneTrade(false, zones[i], currentBid);
               zones[i].triggered = true;
               tradesToday++;
               break;  // Take only one trade per tick
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Check LTF Confirmation                                           |
//+------------------------------------------------------------------+
bool CheckLTFConfirmation(bool forBuy)
{
   MqlRates ltfRates[];
   ArraySetAsSeries(ltfRates, true);
   int copied = CopyRates(_Symbol, LTF, 0, 5, ltfRates);
   
   if(copied < 3) return false;
   
   // For BUY confirmation
   if(forBuy)
   {
      // Simple confirmation: bullish candle closing above midpoint
      if(ltfRates[0].close > ltfRates[0].open && 
         ltfRates[0].close > ((ltfRates[0].high + ltfRates[0].low) / 2))
      {
         return true;
      }
      
      // Alternative: bullish engulfing
      if(ltfRates[0].close > ltfRates[0].open && 
         ltfRates[0].close > ltfRates[1].high && 
         ltfRates[0].open < ltfRates[1].low)
      {
         return true;
      }
   }
   // For SELL confirmation
   else
   {
      // Simple confirmation: bearish candle closing below midpoint
      if(ltfRates[0].close < ltfRates[0].open && 
         ltfRates[0].close < ((ltfRates[0].high + ltfRates[0].low) / 2))
      {
         return true;
      }
      
      // Alternative: bearish engulfing
      if(ltfRates[0].close < ltfRates[0].open && 
         ltfRates[0].close < ltfRates[1].low && 
         ltfRates[0].open > ltfRates[1].high)
      {
         return true;
      }
   }
   
   return false;
}

Здесь реализуется основной механизм исполнения, определяющий, когда обнаруженная зона Market Memory Zone становится основанием для реальной сделки. Функция управляет дневными лимитами сделок: сбрасывает счетчик в начале нового торгового дня и предотвращает дальнейшие входы после достижения максимального числа сделок. Затем функция получает текущие цены Bid и Ask и перебирает все активные зоны, по которым еще не было срабатывания. Для каждой зоны сначала проверяются условия истечения срока действия, чтобы устаревшие зоны были деактивированы. Если цена возвращается в границы действующей зоны, советник определяет, является ли сетап бычьим или медвежьим, и перед исполнением сделки запрашивает подтверждение на младшем таймфрейме. После открытия сделки зона отмечается как сработавшая, чтобы предотвратить повторные входы, а счетчик дневных сделок увеличивается. Это обеспечивает дисциплинированный контроль исполнения.

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

//+------------------------------------------------------------------+
//| Execute Zone Trade                                               |
//+------------------------------------------------------------------+
void ExecuteZoneTrade(bool isBuy, TradeZone &zone, double entryPrice)
{
   // Check if position already exists for this symbol
   if(PositionSelect(_Symbol))
   {
      if(!AllowConsecutiveTrades)
      {
         Print("Position already exists and AllowConsecutiveTrades is false, skipping trade");
         return;
      }
   }
   
   ENUM_ORDER_TYPE orderType = isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   
   // Calculate stop loss
   double stopLoss = CalculateZoneStopLoss(isBuy, zone, entryPrice);
   
   // Calculate take profit
   double takeProfit = CalculateZoneTakeProfit(isBuy, zone, entryPrice, stopLoss);
   
   // Calculate lot size
   double volume = CalculateLotSize(entryPrice, stopLoss);
   
   if(volume <= 0)
   {
      Print("Invalid lot size calculated: ", volume, ". Trade cancelled.");
      return;
   }
   
   // Execute trade
   string comment = StringFormat("%s_%s_%s",
      (isBuy ? "BUY" : "SELL"),
      ZoneTypeToString(zone.type),
      TimeToString(TimeCurrent(), TIME_MINUTES));
   
   bool success = trade.PositionOpen(_Symbol, orderType, volume, 
                                     entryPrice, stopLoss, takeProfit, comment);
   
   if(success)
   {
      ulong ticket = trade.ResultOrder();
      Print("Trade Executed Successfully: ", comment,
            " | Ticket: ", ticket,
            " | Entry: ", entryPrice,
            " | SL: ", stopLoss,
            " | TP: ", takeProfit,
            " | Lots: ", volume);
      
      // Store trade info
      if(activeTradeCount < ArraySize(activeTrades))
      {
         activeTrades[activeTradeCount].entryTime = TimeCurrent();
         activeTrades[activeTradeCount].entryPrice = entryPrice;
         activeTrades[activeTradeCount].stopLoss = stopLoss;
         activeTrades[activeTradeCount].takeProfit = takeProfit;
         activeTrades[activeTradeCount].direction = isBuy ? 1 : -1;
         activeTrades[activeTradeCount].zone = zone;
         activeTrades[activeTradeCount].ticket = ticket;
         activeTradeCount++;
      }
   }
   else
   {
      Print("Trade failed: Error Code=", trade.ResultRetcode(),
            " Description=", trade.ResultRetcodeDescription());
   }
}

//+------------------------------------------------------------------+
//| Calculate Zone Stop Loss                                         |
//+------------------------------------------------------------------+
double CalculateZoneStopLoss(bool isBuy, TradeZone &zone, double entryPrice)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(UseDynamicSL)
   {
      // Zone-based stop loss with buffer
      double buffer = 50 * point;  // 50 pip buffer
      if(isBuy)
      {
         double sl = zone.low - buffer;
         // Ensure SL is below entry
         if(sl >= entryPrice)
            sl = entryPrice - (100 * point);
         return NormalizeDouble(sl, _Digits);
      }
      else
      {
         double sl = zone.high + buffer;
         // Ensure SL is above entry
         if(sl <= entryPrice)
            sl = entryPrice + (100 * point);
         return NormalizeDouble(sl, _Digits);
      }
   }
   else
   {
      // Fixed pip stop loss
      double slDistance = StopLoss_Pips * 10 * point;
      if(isBuy)
         return NormalizeDouble(entryPrice - slDistance, _Digits);
      else
         return NormalizeDouble(entryPrice + slDistance, _Digits);
   }
}

//+------------------------------------------------------------------+
//| Calculate Zone Take Profit                                       |
//+------------------------------------------------------------------+
double CalculateZoneTakeProfit(bool isBuy, TradeZone &zone, double entry, double sl)
{
   double risk = MathAbs(entry - sl);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(UseStructureTP)
   {
      if(isBuy)
      {
         // Take profit at next structure high
         double target = entry + (risk * RiskRewardRatio);
         
         if(atf_lastHigh.price > entry)
            target = atf_lastHigh.price;
         else if(htf_lastHigh.price > entry)
            target = htf_lastHigh.price;
         
         // Ensure minimum distance
         double minDistance = 50 * point;
         if(target - entry < minDistance)
            target = entry + (risk * RiskRewardRatio);
         
         return NormalizeDouble(target, _Digits);
      }
      else
      {
         // Take profit at next structure low
         double target = entry - (risk * RiskRewardRatio);
         
         if(atf_lastLow.price < entry)
            target = atf_lastLow.price;
         else if(htf_lastLow.price < entry)
            target = htf_lastLow.price;
         
         // Ensure minimum distance
         double minDistance = 50 * point;
         if(entry - target < minDistance)
            target = entry - (risk * RiskRewardRatio);
         
         return NormalizeDouble(target, _Digits);
      }
   }
   else
   {
      // Fixed risk/reward ratio
      if(isBuy)
         return NormalizeDouble(entry + (risk * RiskRewardRatio), _Digits);
      else
         return NormalizeDouble(entry - (risk * RiskRewardRatio), _Digits);
   }
}

Функция ExecuteZoneTrade() управляет полным процессом открытия сделки после подтверждения валидного сигнала на вход из зоны. Сначала функция проверяет, существует ли уже позиция по символу, и учитывает настройку AllowConsecutiveTrades, чтобы предотвратить нежелательное наслаивание сделок. Затем функция определяет тип ордера – buy или sell, рассчитывает стоп-лосс и тейк-профит по специальной логике на основе зон и вычисляет подходящий размер лота с учетом параметров риска. Если рассчитанный объем допустим, функция открывает сделку с помощью PositionOpen() и добавляет структурированный комментарий с направлением, типом зоны и временной меткой. После успешного исполнения сведения о сделке сохраняются в массиве activeTrades для последующего управления и отслеживания. Если отправка ордера завершается неудачей, система записывает в лог код возврата брокера и его описание для отладки.

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

//+------------------------------------------------------------------+
//| Update Visualization                                             |
//+------------------------------------------------------------------+
void UpdateVisualization()
{
   ObjectsDeleteAll(0, "ZONE_");
   ObjectsDeleteAll(0, "TRADE_");
   
   // Draw zones
   int activeZoneCount = 0;
   for(int i = 0; i < zoneCount; i++)
   {
      if(zones[i].active)
      {
         DrawZone(zones[i], i);
         activeZoneCount++;
      }
   }
   
   // Draw structure levels
   if(ShowLabels)
   {
      if(atf_lastHigh.price > 0)
         CreateHorizontalLine("ATF_High", atf_lastHigh.price, clrRed, STYLE_DASH);
      
      if(atf_lastLow.price > 0)
         CreateHorizontalLine("ATF_Low", atf_lastLow.price, clrBlue, STYLE_DASH);
      
      if(htf_lastHigh.price > 0)
         CreateHorizontalLine("HTF_High", htf_lastHigh.price, clrDarkRed, STYLE_DOT);
      
      if(htf_lastLow.price > 0)
         CreateHorizontalLine("HTF_Low", htf_lastLow.price, clrDarkBlue, STYLE_DOT);
   }
   
   // Show zone count in corner
   if(ShowLabels)
   {
      string labelText = "Active Zones: " + IntegerToString(activeZoneCount);
      CreateLabel("ZoneCount", labelText, 10, 180, clrWhite);
   }
}

//+------------------------------------------------------------------+
//| Draw Zone                                                        |
//+------------------------------------------------------------------+
void DrawZone(TradeZone &zone, int index)
{
   string name = "ZONE_" + IntegerToString(index);
   
   // Calculate zone width (4 bars forward)
   datetime endTime = zone.time + (PeriodSeconds(zone.tf) * 4);
   
   // Draw rectangle
   if(ObjectCreate(0, name, OBJ_RECTANGLE, 0, zone.time, zone.high, endTime, zone.low))
   {
      // Set color based on zone type and direction
      color zoneColor;
      int zoneStyle = STYLE_SOLID;
      
      switch(zone.type)
      {
         case ZONE_DISPLACEMENT:
            zoneColor = (zone.direction == 1) ? clrLimeGreen : clrIndianRed;
            zoneStyle = STYLE_SOLID;
            break;
         case ZONE_STRUCTURE_TRANSITION:
            zoneColor = (zone.direction == 1) ? clrDodgerBlue : clrOrangeRed;
            zoneStyle = STYLE_DASH;
            break;
         case ZONE_LIQUIDITY_SWEEP:
            zoneColor = (zone.direction == 1) ? clrGold : clrMagenta;
            zoneStyle = STYLE_DOT;
            break;
         default:
            zoneColor = clrGray;
      }
      
      ObjectSetInteger(0, name, OBJPROP_COLOR, zoneColor);
      ObjectSetInteger(0, name, OBJPROP_STYLE, zoneStyle);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_FILL, true);
      ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);
      
      // Add label
      if(ShowLabels)
      {
         string labelName = name + "_LABEL";
         if(ObjectCreate(0, labelName, OBJ_TEXT, 0, zone.time, zone.high))
         {
            string typeText;
            switch(zone.type)
            {
               case ZONE_DISPLACEMENT: typeText = "DISP"; break;
               case ZONE_STRUCTURE_TRANSITION: typeText = "CHoCH"; break;
               case ZONE_LIQUIDITY_SWEEP: typeText = "LIQ"; break;
               default: typeText = "UNK";
            }
            
            string directionText = (zone.direction == 1) ? "BULL" : "BEAR";
            ObjectSetString(0, labelName, OBJPROP_TEXT, typeText + " " + directionText);
            ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
            ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
            ObjectSetInteger(0, labelName, OBJPROP_BACK, true);
         }
      }
      
      if(DebugMode && ShowZones)
         Print("Drawing zone: ", name, " Type: ", ZoneTypeToString(zone.type), 
               " Dir: ", zone.direction, " High: ", zone.high, " Low: ", zone.low);
   }
}

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

Функция DrawZone() выполняет детальную отрисовку каждой зоны. Она создает прямоугольник, начинающийся во времени формирования зоны и ограниченный ее ценовыми границами, продлевая его на несколько баров вперед для визуализации области влияния. Каждому типу зоны – смещения, изменения структуры (CHoCH) или снятия ликвидности – присваиваются отдельные цвет и стиль линии, а бычье и медвежье направления визуально различаются. Опциональные текстовые метки обозначают тип зоны и направление, например DISP BULL или CHoCH BEAR, что позволяет быстро интерпретировать график.


Результаты тестирования на исторических данных

Тестирование на исторических данных проводилось на таймфрейме H1 в течение тестового периода с 1 июня 2025 года по 14 августа 2025 года с настройками по умолчанию:

Equity Curve

Back Test Results


Заключение

Подводя итог, концепция Market Memory Zones была преобразована из визуального аналитического инструмента в полностью автоматизированный торговый механизм путем непосредственного кодирования ее структурной логики в советнике. Реализовано систематическое обнаружение свечей смещения, изменений структуры (CHoCH) и снятия ликвидности. Затем эти зоны сохраняются в контролируемом циклическом буфере, обеспечивающем долгосрочную стабильность при тестировании на исторических данных и исполнении сделок в реальном времени. Каждая зона теперь содержит контекстные данные: направление, условия ATR, срок действия и структурные ориентиры. Это позволяет советнику отслеживать поведение цены в реальном времени. Автоматизированная логика входа, подтверждение на младшем таймфрейме, динамическое размещение стоп-лосса, установка тейк-профита на основе структуры и расчет размера позиции с учетом риска завершают переход от субъективной интерпретации к исполнению по правилам.

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

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

Прикрепленные файлы |
Auto_MMZ.mq5 (41.48 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Изучение стандартной библиотеки MQL5 (Часть 13): Применение библиотеки математических решателей в торговле Изучение стандартной библиотеки MQL5 (Часть 13): Применение библиотеки математических решателей в торговле
Мы представляем полный рабочий процесс для адаптивной фильтрации в MQL5 с использованием решателя CNlEq, подобного алгоритму Левенберга — Марквардта. Советник подбирает параметры модели VAMAC — две экспоненциальные скользящие средние (EWMA) с масштабированием на основе ATR — путем передачи остатков и Якобиана через цикл механизма обратного обмена с решателем CNlEq, с возможностью использования численных или аналитических производных. Код, инструкции по настройке и тесты по паре GBPUSD на таймфрейме H1 показывают, как заменить статические пороги динамической переоценкой на закрытии каждого бара.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Интеграция MQL5 с пакетами обработки данных (Часть 7): Построение многоагентных сред для межсимвольного взаимодействия Интеграция MQL5 с пакетами обработки данных (Часть 7): Построение многоагентных сред для межсимвольного взаимодействия
В статье представлена полная интеграция Python и MQL5 для многоагентной торговли: получение данных из MT5, расчет индикаторов, решения отдельных агентов и взвешенный консенсус, формирующий единое торговое решение. Сигналы сохраняются в JSON-файле, отдаются через Flask и используются советником MQL5 для исполнения сделок с расчетом объема позиции и уровней SL/TP на основе ATR. Маршруты Flask обеспечивают безопасное управление жизненным циклом и мониторинг состояния.