English Deutsch 日本語
preview
Dynamic Swing Architecture: Распознавание структуры рынка — от свингов до автоматического исполнения сделок

Dynamic Swing Architecture: Распознавание структуры рынка — от свингов до автоматического исполнения сделок

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

Содержание:

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


Введение

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

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

Swing High Detection

Swing Low Detection

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


Обзор системы

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

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

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


Начинаем

//+------------------------------------------------------------------+
//|                                      Dynamic Swing Detection.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ru/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/users/johnhlomohang/"
#property version   "1.00"
#include <Trade/Trade.mqh>

CTrade TradeManager;
MqlTick currentTick;

input group "General Trading Parameters"
input int     MinSwingBars = 3;          // Minimum bars for swing formation
input double  MinSwingDistance = 100.0;  // Minimum distance for swing (in points)
input bool    UseWickForTrade = false;   // Use wick or body for trade levels

input group "Trailing Stop Parameters"
input bool UseTrailingStop = true;              // Enable trailing stops
input int BreakEvenAtPips = 500;                // Move to breakeven at this profit (pips) 
input int TrailStartAtPips = 600;               // Start trailing at this profit (pips)
input int TrailStepPips = 100;                  // Trail by this many pips 

Начнем с подключения торговых утилит и подготовки объектов, которые советник будет использовать во время выполнения: #include <Trade/Trade.mqh> дает нам класс CTrade, а CTrade TradeManager создает экземпляр, который обрабатывает отправку ордеров, их изменение и закрытие безопасным способом на более высоком уровне, чтобы нам не приходилось управлять вызовами необработанных тикетов. MqlTick currentTick объявляется, чтобы сохранить использование последнего тика (bid/ask/время) при принятии решений об исполнении. Далее мы предоставляем общие торговые параметры в качестве входных параметров, чтобы трейдер мог настроить поведение без изменения кода: MinSwingBars контролирует, сколько соседних баров требуется для того, чтобы мы назвали бар колебанием (уменьшая ложные колебания, если они поднимаются), MinSwingDistance устанавливает минимальный размер колебания (измеряемый в пунктах), чтобы не учитывать незначительный шум, а UseWickForTrade позволяет выбирать, используются ли в торговых уровнях тени свечей (более агрессивно, фиксирует экстремумы) или тела (более консервативно).

Затем определяем параметры риска и трейлинга, чтобы управление сделками было настраиваемым и прозрачным. UseTrailingStop включает или выключает логику трейлинга, BreakEvenAtPips указывает, при какой прибыли (в пипсах) мы переводим стоп в безубыток, чтобы устранить риск снижения. TrailStartAtPips - это порог прибыли, при котором фактически начинается трейлинг, а TrailStepPips - это степень детализации, с которой стоп следует за ценой (перемещайте стоп каждые X пунктов). Эти параметры позволяют контролировать, когда прибыль защищена и насколько сильно стоп-лосс преследует цену.

//+------------------------------------------------------------------+
//| Swing detection structure                                        |
//+------------------------------------------------------------------+
struct SwingPoint
{
    int       barIndex;
    double    price;
    datetime  time;
    bool      isHigh;
    double    bodyHigh;
    double    bodyLow;
};

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Clean up all objects on deinit
    ObjectsDeleteAll(0, -1, -1);
}

Затем мы определяем пользовательскую структуру под названием SwingPoint, которая служит основой для того, как система сохраняет и отслеживает каждое обнаруженное колебание. Эта структура хранит всю необходимую информацию о колебаниях в одном месте: barIndex записывает точный бар, на котором произошло колебание, цена удерживает ключевой уровень колебания (максимум или минимум) и временные метки, когда оно сформировалось. Флаг isHigh различает максимумы и минимумы колебаний, позволяя алгоритму решать, готовиться ли к продаже или покупке. Кроме того, bodyHigh и bodyLow отображают диапазон значений тела свечи — это полезно, когда системе необходимо различать, использовать ли тени или тела свечей для совершения сделок, на основании выбранных трейдером настроек ввода.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!isNewBar())
        return;
    
    // Detect swings and manage trading logic
    ManageTradingLogic();
    
    if(UseTrailingStop) ManageOpenTrades();
}

Функция OnTick() является ядром любого советника на MQL5, вызываемого на каждом тике. Внутри мы сначала проверяем isNewBar(), чтобы убедиться, что логика выполняется только один раз для каждой новой свечи, избегая дублирования сигналов в пределах одного бара. После подтверждения система вызывает функцию ManageTradingLogic() для обнаружения новых точек поворота и исполнения сделок. Если включены трейлинг-стопы, функция ManageOpenTrades() также запускается для обновления уровней стопа и динамического закрепления прибыли.

//+------------------------------------------------------------------+
//| Manage trading logic                                             |
//+------------------------------------------------------------------+
void ManageTradingLogic()
{
    static SwingPoint lastSwingLow = {-1, 0, 0, false, 0, 0};
    static SwingPoint lastSwingHigh = {-1, 0, 0, true, 0, 0};
    static bool lookingForHigh = false;
    static bool lookingForLow = false;
    
    // Detect current swings
    SwingPoint currentSwing;
    if(!DetectSwing(currentSwing))
        return;
    
    // Bullish scenario logic
    if(!lookingForHigh && !currentSwing.isHigh) // New swing low detected
    {
        lastSwingLow = currentSwing;
        lookingForHigh = true;
        lookingForLow = false;
        Print("Swing Low detected at: ", DoubleToString(currentSwing.price, _Digits), " Time: ", TimeToString(currentSwing.time));
        ExecuteTrade(ORDER_TYPE_BUY, "SwingLow_Buy");
        // Draw swing low
        string swingName = "SwingLow_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 217, clrBlue, 1);
    }
    else if(lookingForHigh && currentSwing.isHigh) // Swing high after swing low
    {
        lastSwingHigh = currentSwing;
        lookingForHigh = false;
        Print("Swing High detected after Swing Low.");
        
        // Draw swing high
        string swingName = "SwingHigh_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 218, clrRed, -1);
    }
    
    // Bearish scenario logic
    if(!lookingForLow && currentSwing.isHigh) // New swing high detected
    {
        lastSwingHigh = currentSwing;
        lookingForLow = true;
        lookingForHigh = false;
        Print("Swing High detected at: ", DoubleToString(currentSwing.price, _Digits), " Time: ", TimeToString(currentSwing.time));
        ExecuteTrade(ORDER_TYPE_SELL, "SwingHigh_Sell");
        // Draw swing high
        string swingName = "SwingHigh_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 218, clrRed, -1);
    }
    else if(lookingForLow && !currentSwing.isHigh) // Swing low after swing high
    {
        lastSwingLow = currentSwing;
        lookingForLow = false;
        Print("Swing Low detected after Swing High.");
        
        // Draw swing low
        string swingName = "SwingLow_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 217, clrBlue, 1);
    }
}

Функция ManageTradingLogic() — это мозг системы, принимающий все решения на основе вновь обнаруженных колебаний. Внутри мы инициализируем две статические переменные, lastSwingLow и lastSwingHigh, для хранения самых последних точек поворота, чтобы система всегда знала, где произошел последний структурный поворот. Такие флаги, как lookingForHigh и lookingForLow, помогают советнику запоминать, какого рода колебания он ожидает в следующий раз, гарантируя, что на каждое движение цены он реагирует логически, а не случайным образом. Как только формируется новый бар, система вызывает функцию DetectSwing(), чтобы определить, произошло ли действительное колебание, и если нет, то просто ждет следующего обновления бара.

Когда формируется бычий сценарий, то есть обнаружен новый минимум колебаний, система распознает это как потенциальное давление со стороны покупателей. Она обновляет запись lastSwingLow, переключается на поиск следующего максимума колебаний и мгновенно исполняет сделку на покупку с помощью ExecuteTrade(ORDER_TYPE_BUY, "SwingLow_Buy"). Чтобы сделать это событие визуально наглядным, советник также рисует на графике синюю метку в месте формирования минимума колебаний, давая трейдерам возможность видеть, как алгоритм интерпретирует структуру цены в реальном времени. Как только после этого появляется максимум колебаний, он просто обозначается красным цветом как подтверждение завершения бычьего движения.

В медвежьем сценарии логика повторяется зеркально. Обнаружение максимума колебаний указывает на потенциальное давление со стороны продавцов. Советник записывает это колебание, переключает флаги направления и совершает сделку на продажу с помощью ExecuteTrade(ORDER_TYPE_SELL, "SwingHigh_Sell"). Для наглядности, колебание на графике обозначено красным цветом, а после достижения рынком следующего минимума — синим маркером. Такое попеременное распознавание максимумов и минимумов гарантирует, что система постоянно адаптируется к текущему ценовому потоку, автоматически совершая сделки, соответствующие последней рыночной структуре, без какого-либо ручного вмешательства.

//+------------------------------------------------------------------+
//| Detect swing points dynamically                                  |
//+------------------------------------------------------------------+
bool DetectSwing(SwingPoint &swing)
{
    swing.barIndex = -1;
    
    int barsToCheck = MinSwingBars * 2 + 1;
    if(Bars(_Symbol, _Period) < barsToCheck) 
        return false;
    
    // Check multiple recent bars for swings
    for(int i = MinSwingBars; i <= MinSwingBars + 5; i++)
    {
        if(i >= Bars(_Symbol, _Period)) break;
        
        // Check for swing high
        if(IsSwingHigh(i))
        {
            swing.barIndex = i;
            swing.price = UseWickForTrade ? iHigh(_Symbol, _Period, i) : 
                          MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.time = iTime(_Symbol, _Period, i);
            swing.isHigh = true;
            swing.bodyHigh = MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.bodyLow = MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            return true;
        }
        // Check for swing low
        else if(IsSwingLow(i))
        {
            swing.barIndex = i;
            swing.price = UseWickForTrade ? iLow(_Symbol, _Period, i) : 
                          MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.time = iTime(_Symbol, _Period, i);
            swing.isHigh = false;
            swing.bodyHigh = MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.bodyLow = MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            return true;
        }
    }
    
    return false;
}

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

//+------------------------------------------------------------------+
//| Check if bar is swing high                                       |
//+------------------------------------------------------------------+
bool IsSwingHigh(int barIndex)
{
    if(barIndex < MinSwingBars || barIndex >= Bars(_Symbol, _Period) - MinSwingBars)
        return false;
        
    double currentHigh = iHigh(_Symbol, _Period, barIndex);
    
    // Check left side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double leftHigh = iHigh(_Symbol, _Period, barIndex + i);
        if(leftHigh >= currentHigh - MinSwingDistance * _Point)
            return false;
    }
    
    // Check right side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double rightHigh = iHigh(_Symbol, _Period, barIndex - i);
        if(rightHigh >= currentHigh - MinSwingDistance * _Point)
            return false;
    }
    
    return true;
}

//+------------------------------------------------------------------+
//| Check if bar is swing low                                        |
//+------------------------------------------------------------------+
bool IsSwingLow(int barIndex)
{
    if(barIndex < MinSwingBars || barIndex >= Bars(_Symbol, _Period) - MinSwingBars)
        return false;
        
    double currentLow = iLow(_Symbol, _Period, barIndex);
    
    // Check left side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double leftLow = iLow(_Symbol, _Period, barIndex + i);
        if(leftLow <= currentLow + MinSwingDistance * _Point)
            return false;
    }
    
    // Check right side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double rightLow = iLow(_Symbol, _Period, barIndex - i);
        if(rightLow <= currentLow + MinSwingDistance * _Point)
            return false;
    }
    
    return true;
}

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

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

//+------------------------------------------------------------------+
//| Draw swing point on chart                                        |
//+------------------------------------------------------------------+
void drawswing(string objName, datetime time, double price, int arrCode, color clr, int direction)
{
   if(ObjectFind(0, objName) < 0)
   {
      // Create arrow object
      if(!ObjectCreate(0, objName, OBJ_ARROW, 0, time, price))
      {
         Print("Error creating swing object: ", GetLastError());
         return;
      }
      
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3);
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      if(direction > 0)
      {
         ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      }
      else if(direction < 0)
      {
         ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      }
      
      // Create text label
      string textName = objName + "_Text";
      string text = DoubleToString(price, _Digits);
      
      if(ObjectCreate(0, textName, OBJ_TEXT, 0, time, price))
      {
         ObjectSetString(0, textName, OBJPROP_TEXT, text);
         ObjectSetInteger(0, textName, OBJPROP_COLOR, clr);
         ObjectSetInteger(0, textName, OBJPROP_FONTSIZE, 8);
         
         // Adjust text position based on direction
         double offset = (direction > 0) ? - (100 * _Point) : (100 * _Point);
         ObjectSetDouble(0, textName, OBJPROP_PRICE, price + offset);
      }
   }
}

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

//+------------------------------------------------------------------+
//| Helper functions                                                 |
//+------------------------------------------------------------------+
bool isNewBar()
{
   static datetime lastBar;
   datetime currentBar = iTime(_Symbol, _Period, 0);
   if(lastBar != currentBar)
   {
      lastBar = currentBar;
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| Execute trade with proper risk management                        |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment)
{
   // Calculate position size based on risk
   double lotSize = 0.03;
   if(lotSize <= 0)
   {
      Print("Failed to calculate position size");
      return;
   }
   
   // Get current tick data
   if(!SymbolInfoTick(_Symbol, currentTick))
   {
      Print("Failed to get current tick data. Error: ", GetLastError());
      return;
   }
   
   // Define stop levels in points (adjust these values as needed)
   int stopLossPoints = 600;
   int takeProfitPoints = 2555;
   
   // 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);
   }
   
   // Validate stop levels before sending the trade
   if(!ValidateStopLevels(orderType, currentTick.ask, currentTick.bid, stopLoss, takeProfit))
   {
      Print("Invalid stop levels. Trade not executed.");
      return;
   }
   
   // Execute trade
   bool requestSent;
   if(orderType == ORDER_TYPE_BUY)
   {
      requestSent = TradeManager.Buy(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
   }
   else
   {
      requestSent = TradeManager.Sell(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
   }
   
   // Check if the request was sent successfully and then check the server's result
   if(requestSent)
   {
      // Check the server's return code from the trade operation
      uint result = TradeManager.ResultRetcode();
      if(result == TRADE_RETCODE_DONE || result == TRADE_RETCODE_DONE_PARTIAL)
      {
         Print("Trade executed successfully. Ticket: ", TradeManager.ResultDeal());
      }
      else if(result == TRADE_RETCODE_REQUOTE || result == TRADE_RETCODE_TIMEOUT || result == TRADE_RETCODE_PRICE_CHANGED)
      {
         Print("Trade failed due to price change. Consider implementing a retry logic. Retcode: ", TradeManager.ResultRetcodeDescription());
         // Here you can add logic to re-check prices and re-send the request
      }
      else
      {
         Print("Trade execution failed. Retcode: ", TradeManager.ResultRetcodeDescription());
         // Handle other specific errors like TRADE_RETCODE_INVALID_STOPS (10016)
      }
   }
   else
   {
      Print("Failed to send trade request. Last Error: ", GetLastError());
   }
}

Вспомогательные функции играют важную роль в обеспечении эффективной работы EA и реагировании только в случае необходимости. Например, функция isNewBar() предотвращает выполнение повторяющейся логики на каждом тике, подтверждая формирование новой свечи до начала каких-либо вычислений или совершения сделок. Это достигается путем сохранения времени последнего обработанного бара и сравнения его с текущим; если они различаются, это означает, что открылся новый бар, и функция возвращает значение true. Этот простой, но крайне важный механизм сокращает ненужные вычисления и гарантирует, что сделки оцениваются только один раз за свечу, что обеспечивает чистоту, эффективность и синхронизацию системы с новыми рыночными данными.

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

//+------------------------------------------------------------------+
//| Validate stop levels against broker requirements                 |
//+------------------------------------------------------------------+
bool ValidateStopLevels(ENUM_ORDER_TYPE orderType, double ask, double bid, double &sl, double &tp)
{
   double spread = ask - bid;
   // Get the minimum allowed stop distance in points
   int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   double minDist = stopLevel * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(orderType == ORDER_TYPE_BUY)
   {
      // Check if Stop Loss is too close to or above the current Bid price
      if(sl >= bid - minDist) 
      {
         // Option 1: Adjust SL to the minimum allowed distance
         sl = NormalizeDouble(bid - minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Buy Stop Loss adjusted to minimum allowed level.");
      }
      // Check if Take Profit is too close to or below the current Ask price
      if(tp <= ask + minDist)
      {
         // Option 1: Adjust TP to the minimum allowed distance
         tp = NormalizeDouble(ask + minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Buy Take Profit adjusted to minimum allowed level.");
      }
   }
   else // ORDER_TYPE_SELL
   {
      // Check if Stop Loss is too close to or below the current Ask price
      if(sl <= ask + minDist)
      {
         // Option 1: Adjust SL to the minimum allowed distance
         sl = NormalizeDouble(ask + minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Sell Stop Loss adjusted to minimum allowed level.");
      }
      // Check if Take Profit is too close to or above the current Bid price
      if(tp >= bid - minDist)
      {
         // Option 1: Adjust TP to the minimum allowed distance
         tp = NormalizeDouble(bid - minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Sell Take Profit adjusted to minimum allowed level.");
      }
   }
   
   return true;
}

Функция ValidateStopLevels() гарантирует, что уровни стоп-лосса и тейк-профита для каждой сделки соответствуют минимальным требованиям брокера по расстоянию, чтобы предотвратить отклонение недействительных ордеров. Она получает значение параметра SYMBOL_TRADE_STOPS_LEVEL брокера, рассчитывает минимально допустимое расстояние стоп-лосса в пунктах и ​​корректирует как стоп-лосс, так и тейк-профит, если они установлены слишком близко к рыночным ценам bid или ask. Эта защитная мера не только гарантирует соблюдение правил брокера, но и стабилизирует исполнение ордеров, автоматически корректируя рискованные или недопустимые уровни цен перед отправкой сделки на сервер.

//+------------------------------------------------------------------+
//| Trailing stop function                                           |
//+------------------------------------------------------------------+
void ManageOpenTrades()
{
   if(!UseTrailingStop) return;

   int total = PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
   {
      // get ticket (PositionGetTicket returns ulong; it also selects the position)
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;

      // ensure the position is selected (recommended)
      if(!PositionSelectByTicket(ticket)) continue;

      // Optional: only operate on same symbol or your EA's magic number
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      // if(PositionGetInteger(POSITION_MAGIC) != MyMagicNumber) continue;

      // read position properties AFTER selecting
      double open_price   = PositionGetDouble(POSITION_PRICE_OPEN);
      double current_price= PositionGetDouble(POSITION_PRICE_CURRENT);
      double current_sl   = PositionGetDouble(POSITION_SL);
      double current_tp   = PositionGetDouble(POSITION_TP);
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      // pip size
      double pip_price = PipsToPrice(1);

      // profit in pips (use current_price returned above)
      double profit_price = (pos_type == POSITION_TYPE_BUY) ? (current_price - open_price)
                                                             : (open_price - current_price);
      double profit_pips = profit_price / pip_price;
      if(profit_pips <= 0) continue;

      // get broker min stop distance (in price units)
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
      double stop_level_points = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
      double stopLevelPrice = stop_level_points * point;

      // get market Bid/Ask for stop-level checks
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

      // -------------------------
      // 1) Move to breakeven
      // -------------------------
      if(profit_pips >= BreakEvenAtPips)
      {
         double breakeven = open_price;
         // small adjustment to help account for spread/commissions (optional)
         if(pos_type == POSITION_TYPE_BUY)  breakeven += point; 
         else                                breakeven -= point;

         // Check stop-level rules: for BUY SL must be >= (bid - stopLevelPrice) distance below bid
         if(pos_type == POSITION_TYPE_BUY)
         {
            if((bid - breakeven) >= stopLevelPrice) // allowed by server
            {
               if(breakeven > current_sl) // only move SL up
               {
                  if(!TradeManager.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
         else // SELL
         {
            if((breakeven - ask) >= stopLevelPrice)
            {
               if(current_sl == 0.0 || breakeven < current_sl) // move SL down
               {
                  if(!TradeManager.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
      } // end breakeven

      // -------------------------
      // 2) Trailing in steps after TrailStartAtPips
      // -------------------------
      if(profit_pips >= TrailStartAtPips)
      {
         double extra_pips = profit_pips - TrailStartAtPips;
         int step_count = (int)(extra_pips / TrailStepPips);

         // compute desired SL relative to open_price (as per your original request)
         double desiredOffsetPips = (double)(TrailStartAtPips + step_count * TrailStepPips);
         double new_sl_price;

         if(pos_type == POSITION_TYPE_BUY)
         {
            new_sl_price = open_price + PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Bid
            if((bid - new_sl_price) < stopLevelPrice)
               new_sl_price = bid - stopLevelPrice;

            if(new_sl_price > current_sl) // only move SL up
            {
               if(!TradeManager.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Buy) ticket %I64u error %d", ticket, GetLastError());
            }
         }
         else // SELL
         {
            new_sl_price = open_price - PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Ask
            if((new_sl_price - ask) < stopLevelPrice)
               new_sl_price = ask + stopLevelPrice;

            if(current_sl == 0.0 || new_sl_price < current_sl) // only move SL down (more profitable)
            {
               if(!TradeManager.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Sell) ticket %I64u error %d", ticket, GetLastError());
            }
         }
      }
   } 
}

//+------------------------------------------------------------------+
//| Helper: convert pips -> price (taking 3/5-digit fractional pips) |
//+------------------------------------------------------------------+
double PipsToPrice(int pips)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int digits   = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double pip   = (digits == 3 || digits == 5) ? point * 10.0 : point;
   return(pips * pip);
}

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

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

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


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

Тестирование на истории оценивалось на таймфрейме 2H в течение приблизительно 2-месячного окна тестирования (с 08 апреля 2025 г. по 29 июля 2025 г.) с настройками по умолчанию.

Кривая эквити

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


Заключение

В заключение, мы разработали архитектуру Dynamic Swing Architecture: распознавание структуры рынка от колебаний до автоматического исполнения — систему, предназначенную для интерпретации необработанных рыночных движений на основе логики, основанной на колебаниях рынка, и преобразования их в интеллектуальные торговые решения. Архитектура начинается с точного определения колебаний, выявления критических максимумов и минимумов, которые формируют основу структуры рынка. Затем она интегрирует исполнение с управлением рисками, гарантируя, что каждая сделка совершается с надлежащей проверкой. Для поддержания эффективности торговли система использует автоматизированную логику трейлинга, которая адаптируется к движению цены, переводя стоп-лоссы в безубыток и разумно отслеживая прибыль по мере развития трендов. Каждый компонент — от структурного анализа до исполнения и управления — работает слаженно, формируя самоподдерживающуюся, адаптивную торговую платформу.

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
TahianaBE
TahianaBE | 22 окт. 2025 в 11:20
Спасибо за прекрасную статью.
Hlomohang John Borotho
Hlomohang John Borotho | 23 окт. 2025 в 22:11
TahianaBE #:
Спасибо за прекрасную статью.
Пожалуйста.
Tonij Trisno
Tonij Trisno | 4 нояб. 2025 в 06:47
Здравствуйте. Спасибо за этот замечательный советник и результаты бэктестов. Могу ли я узнать, на какой паре/символе вы тестировали его на таймфрейме H2, который дал результат в вашей статье.
Это поможет нам подтвердить, что у нас тоже правильный такой же/подобный результат. Спасибо.
Hlomohang John Borotho
Hlomohang John Borotho | 6 нояб. 2025 в 21:51
Tonij Trisno бэктестов. Могу ли я узнать, на какой паре/символе вы тестировали его на таймфрейме H2, который дал результат в вашей статье.
Это поможет нам подтвердить, что у нас тоже правильный такой же/подобный результат. Спасибо.
Здравствуйте, для получения одинаковых результатов, пожалуйста, обратите внимание на окно тестирования "(08 апреля 2025 - 29 июля 2025)".
Архитектура машинного обучения в MetaTrader 5 (Часть 6): Проектирование системы кэширования промышленного уровня Архитектура машинного обучения в MetaTrader 5 (Часть 6): Проектирование системы кэширования промышленного уровня
Устали смотреть на индикаторы выполнения вместо того, чтобы тестировать торговые стратегии? Традиционное кэширование не подходит для финансового машинного обучения, что приводит к потере результатов вычислений и вынуждает вас к повторному запуску, что вызывает раздражение. Мы разработали сложную архитектуру кэширования, учитывающую специфику финансовых данных — временные зависимости, сложные структуры данных и постоянную угрозу смещения look-ahead. Наша трёхслойная система обеспечивает значительное повышение скорости, автоматически отбрасывая устаревшие результаты и предотвращая утечку ценных данных. Хватит ждать результатов расчетов — начинайте действовать в темпе, которого требуют рынки.
Алгоритм оптимизации грифов — Buzzard Optimization Algorithm (BUZOA) Алгоритм оптимизации грифов — Buzzard Optimization Algorithm (BUZOA)
BUZOA — популяционный метаэвристический алгоритм, в котором каждый агент на каждой итерации случайно выбирает одну из трёх тактик охоты: узкий поиск вокруг личного рекорда, классический PSO-шаг к лидеру стаи или полную телепортацию в случайную точку пространства. В статье разбирается реализация алгоритма на MQL5, показывается найденная в оригинальной формулировке ошибка знака коэффициента и приводятся результаты бенчмарка на стандартном тестовом стенде.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Разработка инструментария для анализа Price Action (Часть 35): Обучение и развертывание прогнозных моделей Разработка инструментария для анализа Price Action (Часть 35): Обучение и развертывание прогнозных моделей
Исторические данные – вовсе не "мусор", а основа любого надежного рыночного анализа. В этой статье мы шаг за шагом пройдем путь от сбора истории до ее использования для обучения прогностической модели, а затем – до развертывания этой модели для прогнозирования цен в реальном времени. Давайте разберемся, как это сделать.