English
preview
От новичка до эксперта: Адаптивное управление рисками в стратегиях на основе ликвидности

От новичка до эксперта: Адаптивное управление рисками в стратегиях на основе ликвидности

MetaTrader 5 — Примеры |
104 1
Clemence Benjamin
Clemence Benjamin

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

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

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

По сути, проблема не в стратегии, а в отсутствии системного подхода к управлению неопределенностью:

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

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

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

В этой статье мы строим такую систему из четырех взаимосвязанных слоев:

  1. фильтрация перед открытием позиции — оценка качества зоны до принятия риска по силе импульса, структуре базы и согласованности со старшим таймфреймом;
  2. адаптивное размещение стопов — переход от фиксированных стопов к размещению с учетом рыночной ситуации, снятия ликвидности и инвалидации структуры;
  3. динамический расчет размера позиции — корректировка рисковой нагрузки с учетом качества сетапа и снижение риска при торговле по более слабым или перевернутым зонам;
  4. управление после входа — активное сопровождение сделок с частичным закрытием позиций, переводом в безубыток и контролем по времени.

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

К концу статьи вы создадите адаптивный советник на языке MQL5, способный обрабатывать перевороты зон, размещать стратегически обоснованные отложенные ордера и учитывать надежные правила управления рисками непосредственно при их исполнении.

Содержание

  1. Введение
  2. Концепция
  3. Реализация
  4. Оптимизация и пограничные случаи
  5. Тестирование и результаты
  6. Заключение
  7. Ключевые выводы
  8. Приложения



Введение

Установив, что главное слабое место торговли на основе ликвидности — не выявление зон, а управление риском, мы можем перейти к формализации его контроля, чтобы сделать этот процесс согласованным и проверяемым.

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

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

Для этого управление рисками должно соответствовать ряду основных инженерных требований:

Требование Назначение
Воспроизводимость Обеспечить одинаковые торговые решения при одинаковых входных данных для надежного тестирования и проверки
Согласованность Применять одни и те же правила управления рисками независимо от рыночных условий или недавних результатов
Адаптивность Динамически регулировать рисковую нагрузку с учетом волатильности, структуры зон и убедительности рыночного контекста без ручного вмешательства
Масштабируемость Поддерживать одновременное управление несколькими зонами и символами без увеличения сложности системы
Эффективность исполнения Выполнять все расчеты в реальном времени без дополнительных задержек и ошибок исполнения

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

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



Концепция

Для практической реализации этого подхода мы представляем AdaptiveLiquidityLifecycleTrader — советник, в котором управление рисками встроено во весь жизненный цикл сделки на основе ликвидности.

Вместо выделения управления рисками в отдельный слой система оценивает риск и учитывает его на каждом этапе исполнения:

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

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

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



Реализация

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

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

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

При работе советника каждая сделка проходит одну и ту же последовательность этапов:

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

Благодаря этому риск учитывается не после принятия торгового решения, а непосредственно в процессе его принятия. Ни одна сделка не допускается за пределами заданных ограничений.

Ниже мы рассмотрим реализацию каждого компонента и покажем, как код обеспечивает соблюдение правил. Основное внимание уделяется тому, как каждая функция обеспечивает единообразное применение правил управления рисками во всех сделках.

Шаг 1 — структура советника и входные параметры

Каждый советник на языке MQL5 начинается с директив, задающих свойства, и входных параметров. Входные параметры сгруппированы по разделам: обнаружение зон, внешний вид, управление рисками, входы, разворотные паттерны, переворот зон и работа советника. В советнике предусмотрено несколько входных параметров, связанных с риском: RiskPercent, StopBufferFactor, MinZoneHeightPoints, MaxZoneHeightPoints, UseATRNormalization, ATRPeriod и FlipRiskPercent. Они позволяют трейдеру полностью контролировать величину принимаемого риска и условия допуска сделки. Например, RiskPercent задает базовый риск на сделку, а FlipRiskPercent — пониженный риск при торговле по уже пробитым (перевернутым) зонам с учетом того, что такие сетапы статистически менее надежны. Нормализация по ATR помогает отфильтровать зоны, слишком малые по сравнению с недавней волатильностью, и допускать торговлю только по значимым уровням.
//+------------------------------------------------------------------+
//|                             AdaptiveLiquidityLifecycleTrader.mq5 |
//|                                Copyright 2026, Clemence Benjamin |
//|                                         https://www.mql5.com     |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.21"
#property strict

#include <Trade/Trade.mqh>

CTrade trade;

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
//--- Zone detection (higher timeframe)
input ENUM_TIMEFRAMES ZoneTimeframe   = PERIOD_H1;       // Timeframe for zone detection
input int                LookbackBars    = 500;            // Number of HTF bars to scan
input double             RatioMultiplier = 3.0;            // Impulse-to-base ratio
input int                ExtendBars      = 50;             // How many HTF bars to extend zone to the right

//--- Visual (optional)
input color             DemandZoneColor = clrLimeGreen;
input color             SupplyZoneColor = clrTomato;
input uchar             ZoneOpacity     = 40;             // 0-255 transparency
input bool              FillRectangles  = true;

//--- Risk management
input double             RiskPercent         = 1.0;      // % of account balance per trade
input double             StopBufferFactor    = 0.2;      // Stop buffer = zoneHeight * StopBufferFactor
input double             MinZoneHeightPoints = 10;       // Minimum zone height in points
input double             MaxZoneHeightPoints = 100;      // Maximum zone height in points
input bool              UseATRNormalization = true;    // Filter weak zones (zoneHeight < 0.5*ATR)
input int                ATRPeriod           = 14;

//--- Entry settings
input bool              AllowBuy            = true;
input bool              AllowSell           = true;
input int                MaxSpreadPoints     = 30;        // Max allowed spread
input double             RiskReward          = 2.0;        // TP = SL * RiskReward
input int                PendingExpiryHours  = 48;        // Pending order lifetime (hours)

//--- Reversal patterns (for flipped zones)
input bool              UseEngulfing        = true;
input bool              UsePinBar           = true;
input bool              UseInsideBreak      = true;

//--- Zone flipping
input bool              EnableZoneFlipping  = true;
input double             ViolationMultiplier = 1.5;        // Impulsive bar must be >= zoneHeight * ViolationMultiplier

//--- Position sizing for flipped zones
input double             FlipRiskPercent     = 0.5;        // Reduced risk for flipped entries

//--- EA behavior
input ulong              MagicNumber         = 777333;

Шаг 2 — структура зоны и глобальные переменные

Структура LiquidityZone хранит все данные отдельной зоны, включая ее ценовые границы (максимум и минимум), время создания и срок действия зоны. Флаг triggered предотвращает повторное размещение ордеров по одной и той же зоне — это критически важная защита при повторной оценке зон советником на новых барах. Флаг flipped указывает, что зона уже пробита (т.е. цена прошла через нее импульсным движением) и ее роль изменилась; торговля по таким зонам ведется с пониженным риском. Динамический массив zones[] содержит все активные зоны и упрощает их перебор для проверки срока действия, переворота и поиска возможностей для входа. Кроме того, мы объявляем хэндлы ATR (используемого для фильтрации слабых зон) и переменные для отслеживания времени последнего обновления старшего таймфрейма и текущего бара, чтобы обнаружение зон и управление ими выполнялись только при необходимости.
//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
struct LiquidityZone
  {
   double            high;            // zone top
   double            low;             // zone bottom
   datetime          start_time;      // bar time when formed
   datetime          expiry_time;     // start + ExtendBars * HTF period
   bool              demand;          // true = demand, false = supply
   bool              triggered;       // true if pending order placed
   bool              flipped;         // has been flipped
   ulong             orderTicket;     // ticket of pending order (0 if none)
  };

LiquidityZone zones[];                // dynamic array of active zones
datetime lastHtfUpdateTime = 0;
datetime lastBarTime = 0;

double point;                         // point value (adjusted for 5/3 digits)

int atrHandle = INVALID_HANDLE;       // handle for ATR indicator

Шаг 3 — служебные функции — преобразование периода, отрисовка зон, отмена ордеров

Эти вспомогательные функции сохраняют код основной логики понятным и удобным для сопровождения. GetPeriodSeconds() преобразует значение перечисления таймфреймов языка MQL5 в длительность периода в секундах, что необходимо для единообразного расчета срока действия зон на графиках с разными периодами. DeleteAllZones() удаляет все отрисованные прямоугольники при удалении советника, не допуская загромождения графика. DrawZone() создает прямоугольник с заливкой и заданной пользователем непрозрачностью и размещает его позади ценового графика, чтобы свечи оставались видимыми. Имя прямоугольника включает время начала и срок действия зоны, что упрощает его последующую идентификацию и удаление. CancelZoneOrder() безопасно удаляет связанный с зоной отложенный ордер и сбрасывает ее поле orderTicket, сохраняя целостность данных в массиве зон.
//+------------------------------------------------------------------+
//| Returns period in seconds                                        |
//+------------------------------------------------------------------+
int GetPeriodSeconds(ENUM_TIMEFRAMES tf)
  {
   switch(tf)
     {
      case PERIOD_M1: return(60);
      case PERIOD_M5: return(300);
      case PERIOD_M15: return(900);
      case PERIOD_M30: return(1800);
      case PERIOD_H1: return(3600);
      case PERIOD_H4: return(14400);
      case PERIOD_D1: return(86400);
      case PERIOD_W1: return(604800);
      case PERIOD_MN1: return(2592000);
      default: return(3600);
     }
  }

//+------------------------------------------------------------------+
//| Deletes all drawn zone rectangles                                |
//+------------------------------------------------------------------+
void DeleteAllZones()
  {
   for(int i = ObjectsTotal(0, 0, -1) - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i);
      if(StringFind(name, "LiqZone_") == 0)
         ObjectDelete(0, name);
     }
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Draws a liquidity zone rectangle on chart                        |
//+------------------------------------------------------------------+
void DrawZone(datetime start_time, datetime expiry_time, double price_top, double price_bottom, bool isDemand)
  {
   string obj_name = "LiqZone_" + IntegerToString(start_time) + "_" + IntegerToString(expiry_time);
   if(ObjectFind(0, obj_name) >= 0)
      ObjectDelete(0, obj_name);

   color zone_color = isDemand ? DemandZoneColor : SupplyZoneColor;

   if(ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0,
                   start_time, price_top, expiry_time, price_bottom))
     {
      ObjectSetInteger(0, obj_name, OBJPROP_COLOR, zone_color);
      ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, obj_name, OBJPROP_FILL, FillRectangles);
      ObjectSetInteger(0, obj_name, OBJPROP_BACK, true);
      ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, false);

      if(FillRectangles)
        {
         color fill_clr = (color)ColorToARGB(zone_color, ZoneOpacity);
         ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, fill_clr);
        }
     }
  }

//+------------------------------------------------------------------+
//| Cancels pending order associated with a zone                     |
//+------------------------------------------------------------------+
void CancelZoneOrder(int idx)
  {
   if(idx < 0 || idx >= ArraySize(zones))
      return;
   if(zones[idx].orderTicket != 0)
     {
      trade.OrderDelete(zones[idx].orderTicket);
      zones[idx].orderTicket = 0;
     }
  }

Шаг 4 — размещение ордеров с учетом риска, PlaceZonePendingOrders()

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

Затем функция проверяет, разрешена ли торговля в выбранном направлении (AllowBuy/AllowSell) и приемлем ли текущий спред. Только после этих проверок функция рассчитывает расстояние до стоп-лосса (с помощью GetStopDistance(), которая добавляет отступ за границей зоны) и размер позиции с помощью ComputeLotSize(). Важно, что выбранный процент риска — RiskPercent для свежих зон или FlipRiskPercent для перевернутых — передается в процедуру расчета размера позиции в лотах. Это обеспечивает сниженную рисковую нагрузку при торговле по перевернутым зонам. На последнем этапе размещается лимитный ордер со сроком действия, а его тикет сохраняется в структуре зоны.

//+------------------------------------------------------------------+
//| Places pending order at zone boundary                            |
//+------------------------------------------------------------------+
void PlaceZonePendingOrders(int idx)
  {
   if(idx < 0 || idx >= ArraySize(zones))
      return;
   if(zones[idx].triggered)
      return;

   double zoneHeight = zones[idx].high - zones[idx].low;

//--- Filter by min/max zone height
   double minHeight = MinZoneHeightPoints * point;
   double maxHeight = MaxZoneHeightPoints * point;
   if(zoneHeight < minHeight || zoneHeight > maxHeight)
      return;

//--- ATR filter
   if(UseATRNormalization)
     {
      double atr = GetATR(1);
      if(atr > 0 && zoneHeight < 0.5 * atr)
         return;
     }

   bool isBuy = zones[idx].demand;

   if(isBuy && !AllowBuy)
      return;
   if(!isBuy && !AllowSell)
      return;

   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double entryPrice = isBuy ? zones[idx].low : zones[idx].high;
   entryPrice = NormalizeDouble(entryPrice, _Digits);
   double stopLoss = NormalizeDouble(GetStopDistance(zones[idx], isBuy), _Digits);
   double stopDist = MathAbs(entryPrice - stopLoss);
   double tp = isBuy ? entryPrice + stopDist * RiskReward : entryPrice - stopDist * RiskReward;
   tp = NormalizeDouble(tp, _Digits);
   datetime expiry = TimeCurrent() + PendingExpiryHours * 3600;

//--- Ensure limit order is on correct side of current price
   if(isBuy && entryPrice >= ask)
      return;
   if(!isBuy && entryPrice <= bid)
      return;

   double lot = ComputeLotSize(stopDist, zones[idx].flipped ? FlipRiskPercent : RiskPercent);
   if(lot <= 0)
      return;

   trade.SetExpertMagicNumber(MagicNumber);
   bool success = false;
   if(isBuy)
      success = trade.BuyLimit(lot, entryPrice, _Symbol, stopLoss, tp, ORDER_TIME_SPECIFIED, expiry, "LiqZone_Pending");
   else
      success = trade.SellLimit(lot, entryPrice, _Symbol, stopLoss, tp, ORDER_TIME_SPECIFIED, expiry, "LiqZone_Pending");

   if(success)
     {
      zones[idx].triggered = true;
      ulong ticket = trade.ResultOrder();
      if(ticket != 0)
         zones[idx].orderTicket = ticket;
      else
         zones[idx].orderTicket = 1;
     }
  }

Шаг 5 — обнаружение зон на старшем таймфрейме, UpdateZones()

Эта функция вызывается при появлении нового бара на старшем таймфрейме. Она копирует данные OHLC и времени для таймфрейма, заданного ZoneTimeframe, а затем устанавливает индексацию массивов как у временных рядов (новые значения в начале), чтобы упростить работу с индексами. Основной алгоритм ищет паттерн "база → импульс": два последовательных бычьих (или медвежьих) бара, у которых диапазон второго бара не меньше диапазона первого, умноженного на RatioMultiplier. Первый бар образует базу (зону), а второй — импульсное движение, подтверждающее соответствие области условиям зоны предложения или спроса. При обнаружении такого паттерна создается новая LiquidityZone, которая добавляется в динамический массив. Зона отображается на графике и сразу передается в PlaceZonePendingOrders() для размещения ордера с проверкой риска. Использование старшего таймфрейма (например, H1 или H4) позволяет выявлять зоны, отражающие значительную активность институциональных участников и менее подверженные рыночному шуму.

//+------------------------------------------------------------------+
//| Scans higher timeframe for new supply/demand zones               |
//+------------------------------------------------------------------+
void UpdateZones()
  {
//--- Copy HTF data
   double htf_open[], htf_close[], htf_high[], htf_low[];
   datetime htf_time[];
   int htf_bars = CopyOpen(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_open);
   if(htf_bars <= 0) return;
   CopyClose(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_close);
   CopyHigh(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_high);
   CopyLow(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_low);
   CopyTime(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_time);

   ArraySetAsSeries(htf_open, true);
   ArraySetAsSeries(htf_close, true);
   ArraySetAsSeries(htf_high, true);
   ArraySetAsSeries(htf_low, true);
   ArraySetAsSeries(htf_time, true);

   int htfPeriodSec = GetPeriodSeconds(ZoneTimeframe);

   for(int i = 2; i < htf_bars - 2; i++)
     {
      int base_idx = i + 2;
      int impulse_idx = i + 1;
      if(base_idx >= htf_bars) continue;

      double base_range = htf_high[base_idx] - htf_low[base_idx];
      double impulse_range = htf_high[impulse_idx] - htf_low[impulse_idx];
      if(base_range <= 0) continue;

      // Demand zone
      if(htf_open[base_idx] < htf_close[base_idx] &&
         htf_open[impulse_idx] < htf_close[impulse_idx] &&
         impulse_range >= base_range * RatioMultiplier)
        {
         // Avoid duplicates
         bool exists = false;
         for(int j = 0; j < ArraySize(zones); j++)
            if(zones[j].start_time == htf_time[base_idx]) { exists = true; break; }

         if(!exists)
           {
            LiquidityZone z;
            z.high = htf_high[base_idx];
            z.low  = htf_low[base_idx];
            z.start_time = htf_time[base_idx];
            z.expiry_time = z.start_time + ExtendBars * htfPeriodSec;
            z.demand = true;
            z.triggered = false;
            z.flipped = false;
            z.orderTicket = 0;

            int size = ArraySize(zones);
            ArrayResize(zones, size + 1);
            zones[size] = z;

            DrawZone(z.start_time, z.expiry_time, z.high, z.low, true);
            PlaceZonePendingOrders(size);
           }
        }

      // Supply zone (similar, with demand = false)
      if(htf_open[base_idx] > htf_close[base_idx] &&
         htf_open[impulse_idx] > htf_close[impulse_idx] &&
         impulse_range >= base_range * RatioMultiplier)
        {
         // ... (same structure, z.demand = false)
        }
     }
  }

Кратко отметим: полный код зоны предложения идентичен коду зоны спроса, но используется demand = false. Полный код советника, приведенный в конце, включает оба варианта.

Шаг 6 — истечение срока действия зон и очистка, RemoveExpiredZones()

Эта функция вызывается на каждом тике (но выполняется только при появлении нового бара благодаря проверке IsNewBar()). Она перебирает все активные зоны и сравнивает текущее время со значением expiry_time каждой зоны. Если срок действия зоны истек, функция отменяет связанный с ней отложенный ордер, удаляет прямоугольник с графика и зону из динамического массива. Такая очистка необходима, чтобы не накапливать лишние данные в состоянии советника и не допускать случайного открытия сделок по старым зонам или загромождения графика. Механизм срока действия зоны также ограничивает время существования каждой зоны, учитывая, что уровни спроса и предложения постепенно теряют актуальность.
//+------------------------------------------------------------------+
//| Removes zones that have expired and their pending orders         |
//+------------------------------------------------------------------+
void RemoveExpiredZones()
  {
   datetime current_time = TimeCurrent();
   for(int i = ArraySize(zones) - 1; i >= 0; i--)
     {
      if(current_time > zones[i].expiry_time)
        {
         CancelZoneOrder(i);
         string obj_name = "LiqZone_" + IntegerToString(zones[i].start_time) + "_" + IntegerToString(zones[i].expiry_time);
         ObjectDelete(0, obj_name);
         for(int j = i; j < ArraySize(zones) - 1; j++)
            zones[j] = zones[j+1];
         ArrayResize(zones, ArraySize(zones) - 1);
        }
     }
  }

Шаг 7 — переворот зон и снижение риска, CheckFlipConditions() и FlipZone()

Когда цена пробивает зону импульсным движением (т.е. диапазон последнего закрытого бара ≥ zoneHeight * ViolationMultiplier и бар закрывается за противоположной границей зоны), роль зоны меняется на противоположную. Этот механизм адаптируется к динамике рынка: пробитая зона предложения, не удержавшая цену, может стать зоной спроса после резкого разворота. FlipZone() обновляет свойство demand зоны, заново задает срок действия зоны (продлевая его) и снова вызывает PlaceZonePendingOrders() — на этот раз с использованием FlipRiskPercent, поскольку зона теперь помечена как перевернутая. Снижая процент риска для перевернутых зон, советник учитывает повышенную неопределенность таких сетапов и необходимость осторожной торговли по ним. Функция CheckFlipConditions() выполняется на каждом новом баре и проверяет все активные зоны на наличие таких пробоев.
//+------------------------------------------------------------------+
//| Flips a zone's role (demand<->supply) after violation            |
//+------------------------------------------------------------------+
void FlipZone(int idx, bool newDemand, datetime violation_time)
  {
   if(idx < 0 || idx >= ArraySize(zones)) return;
   CancelZoneOrder(idx);
   string obj_name = "LiqZone_" + IntegerToString(zones[idx].start_time) + "_" + IntegerToString(zones[idx].expiry_time);
   ObjectDelete(0, obj_name);

   zones[idx].demand = newDemand;
   zones[idx].expiry_time = violation_time + ExtendBars * GetPeriodSeconds(ZoneTimeframe);
   zones[idx].triggered = false;
   zones[idx].flipped = true;
   zones[idx].orderTicket = 0;

   DrawZone(zones[idx].start_time, zones[idx].expiry_time, zones[idx].high, zones[idx].low, newDemand);
   PlaceZonePendingOrders(idx);
  }

//+------------------------------------------------------------------+
//| Checks for impulsive price action that violates a zone           |
//+------------------------------------------------------------------+
void CheckFlipConditions()
  {
   if(!EnableZoneFlipping) return;

   double high1 = iHigh(Symbol(), PERIOD_CURRENT, 1);
   double low1  = iLow(Symbol(), PERIOD_CURRENT, 1);
   double close1 = iClose(Symbol(), PERIOD_CURRENT, 1);
   double open1 = iOpen(Symbol(), PERIOD_CURRENT, 1);
   double range1 = high1 - low1;

   for(int i = 0; i < ArraySize(zones); i++)
     {
      if(TimeCurrent() > zones[i].expiry_time) continue;
      double zoneHeight = zones[i].high - zones[i].low;
      if(zoneHeight <= 0) continue;

      if(!zones[i].demand && close1 > zones[i].high &&
         close1 > open1 && range1 >= zoneHeight * ViolationMultiplier)
        { FlipZone(i, true, TimeCurrent()); break; }
      else if(zones[i].demand && close1 < zones[i].low &&
               close1 < open1 && range1 >= zoneHeight * ViolationMultiplier)
        { FlipZone(i, false, TimeCurrent()); break; }
     }
  }

Шаг 8 — вход по перевернутым зонам, PlaceFlippedZoneEntry()

По свежим зонам вход выполняется лимитными ордерами на границе зоны, а по перевернутым — только рыночными ордерами после возвращения цены в зону и появления разворотного паттерна. Этот консервативный подход предполагает ожидание подтверждения того, что перевернутая зона действительно служит новой поддержкой или сопротивлением. Функция проверяет, находится ли текущая цена закрытия внутри зоны, а затем с помощью отдельных функций распознавания определяет наличие разворотного паттерна (например, бычьего поглощения, пин-бара или пробоя внутреннего бара). После подтверждения она рассчитывает стоп-лосс (за противоположной границей зоны с дополнительным отступом), тейк-профит (по соотношению риска и вознаграждения) и размер позиции с помощью ComputeLotSize() с параметром FlipRiskPercent. Благодаря этому размер позиций даже в сделках по перевернутым зонам с пониженным риском соответствует общим правилам управления рисками счета.
//+------------------------------------------------------------------+
//| Places market entry for flipped zone on pullback confirmation    |
//+------------------------------------------------------------------+
void PlaceFlippedZoneEntry(int idx)
  {
   if(idx < 0 || idx >= ArraySize(zones)) return;
   if(!zones[idx].flipped || zones[idx].triggered) return;

   bool isBuy = zones[idx].demand;
   double currentClose = iClose(_Symbol, PERIOD_CURRENT, 1);

   if(isBuy && currentClose >= zones[idx].low && currentClose <= zones[idx].high)
     {
      if(DemandReversal(1))
        {
         double stopLoss = zones[idx].low - (zones[idx].high - zones[idx].low) * StopBufferFactor;
         double takeProfit = currentClose + (currentClose - stopLoss) * RiskReward;
         double stopDistance = MathAbs(currentClose - stopLoss);
         double lot = ComputeLotSize(stopDistance, FlipRiskPercent);
         if(lot > 0)
           {
            trade.SetExpertMagicNumber(MagicNumber);
            trade.Buy(lot, _Symbol, 0, stopLoss, takeProfit, "LiqZone_Flipped");
            zones[idx].triggered = true;
           }
        }
     }
   else if(!isBuy && currentClose >= zones[idx].low && currentClose <= zones[idx].high)
     {
      if(SupplyReversal(1))
        {
         // ... similarly for sell
        }
     }
  }

Шаг 9 — вспомогательные функции обнаружения разворотных паттернов и расчета размера позиции

В советнике предусмотрены отдельные функции распознавания паттернов (BullishEngulfing, BearishPinBar и т.д.), использование которых можно включать и выключать с помощью входных параметров. Эти функции анализируют одну или несколько последних свечей, выявляя разворотные сигналы с высокой вероятностью отработки, которые повышают шансы на успешную сделку при входе от перевернутой зоны. Ключевая функция ComputeLotSize() использует баланс счета, выбранный процент риска (RiskPercent или FlipRiskPercent), расстояние до стоп-лосса в пунктах и стоимость тика символа, чтобы рассчитать точный размер позиции в лотах с риском, равным заданному проценту от баланса счета. В этом суть расчета размера позиции на основе риска: каждая сделка независимо от расстояния до стоп-лосса сохраняет одинаковый относительный уровень риска по отношению к средствам счета. Кроме того, функция учитывает минимальный и максимальный допустимый размер позиции в лотах и нормализует итоговое значение до двух знаков после запятой для совместимости с большинством брокеров.
//+------------------------------------------------------------------+
//| Calculates lot size based on risk percent and stop distance      |
//+------------------------------------------------------------------+
double ComputeLotSize(double stopDistance, double riskPercent)
  {
   double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmount = accountBalance * (riskPercent / 100.0);
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double pointsPerStop = stopDistance / tickSize;
   double riskPerLot = pointsPerStop * tickValue;
   if(riskPerLot <= 0) return(0);
   double lot = riskAmount / riskPerLot;
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   lot = MathMax(minLot, MathMin(maxLot, lot));
   return(NormalizeDouble(lot, 2));
  }

Шаг 10 — основной рабочий цикл советника, OnTick()

Функция обработки событий тика вызывается при каждом изменении цены, но выполняет основную логику только при появлении нового бара (IsNewBar()), что исключает лишние вычисления. Сначала проверяется, не превышает ли текущий спред заданный пользователем предел (MaxSpreadPoints), чтобы сделки не совершались в неблагоприятных рыночных условиях. Затем, если появился новый бар старшего таймфрейма, вызывается UpdateZones() для поиска новых зон спроса и предложения. Затем удаляются зоны с истекшим сроком действия и проверяются условия их переворота на текущем баре. Наконец, выполняется перебор всех активных зон в поисках перевернутых, по которым вход еще не выполнялся, и попытка открыть позицию рыночным ордером с помощью PlaceFlippedZoneEntry(). Такой четкий порядок выполнения разделяет задачи (обнаружение зон, их сопровождение, обнаружение переворотов и вход) и делает управление рисками в целом прозрачным и понятным.
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(!IsNewBar()) return;
   if(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > MaxSpreadPoints) return;

   datetime latestHtfTime = iTime(Symbol(), ZoneTimeframe, 0);
   if(latestHtfTime != lastHtfUpdateTime)
     {
      UpdateZones();
      lastHtfUpdateTime = latestHtfTime;
     }

   RemoveExpiredZones();
   CheckFlipConditions();

   for(int i = 0; i < ArraySize(zones); i++)
      if(zones[i].flipped && !zones[i].triggered)
         PlaceFlippedZoneEntry(i);
  }



Оптимизация и пограничные случаи

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

Граничный случай Защитный механизм
Высота зоны слишком мала или слишком велика Зона отфильтровывается до размещения ордера
Высота зоны < 0,5 × ATR Зона отклоняется, если включен UseATRNormalization
Спред превышает лимит Ордер не размещается
Истекает срок действия отложенного ордера Ордер автоматически отменяется, зона удаляется
Пробой с недостаточной силой импульса Условие переворота не выполняется
Ошибка расчета расстояния до стоп-лосса Расчет размера позиции возвращает 0, ордер не размещается

Эффективность обеспечивается следующим:

  • OnTick выполняет обработку только при появлении новых баров (проверка IsNewBar());
  • зоны старшего таймфрейма обновляются только при появлении нового бара на нем;
  • зоны с истекшим сроком действия удаляются за один обратный проход по массиву;
  • повторное размещение ордеров исключено благодаря флагу triggered.

Предусмотрены следующие защитные механизмы:

  • магическое число отличает сделки советника от сделок, открытых вручную;
  • отложенные ордера имеют ограниченный срок действия;
  • торговля по перевернутым зонам ведется с пониженным риском;
  • размер позиции в лотах ограничивается допустимыми минимумом и максимумом.

Практическое применение

Этапы настройки:

Скомпилируйте советник и добавьте его на любой график M5-M15.

Настройте входные параметры:

  • задайте ZoneTimeframe (рекомендуется H1);
  • настройте RiskPercent (рекомендуется 1-2%);
  • задайте FlipRiskPercent (рекомендуется 0,5-1%);
  • настройте фильтры высоты зоны с учетом волатильности торгового инструмента.
  • Включите автоторговлю.
Ожидаемое поведение:
  • зоны старшего таймфрейма отображаются в виде полупрозрачных прямоугольников;
  • лимитные ордера на покупку размещаются на нижних границах зон спроса, а лимитные ордера на продажу — на верхних границах зон предложения;
  • для каждого ордера задаются стоп-лосс с отступом ниже или выше зоны и тейк-профит на расстоянии RiskReward × расстояние до стоп-лосса;
  • срок действия зон истекает через ExtendBars баров старшего таймфрейма;
  • при импульсном пробое зона меняет роль и перерисовывается;
  • по перевернутым зонам вход выполняется рыночными ордерами на откатах при наличии разворотных паттернов и с пониженным риском.
Какие решения становятся обоснованнее?
Устранены следующие проблемы:
  • подбор размера позиции наугад — советник рассчитывает его точно;
  • раздумья о том, стоит ли торговать по сомнительной зоне, — фильтры автоматически отклоняют зоны низкого качества;
  • чрезмерная торговля по перевернутым зонам — риск автоматически снижается вдвое.
Какие процессы автоматизируются или становятся надежнее?
  • обнаружение зон на старшем таймфрейме;
  • расчет размера позиции с учетом риска;
  • размещение стоп-лосса и тейк-профита;
  • контроль срока действия зон и их удаление;
  • обнаружение переворота зоны и повторный вход с пониженным риском.


Тестирование и результаты

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

Рис. 1. Визуализация работы советника AdaptiveLiquidityLifecycleTrader со встроенным управлением рисками в Тестере стратегий

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

Все параметры сделки — включая уровень входа, расположение стоп-лосса и размер позиции — рассчитываются по заданным правилам на основе настроенных входных параметров. Это обеспечивает постоянную рисковую нагрузку по каждой сделке независимо от различий в размере зоны или рыночной волатильности.

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



Заключение

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

Теперь вы можете:

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

Управление рисками — не отдельный этап после поиска возможности для сделки; оно должно быть заложено в саму основу торговой логики. В этом советнике риск учитывается в каждом решении: при фильтрации зон по высоте и ATR, размещении стоп-лоссов с отступом, расчете размера позиции на основе риска по счету и снижении рисковой нагрузки при торговле по перевернутым зонам.

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

В торговле на основе ликвидности зоны определяют, где торговать, а управление рисками — стоит ли входить в сделку и какой риск принимать.


Ключевые выводы

Ключевой вывод Описание
Фильтр по высоте зоны Параметры `MinZoneHeightPoints` и `MaxZoneHeightPoints` позволяют отсеивать слишком малые зоны (шум) и слишком большие зоны (чрезмерный риск), повышая качество сделок.
Нормализация по ATR Сравнение высоты зоны с текущим ATR позволяет открывать сделки только по зонам, значимым с учетом недавней волатильности, и адаптироваться к меняющимся рыночным условиям.
Коэффициент отступа стоп-лосса Размещение стоп-лоссов с дополнительным отступом (в процентах от высоты зоны) оставляет цене пространство для движения и снижает вероятность закрытия позиции по стопу при обычном ретесте.
Расчет размера позиции на основе риска Расчет размера позиции в лотах так, чтобы риск по каждой сделке составлял фиксированный процент баланса счета, обеспечивает единый подход к риску при разных расстояниях до стоп-лосса.
Фильтр спреда Допуск к торговле только при спреде в пределах установленного лимита защищает от входа в периоды низкой ликвидности и высоких издержек.
Снижение риска при торговле по перевернутым зонам Пониженный процент риска при торговле по перевернутым зонам учитывает повышенную неопределенность после пробоя и помогает сохранять капитал, когда шансы менее благоприятны.
Управление сроками действия Ограничение срока действия отложенных ордеров и удаление просроченных зон не позволяют накапливаться лишним данным и ордерам, потерявшим актуальность.
Отдельные функции распознавания паттернов Выделение проверок разворотных паттернов в отдельные функции (например, BullishEngulfing) и их объединение в DemandReversal упрощает изменение и расширение кода.

Приложения

Имя файла Тип Версия Описание
AdaptiveLiquidityLifecycleTrader.mq5 Советник 1.21 Полноценный советник со встроенным управлением рисками для торговли по зонам ликвидности, включая обнаружение зон, динамический расчет размера позиции, обработку переворотов зон и фильтры волатильности

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Prince Nyarko
Prince Nyarko | 9 апр. 2026 в 19:40

Сэр, пожалуйста, я хотел бы узнать больше о торговле, так как я новичок и хочу приобрести некоторые навыки

Торговые инструменты MQL5 (Часть 33): Создание системы документации с расширенным форматированием для программ на языке MQL5 Торговые инструменты MQL5 (Часть 33): Создание системы документации с расширенным форматированием для программ на языке MQL5
Мы расширяем возможности мастера настройки из части 9, чтобы создать для MetaTrader 5 систему документации, встроенную в график и использующую холст для отрисовки. Панель имеет вкладки и поддерживает прокрутку, форматирование фрагментов текста, изображения и интерактивные элементы управления; для её отрисовки используется сглаживание методом суперсэмплинга. В результате получился движок документации для многократного использования, который можно встроить в любую программу на языке MQL5 для отображения автономной документации прямо на графике.
От новичка до эксперта: Выявление переворотов зон ликвидности на языке MQL5 От новичка до эксперта: Выявление переворотов зон ликвидности на языке MQL5
В этой статье представлен индикатор на языке MQL5, который обнаруживает перевороты зон ликвидности и управляет ими. Он выявляет зоны предложения и спроса на старших таймфреймах с помощью паттерна "база → импульс", применяет формализованные пороговые значения для пробоя и силы импульса и автоматически переворачивает зоны при изменении структуры. В результате получается динамическая карта поддержки и сопротивления, которая сокращает необходимость ручной перерисовки и дает понятный и практически применимый контекст для оценки сигналов и ретестов.
Алгоритм оптимизации, вдохновлённый охотой лисы —  Fox-inspired Optimization Algorithm (FOX) Алгоритм оптимизации, вдохновлённый охотой лисы — Fox-inspired Optimization Algorithm (FOX)
Разбираем FOX (Fox-inspired Optimization Algorithm): реализуем алгоритм в MQL5, детально проверяем ветви "прыжка" и "блуждания" и показываем, как притяжение к нулю и фиксированный масштаб шага мешают поиску в торговых диапазонах. Пошагово измеряем вклад каждого механизма на тестовом стенде и объясняем расхождения формул и кода. В конце — готовый к использованию класс C_AO_FOX и исправленная версия с шагом в долях диапазона.
От новичка до эксперта: Усиление стратегий ликвидности за счет структурного подтверждения на нескольких таймфреймах От новичка до эксперта: Усиление стратегий ликвидности за счет структурного подтверждения на нескольких таймфреймах
Согласованность структур ликвидности на старшем таймфрейме и разворотных паттернов на младшем может существенно повлиять как на вероятность, так и на направление следующего движения цены. Сочетая структурные зоны ликвидности на старших таймфреймах с точными подтверждениями разворота на младших, трейдеры могут точнее выбирать момент входа и повышать общее качество сделок. В этой статье показано, как усовершенствовать торговые стратегии на основе ликвидности с помощью структурного подтверждения на старшем таймфрейме и эффективно реализовать этот подход на языке MQL5.