English
preview
Теория графов: Применение поиска в глубину (DFS) в торговых системах

Теория графов: Применение поиска в глубину (DFS) в торговых системах

MetaTrader 5Примеры |
53 1
Hlomohang John Borotho
Hlomohang John Borotho

Содержание:

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



Введение

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

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


Обзор системы и принципы работы

Отображение концепции:
Теория графов   Рыночный эквивалент
Узел Свинг-хай/Свинг-лоу
Ребро Пробой или коррекция
Ветка Бычье или медвежье продолжение
Целевой узел Цель по ликвидности
Глубина Сила структурного продолжения
Возврат с откатом Инвалидация структуры

В теории графов поиск в глубину (DFS) работает следующим образом: выбирается ветвь и, прежде чем рассматривать альтернативные варианты, исследуется максимально глубоко. Применительно к структуре рынка это означает, что алгоритм следует единой структурной последовательности цены через последовательные свинговые точки, рассматривая каждый подтвержденный свинг-хай или свинг-лоу как узел в графе. Например, цена может начать с подтвержденного минимума свинга (Узел A), перейти к максимуму свинга (Узел B), затем откатывается, формируя Узел C, затем пробивает предыдущий максимум и формирует Узел D и, наконец, расширяется до Узла E. В терминах DFS система следует этой последовательности: A -> B -> C -> D -> E, до тех пор, пока структурные условия остаются валидными.

Однако, прежде чем сделка может быть совершена, алгоритм должен достичь минимальной структурной глубины, то есть последовательность подтвержденных свингов, которая четко демонстрирует направленное развитие (например, более высокий максимум -> более высокий минимум -> пробой структуры в бычьих условиях или более низкий минимум -> более низкий максимум -> продолжение тренда в медвежьих условиях). Только когда эта структурная цепочка обнаружена и подтверждена, система рассматривает возможность определения рыночной направленности.

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

Аналогично, медвежья направленность рынка подтверждается, когда путь приводит к более низким минимумам и более низким максимумам, причем последний более низкий максимум выступает ключевым структурным уровнем. После подтверждения этого направленного уклона и при условии, что глубина пути удовлетворяет минимальным требованиям, система оценивает, может ли путь реалистично привести к значимой цели, такой как пул ликвидности, зона спроса или предложения, или соответствует заранее заданному соотношению риск/прибыль. Если эти условия выполнены, исполняется сделка в направлении активного пути. Если же цена пробивает уровень структурной инвалидации, алгоритм рассматривает это как нарушение пути, выполняет откат DFS к последнему валидному узлу и начинает исследовать альтернативное направление рынка.



Приступаем к работе

//+------------------------------------------------------------------+
//|                                                          DFS.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"
#property strict

//+------------------------------------------------------------------+
//| Include trade class                                              |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh>
CTrade TradeManager;

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int      SwingPeriod      = 7;            // Bars left/right for pivot
input int      MinDepth         = 7;            // Minimum structural depth
input double   TargetTolerance  = 60;           // Tolerance for target hit (in points)
input bool     EnableVisual     = true;         // Draw nodes and paths
input double   FixedLotSize     = 0.01;         // Fixed lot size (if risk management disabled)
input bool     UseRiskManagement = true;        // Use risk-based position sizing
input double   RiskPercent       = 1.0;         // Risk percentage per trade
input int      StopLossPoints    = 600;         // Stop loss in points
input int      TakeProfitPoints  = 800;         // Take profit in points
input string   TradeComment      = "SwingGraph"; // Trade comment
input int      MagicNumber       = 123456;      // Expert magic number
input bool     EnableDailyReset  = true;        // Reset nodes at start of each day
input int      MaxNodesToKeep    = 100;         // Maximum nodes to keep (0 = unlimited)
input bool     EnableMemoryOptimization = true; // Enable memory optimization

//+------------------------------------------------------------------+
//| Structures                                                       |
//+------------------------------------------------------------------+
struct SwingNode
  {
   datetime          time;          // Bar time
   double            price;         // Swing price
   int               type;          // 1 = high, -1 = low
   int               index;         // Node index
   bool              visited;       // For DFS
  };

Для начала мы закладываем основу для советника, импортируем торговый функционал и определяем основные параметры конфигурации, которые управляют поведением системы. Библиотека #include <Trade/Trade.mqh> предоставляет доступ к торговым функциям MetaTrader, а объект CTrade TradeManager используется для исполнения ордеров на покупку и продажу. Входные параметры позволяют пользователю настраивать важные аспекты стратегии, такие как чувствительность определения свингов (SwingPeriod), минимальная структурная глубина, необходимая для анализа (MinDepth), и допуск для распознавания достижения ценой целевого уровня (TargetTolerance).

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

Мы также вводим оперативные элементы управления и настройки управления памятью, которые помогают поддерживать стабильность и эффективность советника во время непрерывного исполнения. Например, EnableDailyReset позволяет системе очищать и перестраивать свои структурные узлы в начале каждого торгового дня, а MaxNodesToKeep и EnableMemoryOptimization предотвращают накопление избыточных данных, которые могут замедлить работу алгоритма.

Наконец, структура SwingNode определяет основную единицу данных, используемую для моделирования структуры рынка, где каждый узел представляет собой обнаруженный свинг-хай или свинг-лоу. Каждый узел хранит время свинга, ценовой уровень, его тип (максимум или минимум), уникальный индекс и флаг посещения, используемый процессом поиска в глубину (DFS).

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
SwingNode nodes[];
int nodeCount = 0;
datetime lastBarTime = 0;
datetime lastDayDate = 0;    // Track last day for reset

//--- For DFS state
int currentPath[];
int pathDepth = 0;
int currentDirection = 0;    // 1 bullish, -1 bearish, 0 none
double lastHigherLow = 0;
double lastLowerHigh = 0;

//--- Trade flags
bool inTrade = false;
ulong tradeTicket = 0;
MqlTick currentTick;

//--- Rate arrays for timeseries data
double high[];
double low[];
datetime time[];
int barsCount = 0;

//--- Diagnostic counters
int tickCount = 0;
datetime lastCleanupTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   Print("==========================================");
   Print("SwingGraphTrader initialized");
   Print("SwingPeriod = ", SwingPeriod);
   Print("MinDepth = ", MinDepth);
   Print("TargetTolerance = ", TargetTolerance);
   Print("EnableDailyReset = ", EnableDailyReset);
   Print("MaxNodesToKeep = ", MaxNodesToKeep);
   Print("==========================================");

   //--- Initialize arrays with optimal size
   ArrayResize(nodes, 0, MaxNodesToKeep > 0 ? MaxNodesToKeep + 50 : 1000);
   nodeCount = 0;

   //--- Get initial time
   datetime currentTime[];
   if(CopyTime(_Symbol, _Period, 0, 1, currentTime) <= 0)
     {
      Print("Failed to copy initial time. Error: ", GetLastError());
      return INIT_FAILED;
     }
   lastBarTime = currentTime[0];

   //--- Set initial day
   MqlDateTime dt;
   TimeToStruct(lastBarTime, dt);
   dt.hour = 0;
   dt.min = 0;
   dt.sec = 0;
   lastDayDate = StructToTime(dt);

   Print("Initial lastBarTime = ", TimeToString(lastBarTime));
   Print("Initial lastDayDate = ", TimeToString(lastDayDate));

   TradeManager.SetExpertMagicNumber(MagicNumber);

   //--- Pre-allocate arrays for better performance
   ArrayResize(high, 0, 10000);
   ArrayResize(low, 0, 10000);
   ArrayResize(time, 0, 10000);

   return(INIT_SUCCEEDED);
  }

Далее мы объявляем набор глобальных переменных, которые поддерживают внутреннее состояние советника во время его работы. Массив nodes[] хранит коллекцию обнаруженных свинговых точек, каждая из которых представлена структурой SwingNode, а nodeCount отслеживает количество узлов на данный момент. Переменные lastBarTime и lastDayDate помогают советнику обнаруживать формирование новых свечей и определять, когда следует произвести дневной сброс. Раздел с меткой DFS state, содержит переменные, поддерживающие логику поиска в глубину, используемую для анализа структуры рынка.

Например, currentPath[] записывает последовательность свинг-узлов, формирующих активный структурный путь, pathDepth отслеживает, насколько глубоко продвинулся обход, а currentDirection указывает, следует ли система в данный момент бычьему или медвежьему пути. Дополнительные переменные, такие как lastHigherLow и lastLowerHigh, помогают отслеживать структурную валидацию по мере изменения цены. Мы также включаем флаги, связанные с торговлей, такие как inTrade и tradeTicket, чтобы отслеживать, удерживает ли советник в данный момент позицию, а также массивы временных рядов (high[], low[], time[]), используемые для хранения исторических данных свечей для обнаружения свингов и структурного анализа.

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

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

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(EnableVisual)
      ObjectsDeleteAll(0, "SwingGraph_");

   //--- Free memory
   ArrayFree(nodes);
   ArrayFree(high);
   ArrayFree(low);
   ArrayFree(time);
   ArrayFree(currentPath);

   Print("SwingGraphTrader deinitialized. Reason: ", reason);
  }

//+------------------------------------------------------------------+
//| Reset all data at day change                                     |
//+------------------------------------------------------------------+
void CheckAndResetDaily()
  {
   if(!EnableDailyReset)
      return;

   //--- Get current day
   MqlDateTime currentDt;
   TimeToStruct(TimeCurrent(), currentDt);
   currentDt.hour = 0;
   currentDt.min = 0;
   currentDt.sec = 0;
   datetime currentDay = StructToTime(currentDt);

   //--- Check if day changed
   if(currentDay > lastDayDate)
     {
      Print("Day changed from ", TimeToString(lastDayDate), " to ", TimeToString(currentDay));
      Print("Resetting all nodes and state for new day");

      //--- Reset all nodes
      ArrayResize(nodes, 0, MaxNodesToKeep > 0 ? MaxNodesToKeep + 50 : 1000);
      nodeCount = 0;

      //--- Reset DFS state
      currentDirection = 0;
      pathDepth = 0;
      ArrayResize(currentPath, 0);
      lastHigherLow = 0;
      lastLowerHigh = 0;

      //--- Clear all drawings
      if(EnableVisual)
         ObjectsDeleteAll(0, "SwingGraph_");

      //--- Free timeseries arrays to release memory
      if(EnableMemoryOptimization)
        {
         ArrayFree(high);
         ArrayFree(low);
         ArrayFree(time);
        }

      lastDayDate = currentDay;
      Print("Daily reset complete. Memory freed.");
     }
  }

//+------------------------------------------------------------------+
//| Optimize memory usage                                            |
//+------------------------------------------------------------------+
void OptimizeMemory()
  {
   if(!EnableMemoryOptimization)
      return;

   //--- Periodically clean up old nodes if we exceed max nodes
   if(MaxNodesToKeep > 0 && nodeCount > MaxNodesToKeep)
     {
      Print("Node count (", nodeCount, ") exceeds MaxNodesToKeep (", MaxNodesToKeep, "). Cleaning up old nodes.");

      int nodesToRemove = nodeCount - MaxNodesToKeep;

      //--- Shift remaining nodes to beginning of array
      for(int i = 0; i < MaxNodesToKeep; i++)
        {
         nodes[i] = nodes[i + nodesToRemove];
         nodes[i].index = i; // Update indices
        }

      nodeCount = MaxNodesToKeep;
      ArrayResize(nodes, nodeCount, MaxNodesToKeep + 50);

      //--- Reset path if it contains removed nodes
      bool pathNeedsReset = false;
      for(int i = 0; i < pathDepth; i++)
        {
         if(currentPath[i] < nodesToRemove)
           {
            pathNeedsReset = true;
            break;
           }
         //--- Adjust indices
         currentPath[i] -= nodesToRemove;
        }

      if(pathNeedsReset)
        {
         currentDirection = 0;
         pathDepth = 0;
         ArrayResize(currentPath, 0);
         Print("Path reset due to node cleanup");
        }

      Print("Node cleanup complete. New node count: ", nodeCount);
     }

   //--- Periodically free timeseries arrays if they're too large
   if(ArraySize(high) > 10000)
     {
      ArrayResize(high, 0, 10000);
      ArrayResize(low, 0, 10000);
      ArrayResize(time, 0, 10000);
      Print("Timeseries arrays reset to save memory");
     }
  }

Этот раздел кода управляет выключением и ежедневным сбросом советника. Функция OnDeinit() запускается при удалении советника с графика, закрытии платформы или перекомпиляции программы. Ее цель — безопасно очистить среду путем удаления любых визуальных объектов, нарисованных советником, и освобождения памяти, выделенной для таких массивов, как nodes, high, low, time и currentPath. Это предотвращает утечки памяти и гарантирует, что система завершает работу корректно, не нарушая стабильность платформы. Функция также выводит сообщение о том, что SwingGraphTrader был деинициализирован, вместе с кодом причины, что помогает при отладке и мониторинге.

После этого функция CheckAndResetDaily() гарантирует, что торговая система начинает новый торговый день с чистого состояния, когда EnableDailyReset активен. Она сравнивает текущую дату с сохраненной датой lastDayDate, и если обнаруживается новый день, он сбрасывает структурные узлы, очищает состояние обхода DFS, удаляет графические объекты на графике и, при необходимости, освобождает массивы временных рядов для освобождения памяти.

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

Кроме того, функция отслеживает размер массивов истории цен (high, low и time) и изменяет их размер, если они становятся слишком большими, помогая поддерживать эффективное использование памяти. Вместе эти механизмы позволяют советнику работать непрерывно, сохраняя при этом его внутренние структуры данных чистыми, эффективными и согласованными с самой последней рыночной информацией.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Check for daily reset
   CheckAndResetDaily();

   //--- Periodically optimize memory (every 1000 ticks or 1 hour)
   tickCount++;
   if(tickCount % 1000 == 0 || TimeCurrent() - lastCleanupTime > 3600)
     {
      OptimizeMemory();
      lastCleanupTime = TimeCurrent();
     }

   if(inTrade)
     {
      if(!PositionSelectByTicket(tradeTicket))
        {
         Print("Trade closed externally. Resetting state.");
         inTrade = false;
         tradeTicket = 0;
         currentDirection = 0;
         pathDepth = 0;
         ArrayResize(currentPath,0);
        }
     }

   //--- Get current tick data
   if(!SymbolInfoTick(_Symbol, currentTick))
     {
      Print("Failed to get current tick data. Error: ", GetLastError());
      return;
     }

   //--- Check new bar
   datetime currentTime[];
   if(CopyTime(_Symbol, _Period, 0, 1, currentTime) <= 0)
     {
      Print("Failed to copy current time. Error: ", GetLastError());
      return;
     }

   //--- Process only on new bar
   if(currentTime[0] == lastBarTime)
      return;

   lastBarTime = currentTime[0];
   Print("New bar detected at ", TimeToString(lastBarTime));

   //--- Update timeseries data - reuse arrays to minimize memory allocation
   barsCount = Bars(_Symbol, _Period);
   Print("Bars count: ", barsCount);

   if(barsCount < SwingPeriod * 2 + 5)
     {
      Print("Not enough bars: ", barsCount, " < ", SwingPeriod * 2 + 5);
      return;
     }

   //--- Set series as time series
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(time,true);

   //--- Reuse existing arrays instead of creating new ones
   int copiedHigh = CopyHigh(_Symbol, _Period, 0, barsCount, high);
   int copiedLow = CopyLow(_Symbol, _Period, 0, barsCount, low);
   int copiedTime = CopyTime(_Symbol, _Period, 0, barsCount, time);

   Print("Copied high: ", copiedHigh, ", low: ", copiedLow, ", time: ", copiedTime);
   if(copiedHigh <= 0 || copiedLow <= 0 || copiedTime <= 0)
     {
      Print("Failed to copy price data. Errors: ", GetLastError());
      return;
     }

   //--- Update swings
   int oldNodeCount = nodeCount;
   DetectSwing();
   if(nodeCount > oldNodeCount)
      Print("Added ", nodeCount - oldNodeCount, " new nodes. Total nodes: ", nodeCount);

   //--- Detect invalidation of current path
   if(!inTrade && currentDirection != 0)
     {
      if(IsPathInvalidated())
        {
         Print("Path invalidated. Backtracking.");
         BacktrackAndSwitch();
        }
     }

   //--- Run DFS to find new path if not in trade
   if(!inTrade)
     {
      FindBestPath();
     }

   //--- Check if we should enter a trade
   if(!inTrade && currentDirection != 0 && pathDepth >= MinDepth)
     {
      Print("Trade condition satisfied: direction=", currentDirection, " depth=", pathDepth);

      if(currentDirection == 1)
         ExecuteTrade(ORDER_TYPE_BUY, TradeComment + " Bullish");
      else
         ExecuteTrade(ORDER_TYPE_SELL, TradeComment + " Bearish");
     }

   //--- Visualization - only if enabled
   if(EnableVisual)
     {
      DrawSwings();
      DrawActivePath();
     }
  }

//+------------------------------------------------------------------+
//| Swing Detection                                                  |
//+------------------------------------------------------------------+
void DetectSwing()
  {
   int currentBar =  SwingPeriod + 1; // use previous completed bar
   if(currentBar >= barsCount - SwingPeriod)
     {
      Print("currentBar (", currentBar, ") >= barsCount - SwingPeriod (", barsCount - SwingPeriod, ") - returning");
      return;
     }

   if(IsSwingHigh(currentBar, SwingPeriod))
     {
      Print("Swing high detected at bar ", currentBar, ", price: ", high[currentBar]);
      AddNode(time[currentBar], high[currentBar], 1);
     }
   else
      if(IsSwingLow(currentBar, SwingPeriod))
        {
         Print("Swing low detected at bar ", currentBar, ", price: ", low[currentBar]);
         AddNode(time[currentBar], low[currentBar], -1);
        }
  }

//+------------------------------------------------------------------+
//| IsSwingHigh                                                      |
//+------------------------------------------------------------------+
bool IsSwingHigh(int bar, int period)
  {
   double barHigh = high[bar];
   for(int i = bar - period; i <= bar + period; i++)
     {
      if(i < 0 || i >= barsCount)
         continue;
      if(high[i] > barHigh)
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//|  IsSwingLow                                                      |
//+------------------------------------------------------------------+
bool IsSwingLow(int bar, int period)
  {
   double barLow = low[bar];
   for(int i = bar - period; i <= bar + period; i++)
     {
      if(i < 0 || i >= barsCount)
         continue;
      if(low[i] < barLow)
         return false;
     }
   return true;
  }

Этот раздел кода представляет собой основной цикл исполнения советника, в котором торговая логика обрабатывается каждый раз при приходе нового тика. Функция OnTick() сначала проверяет, требуется ли ежедневный сброс, и периодически запускает оптимизацию памяти, чтобы поддерживать эффективность системы во время длительной работы. Она также проверяет, активна ли существующая сделка, и сбрасывает внутреннее состояние, если позиция была закрыта извне. Затем советник извлекает последние тиковые данные и гарантирует, что обработка происходит только при формировании новой свечи (бара), что предотвращает избыточные вычисления на каждом тике. При обнаружении нового бара программа обновляет исторические массивы цен (high, low и time), гарантируя наличие достаточного количества баров для выполнения анализа свингов.

Далее функция DetectSwing() сканирует данные о ценах, чтобы определить новые максимумы и минимумы свингов, используя вспомогательные функции IsSwingHigh() и IsSwingLow(), чтобы подтвердить, находится ли бар выше или ниже окружающих свечей в пределах определенного SwingPeriod. Если обнаруживаются новые структурные узлы, они добавляются в граф узлов, что позволяет системе поддерживать развивающееся представление структуры рынка. После обновления узлов советник проверяет, был ли текущий путь DFS признан недействительным, и при необходимости возвращается назад, а затем запускает логику поиска в глубину, чтобы найти наиболее перспективный структурный путь. Когда установлена валидная направленность рынка и глубина структуры соответствует минимальным требованиям, советник совершает сделку в обнаруженном направлении, при необходимости рисуя свинговые точки и активный путь на графике для визуализации.

//+------------------------------------------------------------------+
//|  AddNode                                                         |
//+------------------------------------------------------------------+
void AddNode(datetime nodeTime, double price, int type)
  {
   //--- Avoid duplicate at same time
   if(nodeCount > 0 && nodes[nodeCount-1].time == nodeTime)
     {
      Print("Duplicate node at same time, skipping.");
      return;
     }

   //--- Check if we need to resize with extra capacity
   if(nodeCount >= ArraySize(nodes))
     {
      int newSize = nodeCount + (MaxNodesToKeep > 0 ? 50 : 100);
      ArrayResize(nodes, newSize, MaxNodesToKeep > 0 ? MaxNodesToKeep + 50 : 1000);
     }

   nodes[nodeCount].time = nodeTime;
   nodes[nodeCount].price = price;
   nodes[nodeCount].type = type;
   nodes[nodeCount].index = nodeCount;
   nodes[nodeCount].visited = false;
   nodeCount++;
   Print("Node added: index=", nodeCount-1, ", time=", TimeToString(nodeTime), ", price=", price, ", type=", type);
  }

//+------------------------------------------------------------------+
//| DFS Path Finding                                                 |
//+------------------------------------------------------------------+
void FindBestPath()
  {
   if(nodeCount < 2)
     {
      Print("Not enough nodes for DFS: ", nodeCount);
      return;
     }

   //--- Reset visited flags
   for(int i = 0; i < nodeCount; i++)
      nodes[i].visited = false;

   int bestDepth = 0;
   int bestDirection = 0;
   int bestPath[];

   //--- Try each node as starting point
   for(int start = 0; start < nodeCount; start++)
     {
      //--- Bullish from a low
      if(nodes[start].type == -1)
        {
         int path[];
         int depth = DFS_Bullish(start, nodes[start].price, -1e9, path);
         if(depth > bestDepth)
           {
            bestDepth = depth;
            bestDirection = 1;
            ArrayResize(bestPath, ArraySize(path));
            for(int i = 0; i < ArraySize(path); i++)
               bestPath[i] = path[i];
           }
        }
      //--- Bearish from a high
      if(nodes[start].type == 1)
        {
         int path[];
         int depth = DFS_Bearish(start, nodes[start].price, 1e9, path);
         if(depth > bestDepth)
           {
            bestDepth = depth;
            bestDirection = -1;
            ArrayResize(bestPath, ArraySize(path));
            for(int i = 0; i < ArraySize(path); i++)
               bestPath[i] = path[i];
           }
        }
     }

   Print("Best depth found: ", bestDepth, ", direction: ", bestDirection);

   //--- Set current path if depth meets minimum
   if(bestDepth >= MinDepth)
     {
      ArrayResize(currentPath, bestDepth);
      for(int i = 0; i < bestDepth; i++)
         currentPath[i] = bestPath[i];
      pathDepth = bestDepth;
      currentDirection = bestDirection;
      UpdateKeyLevels();
      Print("Path set: depth=", pathDepth, ", direction=", currentDirection, ", lastHigherLow=", lastHigherLow, ", lastLowerHigh=", lastLowerHigh);
     }

   if(bestDepth < MinDepth)
     {
      currentDirection = 0;
      pathDepth = 0;
      ArrayResize(currentPath,0);
     }

  }

//+------------------------------------------------------------------+
//|  Bullish DFS                                                     |
//+------------------------------------------------------------------+
int DFS_Bullish(int nodeIdx, double lastLow, double lastHigh, int &path[])
  {
   //--- nodeIdx is a low
   nodes[nodeIdx].visited = true;

   //--- Initialize path with current node
   ArrayResize(path, 1);
   path[0] = nodeIdx;
   int maxDepth = 1;

   //--- Look for a high after this low
   for(int i = nodeIdx+1; i < nodeCount; i++)
     {
      if(nodes[i].type == 1 && !nodes[i].visited)
        {
         if(lastHigh == -1e9 || nodes[i].price > lastHigh)
           {
            //--- Look for a subsequent low after this high
            for(int j = i+1; j < nodeCount; j++)
              {
               if(nodes[j].type == -1 && !nodes[j].visited)
                 {
                  if(nodes[j].price > lastLow)
                    {
                     int subPath[];
                     int subDepth = DFS_Bullish(j, nodes[j].price, nodes[i].price, subPath);

                     if(subDepth + 2 > maxDepth)
                       {
                        maxDepth = subDepth + 2;
                        ArrayResize(path, maxDepth);
                        path[0] = nodeIdx;        // current low
                        path[1] = i;              // intermediate high
                        for(int k = 2; k < maxDepth; k++)
                           path[k] = subPath[k-2]; // subsequent nodes
                       }
                    }
                 }
              }
           }
        }
     }
   nodes[nodeIdx].visited = false;
   return maxDepth;
  }

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

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

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

//+------------------------------------------------------------------+
//|  Bearish DFS                                                     |
//+------------------------------------------------------------------+
int DFS_Bearish(int nodeIdx, double lastHigh, double lastLow, int &path[])
  {
   //--- nodeIdx is a high
   nodes[nodeIdx].visited = true;

   //--- Initialize path with current node
   ArrayResize(path, 1);
   path[0] = nodeIdx;
   int maxDepth = 1;

   for(int i = nodeIdx+1; i < nodeCount; i++)
     {
      if(nodes[i].type == -1 && !nodes[i].visited)
        {
         if(lastLow == 1e9 || nodes[i].price < lastLow)
           {
            for(int j = i+1; j < nodeCount; j++)
              {
               if(nodes[j].type == 1 && !nodes[j].visited)
                 {
                  if(nodes[j].price < lastHigh)
                    {
                     int subPath[];
                     int subDepth = DFS_Bearish(j, nodes[j].price, nodes[i].price, subPath);

                     if(subDepth + 2 > maxDepth)
                       {
                        maxDepth = subDepth + 2;
                        ArrayResize(path, maxDepth);
                        path[0] = nodeIdx;        // current high
                        path[1] = i;              // intermediate low
                        for(int k = 2; k < maxDepth; k++)
                           path[k] = subPath[k-2]; // subsequent nodes
                       }
                    }
                 }
              }
           }
        }
     }
   nodes[nodeIdx].visited = false;
   return maxDepth;
  }

//+------------------------------------------------------------------+
//| Invalidation and Backtracking                                    |
//+------------------------------------------------------------------+
bool IsPathInvalidated()
  {
   if(pathDepth == 0)
      return false;

   double price = currentTick.bid;
   Print("Checking invalidation: price=", price, ", lastHigherLow=", lastHigherLow, ", lastLowerHigh=", lastLowerHigh);

   if(currentDirection == 1) // bullish
     {
      if(price < lastHigherLow - (TargetTolerance * Point()))
        {
         Print("Bullish path invalidated: price below lastHigherLow");
         return true;
        }
     }
   else
      if(currentDirection == -1) // bearish
        {
         if(price > lastLowerHigh + (TargetTolerance * Point()))
           {
            Print("Bearish path invalidated: price above lastLowerHigh");
            return true;
           }
        }
   return false;
  }

//+------------------------------------------------------------------+
//| BacktrackAndSwitch                                               |
//+------------------------------------------------------------------+
void BacktrackAndSwitch()
  {
   currentDirection = 0;
   pathDepth = 0;
   ArrayResize(currentPath, 0);
   lastHigherLow = 0;
   lastLowerHigh = 0;
   Print("Backtrack: reset all path state.");
  }

//+------------------------------------------------------------------+
//|  UpdateKeyLvls                                                   |
//+------------------------------------------------------------------+
void UpdateKeyLevels()
  {
   if(pathDepth == 0)
      return;

   if(currentDirection == 1)
     {
      for(int i = pathDepth-1; i >= 0; i--)
        {
         if(nodes[currentPath[i]].type == -1)
           {
            lastHigherLow = nodes[currentPath[i]].price;
            break;
           }
        }
     }
   else
      if(currentDirection == -1)
        {
         for(int i = pathDepth-1; i >= 0; i--)
           {
            if(nodes[currentPath[i]].type == 1)
              {
               lastLowerHigh = nodes[currentPath[i]].price;
               break;
              }
           }
        }
  }

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

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

Вторая часть кода управляет проверкой пути, инвалидированием структуры и возвратом, которые являются критически важными компонентами применения логики DFS в торговой среде. Функция IsPathInvalidated() проверяет, остается ли текущий структурный путь валидным, сравнивая текущую рыночную цену с ключевыми структурными уровнями, такими как самый последний более высокий минимум для бычьих путей или более низкий максимум для медвежьих путей. Если цена уходит за эти уровни с учетом заданного допуска, алгоритм считает текущий структурный путь недействительным.

В этом случае функция BacktrackAndSwitch() сбрасывает состояние DFS, очищая активный путь, устраняя направленность рынка и сбрасывая ключевые уровни, что позволяет системе искать новый структурный путь. Функция UpdateKeyLevels() дополняет этот процесс, сканируя активный путь для определения самого последнего структурного уровня, релевантного текущему направлению тренда. Эти уровни служат опорными точками для определения того, продолжает ли текущее движение рынка поддерживать активный путь DFS или требует от алгоритма возврата назад и изучения альтернативных направлений рынка.

//+------------------------------------------------------------------+
//| Target Identification                                            |
//+------------------------------------------------------------------+
bool IsTargetReached(SwingNode &node)
  {
   double targetPrice = 0;
   int barsToCheck = 20;

   if(barsCount < barsToCheck)
     {
      Print("Not enough bars for target check: ", barsCount, " < ", barsToCheck);
      return false;
     }

   if(currentDirection == 1) // bullish target is above
     {
      double highArray[];
      int copied = CopyHigh(_Symbol, _Period, 1, barsToCheck, highArray);
      if(copied <= 0)
        {
         Print("Failed to copy highs for target. Error: ", GetLastError());
         return false;
        }

      int highestIdx = ArrayMaximum(highArray);
      if(highestIdx >= 0)
         targetPrice = highArray[highestIdx];
     }
   else // bearish
     {
      double lowArray[];
      int copied = CopyLow(_Symbol, _Period, 1, barsToCheck, lowArray);
      if(copied <= 0)
        {
         Print("Failed to copy lows for target. Error: ", GetLastError());
         return false;
        }

      int lowestIdx = ArrayMinimum(lowArray);
      if(lowestIdx >= 0)
         targetPrice = lowArray[lowestIdx];
     }

   double distance = MathAbs(node.price - targetPrice);
   Print("Target check: node.price=", node.price, ", targetPrice=", targetPrice, ", distance=", distance, ", tolerance=", TargetTolerance);

   if(distance < TargetTolerance)
      return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Trade Execution                                                  |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment)
  {
   Print("Attempting to execute trade: ", (orderType==ORDER_TYPE_BUY?"BUY":"SELL"), ", comment: ", comment);

   //--- Calculate position size based on risk
   double lotSize = 0;

   if(UseRiskManagement)
     {
      lotSize = CalculateLotSize(orderType, StopLossPoints);
      if(lotSize <= 0)
        {
         Print("Failed to calculate position size, using fixed lot size: ", FixedLotSize);
         lotSize = FixedLotSize;
        }
     }
   else
     {
      lotSize = FixedLotSize;
     }

   if(lotSize <= 0)
     {
      Print("Invalid lot size. Trade not executed.");
      return;
     }
   Print("Lot size: ", lotSize);

   //--- Get current tick data
   if(!SymbolInfoTick(_Symbol, currentTick))
     {
      Print("Failed to get current tick data. Error: ", GetLastError());
      return;
     }

   //--- Calculate stop loss and take profit prices
   double stopLoss = 0.0, takeProfit = 0.0;
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

   if(orderType == ORDER_TYPE_BUY)
     {
      stopLoss = NormalizeDouble(currentTick.bid - (StopLossPoints * point), digits);
      takeProfit = NormalizeDouble(currentTick.ask + (TakeProfitPoints * point), digits);
     }
   else
      if(orderType == ORDER_TYPE_SELL)
        {
         stopLoss = NormalizeDouble(currentTick.ask + (StopLossPoints * point), digits);
         takeProfit = NormalizeDouble(currentTick.bid - (TakeProfitPoints * point), digits);
        }
   Print("SL: ", stopLoss, ", TP: ", takeProfit);

   //--- Validate stop levels before sending the trade
   if(!ValidateStopLevels(orderType, currentTick.ask, currentTick.bid, stopLoss, takeProfit))
     {
      Print("Invalid stop levels. Trade not executed.");
      return;
     }

   if(PositionSelect(_Symbol))
      return;

   //--- Execute trade
   bool requestSent = false;

   if(orderType == ORDER_TYPE_BUY)
     {
      requestSent = TradeManager.Buy(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
     }
   else
      if(orderType == ORDER_TYPE_SELL)
        {
         requestSent = TradeManager.Sell(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
        }

   //--- Check if the request was sent successfully
   if(requestSent)
     {
      //--- Check the server's return code from the trade operation
      uint result = TradeManager.ResultRetcode();
      Print("Trade request sent. Retcode: ", result, " - ", TradeManager.ResultRetcodeDescription());
      if(result == TRADE_RETCODE_DONE || result == TRADE_RETCODE_DONE_PARTIAL)
        {
         Print("Trade executed successfully. Ticket: ", TradeManager.ResultOrder());
         inTrade = true;
         tradeTicket = TradeManager.ResultOrder();
        }
      else
         if(result == TRADE_RETCODE_REQUOTE || result == TRADE_RETCODE_TIMEOUT || result == TRADE_RETCODE_PRICE_CHANGED)
           {
            Print("Trade failed due to price change. Retcode: ", TradeManager.ResultRetcodeDescription());
           }
         else
           {
            Print("Trade execution failed. Retcode: ", TradeManager.ResultRetcodeDescription());
           }
     }
   else
     {
      Print("Failed to send trade request. Last Error: ", GetLastError());
     }
  }

Функция IsTargetReached() отвечает за определение того, достиг ли структурный узел потенциальной рыночной цели на основе недавних экстремумов цен. Сначала функция проверяет наличие достаточного количества баров для проведения анализа, а затем определяет целевой уровень в зависимости от текущего направления рынка. В бычьем сценарии алгоритм извлекает самый высокий максимум из последнего набора свечей, а в медвежьем сценарии — самый низкий минимум. Эти значения представляют собой ближайшие цели ликвидности или расширения, к которым цена может стремиться. После определения целевого уровня функция вычисляет расстояние между ценой узла и обнаруженным целевым значением. Если это расстояние находится в пределах заданного значения TargetTolerance, функция возвращает значение true, указывая на то, что структурное движение фактически достигло своей целевой области.

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

Перед отправкой ордера система проверяет уровни стоп-лосса на соответствие ограничениям брокера, а также убеждается в отсутствии открытых позиций по данному символу. Если все условия выполнены, функция отправляет запрос на покупку или продажу через класс CTrade, а затем оценивает код ответа сервера. Если сделка успешно открыта, советник записывает номер тикета и обновляет состояние сделки, позволяя системе отслеживать и управлять активной позицией.

//+------------------------------------------------------------------+
//| Visualization                                                    |
//+------------------------------------------------------------------+
void DrawSwings()
  {
   //--- Don't draw if disabled
   if(!EnableVisual)
      return;

   string prefix = "SwingGraph_";
   ObjectsDeleteAll(0, prefix);

   for(int i = 0; i < nodeCount; i++)
     {
      string name = prefix + "Node_" + IntegerToString(i);
      color clr = (nodes[i].type == 1) ? clrBlue : clrRed;

      if(!ObjectCreate(0, name, OBJ_ARROW, 0, nodes[i].time, nodes[i].price))
         Print("Failed to create object ", name, ". Error: ", GetLastError());
      else
        {
         ObjectSetInteger(0, name, OBJPROP_ARROWCODE, (nodes[i].type == 1) ? 241 : 242);
         ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
         ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
        }
     }

   //--- Draw edges connecting consecutive nodes
   for(int i = 1; i < nodeCount; i++)
     {
      string name = prefix + "Edge_" + IntegerToString(i-1) + "_" + IntegerToString(i);
      if(!ObjectCreate(0, name, OBJ_TREND, 0, nodes[i-1].time, nodes[i-1].price, nodes[i].time, nodes[i].price))
         Print("Failed to create edge ", name, ". Error: ", GetLastError());
      else
        {
         ObjectSetInteger(0, name, OBJPROP_COLOR, clrGray);
         ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
         ObjectSetInteger(0, name, OBJPROP_RAY, false);
        }
     }
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void DrawActivePath()
  {
   //--- Don't draw if disabled
   if(!EnableVisual)
      return;
   if(pathDepth < 2)
      return;

   string prefix = "SwingGraph_Path_";
   for(int i = 1; i < pathDepth; i++)
     {
      string name = prefix + IntegerToString(i-1) + "_" + IntegerToString(i);
      int from = currentPath[i-1];
      int to = currentPath[i];
      if(from >= 0 && from < nodeCount && to >= 0 && to < nodeCount)
        {
         if(!ObjectCreate(0, name, OBJ_TREND, 0, nodes[from].time, nodes[from].price, nodes[to].time, nodes[to].price))
            Print("Failed to create path segment ", name, ". Error: ", GetLastError());
         else
           {
            ObjectSetInteger(0, name, OBJPROP_COLOR, (currentDirection == 1) ? clrGreen : clrOrange);
            ObjectSetInteger(0, name, OBJPROP_WIDTH, 3);
            ObjectSetInteger(0, name, OBJPROP_RAY, false);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Функции DrawSwings() и DrawActivePath() отвечают за визуализацию структуры графа и активного пути DFS непосредственно на торговом графике. Функция DrawSwings() сначала удаляет ранее нарисованные объекты с префиксом "SwingGraph_" для предотвращения дублирования, затем перебирает все сохраненные узлы свинга и графически представляет их в виде стрелок в соответствующих временных и ценовых координатах. Максимумы свингов цены отображаются синими стрелками, а минимумы — красными, что позволяет легко различать точки структурного поворота. Кроме того, функция рисует серые линии тренда между последовательными узлами, эффективно формируя визуальный граф обнаруженной структуры рынка.

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



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

Основная цель системы заключалась в преобразовании субъективной структуры ценового движения в детерминированный, основанный на правилах обход свингов, где сделки исполняются только после подтверждения валидного структурного пути достаточной глубины и подтвердит направленность рынка. Тестирование на истории было проведено по паре XAUUSD на таймфрейме M15 в течение 2-месячного периода тестирования (с 01 декабря 2025 года по 30 января 2026 года) с настройками по умолчанию:

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


Заключение

Если кратко, мы применили концепцию поиска в глубину (DFS) из теории графов к торговле, преобразовав структуру рынка в графовую модель взаимосвязанных свинговых точек, где каждый свинг-хай или свинг-лоу становится узлом, а взаимосвязи между ними образуют ребра. В рамках этой структуры алгоритм исследует потенциальные пути цен, продвигаясь глубоко вдоль одной структурной ветви — например, последовательности более высоких максимумов и более высоких минимумов — прежде чем рассматривать альтернативные направления. Чтобы реализовать это на практике, мы реализовали работающий советник на MQL5, который включает в себя обнаружение свингов, хранение узлов в структуре SwingNode, обход DFS для бычьих и медвежьих путей, а также выбор пути на основе структурной глубины с использованием критерия MinDepth. Система также извлекает ключевые уровни инвалидирования (lastHigherLow и lastLowerHigh) с настраиваемым допуском (TargetTolerance), исполняет сделки с заданными SL/TP и опциональным определением размера на основе риска, а также визуализирует результирующие узлы и активный структурный путь непосредственно на графике.

В заключение, интеграция DFS в торговлю преобразует необработанное ценовое движение в структурированное пространство поиска, которое трейдеры могут анализировать и тестировать программно. С помощью этой структуры можно моделировать структуру рынка в виде графа, отслеживать потенциальные пути движения между уровнями свингов и объективно определять, когда подтверждается или перестает быть валидной направленность рынка. Полученный шаблон советника обеспечивает воспроизводимый и тестируемый рабочий процесс, позволяющий скомпилировать систему, запустить ее в тестере стратегий и экспериментировать с такими параметрами, как SwingPeriod, MinDepth и настройками риска, наблюдая за тем, как изменяются pathDepth, currentDirection и структурные уровни. Формализация логики свингов в детерминированные правила преобразует субъективную интерпретацию графиков в проверяемую задачу поиска, позволяя проводить систематические эксперименты, совершенствовать торговую логику и разрабатывать более сложные алгоритмические стратегии.

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

Прикрепленные файлы |
DFS.mq5 (33.12 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Stanislav Korotky
Stanislav Korotky | 13 мар. 2026 в 15:24

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

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

Создание пользовательских индикаторов в MQL5 (Часть 4): Smart WaveTrend Crossover на основе двух осцилляторов Создание пользовательских индикаторов в MQL5 (Часть 4): Smart WaveTrend Crossover на основе двух осцилляторов
В этой статье мы разработаем пользовательский индикатор MQL5 под названием Smart WaveTrend Crossover, использующий два осциллятора WaveTrend: один — для формирования сигналов пересечения, второй — для фильтрации тренда, с настраиваемыми параметрами длины канала, периода усреднения и скользящей средней. Индикатор отображает цветные свечи в зависимости от направления тренда, отображает стрелки покупки и продажи при пересечениях, а также включает параметры для включения подтверждения по тренду и настройки визуальных элементов, таких как цвета и смещения.
Создание пользовательских индикаторов в MQL5 (Часть 6): Усовершенствование расчётов RSI  — сглаживание, динамические цвета и и поддержка нескольких таймфреймов Создание пользовательских индикаторов в MQL5 (Часть 6): Усовершенствование расчётов RSI — сглаживание, динамические цвета и и поддержка нескольких таймфреймов
В этой статье мы создадим универсальный индикатор RSI в MQL5 с поддержкой нескольких вариантов расчёта, источников данных и методов сглаживания для более качественного анализа. Мы добавим изменение оттенка для цветовой индикации, динамические границы зон перекупленности/перепроданности и уведомления о смене тренда. Индикатор включает мультитаймфреймовую поддержку с интерполяцией и предоставляет настраиваемый инструмент RSI для разных стратегий.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (Окончание) Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (Окончание)
Лучше всего здесь работает сочетание вариантов 2 и 4: оно сохраняет техническую точность, не обещает доходность и сразу показывает практический смысл архитектуры. В статье завершается адаптация фреймворка CogDriver к анализу финансовых рынков. Представление рыночной сцены, временная память, прогнозный план и оценка ожидаемой ошибки объединяются в единый торговый контур, при этом прогнозирование отделено от принятия решений. Рассматриваются построение моделей, организация обучения и проверка архитектуры в тестере стратегий MetaTrader 5 с акцентом на снижение избыточной реактивности и дрожания торговых решений.