English 中文 Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 23): Зональное восстановление с трейлинг-стопом и логикой корзин

Автоматизация торговых стратегий на MQL5 (Часть 23): Зональное восстановление с трейлинг-стопом и логикой корзин

MetaTrader 5Трейдинг |
371 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В предыдущей статье (Часть 22), мы разработали систему зонального восстановления (Zone Recovery System) для трендовой торговли на основе конвертов в MetaQuotes Language 5 (MQL5), используя индекс относительной силы (RSI) и конверты для автоматизации сделок и управления убытками с помощью структурированных зон восстановления. Здесь мы усовершенствуем эту стратегию, включив в нее трейлинг-стопы для динамической фиксации прибыли и систему из нескольких корзин (multi-basket system) для эффективной обработки множества торговых сигналов, тем самым повышая адаптивность на волатильных рынках. Мы рассмотрим следующие темы:

  1. Усовершенствованный трейлинг-стоп и архитектура на основе нескольких корзин
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

В итоге у нас будет усовершенствованная торговая система на MQL5 с расширенными функциями, готовая к тестированию и дальнейшей настройке.


Усовершенствованный трейлинг-стоп и архитектура на основе нескольких корзин

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

АРХИТЕКТУРА ТРЕЙЛИНГ-СТОПОВ

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


Реализация средствами MQL5

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

input group "======= EA GENERAL SETTINGS ======="
input TradingLotSizeOptions lotOption = UNFIXED_LOTSIZE;               // Lot Size Option
input double initialLotSize = 0.01;                                    // Initial Lot Size
input double riskPercentage = 1.0;                                     // Risk Percentage (%)
input int    riskPoints = 300;                                         // Risk Points
input int    baseMagicNumber = 123456789;                              // Base Magic Number
input int    maxInitialPositions = 1;                                  // Maximum Initial Positions (Baskets/Signals)
input double zoneTargetPoints = 600;                                   // Zone Target Points
input double zoneSizePoints = 300;                                     // Zone Size Points
input bool   enableInitialTrailing = true;                             // Enable Trailing Stop for Initial Positions
input int    trailingStopPoints = 50;                                  // Trailing Stop Points
input int    minProfitPoints = 50;                                     // Minimum Profit Points to Start Trailing

Начнем усовершенствование нашей системы зонального восстановления для трендовой торговли на основе конвертов в MQL5, обновляя входные параметры в группе EA GENERAL SETTINGS (общие настройки советника) для поддержки трейлинг-стопов и торговли несколькими корзинами. Внесем четыре ключевых изменения во входные данные. Во-первых, переименуем magicNumber в baseMagicNumber, установим его в 123456789, чтобы использовать в качестве отправной точки для генерации уникальных "магических" чисел для нескольких торговых корзин, обеспечивая отслеживание каждой корзины отдельно в нашей системе. Во-вторых, заменяем maxOrders на maxInitialPositions, устанавливая значение равным 1, чтобы ограничить количество начальных торговых корзин, что позволит нам эффективно управлять несколькими торговыми сигналами.

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

НОВЫЙ НАБОР ВХОДНЫХ ПАРАМЕТРОВ

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

//--- Forward Declaration of MarketZoneTrader
class MarketZoneTrader;

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

//--- Basket Manager Class to Handle Multiple Traders
class BasketManager {
private:
   MarketZoneTrader* m_traders[];                                        //--- Array of trader instances
   int               m_handleRsi;                                        //--- RSI indicator handle
   int               m_handleEnvUpper;                                   //--- Upper Envelopes handle
   int               m_handleEnvLower;                                   //--- Lower Envelopes handle
   double            m_rsiBuffer[];                                     //--- RSI data buffer
   double            m_envUpperBandBuffer[];                            //--- Upper Envelopes buffer
   double            m_envLowerBandBuffer[];                            //--- Lower Envelopes buffer
   string            m_symbol;                                          //--- Trading symbol
   int               m_baseMagicNumber;                                 //--- Base magic number
   int               m_maxInitialPositions;                             //--- Maximum baskets (signals)

   //--- Initialize Indicators
   bool initializeIndicators() {
      m_handleRsi = iRSI(m_symbol, PERIOD_CURRENT, 8, PRICE_CLOSE);
      if (m_handleRsi == INVALID_HANDLE) {
         Print("Failed to initialize RSI indicator");
         return false;
      }
      m_handleEnvUpper = iEnvelopes(m_symbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1);
      if (m_handleEnvUpper == INVALID_HANDLE) {
         Print("Failed to initialize upper Envelopes indicator");
         return false;
      }
      m_handleEnvLower = iEnvelopes(m_symbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4);
      if (m_handleEnvLower == INVALID_HANDLE) {
         Print("Failed to initialize lower Envelopes indicator");
         return false;
      }
      ArraySetAsSeries(m_rsiBuffer, true);
      ArraySetAsSeries(m_envUpperBandBuffer, true);
      ArraySetAsSeries(m_envLowerBandBuffer, true);
      return true;
   }

}

Чтобы упростить управление сделками в составе корзины, определим класс BasketManager с приватными членами для управления несколькими экземплярами класса MarketZoneTrader и данными индикатора. Создаем m_traders, массив указателей MarketZoneTrader, для хранения отдельных торговых корзин, каждая из которых представляет собой отдельный цикл зонального восстановления. Это изменение имеет решающее значение, поскольку позволяет нам обрабатывать несколько торговых сигналов одновременно, в отличие от подхода с одним экземпляром в предыдущей версии. Также объявим массивы m_handleRsi, m_handleEnvUpper и m_handleEnvLower для хранения хэндлов индикаторов, а также массивы m_rsiBuffer, m_envUpperBandBuffer и m_envLowerBandBuffer для хранения данных RSI и конвертов, перенося управление индикаторами из MarketZoneTrader в BasketManager для централизованного управления всеми корзинами.

Также добавим параметр m_symbol для хранения торгового символа, m_baseMagicNumber для генерации уникальных магических чисел для каждой корзины и m_maxInitialPositions для ограничения количества активных корзин, что соответствует новому входному параметру maxInitialPositions. В функции initializeIndicators настроим индикатор RSI с iRSI, используя 8-периодный режим, и конверты с iEnvelopes (150-периодные с отклонением 0.1 и 95-периодный с отклонением 1.4), проверим на INVALID_HANDLE и регистрируем ошибки с помощью Print. Настроим m_rsiBuffer, m_envUpperBandBuffer и m_envLowerBandBuffer в качестве массивов временных рядов с помощью ArraySetAsSeries. Новая структура классов позволит нам эффективно координировать несколько торговых корзин, централизуя данные индикаторов для согласованного формирования сигналов по всем корзинам. Затем нам потребуется разработать логику для подсчета всех отдельных позиций корзин для упрощения отслеживания и очистки корзин.

//--- Count Active Baskets
int countActiveBaskets() {
   int count = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() != MarketZoneTrader::INACTIVE) {
         count++;
      }
   }
   return count;
}

//--- Cleanup Terminated Baskets
void cleanupTerminatedBaskets() {
   int newSize = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() == MarketZoneTrader::INACTIVE) {
         delete m_traders[i];
         m_traders[i] = NULL;
      }
      if (m_traders[i] != NULL) newSize++;
   }
   MarketZoneTrader* temp[];
   ArrayResize(temp, newSize);
   int index = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL) {
         temp[index] = m_traders[i];
         index++;
      }
   }
   ArrayFree(m_traders);
   ArrayResize(m_traders, newSize);
   for (int i = 0; i < newSize; i++) {
      m_traders[i] = temp[i];
   }
   ArrayFree(temp);
}

Здесь мы добавляем две новые функции в класс BasketManager - countActiveBaskets и cleanupTerminatedBaskets. Начнем с функции countActiveBaskets, которая отслеживает количество активных торговых корзин. Инициализируем переменную count значением 0 и перебираем массив m_traders, используя функцию ArraySize. Для каждой ненулевой записи m_traders, проверяем, не является ли ее состояние, полученное с помощью функции getCurrentState, значением MarketZoneTrader::INACTIVE. Если условие выполняется, увеличиваем count. Возвращаем count, чтобы отслеживать количество одновременно открытых корзин, что крайне важно для того, чтобы оставаться в пределах лимита m_maxInitialPositions при открытии новых корзин.

Далее создадим функцию cleanupTerminatedBaskets для удаления неактивных корзин и оптимизации памяти. Сначала подсчитываем количество ненулевых записей в массиве m_traders, проходясь по нему в цикле. Если trader не равен null и его getCurrentState возвращает MarketZoneTrader::INACTIVE, используем delete для освобождения памяти и устанавливаем значение в NULL. Отслеживаем количество оставшихся ненулевых значений trader в newSize. Далее создаем массив temp, изменяем его размер до newSize с ArrayResize и копируем ненулевые trader из m_traders в temp, с помощью счетчика index. Очищаем m_traders с ArrayFree, меняем размер newSize и переносим trader обратно из temp. Наконец, освобождаем temp с ArrayFree. Очистка удаляет невостребованные корзины, поддерживая эффективность нашей системы и готовность к новым сделкам. Перейдем к модификатору публичного доступа, где изменим способ обработки конструктора и деструктора при инициализации и уничтожении членов и элементов класса.

public:
   BasketManager(string symbol, int baseMagic, int maxInitPos) {
      m_symbol = symbol;
      m_baseMagicNumber = baseMagic;
      m_maxInitialPositions = maxInitPos;
      ArrayResize(m_traders, 0);
      m_handleRsi = INVALID_HANDLE;
      m_handleEnvUpper = INVALID_HANDLE;
      m_handleEnvLower = INVALID_HANDLE;
   }

   ~BasketManager() {
      for (int i = 0; i < ArraySize(m_traders); i++) {
         if (m_traders[i] != NULL) delete m_traders[i];
      }
      ArrayFree(m_traders);
      cleanupIndicators();
   }

Начнем с конструктора BasketManager, которая принимает в качестве параметров symbol, baseMagic и maxInitPos. Присвоим их m_symbol, m_baseMagicNumber и m_maxInitialPositions соответственно, чтобы установить торговый символ, базовое магическое число для уникальной идентификации корзины и максимальное количество активных корзин. Инициализируем массив m_traders нулевым значением, используя функцию ArrayResize и устанавливаем хэндлы индикатора — m_handleRsi, m_handleEnvUpper и m_handleEnvLower — на INVALID_HANDLE, чтобы подготовиться к последующей настройке индикатора. Конструктор имеет решающее значение для настройки системы с несколькими корзинами.

Далее создадим деструктор ~BasketManager для очистки ресурсов. Как правило, в качестве префикса для деструкторов используется знак тильды, просто для напоминания. Переберем массив m_traders, используя ArraySize, и удалим все ненулевые экземпляры MarketZoneTrader с помощью delete, чтобы освободить их память. Очистим массив m_traders с ArrayFree и вызовем cleanupIndicators для освобождения хэндлов индикаторов и буферов. Это гарантирует корректное завершение работы нашей системы, предотвращая утечки памяти при остановке советника. В предыдущей версии нам приходилось добавлять логику удаления в обработчик события OnDeinit сразу после обнаружения утечки памяти, но здесь его можно добавить на более раннем этапе, поскольку мы уже знаем, что нужно позаботиться об утечках памяти. Затем нам необходимо изменить логику инициализации таким образом, чтобы она могла загружать существующие позиции в соответствующие корзины. Вот логика, которую мы используем для достижения этой цели.

bool initialize() {
   if (!initializeIndicators()) return false;
   //--- Load existing positions into baskets
   int totalPositions = PositionsTotal();
   for (int i = 0; i < totalPositions; i++) {
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket)) {
         if (PositionGetString(POSITION_SYMBOL) == m_symbol) {
            long magic = PositionGetInteger(POSITION_MAGIC);
            if (magic >= m_baseMagicNumber && magic < m_baseMagicNumber + m_maxInitialPositions) {
               //--- Check if basket already exists for this magic
               bool exists = false;
               for (int j = 0; j < ArraySize(m_traders); j++) {
                  if (m_traders[j] != NULL && m_traders[j].getMagicNumber() == magic) {
                     exists = true;
                     break;
                  }
               }
               if (!exists && countActiveBaskets() < m_maxInitialPositions) {
                  createNewBasket(magic, ticket);
               }
            }
         }
      }
   }
   Print("BasketManager initialized with ", ArraySize(m_traders), " existing baskets");
   return true;
}

/*
//--- PREVIOUS INITIALIZATION
int initialize() {
   //--- Initialization Start
   m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number
   int totalPositions = PositionsTotal();                           //--- Get total positions
   
   for (int i = 0; i < totalPositions; i++) {                       //--- Iterate positions
      ulong ticket = PositionGetTicket(i);                          //--- Get ticket
      if (PositionSelectByTicket(ticket)) {                         //--- Select position
         if (PositionGetString(POSITION_SYMBOL) == m_tradeConfig.marketSymbol && PositionGetInteger(POSITION_MAGIC) == m_tradeConfig.tradeIdentifier) { //--- Check symbol and magic
            if (activateTrade(ticket)) {                            //--- Activate position
               Print("Existing position activated: Ticket=", ticket); //--- Log activation
            } else {
               Print("Failed to activate existing position: Ticket=", ticket); //--- Log failure
            }
         }
      }
   }
   
   m_handleRsi = iRSI(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 8, PRICE_CLOSE); //--- Initialize RSI
   if (m_handleRsi == INVALID_HANDLE) {                             //--- Check RSI
      Print("Failed to initialize RSI indicator");                  //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvUpper = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1); //--- Initialize upper Envelopes
   if (m_handleEnvUpper == INVALID_HANDLE) {                        //--- Check upper Envelopes
      Print("Failed to initialize upper Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvLower = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4); //--- Initialize lower Envelopes
   if (m_handleEnvLower == INVALID_HANDLE) {                        //--- Check lower Envelopes
      Print("Failed to initialize lower Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   ArraySetAsSeries(m_rsiBuffer, true);                             //--- Set RSI buffer
   ArraySetAsSeries(m_envUpperBandBuffer, true);                    //--- Set upper Envelopes buffer
   ArraySetAsSeries(m_envLowerBandBuffer, true);                    //--- Set lower Envelopes buffer
   
   Print("EA initialized successfully");                            //--- Log success
   return INIT_SUCCEEDED;                                           //--- Return success
   //--- Initialization End
}
*/

Здесь мы реализуем обновленную функцию initialize в классе BasketManager для улучшения нашей системы торговли с использованием нескольких корзин, инициализируя индикаторы и загружая существующие позиции в отдельные корзины. Для начала вызовем функцию initializeIndicators для настройки RSI и конвертов, возвращая false в случае неудачи, чтобы убедиться, что наша система располагает необходимыми рыночными данными. В отличие от предыдущей версии, где настройка индикаторов осуществлялась непосредственно в функции initialize в MarketZoneTrader, теперь мы централизуем этот процесс в BasketManager, чтобы использовать данные индикаторов для нескольких корзин. Далее проверяем существующие позиции, используя функцию PositionsTotal и проходим циклом по каждой позиции, получая ее ticket с функцией PositionGetTicket.

Если PositionSelectByTicket успешен и символ позиции совпадает с m_symbol (с помощью PositionGetString), проверяем, попадает ли его магическое число, полученное с помощью функции PositionGetInteger, в диапазон от m_baseMagicNumber до m_baseMagicNumber + m_maxInitialPositions. Затем проверяем, существует ли уже корзина для этого магического числа, проходя циклом по m_traders и вызывая функцию getMagicNumber для ненулевых записей. Если корзина отсутствует и значение countActiveBaskets меньше m_maxInitialPositions, вызываем функцию createNewBasket с магическим числом и ticket, чтобы загрузить позицию в новую корзину. Наконец, выводим количество инициализированных корзин с помощью Print, используя ArraySize m_traders и возвращаем true. При запуске программы мы получаем следующий результат.

ИНИЦИАЛИЗАЦИЯ КОРЗИН

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

void processTick() {
   //--- Process existing baskets
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL) {
         m_traders[i].processTick(m_rsiBuffer, m_envUpperBandBuffer, m_envLowerBandBuffer);
      }
   }
   cleanupTerminatedBaskets();

   //--- Check for new signals on new bar
   if (!isNewBar()) return;

   if (!CopyBuffer(m_handleRsi, 0, 0, 3, m_rsiBuffer)) {
      Print("Error loading RSI data. Reverting.");
      return;
   }
   if (!CopyBuffer(m_handleEnvUpper, 0, 0, 3, m_envUpperBandBuffer)) {
      Print("Error loading upper envelopes data. Reverting.");
      return;
   }
   if (!CopyBuffer(m_handleEnvLower, 1, 0, 3, m_envLowerBandBuffer)) {
      Print("Error loading lower envelopes data. Reverting.");
      return;
   }

   const int rsiOverbought = 70;
   const int rsiOversold = 30;
   int ticket = -1;
   ENUM_ORDER_TYPE signalType = (ENUM_ORDER_TYPE)-1;

   double askPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_ASK), Digits());
   double bidPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_BID), Digits());

   if (m_rsiBuffer[1] < rsiOversold && m_rsiBuffer[2] > rsiOversold && m_rsiBuffer[0] < rsiOversold) {
      if (askPrice > m_envUpperBandBuffer[0]) {
         if (countActiveBaskets() < m_maxInitialPositions) {
            signalType = ORDER_TYPE_BUY;
         }
      }
   } else if (m_rsiBuffer[1] > rsiOverbought && m_rsiBuffer[2] < rsiOverbought && m_rsiBuffer[0] > rsiOverbought) {
      if (bidPrice < m_envLowerBandBuffer[0]) {
         if (countActiveBaskets() < m_maxInitialPositions) {
            signalType = ORDER_TYPE_SELL;
         }
      }
   }

   if (signalType != (ENUM_ORDER_TYPE)-1) {
      //--- Create new basket with unique magic number
      int newMagic = m_baseMagicNumber + ArraySize(m_traders);
      if (newMagic < m_baseMagicNumber + m_maxInitialPositions) {
         MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, newMagic);
         ticket = newTrader.openInitialOrder(signalType); //--- Open INITIAL position
         if (ticket > 0 && newTrader.activateTrade(ticket)) {
            int size = ArraySize(m_traders);
            ArrayResize(m_traders, size + 1);
            m_traders[size] = newTrader;
            Print("New basket created: Magic=", newMagic, ", Ticket=", ticket, ", Type=", EnumToString(signalType));
         } else {
            delete newTrader;
            Print("Failed to create new basket: Ticket=", ticket);
         }
      } else {
         Print("Maximum initial positions (baskets) reached: ", m_maxInitialPositions);
      }
   }
}

В функции мы начинаем с перебора массива m_traders, используя функцию ArraySize. Для каждого ненулевого экземпляра MarketZoneTrader, вызываем его функцию processTick, передавая m_rsiBuffer, m_envUpperBandBuffer и m_envLowerBandBuffer для обработки логики отдельных корзин. Это отличается от предыдущей версии, где функция processTick напрямую управляла одним торговым циклом. Затем вызываем функцию cleanupTerminatedBaskets, чтобы удалить неактивные корзины, обеспечивая эффективное использование ресурсов. Далее проверяем наличие новых торговых сигналов только на новом баре, используя функцию isNewBar, и завершаем работу при false, чтобы сэкономить ресурсы.

Загрузим данные индикатора с помощью CopyBuffer для m_handleRsi, m_handleEnvUpper и m_handleEnvLower в соответствующие буферы с записью ошибок с помощью функции Print и завершением работы при возникновении проблем, в отличие от предыдущей версии, где это делалось в MarketZoneTrader. Установим rsiOverbought на 70 и rsiOversold на 30, а также инициализируем ticket и signalType. Получим askPrice и bidPrice, используя SymbolInfoDouble с SYMBOL_ASK и SYMBOL_BID, нормализованные с помощью функции NormalizeDouble.

Для сигнала на покупку, если m_rsiBuffer указывает на условия перепроданности и askPrice превышает m_envUpperBandBuffer, устанавливаем signalType в значение ORDER_TYPE_BUY, если countActiveBaskets ниже m_maxInitialPositions. Для сигнала на продажу, если m_rsiBuffer показывает условия перекупленности, а bidPrice ниже m_envLowerBandBuffer, устанавливаем signalType на ORDER_TYPE_SELL. Если существует допустимый signalType, создаем уникальное магическое число с m_baseMagicNumber плюс ArraySize(m_traders). Если оно находится в пределах m_maxInitialPositions, создаем новый MarketZoneTrader с входными параметрами и новым магическим числом.

Вызовем openInitialOrder с signalType. Если возвращенный ticket действителен и функция activateTrade выполняется успешно, добавляем новый trader в m_traders, используя ArrayResize и фиксируем успех с помощью Print и функции EnumToString. В противном случае удаляем trader и регистрируем ошибку или отмечаем, если достигнут лимит корзины. После открытия новых сделок нам потребуется создать для них новые корзины. Ниже приведена необходимая логика.

private:
   void createNewBasket(long magic, ulong ticket) {
      MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, magic);
      if (newTrader.activateTrade(ticket)) {
         int size = ArraySize(m_traders);
         ArrayResize(m_traders, size + 1);
         m_traders[size] = newTrader;
         Print("Existing position loaded into basket: Magic=", magic, ", Ticket=", ticket);
      } else {
         delete newTrader;
         Print("Failed to load existing position into basket: Ticket=", ticket);
      }
   }

Реализуем функцию createNewBasket в приватном разделе класса BasketManager. Это новое дополнение, призванное улучшить нашу систему торговли с использованием нескольких корзин путем создания и управления новыми торговыми корзинами для существующих позиций. Для начала создадим новый экземпляр MarketZoneTrader с именем newTrader, используя входные параметры lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints и предоставленное "магическое" число для настройки уникальной корзины сделок. Напомним, что в предыдущей версии этот пользовательский входной параметр присутствовал на этапе инициализации, поскольку нам требовался только один экземпляр зоны. Поэтому он применялся ко всем новым позициям, но в данном случае мы организуем его в новые экземпляры классов. Вот код для более быстрого сравнения.

//--- PREVIOUS VERSION OF NEW CLASS INSTANCE
//--- Global Instance
MarketZoneTrader *trader = NULL;                                        //--- Declare trader instance

int OnInit() {
   //--- EA Initialization Start
   trader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints, zoneSizePoints); //--- Create trader instance
   return trader.initialize();                                           //--- Initialize EA
   //--- EA Initialization End
}

Затем вызовем activateTrade для объекта newTrader с заданным ticket, чтобы добавить существующую позицию в корзину. В случае успеха мы получим текущий размер массива m_traders, используя ArraySize, увеличим его на единицу с помощью ArrayResize и добавим newTrader в новый слот. Зарегистрируем успешное выполнение с помощью функции Print, включая значения magic и ticket. Если activateTrade завершается ошибкой, удалим newTrader, чтобы освободить память, и регистрируем ошибку с помощью Print. Теперь эта функция позволит нам организовывать существующие позиции в отдельные корзины, что является ключевой особенностью нашей системы из нескольких корзин, в отличие от подхода с одним экземпляром в предыдущей версии. Этот класс позволит нам эффективно управлять корзинами сделок. Теперь перейдем к модификации базового класса таким образом, чтобы он мог содержать новые функции, такие как множественные корзины и трейлинг-стоп. Начнем с членов класса.

//--- Modified MarketZoneTrader Class
class MarketZoneTrader {
private:
   enum TradeState { INACTIVE, RUNNING, TERMINATING };

   struct TradeMetrics {
      bool   operationSuccess;
      double totalVolume;
      double netProfitLoss;
   };

   struct ZoneBoundaries {
      double zoneHigh;
      double zoneLow;
      double zoneTargetHigh;
      double zoneTargetLow;
   };

   struct TradeConfig {
      string         marketSymbol;
      double         openPrice;
      double         initialVolume;
      long           tradeIdentifier;
      string         initialTradeLabel;  //--- Label for initial positions
      string         recoveryTradeLabel; //--- Label for recovery positions
      ulong          activeTickets[];
      ENUM_ORDER_TYPE direction;
      double         zoneProfitSpan;
      double         zoneRecoverySpan;
      double         accumulatedBuyVolume;
      double         accumulatedSellVolume;
      TradeState     currentState;
      bool           hasRecoveryTrades;  //--- Flag to track recovery trades
      double         trailingStopLevel;  //--- Virtual trailing stop level
   };

   struct LossTracker {
      double tradeLossTracker;
   };

   TradeConfig           m_tradeConfig;
   ZoneBoundaries        m_zoneBounds;
   LossTracker           m_lossTracker;
   string                m_lastError;
   int                   m_errorStatus;
   CTrade                m_tradeExecutor;
   TradingLotSizeOptions m_lotOption;
   double                m_initialLotSize;
   double                m_riskPercentage;
   int                   m_riskPoints;
   double                m_zoneTargetPoints;
   double                m_zoneSizePoints;
}

В данном случае мы улучшаем нашу программу, модифицируя класс MarketZoneTrader, а именно его приватный раздел, чтобы включить новые функции, поддерживающие трейлинг-стопы и улучшенную маркировку сделок. Мы сохраняем основную структуру, но вносим ключевые изменения в структуру TradeConfig в соответствии с нашей усовершенствованной стратегией. Сохраним перечисление TradeState с состояниями INACTIVE, RUNNING и TERMINATING, а также структуры TradeMetrics, ZoneBoundaries и LossTracker неизменными по сравнению с предыдущей версией, поскольку они продолжают управлять состояниями сделок, показателями эффективности, границами зон и отслеживанием убытков.

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

Среди переменных-членов мы сохраняем переменные m_tradeConfig, m_zoneBounds, m_lossTracker, m_lastError, m_errorStatus, m_tradeExecutor, m_lotOption, m_initialLotSize, m_riskPercentage, m_riskPoints, m_zoneTargetPoints и m_zoneSizePoints в их первоначальном виде, но теперь их роли поддерживают новую функциональность трейлинг-стопа и несколько корзин в каждом экземпляре MarketZoneTrader. В частности, мы удаляем из класса переменные, связанные с индикаторами, такие как m_handleRsi и m_rsiBuffer, поскольку теперь они централизованно управляются классом BasketManager, что позволяет каждому трейдеру сосредоточиться на отдельных операциях с корзиной. В конструкторе и деструкторе нам потребуется немного изменить некоторые переменные, чтобы они обрабатывали новые возможности.

public:
   MarketZoneTrader(TradingLotSizeOptions lotOpt, double initLot, double riskPct, int riskPts, double targetPts, double sizePts, long magic) {
      m_tradeConfig.currentState = INACTIVE;
      ArrayResize(m_tradeConfig.activeTickets, 0);
      m_tradeConfig.zoneProfitSpan = targetPts * _Point;
      m_tradeConfig.zoneRecoverySpan = sizePts * _Point;
      m_lossTracker.tradeLossTracker = 0.0;
      m_lotOption = lotOpt;
      m_initialLotSize = initLot;
      m_riskPercentage = riskPct;
      m_riskPoints = riskPts;
      m_zoneTargetPoints = targetPts;
      m_zoneSizePoints = sizePts;
      m_tradeConfig.marketSymbol = _Symbol;
      m_tradeConfig.tradeIdentifier = magic;
      m_tradeConfig.initialTradeLabel = "EA_INITIAL_" + IntegerToString(magic); //--- Label for initial positions
      m_tradeConfig.recoveryTradeLabel = "EA_RECOVERY_" + IntegerToString(magic); //--- Label for recovery positions
      m_tradeConfig.hasRecoveryTrades = false; //--- Initialize recovery flag
      m_tradeConfig.trailingStopLevel = 0.0; //--- Initialize trailing stop
      m_tradeExecutor.SetExpertMagicNumber(magic);
   }

   ~MarketZoneTrader() {
      ArrayFree(m_tradeConfig.activeTickets);
   }

Начнем с конструктора MarketZoneTrader. Теперь он принимает дополнительный параметр magic для присвоения каждой корзине сделок уникального магического числа, в отличие от предыдущей версии, где использовалось фиксированное магическое число. Для улучшения маркировки сделок мы добавляем m_tradeConfig.initialTradeLabel как EA_INITIAL плюс magic (с помощью IntegerToString) и m_tradeConfig.recoveryTradeLabel как EA_RECOVERY плюс magic, что позволяет четко идентифицировать начальные и восстановительные сделки в рамках корзины. Инициализируем m_tradeConfig.hasRecoveryTrades значением false, чтобы отслеживать статус восстановительных сделок, и устанавливаем m_tradeConfig.trailingStopLevel равным 0.0 для виртуального трейлинг-стопа. Обе функции являются новыми. Наконец, настраиваем m_tradeExecutor с помощью функции SetExpertMagicNumber, используя параметр magic. Для удобства выделили основные изменения.

Далее упростим деструктор ~MarketZoneTrader по сравнению с предыдущей версией, которая называлась cleanup. Теперь мы очищаем m_tradeConfig.activeTickets только с помощью ArrayFree, в то время как индикаторы очищаются с помощью BasketManager, область действия деструктора сужается до ресурсов, специфичных для корзины. Затем мы можем обновить функцию, отвечающую за активацию сделок, чтобы она могла инициализировать уровень трейлинг-стопа и состояние восстановления для первоначальных сделок.

bool activateTrade(ulong ticket) {

   m_tradeConfig.hasRecoveryTrades = false;
   m_tradeConfig.trailingStopLevel = 0.0;
   
   //--- THE REST OF THE LOGIC REMAINS
   
   return true;
}

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

int openInitialOrder(ENUM_ORDER_TYPE orderType) {
   //--- Open INITIAL position based on signal
   int ticket;
   double openPrice;
   if (orderType == ORDER_TYPE_BUY) {
      openPrice = NormalizeDouble(getMarketAsk(), Digits());
   } else if (orderType == ORDER_TYPE_SELL) {
      openPrice = NormalizeDouble(getMarketBid(), Digits());
   } else {
      Print("Invalid order type [Magic=", m_tradeConfig.tradeIdentifier, "]");
      return -1;
   }
   double lotSize = 0;
   if (m_lotOption == FIXED_LOTSIZE) {
      lotSize = m_initialLotSize;
   } else if (m_lotOption == UNFIXED_LOTSIZE) {
      lotSize = calculateLotSize(m_riskPercentage, m_riskPoints);
   }
   if (lotSize <= 0) {
      Print("Invalid lot size [Magic=", m_tradeConfig.tradeIdentifier, "]: ", lotSize);
      return -1;
   }
   if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, 0, 0, m_tradeConfig.initialTradeLabel)) {
      ticket = (int)m_tradeExecutor.ResultOrder();
      Print("INITIAL trade opened [Magic=", m_tradeConfig.tradeIdentifier, "]: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize);
   } else {
      ticket = -1;
      Print("Failed to open INITIAL order [Magic=", m_tradeConfig.tradeIdentifier, "]: Type=", EnumToString(orderType), ", Volume=", lotSize);
   }
   return ticket;
}

В публичном разделе класса MarketZoneTrader мы добавили новую функцию openInitialOrder, которая поддерживает наши улучшения в области работы с несколькими корзинами и улучшенной маркировки сделок, открывая начальные позиции для конкретной корзины сделок с уникальным идентификатором. Начнем с инициализации ticket и openPrice. Для orderType в значении ORDER_TYPE_BUY установим openPrice, используя getMarketAsk, и нормализуем его с помощью NormalizeDouble и Digits. Для ORDER_TYPE_SELL используем getMarketBid. Если orderType недействителен, регистрируем ошибку с помощью Print, включающего m_tradeConfig.tradeIdentifier, и возвращаем -1.

Определяем lotSize на основе m_lotOption: для FIXED_LOTSIZE используем m_initialLotSize; для UNFIXED_LOTSIZE вызываем функцию calculateLotSize с параметрами m_riskPercentage и m_riskPoints. При недопустимом lotSize, регистрируем ошибку с помощью Print и возвращаем -1. Затем откроем позицию с помощью m_tradeExecutor.PositionOpen с m_tradeConfig.marketSymbol, orderType, lotSize, openPrice и m_tradeConfig.initialTradeLabel для четкой маркировки первоначальных сделок. В случае успеха присвоим ticket значение ResultOrder и выведем результат с помощью Print, включая m_tradeConfig.tradeIdentifier и функцию EnumToString. При неудаче установим ticket на -1 и выведем сообщение об ошибке. Наконец, возвращаем ticket. В отличие от функции openOrder в предыдущей версии, эта функция использует новый параметр initialTradeLabel и фокусируется исключительно на начальных позициях, что соответствует нашей системе из нескольких корзин. В результате компиляции мы получаем следующий результат.

НАЧАЛЬНАЯ КОРЗИНА

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

void evaluateMarketTick() {
   if (m_tradeConfig.currentState == INACTIVE) return;
   if (m_tradeConfig.currentState == TERMINATING) {
      finalizePosition();
      return;
   }
   double currentPrice;
   double profitPoints = 0.0;

   //--- Handle BUY initial position
   if (m_tradeConfig.direction == ORDER_TYPE_BUY) {
      currentPrice = getMarketBid();
      profitPoints = (currentPrice - m_tradeConfig.openPrice) / _Point;

      //--- Trailing Stop Logic for Initial Position
      if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
         //--- Calculate desired trailing stop level
         double newTrailingStop = currentPrice - trailingStopPoints * _Point;
         //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
         if (profitPoints >= minProfitPoints + trailingStopPoints) {
            if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop > m_tradeConfig.trailingStopLevel) {
               m_tradeConfig.trailingStopLevel = newTrailingStop;
               Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
            }
         }
         //--- Check if price has hit trailing stop
         if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice <= m_tradeConfig.trailingStopLevel) {
            Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " <= TrailingStop=", m_tradeConfig.trailingStopLevel);
            finalizePosition();
            return;
         }
      }

      //--- Zone Recovery Logic
      if (currentPrice > m_zoneBounds.zoneTargetHigh) {
         Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh);
         finalizePosition();
         return;
      } else if (currentPrice < m_zoneBounds.zoneLow) {
         Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow);
         triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice);
      }
   }
   //--- Handle SELL initial position
   else if (m_tradeConfig.direction == ORDER_TYPE_SELL) {
      currentPrice = getMarketAsk();
      profitPoints = (m_tradeConfig.openPrice - currentPrice) / _Point;

      //--- Trailing Stop Logic for Initial Position
      if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
         //--- Calculate desired trailing stop level
         double newTrailingStop = currentPrice + trailingStopPoints * _Point;
         //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
         if (profitPoints >= minProfitPoints + trailingStopPoints) {
            if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop < m_tradeConfig.trailingStopLevel) {
               m_tradeConfig.trailingStopLevel = newTrailingStop;
               Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
            }
         }
         //--- Check if price has hit trailing stop
         if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice >= m_tradeConfig.trailingStopLevel) {
            Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " >= TrailingStop=", m_tradeConfig.trailingStopLevel);
            finalizePosition();
            return;
         }
      }

      //--- Zone Recovery Logic
      if (currentPrice < m_zoneBounds.zoneTargetLow) {
         Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow);
         finalizePosition();
         return;
      } else if (currentPrice > m_zoneBounds.zoneHigh) {
         Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh);
         triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice);
      }
   }
}

Здесь мы улучшаем программу, обновляя функцию evaluateMarketTick, чтобы включить логику трейлинг-стопа, сохраняя при этом существующую логику зонального восстановления. Начнем с проверки, находится ли значение m_tradeConfig.currentState в состоянии INACTIVE или TERMINATING, после чего, как и прежде, завершим работу или вызовем функцию finalizePosition. Для позиции на покупку (m_tradeConfig.direction в качестве ORDER_TYPE_BUY) получим currentPrice с помощью getMarketBid и вычислим profitPoints как разницу между currentPrice и m_tradeConfig.openPrice, деленную на _Point. Новая логика трейлинг-стопа проверяет, истинно ли значение enableInitialTrailing, ложно ли значение m_tradeConfig.hasRecoveryTrades и соответствует ли значение profitPoints значению minProfitPoints или превышает его. В этом случае мы вычисляем newTrailingStop, вычитая trailingStopPoints, умноженное на _Point, из currentPrice. Если значение profitPoints также превышает minProfitPoints плюс trailingStopPoints, и m_tradeConfig.trailingStopLevel равно 0,0 или меньше newTrailingStop, обновляем m_tradeConfig.trailingStopLevel и уведомляем об этом с помощью функции Print.

Если параметр m_tradeConfig.trailingStopLevel установлен и значение currentPrice падает ниже него, мы регистрируем срабатывание и вызываем finalizePosition для закрытия сделки. Логика зонального восстановления остается неизменной: позиция закрывается, если currentPrice превышает m_zoneBounds.zoneTargetHigh, или запускается восстановительная сделка на продажу с помощью функции triggerRecoveryTrade, если цена падает ниже m_zoneBounds.zoneLow.

Для позиции на продажу (m_tradeConfig.direction в качестве ORDER_TYPE_SELL), извлекаем currentPrice с помощью getMarketAsk и вычисляем profitPoints в обратном порядке. Логика трейлинг-стопа зеркально отражает вариант на покупку. Устанавливаем newTrailingStop, добавляя _Point trailingStopPoints раз на currentPrice и обновляя m_tradeConfig.trailingStopLevel, если условия выполнены. Позиция закрывается, если currentPrice превышает это значение. Логика зонального восстановления закрывает позицию, если currentPrice ниже m_zoneBounds.zoneTargetLow, или запускает восстановительную сделку на покупку, если она выше m_zoneBounds.zoneHigh. Мы не включаем физический трейлинг-стоп, потому что хотим иметь полный контроль над системой. Таким образом, мы можем отслеживать и управлять всеми экземплярами. Ниже приведен результат выполнения программы для функции трейлинг-стопа.

ЭКЗЕМПЛЯР ТРЕЙЛИНГ-СТОПА

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

//--- Global Instance
BasketManager *manager = NULL;

int OnInit() {
   manager = new BasketManager(_Symbol, baseMagicNumber, maxInitialPositions);
   if (!manager.initialize()) {
      delete manager;
      manager = NULL;
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason) {
   if (manager != NULL) {
      delete manager;
      manager = NULL;
      Print("EA deinitialized");
   }
}

void OnTick() {
   if (manager != NULL) {
      manager.processTick();
   }
}

Обновим глобальные обработчики экземпляров и событий, чтобы использовать новый класс BasketManager, заменив использование класса MarketZoneTrader в предыдущей версии для улучшения системы торговли несколькими корзинами путем централизации управления несколькими корзинами сделок. Начнем с объявления глобального указателя "менеджера" на класс BasketManager, инициализированный NULL, вместо прежнего указателя trader на MarketZoneTrader. Это изменение имеет решающее значение, поскольку позволяет нам управлять несколькими корзинами сделок через единый менеджер, в отличие от подхода с использованием одного экземпляра в предыдущей версии.

В обработчике событий OnInit мы создаем новый экземпляр BasketManager для manager, передавая _Symbol, baseMagicNumber и maxInitialPositions, чтобы настроить его для текущего графика, уникального идентификатора корзины и максимального количества корзин. Вызовем метод manager.initialize для настройки индикаторов и загрузки существующих позиций, и если он не срабатывает, мы удаляем manager, устанавливаем его значение в NULL и возвращаем INIT_FAILED. В случае успеха возвращаем INIT_SUCCEEDED.

В обработчике событий OnDeinit проверяем, не равен ли manager NULL, затем удаляем его с помощью delete, устанавливаем в NULL и сообщаем о деинициализации с помощью Print. В OnTick проверяем, не равен ли manager NULL и вызываем manager.processTick для обработки рыночных тиков по всем корзинам, заменяя предыдущий вызов на trader.processTick. Это централизует обработку тиков для нескольких корзин, повышая способность системы управлять одновременно поступающими торговыми сигналами. В результате компиляции мы получаем следующее.

ПОСЛЕДНИЕ СДЕЛКИ

Из изображения видно, что мы можем создавать отдельные сигнальные корзины и управлять ими, используя различные метки, сформированные на основе предоставленного магического числа. Осталось протестировать программу на истории.


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

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

График тестирования на истории:

График

Отчет о тестировании на истории:

ОТЧЕТ


Заключение

Мы улучшили нашу систему зонального восстановления для трендовой торговли на основе конвертов на MQL5, введя трейлинг-стопы и торговую систему на основе нескольких корзин. Приложение из Части 22 было дополнено такими новыми компонентами, как класс BasketManager и обновленные функции MarketZoneTrader. Эти улучшения обеспечивают более гибкую и надежную торговую платформу, которую можно дополнительно настроить, изменив такие параметры, как trailingStopPoints или maxInitialPositions.

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

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

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

Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Tyrone Chan
Tyrone Chan | 31 авг. 2025 в 07:20

Сэр, не открываются первоначальные сделки на продажу.

Связано ли это с логикой торговли?

Реализация частичного закрытия позиций в MQL5 Реализация частичного закрытия позиций в MQL5
В статье разрабатывается класс для управления частичным закрытием позиций в MQL5 с последующей интеграцией в советника Order Blocks. Кроме того, представлены результаты тестирования, сравнивающие стратегию с использованием частичных закрытий и без них, а также анализ того, при каких условиях их использование может обеспечивать и максимизировать прибыль. В заключение делается вывод, что в торговых стратегиях, особенно ориентированных на более широкие ценовые движения, использование частичных закрытий может быть довольно выгодным.
Архитектура коллективных торговых решений ИИ-агентов Архитектура коллективных торговых решений ИИ-агентов
Статья описывает архитектуру мультиагентной торговой системы на базе языковой модели grok-4-fast, где вместо одного системного промпта работают четыре независимых аналитика с принципиально разными ролями: бык, медведь, риск-менеджер и арбитр. Три аналитика запускаются параллельно через ThreadPoolExecutor и за 3–5 секунд формируют аргументированные позиции по одним и тем же рыночным данным, после чего детерминированный судья выносит финальный вердикт по жёстким правилам.
Переосмысливаем классические стратегии (Часть 14): Анализ нескольких стратегий Переосмысливаем классические стратегии (Часть 14): Анализ нескольких стратегий
В этой статье мы продолжаем построение ансамбля торговых стратегий с использованием генетического оптимизатора MT5 для настройки параметров стратегий. Сегодня мы проанализируем данные в Python, чтобы проверить, сможет ли такая модель лучше предсказывать, какая стратегия окажется более успешной и какая сработает точнее, и окажется ли это эффективнее прямого прогнозирования доходности. Сразу скажу, что тестирование приложения с такой статистической моделью показало резкое ухудшение в результатах. Все дело в генетическом оптимизаторе — к сожалению, он отдает предпочтение коррелированным стратегиям. Поэтому мы пересмотрим метод, чтобы сохранить фиксированные веса голосов и сосредоточить оптимизацию на настройках индикаторов.
Марковские цепи в трейдинге и прогнозировании цены Марковские цепи в трейдинге и прогнозировании цены
В этой статье мы рассмотрим, как строить и применять марковские цепи в условиях рынка: от выбора состояний и подсчета переходов до генерации прогнозов траекторий и уровней. Также, мы увидим, как можно применять марковские цепи для качественных и количественных данных, способы учета редких событий и влияние горизонта прогноза. Даны примеры на ценах и индикаторах, а также вариант для оценки последовательности сделок, с готовыми реализациями в MQL5.