English Deutsch 日本語
preview
Свинговые экстремумы и откаты в MQL5 (Часть 1): Разработка мультитаймфреймового индикатора

Свинговые экстремумы и откаты в MQL5 (Часть 1): Разработка мультитаймфреймового индикатора

MetaTrader 5Примеры |
32 4
Hlomohang John Borotho
Hlomohang John Borotho

Содержание

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


Введение

Ранее мы разработали индикатор свинговых экстремумов и откатов (Swing Extremes and the Pullback Indicator) путем систематического выявления свинговых максимумов и минимумов в ценовом движении младшего таймфрейма (LTF). Подход был сосредоточен на выявлении и разметке краткосрочных структурных сдвигов — более высоких максимумов, более низких минимумов, откатов и пауз в импульсном движении — для определения возможных разворотов до того, как они станут очевидными на старших таймфреймах. Сочетание логики обнаружения свингов с правилами подтверждения откатов позволило создать систему, преобразующую необработанную волатильность младшего таймфрейма (LTF) в структурированную и понятную модель поведения рынка.

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


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

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

Extreme Bearish Move

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

Extreme Bullish Move


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

//+------------------------------------------------------------------+
//|                                          Auto Swing Extremes.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

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

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_TIMEFRAMES HTF = PERIOD_H1;      // Higher Timeframe
input ENUM_TIMEFRAMES LTF = PERIOD_M5;      // Lower Timeframe
input int             SwingBars = 3;        // Bars for swing detection
input double          RiskPercent = 1.0;    // Risk per trade (%)
input int             Stop_Loss = 2000;     // StopLoss
input bool            Visualize = true;     // Show swing points & structure

//+------------------------------------------------------------------+
//| Swing Point Structure                                            |
//+------------------------------------------------------------------+
struct SwingPoint
{
   datetime          time;
   double            price;
   int               type;     // 1 = High, -1 = Low
   int               barIndex;
   
   void Reset()
   {
      time = 0;
      price = 0.0;
      type = 0;
      barIndex = 0;
   }
};

Для начала инициализируется торговая среда: подключается файл <Trade/Trade.mqh> и создается объект CTrade, предоставляющий структурированный способ программного исполнения сделок и управления ими. Затем определяются ключевые входные параметры: старший таймфрейм (HTF) и младший таймфрейм (LTF) для мультитаймфреймового анализа структуры, количество SwingBars для обнаружения подтвержденных свинговых максимумов и минимумов, процент риска по счету на сделку, фиксированное значение стоп-лосса и переключатель визуализации, позволяющий при необходимости отображать структурные элементы на графике. Эти параметры делают систему настраиваемой, позволяя стратегии адаптироваться к различным рыночным условиям, символам и предпочтениям трейдеров без изменения основной логики.

Затем объявляется структура SwingPoint для формального представления рыночной структуры в коде. Эта структура хранит временную метку свинга, ценовой уровень, тип (1 для свингового максимума и -1 для свингового минимума) и индекс соответствующего бара, объединяя все необходимые данные для отслеживания структурных точек разворота. Функция Reset() позволяет безопасно очистить и повторно использовать свинговую точку, сохраняя целостность данных при обновлениях. Организация информации о свингах в структурированном объекте закладывает основу для автоматического обнаружения рыночной структуры.

//+------------------------------------------------------------------+
//| Market Structure States                                          |
//+------------------------------------------------------------------+
enum STRUCTURE_STATE
{
   STRUCT_NEUTRAL,
   STRUCT_BULLISH,
   STRUCT_BEARISH
};

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
SwingPoint htf_lastHigh, htf_prevHigh;
SwingPoint htf_lastLow, htf_prevLow;
SwingPoint ltf_lastHigh, ltf_prevHigh;
SwingPoint ltf_lastLow, ltf_prevLow;

STRUCTURE_STATE htfBias = STRUCT_NEUTRAL;
STRUCTURE_STATE ltfStructure = STRUCT_NEUTRAL;

datetime lastHtfUpdate = 0;
datetime lastLtfUpdate = 0;

// Track last traded swing to avoid multiple entries on same swing
datetime lastBuySwingTime = 0;
datetime lastSellSwingTime = 0;
double lastBuySwingPrice = 0;
double lastSellSwingPrice = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Validate timeframe combination
   if(PeriodSeconds(HTF) <= PeriodSeconds(LTF))
   {
      Alert("HTF must be higher than LTF!");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   // Initialize swing points
   htf_lastHigh.Reset(); htf_prevHigh.Reset();
   htf_lastLow.Reset(); htf_prevLow.Reset();
   ltf_lastHigh.Reset(); ltf_prevHigh.Reset();
   ltf_lastLow.Reset(); ltf_prevLow.Reset();
   
   // Initialize trade tracking
   lastBuySwingTime = 0;
   lastSellSwingTime = 0;
   lastBuySwingPrice = 0;
   lastSellSwingPrice = 0;
   
   // Initial structure detection
   UpdateHTFStructure();
   UpdateLTFStructure();
   
   return INIT_SUCCEEDED;
}

В этом разделе определяется основа структурной логики, задающая интерпретацию направления рынка стратегией. Перечисление STRUCTURE_STATE определяет три возможных состояния рынка — нейтральное, бычье и медвежье. Это позволяет в любой момент времени четко классифицировать направленность старшего таймфрейма (HTF) и структуру младшего таймфрейма (LTF). Глобальные переменные SwingPoint объявляются для отслеживания последних и предыдущих максимумов и минимумов на старшем таймфрейме (HTF) и младшем таймфрейме (LTF). Это позволяет сравнивать последовательные структурные точки, например более высокие максимумы и более низкие минимумы. Дополнительные переменные состояния, такие как htfBias и ltfStructure, хранят активное структурное направление. Временные метки lastHtfUpdate и lastLtfUpdate обеспечивают обновление структуры только при формировании новых баров, поддерживая эффективность и предотвращая избыточные пересчеты.

В глобальной области видимости также определены защитные механизмы управления торговлей, предотвращающие повторное открытие сделок с одного и того же свингового уровня. Благодаря сохранению значений lastBuySwingTime, lastSellSwingTime и соответствующих им цен система избегает исполнения нескольких сделок по одинаковым структурным сигналам. В функции OnInit() программа сначала проверяет, действительно ли выбранный старший таймфрейм (HTF) выше младшего таймфрейма (LTF), обеспечивая корректную мультитаймфреймовую иерархию. Затем программа сбрасывает все структуры свингов и переменные отслеживания сделок, обеспечивая корректное начальное состояние. Затем программа выполняет начальное сканирование структуры с помощью функций UpdateHTFStructure() и UpdateLTFStructure(), синхронизируя систему с текущими рыночными условиями до начала реальной торговли.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Clean up objects
   ObjectsDeleteAll(0, "MS_");
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   static datetime lastLtfBar = 0;
   datetime currentLtfBar = iTime(_Symbol, LTF, 0);
   
   // Process on new LTF bar
   if(currentLtfBar != lastLtfBar)
   {
      lastLtfBar = currentLtfBar;
      
      // Update HTF bias if needed (on new HTF bar)
      if(TimeCurrent() - lastHtfUpdate >= PeriodSeconds(HTF))
      {
         UpdateHTFStructure();
         lastHtfUpdate = TimeCurrent();
      }
      
      // Store previous LTF structure for comparison
      STRUCTURE_STATE prevLtfStructure = ltfStructure;
      
      // Update LTF structure
      UpdateLTFStructure();
      
      // Check for structure breaks
      CheckStructureBreak();
      
      // Try to execute trades if structure changed or conditions met
      if(ltfStructure != prevLtfStructure || 
         (ltf_lastHigh.price > 0 && ltf_lastLow.price > 0))
      {
         TryExecuteTrade();
      }
            
      // Update visualization
      if(Visualize) UpdateVisualization();
   }
}

Здесь код управляет очисткой ресурсов и ходом выполнения советника в реальном времени. В функции OnDeinit() удаляются все объекты графика с префиксом "MS_", чтобы при отсоединении советника очистить графические элементы, связанные со структурой. Затем выполняется перерисовка графика для визуальной согласованности. В функции OnTick() система выполняет логику только при формировании нового бара младшего таймфрейма (LTF), сравнивая временные метки. Это предотвращает избыточные вычисления на каждом тике. При формировании нового бара система при необходимости обновляет направленность старшего таймфрейма (HTF), обновляет структуру младшего таймфрейма (LTF), проверяет пробои структуры и сравнивает предыдущее и текущее состояния структуры. Если происходит структурный сдвиг или имеются подтвержденные данные о свинговых экстремумах, система пытается исполнить сделку с помощью функции TryExecuteTrade(). Если визуализация включена, система обновляет графические объекты, отображая последнее состояние структуры и обеспечивая синхронизацию логики с визуальной обратной связью.

//+------------------------------------------------------------------+
//| Update Higher Timeframe Structure                                |
//+------------------------------------------------------------------+
void UpdateHTFStructure()
{
   // Get HTF data
   MqlRates htfRates[];
   ArraySetAsSeries(htfRates, true);
   CopyRates(_Symbol, HTF, 0, 100, htfRates);
   
   if(ArraySize(htfRates) < SwingBars * 2 + 1) return;
   
   // Detect swing points on HTF
   for(int i = SwingBars; i < ArraySize(htfRates) - SwingBars; i++)
   {
      bool isHigh = true;
      bool isLow = true;
      
      // Check if current bar is swing high
      for(int j = 1; j <= SwingBars; j++)
      {
         if(htfRates[i].high <= htfRates[i-j].high || 
            htfRates[i].high <= htfRates[i+j].high)
         {
            isHigh = false;
         }
         
         if(htfRates[i].low >= htfRates[i-j].low || 
            htfRates[i].low >= htfRates[i+j].low)
         {
            isLow = false;
         }
      }
      
      // Update swing highs
      if(isHigh)
      {
         htf_prevHigh = htf_lastHigh;
         htf_lastHigh.time = htfRates[i].time;
         htf_lastHigh.price = htfRates[i].high;
         htf_lastHigh.type = 1;
         htf_lastHigh.barIndex = i;
      }
      
      // Update swing lows
      if(isLow)
      {
         htf_prevLow = htf_lastLow;
         htf_lastLow.time = htfRates[i].time;
         htf_lastLow.price = htfRates[i].low;
         htf_lastLow.type = -1;
         htf_lastLow.barIndex = i;
      }
   }
   
   // Update HTF bias
   htfBias = DetectStructure(htf_lastHigh, htf_prevHigh, htf_lastLow, htf_prevLow);
}

//+------------------------------------------------------------------+
//| Update Lower Timeframe Structure                                 |
//+------------------------------------------------------------------+
void UpdateLTFStructure()
{
   // Get LTF data
   MqlRates ltfRates[];
   ArraySetAsSeries(ltfRates, true);
   CopyRates(_Symbol, LTF, 0, 100, ltfRates);
   
   if(ArraySize(ltfRates) < SwingBars * 2 + 1) return;
   
   // Detect swing points on LTF
   for(int i = SwingBars; i < ArraySize(ltfRates) - SwingBars; i++)
   {
      bool isHigh = true;
      bool isLow = true;
      
      // Check if current bar is swing high
      for(int j = 1; j <= SwingBars; j++)
      {
         if(ltfRates[i].high <= ltfRates[i-j].high || 
            ltfRates[i].high <= ltfRates[i+j].high)
         {
            isHigh = false;
         }
         
         if(ltfRates[i].low >= ltfRates[i-j].low || 
            ltfRates[i].low >= ltfRates[i+j].low)
         {
            isLow = false;
         }
      }
      
      // Update swing highs
      if(isHigh)
      {
         ltf_prevHigh = ltf_lastHigh;
         ltf_lastHigh.time = ltfRates[i].time;
         ltf_lastHigh.price = ltfRates[i].high;
         ltf_lastHigh.type = 1;
         ltf_lastHigh.barIndex = i;
      }
      
      // Update swing lows
      if(isLow)
      {
         ltf_prevLow = ltf_lastLow;
         ltf_lastLow.time = ltfRates[i].time;
         ltf_lastLow.price = ltfRates[i].low;
         ltf_lastLow.type = -1;
         ltf_lastLow.barIndex = i;
      }
   }
   
   // Update LTF structure
   ltfStructure = DetectStructure(ltf_lastHigh, ltf_prevHigh, ltf_lastLow, ltf_prevLow);
}

Функция UpdateHTFStructure() анализирует ценовые данные старшего таймфрейма для выявления подтвержденных свинговых максимумов и свинговых минимумов на основе заданного параметра SwingBars. Она получает последние 100 свечей старшего таймфрейма (HTF), проверяет наличие достаточных данных, а затем анализирует каждый подходящий бар, чтобы определить, является ли он структурным экстремумом. Бар классифицируется как свинговый максимум, если его максимум превышает максимумы окружающих баров в заданном диапазоне, и как свинговый минимум, если его минимум ниже минимумов соседних баров. Когда подтверждается новый свинг, предыдущий свинг сохраняется, а последний обновляется с указанием времени, цены, типа и индекса. После обработки всех кандидатов функция определяет общую направленность старшего таймфрейма — бычью, медвежью или нейтральную — сравнивая последние и предыдущие свинговые максимумы и минимумы с помощью DetectStructure().

Функция UpdateLTFStructure() применяет ту же логику на младшем таймфрейме, используя тот же процесс выявления свингов для более детализированных ценовых данных. Она оценивает каждый бар в пределах допустимого диапазона, подтверждает структурные максимумы и минимумы симметричными сравнениями и соответствующим образом обновляет сохраненные переменные свингов LTF. Сохраняя последние и предыдущие свинговые точки, система может определить, формирует ли рынок более высокие максимумы, более низкие минимумы или переходит к структурному сдвигу. На заключительном этапе с помощью DetectStructure() задается обновленное состояние структуры младшего таймфрейма (LTF). Это обеспечивает работу торговой логики и обнаружения разворотов на основе постоянно обновляемой и объективно определенной рыночной структуры обоих таймфреймов.

//+------------------------------------------------------------------+
//| Detect Market Structure                                          |
//+------------------------------------------------------------------+
STRUCTURE_STATE DetectStructure(SwingPoint &lastHigh, SwingPoint &prevHigh,
                                SwingPoint &lastLow, SwingPoint &prevLow)
{
   // Need at least two highs and two lows
   if(lastHigh.price == 0 || prevHigh.price == 0 || 
      lastLow.price == 0 || prevLow.price == 0)
      return STRUCT_NEUTRAL;
   
   bool isHigherHigh = lastHigh.price > prevHigh.price;
   bool isHigherLow = lastLow.price > prevLow.price;
   bool isLowerLow = lastLow.price < prevLow.price;
   bool isLowerHigh = lastHigh.price < prevHigh.price;
   
   if(isHigherHigh && isHigherLow)
      return STRUCT_BULLISH;
   
   if(isLowerLow && isLowerHigh)
      return STRUCT_BEARISH;
   
   return STRUCT_NEUTRAL;
}
Функция DetectStructure() определяет текущую рыночную структуру, сравнивая последние и предыдущие свинговые максимумы и минимумы. Сначала функция проверяет наличие достаточных данных по свингам: необходимы как минимум два подтвержденных свинговых максимума и два подтвержденных свинговых минимума. В противном случае она возвращает нейтральное состояние. Затем функция оценивает развитие структуры: если последний свинговый максимум выше предыдущего и при этом последний свинговый минимум выше предыдущего, рынок классифицируется как бычий. И напротив, если последний свинговый минимум ниже предыдущего и при этом последний свинговый максимум ниже предыдущего, структура считается медвежьей. Если ни одно условие не выполняется полностью, функция по умолчанию устанавливает нейтральное состояние, указывающее на консолидацию или переход структуры.
//+------------------------------------------------------------------+
//| New Sell Condition                                               |
//+------------------------------------------------------------------+
bool CheckSellCondition()
{
   // Need valid LTF swing points
   if(ltf_lastHigh.price == 0 || ltf_lastLow.price == 0)
      return false;
   
   // Get current price
   double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   
   // Check if price is above both last high and last low
   bool priceAboveHigh = currentPrice > ltf_lastHigh.price;
   bool priceAboveLow = currentPrice > ltf_lastLow.price;
   
   // Check if this is a new swing (not already traded)
   bool isNewSwing = (ltf_lastHigh.time != lastSellSwingTime) || 
                     (ltf_lastHigh.price != lastSellSwingPrice);
   
   return (priceAboveHigh && priceAboveLow && isNewSwing);
}

//+------------------------------------------------------------------+
//| New Buy Condition                                                |
//+------------------------------------------------------------------+
bool CheckBuyCondition()
{
   // Need valid LTF swing points
   if(ltf_lastLow.price == 0)
      return false;
   
   // Get current price
   double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   // Check if price is below last low
   bool priceBelowLow = currentPrice < ltf_lastLow.price;
   
   // Check if this is a new swing (not already traded)
   bool isNewSwing = (ltf_lastLow.time != lastBuySwingTime) || 
                     (ltf_lastLow.price != lastBuySwingPrice);
   
   return (priceBelowLow && isNewSwing);
}

//+------------------------------------------------------------------+
//| Execute trade                                                    |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol)
{
   // Get current price
   double price = (tradeType == ORDER_TYPE_BUY)
                  ? SymbolInfoDouble(symbol, SYMBOL_ASK)
                  : SymbolInfoDouble(symbol, SYMBOL_BID);
   price = NormalizeDouble(price, _Digits);
   
   // Calculate stop loss
   double sl = CalculateSL(tradeType == ORDER_TYPE_BUY);
   sl = NormalizeDouble(sl, _Digits);
   
   // Calculate take profit based on your requirements
   double tp = 0;
   if(tradeType == ORDER_TYPE_BUY)
   {
      // For Buy: TP at next structure level (last high)
      tp = (ltf_lastHigh.price > 0) ? ltf_lastHigh.price : price + (MathAbs(price - sl) * 2);
   }
   else if(tradeType == ORDER_TYPE_SELL) // ORDER_TYPE_SELL
   {
      // For Sell: TP at last low (as requested)
      tp = (ltf_lastLow.price > 0) ? ltf_lastLow.price : price - (MathAbs(price - sl) * 2);
   }
   tp = NormalizeDouble(tp, _Digits);
   
   // Ensure TP is valid (not too close to entry)
   double minDistance = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE) * 10;
   if(MathAbs(price - tp) < minDistance)
   {
      Print("TP too close to entry, adjusting...");
      if(tradeType == ORDER_TYPE_BUY)
         tp = price + (MathAbs(price - sl) * 2);
      else
         tp = price - (MathAbs(price - sl) * 2);
   }
   
   // Calculate lot size based on risk
   double volume = CalculateLotSize(price, sl);
   
   // Ensure volume is within valid range
   double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   double volume_min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double volume_max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   
   // Normalize volume to step size
   volume = floor(volume / volume_step) * volume_step;
   volume = MathMax(volume, volume_min);
   volume = MathMin(volume, volume_max);
   
   // Prepare trade comment
   string comment = StringFormat("MS_%s_%s",
      (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL",
      TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
   
   // Execute trade using CTrade
   bool success = trade.PositionOpen(symbol, tradeType, volume, price, sl, tp, comment);
   
   if(success)
   {
      // Update swing tracking to avoid multiple entries
      if(tradeType == ORDER_TYPE_BUY)
      {
         lastBuySwingTime = ltf_lastLow.time;
         lastBuySwingPrice = ltf_lastLow.price;
      }
      else
      {
         lastSellSwingTime = ltf_lastHigh.time;
         lastSellSwingPrice = ltf_lastHigh.price;
      }
      
      Print(StringFormat("Trade Opened: %s | Price: %.5f | SL: %.5f | TP: %.5f | Lots: %.2f",
         (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL",
         price, sl, tp, volume));
   }
   else
   {
      Print("Trade failed: ", trade.ResultRetcodeDescription());
   }
}

//+------------------------------------------------------------------+
//| Try Execute Trade                                                |
//+------------------------------------------------------------------+
void TryExecuteTrade()
{
   // Check if position already exists
   if(PositionSelect(_Symbol)) 
   {
      Print("Position already exists, waiting for closure...");
      return;
   }
   
   // For Sell positions: Price above both last high and last low
   if(CheckSellCondition())
   {
      // Optional: Check HTF bias filter
      if(htfBias == STRUCT_BEARISH || htfBias == STRUCT_NEUTRAL)
      {
         Print("SELL condition met: Price above LTF high and low");
         OpenSell();
         return;
      }
   }
   
   // For Buy positions: Price below last low
   if(CheckBuyCondition())
   {
      // Optional: Check HTF bias filter
      if(htfBias == STRUCT_BULLISH || htfBias == STRUCT_NEUTRAL)
      {
         Print("BUY condition met: Price below LTF low");
         OpenBuy();
         return;
      }
   }
}

Функции CheckSellCondition() и CheckBuyCondition() определяют структурные условия открытия сделок на основе смещения младшего таймфрейма (LTF). Для сетапа на продажу система сначала проверяет наличие подтвержденных данных по свингам, а затем определяет, находится ли текущая цена Bid выше последнего свингового максимума и свингового минимума LTF. Это указывает на бычье перерастяжение за пределы недавней структуры. Кроме того, система проверяет, не использовался ли этот свинг уже для сделки, сравнивая сохраненные значения его времени и цены. Это предотвращает повторные входы. Аналогично условие покупки подтверждает, что цена опустилась ниже последнего свингового минимума LTF, сигнализируя о медвежьем истощении. Кроме того, перед возвратом true проверяется актуальность свинга. В совокупности эти условия преобразуют структурные экстремумы в объективные сигналы входа, основанные на правилах, и обеспечивают принцип "одна сделка на один свинг".

Функция ExecuteTrade() отвечает за формирование сделки и управление рисками. Она определяет корректную цену входа (Ask для покупок и Bid для продаж), рассчитывает стоп-лосс отдельной функцией и динамически задает тейк-профит на основе структуры: последний свинговый максимум LTF используется как TP для сделок на покупку, а последний свинговый минимум LTF — как TP для сделок на продажу. Если структурные уровни TP недоступны или находятся слишком близко к цене входа, система по умолчанию использует резервный расчет на основе соотношения риска и вознаграждения. Размер позиции рассчитывается в соответствии с процентом риска по счету, а затем нормализуется с учетом требований брокера к минимальному, максимальному объему и шагу объема. Сделка исполняется с помощью объекта CTrade; при успешном исполнении сохраняются данные соответствующего свинга, чтобы предотвратить повторные входы на одном структурном уровне.

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

//+------------------------------------------------------------------+
//| Check Structure Break                                            |
//+------------------------------------------------------------------+
void CheckStructureBreak()
{
   MqlRates current[];
   ArraySetAsSeries(current, true);
   CopyRates(_Symbol, LTF, 0, 1, current);
   
   if(ArraySize(current) < 1) return;
   
   // Bullish structure break
   if(ltfStructure == STRUCT_BULLISH && current[0].close < ltf_lastLow.price)
   {
      ResetLTFStructure();
   }
   
   // Bearish structure break
   if(ltfStructure == STRUCT_BEARISH && current[0].close > ltf_lastHigh.price)
   {
      ResetLTFStructure();
   }
}

//+------------------------------------------------------------------+
//| Reset LTF Structure                                              |
//+------------------------------------------------------------------+
void ResetLTFStructure()
{
   ltf_lastHigh.Reset();
   ltf_prevHigh.Reset();
   ltf_lastLow.Reset();
   ltf_prevLow.Reset();
   ltfStructure = STRUCT_NEUTRAL;
   
   // Also reset trade tracking
   lastBuySwingTime = 0;
   lastSellSwingTime = 0;
   lastBuySwingPrice = 0;
   lastSellSwingPrice = 0;
}

//+------------------------------------------------------------------+
//| Update Visualization                                             |
//+------------------------------------------------------------------+
void UpdateVisualization()
{
   ObjectsDeleteAll(0, "MS_");
   
   // Draw HTF swing points
   if(htf_lastHigh.price > 0)
      DrawSwingPoint(htf_lastHigh, "HTF_High", clrRed, HTF);
   
   if(htf_prevHigh.price > 0)
      DrawSwingPoint(htf_prevHigh, "HTF_PrevHigh", clrRed, HTF);
   
   if(htf_lastLow.price > 0)
      DrawSwingPoint(htf_lastLow, "HTF_Low", clrBlue, HTF);
   
   if(htf_prevLow.price > 0)
      DrawSwingPoint(htf_prevLow, "HTF_PrevLow", clrBlue, HTF);
   
   // Draw LTF swing points
   if(ltf_lastHigh.price > 0)
      DrawSwingPoint(ltf_lastHigh, "LTF_High", clrOrange, LTF);
   
   if(ltf_prevHigh.price > 0)
      DrawSwingPoint(ltf_prevHigh, "LTF_PrevHigh", clrOrange, LTF);
   
   if(ltf_lastLow.price > 0)
      DrawSwingPoint(ltf_lastLow, "LTF_Low", clrGreen, LTF);
   
   if(ltf_prevLow.price > 0)
      DrawSwingPoint(ltf_prevLow, "LTF_PrevLow", clrGreen, LTF);
   
   // Draw current price lines for reference
   double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   CreateHorizontalLine("Current_Bid", currentBid, clrYellow, STYLE_DASH);
   CreateHorizontalLine("Current_Ask", currentAsk, clrYellow, STYLE_DASH);
   
   // Draw trade condition zones
   if(ltf_lastHigh.price > 0)
      CreateHorizontalLine("Sell_Zone_Min", ltf_lastHigh.price, clrRed, STYLE_SOLID);
   
   if(ltf_lastLow.price > 0)
   {
      CreateHorizontalLine("Buy_Zone_Max", ltf_lastLow.price, clrBlue, STYLE_SOLID);
      CreateHorizontalLine("Sell_TP_Level", ltf_lastLow.price, clrGreen, STYLE_DOT);
   }
   
   // Draw structure labels
   DrawStructureLabels();
}

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

Функция UpdateVisualization() обеспечивает понятное визуальное представление в реальном времени свингов старшего и младшего таймфреймов, текущей цены и ключевых торговых зон. Сначала удаляются все существующие объекты с префиксом MS_, а затем маркерами с цветовой кодировкой отрисовываются все релевантные свинговые точки HTF и LTF. Текущие цены Bid и Ask отображаются желтыми штриховыми линиями, а торговые зоны — уровни покупки, продажи и тейк-профита — отрисовываются сплошными или точечными линиями.

//+------------------------------------------------------------------+
//| Draw Swing Point                                                 |
//+------------------------------------------------------------------+
void DrawSwingPoint(SwingPoint &sp, string name, color clr, ENUM_TIMEFRAMES tf)
{
   ObjectCreate(0, "MS_" + name, OBJ_ARROW, 0, sp.time, sp.price);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_ARROWCODE, sp.type == 1 ? 218 : 217);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_WIDTH, 3);
   ObjectSetString(0, "MS_" + name, OBJPROP_TOOLTIP, 
                  StringFormat("%s: %.5f", sp.type == 1 ? "High" : "Low", sp.price));
}

//+------------------------------------------------------------------+
//| Draw Structure Labels                                            |
//+------------------------------------------------------------------+
void DrawStructureLabels()
{
   string biasText = "HTF Bias: ";
   switch(htfBias)
   {
      case STRUCT_BULLISH: biasText += "BULLISH"; break;
      case STRUCT_BEARISH: biasText += "BEARISH"; break;
      default: biasText += "NEUTRAL"; break;
   }
   
   string ltfText = "LTF Structure: ";
   switch(ltfStructure)
   {
      case STRUCT_BULLISH: ltfText += "HH+HL"; break;
      case STRUCT_BEARISH: ltfText += "LL+LH"; break;
      default: ltfText += "NEUTRAL"; break;
   }
   
   // Current trade conditions
   string conditionText = "";
   if(CheckBuyCondition())
      conditionText = "BUY Condition: ACTIVE (Price < LTF Low)";
   else if(CheckSellCondition())
      conditionText = "SELL Condition: ACTIVE (Price > LTF High & Low)";
   else
      conditionText = "No Trade Condition Met";
   
   // Create label objects
   CreateLabel("MS_BiasLabel", biasText, 10, 20, clrDarkOrange);
   CreateLabel("MS_LTFLabel", ltfText, 10, 40, clrDarkOrange);
   CreateLabel("MS_ConditionLabel", conditionText, 10, 60, 
               (CheckBuyCondition() || CheckSellCondition()) ? clrLime : clrGray);
   
   // Display LTF swing prices
   if(ltf_lastHigh.price > 0)
      CreateLabel("MS_LTFHighLabel", StringFormat("LTF High: %.5f", ltf_lastHigh.price), 10, 80, clrOrange);
   
   if(ltf_lastLow.price > 0)
      CreateLabel("MS_LTFLowLabel", StringFormat("LTF Low: %.5f", ltf_lastLow.price), 10, 100, clrGreen);
}

//+------------------------------------------------------------------+
//| Create Text Label                                                |
//+------------------------------------------------------------------+
void CreateLabel(string name, string text, int x, int y, color clr)
{
   ObjectCreate(0, "MS_" + name, OBJ_LABEL, 0, 0, 0);
   ObjectSetString(0, "MS_" + name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_FONTSIZE, 10);
}

//+------------------------------------------------------------------+
//| Create Horizontal Line                                           |
//+------------------------------------------------------------------+
void CreateHorizontalLine(string name, double price, color clr, ENUM_LINE_STYLE style)
{
   ObjectCreate(0, "MS_" + name, OBJ_HLINE, 0, 0, price);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_STYLE, style);
   ObjectSetInteger(0, "MS_" + name, OBJPROP_WIDTH, 1);
}

В этом разделе кода система обеспечивает полную визуализацию рыночной структуры и торговых условий на графике. Функция DrawSwingPoint() отмечает свинговые максимумы и минимумы стрелками с цветовой кодировкой и добавляет всплывающие подсказки с указанием точной цены и типа — максимум или минимум. Функция DrawStructureLabels() дополняет визуализацию текстовой информацией о направленности HTF, текущей структуре LTF и активных торговых условиях, предоставляя быстрый обзор рыночного контекста. Метки также выделяют цены свингового максимума и минимума LTF, помогая визуализировать потенциальные зоны разворота и поддерживая оперативное принятие решений.

Для поддержки этих визуальных элементов вспомогательные функции CreateLabel() и CreateHorizontalLine() стандартизируют создание текстовых меток и горизонтальных ориентировочных линий. Метки размещаются в заданных экранных координатах с настраиваемыми цветами и размерами шрифта, а горизонтальные линии обозначают ключевые ценовые уровни: текущие цены Bid и Ask, свинговые максимумы и минимумы, а также торговые зоны. В совокупности эти функции преобразуют необработанные структурные данные в понятную и практически применимую визуальную основу, позволяя трейдерам в реальном времени видеть на графике рыночные экстремумы и потенциальные торговые возможности.


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

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

Equity Curve

Back Test Results



Заключение

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

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Elijah sanchey
Elijah sanchey | 25 февр. 2026 в 22:12
Как получить этот показатель?
Juvenille Emperor Limited
Eleni Anna Branou | 25 февр. 2026 в 22:13
Elijah sanchey #:
Как получить этот показатель
Ознакомьтесь со статьёй.
Jacryptoking
Jacryptoking | 26 февр. 2026 в 12:43
Вы умеете программировать, но вам нужно уделять больше времени пониманию стратегии, которую вы реализуете.


Ваши советники терпят неудачу из-за логики, лежащей в основе концепции, которую вы реализуете.
Educator Jay
Educator Jay | 26 февр. 2026 в 13:10
Проверь и дай мне знать
Нейроструктурный торговый движок — NSTE (Часть I): Построение мультиаккаунтной системы с учетом ограничений проп-фирм Нейроструктурный торговый движок — NSTE (Часть I): Построение мультиаккаунтной системы с учетом ограничений проп-фирм
В этой статье закладывается архитектура мультиаккаунтной алгоритмической торговой системы, работающей с криптовалютными CFD в MetaTrader 5 с учетом ограничений проп-фирм. В ней определены три основных принципа — фиксированный долларовый риск, один скрипт на счет и централизованная конфигурация, — а затем подробно описаны разделение задач между Python и MQL5, 60-секундный цикл обработки и передача сигналов на основе JSON. В статье приводятся практические методы расчета размера лота, проверки безопасности и шаблоны управления позициями для надежного развертывания.
Нейросети в трейдинге: Управление риском через распределение результатов (AC-SRM) Нейросети в трейдинге: Управление риском через распределение результатов (AC-SRM)
Средняя доходность не показывает, как распределены возможные прибыли и убытки, а значит, скрывает часть риска торговой стратегии. AC-SRM переносит этот риск в сам процесс обучения: политика оценивается по распределению результатов через спектральную меру риска, а не только по среднему значению. Практическую реализацию начинаем с OpenCL-кернела FQF_DistributionTargetGradient, который выбирает ближайшего к цели представителя распределения и формирует градиенты его значения и вероятности, подготавливая распределительный Критик для дальнейшей риск-ориентированной оптимизации.
Разработка индикатора паттерна "Мегафон" на MQL5 Разработка индикатора паттерна "Мегафон" на MQL5
Создадим на языке MQL5 индикатор паттерна «Мегафон», который обнаруживает на графике расширяющиеся структуры. В статье подробно рассматриваются вопросы идентификации и уточнения свингов, проверки линий тренда, подтверждения пробоя и проекции уровней стоп-лосса и тейк-профита с использованием графических объектов для линий, меток и сигналов. В результате вы получаете реализацию на основе правил, которая автоматизирует обнаружение паттернов и генерирует торговые уровни, пригодные для практического использования, непосредственно в MetaTrader 5.
Использование Экономического календаря MQL5 для фильтрации новостей (Часть 1): Реализация временных окон до и после новостей в MQL5 Использование Экономического календаря MQL5 для фильтрации новостей (Часть 1): Реализация временных окон до и после новостей в MQL5
Мы разработаем новостной фильтр на основе календаря на языке MQL5 без веб-запросов и внешних DLL. В части 1 рассматриваются загрузка и кэширование событий, сопоставление символов с валютами, фильтрация по уровню важности, определение окон до и после новостей, блокировка открытия новых сделок во время активного новостного периода и опциональное закрытие позиций перед новостью. В результате получается настраиваемый защитный механизм, совместимый с требованиями проп-фирм, который сокращает число ошибочных пауз в торговле и защищает входы в сделки в периоды волатильности.