English Deutsch 日本語
preview
Создание интеллектуального торгового менеджера в MQL5: Автоматизация перевода в безубыток, трейлинг-стопа и частичного закрытия позиции

Создание интеллектуального торгового менеджера в MQL5: Автоматизация перевода в безубыток, трейлинг-стопа и частичного закрытия позиции

MetaTrader 5Эксперты |
74 0
Chacha Ian Maroa
Chacha Ian Maroa

Введение

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

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

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

В данной статье представлено автоматизированное решение для этих задач. Она поможет вам создать полнофункциональный советник для управления сделками (Trade Manager). Советник управляет тремя важнейшими аспектами сопровождения сделок: автоматическим перемещением стоп-лосса на уровень безубыточности для предотвращения убытков, отслеживанием уровней стоп-лосса по мере движения цены в пользу позиции и закрытием частичной прибыли для постепенного закрепления прибыли.



Как устроен советник Smart Trade Manager.

Интеллектуальный инструмент управления сделками (Smart Trade Manager) под названием AutoProtect разработан как советник в среде MQL5. Важно отметить, что MQL5 поддерживает различные типы программ, включая скрипты, индикаторы, сервисы и советники. Поскольку цель инструмента AutoProtect — автоматические мониторинг и управление открытыми позициями, он должен работать непрерывно в режиме реального времени.

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

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

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

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

Советник (EA) поставляется с несколькими входными параметрами, сгруппированными в логические группы для большей ясности и удобства настройки:

//+------------------------------------------------------------------+
//| User Input Variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input bool  manageTradesByMagicNumber   = true;
input ulong tradeSelectionMagicNumber   = 254700680002;

input group "Break-Even"
input bool  enableBreakEven            = false;
input int   breakEvenTriggerPoints     = 50;
input int   breakEvenLockPoints        = 0;

input group "Trailing Stop"
input bool  enableTrailingStop         = false;
input int   trailingStartPoints        = 50;
input int   trailingStepPoints         = 20;
input int   trailingDistancePoints     = 100;

input group "Partial Close"
input bool   enablePartialClose        = false;
input int    partialCloseTriggerPoints = 100;
input double partialClosePercent       = 50.0;

Каждый параметр исполняет определенную роль. Например, параметр breakEvenTriggerPoints определяет, когда активировать перевод в безубыток, а параметр breakEvenLockPoints определяет, на каком количестве дополнительных пунктов за пределами точки входа следует зафиксировать прибыль. В разделе «трейлинг-стоп» параметр trailingStartPoints обозначает уровень прибыли, с которого начинается трейлинг-стоп, trailingStepPoints определяет частоту перемещения стопа, а trailingDistancePoints показывает, насколько далеко стоп должен оставаться позади текущей цены. Параметры partialCloseTriggerPoints и partialClosePercent определяют, какую часть сделки следует закрыть, когда цена переместится на определенное расстояние (указанное в пунктах) в зону прибыли.

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...
      
   //--- If both BreakEven and Trailing are enabled,trailing should only start after break-even has triggered.
   if(enableBreakEven && enableTrailingStop){
      usefulTrailingStartPoints = breakEvenTriggerPoints;
   }
   
   ...
   
   return(INIT_SUCCEEDED);
}

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



Настройка проекта

Чтобы начать разработку советника AutoProtect, откройте MetaEditor, создайте новый проект советника и назовите его AutoProtect.mq5. После создания файла проекта замените его содержимое следующим шаблонным кодом.

//+------------------------------------------------------------------+
//|                                                  AutoProtect.mq5 |
//|          Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian |
//|                          https://www.mql5.com/en/users/chachaian |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/en/users/chachaian"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| User Input Variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input bool  manageTradesByMagicNumber   = true;
input ulong tradeSelectionMagicNumber   = 254700680002;

input group "Break-Even"
input bool  enableBreakEven            = false;
input int   breakEvenTriggerPoints     = 50;
input int   breakEvenLockPoints        = 0;

input group "Trailing Stop"
input bool  enableTrailingStop         = false;
input int   trailingStartPoints        = 50;
input int   trailingStepPoints         = 20;
input int   trailingDistancePoints     = 100;

input group "Partial Close"
input bool   enablePartialClose        = false;
input int    partialCloseTriggerPoints = 100;
input double partialClosePercent       = 50.0;

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;

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

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   //---
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   //---
}

//+------------------------------------------------------------------+
//| Trade Transaction Events handler                                 |
//+------------------------------------------------------------------+
void OnTradeTransaction(
   const MqlTradeTransaction &trans,
   const MqlTradeRequest     &request,
   const MqlTradeResult      &result){
      // ----
}
//+------------------------------------------------------------------+

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

Для автоматизации торговых операций, таких как изменение стоп-уровней или закрытие позиций, мы сначала подключаем стандартную библиотеку Trade.mqh. Эта библиотека предоставляет доступ к классу CTrade, который упрощает управление ордерами благодаря встроенным методам, таким как PositionModify и PositionClosePartial.

Далее создаем глобальный экземпляр класса CTrade с именем Trade. Этот объект будет использоваться по всему советнику для выполнения всех торговых операций, обеспечивая эффективное и согласованное взаимодействие с торговой средой.

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

Логика выбора сделок

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

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

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

Чтобы это стало возможным, определена пользовательская структура под названием MqlTradeInfo. Эта структура содержит важные сведения о сделке, такие как номер ордера, тип позиции, цена при открытии позиции, уровни стоп-лосс и тейк-профит и параметры трейлинга. Затем мы поддерживаем глобальный массив таких структур для отслеживания всех позиций, управляемых этим советником.

...

//+------------------------------------------------------------------+
//| User Input Variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input bool  manageTradesByMagicNumber   = true;
input ulong tradeSelectionMagicNumber   = 254700680002;

...

//+------------------------------------------------------------------+
//| Data Structures                                                  |
//+------------------------------------------------------------------+
struct MqlTradeInfo{
   ulong ticket;
   ENUM_POSITION_TYPE positionType;
   double openPrice;
   double originalStopLevel;
   double currentStopLevel;
   double nextStopLevel;
   double originalTargetLevel;
   double nextTrailTriggerPrice;
   bool   isMovedToBreakEven;
   bool   isPartialProfitsSecured;
};

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
MqlTradeInfo tradeInfo[];


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //---
   return(INIT_SUCCEEDED);
}

...

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

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   //---
}

//+------------------------------------------------------------------+
//| Trade Transaction Events handler                                 |
//+------------------------------------------------------------------+
void OnTradeTransaction(
   const MqlTradeTransaction &trans,
   const MqlTradeRequest     &request,
   const MqlTradeResult      &result){
   
   // When a new position is opened
   if(trans.type   != TRADE_TRANSACTION_DEAL_ADD){
      return;
   }
   
   if(trans.symbol != _Symbol){
      return;
   }
   
   if(trans.deal   == 0){
      return;
   }
   
   bool selected = false;

   HistorySelect(TimeCurrent() - 60, TimeCurrent());
   for(int i = 0; i < 6; i++){
      if(HistoryDealSelect(trans.deal)){
         selected = true;
         break;
      }
      Sleep(5);
      HistorySelect(TimeCurrent() - 60, TimeCurrent());
   }
   
   long entry = -1;
   if(selected){
      entry = (long)HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
   }
   
   if(selected && entry == DEAL_ENTRY_IN){
   
      ulong  positionTicket      = trans.position;
      double openPrice           = 0.000000;
      double originalStopLevel   = 0.000000;
      double originalTargetLevel = 0.000000;
      ulong  magicNumber         = 0;
      
      if(PositionSelectByTicket(positionTicket)){
         openPrice           = PositionGetDouble(POSITION_PRICE_OPEN);
         originalStopLevel   = PositionGetDouble(POSITION_SL);
         originalTargetLevel = PositionGetDouble(POSITION_TP);
         magicNumber         = (ulong)PositionGetInteger(POSITION_MAGIC);
      }
      
      if(manageTradesByMagicNumber){
         if(magicNumber != tradeSelectionMagicNumber){
            return;
         }
      }
   
      if(trans.deal_type == DEAL_TYPE_BUY){
         Print("NEW LONG opened (confirmed via history): deal=", trans.deal, " pos=", trans.position);
         ArrayResize(tradeInfo, ArraySize(tradeInfo) + 1);
         tradeInfo[ArraySize(tradeInfo) - 1].ticket                  = positionTicket;
         tradeInfo[ArraySize(tradeInfo) - 1].positionType            = POSITION_TYPE_BUY;
         tradeInfo[ArraySize(tradeInfo) - 1].openPrice               = openPrice;
         tradeInfo[ArraySize(tradeInfo) - 1].originalStopLevel       = originalStopLevel;
         tradeInfo[ArraySize(tradeInfo) - 1].currentStopLevel        = originalStopLevel;
         tradeInfo[ArraySize(tradeInfo) - 1].nextStopLevel           = originalStopLevel + trailingStepPoints * pointValue;
         tradeInfo[ArraySize(tradeInfo) - 1].originalTargetLevel     = originalTargetLevel;
         tradeInfo[ArraySize(tradeInfo) - 1].nextTrailTriggerPrice   = openPrice + usefulTrailingStartPoints  * pointValue;
         tradeInfo[ArraySize(tradeInfo) - 1].isMovedToBreakEven      = false;
         tradeInfo[ArraySize(tradeInfo) - 1].isPartialProfitsSecured = false;
      }
      
      if(trans.deal_type == DEAL_TYPE_SELL){
         Print("NEW SHORT opened (confirmed via history): deal=", trans.deal, " pos=", trans.position);
         tradeInfo[ArraySize(tradeInfo) - 1].ticket                  = positionTicket;
         tradeInfo[ArraySize(tradeInfo) - 1].positionType            = POSITION_TYPE_SELL;
         tradeInfo[ArraySize(tradeInfo) - 1].openPrice               = openPrice;
         tradeInfo[ArraySize(tradeInfo) - 1].originalStopLevel       = originalStopLevel;
         tradeInfo[ArraySize(tradeInfo) - 1].currentStopLevel        = originalStopLevel;
         tradeInfo[ArraySize(tradeInfo) - 1].nextStopLevel           = originalStopLevel - trailingStepPoints * pointValue;
         tradeInfo[ArraySize(tradeInfo) - 1].originalTargetLevel     = originalTargetLevel;
         tradeInfo[ArraySize(tradeInfo) - 1].nextTrailTriggerPrice   = openPrice - usefulTrailingStartPoints  * pointValue;
         tradeInfo[ArraySize(tradeInfo) - 1].isMovedToBreakEven      = false;
         tradeInfo[ArraySize(tradeInfo) - 1].isPartialProfitsSecured = false;
      }
   }
   
   if(!selected){
   
      if(trans.deal_type == DEAL_TYPE_BUY  && trans.position != 0){
         Print("Probable NEW LONG (fallback): deal=", trans.deal, " pos=", trans.position);
      }
      
      if(trans.deal_type == DEAL_TYPE_SELL && trans.position != 0){
         Print("Probable NEW SHORT (fallback): deal=", trans.deal, " pos=", trans.position);
      }
      
   }
   
}
//+------------------------------------------------------------------+

Этот блок кода гарантирует, что AutoProtect автоматически регистрирует каждую новую позицию, соответствующую условиям фильтра по символу и (если оно задано) «магическому числу». С этого момента такие сделки официально отслеживаются и могут быть безопасно изменены модулем трейлинг-стопа в советнике.

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

Теперь добавим еще несколько глобальных переменных, которые будут хранить важные торговые данные, используемые в различных разделах советника. К ним относятся freezeLevelPoints, askPrice, bidPrice и pointValue.

...

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
MqlTradeInfo tradeInfo[];
double closePriceMinutesData [];
int usefulTrailingStartPoints = trailingStartPoints;
long freezeLevelPoints;
double askPrice;
double bidPrice;
double pointValue;

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

...

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

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //--- Initialize global variables
   freezeLevelPoints  = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
   
   return(INIT_SUCCEEDED);
}

...

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

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Scope variables
   askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   
}

...

pointValue отображает наименьшее изменение цены для текущего символа. Это значение остается постоянным, поэтому мы можем инициализировать его вместе с freezeLevelPoints в функции OnInit.

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
int OnInit(){

   //--- Initialize global variables
   freezeLevelPoints  = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
   pointValue         = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   return(INIT_SUCCEEDED);
}

...

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

double closePriceMinutesData[];
int usefulTrailingStartPoints = trailingStartPoints;

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

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

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- If both BreakEven and Trailing are enabled,trailing should only start after break-even has triggered.
   if(enableBreakEven && enableTrailingStop){
      usefulTrailingStartPoints = breakEvenTriggerPoints;
   }
   
   // Set arrays as series
   ArraySetAsSeries(closePriceMinutesData, true);
   
   return(INIT_SUCCEEDED);
}

...

Функция ArraySetAsSeries гарантирует, что самая последняя точка данных массива closePriceMinutesData всегда будет храниться по индексу 0 массива, и это упрощает доступ к последним ценовым данным.

Затем, внутри функции OnTick, загружаем семь последних цен закрытия с таймфрейма M1, используя функцию CopyClose:

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   ...
   bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   
   // Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }
   
}

...

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



Функциональность перевода в безубыток

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

...

//--- UTILITY FUNCTIONS
//+------------------------------------------------------------------+
//| Checks if position modification is allowed                       |                               
//+------------------------------------------------------------------+
bool IsTradeModificationAllowed(long freezeLevelPts, ENUM_ORDER_TYPE action, double askPr, double bidPr, double stopLossLvl, double takeProfitLvl){
   double freezeDistance = freezeLevelPts * pointValue;
   
   if(freezeLevelPts == 0){
      return true;
   }
   
   if(action == ORDER_TYPE_BUY) {
      double distanceFromSpotPriceToTP = takeProfitLvl - bidPr;
      double distanceFromSpotPriceToSL = bidPr - stopLossLvl;
      
      if(distanceFromSpotPriceToTP > freezeDistance && distanceFromSpotPriceToSL > freezeDistance){
         return true;
      }
   }
      
   if(action == ORDER_TYPE_SELL){
      double distanceFromSpotPriceToTP = askPr - takeProfitLvl;
      double distanceFromSpotPriceToSL = stopLossLvl - askPr;
      
      if(distanceFromSpotPriceToTP > freezeDistance && distanceFromSpotPriceToSL > freezeDistance){
         return true;
      }    
   }
   return false;
}

//+------------------------------------------------------------------+
//| To detect a crossover                                            |                               
//+------------------------------------------------------------------+
bool IsCrossOver(const double price, const double &closePriceMinsData[]){
   if(closePriceMinsData[1] <= price && closePriceMinsData[0] > price){
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| To detect a crossunder                                           |                               
//+------------------------------------------------------------------+
bool IsCrossUnder(const double price, const double &closePriceMinsData[]){
   if(closePriceMinsData[1] >= price && closePriceMinsData[0] < price){
      return true;
   }
   return false;
}

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

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

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

...

//--- UTILITY FUNCTIONS

...

//+------------------------------------------------------------------+
//| To mange the trade break even functionality                      |                               
//+------------------------------------------------------------------+
void ManageBreakEven   (){
   int totalPositions = PositionsTotal();
   for(int i = totalPositions - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket != 0){
         // Get some useful position properties
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         double openPrice                = PositionGetDouble(POSITION_PRICE_OPEN);
         double currentPrice             = PositionGetDouble(POSITION_PRICE_CURRENT);
         string symbol                   = PositionGetString(POSITION_SYMBOL);
         double stopLevel                = PositionGetDouble(POSITION_SL);
         double takeProfitLevel          = PositionGetDouble(POSITION_TP);
         ulong magicNumber               = PositionGetInteger(POSITION_MAGIC);
         
         if(symbol == _Symbol){
         
            if(!manageTradesByMagicNumber){

               if(positionType == POSITION_TYPE_BUY ){                 
                  for(int j = ArraySize(tradeInfo) - 1; j >= 0; j--){
                     if(tradeInfo[j].ticket == ticket && tradeInfo[j].isMovedToBreakEven == false){
                        if(IsCrossOver(openPrice + breakEvenTriggerPoints * pointValue, closePriceMinutesData)){
                           // Move SL to breakeven + breakEvenLockPoints
                           double newStopLevel = openPrice + (breakEvenLockPoints * pointValue);
                           if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_BUY, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                              if(!Trade.PositionModify(ticket, newStopLevel, takeProfitLevel)){
                                 Print("Error while moving Stop Loss to breakeven! ", GetLastError());
                                 Print(Trade.ResultComment());
                              }
                              tradeInfo[j].isMovedToBreakEven = true;
                           }
                        }
                     }
                  }
               }
       
               if(positionType == POSITION_TYPE_SELL){
                  for(int j = ArraySize(tradeInfo) - 1; i >= 0; i--){
                     if(tradeInfo[j].ticket == ticket && tradeInfo[j].isMovedToBreakEven == false){
                        if(IsCrossUnder(openPrice - breakEvenTriggerPoints * pointValue, closePriceMinutesData)){
                           // Move SL to breakeven + breakEvenLockPoints
                           double newStopLevel = openPrice - (breakEvenLockPoints * pointValue);
                           if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_BUY, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                              if(!Trade.PositionModify(ticket, newStopLevel, takeProfitLevel)){
                                 Print("Error while moving Stop Loss to breakeven! ", GetLastError());
                                 Print(Trade.ResultComment());
                              }
                              tradeInfo[j].isMovedToBreakEven = true;
                           }
                        }
                     }
                  }
               }

            }
            
            if(manageTradesByMagicNumber){
               if(magicNumber == tradeSelectionMagicNumber){
                  if(positionType == POSITION_TYPE_BUY ){                 
                     for(int j = ArraySize(tradeInfo) - 1; j >= 0; j--){
                        if(tradeInfo[j].ticket == ticket && tradeInfo[j].isMovedToBreakEven == false){
                           if(IsCrossOver(openPrice + breakEvenTriggerPoints * pointValue, closePriceMinutesData)){
                              // Move SL to breakeven + breakEvenLockPoints
                              double newStopLevel = openPrice + (breakEvenLockPoints * pointValue);
                              if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_BUY, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                                 if(!Trade.PositionModify(ticket, newStopLevel, takeProfitLevel)){
                                    Print("Error while moving Stop Loss to breakeven! ", GetLastError());
                                    Print(Trade.ResultComment());
                                 }
                                 tradeInfo[j].isMovedToBreakEven = true;
                              }
                           }
                        }
                     }
                  }
          
                  if(positionType == POSITION_TYPE_SELL){
                     for(int j = ArraySize(tradeInfo) - 1; i >= 0; i--){
                        if(tradeInfo[j].ticket == ticket && tradeInfo[j].isMovedToBreakEven == false){
                           if(IsCrossUnder(openPrice - breakEvenTriggerPoints * pointValue, closePriceMinutesData)){
                              // Move SL to breakeven + breakEvenLockPoints
                              double newStopLevel = openPrice - (breakEvenLockPoints * pointValue);
                              if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_BUY, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                                 if(!Trade.PositionModify(ticket, newStopLevel, takeProfitLevel)){
                                    Print("Error while moving Stop Loss to breakeven! ", GetLastError());
                                    Print(Trade.ResultComment());
                                 }
                                 tradeInfo[j].isMovedToBreakEven = true;
                              }
                           }
                        }
                     }
                  }
               }
            }
            
         }else{
            continue;
         }
      }else{
         Print("Error while getting a position ticket!", GetLastError());
         continue;
      }
   }
}

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

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

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

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

Короче говоря, цель этой функции — автоматическая защита капитала трейдера путем обеспечения того, чтобы после улучшения сделки на заданное расстояние в пунктах эта сделка она больше не могла превратиться в убыток. Эта небольшая, но крайне важная функция является одним из краеугольных камней советника AutoProtect Smart Trade Manager.



Функция трейлинг-стопа

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

...

//+------------------------------------------------------------------+
//| To mange the trade break even functionality                      |                               
//+------------------------------------------------------------------+
void ManageBreakEven   (){
   ...
}

//+------------------------------------------------------------------+
//| To manage the trade trailing stop functionality                  |                               
//+------------------------------------------------------------------+
void ManageTrailingStop(){
   int totalPositions = PositionsTotal();
   // Loop through all open positions
   for(int i = totalPositions - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket != 0){
         // Get some useful position properties
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         double openPrice                = PositionGetDouble (POSITION_PRICE_OPEN);
         double currentPrice             = PositionGetDouble (POSITION_PRICE_CURRENT);
         string symbol                   = PositionGetString (POSITION_SYMBOL);
         double stopLevel                = PositionGetDouble (POSITION_SL);
         double takeProfitLevel          = PositionGetDouble (POSITION_TP);
         ulong magicNumber               = PositionGetInteger(POSITION_MAGIC);
         // Skip positions not matching this financial security
         if(symbol == _Symbol){            
            if(!manageTradesByMagicNumber){

               if(positionType == POSITION_TYPE_BUY ){
                  
                  // 07-10-2025
                  for(int i = ArraySize(tradeInfo) - 1; i >= 0; i--){
                     if(tradeInfo[i].ticket == ticket){
                        if(IsCrossOver(tradeInfo[i].nextTrailTriggerPrice, closePriceMinutesData)){
                           // Physically trail SL only when next SL level is above current SL Level
                           if(tradeInfo[i].nextStopLevel > tradeInfo[i].currentStopLevel){
                              if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_BUY, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                                 if(!Trade.PositionModify(ticket, tradeInfo[i].nextStopLevel, takeProfitLevel)){
                                    Print("Error while trailing Stop Loss! ", GetLastError());
                                    Print(Trade.ResultComment());
                                    Print(Trade.ResultRetcode());
                                 }
                              }
                           }
                           tradeInfo[i].currentStopLevel = tradeInfo[i].currentStopLevel + trailingStepPoints * pointValue;
                           tradeInfo[i].nextStopLevel    = tradeInfo[i].nextStopLevel    + trailingStepPoints * pointValue;                          
                        }
                     }
                  }
               }
               
               if(positionType == POSITION_TYPE_SELL){
                  // 07-10-2025
                  for(int i = ArraySize(tradeInfo) - 1; i >= 0; i--){
                     if(tradeInfo[i].ticket == ticket){
                        if(IsCrossUnder(tradeInfo[i].nextTrailTriggerPrice, closePriceMinutesData)){
                           // Physically trail SL only when next SL level is above current SL Level
                           if(tradeInfo[i].nextStopLevel < tradeInfo[i].currentStopLevel){
                              if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_SELL, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                                 if(!Trade.PositionModify(ticket, tradeInfo[i].nextStopLevel, takeProfitLevel)){
                                    Print("Error while trailing Stop Loss! ", GetLastError());
                                    Print(Trade.ResultComment());
                                    Print(Trade.ResultRetcode());
                                 }
                              }
                           }
                           tradeInfo[i].currentStopLevel = tradeInfo[i].currentStopLevel - trailingStepPoints * pointValue;
                           tradeInfo[i].nextStopLevel    = tradeInfo[i].nextStopLevel    - trailingStepPoints * pointValue;                          
                        }
                     }
                  }
               }
            }
            
            if( manageTradesByMagicNumber){
               if(magicNumber == tradeSelectionMagicNumber){
 
                  if(positionType == POSITION_TYPE_BUY ){
                     // 07-10-2025
                     for(int i = ArraySize(tradeInfo) - 1; i >= 0; i--){
                        if(tradeInfo[i].ticket == ticket){
                           if(IsCrossOver(tradeInfo[i].nextTrailTriggerPrice, closePriceMinutesData)){
                              // Physically trail SL only when next SL level is above current SL Level
                              if(tradeInfo[i].nextStopLevel > tradeInfo[i].currentStopLevel){
                                 if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_BUY, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                                    if(!Trade.PositionModify(ticket, tradeInfo[i].nextStopLevel, takeProfitLevel)){
                                       Print("Error while trailing Stop Loss! ", GetLastError());
                                    Print(Trade.ResultComment());
                                    Print(Trade.ResultRetcode());
                                 }
                              }
                           }
                           tradeInfo[i].currentStopLevel = tradeInfo[i].currentStopLevel + trailingStepPoints * pointValue;
                           tradeInfo[i].nextStopLevel    = tradeInfo[i].nextStopLevel    + trailingStepPoints * pointValue;                          
                        }
                     }
                  }
               }
               
               if(positionType == POSITION_TYPE_SELL){
                  // 07-10-2025
                  for(int i = ArraySize(tradeInfo) - 1; i >= 0; i--){
                     if(tradeInfo[i].ticket == ticket){
                        if(IsCrossUnder(tradeInfo[i].nextTrailTriggerPrice, closePriceMinutesData)){
                           // Physically trail SL only when next SL level is above current SL Level
                           if(tradeInfo[i].nextStopLevel < tradeInfo[i].currentStopLevel){
                              if(IsTradeModificationAllowed(freezeLevelPoints, ORDER_TYPE_SELL, askPrice, bidPrice, stopLevel, takeProfitLevel)){
                                 if(!Trade.PositionModify(ticket, tradeInfo[i].nextStopLevel, takeProfitLevel)){
                                    Print("Error while trailing Stop Loss! ", GetLastError());
                                       Print(Trade.ResultComment());
                                       Print(Trade.ResultRetcode());
                                    }
                                 }
                              }
                              tradeInfo[i].currentStopLevel = tradeInfo[i].currentStopLevel - trailingStepPoints * pointValue;
                              tradeInfo[i].nextStopLevel    = tradeInfo[i].nextStopLevel    - trailingStepPoints * pointValue;                          
                           }
                        }
                     }
                  }
               }else{
                  continue;
               }
            }
            
         }else{
            continue;
         }
      }else{
         Print("Error while getting a position ticket! ", GetLastError());
         continue;
      }
   }
}

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

Внутри функции программа сравнивает последние рыночные цены, используя вспомогательные функции IsCrossOver и IsCrossUnder. Эти проверки определяют, когда цена пересекает определенные уровни, которые запускают корректировку стоп-лосса. При пересечении уровня сверху вниз или снизу вверх советник рассчитывает новый уровень стоп-лосса и перемещает его на предварительно заданный шаг (trailingStepPoints) в направлении позиции.

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

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

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



Функциональность частичного закрытия позиций

Непосредственно под функцией ManageTrailingStop добавьте функцию ManagePartialClose.

...

//+------------------------------------------------------------------+
//| To manage the trade trailing stop functionality                  |                               
//+------------------------------------------------------------------+
void ManageTrailingStop(){
   ...
}

//+------------------------------------------------------------------+
//| To manage the trade partial close functionality                  |                               
//+------------------------------------------------------------------+
void ManagePartialClose(){
   int totalPositions = PositionsTotal();
      
   for(int i = totalPositions - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket != 0){
         // Get some useful position properties
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         double openPrice                = PositionGetDouble (POSITION_PRICE_OPEN);
         double currentPrice             = PositionGetDouble (POSITION_PRICE_CURRENT);
         string symbol                   = PositionGetString (POSITION_SYMBOL);
         ulong magicNumber               = PositionGetInteger(POSITION_MAGIC);
         double positionVolume           = PositionGetDouble (POSITION_VOLUME);
         if(symbol == _Symbol){
         
            double volumeToDecrease = NormalizeDouble((partialClosePercent / 100.0) * positionVolume, 2);
            
            if(!manageTradesByMagicNumber){           
               if(positionType == POSITION_TYPE_BUY ){               
                  for(int j = ArraySize(tradeInfo) - 1; j >= 0; j--){
                     if(tradeInfo[j].ticket == ticket && tradeInfo[j].isPartialProfitsSecured == false){
                        if(IsCrossOver(openPrice + partialCloseTriggerPoints * pointValue, closePriceMinutesData)){
                           if(!Trade.PositionClosePartial(ticket, volumeToDecrease)){
                              Print("Error closing partial volume! ", GetLastError());
                              Print(Trade.ResultComment());
                           }
                           tradeInfo[j].isPartialProfitsSecured = true;
                        }
                     }
                  }
               }
               
               if(positionType == POSITION_TYPE_SELL){               
                  for(int j = ArraySize(tradeInfo) - 1; j >= 0; j--){
                     if(tradeInfo[j].ticket == ticket && tradeInfo[j].isPartialProfitsSecured == false){
                        if(IsCrossOver(openPrice - partialCloseTriggerPoints * pointValue, closePriceMinutesData)){
                           if(!Trade.PositionClosePartial(ticket, volumeToDecrease)){
                              Print("Error closing partial volume! ", GetLastError());
                              Print(Trade.ResultComment());
                           }
                           tradeInfo[j].isPartialProfitsSecured = true;
                        }
                     }
                  }
               }
               
            }
            
            if( manageTradesByMagicNumber){
               if(magicNumber == tradeSelectionMagicNumber){
               
                  if(positionType == POSITION_TYPE_BUY ){               
                     for(int j = ArraySize(tradeInfo) - 1; j >= 0; j--){
                        if(tradeInfo[j].ticket == ticket && tradeInfo[j].isPartialProfitsSecured == false){
                           if(IsCrossOver(openPrice + partialCloseTriggerPoints * pointValue, closePriceMinutesData)){
                              if(!Trade.PositionClosePartial(ticket, volumeToDecrease)){
                                 Print("Error closing partial volume! ", GetLastError());
                              Print(Trade.ResultComment());
                           }
                           tradeInfo[j].isPartialProfitsSecured = true;
                        }
                     }
                  }
               }
               
               if(positionType == POSITION_TYPE_SELL){               
                  for(int j = ArraySize(tradeInfo) - 1; j >= 0; j--){
                     if(tradeInfo[j].ticket == ticket && tradeInfo[j].isPartialProfitsSecured == false){
                        if(IsCrossOver(openPrice - partialCloseTriggerPoints * pointValue, closePriceMinutesData)){
                           if(!Trade.PositionClosePartial(ticket, volumeToDecrease)){
                              Print("Error closing partial volume! ", GetLastError());
                                 Print(Trade.ResultComment());
                              }
                              tradeInfo[j].isPartialProfitsSecured = true;
                           }
                        }
                     }
                  }
               
               }
            }
            
         }
         
      }else{
         Print("Error while getting a position ticket!", GetLastError());
         continue;
      }
   }
}

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

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

Затем проверяет, соответствует ли позиция текущему символу на графике. Кроме того, если управление сделками фильтруется по «магическому числу», то прежде чем продолжить, она проверяет соответствие позиции указанному «магическому числу».

Функция вычисляет объем, который будет закрыт, применяя процентное соотношение частичного закрытия к текущему объему сделки. Как только цена достигает прибыльного уровня на указанном триггерном расстоянии (в пунктах), советник закрывает рассчитанную часть сделки, используя метод CTrade.PositionClosePartial.

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

Если в процессе частичного закрытия возникает ошибка, в журнал для отладки выводится сообщение об ошибке и причина сбоя.



Тестирование и проверка

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

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

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

1. Проверка активных позиций

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

    ...
    
    //+------------------------------------------------------------------+
    //| To manage the trade partial close functionality                  |                               
    //+------------------------------------------------------------------+
    void ManagePartialClose(){
       ...
    }
    
    //+------------------------------------------------------------------+
    //| To check if there is an active buy position opened by this EA    |                               
    //+------------------------------------------------------------------+
    bool IsThereAnActiveBuyPosition(ulong magicNm){
       for(int i = PositionsTotal() - 1; i >= 0; i--){
          ulong ticket = PositionGetTicket(i);
          if(ticket == 0){
             Print("Error while fetching position ticket ", _LastError);
             continue;
          }else{
             if(PositionGetInteger(POSITION_MAGIC) == magicNm && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
                return true;
             }
          }
       }
       return false;
    }
    
    //+------------------------------------------------------------------+
    //| To check if there is an active sell position opened by this EA   |                               
    //+------------------------------------------------------------------+
    bool IsThereAnActiveSellPosition(ulong mgcNumber){
       for(int i = PositionsTotal() - 1; i >= 0; i--){
          ulong ticket = PositionGetTicket(i);
          if(ticket == 0){
             Print("Error while fetching position ticket ", _LastError);
             continue;
          }else{
             if(PositionGetInteger(POSITION_MAGIC) == mgcNumber && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
                return true;
             }
          }
       }
       return false;
    }
    
    ...

    Эти функции используются для проверки наличия активной позиции на покупку или продажу, открытой нашим советником. Каждая функция циклически перебирает все открытые позиции на счете, используя функции PositionsTotal и PositionGetTicket. Затем она сравнивает магическое число и тип позиции, чтобы определить, принадлежит ли позиция нашему советнику. Если найдена соответствующая позиция, функция возвращает true; в противном случае — false. Это гарантирует, что советник не будет открывать множество сделок одного и того же типа без необходимости.

    2. Разрешение однократного исполнения сделки

    Далее мы вводим новую глобальную переменную.

    bool isNewTradeAllowed;

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

    isNewTradeAllowed  = true;

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

    3. Автоматическое открытие тестовой сделки

    В функции OnTick добавляем логику для открытия позиции на покупку сразу после запуска.

    ...
    
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick(){
    
       ...
       
       //--- Get some minutes data
       if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
          Print("Error while copying minutes datas ", GetLastError());
          return;
       }
       
       //--- Open a long position immediately after launch
       if(isNewTradeAllowed){
          if(!IsThereAnActiveBuyPosition(tradeSelectionMagicNumber) && !IsThereAnActiveSellPosition(tradeSelectionMagicNumber)){
             Trade.Buy(1.0, _Symbol, askPrice, askPrice - 400 * pointValue, askPrice + 800 * pointValue);
             isNewTradeAllowed = false;
          }
       }
    }
    
    ...

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

    4. Вызов защитных функций

    Файлы AutoProtect.mq5 и AutoProtectTest.mq5 вызывают основные защитные функции, как показано ниже:

    ...
    
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick(){
    
       ...
       
       //--- Open a long position immediately after launch
       if(isNewTradeAllowed){
          ...
       }
       
       if(enableBreakEven){
          ManageBreakEven();
       }
       
       if(enableTrailingStop){
          ManageTrailingStop();
       }
       
       if(enablePartialClose){
          ManagePartialClose();
       }
       
    }
    
    ...

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

    5. Настройка внешнего вида графика

    Для обеспечения большей наглядности нашей тестовой среды определяем вспомогательную функцию с именем ConfigureChartAppearance.

    ...
    
    //+------------------------------------------------------------------+
    //| To check if there is an active sell position opened by this EA   |                               
    //+------------------------------------------------------------------+
    bool IsThereAnActiveSellPosition(ulong mgcNumber){
       ...
    }
    
    //+------------------------------------------------------------------+
    //| This function configures the chart's appearance                  |                               
    //+------------------------------------------------------------------+
    bool ConfigureChartAppearance(){
       if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhiteSmoke)){
          Print("Error while setting chart background, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){
          Print("Error while setting chart grid, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)){
          Print("Error while setting chart mode, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrGreen)){
          Print("Error while setting bullish candles color, ", GetLastError());
          return false;
       }   
       if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrDarkRed)){
          Print("Error while setting bearish candles color, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrGreen)){
          Print("Error while setting bearish candles color, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrDarkRed)){
          Print("Error while setting bearish candles color, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_COLOR_BID, clrDarkRed)){
          Print("Error while setting chart bid line, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_COLOR_ASK, clrGreen)){
          Print("Error while setting chart ask line, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_SHOW_ONE_CLICK, true)){
          Print("Error while setting one click buttons, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_COLOR_STOP_LEVEL, clrDarkBlue)){
          Print("Error while setting stop levels, ", GetLastError());
          return false;
       }
       if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){
          Print("Error while setting chart foreground, ", GetLastError());
          return false;
       }
       
       return true;
    }
    
    //+------------------------------------------------------------------+

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

    Для примера:

    • Цвет фона установлен на clrWhiteSmoke.
    • Бычьи свечи окрашиваются в зеленый цвет, а медвежьи — в темно-красный.
    • Линии bid и ask тоже окрашены для удобства идентификации.

    Каждая настройка применяется с помощью функции ChartSetInteger MQL5. В случае возникновения ошибки функция выводит сообщение об ошибке и возвращает значение false. Затем вызываем эту функцию внутри метода OnInit.

    ...
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit(){
       
       if(!ConfigureChartAppearance()){
          Print("Error while configuring the chart's appearance, ", GetLastError());
          return(INIT_FAILED);
       }
       
       ...
       
    }
    
    ...
    

    Это гарантирует правильную настройку графика до запуска советника. Если настройка не удалась, советник останавливает инициализацию, чтобы избежать визуальной путаницы.

    Такая конфигурация обеспечивает чистую и автоматизированную среду для тестирования каждой функции. Это гарантирует воспроизводимость, контролируемость и простоту наблюдения за результатами наших тестов, особенно при проверке работы функций BreakEven, Trailing Stop и Partial Close.

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

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

    input parameters for breakeven

    Сразу после запуска советника вы заметите, что он открывает новую длинную позицию со стоп-лоссом, установленным на 400 пунктов ниже цены входа, и тейк-профитом, установленным на 800 пунктов выше нее.

    Открытие позиции

    Как только позиция переместится на 200 пунктов в зону прибыли, вы заметите, что стоп-лосс автоматически скорректировался до уровня безубыточности.

    sl moved to breakeven

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

    testing trailing stop

    Установим значение trailingStartPoints равным 50, а trailingStepPoints — равным 20. Далее запустим советника на графике EURUSD и понаблюдаем за его поведением. Сразу после запуска вы заметите, что советник открывает длинную позицию со стоп-лоссом на 400 пунктов ниже цены входа и тейк-профитом на 800 пунктов выше нее.

    trailing stop functionality first time launch

    В данном случае стоп-уровень был перемещен с 1.05945 на 1.05985.

    testing the trailing stop function step 1

    Затем стоп-уровень был скорректирован с 1.05985 до 1.06025.

    testing the trailing stop functionality step 2

    Здесь трейлинг-стоп снова был отрегулирован с 1.0625 до 1.06165.

    testing the trailing stop functionality step 3

    Это наглядно демонстрирует, что функциональность трейлинг-стопа работает, как ожидается. Наконец, перейдем к тестированию функции частичного закрытия. Отключим все остальные функции и включим только функцию частичного закрытия. Установим значение PartialTriggerPoints равным 200, а PartialClosePercent — 50,0. Это означает, что 50% объема позиции будет закрыто, как только цена пройдет 200 пунктов в прибыльную сторону. 

    Настройки частичного закрытия

    Давайте теперь снова запустим советника на графике EURUSD и понаблюдаем за его поведением. И снова вы заметите, что советник открывает длинную позицию сразу после запуска. Для этой позиции установлен стоп-лосс на уровне 400 пунктов ниже цены входа и тейк-профит на уровне 800 пунктов выше цены входа. Кроме того, обратите внимание, что позиция открыта с объемом в 1 стандартный лот.

    Частичное закрытие в действии

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



    Заключение

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

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

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

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

    Прикрепленные файлы |
    AutoProtect.mq5 (30.1 KB)
    AutoProtectTest.mq5 (34.25 KB)
    Как внедрить метапромптинг торговых сигналов в советнике MQL5 Как внедрить метапромптинг торговых сигналов в советнике MQL5
    Метапромптинг — подход, при котором LLM сама оптимизирует торговые инструкции на основе реального P&L и метрик качества сигналов. В статье показана практическая реализация на Python и MQL5: реестр версий промптов, исполнительный агент, оценщик по directional accuracy и profit factor и мета-LLM, которая в цикле генерирует улучшения. Решение встраивается в советник без остановки торговли.
    Освоение быстрых сделок: Преодоление паралича исполнения Освоение быстрых сделок: Преодоление паралича исполнения
    Трейлинг-индикатор UT BOT ATR - это персональный и настраиваемый индикатор, который очень эффективен для трейдеров, предпочитающих принимать быстрые решения и зарабатывать деньги на разнице в цене, что называется краткосрочной торговлей (скальперы), а также оказывается жизненно важным и очень эффективным для долгосрочных трейдеров (позиционные трейдеры).
    От CPU к GPU в MQL5: практическая схема OpenCL для ускорения исследований, оптимизаций и паттернов От CPU к GPU в MQL5: практическая схема OpenCL для ускорения исследований, оптимизаций и паттернов
    Узнайте, как выстроить практическую схему перехода от CPU к GPU в MQL5 с использованием OpenCL. Подробно рассматриваются инициализация контекста, организация буферов, крупные батчи, запуск kernel и минимизация обменов данными. Приведены типовые ошибки и способы их устранения. Пример со свечными паттернами иллюстрирует практическую пользу подхода.
    Статистический арбитраж на основе коинтегрированных акций (Часть 10): Обнаружение структурных разрывов Статистический арбитраж на основе коинтегрированных акций (Часть 10): Обнаружение структурных разрывов
    В данной статье представлен тест Чоу для выявления структурных разрывов в зависимостях между парами переменных, а также применение метода кумулятивной суммы квадратов (CUSUM) для мониторинга и раннего выявления структурных разрывов. В статье объявление о партнерстве между Nvidia и Intel и заявление правительства США о введении внешнеторговых пошлин приводятся в качестве примеров, иллюстрирующих, соответственно, инверсию наклона и сдвиг пересечения. Предоставляются скрипты на Python для всех тестов.