English 中文 Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 19): Envelopes Trend Bounce Scalping — Исполнение сделок и управление рисками (Часть II)

Автоматизация торговых стратегий на MQL5 (Часть 19): Envelopes Trend Bounce Scalping — Исполнение сделок и управление рисками (Часть II)

MetaTrader 5Трейдинг |
82 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В предыдущей статье (Часть 18) мы заложили основы стратегии скальпинга на коррекции на основе конвертов (Envelopes Trend Bounce Scalping) на MetaQuotes Language 5 (MQL5), разработав основную инфраструктуру советника и логику генерации сигналов. Теперь, в Части 19, мы переходим к внедрению исполнения сделок и управления рисками для полной автоматизации стратегии. Мы рассмотрим следующие темы:

  1. Стратегическая дорожная карта и архитектура
  2. Реализация средствами MQL5
  3. Тестирование на истории и оптимизация
  4. Заключение

В итоге у нас будет полноценная торговая система на MQL5 для скальпинга на отскоках от тренда.


Стратегическая дорожная карта и архитектура

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

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

ПЛАН АРХИТЕКТУРЫ


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

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

//--- Define interface for strategy signal expressions
interface IAdvisorStrategyExpression {
   bool Evaluate();                                       //--- Evaluate signal
   bool GetFireOnlyWhenReset();                           //--- Retrieve fire-only-when-reset flag
   void SetFireOnlyWhenReset(bool value);                 //--- Set fire-only-when-reset flag
   void ResetSignalValue();                               //--- Reset signal value
};

//--- Define base class for advisor signals
class ASSignal : public IAdvisorStrategyExpression {
private:
   bool _fireOnlyWhenReset;                               //--- Store fire-only-when-reset flag
   int _previousSignalValue;                              //--- Store previous signal value
   int _signalValue;                                      //--- Store current signal value

protected:
   //--- Declare pure virtual method for signal evaluation
   bool virtual EvaluateSignal() = 0;                      //--- Require derived classes to implement

public:
   //--- Initialize signal
   void ASSignal() {
      _fireOnlyWhenReset = false;                         //--- Set fire-only-when-reset to false
      _previousSignalValue = -1;                          //--- Set previous value to -1
      _signalValue = -1;                                  //--- Set current value to -1
   }

   //--- Evaluate signal
   bool Evaluate() {
      if (_signalValue == -1) {                           //--- Check if signal uncomputed
         _signalValue = EvaluateSignal();                 //--- Compute signal
      }
      if (_fireOnlyWhenReset) {                           //--- Check fire-only-when-reset
         return (_previousSignalValue <= 0 && _signalValue == 1); //--- Return true if signal transitions to 1
      } else {
         return _signalValue == 1;                        //--- Return true if signal is 1
      }
   }

   //--- Retrieve fire-only-when-reset flag
   bool GetFireOnlyWhenReset() {
      return _fireOnlyWhenReset;                          //--- Return flag
   }

   //--- Set fire-only-when-reset flag
   void SetFireOnlyWhenReset(bool value) {
      _fireOnlyWhenReset = value;                         //--- Set flag
   }

   //--- Reset signal value
   void ResetSignalValue() {
      _previousSignalValue = _signalValue;                //--- Store current as previous
      _signalValue = -1;                                  //--- Reset current value
   }
};

Для создания оптимизированной системы обработки торговых сигналов мы сосредоточились на модульной и надежной системе оценки сигналов. Начнем с определения интерфейса IAdvisorStrategyExpression, который мы используем для создания согласованной схемы работы с сигналами. Этот интерфейс включает четыре ключевые функции: функцию Evaluate для определения активности сигнала, функцию GetFireOnlyWhenReset для проверки флага, управляющего срабатыванием сигнала, функцию SetFireOnlyWhenReset для изменения этого флага и функцию ResetSignalValue для очистки состояния сигнала перед последующими вычислениями.

Затем мы разрабатываем класс ASSignal, который мы разработали для реализации интерфейса IAdvisorStrategyExpression, служащего основой для конкретных типов сигналов в нашей стратегии. Внутри класса ASSignal мы определяем три приватные переменные: _fireOnlyWhenReset для управления активацией сигналов только после перезапуска, _previousSignalValue для отслеживания предыдущего состояния сигнала и _signalValue для хранения текущего состояния. Инициализируем их в функции-конструкторе ASSignal, устанавливая _fireOnlyWhenReset на false, а _previousSignalValue и _signalValue на -1, что указывает на невычисленное состояние. Наша функция Evaluate проверяет, равно ли _signalValue -1, вызывая чисто виртуальную функцию EvaluateSignal (которая будет определена в производных классах) для вычисления сигнала, и возвращает true в зависимости от значения _fireOnlyWhenReset — либо когда _signalValue равно 1, либо при переходе от неположительного значения _previousSignalValue к 1.

Для управления поведением сигналов мы реализовали функцию GetFireOnlyWhenReset для получения значения флага _fireOnlyWhenReset и функцию SetFireOnlyWhenReset для его обновления, что позволяет нам точно настраивать время срабатывания сигналов. Мы также включили функцию ResetSignalValue, которую используем для сохранения значения _signalValue в переменной _previousSignalValue и сброса значения _signalValue до -1, подготавливая почву для следующего цикла оценки. После того, как мы пометили функцию EvaluateSignal как чисто виртуальную, нам необходимы производные классы для логики конкретного сигнала, гарантирующей гибкость. Теперь мы можем определить еще несколько классов для логики управления сигналами.

//--- Define class for managing trade signals
class TradeSignalCollection {
private:
   IAdvisorStrategyExpression* _tradeSignals[];           //--- Store array of signal pointers
   int _pointer;                                          //--- Track current iteration index
   int _size;                                             //--- Track number of signals

public:
   //--- Initialize empty signal collection
   void TradeSignalCollection() {
      _pointer = -1;                                      //--- Set initial pointer to -1
      _size = 0;                                          //--- Set initial size to 0
   }

   //--- Destructor to clean up signals
   void ~TradeSignalCollection() {
      for (int i = 0; i < ArraySize(_tradeSignals); i++) { //--- Iterate signals
         delete(_tradeSignals[i]);                        //--- Delete each signal
      }
   }

   //--- Add signal to collection
   void Add(IAdvisorStrategyExpression* item) {
      _size = _size + 1;                                  //--- Increment size
      ArrayResize(_tradeSignals, _size, 8);               //--- Resize array with reserve
      _tradeSignals[(_size - 1)] = item;                  //--- Store signal at last index
   }

   //--- Remove signal at index
   IAdvisorStrategyExpression* Remove(int index) {
      IAdvisorStrategyExpression* removed = NULL;         //--- Initialize removed signal as null
      if (index >= 0 && index < _size) {                  //--- Check valid index
         removed = _tradeSignals[index];                  //--- Store signal to remove
         for (int i = index; i < (_size - 1); i++) {      //--- Shift signals left
            _tradeSignals[i] = _tradeSignals[i + 1];      //--- Move signal
         }
         ArrayResize(_tradeSignals, ArraySize(_tradeSignals) - 1, 8); //--- Reduce array size
         _size = _size - 1;                               //--- Decrement size
      }
      return removed;                                     //--- Return removed signal or null
   }

   //--- Retrieve signal at index
   IAdvisorStrategyExpression* Get(int index) {
      if (index >= 0 && index < _size) {                  //--- Check valid index
         return _tradeSignals[index];                     //--- Return signal
      }
      return NULL;                                        //--- Return null for invalid index
   }

   //--- Retrieve number of signals
   int Count() {
      return _size;                                       //--- Return current size
   }

   //--- Reset iterator to start
   void Rewind() {
      _pointer = -1;                                      //--- Set pointer to -1
   }

   //--- Move to next signal
   IAdvisorStrategyExpression* Next() {
      _pointer++;                                         //--- Increment pointer
      if (_pointer == _size) {                            //--- Check if at end
         Rewind();                                        //--- Reset pointer
         return NULL;                                     //--- Return null
      }
      return Current();                                   //--- Return current signal
   }

   //--- Move to previous signal
   IAdvisorStrategyExpression* Prev() {
      _pointer--;                                         //--- Decrement pointer
      if (_pointer == -1) {                               //--- Check if before start
         return NULL;                                     //--- Return null
      }
      return Current();                                   //--- Return current signal
   }

   //--- Check if more signals exist
   bool HasNext() {
      return (_pointer < (_size - 1));                    //--- Return true if pointer is before end
   }

   //--- Retrieve current signal
   IAdvisorStrategyExpression* Current() {
      return _tradeSignals[_pointer];                     //--- Return signal at pointer
   }

   //--- Retrieve current iterator index
   int Key() {
      return _pointer;                                    //--- Return current pointer
   }
};

//--- Define class for managing trading signals
class AdvisorStrategy {
private:
   TradeSignalCollection* _openBuySignals;                //--- Store open Buy signals
   TradeSignalCollection* _openSellSignals;               //--- Store open Sell signals
   TradeSignalCollection* _closeBuySignals;               //--- Store close Buy signals
   TradeSignalCollection* _closeSellSignals;              //--- Store close Sell signals

   //--- Evaluate signal at specified level
   bool EvaluateASLevel(TradeSignalCollection* signals, int level) {
      if (level > 0 && level <= signals.Count()) {        //--- Check valid level
         return signals.Get(level - 1).Evaluate();        //--- Evaluate signal
      }
      return false;                                       //--- Return false for invalid level
   }

public:
   //--- Initialize strategy
   void AdvisorStrategy() {
      _openBuySignals = new TradeSignalCollection();      //--- Create open Buy signals collection
      _openSellSignals = new TradeSignalCollection();     //--- Create open Sell signals collection
      _closeBuySignals = new TradeSignalCollection();     //--- Create close Buy signals collection
      _closeSellSignals = new TradeSignalCollection();    //--- Create close Sell signals collection
   }

   //--- Destructor to clean up signals
   void ~AdvisorStrategy() {
      delete(_openBuySignals);                            //--- Delete open Buy signals
      delete(_openSellSignals);                           //--- Delete open Sell signals
      delete(_closeBuySignals);                           //--- Delete close Buy signals
      delete(_closeSellSignals);                          //--- Delete close Sell signals
   }

   //--- Retrieve trading advice
   bool GetAdvice(TradeAction tradeAction, int level) {
      if (tradeAction == OpenBuyAction) {                  //--- Check open Buy action
         return EvaluateASLevel(_openBuySignals, level);   //--- Evaluate Buy signal
      } else if (tradeAction == OpenSellAction) {          //--- Check open Sell action
         return EvaluateASLevel(_openSellSignals, level);  //--- Evaluate Sell signal
      } else if (tradeAction == CloseBuyAction) {          //--- Check close Buy action
         return EvaluateASLevel(_closeBuySignals, level);  //--- Evaluate close Buy signal
      } else if (tradeAction == CloseSellAction) {         //--- Check close Sell action
         return EvaluateASLevel(_closeSellSignals, level); //--- Evaluate close Sell signal
      } else {
         Alert("Unsupported TradeAction in Advisor Strategy. TradeAction: " + DoubleToStr(tradeAction)); //--- Log unsupported action
      }
      return false;                                       //--- Return false for invalid action
   }

   //--- Register open Buy signal
   void RegisterOpenBuy(IAdvisorStrategyExpression* openBuySignal, int level) {
      if (level <= _openBuySignals.Count()) {             //--- Check if level already set
         Alert("Register Open Buy failed: level already set."); //--- Log failure
         return;                                          //--- Exit
      }
      _openBuySignals.Add(openBuySignal);                 //--- Add signal
   }

   //--- Register open Sell signal
   void RegisterOpenSell(IAdvisorStrategyExpression* openSellSignal, int level) {
      if (level <= _openSellSignals.Count()) {            //--- Check if level already set
         Alert("Register Open Sell failed: level already set."); //--- Log failure
         return;                                          //--- Exit
      }
      _openSellSignals.Add(openSellSignal);               //--- Add signal
   }

   //--- Register close Buy signal
   void RegisterCloseBuy(IAdvisorStrategyExpression* closeBuySignal, int level) {
      if (level <= _closeBuySignals.Count()) {            //--- Check if level already set
         Alert("Register Close Buy failed: level already set."); //--- Log failure
         return;                                          //--- Exit
      }
      _closeBuySignals.Add(closeBuySignal);               //--- Add signal
   }

   //--- Register close Sell signal
   void RegisterCloseSell(IAdvisorStrategyExpression* closeSellSignal, int level) {
      if (level <= _closeSellSignals.Count()) {           //--- Check if level already set
         Alert("Register Close Sell failed: level already set."); //--- Log failure
         return;                                          //--- Exit
      }
      _closeSellSignals.Add(closeSellSignal);             //--- Add signal
   }

   //--- Retrieve number of signals for action
   int GetNumberOfExpressions(TradeAction tradeAction) {
      if (tradeAction == OpenBuyAction) {                 //--- Check open Buy action
         return _openBuySignals.Count();                  //--- Return Buy signal count
      } else if (tradeAction == OpenSellAction) {         //--- Check open Sell action
         return _openSellSignals.Count();                 //--- Return Sell signal count
      } else if (tradeAction == CloseBuyAction) {         //--- Check close Buy action
         return _closeBuySignals.Count();                 //--- Return close Buy signal count
      } else if (tradeAction == CloseSellAction) {        //--- Check close Sell action
         return _closeSellSignals.Count();                //--- Return close Sell signal count
      }
      return 0;                                           //--- Return 0 for invalid action
   }

   //--- Set fire-only-when-reset for all signals
   void SetFireOnlyWhenReset(bool value) {
      _openBuySignals.Rewind();                           //--- Reset Buy signals iterator
      while (_openBuySignals.Next() != NULL) {            //--- Iterate Buy signals
         _openBuySignals.Current().SetFireOnlyWhenReset(value); //--- Set flag
      }
      _openSellSignals.Rewind();                          //--- Reset Sell signals iterator
      while (_openSellSignals.Next() != NULL) {           //--- Iterate Sell signals
         _openSellSignals.Current().SetFireOnlyWhenReset(value); //--- Set flag
      }
      _closeBuySignals.Rewind();                          //--- Reset close Buy signals iterator
      while (_closeBuySignals.Next() != NULL) {           //--- Iterate close Buy signals
         _closeBuySignals.Current().SetFireOnlyWhenReset(value); //--- Set flag
      }
      _closeSellSignals.Rewind();                         //--- Reset close Sell signals iterator
      while (_closeSellSignals.Next() != NULL) {          //--- Iterate close Sell signals
         _closeSellSignals.Current().SetFireOnlyWhenReset(value); //--- Set flag
      }
   }

   //--- Handle tick event for signals
   void HandleTick() {
      _openBuySignals.Rewind();                           //--- Reset Buy signals iterator
      while (_openBuySignals.Next() != NULL) {            //--- Iterate Buy signals
         _openBuySignals.Current().ResetSignalValue();    //--- Reset signal value
         if (_openBuySignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset
            _openBuySignals.Current().Evaluate();         //--- Evaluate signal
         }
      }
      _openSellSignals.Rewind();                          //--- Reset Sell signals iterator
      while (_openSellSignals.Next() != NULL) {           //--- Iterate Sell signals
         _openSellSignals.Current().ResetSignalValue();   //--- Reset signal value
         if (_openSellSignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset
            _openSellSignals.Current().Evaluate();        //--- Evaluate signal
         }
      }
      _closeBuySignals.Rewind();                          //--- Reset close Buy signals iterator
      while (_closeBuySignals.Next() != NULL) {           //--- Iterate close Buy signals
         _closeBuySignals.Current().ResetSignalValue();   //--- Reset signal value
         if (_closeBuySignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset
            _closeBuySignals.Current().Evaluate();        //--- Evaluate signal
         }
      }
      _closeSellSignals.Rewind();                         //--- Reset close Sell signals iterator
      while (_closeSellSignals.Next() != NULL) {          //--- Iterate close Sell signals
         _closeSellSignals.Current().ResetSignalValue();  //--- Reset signal value
         if (_closeSellSignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset
            _closeSellSignals.Current().Evaluate();       //--- Evaluate signal
         }
      }
   }
};

Здесь мы реализуем дополнительные классы для управления торговыми сигналами. Поскольку мы уже объясняли принцип работы классов в предыдущих частях, надеемся, вы уже знакомы с синтаксисом классов, поэтому мы покажем только самые важные моменты. Создадим класс TradeSignalCollection, используя _tradeSignals для хранения указателей IAdvisorStrategyExpression, _pointer для итерации и _size для подсчета сигналов, инициализируемый конструктором TradeSignalCollection. Мы добавляем сигналы с помощью функции Add, удаляем их с помощью Remove, а навигацию осуществляем с помощью функций Next, Prev, Current, Rewind и HasNext, при этом очистка данных выполняется автоматически деструктором.

В классе AdvisorStrategy, определяем _openBuySignals, _openSellSignals, _closeBuySignals и _closeSellSignals как объекты TradeSignalCollection, которые создаются в конструкторе AdvisorStrategy. Мы оцениваем сигналы с помощью функций GetAdvice и EvaluateASLevel, регистрируем сигналы через RegisterOpenBuy, RegisterOpenSell, RegisterCloseBuy и RegisterCloseSell, а также управляем количеством сигналов с помощью функции GetNumberOfExpressions. Функция SetFireOnlyWhenReset применяет флаги сброса, а функция HandleTick сбрасывает и оценивает сигналы с помощью функций ResetSignalValue и Evaluate, обеспечивая эффективную обработку сигналов для нашей стратегии. Теперь мы можем определять классы для сигналов на продажу и покупку на заданных уровнях. Начнем с класса на продажу.

//--- Define class for open Sell signal at level 1
class ASOpenSellLevel1 : public ASSignal {
protected:
   //--- Evaluate Sell signal
   bool EvaluateSignal() {
      Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder(); //--- Retrieve recent open order
      if (openOrder != NULL && openOrder.Type == ORDER_TYPE_BUY) { //--- Check if Buy order
         openOrder = NULL;                                //--- Clear if Buy to avoid conflict
      }
      if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) //--- Check no recent order
            && ((BidFunc.GetValue(0) < fn_iMA_SMA_4(Symbol(), 0)) //--- Check Bid below 4-period SMA
                && ((BidFunc.GetValue(0) > fn_iMA_SMA8(Symbol(), 0)) //--- Check Bid above 8-period SMA
                    && ((BidFunc.GetValue(0) < fn_iEnvelopes_ENV_UPPER(Symbol(), 0, 0)) //--- Check Bid below upper Envelope
                        && ((fn_iRSI_RSI(Symbol(), 1) < OpenSell_Const_0) //--- Check previous RSI below threshold
                            && (fn_iRSI_RSI(Symbol(), 0) >= OpenSell_Const_0) //--- Check current RSI above threshold
                           )
                       )
                   )
               )
           )
            || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) //--- Check existing order
                && (BidFunc.GetValue(0) > ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) + (PipPoint * OpenSell_Const_1))) //--- Check Bid above open price plus offset
               )
          )) {
         return true;                                     //--- Return true for Sell signal
      }
      return false;                                       //--- Return false if no signal
   }

public:
   //--- Initialize Sell signal
   void ASOpenSellLevel1() {}                             //--- Empty constructor
};

Мы разрабатываем логику обработки сигналов на продажу, используя класс ASOpenSellLevel1, производный от класса ASSignal. В защищенной функции EvaluateSignal мы проверяем последний ордер с помощью функции GetMostRecentOpenOrder из Wallet класса _ea, очищая его, если это ордер на покупку. Мы активируем сигнал на продажу, если нет недавних ордеров, а цена покупки, полученная из функции GetValue модуля BidFunc, находится ниже 4-периодной скользящей средней (fn_iMA_SMA_4), выше 8-периодной скользящей средней (fn_iMA_SMA8), ниже верхней полосы конвертов (fn_iEnvelopes_ENV_UPPER), при этом предыдущий RSI (fn_iRSI_RSI) ниже OpenSell_Const_0, а текущий RSI равен или выше него, или если существует ордер, и цена bid превышает цену открытия плюс OpenSell_Const_1, умноженный на PinPoint.

Для корректного сигнала мы возвращаем true, в противном случае — false. Конструктор функции ASOpenSellLevel1 пуст, используется инициализация с помощью функции ASSignal. Мы используем ту же логику и для сигналов на покупку.

//--- Define class for open Buy signal at level 1
class ASOpenBuyLevel1 : public ASSignal {
protected:
   //--- Evaluate Buy signal
   bool EvaluateSignal() {
      Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder();  //--- Retrieve most recent open order
      if (openOrder != NULL && openOrder.Type == ORDER_TYPE_SELL) { //--- Check if Sell order
         openOrder = NULL;                                          //--- Clear if Sell to avoid conflict
      }
      if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) //--- Check no recent order
            && ((AskFunc.GetValue(0) > fn_iMA_SMA_4(Symbol(), 0))                                   //--- Check Ask above 4-period SMA
                && ((AskFunc.GetValue(0) < fn_iMA_SMA8(Symbol(), 0))                                //--- Check Ask below 8-period SMA
                    && ((AskFunc.GetValue(0) > fn_iEnvelopes_ENV_LOW(Symbol(), 1, 0))               //--- Check Ask above lower Envelope
                        && ((fn_iRSI_RSI(Symbol(), 1) > OpenBuy_Const_0)                            //--- Check previous RSI above threshold
                            && (fn_iRSI_RSI(Symbol(), 0) <= OpenBuy_Const_0)                        //--- Check current RSI below threshold
                           )
                       )
                   )
               )
           )
            || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) //--- Check existing order
                && (AskFunc.GetValue(0) < ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) - (PipPoint * OpenBuy_Const_1))) //--- Check Ask below open price minus offset
               )
          )) {
         return true;                                     //--- Return true for Buy signal
      }
      return false;                                       //--- Return false if no signal
   }

public:
   //--- Initialize Buy signal
   void ASOpenBuyLevel1() {}                              //--- Empty constructor
};

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

//--- Define input group for trade and risk module settings
input string trademodule = "------TRADE/RISK MODULE------";//--- Label trade/risk module inputs
//--- Define input group for open Buy constants
input double LotSizePercentage = 1;                        //--- Set lot size as percentage of account balance (default: 1%)
input double OpenBuy_Const_0 = 11;                         //--- Set RSI threshold for Buy signal (default: 11)
input double OpenBuy_Const_1 = 10;                         //--- Set pip offset for additional Buy orders (default: 10 pips)
input double OpenSell_Const_0 = 89;                        //--- Set RSI threshold for Sell signal (default: 89)
input double OpenSell_Const_1 = 10;                        //--- Set pip offset for additional Sell orders (default: 10 pips)

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

//--- Define interface for money management
interface IMoneyManager {
   double GetLotSize();                                   //--- Retrieve lot size
   int GetNextLevel(Wallet* wallet);                      //--- Retrieve next trading level
};

//--- Define class for money management
class MoneyManager : public IMoneyManager {
public:
   //--- Initialize money manager
   void MoneyManager(Wallet* wallet) {
      _minLot = MarketInfo_LibFunc(Symbol(), MODE_MINLOT);   //--- Set minimum lot size
      _maxLot = MarketInfo_LibFunc(Symbol(), MODE_MAXLOT);   //--- Set maximum lot size
      _lotStep = MarketInfo_LibFunc(Symbol(), MODE_LOTSTEP); //--- Set lot step
   }

   //--- Retrieve calculated lot size
   double GetLotSize() {
      double lotSize = NormalizeLots(NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * 0.0001 * LotSizePercentage / 100.0, 2)); //--- Calculate lot size
      return lotSize;                                     //--- Return normalized lot size
   }

   //--- Retrieve next trading level
   int GetNextLevel(Wallet* wallet) {
      return wallet.GetOpenOrders().Count() + 1;          //--- Return next level based on open orders
   }

private:
   double _minLot;                                        //--- Store minimum lot size
   double _maxLot;                                        //--- Store maximum lot size
   double _lotStep;                                       //--- Store lot step

   //--- Normalize lot size to broker specifications
   double NormalizeLots(double lots) {
      lots = MathRound(lots / _lotStep) * _lotStep;       //--- Round to lot step
      if (lots < _minLot) lots = _minLot;                 //--- Enforce minimum lot
      else if (lots > _maxLot) lots = _maxLot;            //--- Enforce maximum lot
      return lots;                                        //--- Return normalized lot size
   }
};

Для настройки управления капиталом в рамках стратегии мы определяем интерфейс IMoneyManager с функциями GetLotSize и GetNextLevel для стандартизации размеров сделок и повышения уровня. В классе MoneyManager, реализующем интерфейс IMoneyManager, мы инициализируем переменные _minLot, _maxLot и _lotStep в конструкторе класса MoneyManager с помощью функции MarketInfo_LibFunc. Наша функция GetLotSize рассчитывает размер лота на основе AccountInfoDouble, скорректированном с помощью LotSizePercentage и нормализованном с помощью NormalizeLots в соответствии с правилами брокера.

Функция GetNextLevel возвращает количество открытых ордеров из функции GetOpenOrders плюс единица. Функция NormalizeLots обеспечивает корректность размеров партий, округляя значения до _lotStep и учитывая значения _minLot и _maxLot. Это поможет создать четкую систему управления объемом и уровнем торговли. Теперь мы можем перейти к управлению сделками, и для этого нам потребуется еще один интерфейс.

//--- Define enumeration for trading module demands
enum TradingModuleDemand {
   NoneDemand = 0,                                        //--- Represent no demand
   NoBuyDemand = 1,                                       //--- Prevent Buy orders
   NoSellDemand = 2,                                      //--- Prevent Sell orders
   NoOpenDemand = 4,                                      //--- Prevent all open orders
   OpenBuySellDemand = 8,                                 //--- Demand both Buy and Sell opens
   OpenBuyDemand = 16,                                    //--- Demand Buy open
   OpenSellDemand = 32,                                   //--- Demand Sell open
   CloseBuyDemand = 64,                                   //--- Demand Buy close
   CloseSellDemand = 128,                                 //--- Demand Sell close
   CloseBuySellDemand = 256                               //--- Demand both Buy and Sell closes
};

//--- Define interface for trading module signals
interface ITradingModuleSignal {
   string GetName();                                      //--- Retrieve signal name
   bool Evaluate(Order* openOrder = NULL);                //--- Evaluate signal
};

//--- Define interface for trading module values
interface ITradingModuleValue {
   string GetName();                                      //--- Retrieve value name
   double Evaluate(Order* openOrder = NULL);              //--- Evaluate value
};

//--- Define interface for trade strategy modules
interface ITradeStrategyModule {
   TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand demand, int level = 1); //--- Evaluate module
   void RegisterTradeSignal(ITradingModuleSignal* tradeSignal); //--- Register signal
};

//--- Define interface for open trade strategy modules
interface ITradeStrategyOpenModule : public ITradeStrategyModule {
   TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand demand, int requestedEvaluationLevel = 0); //--- Evaluate open signals
   TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand demand); //--- Evaluate close signals
};

//--- Define interface for close trade strategy modules
interface ITradeStrategyCloseModule : public ITradeStrategyModule {
   ORDER_GROUP_TYPE GetOrderGroupingType();                  //--- Retrieve grouping type
   void RegisterTradeValue(ITradingModuleValue* tradeValue); //--- Register value
};

Здесь мы закладываем основу для управления торговыми решениями, определяя структурированный набор перечислений и интерфейсов. Мы начинаем с создания перечисления TradingModuleDemand, которое используем для классификации торговых действий и ограничений, таких как NoneDemand для отсутствия действий, NoBuyDemand и NoSellDemand для блокировки определенных типов ордеров, OpenBuyDemand и OpenSellDemand для инициирования сделок, а также CloseBuyDemand и CloseSellDemand для закрытия позиций и другие. Это перечисление позволит четко обозначить цель стратегии при совершении торговых операций.

Затем мы определяем интерфейс ITradingModuleSignal, который мы разработали, чтобы стандартизировать операции с торговыми сигналами, включая функцию GetName для получения идентификатора сигнала и функцию Evaluate для оценки активности сигнала, с возможностью учета открытого ордера. Аналогичным образом вводим интерфейс ITradingModuleValue с функциями GetName и Evaluate для управления числовыми значениями, связанными с торговыми решениями, такими как целевые показатели прибыли. Для модулей стратегии создадим интерфейс ITradeStrategyModule, который включает функцию Evaluate для обработки торговых запросов на основе объекта Wallet и функцию RegisterTradeSignal для включения сигналов.

Расширим функционал с помощью интерфейса ITradeStrategyOpenModule, добавляя функции EvaluateOpenSignals и EvaluateCloseSignals для обработки сигналов открытия и закрытия сделок, а также интерфейс ITradeStrategyCloseModule, который включает функцию GetOrderGroupingType для группировки ордеров и функцию RegisterTradeValue для регистрации значений. Вместе эти компоненты сформируют гибкую основу для координации торговых действий в рамках нашей стратегии. Теперь мы можем определять классы управления на основе этих модулей.

//--- Define class for managing trade strategy
class TradeStrategy {
public:
   ITradeStrategyCloseModule* CloseModules[];             //--- Store close modules

private:
   ITradeStrategyModule* _preventOpenModules[];           //--- Store prevent-open modules
   ITradeStrategyOpenModule* _openModule;                 //--- Store open module

   //--- Evaluate prevent-open modules
   TradingModuleDemand EvaluatePreventOpenModules(Wallet* wallet, TradingModuleDemand preventOpenDemand, int evaluationLevel = 1) {
      TradingModuleDemand preventOpenDemands[];                           //--- Declare prevent-open demands array
      ArrayResize(preventOpenDemands, ArraySize(_preventOpenModules), 8); //--- Resize array
      for (int i = 0; i < ArraySize(_preventOpenModules); i++) {          //--- Iterate modules
         preventOpenDemands[i] = _preventOpenModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); //--- Evaluate module
      }
      return PreventOpenModuleBase::GetCombinedPreventOpenDemand(preventOpenDemands); //--- Return combined demand
   }

   //--- Evaluate close modules
   TradingModuleDemand EvaluateCloseModules(Wallet* wallet, TradingModuleDemand closeDemand, int evaluationLevel = 1) {
      TradingModuleDemand closeDemands[];                    //--- Declare close demands array
      ArrayResize(closeDemands, ArraySize(CloseModules), 8); //--- Resize array
      for (int i = 0; i < ArraySize(CloseModules); i++) {    //--- Iterate modules
         closeDemands[i] = CloseModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); //--- Evaluate module
      }
      return CloseModuleBase::GetCombinedCloseDemand(closeDemands); //--- Return combined demand
   }

   //--- Evaluate close conditions for TP/SL
   void EvaluateCloseConditions(Wallet* wallet, TradingModuleDemand signalDemand) {
      OrderCollection* openOrders = wallet.GetOpenOrders(); //--- Retrieve open orders
      if (openOrders.Count() == 0) {                        //--- Check if no open orders
         return;                                            //--- Exit
      }
      double bid = Bid_LibFunc();                           //--- Retrieve Bid price
      double ask = Ask_LibFunc();                           //--- Retrieve Ask price
      for (int i = openOrders.Count() - 1; i >= 0; i--) {   //--- Iterate open orders
         Order* order = openOrders.Get(i);                  //--- Get order
         bool closeSignal = (order.Type == OP_BUY && signalDemand == CloseBuyDemand) ||   //--- Check Buy close signal
                            (order.Type == OP_SELL && signalDemand == CloseSellDemand) || //--- Check Sell close signal
                            signalDemand == CloseBuySellDemand;                           //--- Check Buy/Sell close signal
         bool closeManualSLTP = AllowManualTPSLChanges && ((order.StopLossManual != 0 && order.Type == OP_BUY && bid <= order.StopLossManual) ||    //--- Check manual Buy SL
                                                          (order.StopLossManual != 0 && order.Type == OP_SELL && ask >= order.StopLossManual) ||    //--- Check manual Sell SL
                                                          (order.TakeProfitManual != 0 && order.Type == OP_BUY && bid >= order.TakeProfitManual) || //--- Check manual Buy TP
                                                          (order.TakeProfitManual != 0 && order.Type == OP_SELL && ask <= order.TakeProfitManual)); //--- Check manual Sell TP
         bool fullOrderClose = closeSignal || closeManualSLTP; //--- Determine full close
         OrderCloseInfo* activePartialCloseCloseInfo = NULL;   //--- Initialize partial close info
         if (!fullOrderClose) {                                //--- Check if not full close
            if (!AllowManualTPSLChanges || order.StopLossManual == 0) { //--- Check manual SL
               for (int cli = 0; cli < ArraySize(order.CloseInfosSL); cli++) { //--- Iterate SL info
                  if (order.CloseInfosSL[cli].IsOld) continue; //--- Skip old info
                  if (order.CloseInfosSL[cli].IsClosePriceSLHit(order.Type, ask, bid)) { //--- Check SL hit
                     if (activePartialCloseCloseInfo == NULL || order.CloseInfosSL[cli].Percentage > activePartialCloseCloseInfo.Percentage) { //--- Check higher percentage
                        activePartialCloseCloseInfo = order.CloseInfosSL[cli]; //--- Set active info
                     }
                  }
               }
            }
            if (!AllowManualTPSLChanges || order.TakeProfitManual == 0) {      //--- Check manual TP
               for (int cli = 0; cli < ArraySize(order.CloseInfosTP); cli++) { //--- Iterate TP info
                  if (order.CloseInfosTP[cli].IsOld) continue;                 //--- Skip old info
                  if (order.CloseInfosTP[cli].IsClosePriceTPHit(order.Type, ask, bid)) { //--- Check TP hit
                     if (activePartialCloseCloseInfo == NULL || order.CloseInfosTP[cli].Percentage > activePartialCloseCloseInfo.Percentage) { //--- Check higher percentage
                        activePartialCloseCloseInfo = order.CloseInfosTP[cli]; //--- Set active info
                     }
                  }
               }
            }
            fullOrderClose = activePartialCloseCloseInfo != NULL && activePartialCloseCloseInfo.Percentage == 100; //--- Check if full close
         }
         if (fullOrderClose) {                            //--- Handle full close
            TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, 0);      //--- Evaluate prevent-open
            TradingModuleDemand openDemand = _openModule.EvaluateOpenSignals(wallet, finalPreventOpenAdvice, 1); //--- Evaluate open signals
            int orderTypeOfOpeningOrder = wallet.GetOpenOrders().Get(0).Type;                                    //--- Get first order type
            if ((orderTypeOfOpeningOrder == ORDER_TYPE_BUY && openDemand == OpenBuyDemand) ||                    //--- Check Buy re-entry
                (orderTypeOfOpeningOrder == ORDER_TYPE_SELL && openDemand == OpenSellDemand) ||                  //--- Check Sell re-entry
                (openDemand == OpenBuySellDemand)) {      //--- Check Buy/Sell re-entry
               return;                                    //--- Block close to prevent re-entry
            }
            wallet.SetOpenOrderToPendingClose(order);      //--- Move order to pending close
         } else if (activePartialCloseCloseInfo != NULL) { //--- Handle partial close
            Order* partialCloseOrder = order.SplitOrder(activePartialCloseCloseInfo.Percentage); //--- Split order
            if (partialCloseOrder.Lots < 1e-13) {          //--- Check if last piece
               delete(partialCloseOrder);                  //--- Delete split order
               wallet.SetOpenOrderToPendingClose(order);   //--- Move to pending close
            } else {
               partialCloseOrder.ParentOrder = order;      //--- Link to parent order
               if (wallet.AddPendingCloseOrder(partialCloseOrder)) { //--- Add to pending close
                  activePartialCloseCloseInfo.IsOld = true; //--- Mark info as old
               }
            }
         }
      }
   }

public:
   //--- Initialize trade strategy
   void TradeStrategy(ITradeStrategyOpenModule* openModule) {
      _openModule = openModule;                           //--- Set open module
   }

   //--- Destructor to clean up strategy
   void ~TradeStrategy() {
      for (int i = ArraySize(_preventOpenModules) - 1; i >= 0; i--) { //--- Iterate prevent-open modules
         delete(_preventOpenModules[i]);                  //--- Delete module
      }
      delete(_openModule);                                //--- Delete open module
      for (int i = ArraySize(CloseModules) - 1; i >= 0; i--) { //--- Iterate close modules
         delete(CloseModules[i]);                         //--- Delete module
      }
   }

   //--- Evaluate trading strategy
   void Evaluate(Wallet* wallet) {
      int orderCount = wallet.GetOpenOrders().Count();    //--- Retrieve open order count
      TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, orderCount + 1); //--- Evaluate prevent-open
      if (orderCount > 0) {                               //--- Check if orders exist
         EvaluateCloseModules(wallet, NoneDemand);        //--- Evaluate close modules
         TradingModuleDemand signalDemand = _openModule.EvaluateCloseSignals(wallet, finalPreventOpenAdvice); //--- Evaluate close signals
         EvaluateCloseConditions(wallet, signalDemand);   //--- Evaluate close conditions
      }
      _openModule.Evaluate(wallet, finalPreventOpenAdvice, 0); //--- Evaluate open module
   }

   //--- Register prevent-open module
   void RegisterPreventOpenModule(ITradeStrategyModule* preventOpenModule) {
      int size = ArraySize(_preventOpenModules);          //--- Get current array size
      ArrayResize(_preventOpenModules, size + 1, 8);      //--- Resize array
      _preventOpenModules[size] = preventOpenModule;      //--- Add module
   }

   //--- Register close module
   void RegisterCloseModule(ITradeStrategyCloseModule* closeModule) {
      int size = ArraySize(CloseModules);                 //--- Get current array size
      ArrayResize(CloseModules, size + 1, 8);             //--- Resize array
      CloseModules[size] = closeModule;                   //--- Add module
   }
};

//--- Define base class for module calculations
class ModuleCalculationsBase {
public:
   //--- Calculate profit for order collection
   static double CalculateOrderCollectionProfit(OrderCollection &orders, ORDER_PROFIT_CALCULATION_TYPE calculationType) {
      double collectionProfit = 0;                        //--- Initialize profit
      for (int i = 0; i < orders.Count(); i++) {          //--- Iterate orders
         Order* order = orders.Get(i);                    //--- Get order
         collectionProfit += CalculateOrderProfit(order, calculationType); //--- Add order profit
      }
      return collectionProfit;                            //--- Return total profit
   }

   //--- Calculate profit for single order
   static double CalculateOrderProfit(Order* order, ORDER_PROFIT_CALCULATION_TYPE calculationType) {
      if (calculationType == Pips) {                      //--- Check pips calculation
         return order.CalculateProfitPips();              //--- Return profit in pips
      } else if (calculationType == Money) {              //--- Check money calculation
         return order.CalculateProfitCurrency();          //--- Return profit in currency
      } else if (calculationType == EquityPercentage) {   //--- Check equity percentage
         return order.CalculateProfitEquityPercentage();  //--- Return profit as percentage
      } else {
         Alert("Can't execute CalculateOrderCollectionProfit. Unknown calculationType: " + IntegerToString(calculationType)); //--- Log error
         return 0;                                        //--- Return 0 for invalid type
      }
   }
};

//--- Define base class for open trade modules
class OpenModuleBase : public ITradeStrategyOpenModule {
protected:
   AdvisorStrategy* _advisorStrategy;                     //--- Store advisor strategy
   IMoneyManager* _moneyManager;                          //--- Store money manager

   //--- Create new order
   Order* OpenOrder(ENUM_ORDER_TYPE orderType, bool mustBeVisibleOnChart) {
      Order* order = new Order(mustBeVisibleOnChart);     //--- Create new order
      order.SymbolCode = Symbol();                        //--- Set symbol
      order.Type = orderType;                             //--- Set order type
      order.MagicNumber = MagicNumber;                    //--- Set magic number
      order.Lots = _moneyManager.GetLotSize();            //--- Set lot size
      if (order.Type == ORDER_TYPE_BUY) {                 //--- Check Buy order
         order.OpenPrice = Ask_LibFunc();                 //--- Set open price to Ask
         order.StopLoss = -DBL_MAX;                       //--- Set initial SL to minimum
         order.TakeProfit = DBL_MAX;                      //--- Set initial TP to maximum
      } else if (order.Type == ORDER_TYPE_SELL) {         //--- Check Sell order
         order.OpenPrice = Bid_LibFunc();                 //--- Set open price to Bid
         order.StopLoss = DBL_MAX;                        //--- Set initial SL to maximum
         order.TakeProfit = -DBL_MAX;                     //--- Set initial TP to minimum
      }
      order.LowestProfitPips = DBL_MAX;                   //--- Set initial lowest profit
      order.HighestProfitPips = -DBL_MAX;                 //--- Set initial highest profit
      order.Comment = OrderComment;                       //--- Set order comment
      OrderRepository::CalculateAndSetCommision(order);   //--- Calculate and set commission
      return order;                                       //--- Return order
   }

public:
   //--- Initialize open module
   void OpenModuleBase(AdvisorStrategy* advisorStrategy, IMoneyManager* moneyManager) {
      _advisorStrategy = advisorStrategy;                 //--- Set advisor strategy
      _moneyManager = moneyManager;                       //--- Set money manager
   }

   //--- Retrieve trade actions
   void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) {
      TradeAction tempresult[];                             //--- Declare temporary actions array
      if (wallet.GetOpenOrders().Count() > 0) {             //--- Check if open orders exist
         Order* firstOrder = wallet.GetOpenOrders().Get(0); //--- Get first open order
         if (firstOrder.Type == ORDER_TYPE_BUY) {           //--- Check if Buy order
            ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array
            tempresult[0] = OpenBuyAction;                  //--- Add open Buy action
            ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array
            tempresult[1] = CloseBuyAction;                 //--- Add close Buy action
         } else if (firstOrder.Type == ORDER_TYPE_SELL) {   //--- Check if Sell order
            ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array
            tempresult[0] = OpenSellAction;                 //--- Add open Sell action
            ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array
            tempresult[1] = CloseSellAction;                //--- Add close Sell action
         } else {
            Alert("Unsupported ordertype. Ordertype: " + DoubleToStr(firstOrder.Type)); //--- Log error
         }
      } else {
         ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array
         tempresult[0] = OpenBuyAction;                   //--- Add open Buy action
         ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array
         tempresult[1] = OpenSellAction;                  //--- Add open Sell action
      }
      for (int i = 0; i < ArraySize(tempresult); i++) {   //--- Iterate actions
         if ((preventOpenDemand == NoOpenDemand && (tempresult[i] == OpenBuyAction || tempresult[i] == OpenSellAction)) || //--- Check no open demand
             (preventOpenDemand == NoBuyDemand && tempresult[i] == OpenBuyAction) || //--- Check no Buy demand
             (preventOpenDemand == NoSellDemand && tempresult[i] == OpenSellAction)) { //--- Check no Sell demand
            continue;                                     //--- Skip action
         }
         ArrayResize(result, ArraySize(result) + 1, 8);   //--- Resize result array
         result[ArraySize(result) - 1] = tempresult[i];   //--- Add action
      }
   }

   //--- Register trade signal (empty implementation)
   virtual void RegisterTradeSignal(ITradingModuleSignal* tradeSignal) {} //--- Do nothing

   //--- Combine open demands
   static TradingModuleDemand GetCombinedOpenDemand(TradingModuleDemand &openDemands[]) {
      TradingModuleDemand result = NoneDemand;            //--- Initialize result
      for (int i = 0; i < ArraySize(openDemands); i++) {  //--- Iterate demands
         if (result == OpenBuySellDemand) {               //--- Check if Buy/Sell demand
            return OpenBuySellDemand;                     //--- Return Buy/Sell demand
         }
         if (openDemands[i] == OpenBuySellDemand) {       //--- Check if demand is Buy/Sell
            result = OpenBuySellDemand;                   //--- Set Buy/Sell demand
         } else if (result == NoneDemand && openDemands[i] == OpenBuyDemand) { //--- Check Buy demand
            result = OpenBuyDemand;                       //--- Set Buy demand
         } else if (result == NoneDemand && openDemands[i] == OpenSellDemand) { //--- Check Sell demand
            result = OpenSellDemand;                      //--- Set Sell demand
         } else if (result == OpenBuyDemand && openDemands[i] == OpenSellDemand) { //--- Check mixed demands
            result = OpenBuySellDemand;                   //--- Set Buy/Sell demand
         } else if (result == OpenSellDemand && openDemands[i] == OpenBuyDemand) { //--- Check mixed demands
            result = OpenBuySellDemand;                   //--- Set Buy/Sell demand
         }
      }
      return result;                                      //--- Return combined demand
   }

   //--- Combine close demands
   static TradingModuleDemand GetCombinedCloseDemand(TradingModuleDemand &closeDemands[]) {
      TradingModuleDemand result = NoneDemand;            //--- Initialize result
      for (int i = 0; i < ArraySize(closeDemands); i++) { //--- Iterate demands
         if (result == CloseBuySellDemand) {              //--- Check if Buy/Sell demand
            return CloseBuySellDemand;                    //--- Return Buy/Sell demand
         }
         if (closeDemands[i] == CloseBuySellDemand) {     //--- Check if demand is Buy/Sell
            result = CloseBuySellDemand;                  //--- Set Buy/Sell demand
         } else if (result == NoneDemand && closeDemands[i] == CloseBuyDemand) { //--- Check Buy demand
            result = CloseBuyDemand;                      //--- Set Buy demand
         } else if (result == NoneDemand && closeDemands[i] == CloseSellDemand) { //--- Check Sell demand
            result = CloseSellDemand;                     //--- Set Sell demand
         } else if (result == CloseBuyDemand && closeDemands[i] == CloseSellDemand) { //--- Check mixed demands
            result = CloseBuySellDemand;                  //--- Set Buy/Sell demand
         } else if (result == CloseSellDemand && closeDemands[i] == CloseBuyDemand) { //--- Check mixed demands
            result = CloseBuySellDemand;                  //--- Set Buy/Sell demand
         }
      }
      return result;                                      //--- Return combined demand
   }

   //--- Retrieve number of open orders
   int GetNumberOfOpenOrders(Wallet* wallet) {
      return wallet.GetOpenOrders().Count();              //--- Return open order count
   }
};

Создадим структуру торговой стратегии, используя класс TradeStrategy, хранящий объекты ITradeStrategyCloseModule в CloseModules, объекты ITradeStrategyModule - в _preventOpenModules и ITradeStrategyOpenModule - в _openModule. Инициализируем _openModule в конструкторе TradeStrategy и очищаем модули в деструкторе. Наша функция Evaluate оценивает открытые ордера с помощью функции GetOpenOrders, оценивает _preventOpenModules с помощью функций EvaluatePreventOpenModules и GetCombinedPreventOpenDemand, а также обрабатывает CloseModules с помощью функций EvaluateCloseModules и GetCombinedCloseDemand.

Функция EvaluateCloseConditions проверяет типы ордеров на соответствие CloseBuyDemand или CloseSellDemand, проверяет ручную установку стоп-лосса/тейк-профита с помощью функций Ask_LibFunc и Bid_LibFunc, а также обрабатывает закрытие ордеров с помощью функций SetOpenOrderToPendingClose или SplitOrder. Регистрируем модули с помощью RegisterPreventOpenModule и RegisterCloseModule. Класс ModuleCalculationsBase вычисляет прибыль с помощью CalculateOrderCollectionProfit и CalculateOrderProfit, а OpenModuleBase создает ордера с помощью OpenOrder и определяет действия с помощью GetTradeActions, объединяя запросы с помощью GetCombinedOpenDemand и GetCombinedCloseDemand для создания целостной системы.

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

//--- Define input for maximum open orders
input int MaxNumberOfOpenOrders1 = 1;                     //--- Set maximum number of open orders (default: 1)

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

//--- Define class for multiple open module
class MultipleOpenModule_1 : public OpenModuleBase {
protected:
   TradingModuleDemand previousSignalDemand;              //--- Store previous signal demand

public:
   //--- Initialize multiple open module
   void MultipleOpenModule_1(AdvisorStrategy* advisorStrategy, MoneyManager* moneyManager)
      : OpenModuleBase(advisorStrategy, moneyManager) {
      _advisorStrategy.SetFireOnlyWhenReset(true);         //--- Configure signals to fire only when reset
   }

   //--- Evaluate and act on signals
   TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand preventOpenDemand, int level) {
      TradingModuleDemand newSignalsDemand = EvaluateSignals(wallet, preventOpenDemand, level); //--- Evaluate signals
      if (newSignalsDemand != NoneDemand) {                //--- Check if demand exists
         EvaluateOpenConditions(wallet, newSignalsDemand); //--- Evaluate open conditions
      }
      return newSignalsDemand;                             //--- Return new signal demand
   }

   //--- Evaluate open signals without acting
   TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand, int requestedEvaluationLevel) {
      TradingModuleDemand openDemands[];                   //--- Declare open demands array
      TradeAction tradeActionsToEvaluate[];                //--- Declare actions to evaluate
      GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions
      AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); //--- Add previous demand actions
      int level;                                           //--- Declare level
      if (requestedEvaluationLevel == 0) {                 //--- Check if level unspecified
         level = _moneyManager.GetNextLevel(wallet);       //--- Set level based on orders
      } else {
         level = requestedEvaluationLevel;                 //--- Use specified level
      }
      for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions
         if (tradeActionsToEvaluate[i] == CloseBuyAction || tradeActionsToEvaluate[i] == CloseSellAction) { //--- Skip close actions
            continue;                                      //--- Move to next
         }
         if (requestedEvaluationLevel == 0) {              //--- Check if level unspecified
            level = GetTopLevel(tradeActionsToEvaluate[i], level); //--- Cap level
            if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { //--- Check order limit
               level += 1;                                 //--- Increment level
            }
         }
         if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], level)) { //--- Check if action advised
            if (tradeActionsToEvaluate[i] == OpenBuyAction) { //--- Check Buy action
               int size = ArraySize(openDemands);             //--- Get current size
               int newSize = size + 1;                        //--- Calculate new size
               ArrayResize(openDemands, newSize, 8);          //--- Resize array
               openDemands[newSize - 1] = OpenBuyDemand;      //--- Add Buy demand
            } else if (tradeActionsToEvaluate[i] == OpenSellAction) { //--- Check Sell action
               int size = ArraySize(openDemands);             //--- Get current size
               int newSize = size + 1;                        //--- Calculate new size
               ArrayResize(openDemands, newSize, 8);          //--- Resize array
               openDemands[newSize - 1] = OpenSellDemand;     //--- Add Sell demand
            }
         }
      }
      TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); //--- Combine open demands
      TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, level - 1); //--- Adjust for previous demand
      multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); //--- Filter prevent-open demands
      return multiOrderOpenSignal;                        //--- Return final open signal
   }

   //--- Retrieve trade actions (custom for multiple orders)
   void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) {
      if (wallet.GetOpenOrders().Count() > 0) {             //--- Check if open orders exist
         Order* firstOrder = wallet.GetOpenOrders().Get(0); //--- Get first open order
         if (firstOrder.Type == ORDER_TYPE_BUY) {           //--- Check if Buy order
            ArrayResize(result, ArraySize(result) + 1, 8);  //--- Resize array
            result[0] = OpenBuyAction;                      //--- Add open Buy action
            ArrayResize(result, ArraySize(result) + 1, 8);  //--- Resize array
            result[1] = CloseBuyAction;                     //--- Add close Buy action
         } else if (firstOrder.Type == ORDER_TYPE_SELL) {   //--- Check if Sell order
            ArrayResize(result, ArraySize(result) + 1, 8);  //--- Resize array
            result[0] = OpenSellAction;                     //--- Add open Sell action
            ArrayResize(result, ArraySize(result) + 1, 8);  //--- Resize array
            result[1] = CloseSellAction;                    //--- Add close Sell action
         } else {
            Alert("Unsupported ordertype");                 //--- Log error
         }
      } else {
         ArrayResize(result, ArraySize(result) + 1, 8);     //--- Resize array
         result[0] = OpenBuyAction;                         //--- Add open Buy action
         ArrayResize(result, ArraySize(result) + 1, 8);     //--- Resize array
         result[1] = OpenSellAction;                        //--- Add open Sell action
      }
   }

   //--- Evaluate close signals
   TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand) {
      TradingModuleDemand closeDemands[];                 //--- Declare close demands array
      TradeAction tradeActionsToEvaluate[];               //--- Declare actions to evaluate
      GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions
      for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions
         if (tradeActionsToEvaluate[i] != CloseBuyAction && tradeActionsToEvaluate[i] != CloseSellAction) { //--- Skip non-close actions
            continue;                                     //--- Move to next
         }
         if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], 1)) { //--- Check if action advised (level 1)
            if (tradeActionsToEvaluate[i] == CloseBuyAction) { //--- Check Buy close
               int size = ArraySize(closeDemands);          //--- Get current size
               int newSize = size + 1;                      //--- Calculate new size
               ArrayResize(closeDemands, newSize, 8);       //--- Resize array
               closeDemands[newSize - 1] = CloseBuyDemand;  //--- Add Buy close demand
            } else if (tradeActionsToEvaluate[i] == CloseSellAction) { //--- Check Sell close
               int size = ArraySize(closeDemands);          //--- Get current size
               int newSize = size + 1;                      //--- Calculate new size
               ArrayResize(closeDemands, newSize, 8);       //--- Resize array
               closeDemands[newSize - 1] = CloseSellDemand; //--- Add Sell close demand
            }
         }
      }
      TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); //--- Combine close demands
      return combinedCloseSignalDemand;                   //--- Return combined demand
   }

private:
   //--- Evaluate open and close signals
   TradingModuleDemand EvaluateSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand, int requestedEvaluationLevel) {
      TradingModuleDemand openDemands[];                  //--- Declare open demands array
      TradingModuleDemand closeDemands[];                 //--- Declare close demands array
      TradeAction tradeActionsToEvaluate[];               //--- Declare actions to evaluate
      GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions
      AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); //--- Add previous demand actions
      int moneyManagementLevel;                           //--- Declare level
      if (requestedEvaluationLevel == 0) {                //--- Check if level unspecified
         moneyManagementLevel = _moneyManager.GetNextLevel(wallet); //--- Set level based on orders
      } else {
         moneyManagementLevel = requestedEvaluationLevel; //--- Use specified level
      }
      for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions
         int tradeActionEvaluationLevel = GetTopLevel(tradeActionsToEvaluate[i], moneyManagementLevel); //--- Cap level
         if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { //--- Check order limit
            tradeActionEvaluationLevel += 1;              //--- Increment level
         }
         if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], tradeActionEvaluationLevel)) { //--- Check if action advised
            if (tradeActionsToEvaluate[i] == OpenBuyAction) { //--- Check Buy open
               int size = ArraySize(openDemands);         //--- Get current size
               int newSize = size + 1;                    //--- Calculate new size
               ArrayResize(openDemands, newSize, 8);      //--- Resize array
               openDemands[newSize - 1] = OpenBuyDemand;  //--- Add Buy demand
            } else if (tradeActionsToEvaluate[i] == OpenSellAction) { //--- Check Sell open
               int size = ArraySize(openDemands);         //--- Get current size
               int newSize = size + 1;                    //--- Calculate new size
               ArrayResize(openDemands, newSize, 8);      //--- Resize array
               openDemands[newSize - 1] = OpenSellDemand; //--- Add Sell demand
            } else if (tradeActionsToEvaluate[i] == CloseBuyAction) { //--- Check Buy close
               int size = ArraySize(closeDemands);        //--- Get current size
               int newSize = size + 1;                    //--- Calculate new size
               ArrayResize(closeDemands, newSize, 8);     //--- Resize array
               closeDemands[newSize - 1] = CloseBuyDemand; //--- Add Buy close demand
            } else if (tradeActionsToEvaluate[i] == CloseSellAction) { //--- Check Sell close
               int size = ArraySize(closeDemands);        //--- Get current size
               int newSize = size + 1;                    //--- Calculate new size
               ArrayResize(closeDemands, newSize, 8);     //--- Resize array
               closeDemands[newSize - 1] = CloseSellDemand; //--- Add Sell close demand
            }
         }
      }
      TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); //--- Combine close demands
      if (combinedCloseSignalDemand != NoneDemand) {       //--- Check if close demand exists
         return combinedCloseSignalDemand;                //--- Return close demand
      }
      TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); //--- Combine open demands
      TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, GetNumberOfOpenOrders(wallet)); //--- Adjust for previous demand
      previousSignalDemand = combinedOpenSignalDemand;    //--- Update previous demand
      multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); //--- Filter prevent-open demands
      return multiOrderOpenSignal;                        //--- Return final signal
   }

   //--- Evaluate open conditions and add orders
   void EvaluateOpenConditions(Wallet* wallet, TradingModuleDemand signalDemand) {
      if (signalDemand == OpenBuySellDemand) {            //--- Check Buy/Sell demand
         return;                                          //--- Exit (hedging not supported)
      } else {
         double currentFreeMargin = AccountFreeMargin_LibFunc(); //--- Retrieve free margin
         double requiredMargin;                           //--- Declare required margin
         if (signalDemand == OpenBuyDemand) {             //--- Check Buy demand
            if (!MarginRequired(ORDER_TYPE_BUY, _moneyManager.GetLotSize(), requiredMargin)) { //--- Check margin
               return;                                    //--- Exit if margin check fails
            }
            if (currentFreeMargin < requiredMargin) {     //--- Check sufficient margin
               HandleErrors("Not enough free margin to open buy order with requested volume."); //--- Log error
               return;                                    //--- Exit
            }
            wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_BUY, false)); //--- Add Buy order
         } else if (signalDemand == OpenSellDemand) {     //--- Check Sell demand
            if (!MarginRequired(ORDER_TYPE_SELL, _moneyManager.GetLotSize(), requiredMargin)) { //--- Check margin
               return;                                    //--- Exit if margin check fails
            }
            if (currentFreeMargin < requiredMargin) {     //--- Check sufficient margin
               HandleErrors("Not enough free margin to open sell order with requested volume."); //--- Log error
               return;                                    //--- Exit
            }
            wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_SELL, false)); //--- Add Sell order
         }
      }
   }

   //--- Adjust open demand based on previous demand
   TradingModuleDemand GetOpenDemandBasedOnPreviousOpenDemand(TradingModuleDemand openDemand, int numberOfOpenOrders) {
      if (numberOfOpenOrders == 0 || previousSignalDemand == NoneDemand) { //--- Check no orders or no previous demand
         return openDemand;                               //--- Return current demand
      }
      if (previousSignalDemand == OpenBuyDemand && openDemand == OpenSellDemand) { //--- Check Buy to Sell switch
         return openDemand;                               //--- Allow Sell demand
      } else if (previousSignalDemand == OpenSellDemand && openDemand == OpenBuyDemand) { //--- Check Sell to Buy switch
         return openDemand;                               //--- Allow Buy demand
      }
      return NoneDemand;                                  //--- Block same-direction or mixed demands
   }

private:
   //--- Cap evaluation level
   int GetTopLevel(TradeAction tradeAction, int level) {
      int numberOfExpressions = _advisorStrategy.GetNumberOfExpressions(tradeAction); //--- Retrieve expression count
      if (level > numberOfExpressions) {                  //--- Check if level exceeds expressions
         level = numberOfExpressions;                     //--- Cap level
      }
      return level;                                       //--- Return capped level
   }

   //--- Add previous demand action if missing
   void AddPreviousDemandTradeActionIfMissing(TradeAction& result[]) {
      if (previousSignalDemand == NoneDemand) {           //--- Check if no previous demand
         return;                                          //--- Exit
      }
      bool foundPreviousDemand = false;                   //--- Initialize found flag
      if (previousSignalDemand == OpenBuyDemand) {        //--- Check Buy demand
         AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); //--- Add Buy action
      } else if (previousSignalDemand == OpenSellDemand) { //--- Check Sell demand
         AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); //--- Add Sell action
      } else if (previousSignalDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand
         AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); //--- Add Buy action
         AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); //--- Add Sell action
      }
   }

   //--- Add specific previous demand action
   void AddPreviousDemandTradeAction(TradeAction& result[], TradingModuleDemand demand, TradeAction action) {
      bool foundPreviousDemand = false;                   //--- Initialize found flag
      if (previousSignalDemand == demand) {               //--- Check matching demand
         for (int i = 0; i < ArraySize(result); i++) {    //--- Iterate actions
            if (action == result[i]) {                    //--- Check if action exists
               foundPreviousDemand = true;                //--- Set found flag
            }
         }
         if (!foundPreviousDemand) {                      //--- Check if action missing
            ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array
            result[ArraySize(result) - 1] = action;       //--- Add action
         }
      }
   }

   //--- Filter prevent-open demands
   TradingModuleDemand FilterPreventOpenDemand(TradingModuleDemand multiOrderOpendDemand, TradingModuleDemand preventOpenDemand) {
      if (multiOrderOpendDemand == NoneDemand) {          //--- Check no demand
         return multiOrderOpendDemand;                    //--- Return no demand
      } else if (multiOrderOpendDemand == OpenBuyDemand && (preventOpenDemand == NoBuyDemand || preventOpenDemand == NoOpenDemand)) { //--- Check blocked Buy
         return NoneDemand;                               //--- Block Buy demand
      } else if (multiOrderOpendDemand == OpenSellDemand && (preventOpenDemand == NoSellDemand || preventOpenDemand == NoOpenDemand)) { //--- Check blocked Sell
         return NoneDemand;                               //--- Block Sell demand
      } else if (multiOrderOpendDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand
         if (preventOpenDemand == NoBuyDemand) {          //--- Check no Buy
            return OpenSellDemand;                        //--- Allow Sell demand
         } else if (preventOpenDemand == NoSellDemand) {  //--- Check no Sell
            return OpenBuyDemand;                         //--- Allow Buy demand
         } else if (preventOpenDemand == NoOpenDemand) {  //--- Check no open
            return NoneDemand;                            //--- Block all demands
         }
      }
      return multiOrderOpendDemand;                       //--- Return unfiltered demand
   }
};

Здесь мы разрабатываем модуль для управления открытием нескольких торговых операций с использованием класса MultipleOpenModule_1, производного от класса OpenModuleBase. Инициализируем его с помощью конструктора MultipleOpenModule_1, установив _advisorStrategy таким образом, чтобы сигналы запускались только после перезапуска с помощью SetFireOnlyWhenReset и сохраняя предыдущие запросы в previousSignalDemand. Наша функция Evaluate оценивает сигналы с помощью EvaluateSignals и обрабатывает корректные запросы с помощью EvaluateOpenConditions, возвращая запрос.

В EvaluateOpenSignals мы получаем данные о действиях с помощью функции GetTradeActions, добавляем предыдущие запросы с помощью AddPreviousDemandTradeActionIfMissing и устанавливаем уровень, используя GetNextLevel из _moneyManager или указанное значение. Мы выполняем итерации действий, пропуская CloseBuyAction и CloseSellAction, ограничиваем уровни с помощью GetTopLevel и проверяем лимиты ордеров с помощью GetOpenOrders и MaxNumberOfOpenOrders1. Корректные сигналы из GetAdvice от _advisorStrategy добавляют OpenBuyDemand или OpenSellDemand в массив в сочетании с GetCombinedOpenDemand, корректируются с помощью GetOpenDemandBasedOnPreviousOpenDemand и сортируются функцией FilterPreventOpenDemand.

Функция EvaluateCloseSignals аналогичным образом оценивает значения CloseBuyAction и CloseSellAction, суммируя CloseBuyDemand или CloseSellDemand и объединяя их с функцией GetCombinedCloseDemand.

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

ВХОДНЫЕ ПАРАМЕТРЫ УПРАВЛЕНИЯ

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

//--- Define interface for trader
interface ITrader {
   void HandleTick();                                     //--- Handle tick event
   void Init();                                           //--- Initialize trader
   Wallet* GetWallet();                                   //--- Retrieve wallet
};

//--- Declare global trader pointer
ITrader *_ea;                                             //--- Store EA instance

//--- Define main Expert Advisor class
class EA : public ITrader {
private:
   bool _firstTick;                                       //--- Track first tick
   TradeStrategy* _tradeStrategy;                         //--- Store trade strategy
   AdvisorStrategy* _advisorStrategy;                     //--- Store advisor strategy
   IMoneyManager* _moneyManager;                          //--- Store money manager
   Wallet* _wallet;                                       //--- Store wallet

public:
   //--- Initialize EA
   void EA() {
      _firstTick = true;                                  //--- Set first tick flag
      _wallet = new Wallet();                             //--- Create wallet
      _wallet.SetLastClosedOrdersByTimeframe(DisplayOrderDuringTimeframe); //--- Set closed orders timeframe
      _advisorStrategy = new AdvisorStrategy();           //--- Create advisor strategy
      _advisorStrategy.RegisterOpenBuy(new ASOpenBuyLevel1(), 1); //--- Register Buy signal
      _advisorStrategy.RegisterOpenSell(new ASOpenSellLevel1(), 1); //--- Register Sell signal
      _moneyManager = new MoneyManager(_wallet);          //--- Create money manager
      _tradeStrategy = new TradeStrategy(new MultipleOpenModule_1(_advisorStrategy, _moneyManager)); //--- Create trade strategy
      _tradeStrategy.RegisterCloseModule(new TakeProfitCloseModule_1()); //--- Register TP module
      _tradeStrategy.RegisterCloseModule(new StopLossCloseModule_1()); //--- Register SL module
   }

   //--- Destructor to clean up EA
   void ~EA() {
      delete(_tradeStrategy);                             //--- Delete trade strategy
      delete(_moneyManager);                              //--- Delete money manager
      delete(_advisorStrategy);                           //--- Delete advisor strategy
      delete(_wallet);                                    //--- Delete wallet
   }

   //--- Initialize EA components
   void Init() {
      IsDemoLiveOrVisualMode = !MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE); //--- Set mode flag
      UnitsOneLot = MarketInfo_LibFunc(Symbol(), MODE_LOTSIZE); //--- Set lot size
      SetOrderGrouping();                                 //--- Configure order grouping
      _wallet.LoadOrdersFromBroker();                     //--- Load orders from broker
   }

   //--- Handle tick event
   void HandleTick() {
      if (MQLInfoInteger(MQL_TESTER) == 0) {              //--- Check if not in tester
         SyncOrders();                                    //--- Synchronize orders
      }
      if (AllowManualTPSLChanges) {                       //--- Check if manual TP/SL allowed
         SyncManualTPSLChanges();                         //--- Synchronize manual TP/SL
      }
      AskFunc.Evaluate();                                 //--- Update Ask price
      BidFunc.Evaluate();                                 //--- Update Bid price
      UpdateOrders();                                     //--- Update order profits
      if (!StopEA) {                                      //--- Check if EA not stopped
         _wallet.HandleTick();                            //--- Handle wallet tick
         _advisorStrategy.HandleTick();                   //--- Handle strategy tick
         if (_wallet.GetPendingOpenOrders().Count() == 0 && _wallet.GetPendingCloseOrders().Count() == 0) { //--- Check no pending orders
            _tradeStrategy.Evaluate(_wallet);             //--- Evaluate strategy
         }
         if (ExecutePendingCloseOrders()) {               //--- Execute close orders
            if (!ExecutePendingOpenOrders()) {            //--- Execute open orders
               HandleErrors(StringFormat("Open (all) order(s) failed. Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error
            }
         } else {
            HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error
         }
      } else {
         if (ExecutePendingCloseOrders()) {               //--- Execute close orders
            _wallet.SetAllOpenOrdersToPendingClose();     //--- Move open orders to pending close
         } else {
            HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error
         }
      }
      if (_firstTick) {                                   //--- Check if first tick
         _firstTick = false;                              //--- Clear first tick flag
      }
   }

   //--- Retrieve wallet
   Wallet* GetWallet() {
      return _wallet;                                     //--- Return wallet
   }

private:
   //--- Configure order grouping
   void SetOrderGrouping() {
      int size = ArraySize(_tradeStrategy.CloseModules);  //--- Get close modules size
      ORDER_GROUP_TYPE groups[];                          //--- Declare groups array
      ArrayResize(groups, size);                          //--- Resize array
      for (int i = 0; i < ArraySize(_tradeStrategy.CloseModules); i++) { //--- Iterate modules
         groups[i] = _tradeStrategy.CloseModules[i].GetOrderGroupingType(); //--- Set grouping type
      }
      _wallet.ActivateOrderGroups(groups);                //--- Activate groups
   }

   //--- Synchronize orders with broker
   void SyncOrders() {
      OrderCollection* currentOpenOrders = OrderRepository::GetOpenOrders(MagicNumber, NULL, Symbol()); //--- Retrieve open orders
      if (currentOpenOrders.Count() != (_wallet.GetOpenOrders().Count() + _wallet.GetPendingCloseOrders().Count())) { //--- Check order mismatch
         Print("(Manual) orderchanges detected" + " (found in MT: " + IntegerToString(currentOpenOrders.Count()) + " and in wallet: " + IntegerToString(_wallet.GetOpenOrders().Count()) + "), resetting EA, loading open orders."); //--- Log mismatch
         _wallet.ResetOpenOrders();                       //--- Reset open orders
         _wallet.ResetPendingOrders();                    //--- Reset pending orders
         _wallet.LoadOrdersFromBroker();                  //--- Reload orders
      }
      delete(currentOpenOrders);                          //--- Delete orders collection
   }

   //--- Synchronize manual TP/SL changes
   void SyncManualTPSLChanges() {
      _wallet.GetOpenOrders().Rewind();                   //--- Reset orders iterator
      while (_wallet.GetOpenOrders().HasNext()) {         //--- Iterate orders
         Order* order = _wallet.GetOpenOrders().Next();   //--- Get order
         uint lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Find SL line
         if (lineFindResult != UINT_MAX) {                //--- Check if SL line exists
            double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_SL", OBJPROP_PRICE); //--- Get SL position
            if ((order.StopLossManual == 0 && currentPosition != order.GetClosestSL()) || //--- Check manual SL change
                (order.StopLossManual != 0 && currentPosition != order.StopLossManual)) { //--- Check manual SL mismatch
               order.StopLossManual = currentPosition;       //--- Update manual SL
            }
         }
         lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Find TP line
         if (lineFindResult != UINT_MAX) {                //--- Check if TP line exists
            double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_TP", OBJPROP_PRICE); //--- Get TP position
            if ((order.TakeProfitManual == 0 && currentPosition != order.GetClosestTP()) || //--- Check manual TP change
                (order.TakeProfitManual != 0 && currentPosition != order.TakeProfitManual)) { //--- Check manual TP mismatch
               order.TakeProfitManual = currentPosition;     //--- Update manual TP
            }
         }
      }
   }

   //--- Update order profits
   void UpdateOrders() {
      _wallet.GetOpenOrders().Rewind();                   //--- Reset orders iterator
      while (_wallet.GetOpenOrders().HasNext()) {         //--- Iterate orders
         Order* order = _wallet.GetOpenOrders().Next();   //--- Get order
         double pipsProfit = order.CalculateProfitPips(); //--- Calculate profit
         order.CurrentProfitPips = pipsProfit;            //--- Update current profit
         if (pipsProfit < order.LowestProfitPips) {       //--- Check if lowest profit
            order.LowestProfitPips = pipsProfit;          //--- Update lowest profit
         } else if (pipsProfit > order.HighestProfitPips) { //--- Check if highest profit
            order.HighestProfitPips = pipsProfit;         //--- Update highest profit
         }
      }
   }

   //--- Execute pending close orders
   bool ExecutePendingCloseOrders() {
      OrderCollection* pendingCloseOrders = _wallet.GetPendingCloseOrders(); //--- Retrieve pending close orders
      int ordersToCloseCount = pendingCloseOrders.Count(); //--- Get count
      if (ordersToCloseCount == 0) {                      //--- Check if no orders
         return true;                                     //--- Return true
      }
      if (_wallet.AreOrdersBeingOpened()) {               //--- Check if orders being opened
         return true;                                     //--- Return true
      }
      int ordersCloseSuccessCount = 0;                    //--- Initialize success count
      for (int i = ordersToCloseCount - 1; i >= 0; i--) { //--- Iterate orders
         Order* pendingCloseOrder = pendingCloseOrders.Get(i); //--- Get order
         if (pendingCloseOrder.IsAwaitingDealExecution) { //--- Check if awaiting execution
            ordersCloseSuccessCount++;                    //--- Increment success count
            continue;                                     //--- Move to next
         }
         bool success;                                    //--- Declare success flag
         if (AccountMarginMode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) { //--- Check netting mode
            Order* reversedOrder = new Order(pendingCloseOrder, false); //--- Create reversed order
            reversedOrder.Type = pendingCloseOrder.Type == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set opposite type
            success = OrderRepository::OpenOrder(reversedOrder); //--- Open reversed order
            if (success) {                                //--- Check if successful
               pendingCloseOrder.Ticket = reversedOrder.Ticket; //--- Update ticket
            }
            delete(reversedOrder);                        //--- Delete reversed order
         } else {
            success = OrderRepository::ClosePosition(pendingCloseOrder); //--- Close position
         }
         if (success) {                                   //--- Check if successful
            ordersCloseSuccessCount++;                    //--- Increment success count
         }
      }
      return ordersCloseSuccessCount == ordersToCloseCount; //--- Return true if all successful
   }

   //--- Execute pending open orders
   bool ExecutePendingOpenOrders() {
      OrderCollection* pendingOpenOrders = _wallet.GetPendingOpenOrders(); //--- Retrieve pending open orders
      int ordersToOpenCount = pendingOpenOrders.Count();  //--- Get count
      if (ordersToOpenCount == 0) {                       //--- Check if no orders
         return true;                                     //--- Return true
      }
      int ordersOpenSuccessCount = 0;                     //--- Initialize success count
      for (int i = ordersToOpenCount - 1; i >= 0; i--) {  //--- Iterate orders
         Order* order = pendingOpenOrders.Get(i);         //--- Get order
         if (order.IsAwaitingDealExecution) {             //--- Check if awaiting execution
            ordersOpenSuccessCount++;                     //--- Increment success count
            continue;                                     //--- Move to next
         }
         bool isTradeContextFree = false;                 //--- Initialize trade context flag
         double StartWaitingTime = GetTickCount();        //--- Start timer
         while (true) {                                   //--- Wait for trade context
            if (MQL5InfoInteger(MQL5_TRADE_ALLOWED)) {    //--- Check if trade allowed
               isTradeContextFree = true;                 //--- Set trade context free
               break;                                     //--- Exit loop
            }
            int MaxWaiting_sec = 10;                      //--- Set max wait time
            if (IsStopped()) {                            //--- Check if EA stopped
               HandleErrors("The expert was stopped by a user action."); //--- Log error
               break;                                     //--- Exit loop
            }
            if (GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { //--- Check if timeout
               HandleErrors(StringFormat("The (%d seconds) waiting time exceeded. Trade not allowed: EA disabled, market closed or trade context still not free.", MaxWaiting_sec)); //--- Log error
               break;                                     //--- Exit loop
            }
            Sleep(100);                                   //--- Wait briefly
         }
         if (!isTradeContextFree) {                       //--- Check if trade context not free
            if (!_wallet.CancelPendingOpenOrder(order)) { //--- Attempt to cancel order
               HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error
            }
            continue;                                     //--- Move to next
         }
         bool success = OrderRepository::OpenOrder(order); //--- Open order
         if (success) {                                    //--- Check if successful
            ordersOpenSuccessCount++;                      //--- Increment success count
         } else {
            if (!_wallet.CancelPendingOpenOrder(order)) {  //--- Attempt to cancel order
               HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error
            }
         }
      }
      return ordersOpenSuccessCount == ordersToOpenCount;  //--- Return true if all successful
   }
};

Наконец, мы создаем основную логическую схему управления с помощью класса EA, который реализует интерфейс ITrader с функциями HandleTick, Init и GetWallet. Для управления состоянием и компонентами определяем приватные переменные _firstTick, _tradeStrategy, _advisorStrategy, _moneyManager и _wallet. В конструкторе EA, мы устанавливаем _firstTick на true, инициализируем _wallet новым объектом Wallet, настраиваем его таймфрейм с помощью SetLastClosedOrdersByTimeframe и создаем _advisorStrategy с новым объектом AdvisorStrategy, регистрируя сигналы ASOpenBuyLevel1 и ASOpenSellLevel1 с помощью RegisterOpenBuy и RegisterOpenSell.

Также создаем экземпляр _moneyManager с объектом MoneyManager и _tradeStrategy с объектом TradeStrategy, содержащим MultipleOpenModule_1, регистрируя TakeProfitCloseModule_1 и StopLossCloseModule_1 с помощью RegisterCloseModule. Деструктор очищает все компоненты.

Наша функция Init устанавливает флаги режима с помощью MQLInfoInteger, настраивает размер лота с помощью функции MarketInfo_LibFunc, вызывает функцию SetOrderGrouping для активации групп ордеров с помощью функции GetOrderGroupingType и загружает ордера с помощью функции LoadOrdersFromBroker. Функция HandleTick управляет тиками, синхронизируя ордера с SyncOrders, если не используется тестовый режим, обновляя TP/SL вручную с помощью SyncManualTPSLChanges, если это разрешено, и обновляя цены с помощью функций Evaluate от AskFunc и BidFunc.

Она обновляет данные о прибыли с помощью UpdateOrders, обрабатывает тики кошелька и стратегии с помощью HandleTick, оценивает _tradeStrategy с помощью Evaluate, если нет отложенных ордеров, и выполняет отложенные закрытия и открытия с помощью функций ExecutePendingCloseOrders и ExecutePendingOpenOrders, регистрируя ошибки с помощью HandleErrors в случае сбоев. Если советник остановлен, он закрывает ордера с помощью SetAllOpenOrdersToPendingClose. Теперь мы можем вызывать обработчик событий OnTick для управления сделками.

//--- Handle tick event
datetime LastActionTime = 0;                          //--- Track last action time
void OnTick() {
   string AccountServer = AccountInfoString(ACCOUNT_SERVER);                //--- Retrieve account server
   string AccountCurrency = AccountInfoString(ACCOUNT_CURRENCY);            //--- Retrieve account currency
   string AccountName = AccountInfoString(ACCOUNT_NAME);                    //--- Retrieve account name
   long AccountTradeMode = AccountInfoInteger(ACCOUNT_TRADE_MODE);          //--- Retrieve trade mode
   string ReadableAccountTrademode = "";                                    //--- Initialize readable trade mode
   if (AccountTradeMode == 0) ReadableAccountTrademode = "DEMO ACCOUNT";    //--- Set demo mode
   if (AccountTradeMode == 1) ReadableAccountTrademode = "CONTEST ACCOUNT"; //--- Set contest mode
   if (AccountTradeMode == 2) ReadableAccountTrademode = "REAL ACCOUNT";    //--- Set real mode
   long AccountLogin = AccountInfoInteger(ACCOUNT_LOGIN);                   //--- Retrieve account login
   string AccountCompany = AccountInfoString(ACCOUNT_COMPANY);              //--- Retrieve account company
   long AccountLeverage = AccountInfoInteger(ACCOUNT_LEVERAGE);             //--- Retrieve account leverage
   long AccountLimitOrders = AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);      //--- Retrieve order limit
   double AccountMarginFree = AccountInfoDouble(ACCOUNT_MARGIN_FREE);       //--- Retrieve free margin
   bool AccountTradeAllowed = AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);    //--- Retrieve trade allowed
   bool AccountTradeExpert = AccountInfoInteger(ACCOUNT_TRADE_EXPERT);      //--- Retrieve expert allowed
   string ReadableAccountMarginMode = "";                                   //--- Initialize readable margin mode
   if (AccountMarginMode == 0) ReadableAccountMarginMode = "NETTING MODE";  //--- Set netting mode
   if (AccountMarginMode == 1) ReadableAccountMarginMode = "EXCHANGE MODE"; //--- Set exchange mode
   if (AccountMarginMode == 2) ReadableAccountMarginMode = "HEDGING MODE";  //--- Set hedging mode
   if (OneQuotePerBar) {                                 //--- Check one quote per bar
      datetime currentTime = iTime(_Symbol, _Period, 0); //--- Get current bar time
      if (LastActionTime == currentTime) {               //--- Check if same bar
         return;                                         //--- Exit
      } else {
         LastActionTime = currentTime;                   //--- Update last action time
      }
   }
   Error = NULL;                                         //--- Clear current error
   _ea.HandleTick();                                     //--- Handle tick
   if (IsDemoLiveOrVisualMode) {                         //--- Check visual mode
      MqlDateTime mql_datetime;                          //--- Declare datetime
      TimeCurrent(mql_datetime);                         //--- Get current time
      string comment = "\n" + (string)mql_datetime.year + "." + (string)mql_datetime.mon + "." + (string)mql_datetime.day + "   " + TimeToString(TimeCurrent(), TIME_SECONDS) + OrderInfoComment; //--- Build comment
      if (DisplayOnChartError) {                         //--- Check if error display enabled
         if (Error != NULL) comment += "\n      :: Current error : " + Error; //--- Add current error
         if (ErrorPreviousQuote != NULL) comment += "\n      :: Last error : " + ErrorPreviousQuote; //--- Add previous error
      }
      comment += "";                                     //--- Append empty line
      Comment("ACCOUNT SERVER:  ", AccountServer, "\n",  //--- Display account info
              "ACCOUNT CURRENCY:  ", AccountCurrency, "\n",
              "ACCOUNT NAME:  ", AccountName, "\n",
              "ACCOUNT TRADEMODE:  ", ReadableAccountTrademode, "\n",
              "ACCOUNT LOGIN:  ", AccountLogin, "\n",
              "ACCOUNT COMPANY:  ", AccountCompany, "\n",
              "ACCOUNT LEVERAGE:  ", AccountLeverage, "\n",
              "ACCOUNT LIMIT ORDERS:  ", AccountLimitOrders, "\n",
              "ACCOUNT MARGIN FREE:  ", AccountMarginFree, "\n",
              "ACCOUNT TRADING ALLOWED:  ", AccountTradeAllowed, "\n",
              "ACCOUNT EXPERT ALLOWED:  ", AccountTradeExpert, "\n",
              "ACCOUNT MARGIN ALLOWED:  ", ReadableAccountMarginMode);
   }
}

Здесь мы управляем обновлением рыночной информации в реальном времени для данной программы посредством обработчика событий OnTick, который обрабатывает каждый новый тик цены. Мы начинаем со сбора данных об учетной записи с помощью таких функций, как AccountInfoString, которые позволяют получить информацию о сервере, валюте, имени и компании, AccountInfoInteger для режима торговли, входа в систему, кредитного плеча и лимитов ордеров и AccountInfoDouble для свободной маржи. Для большей ясности мы преобразуем режим торговли в читаемую строку (DEMO ACCOUNT, CONTEST ACCOUNT или REAL ACCOUNT), а режим маржи, хранящийся в AccountMarginMode, — в NETTING MODE, EXCHANGE MODE или HEDGING MODE для большей ясности.

Для управления обработкой тиков устанавливаем флаг OneQuotePerBar, и если он включен, используем функцию iTime для получения времени начала текущего бара и сравнения его с LastActionTime. Если они совпадают, завершаем работу, чтобы избежать повторной обработки; в противном случае обновляем значение LastActionTime. Очищаем все существующие ошибки, устанавливая Error равным null, и вызываем функцию HandleTick для _ea, чтобы выполнить логику стратегии.

Для отображения проверяем параметр IsDemoLiveOrVisualMode, затем используем TimeCurrent и MqlDateTime для форматирования метки времени создавая строку комментария с помощью OrderInfoComment. Если DisplayOnChartError включен, добавляем ошибки из Error и ErrorPreviousQuote. Наконец, используем функцию Comment для отображения сведений о счете на графике, обеспечивая мониторинг и обратную связь в реальном времени для торговой системы. При запуске программы получаем следующий результат.

ГЕНЕРАЦИЯ СИГНАЛОВ

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

//--- Handle trade transactions
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) {
   switch (trans.type) {                               //--- Handle transaction type
   case TRADE_TRANSACTION_DEAL_ADD: {                  //--- Handle deal addition
      datetime end = TimeCurrent();                    //--- Get current server time
      datetime start = end - PeriodSeconds(PERIOD_D1); //--- Set start time (1 day ago)
      HistorySelect(start, end + PeriodSeconds(PERIOD_D1)); //--- Select history
      int dealsTotal = HistoryDealsTotal();            //--- Get total deals
      if (dealsTotal == 0) {                           //--- Check if no deals
         Print("No deals found");                      //--- Log message
         return;                                       //--- Exit
      }
      ulong orderTicketId = HistoryDealGetInteger(trans.deal, DEAL_ORDER); //--- Get order ticket
      CDealInfo dealInfo;                              //--- Declare deal info
      dealInfo.Ticket(trans.deal);                     //--- Set deal ticket
      ENUM_DEAL_ENTRY deal_entry = dealInfo.Entry();   //--- Get deal entry type
      bool found = false;                              //--- Initialize found flag
      if (deal_entry == DEAL_ENTRY_IN) {               //--- Handle deal entry
         OrderCollection* pendingOpenOrders = _ea.GetWallet().GetPendingOpenOrders(); //--- Retrieve pending open orders
         for (int i = 0; i < pendingOpenOrders.Count(); i++) { //--- Iterate orders
            Order* order = pendingOpenOrders.Get(i);   //--- Get order
            if (order.Ticket == orderTicketId) {       //--- Check matching ticket
               found = true;                           //--- Set found flag
               order.OpenTime = dealInfo.Time();       //--- Set open time
               order.OpenPrice = trans.price;          //--- Set open price
               order.TradePrice = order.OpenPrice;     //--- Set trade price
               if (OrderFillingType == ORDER_FILLING_FOK) {                 //--- Check FOK filling
                  order.OrderFilledLots += trans.volume;                    //--- Add volume
                  if (MathAbs(order.Lots - order.OrderFilledLots) < 1e-5) { //--- Check if fully filled
                     order.IsAwaitingDealExecution = false;                 //--- Clear execution flag
                     order.Lots = order.OrderFilledLots;                    //--- Update lots
                     order.TradeVolume = order.Lots;                        //--- Update trade volume
                     _ea.GetWallet().SetPendingOpenOrderToOpen(order);      //--- Move to open
                     Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success
                  }
               } else {
                  order.IsAwaitingDealExecution = false; //--- Clear execution flag
                  bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > 1e-5; //--- Check volume difference
                  order.OrderFilledLots += trans.volume; //--- Add volume
                  order.Lots = order.OrderFilledLots;    //--- Update lots
                  order.TradeVolume = order.Lots;        //--- Update trade volume
                  if (actualVolumeDiffers) {             //--- Check if volume differs
                     Print("Broker executed volume differs from requested volume. Executed volume: " + DoubleToStr(trans.volume)); //--- Log difference
                     OrderRepository::CalculateAndSetCommision(order); //--- Recalculate commission
                  }
                  _ea.GetWallet().SetPendingOpenOrderToOpen(order); //--- Move to open
                  Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success
               }
            }
         }
      } else if (deal_entry == DEAL_ENTRY_OUT) {      //--- Handle deal exit
         OrderCollection* pendingCloseOrders = _ea.GetWallet().GetPendingCloseOrders(); //--- Retrieve pending close orders
         for (int i = 0; i < pendingCloseOrders.Count(); i++) { //--- Iterate orders
            Order* order = pendingCloseOrders.Get(i); //--- Get order
            if (order.Ticket == orderTicketId) {      //--- Check matching ticket
               found = true;                          //--- Set found flag
               if (OrderFillingType == ORDER_FILLING_FOK) {   //--- Check FOK filling
                  order.OrderFilledLots += trans.volume;      //--- Add volume
                  if (MathAbs(order.Lots - order.OrderFilledLots) < 1e-5) { //--- Check if fully filled
                     order.IsAwaitingDealExecution = false;   //--- Clear execution flag
                     order.CloseTime = dealInfo.Time();       //--- Set close time
                     order.ClosePrice = trans.price;          //--- Set close price
                     if (order.MagicNumber == MagicNumber) {  //--- Check EA order
                        TotalCommission += order.Commission;  //--- Add commission
                     }
                     if (IsDemoLiveOrVisualMode) {      //--- Check visual mode
                        AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Delete TP line
                        AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Delete SL line
                     }
                     if (order.ParentOrder != NULL) {   //--- Check if parent order
                        order.ParentOrder.Paint();      //--- Redraw parent
                     }
                     _ea.GetWallet().SetPendingCloseOrderToClosed(order); //--- Move to closed
                     Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success
                  }
               } else {
                  bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > 1e-5; //--- Check volume difference
                  if (actualVolumeDiffers) {                           //--- Handle partial close
                     Print("Broker executed volume differs from requested volume.Requested volume: " + DoubleToStr(order.Lots) + ".Executed volume: " + DoubleToStr(trans.volume)); //--- Log difference
                     Order* remainderOrder = new Order(order, false);  //--- Create remainder order
                     remainderOrder.Ticket = 0;                        //--- Clear ticket
                     remainderOrder.Lots = order.Lots - trans.volume;  //--- Set remaining volume
                     remainderOrder.TradeVolume = remainderOrder.Lots; //--- Update trade volume
                     OrderRepository::CalculateAndSetCommision(remainderOrder); //--- Recalculate commission
                     _ea.GetWallet().GetPendingCloseOrders().Add(remainderOrder); //--- Add remainder
                     order.Lots = trans.volume;                        //--- Update original volume
                     order.TradeVolume = order.Lots;                   //--- Update trade volume
                     OrderRepository::CalculateAndSetCommision(order); //--- Recalculate commission
                  } else {
                     Print("Broker executed volume: " + DoubleToStr(trans.volume)); //--- Log volume
                  }
                  order.IsAwaitingDealExecution = false;   //--- Clear execution flag
                  order.CloseTime = dealInfo.Time();       //--- Set close time
                  order.ClosePrice = trans.price;          //--- Set close price
                  if (order.MagicNumber == MagicNumber) {  //--- Check EA order
                     TotalCommission += order.Commission;  //--- Add commission
                  }
                  if (IsDemoLiveOrVisualMode) {            //--- Check visual mode
                     AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Delete TP line
                     AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Delete SL line
                  }
                  _ea.GetWallet().SetPendingCloseOrderToClosed(order); //--- Move to closed
                  Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success
               }
            }
         }
      }
      if (found) {                                         //--- Check if deal found
         Print("Updated order with deal info.");           //--- Log update
      } else if (trans.symbol == Symbol() && dealInfo.Magic() == MagicNumber) { //--- Check EA deal
         Print("Couldn't find deal info for place/done order"); //--- Log missing deal
      }
      break;
   }
   }
}

В обработчике событий OnTradeTransaction, где мы обрабатываем события MqlTradeTransaction, мы сосредотачиваемся на сделках TRADE_TRANSACTION_DEAL_ADD, получая историю за один день с TimeCurrent и PeriodSeconds с помощью HistorySelect. Если сделки не найдены с помощью параметра HistoryDealsTotal, мы записываем сообщение с помощью Print и завершаем работу. Для каждой сделки мы извлекаем тикет ордера с помощью HistoryDealGetInteger и создаем объект CDealInfo с именем Ticket, чтобы проверить тип записи с помощью функции Entry.

В случае с DEAL_ENTRY_IN (новые сделки), мы выполняем итерацию GetPendingOpenOrders из Wallet от ea, сопоставляя ордера с Time и price из trans. Если OrderFillingType равен ORDER_FILLING_FOK, обновляем OrderFilledLots и, если ордер исполнен, очищаем IsAwaitingDealExecution и переводим ордер в состояние открытия с помощью функции SetPendingOpenOrderToOpen. В противном случае обрабатываем частичное заполнение, регистрируя разницу в объеме с помощью функции Print и обновляя данные через функцию CalculateAndSetCommision.

Для DEAL_ENTRY_OUT (закрытия сделок) мы обрабатываем запрос GetPendingCloseOrders, обновляя соответствующие ордера аналогичным образом, удаляя линии графика с помощью AnyChartObjectDelete, если IsDemoLiveOrVisualMode, добавляя комиссии к TotalCommission и перемещая ордера на закрытие с помощью SetPendingCloseOrderToClosed. Частичные закрытия создают новый ордер с помощью CalculateAndSetCommision для оставшегося объема. Мы регистрируем обновления или ошибки с помощью Print, обеспечивая точное отслеживание сделок. После компиляции получаем следующий результат.

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

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


Тестирование на истории и оптимизация

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

ПРОБЛЕМЫ С ТЕСТИРОВАНИЕМ НА ИСТОРИИ

В качестве решения мы внедрили следующую логику в обработчике событий OnDeinit.

//--- Deinitialize Expert Advisor
void OnDeinit(const int reason) {
   Comment("");
   delete(_ea);                                        //--- Delete EA instance
   delete(AskFunc);                                    //--- Delete Ask function
   delete(BidFunc);                                    //--- Delete Bid function
}

Мы организовали процесс очистки программы с помощью обработчика событий OnDeinit, который срабатывает при удалении советника или выключении терминала. Мы начали с того, что удалили все комментарии к графику с помощью функции Comment, обеспечивая четкое отображение графика. Далее мы освободили память, удалив объект _ea, представляющий собой основной экземпляр EA, с помощью оператора delete. Мы также удалили объекты AskFunc и BidFunc, которые обрабатывают получение данных о ценах, чтобы освободить ресурсы.

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

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

График

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

ОТЧЕТ


Заключение

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

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

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


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

Прикрепленные файлы |
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Моделирование рынка (Часть 20): Первые шаги на SQL (III) Моделирование рынка (Часть 20): Первые шаги на SQL (III)
Хотя мы можем выполнять операции с базой данных, содержащей около 10 записей, но материал усваивается гораздо лучше, когда мы работаем с файлом, который содержит более 15 тысяч записей. То есть, если бы мы попытались создать такое вручную, то эта задача была бы огромной. Однако трудно найти такую базу данных, даже для учебных целей, доступную для скачивания. Но на самом деле нам не нужно к этому прибегать, мы можем использовать MetaTrader 5 для создания базы данных для себя. В сегодняшней статье мы рассмотрим, как это сделать.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Знакомство с языком MQL5 (Часть 34): Освоение API и функции WebRequest в языке MQL5 (VIII) Знакомство с языком MQL5 (Часть 34): Освоение API и функции WebRequest в языке MQL5 (VIII)
В этой статье вы узнаете, как создать панель управления в MetaTrader 5. Мы разберем основы добавления полей ввода, кнопок действий и меток для отображения текста. Используя проектный подход, вы увидите, как настроить панель, в которой пользователи могут вводить сообщения и в итоге отображать ответы API-сервера.