English Deutsch 日本語
preview
Автоматизация торговых стратегий с помощью MQL5 (Часть 47): Торговая система Nick Rypock Trailing Reverse (NRTR) с поддержкой хеджирования

Автоматизация торговых стратегий с помощью MQL5 (Часть 47): Торговая система Nick Rypock Trailing Reverse (NRTR) с поддержкой хеджирования

MetaTrader 5Трейдинг |
68 3
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В нашей предыдущей статье (часть 46) мы создали торговую систему Liquidity Sweep on Break of Structure на языке MQL5, которая определяет зоны ликвидности, выявляет пробои структуры и исполняет сделки с настраиваемыми параметрами риска и визуальными индикаторами. В части 47 мы создаем систему Nick Rypock Trailing Reverse (NRTR), которая использует канальные сигналы разворота для входов по тренду; также в нее добавлены хеджирование, динамический трейлинг-стоп и функции управления рисками. Мы рассмотрим следующие темы:

  1. Разбор стратегии Nick Rypock Trailing Reverse и ее компонентов
  2. Реализация на MQL5
  3. Тестирование на исторических данных
  4. Заключение

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


Разбор стратегии Nick Rypock Trailing Reverse и ее компонентов

Стратегия Nick Rypock Trailing Reverse выявляет потенциальные развороты тренда, отслеживая появление динамических уровней поддержки в канале NRTR, который строится на основе значений Average True Range (среднего истинного диапазона), умноженных на заданный пользователем коэффициент, чтобы сформировать верхнюю и нижнюю границы, адаптирующиеся к волатильности. Сигнал на вход возникает при появлении нового уровня поддержки: покупка открывается в начале восходящей поддержки (восходящего канала), а продажа – в начале нисходящей поддержки (нисходящего канала). При включенном режиме хеджирования допускается удержание противоположных позиций. Управление рисками здесь встроено в систему: размер лота рассчитывается автоматически пропорционально балансу, средствам счета или свободной марже, чтобы ограничить риск по каждой сделке, а защиту с учетом волатильности обеспечивают фиксированные или основанные на ATR уровни стоп-лосса и тейк-профита.

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

NRTR FRAMEWORK


Реализация на MQL5

Чтобы создать программу на языке MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку Experts, нажмите кнопку "Создать" и следуйте подсказкам для создания файла. После этого в среде разработки нужно объявить входные параметры и глобальные переменные, которые будут использоваться во всей программе.

//+------------------------------------------------------------------+
//|                     NRTR - Nick Rypock Trailing Reverse - EA.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_RISK_BASE {
   RISK_BASE_EQUITY    = 1, // Equity
   RISK_BASE_BALANCE   = 2, // Balance
   RISK_BASE_FREEMARGIN = 3 // Free Margin
};

enum ENUM_RISK_DEFAULT_SIZE {
   RISK_DEFAULT_FIXED = 1, // Fixed
   RISK_DEFAULT_AUTO  = 2  // Auto
};

enum ENUM_MODE_SL {
   SL_FIXED = 0, // Fixed
   SL_AUTO  = 1  // Auto
};

enum ENUM_MODE_TP {
   TP_FIXED = 0, // Fixed
   TP_AUTO  = 1  // Auto
};

enum ENUM_TRAILING_TYPE {
   TRAILING_NONE          = 0, // None
   TRAILING_POINTS        = 1, // Points
   TRAILING_SUPPORT_LEVELS = 2 // Support Levels
};

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "Risk Management Settings"
input ENUM_RISK_DEFAULT_SIZE RiskDefaultSize = RISK_DEFAULT_FIXED; // Default Risk Size
input double DefaultLotSize                  = 0.01;               // Default Lot Size
input ENUM_RISK_BASE RiskBase                = RISK_BASE_BALANCE;  // Risk Base
input int MaxRiskPerTrade                    = 5;                  // Max Risk Per Trade (%)
double MinLotSize                            = 0.01;               // Min Lot Size
double MaxLotSize                            = 100;                // Max Lot Size
input int MaxPositions                       = 1;                  // Max Positions
input bool HedgeMode                         = true;               // Hedge Mode
bool CloseOnReversalSignal                   = false;              // Close On Reversal Signal

input group "Stop-Loss and Take-Profit Settings"
input int DefaultStopLossPoints              = 300;                // Default Stop Loss (Points)
input int DefaultTakeProfitPoints            = 300;                // Default Take Profit (Points)
input bool CloseOnStopLossHit                = false;              // Close On Stop Loss Hit
input bool CloseOnTakeProfitHit              = false;              // Close On Take Profit Hit

input group "Additional Settings"
input int MagicNumber                        = 1234567890;         // Magic Number

input group "Trailing Stop Loss"
input ENUM_TRAILING_TYPE TrailingType        = TRAILING_POINTS;    // Trailing Type
input ushort TrailingFrequencySeconds        = 10;                 // Trailing Frequency (Seconds)
input ushort SignalCheckFrequencySeconds     = 10;                 // Signal Check Frequency (Seconds)
input ushort TrailingStopPoints              = 120;                // Trailing Stop (Points)
input ushort TrailingStepPoints              = 100;                // Trailing Step (Points)
input ushort BreakEvenPoints                 = 10;                 // Break Even (Points)
input ushort BreakEvenTriggerPoints          = 30;                 // Break Even Trigger (Points)

input group "NRTR Channel Settings"
input int NRTR_ATR_Period                    = 40;                 // NRTR ATR Period
input double NRTR_Multiplier                 = 2.0;                // NRTR Multiplier
input bool NRTR_Show_Price_Label             = true;               // Show Price Label

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
double CurrentStopLossPoints = DefaultStopLossPoints;              //--- Initialize current SL points
double CurrentTakeProfitPoints = DefaultTakeProfitPoints;          //--- Initialize current TP points
double CurrentTrailingStopPoints = TrailingStopPoints;             //--- Initialize current trailing stop points
double CurrentTrailingStepPoints = TrailingStepPoints;             //--- Initialize current trailing step points
bool PrintLog = false;                                             //--- Set print log flag
datetime LastTrailingTime = 0;                                     //--- Initialize last trailing time
ENUM_MODE_SL StopLossMode = SL_FIXED;                              //--- Set SL mode
ENUM_MODE_TP TakeProfitMode = TP_FIXED;                            //--- Set TP mode

double AtrMultiplierForStopLoss = 2;                               //--- Set ATR multiplier for SL
double AtrMultiplierForTakeProfit = 3;                             //--- Set ATR multiplier for TP

CTrade TradeObject;                                                //--- Declare trade object
CSymbolInfo SymbolInfo;                                            //--- Declare symbol info
int TrendIndicatorHandle = -1;                                     //--- Initialize trend handle
double CurrentTrendValue, PreviousTrendValue;                      //--- Declare trend values

double CurrentTrendDirection, PreviousTrendDirection;              //--- Declare trend directions
double CurrentUpSupport, PreviousUpSupport;                        //--- Declare up supports
double CurrentDownSupport, PreviousDownSupport;                    //--- Declare down supports
double CurrentNrtrAtr, PreviousNrtrAtr;                            //--- Declare NRTR ATR values

Реализация начинается с подключения основных библиотек MQL5: "#include <Trade\Trade.mqh>", "#include <Trade\PositionInfo.mqh>" и "#include <Trade\SymbolInfo.mqh>". Они дают доступ к классам для торговых операций, информации о позициях и данным по символу. Затем определяются несколько перечислений, которые позволяют систематизировать пользовательские настройки. Перечисление ENUM_RISK_BASE задает варианты расчета риска на основе средств счета, баланса или свободной маржи. Перечисление ENUM_RISK_DEFAULT_SIZE используется для выбора между фиксированным и автоматическим расчетом лота. Для стоп-лосса и тейк-профита используются ENUM_MODE_SL и ENUM_MODE_TP с вариантами фиксированного и автоматического режимов. Кроме того, ENUM_TRAILING_TYPE задает тип трейлинг-стопа: отсутствует, по пунктам или по уровням поддержки.

Далее пользовательские входные параметры распределяются по группам для удобства работы. В разделе управления рисками используются RiskDefaultSize для выбора метода расчета размера, DefaultLotSize как фиксированное значение объема, RiskBase как база расчета, MaxRiskPerTrade как процентное ограничение риска, минимальный и максимальный размер лота (заданные как глобальные переменные), MaxPositions для ограничения числа открытых сделок, HedgeMode для разрешения хеджирования и CloseOnReversalSignal для выхода по противоположному сигналу. Для настройки стоп-лосса и тейк-профита добавляются DefaultStopLossPoints и DefaultTakeProfitPoints в пунктах, а также булевы флаги для включения закрытия при достижении этих уровней.

В дополнительных настройках задается MagicNumber (магическое число) для уникальной идентификации сделок. Далее задаются параметры трейлинг-стопа: TrailingType, интервалы в секундах для обновления трейлинга и проверки сигналов, TrailingStopPoints, TrailingStepPoints, BreakEvenPoints и BreakEvenTriggerPoints. В группу параметров канала NRTR входят NRTR_ATR_Period, NRTR_Multiplier и NRTR_Show_Price_Label, позволяющие настраивать индикатор прямо из программы.

Наконец, объявляются глобальные переменные для хранения ключевых значений: текущих значений в пунктах для стопов и трейлинга, флага логирования, времени последнего трейлинга, режимов стоп-лосса и тейк-профита, множителей ATR для динамических уровней, а также объектов CTrade и CSymbolInfo. Изначально хэндл трендового индикатора устанавливается в -1; также объявляются переменные типа double для хранения текущих и предыдущих значений тренда, направлений, уровней поддержки и значений NRTR ATR. Далее инициализируется индикатор. Для того чтобы код оставался организованным и модульным, далее логика по возможности выносится в отдельные функции.

//+------------------------------------------------------------------+
//| Initialize expert                                                |
//+------------------------------------------------------------------+
int OnInit() {
   if (!PreInitChecks()) return INIT_FAILED;                     //--- Perform pre-init checks
   SymbolInfo.Name(Symbol());                                    //--- Set symbol name
   if (!InitIndicatorHandles()) return INIT_FAILED;              //--- Initialize indicator handles
   InitTradeObject();                                            //--- Initialize trade object
   return INIT_SUCCEEDED;                                        //--- Return success
}

//+------------------------------------------------------------------+
//| Perform pre-init checks                                          |
//+------------------------------------------------------------------+
bool PreInitChecks() {
   if (MaxLotSize < MinLotSize) {                                //--- Check lot sizes
      Print("MaxLotSize cannot be less than MinLotSize");        //--- Print error
      return false;                                              //--- Return failure
   }
   return true;                                                  //--- Return success
}

//+------------------------------------------------------------------+
//| Initialize indicator handles                                     |
//+------------------------------------------------------------------+
bool InitIndicatorHandles() {
   TrendIndicatorHandle = iCustom(_Symbol, _Period, "Free Indicators\\NRTR Channel", NRTR_ATR_Period, NRTR_Multiplier, NRTR_Show_Price_Label); //--- Create NRTR handle
   if (TrendIndicatorHandle == INVALID_HANDLE) {                 //--- Check handle
      PrintFormat("Error creating NRTR handle - %d", GetLastError()); //--- Print error
      return false;                                              //--- Return failure
   }
   return true;                                                  //--- Return success
}

//+------------------------------------------------------------------+
//| Initialize trade object                                          |
//+------------------------------------------------------------------+
void InitTradeObject() {
   TradeObject.SetExpertMagicNumber(MagicNumber);                //--- Set magic number
}

В обработчике событий OnInit сначала вызывается PreInitChecks для проверки начальных условий. Если проверка завершается неуспешно, возвращается INIT_FAILED , чтобы программа не продолжала работу с некорректными настройками. Затем в объекте SymbolInfo задается имя текущего символа. Далее вызывается InitIndicatorHandles для загрузки индикатора канала NRTR; при ошибке возвращается INIT_FAILED. Затем вызовом InitTradeObject инициализируется торговый объект, а в конце возвращается INIT_SUCCEEDED, что подтверждает успешную инициализацию.

Функция PreInitChecks проверяет, что MaxLotSize не меньше MinLotSize. Если условие нарушено, выводится сообщение об ошибке и возвращается false; иначе возвращается true, и инициализация продолжается. Внутри InitIndicatorHandles создается хэндл индикатора TrendIndicatorHandle с помощью iCustom с передачей символа, периода, пути к индикатору и параметров NRTR_ATR_Period, NRTR_Multiplier и NRTR_Show_Price_Label. Если хэндл равен INVALID_HANDLE, выводится ошибка с помощью GetLastError, и функция возвращает false; в противном случае возвращается true. Функция InitTradeObject настраивает объект TradeObject, устанавливая для него значение MagicNumber, чтобы идентифицировать сделки, открытые данной программой. Очень важно запустить программу и убедиться, что индикатор загружен успешно, поскольку именно он служит основным источником сигналов.

INITIAL INDICATOR LOAD TO THE PROGRAM

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

//+------------------------------------------------------------------+
//| Fetch indicator data                                             |
//+------------------------------------------------------------------+
bool FetchIndicatorData() {
   double atrBuffer[];                                           //--- Declare ATR buffer as dynamic
   double trendBuffer[];                                         //--- Declare trend buffer as dynamic
   double upSupportBuffer[];                                     //--- Declare up support buffer as dynamic
   double downSupportBuffer[];                                   //--- Declare down support buffer as dynamic
   ArrayResize(atrBuffer, 2);                                    //--- Resize to 2
   ArrayResize(trendBuffer, 2);                                  //--- Resize to 2
   ArrayResize(upSupportBuffer, 2);                              //--- Resize to 2
   ArrayResize(downSupportBuffer, 2);                            //--- Resize to 2
   ArraySetAsSeries(atrBuffer, true);                            //--- Set as series
   ArraySetAsSeries(trendBuffer, true);                          //--- Set as series
   ArraySetAsSeries(upSupportBuffer, true);                      //--- Set as series
   ArraySetAsSeries(downSupportBuffer, true);                    //--- Set as series
   int copyCount;                                                //--- Declare copy count
   bool dataReady = false;                                       //--- Set data ready flag
   int maxAttempts = 5;                                          //--- Set max attempts
   int delayMs = 200;                                            //--- Set delay ms
   int attempt = 0;                                              //--- Initialize attempt
   while (!dataReady && attempt < maxAttempts) {                 //--- Loop until ready
      dataReady = true;                                          //--- Assume ready
      copyCount = CopyBuffer(TrendIndicatorHandle, 5, 1, 2, atrBuffer); //--- Copy ATR
      if (copyCount < 2 || atrBuffer[0] == EMPTY_VALUE) {        //--- Check ATR copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set ATR values
         CurrentNrtrAtr = atrBuffer[0];                          //--- Set current ATR (recent)
         PreviousNrtrAtr = atrBuffer[1];                         //--- Set previous ATR (older)
      }
      copyCount = CopyBuffer(TrendIndicatorHandle, 4, 1, 2, trendBuffer); //--- Copy trend
      if (copyCount < 2) {                                       //--- Check trend copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set trend directions
         CurrentTrendDirection = trendBuffer[0];                 //--- Set current direction (recent)
         PreviousTrendDirection = trendBuffer[1];                //--- Set previous direction (older)
      }
      copyCount = CopyBuffer(TrendIndicatorHandle, 1, 1, 2, upSupportBuffer); //--- Copy up support
      if (copyCount < 2) {                                       //--- Check up copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set up supports
         CurrentUpSupport = upSupportBuffer[0];                  //--- Set current up (recent)
         PreviousUpSupport = upSupportBuffer[1];                 //--- Set previous up (older)
      }
      copyCount = CopyBuffer(TrendIndicatorHandle, 2, 1, 2, downSupportBuffer); //--- Copy down support
      if (copyCount < 2) {                                       //--- Check down copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set down supports
         CurrentDownSupport = downSupportBuffer[0];              //--- Set current down (recent)
         PreviousDownSupport = downSupportBuffer[1];             //--- Set previous down (older)
      }
      if (dataReady) {                                           //--- Check ready
         CurrentTrendValue = (CurrentTrendDirection > 0) ? CurrentUpSupport : ((CurrentTrendDirection < 0) ? CurrentDownSupport : EMPTY_VALUE); //--- Set current trend value
         PreviousTrendValue = (PreviousTrendDirection > 0) ? PreviousUpSupport : ((PreviousTrendDirection < 0) ? PreviousDownSupport : EMPTY_VALUE); //--- Set previous trend value
         if (CurrentTrendValue == EMPTY_VALUE || PreviousTrendValue == EMPTY_VALUE) dataReady = false; //--- Check values
      }
      attempt++;                                                 //--- Increment attempt
      Sleep(delayMs);                                            //--- Delay
   }
   if (!dataReady) {                                             //--- Check final ready
      Print("Failed to fetch indicator data");                   //--- Print error
      return false;                                              //--- Return failure
   }
   return true;                                                  //--- Return success
}

Функция FetchIndicatorData используется для надежного получения и сохранения значений из буферов индикатора канала NRTR. Сначала объявляются динамические массивы atrBuffer, trendBuffer, upSupportBuffer и downSupportBuffer. Затем с помощью ArrayResize размер каждого из них устанавливается равным двум элементам, а через ArraySetAsSeries, они переводятся в режим временного ряда, чтобы самые новые данные находились по индексу 0. Затем инициализируются переменные для числа скопированных элементов, флага готовности данных, максимального числа повторных попыток, равного 5, задержки в 200 миллисекунд и счетчика попыток, начинающегося с 0. В цикле while, который выполняется до готовности данных или исчерпания числа попыток, сначала предполагается, что данные готовы, а затем через CopyBuffer считываются данные двух последних баров из нужных буферов индикатора: буфер 5 – для значений ATR, буфер 4 – для направлений тренда, буфер 1 – для уровней восходящей поддержки и буфер 2 – для уровней нисходящей поддержки.

Если любое копирование завершается неудачно (то есть получено меньше 2 элементов) или последнее значение ATR равно EMPTY_VALUE, данные помечаются как неготовые. В противном случае текущие значения (индекс 0) и предыдущие значения (индекс 1) присваиваются глобальным переменным CurrentNrtrAtr, PreviousNrtrAtr, CurrentTrendDirection, PreviousTrendDirection, CurrentUpSupport, PreviousUpSupport, CurrentDownSupport и PreviousDownSupport. После успешного копирования всех буферов значение CurrentTrendValue вычисляется так: CurrentUpSupport – при положительном направлении, CurrentDownSupport – при отрицательном, иначе EMPTY_VALUE. Аналогично вычисляется и PreviousTrendValue. Если любое из значений тренда равно EMPTY_VALUE, флаг готовности сбрасывается.

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

//+------------------------------------------------------------------+
//| Count open positions                                             |
//+------------------------------------------------------------------+
int CountOpenPositions() {
   int count = 0;                                                //--- Initialize count
   int totalPositions = PositionsTotal();                        //--- Get total positions
   for (int i = 0; i < totalPositions; i++) {                    //--- Loop positions
      if (PositionGetSymbol(i) != Symbol()) continue;            //--- Skip wrong symbol
      if (MagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic
      count++;                                                   //--- Increment count
   }
   return count;                                                 //--- Return count
}

//+------------------------------------------------------------------+
//| Count open positions by type                                     |
//+------------------------------------------------------------------+
int CountOpenPositionsByType(ENUM_POSITION_TYPE positionType) {
   int count = 0;                                                //--- Initialize count
   int totalPositions = PositionsTotal();                        //--- Get total positions
   for (int i = 0; i < totalPositions; i++) {                    //--- Loop positions
      if (PositionGetSymbol(i) != Symbol()) continue;            //--- Skip wrong symbol
      if (MagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic
      if (PositionGetInteger(POSITION_TYPE) == positionType) count++; //--- Increment if match
   }
   return count;                                                 //--- Return count
}

//+------------------------------------------------------------------+
//| Open buy position                                                |
//+------------------------------------------------------------------+
bool OpenBuyPosition(string positionType) {
   double askPrice = SymbolInfoDouble(Symbol(), SYMBOL_ASK);               //--- Get ask price
   double stopLossPrice = CalculateStopLoss(ORDER_TYPE_BUY, askPrice);     //--- Calculate SL
   double takeProfitPrice = CalculateTakeProfit(ORDER_TYPE_BUY, askPrice); //--- Calculate TP
   double positionSize = CalculatePositionSize(stopLossPrice, askPrice);   //--- Calculate size
   string orderComment = positionType + " Buy";                            //--- Set comment
   if (!TradeObject.Buy(positionSize, Symbol(), 0, stopLossPrice, takeProfitPrice, orderComment)) { //--- Open buy
      PrintFormat("Failed to open BUY: %d", TradeObject.ResultRetcode());  //--- Print error
      return false;                                                        //--- Return failure
   }
   PrintFormat("%s Buy Position Opened Successfully", positionType);       //--- Print success
   return true;                                                            //--- Return success
}

//+------------------------------------------------------------------+
//| Open sell position                                               |
//+------------------------------------------------------------------+
bool OpenSellPosition(string positionType) {
   double bidPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID);               //--- Get bid price
   double stopLossPrice = CalculateStopLoss(ORDER_TYPE_SELL, bidPrice);    //--- Calculate SL
   double takeProfitPrice = CalculateTakeProfit(ORDER_TYPE_SELL, bidPrice); //--- Calculate TP
   double positionSize = CalculatePositionSize(stopLossPrice, bidPrice);   //--- Calculate size
   string orderComment = positionType + " Sell";                           //--- Set comment
   if (!TradeObject.Sell(positionSize, Symbol(), 0, stopLossPrice, takeProfitPrice, orderComment)) { //--- Open sell
      PrintFormat("Failed to open SELL: %d", TradeObject.ResultRetcode()); //--- Print error
      return false;                                                        //--- Return failure
   }
   PrintFormat("%s Sell Position Opened Successfully", positionType);      //--- Print success
   return true;                                                            //--- Return success
}

//+------------------------------------------------------------------+
//| Close all buy positions                                          |
//+------------------------------------------------------------------+
void CloseAllBuyPositions() {
   int total = PositionsTotal();                                           //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                                  //--- Loop backward
      if (PositionGetSymbol(i) != Symbol()) continue;                      //--- Skip wrong symbol
      if (PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) continue; //--- Skip non-buy
      if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;     //--- Skip wrong magic
      TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET));      //--- Close position
   }
   Print("All Buy Positions Closed");                                      //--- Print closed
}

//+------------------------------------------------------------------+
//| Close all sell positions                                         |
//+------------------------------------------------------------------+
void CloseAllSellPositions() {
   int total = PositionsTotal();                                           //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                                  //--- Loop backward
      if (PositionGetSymbol(i) != Symbol()) continue;                      //--- Skip wrong symbol
      if (PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL) continue; //--- Skip non-sell
      if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;     //--- Skip wrong magic
      TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET));      //--- Close position
   }
   Print("All Sell Positions Closed");                                     //--- Print closed
}

//+------------------------------------------------------------------+
//| Close all positions                                              |
//+------------------------------------------------------------------+
void CloseAllPositions() {
   int total = PositionsTotal();                                           //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                                  //--- Loop backward
      if (PositionGetSymbol(i) != Symbol()) continue;                      //--- Skip wrong symbol
      if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;     //--- Skip wrong magic
      TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET));      //--- Close position
   }
   Print("All Positions Closed");                                          //--- Print closed
}

//+------------------------------------------------------------------+
//| Calculate stop loss                                              |
//+------------------------------------------------------------------+
double CalculateStopLoss(ENUM_ORDER_TYPE orderType, double entryPrice) {
   double stopLossPrice = 0;                                               //--- Initialize SL price
   if (StopLossMode == SL_FIXED) {                                         //--- Check fixed mode
      if (DefaultStopLossPoints == 0) return 0;                            //--- Return zero if none
      double pointValue = SymbolInfoDouble(Symbol(), SYMBOL_POINT);        //--- Get point value
      if (orderType == ORDER_TYPE_BUY) stopLossPrice = entryPrice - DefaultStopLossPoints * pointValue; //--- Set buy SL
      if (orderType == ORDER_TYPE_SELL) stopLossPrice = entryPrice + DefaultStopLossPoints * pointValue; //--- Set sell SL
   } else {                                                                //--- Handle auto mode
      if (orderType == ORDER_TYPE_BUY) stopLossPrice = entryPrice - PreviousNrtrAtr * AtrMultiplierForStopLoss; //--- Set buy SL
      if (orderType == ORDER_TYPE_SELL) stopLossPrice = entryPrice + PreviousNrtrAtr * AtrMultiplierForStopLoss; //--- Set sell SL
   }
   return NormalizeDouble(stopLossPrice, _Digits);                         //--- Normalize SL
}

//+------------------------------------------------------------------+
//| Calculate take profit                                            |
//+------------------------------------------------------------------+
double CalculateTakeProfit(ENUM_ORDER_TYPE orderType, double entryPrice) {
   double takeProfitPrice = 0;                                             //--- Initialize TP price
   if (TakeProfitMode == TP_FIXED) {                                       //--- Check fixed mode
      if (DefaultTakeProfitPoints == 0) return 0;                          //--- Return zero if none
      double pointValue = SymbolInfoDouble(Symbol(), SYMBOL_POINT);        //--- Get point value
      if (orderType == ORDER_TYPE_BUY) takeProfitPrice = entryPrice + DefaultTakeProfitPoints * pointValue; //--- Set buy TP
      if (orderType == ORDER_TYPE_SELL) takeProfitPrice = entryPrice - DefaultTakeProfitPoints * pointValue; //--- Set sell TP
   } else {                                                                //--- Handle auto mode
      if (orderType == ORDER_TYPE_BUY) takeProfitPrice = entryPrice + PreviousNrtrAtr * AtrMultiplierForTakeProfit; //--- Set buy TP
      if (orderType == ORDER_TYPE_SELL) takeProfitPrice = entryPrice - PreviousNrtrAtr * AtrMultiplierForTakeProfit; //--- Set sell TP
   }
   return NormalizeDouble(takeProfitPrice, _Digits);                       //--- Normalize TP
}

//+------------------------------------------------------------------+
//| Calculate position size                                          |
//+------------------------------------------------------------------+
double CalculatePositionSize(double stopLossPrice, double entryPrice) {
   double size = DefaultLotSize;                                          //--- Set default size
   if (RiskDefaultSize == RISK_DEFAULT_AUTO) {                            //--- Check auto risk
      if (stopLossPrice == 0) stopLossPrice = 200;                        //--- Set default SL if zero
      double riskBaseAmount = 0;                                          //--- Initialize base amount
      double tickValue = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE); //--- Get tick value
      if (RiskBase == RISK_BASE_BALANCE) riskBaseAmount = AccountInfoDouble(ACCOUNT_BALANCE); //--- Set balance base
      else if (RiskBase == RISK_BASE_EQUITY) riskBaseAmount = AccountInfoDouble(ACCOUNT_EQUITY); //--- Set equity base
      else if (RiskBase == RISK_BASE_FREEMARGIN) riskBaseAmount = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Set free margin base
      double stopLossDistancePoints = MathAbs(entryPrice - stopLossPrice) / SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Compute distance
      size = (riskBaseAmount * MaxRiskPerTrade / 100) / (stopLossDistancePoints * tickValue); //--- Compute size
   }
   double lotStep = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);       //--- Get lot step
   double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);         //--- Get max lot
   double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);         //--- Get min lot
   size = MathFloor(size / lotStep) * lotStep;                            //--- Step size
   if (size > MaxLotSize) size = MaxLotSize;                              //--- Clamp max size
   if (size > maxLot) size = maxLot;                                      //--- Clamp symbol max
   if (size < MinLotSize || size < minLot) size = 0;                      //--- Clamp min size
   return size;                                                           //--- Return size
}

Для вспомогательных функций создаем CountOpenPositions для подсчета всех открытых позиций, соответствующих текущему символу и магическому числу. Внутри инициализируется счетчик, через PositionsTotal получаем общее число позиций и в цикле от 0 до этого значения используем PositionGetSymbol для пропуска несовпадающих символов, а PositionGetInteger с POSITION_MAGIC – для фильтрации по MagicNumber, если оно задано; для подходящих позиций счетчик увеличивается, после чего функция возвращает результат. Аналогично, CountOpenPositionsByType подсчитывает позиции заданного типа, например BUY или SELL: цикл остается тем же, но добавляется проверка через PositionGetInteger по POSITION_TYPE на совпадение со входным параметром positionType.

Для открытия сделок определяем функцию OpenBuyPosition, которая принимает строку positionType для формирования комментария. Через SymbolInfoDouble с SYMBOL_ASK получаем цену ask, отдельными функциями рассчитываем стоп-лосс и тейк-профит, определяем размер позиции и формируем комментарий вида "positionType + ' Buy'". Затем пытаемся открыть покупку через TradeObject.Buy: если попытка неудачна, выводим сообщение с кодом возврата, иначе сообщаем об успехе, после чего возвращаем результат. OpenSellPosition работает по той же схеме для продаж: использует цену bid из SYMBOL_BID, соответствующим образом рассчитывает стопы и выполняет TradeObject.Sell с комментарием "Sell".

Для закрытия позиций CloseAllBuyPositions безопасно проходит цикл в обратном порядке – от PositionsTotal - 1 до 0, пропуская несовпадающие символы, позиции с типом, отличным от BUY (через POSITION_TYPE_BUY), и несовпадающие магические числа; подходящие позиции закрываются через TradeObject.PositionClose по тикету из POSITION_TICKET, после чего выводится подтверждение. CloseAllSellPositions делает то же самое для продаж, проверяя POSITION_TYPE_SELL. CloseAllPositions закрывает все позиции, соответствующие символу и магическому числу, без фильтрации по типу, и выводит результат.

В CalculateStopLoss вычисляется цена стоп-лосса на основе типа ордера и цены входа. В фиксированном режиме StopLossMode, если число пунктов равно нулю, функция возвращает ноль; иначе она получает значение pointValue и для BUY или SELL вычитает либо прибавляет количество пунктов, умноженное на pointValue. В автоматическом режиме значение корректируется вычитанием или прибавлением PreviousNrtrAtr * AtrMultiplierForStopLoss с последующей нормализацией по числу знаков. CalculateTakeProfit работает по тому же принципу, используя TakeProfitMode и AtrMultiplierForTakeProfit для автоматической корректировки. Наконец, CalculatePositionSize начинает с лота по умолчанию, но при автоматическом риске задает расстояние по умолчанию, если стоп-лосс равен нулю, определяет базовую сумму риска, получает значение тика tickValue, вычисляет расстояние в пунктах и рассчитывает размер позиции как сумму риска, деленную на произведение расстояния и tickValue. Далее размер позиции округляется в соответствии с шагом лота через MathFloor, ограничивается допустимыми минимальными и максимальными значениями с учетом ограничений символа и возвращается. Теперь мы можем уверенно использовать эти функции для проверки сигналов и открытия позиций.

//+------------------------------------------------------------------+
//| Check for entry signals                                          |
//+------------------------------------------------------------------+
void CheckForEntrySignals() {
   bool buySignal = (CurrentUpSupport != EMPTY_VALUE) && (PreviousUpSupport == EMPTY_VALUE);      //--- Check buy signal (start of long support)
   bool sellSignal = (CurrentDownSupport != EMPTY_VALUE) && (PreviousDownSupport == EMPTY_VALUE); //--- Check sell signal (start of short support)
   if (buySignal) {                                                                               //--- Handle buy signal
      string currUpStr = DoubleToString(CurrentUpSupport, _Digits);
      string prevUpStr = (PreviousUpSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousUpSupport, _Digits);
      PrintFormat("Buy Signal Detected: Start of Long Support (Buffer 1 Recent: %s, Older: %s)", currUpStr, prevUpStr); //--- Print buy signal
      if ((CountOpenPositionsByType(POSITION_TYPE_BUY) == 0 && HedgeMode) || CountOpenPositions() < MaxPositions) {     //--- Check open buys
         OpenBuyPosition("Initial Signal");                                                       //--- Open buy
      } else {                                                                                    //--- Handle rejected
         Print("Buy Trade Rejected: Maximum positions reached or hedge mode restricts");          //--- Print rejected
      }
   }
   if (sellSignal) {                                                                              //--- Handle sell signal
      string currDownStr = DoubleToString(CurrentDownSupport, _Digits);
      string prevDownStr = (PreviousDownSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousDownSupport, _Digits);
      PrintFormat("Sell Signal Detected: Start of Short Support (Buffer 2 Recent: %s, Older: %s)", currDownStr, prevDownStr); //--- Print sell signal
      if ((CountOpenPositionsByType(POSITION_TYPE_SELL) == 0 && HedgeMode) || CountOpenPositions() < MaxPositions) {          //--- Check open sells
         OpenSellPosition("Initial Signal");                                                      //--- Open sell
      } else {                                                                                    //--- Handle rejected
         Print("Sell Trade Rejected: Maximum positions reached or hedge mode restricts");         //--- Print rejected
      }
   }
}

Здесь реализуем функцию CheckForEntrySignals для выявления и обработки потенциальных входов на основе переходов канала NRTR. В этой функции сигнал BUY считается сформированным, когда CurrentUpSupport не равен EMPTY_VALUE, а PreviousUpSupport равен EMPTY_VALUE, то есть начинается уровень поддержки для восходящего канала. Аналогичным образом, сигнал SELL срабатывает, если CurrentDownSupport имеет корректное значение, а PreviousDownSupport равен EMPTY_VALUE, что указывает на начало уровня поддержки для нисходящего канала.

Если обнаружен сигнал BUY, мы преобразуем значения поддержки в строки, где для предыдущей поддержки EMPTY_VALUE заменяется на "EMPTY", выводим отформатированное сообщение с деталями обнаружения из буфера 1 и проверяем условия: если хеджирование включено и открытых покупок нет либо если общее число позиций меньше MaxPositions, вызываем OpenBuyPosition с комментарием "Initial Signal"; в противном случае выводим сообщение об отклонении сигнала. Для сигнала SELL схема та же: форматируем и выводим детали из буфера 2, проверяем, что при включенном хеджировании открытых продаж нет либо что лимит по позициям еще не достигнут, после чего открываем продажу через OpenSellPosition или выводим сообщение об отклонении сигнала. Теперь эту функцию можно вызывать в обработчике тиков, чтобы получать результаты проверки.

//+------------------------------------------------------------------+
//| Handle tick event                                                |
//+------------------------------------------------------------------+
void OnTick() {
   ProcessEachTick();                                            //--- Process tick
}

//+------------------------------------------------------------------+
//| Process each tick                                                |
//+------------------------------------------------------------------+
void ProcessEachTick() {
   if (!FetchIndicatorData()) return;                            //--- Fetch indicator data

   static datetime lastBarTime = WRONG_VALUE;                    //--- Initialize last bar time
   datetime currentBarTime = iTime(Symbol(), Period(), 0);       //--- Get current bar time
   static int newBarTicks = 0;                                   //--- Initialize new bar ticks
   if (currentBarTime == lastBarTime) {                          //--- Check same bar
      newBarTicks++;                                             //--- Increment ticks
      if (newBarTicks > 1) return;                               //--- Skip if more than 1
   } else {                                                      //--- New bar
      newBarTicks = 0;                                           //--- Reset ticks
      lastBarTime = currentBarTime;                              //--- Update last time
   }
   CheckForEntrySignals();                                       //--- Check entry signals
}

В обработчике событий OnTick, мы просто вызываем ProcessEachTick, централизуя логику обработки ценовых обновлений. В ProcessEachTick сначала пытаемся получить свежие данные индикатора через FetchIndicatorData и сразу выходим при ошибке, чтобы решения принимались только на основе корректной информации. Чтобы сократить лишнюю обработку и сосредоточиться на новых барах, используем статическую переменную lastBarTime, инициализированную WRONG_VALUE, и через iTime получаем время открытия текущего бара для символа и периода. Статическая переменная newBarTicks инициализируется нулем и отслеживает число тиков внутри одного бара.

Если время текущего бара совпадает с lastBarTime, то есть это тот же бар, увеличиваем newBarTicks и без дальнейших действий выходим, если значение стало больше 1, чтобы не выполнять лишние проверки на последующих тиках. Иначе, при появлении нового бара, сбрасываем newBarTicks в 0, обновляем lastBarTime и продолжаем обработку. Затем вызываем CheckForEntrySignals, чтобы оценить обновленные данные и при необходимости выполнить новые входы в рынок. После компиляции получаем следующий результат.

SIGNAL GENERATION AND TRADING

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

//+------------------------------------------------------------------+
//| Check for exit signals                                           |
//+------------------------------------------------------------------+
void CheckForExitSignals() {
   bool exitBuySignal = false;                                   //--- Initialize buy exit
   bool exitSellSignal = false;                                  //--- Initialize sell exit
   if (CloseOnReversalSignal) {                                  //--- Check reversal close
      exitBuySignal = (CurrentDownSupport != EMPTY_VALUE) && (PreviousDownSupport == EMPTY_VALUE); //--- Set buy exit (start of short)
      exitSellSignal = (CurrentUpSupport != EMPTY_VALUE) && (PreviousUpSupport == EMPTY_VALUE); //--- Set sell exit (start of long)
   }
   if (exitBuySignal) {                                          //--- Handle buy exit
      string currDownStr = DoubleToString(CurrentDownSupport, _Digits);
      string prevDownStr = (PreviousDownSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousDownSupport, _Digits);
      PrintFormat("Exit Buy Signal Detected (Reversal): Start of Short Support (Buffer 2 Recent: %s, Older: %s)", currDownStr, prevDownStr); //--- Print buy exit
      CloseAllBuyPositions();                                    //--- Close buys
   }
   if (exitSellSignal) {                                         //--- Handle sell exit
      string currUpStr = DoubleToString(CurrentUpSupport, _Digits);
      string prevUpStr = (PreviousUpSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousUpSupport, _Digits);
      PrintFormat("Exit Sell Signal Detected (Reversal): Start of Long Support (Buffer 1 Recent: %s, Older: %s)", currUpStr, prevUpStr); //--- Print sell exit
      CloseAllSellPositions();                                   //--- Close sells
   }
}

//+------------------------------------------------------------------+
//| Close on virtual take profit                                     |
//+------------------------------------------------------------------+
void CloseOnVirtualTakeProfit(ulong ticket) {
   if (!PositionSelectByTicket(ticket)) return;                  //--- Select position
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);   //--- Get entry price
   double virtualTakeProfit = 0;                                 //--- Initialize virtual TP
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);          //--- Get ask
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);          //--- Get bid
   if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
      virtualTakeProfit = entryPrice + DefaultTakeProfitPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set buy TP
      if (virtualTakeProfit <= bid && CloseOnTakeProfitHit) {    //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual TP: Ticket %llu, Price Hit %.5f", ticket, virtualTakeProfit); //--- Print closed
      }
   } else {                                                      //--- Handle sell
      virtualTakeProfit = entryPrice - DefaultTakeProfitPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set sell TP
      if (virtualTakeProfit >= ask && CloseOnTakeProfitHit) {    //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual TP: Ticket %llu, Price Hit %.5f", ticket, virtualTakeProfit); //--- Print closed
      }
   }
}

//+------------------------------------------------------------------+
//| Close on virtual stop loss                                       |
//+------------------------------------------------------------------+
void CloseOnVirtualStopLoss(ulong ticket) {
   if (!PositionSelectByTicket(ticket)) return;                  //--- Select position
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);   //--- Get entry price
   double virtualStopLoss = 0;                                   //--- Initialize virtual SL
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);          //--- Get ask
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);          //--- Get bid
   if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
      virtualStopLoss = entryPrice - DefaultStopLossPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set buy SL
      if (virtualStopLoss >= ask && CloseOnStopLossHit) {        //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual SL: Ticket %llu, Price Hit %.5f", ticket, virtualStopLoss); //--- Print closed
      }
   } else {                                                      //--- Handle sell
      virtualStopLoss = entryPrice + DefaultStopLossPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set sell SL
      if (virtualStopLoss <= bid && CloseOnStopLossHit) {        //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual SL: Ticket %llu, Price Hit %.5f", ticket, virtualStopLoss); //--- Print closed
      }
   }
}

//+------------------------------------------------------------------+
//| Handle virtual closures                                          |
//+------------------------------------------------------------------+
void HandleVirtualClosures() {
   int total = PositionsTotal();                                 //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                        //--- Loop backward
      if (PositionGetSymbol(i) == "") continue;                  //--- Skip invalid
      ulong ticket = PositionGetInteger(POSITION_TICKET);        //--- Get ticket
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong symbol/magic
      if (CloseOnStopLossHit) CloseOnVirtualStopLoss(ticket);    //--- Check virtual SL
      if (CloseOnTakeProfitHit) CloseOnVirtualTakeProfit(ticket); //--- Check virtual TP
   }
}

Сначала разрабатываем CheckForExitSignals для отслеживания условий, при которых нужно закрывать уже открытые позиции. Здесь инициализируем булевы флаги exitBuySignal и exitSellSignal значением false. Если включен CloseOnReversalSignal, то при обнаружении начала поддержки для продаж, когда текущий нижний уровень поддержки корректен, а предыдущий пуст, устанавливаем exitBuySignal = true, а при обнаружении начала поддержки для покупок – exitSellSignal = true. Когда возникает сигнал на выход из покупок, мы форматируем строки значений нижней поддержки, где EMPTY_VALUE заменяется на "EMPTY", выводим подробное сообщение из буфера 2 и вызываем CloseAllBuyPositions для закрытия всех длинных позиций. Аналогично, при сигнале на выход из продаж выводим данные из буфера 1 и вызываем CloseAllSellPositions.

CloseOnVirtualTakeProfit управляет программным закрытием по виртуальному тейк-профиту для заданного тикета. Сначала через PositionSelectByTicket выбираем позицию и, если это не удается, сразу выходим; затем получаем цену входа. Для покупок вычисляем virtualTakeProfit, прибавляя DefaultTakeProfitPoints * point к цене входа; для продаж вычитаем это значение. Получаем цены ask и bid, и если виртуальный уровень достигнут, то есть когда для покупки TP <= bid, для продажи TP >= ask, а опция CloseOnTakeProfitHit включена, закрываем позицию через TradeObject.PositionClose и выводим подтверждение с тикетом и ценой срабатывания.

Аналогичным образом, CloseOnVirtualStopLoss обрабатывает виртуальные стоп-лоссы. После выбора позиции и получения цены входа рассчитываем virtualStopLoss, вычитая пункты для покупок или прибавляя их для продаж. Используя ask и bid, при срабатывании уровня, то есть когда для покупки SL >= ask, для продажи SL <= bid, а опция CloseOnStopLossHit включена, закрываем позицию и выводим подробности. В HandleVirtualClosures выполняется обратный перебор всех позиций от PositionsTotal - 1 до 0 для безопасной итерации. Для каждой позиции, если не удается получить корректный символ, она пропускается; затем получаем тикет и продолжаем цикл, если символ или магическое число не совпадают. Если опция включена CloseOnStopLossHit, вызываем CloseOnVirtualStopLoss; если включена опция CloseOnTakeProfitHit, вызываем CloseOnVirtualTakeProfit, чтобы применять виртуальные проверки ко всем открытым сделкам. Также при включении нужно обрабатывать типы трейлинга – либо по пунктам, либо по уровням поддержки.

//+------------------------------------------------------------------+
//| Adjust to break even                                             |
//+------------------------------------------------------------------+
bool AdjustToBreakEven(ulong ticket) {
   if (TrailingType == TRAILING_NONE) return false;              //--- Check trailing type
   if (!PositionSelectByTicket(ticket)) return false;            //--- Select position
   MqlTradeRequest request = {};                                 //--- Declare request
   MqlTradeResult result = {};                                   //--- Declare result
   request.action = TRADE_ACTION_SLTP;                           //--- Set action SLTP
   request.position = ticket;                                    //--- Set position ticket
   long positionType = PositionGetInteger(POSITION_TYPE);        //--- Get type
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);   //--- Get entry price
   double currentStopLoss = PositionGetDouble(POSITION_SL);      //--- Get current SL
   double currentTakeProfit = PositionGetDouble(POSITION_TP);    //--- Get current TP
   string symbol = PositionGetString(POSITION_SYMBOL);           //--- Get symbol
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);        //--- Get point
   double currentPrice = (positionType == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID) : SymbolInfoDouble(symbol, SYMBOL_ASK); //--- Get current price
   double currentProfitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - entryPrice) / point : (entryPrice - currentPrice) / point; //--- Compute profit points
   double newStopLoss = 0;                                       //--- Initialize new SL
   if (currentProfitPoints <= 0) return false;                   //--- Skip if no profit

   switch (TrailingType) {                                       //--- Switch trailing type
   case TRAILING_POINTS: {                                       //--- Handle points
      if (currentProfitPoints >= BreakEvenTriggerPoints) {       //--- Check BE trigger
         double bePoints = BreakEvenPoints;                      //--- Set BE points
         newStopLoss = (positionType == POSITION_TYPE_BUY) ? NormalizeDouble(entryPrice + (bePoints * point), _Digits) : NormalizeDouble(entryPrice - (bePoints * point), _Digits); //--- Compute new SL
         if ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss)) { //--- Check improvement
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("Breakeven SL Adjusted for %s Position: Ticket %llu, New SL = %.5f (Triggered at %.0f points profit)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted
                  return true;                                      //--- Return success
               }
            }
         }
      }
      if (currentProfitPoints >= CurrentTrailingStopPoints) {       //--- Check trailing trigger
         double trailPoints = currentProfitPoints - CurrentTrailingStepPoints; //--- Compute trail points
         newStopLoss = (positionType == POSITION_TYPE_BUY) ? NormalizeDouble(currentPrice - (CurrentTrailingStopPoints * point), _Digits) : NormalizeDouble(currentPrice + (CurrentTrailingStopPoints * point), _Digits); //--- Compute new SL
         if ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss)) { //--- Check improvement
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("Trailing SL Adjusted for %s Position: Ticket %llu, New SL = %.5f (Current Profit = %.0f points)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted
                  return true;                                      //--- Return success
               }
            }
         }
      }
      break;
   }

   case TRAILING_SUPPORT_LEVELS: {                                 //--- Handle support levels
      double currentSupport = (positionType == POSITION_TYPE_BUY) ? CurrentUpSupport : CurrentDownSupport; //--- Get current support
      double previousSupport = (positionType == POSITION_TYPE_BUY) ? PreviousUpSupport : PreviousDownSupport; //--- Get previous support
      if (currentSupport == EMPTY_VALUE) return false;             //--- Skip if invalid support
      if (currentProfitPoints >= BreakEvenTriggerPoints) {         //--- Check BE trigger
         newStopLoss = currentSupport;                             //--- Set new SL to support
         bool isProfitable = (positionType == POSITION_TYPE_BUY && newStopLoss >= entryPrice) || (positionType == POSITION_TYPE_SELL && newStopLoss <= entryPrice); //--- Ensure beyond entry
         if (isProfitable && ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss))) { //--- Check improvement and profitable
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("Breakeven SL Adjusted to Support Level for %s Position: Ticket %llu, New SL = %.5f (Triggered at %.0f points profit)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted
                  return true;                                      //--- Return success
               }
            }
         }
      }
      if ((positionType == POSITION_TYPE_BUY && currentSupport > previousSupport) || (positionType == POSITION_TYPE_SELL && currentSupport < previousSupport)) { //--- Check support moved
         newStopLoss = currentSupport;                              //--- Set new SL to support
         bool isProfitable = (positionType == POSITION_TYPE_BUY && newStopLoss >= entryPrice) || (positionType == POSITION_TYPE_SELL && newStopLoss <= entryPrice); //--- Ensure beyond entry
         if (isProfitable && ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss))) { //--- Check improvement and profitable
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("SL Trailed to Support Level for %s Position: Ticket %llu, New SL = %.5f (Support Moved from %.5f to %.5f)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, previousSupport, currentSupport); //--- Print trailed
                  return true;                                      //--- Return success
               }
            }
         }
      }
      break;
   }

   default:
      break;
   }
   return false;                                                    //--- Return no adjustment
}

//+------------------------------------------------------------------+
//| Adjust all stop losses                                           |
//+------------------------------------------------------------------+
void AdjustAllStopLosses() {
   for (int i = PositionsTotal() - 1; i >= 0; i--) {                //--- Loop backward
      if (PositionGetSymbol(i) == "") continue;                     //--- Skip invalid
      ulong ticket = PositionGetInteger(POSITION_TICKET);           //--- Get ticket
      AdjustToBreakEven(ticket);                                    //--- Adjust to BE
   }
}

Здесь создаем функцию AdjustToBreakEven для управления трейлингом и переносом в безубыток по конкретной позиции, определяемой ее тикетом. Если TrailingType равен TRAILING_NONE или позицию не удается выбрать через PositionSelectByTicket, сразу возвращаем false. Подготавливаем структуры MqlTradeRequest и  MqlTradeResult, задаем для запроса действие TRADE_ACTION_SLTP для изменения стоп-лосса и тейк-профита и присваиваем тикет позиции. Затем получаем тип позиции, цену входа, текущие стоп-лосс и тейк-профит, символ и значение пункта. Текущая цена – bid для покупок и ask для продаж; далее вычисляем currentProfitPoints как разницу относительно цены входа, нормализованную по point. Если прибыли нет, пропускаем дальнейшую обработку и возвращаем false.

В операторе switch по TrailingType для TRAILING_POINTS сначала проверяем, достигает ли прибыль уровня BreakEvenTriggerPoints; если да, вычисляем новый стоп-лосс на уровне цены входа плюс или минус – для покупок или продаж соответственно – BreakEvenPoints * point с последующей нормализацией. Затем проверяем, улучшает ли это текущий стоп, то есть выше ли он для покупок и ниже ли для продаж, и действительно ли новое значение отличается; после этого задаем в запросе стоп-лосс и тейк-профит, отправляем его через OrderSend  и при успехе выводим сообщение, возвращая true. Далее, все в том же режиме points, если прибыль превышает CurrentTrailingStopPoints, рассчитываем трейлинг-стоп как текущую цену минус или плюс CurrentTrailingStopPoints * point, проверяем улучшение и отличие нового значения, отправляем изменение, при успехе выводим сообщение и возвращаем true.

Для TRAILING_SUPPORT_LEVELS получаем текущие и предыдущие уровни поддержки в зависимости от типа позиции и пропускаем обработку, если текущий уровень пуст. Если прибыль достигла порога безубытка, устанавливаем новый стоп на текущий уровень поддержки, убеждаемся, что он не хуже цены входа, проверяем улучшение и, если значение изменилось, отправляем запрос, после чего при успехе выводим сообщение. Если уровень поддержки сместился в благоприятную сторону, то есть выше для покупок и ниже для продаж, переносим стоп на текущую поддержку, убеждаемся, что новый уровень сохраняет прибыль, и при отличии от текущего значения отправляем запрос, выводим детали трейлинга и возвращаем true. Во всех остальных случаях ничего не делаем и возвращаем false. Функция AdjustAllStopLosses выполняет обратный перебор всех позиций от PositionsTotal - 1 до 0, пропускает недействительные символы, получает тикет каждой позиции и вызывает для нее AdjustToBreakEven, чтобы применить корректировки ко всем открытым сделкам. Теперь все эти функции можно вызывать из управляющей функции для обработки каждого тика. Итоговая функция выглядит следующим образом.

//+------------------------------------------------------------------+
//| Process each tick                                                |
//+------------------------------------------------------------------+
void ProcessEachTick() {
   if (!FetchIndicatorData()) return;                            //--- Fetch indicator data
   int positionCount = CountOpenPositions();                     //--- Count positions
   if (positionCount > 0) {                                      //--- Check positions exist
      CheckForExitSignals();                                     //--- Check exit signals
   }
   static datetime lastBarTime = WRONG_VALUE;                    //--- Initialize last bar time
   datetime currentBarTime = iTime(Symbol(), Period(), 0);       //--- Get current bar time
   static int newBarTicks = 0;                                   //--- Initialize new bar ticks
   if (currentBarTime == lastBarTime) {                          //--- Check same bar
      newBarTicks++;                                             //--- Increment ticks
      if (newBarTicks > 1) return;                               //--- Skip if more than 1
   } else {                                                      //--- New bar
      newBarTicks = 0;                                           //--- Reset ticks
      lastBarTime = currentBarTime;                              //--- Update last time
   }
   CheckForEntrySignals();                                       //--- Check entry signals
   datetime currentTime = TimeCurrent();                         //--- Get current time
   if (currentTime - LastTrailingTime >= TrailingFrequencySeconds) { //--- Check trailing time
      AdjustAllStopLosses();                                     //--- Adjust SLs
      LastTrailingTime = currentTime;                            //--- Update trailing time
   }
   HandleVirtualClosures();                                      //--- Handle virtual closures
}

Далее вызываем эти функции по соответствующим условиям и после компиляции получаем следующий результат.

NRTR TRAILING

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


Тестирование на исторических данных

После тщательного тестирования на исторических данных мы получили следующие результаты.

График тестирования на исторических данных:

GRAPH

Отчет о тестировании на исторических данных:

REPORT


Заключение

В итоге мы разработали торговую систему Nick Rypock Trailing Reverse (NRTR) на языке MQL5, которая выявляет сигналы разворота через динамические каналы, поддерживает входы по тренду, хеджирование покупок и продаж, а также ограничения по числу позиций для ограничения объема открытого риска. Мы добавили функции управления рисками: автоматический расчет лота на основе средств счета, баланса или свободной маржи, а также фиксированные или скорректированные по ATR уровни стоп-лосса и тейк-профита.

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

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
peterssozi2004
peterssozi2004 | 30 янв. 2026 в 21:34
Можно ли мне приобрести этого робота?
Jay4rty
Jay4rty | 31 янв. 2026 в 06:50
Мне тоже интересно, это есть в наличии?
Hemant Yadav
Hemant Yadav | 31 янв. 2026 в 08:09
Здравствуйте, сэр, но этот советник не может провести бэктест на графике M5 пары XAU/USD? Помогите, пожалуйста, как провести бэктест без сбоев.
От начального до среднего уровня: События в объектах (II) От начального до среднего уровня: События в объектах (II)
В данной статье мы рассмотрим принцип работы трех последних типов событий, которые может генерировать объект. Разбираться в этом будет очень увлекательно, так как в итоге мы сделаем то, что многим может показаться безумием, но это вполне возможно и дает весьма удивительный результат.
Советник для размещения ордеров на основе риска с графическим интерфейсом на графике (Часть 2): Добавление интерактивности и логики Советник для размещения ордеров на основе риска с графическим интерфейсом на графике (Часть 2): Добавление интерактивности и логики
Узнайте, как создать интерактивный советник MQL5 с панелью управления на графике. Вы научитесь рассчитывать размер лота на основе риска и отправлять ордера прямо с графика.
Осваиваем графики Kagi в MQL5 (Часть I): Создание движка графика Kagi Осваиваем графики Kagi в MQL5 (Часть I): Создание движка графика Kagi
Узнайте, как создать полноценный движок графиков Kagi в MQL5: строить ценовые развороты, формировать динамические отрезки линий и обновлять структуру Kagi в реальном времени. В первой части показано, как отображать графики Kagi непосредственно в MetaTrader 5, давая трейдерам ясное представление о смене тренда и силе рынка и одновременно закладывая основу для автоматизированной торговой логики на базе Kagi во второй части.
От начального до среднего уровня: События в объектах (I) От начального до среднего уровня: События в объектах (I)
В этой статье мы рассмотрим три из шести событий, которые MetaTrader 5 может генерировать при возникновении каких-либо изменений с объектом на графике. Данные события очень полезны с точки зрения взаимодействия с пользователями. Это происходит так, потому что без понимания этих событий нам придётся приложить гораздо больше усилий для поддержания определённой конфигурации в графике при попытке управлять объектами для конкретных целей.