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

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

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

Приступим
//+------------------------------------------------------------------+ //| 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 года с настройками по умолчанию:


Заключение
Подводя итог, концепция Market Memory Zones была преобразована из визуального аналитического инструмента в полностью автоматизированный торговый механизм путем непосредственного кодирования ее структурной логики в советнике. Реализовано систематическое обнаружение свечей смещения, изменений структуры (CHoCH) и снятия ликвидности. Затем эти зоны сохраняются в контролируемом циклическом буфере, обеспечивающем долгосрочную стабильность при тестировании на исторических данных и исполнении сделок в реальном времени. Каждая зона теперь содержит контекстные данные: направление, условия ATR, срок действия и структурные ориентиры. Это позволяет советнику отслеживать поведение цены в реальном времени. Автоматизированная логика входа, подтверждение на младшем таймфрейме, динамическое размещение стоп-лосса, установка тейк-профита на основе структуры и расчет размера позиции с учетом риска завершают переход от субъективной интерпретации к исполнению по правилам.
Наконец, автоматизация Market Memory Zones устраняет эмоциональную предвзятость, сохраняя структурную логику, лежащую в основе стратегии. Система непрерывно отслеживает зоны, к которым цена с высокой вероятностью может вернуться, подтверждает реакции с помощью анализа нескольких таймфреймов и исполняет сделки по предопределенным правилам управления рисками. Благодаря этому весь процесс становится последовательным, масштабируемым и пригодным для тестирования. Преобразуя ценовую память в структурированную программируемую логику, трейдеры получают ясность, воспроизводимость и возможность оптимизировать результаты на различных инструментах и таймфреймах.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21255
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Изучение стандартной библиотеки MQL5 (Часть 13): Применение библиотеки математических решателей в торговле
Интеграция MQL5 с пакетами обработки данных (Часть 7): Построение многоагентных сред для межсимвольного взаимодействия
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования