Автоматизация торговых стратегий на MQL5 (Часть 19): Envelopes Trend Bounce Scalping — Исполнение сделок и управление рисками (Часть II)
Введение
В предыдущей статье (Часть 18) мы заложили основы стратегии скальпинга на коррекции на основе конвертов (Envelopes Trend Bounce Scalping) на MetaQuotes Language 5 (MQL5), разработав основную инфраструктуру советника и логику генерации сигналов. Теперь, в Части 19, мы переходим к внедрению исполнения сделок и управления рисками для полной автоматизации стратегии. Мы рассмотрим следующие темы:
- Стратегическая дорожная карта и архитектура
- Реализация средствами MQL5
- Тестирование на истории и оптимизация
- Заключение
В итоге у нас будет полноценная торговая система на 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Моделирование рынка (Часть 20): Первые шаги на SQL (III)
Знакомство с языком MQL5 (Часть 34): Освоение API и функции WebRequest в языке MQL5 (VIII)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования