
Automating Trading Strategies in MQL5 (Part 19): Envelopes Trend Bounce Scalping — Trade Execution and Risk Management (Part II)
Introduction
In our previous article (Part 18), we laid the foundation for the Envelopes Trend Bounce Scalping Strategy in MetaQuotes Language 5 (MQL5), building the Expert Advisor’s core infrastructure and signal generation logic. Now, in Part 19, we advance by implementing trade execution and risk management to automate the strategy fully. We will cover the following topics:
By the end, you’ll have a complete MQL5 trading system for scalping trend bounces, optimized for performance—let’s get started!
Strategy Roadmap and Architecture
In Part 18, we established the groundwork for the Envelopes Trend Bounce Scalping Strategy, setting up the system to detect trading signals using price interactions with the Envelopes indicator, confirmed by trend filters like moving averages and RSI. We focused on building the infrastructure to monitor market conditions and identify potential trade opportunities, but we did not enable the system to execute trades or manage risks. Our roadmap for Part 19 shifts to activating trade execution and implementing risk management to ensure the strategy can act on signals effectively and safely.
Our architectural plan prioritizes a modular approach, creating a clear path to translate signals into trades while incorporating safeguards. We aim to develop a mechanism for placing buy and sell orders based on validated signals, alongside a risk management framework that sets stop-loss and take-profit levels, adjusts position sizes based on account balance, and limits overall losses to protect capital. This design will create a cohesive, automated scalping system. We will define some more classes to handle the trading and incorporate all the logic in the tick logic to bring everything to life. In a nutshell, this is what we aim to achieve.
Implementation in MQL5
To create the program in MQL5, open the MetaEditor, go to the Navigator, locate the Experts folder, click on the "New" tab, and follow the prompts to create the file. Once it is made, in the coding environment, we will need to declare some interfaces and classes for enhanced signal generation, organization, and trade management. Let us start with the basic signal expression interface.
//--- 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 } };
To establish a streamlined framework for handling trading signals, we focus on a modular and reliable signal evaluation system. We begin by defining the "IAdvisorStrategyExpression" interface, which we use to create a consistent blueprint for signal operations. This interface includes four key functions: the "Evaluate" function to determine if a signal is active, the "GetFireOnlyWhenReset" function to check a flag controlling signal triggers, the "SetFireOnlyWhenReset" function to modify this flag, and the "ResetSignalValue" function to clear the signal’s state for subsequent evaluations.
We then develop the "ASSignal" class, which we design to implement the "IAdvisorStrategyExpression" interface, serving as the foundation for specific signal types in our strategy. Within the "ASSignal" class, we define three private variables: "_fireOnlyWhenReset" to control whether signals activate only after a reset, "_previousSignalValue" to track the prior signal state, and "_signalValue" to store the current state. We initialize these in the "ASSignal" constructor function, setting "_fireOnlyWhenReset" to false and both "_previousSignalValue" and "_signalValue" to -1, indicating an uncomputed state. Our "Evaluate" function checks if "_signalValue" is -1, invoking the pure virtual "EvaluateSignal" function (to be defined in derived classes) to compute the signal, and returns true based on "_fireOnlyWhenReset"—either when "_signalValue" is 1 or when transitioning from a non-positive "_previousSignalValue" to 1.
To manage signal behavior, we implement the "GetFireOnlyWhenReset" function to retrieve the "_fireOnlyWhenReset" flag’s value and the "SetFireOnlyWhenReset" function to update it, allowing us to fine-tune when signals trigger. We also include the "ResetSignalValue" function, which we use to store "_signalValue" in "_previousSignalValue" and reset "_signalValue" to -1, preparing for the next evaluation cycle. By marking "EvaluateSignal" as pure virtual, we require derived classes to provide specific signal logic, ensuring flexibility. We can now define some more classes for the signals management logic.
//--- 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 } } } };
Here, we implement more classes to manage the trading signals. Since we have already done some explanation on classes in the previous parts, we hope you already know the syntax of the classes, so we will just show the crucial parts. We create the "TradeSignalCollection" class, using "_tradeSignals" to store "IAdvisorStrategyExpression" pointers, "_pointer" for iteration, and "_size" for signal count, initialized in the "TradeSignalCollection" constructor. We add signals with the "Add" function, remove them with "Remove", and navigate using "Next", "Prev", "Current", "Rewind", and "HasNext" functions, with cleanup handled by the destructor.
In the "AdvisorStrategy" class, we define "_openBuySignals", "_openSellSignals", "_closeBuySignals", and "_closeSellSignals" as "TradeSignalCollection" objects, set up in the "AdvisorStrategy" constructor. We evaluate signals with the "GetAdvice" and "EvaluateASLevel" functions, register signals via "RegisterOpenBuy", "RegisterOpenSell", "RegisterCloseBuy", and "RegisterCloseSell", and manage signal counts with "GetNumberOfExpressions". The "SetFireOnlyWhenReset" function applies reset flags, and "HandleTick" resets and evaluates signals using "ResetSignalValue" and "Evaluate", ensuring efficient signal handling for our strategy. We can now define classes for sell and buy signals at given levels. Let us start with the selling class.
//--- 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 };
We develop a sell signal logic using the "ASOpenSellLevel1" class, derived from the "ASSignal" class. In the protected "EvaluateSignal" function, we check the latest order via the "GetMostRecentOpenOrder" function from "_ea"’s "Wallet", clearing it if it’s a buy order. We trigger a sell signal if no recent order exists and the bid price, from the "GetValue" function of "BidFunc", is below the 4-period SMA ("fn_iMA_SMA_4"), above the 8-period SMA ("fn_iMA_SMA8"), below the upper Envelopes band ("fn_iEnvelopes_ENV_UPPER"), with the prior RSI ("fn_iRSI_RSI") below "OpenSell_Const_0" and current RSI at or above it, or if an order exists and the bid exceeds the open price plus "PipPoint" times "OpenSell_Const_1".
We return true for a valid signal, else false. The "ASOpenSellLevel1" constructor function is empty, leveraging "ASSignal" initialization. We use the same logic for a buy signal.
//--- 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 };
To define the buy logic, we simply use the same logic as we did for the sell. Then, we can add some inputs to control the signal trading logic.
//--- 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)
After defining the input variables for signal generation, we can move on to defining logic for risk management and trade management after signal execution. To achieve that, we will need another interface and class.
//--- 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 } };
To set up money management for the strategy, we define the "IMoneyManager" interface with the "GetLotSize" and "GetNextLevel" functions to standardize trade sizing and level progression. In the "MoneyManager" class, implementing "IMoneyManager", we initialize "_minLot", "_maxLot", and "_lotStep" variables in the "MoneyManager" constructor using the "MarketInfo_LibFunc" function. Our "GetLotSize" function calculates lot size from account balance via AccountInfoDouble, adjusted by "LotSizePercentage", and normalized with "NormalizeLots" to fit broker rules.
The "GetNextLevel" function returns the count of open orders from "GetOpenOrders" plus one. The "NormalizeLots" function ensures valid lot sizes by rounding to "_lotStep" and respecting "_minLot" and "_maxLot". This will help create a concise trade volume and level management system. We can now graduate to managing the trades and we will require another interface for that.
//--- 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 };
Here, we lay the groundwork for managing trading decisions by defining a structured set of enumerations and interfaces. We start by creating the "TradingModuleDemand" enumeration, which we use to categorize trading actions and restrictions, such as "NoneDemand" for no action, "NoBuyDemand" and "NoSellDemand" to block specific order types, "OpenBuyDemand" and "OpenSellDemand" to initiate trades, and "CloseBuyDemand" and "CloseSellDemand" to close positions, among others. This enumeration will provide a clear way to signal the strategy’s intent for trade execution.
We then define the "ITradingModuleSignal" interface, which we design to standardize trading signal operations, including the "GetName" function to retrieve a signal’s identifier and the "Evaluate" function to assess whether a signal is active, optionally considering an open order. Similarly, we introduce the "ITradingModuleValue" interface with the "GetName" and "Evaluate" functions to manage numerical values associated with trading decisions, such as profit targets. For strategy modules, we create the "ITradeStrategyModule" interface, which includes the "Evaluate" function to process trading demands based on a "Wallet" object and the "RegisterTradeSignal" function to incorporate signals.
We extend this with the "ITradeStrategyOpenModule" interface, adding the "EvaluateOpenSignals" and "EvaluateCloseSignals" functions to handle signals for opening and closing trades, and the "ITradeStrategyCloseModule" interface, which includes the "GetOrderGroupingType" function for order grouping and the "RegisterTradeValue" function for value registration. Together, these components will form a flexible framework for coordinating trade actions in our strategy. We can now define management classes based on these modules.
//--- 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 } };
We build the trading strategy framework using the "TradeStrategy" class, storing "ITradeStrategyCloseModule" objects in "CloseModules", "ITradeStrategyModule" objects in "_preventOpenModules", and an "ITradeStrategyOpenModule" in "_openModule". We initialize "_openModule" in the "TradeStrategy" constructor and clean up modules in the destructor. Our "Evaluate" function assesses open orders via "GetOpenOrders", evaluates "_preventOpenModules" with "EvaluatePreventOpenModules" and "GetCombinedPreventOpenDemand", and processes "CloseModules" with "EvaluateCloseModules" and "GetCombinedCloseDemand".
The "EvaluateCloseConditions" function checks order types against "CloseBuyDemand" or "CloseSellDemand", verifies manual stop-loss/take-profit using "Ask_LibFunc" and "Bid_LibFunc", and handles closures with "SetOpenOrderToPendingClose" or "SplitOrder". We register modules with "RegisterPreventOpenModule" and "RegisterCloseModule". The "ModuleCalculationsBase" class computes profits via "CalculateOrderCollectionProfit" and "CalculateOrderProfit", while "OpenModuleBase" creates orders with "OpenOrder" and determines actions with "GetTradeActions", merging demands using "GetCombinedOpenDemand" and "GetCombinedCloseDemand" for a cohesive system.
We are now set to take trades, and to ensure we don't overtrade, we can have an external variable to control the maximum number of trades per instance.
//--- Define input for maximum open orders input int MaxNumberOfOpenOrders1 = 1; //--- Set maximum number of open orders (default: 1)
After setting the trade count restriction variable, we can now define a class to open multiple modules.
//--- 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 } };
Here, we develop a module for managing multiple trade openings using the "MultipleOpenModule_1" class, derived from "OpenModuleBase". We initialize it with the "MultipleOpenModule_1" constructor, setting "_advisorStrategy" to trigger signals only after reset via "SetFireOnlyWhenReset" and storing previous demands in "previousSignalDemand". Our "Evaluate" function assesses signals with "EvaluateSignals" and acts on valid demands using "EvaluateOpenConditions", returning the demand.
In "EvaluateOpenSignals", we retrieve actions with "GetTradeActions", add prior demands via "AddPreviousDemandTradeActionIfMissing", and set the level using "_moneyManager"’s "GetNextLevel" or a specified value. We iterate actions, skipping "CloseBuyAction" and "CloseSellAction", cap levels with "GetTopLevel", and check order limits with "GetOpenOrders" and "MaxNumberOfOpenOrders1". Valid signals from "_advisorStrategy"’s "GetAdvice" add "OpenBuyDemand" or "OpenSellDemand" to an array, combined with "GetCombinedOpenDemand", adjusted by "GetOpenDemandBasedOnPreviousOpenDemand", and filtered by "FilterPreventOpenDemand".
The "EvaluateCloseSignals" function similarly evaluates "CloseBuyAction" and "CloseSellAction", adding "CloseBuyDemand" or "CloseSellDemand" and combining with "GetCombinedCloseDemand".
In "EvaluateOpenConditions", we verify the margin with "AccountFreeMargin_LibFunc" and "MarginRequired", adding buy or sell orders via "OpenOrder" if sufficient, or logging errors with "HandleErrors". We use a similar logic to close the open orders, set take profit, and stop loss, as well as have the corresponding inputs. Upon execution, we now have the following inputs.
We can now create a final class to handle all execution and management already implemented. To achieve that, we use the following logic.
//--- 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 } };
Finally, we establish the core logic control using the "EA" class, which implements the "ITrader" interface with "HandleTick", "Init", and "GetWallet" functions. You should now be fully aware of these functions that we already did. We define private variables "_firstTick", "_tradeStrategy", "_advisorStrategy", "_moneyManager", and "_wallet" to manage state and components. In the "EA" constructor, we set "_firstTick" to true, initialize "_wallet" with a new "Wallet" object, configure its timeframe via "SetLastClosedOrdersByTimeframe", and create "_advisorStrategy" with a new "AdvisorStrategy" object, registering "ASOpenBuyLevel1" and "ASOpenSellLevel1" signals using "RegisterOpenBuy" and "RegisterOpenSell".
We also instantiate "_moneyManager" with a "MoneyManager" object and "_tradeStrategy" with a "TradeStrategy" object containing a "MultipleOpenModule_1", registering "TakeProfitCloseModule_1" and "StopLossCloseModule_1" via "RegisterCloseModule". The destructor cleans up all components.
Our "Init" function sets mode flags with MQLInfoInteger, configures lot size via "MarketInfo_LibFunc", calls "SetOrderGrouping" to activate order groups using "GetOrderGroupingType", and loads orders with "LoadOrdersFromBroker". The "HandleTick" function manages ticks, syncing orders with "SyncOrders" if not in tester mode, updating manual TP/SL with "SyncManualTPSLChanges" if allowed, and refreshing prices via "AskFunc" and "BidFunc"’s "Evaluate" functions.
It updates profits with "UpdateOrders", processes wallet and strategy ticks with "HandleTick", evaluates "_tradeStrategy" with "Evaluate" if no pending orders, and executes pending closes and opens with "ExecutePendingCloseOrders" and "ExecutePendingOpenOrders", logging errors with "HandleErrors" if failures occur. If the EA is stopped, it closes orders with "SetAllOpenOrdersToPendingClose". We can now call this on the OnTick event handler to manage the trades.
//--- 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); } }
Here, we manage real-time market updates for the program through the OnTick event handler, which processes each new price tick. We begin by collecting account details using functions like "AccountInfoString" for the server, currency, name, and company, AccountInfoInteger for trade mode, login, leverage, and order limits, and AccountInfoDouble for free margin. We convert the trade mode into a readable string ("DEMO ACCOUNT", "CONTEST ACCOUNT", or "REAL ACCOUNT") and the margin mode, stored in "AccountMarginMode", into "NETTING MODE", "EXCHANGE MODE", or "HEDGING MODE" for clarity.
To control tick processing, we check the "OneQuotePerBar" flag, and if enabled, we use the iTime function to get the current bar’s start time, comparing it with "LastActionTime". If they match, we exit to avoid duplicate processing; otherwise, we update "LastActionTime". We clear any existing error with "Error" set to null and call the "HandleTick" function on "_ea" to execute the strategy’s logic.
For display, we check "IsDemoLiveOrVisualMode", then use TimeCurrent and MqlDateTime to format the timestamp, building a comment string with "OrderInfoComment". If "DisplayOnChartError" is enabled, we append errors from "Error" and "ErrorPreviousQuote". Finally, we use the Comment function to display account details on the chart, ensuring real-time monitoring and feedback for the trading system. When we run the program, we have the following output.
From the image, we can see that we can generate and execute a trade, in this case, a but trade. What we now need to do is manage the trade, and to achieve that, we need to keep track of the transactions, so we will the OnTradeTransaction event handler to listen to successfully opened trades and manage them as follows.
//--- 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; } } }
On the OnTradeTransaction event handler where we process MqlTradeTransaction events, we focus on TRADE_TRANSACTION_DEAL_ADD transactions, where we retrieve a one-day history window with TimeCurrent and "PeriodSeconds" via HistorySelect. If no deals are found with "HistoryDealsTotal", we log a message with "Print" and exit. For each deal, we extract the order ticket using "HistoryDealGetInteger" and create a "CDealInfo" object with "Ticket" to check the entry type via "Entry".
For DEAL_ENTRY_IN (new trades), we iterate "GetPendingOpenOrders" from "_ea"’s "Wallet", matching tickets to update orders with "Time" and "price" from "trans". If "OrderFillingType" is "ORDER_FILLING_FOK", we update "OrderFilledLots" and, if filled, clear "IsAwaitingDealExecution" and move the order to open with "SetPendingOpenOrderToOpen". Otherwise, we handle partial fills, logging volume differences with "Print" and updating via "CalculateAndSetCommision".
For "DEAL_ENTRY_OUT" (closures), we process "GetPendingCloseOrders", updating matching orders similarly, removing chart lines with "AnyChartObjectDelete" if "IsDemoLiveOrVisualMode", adding commissions to "TotalCommission", and moving orders to close with "SetPendingCloseOrderToClosed". Partial closes create a new order with "CalculateAndSetCommision" for the remaining volume. We log updates or errors with "Print", ensuring accurate trade tracking. Now upon compilation, we have the following output.
From the image, we can see that we manage the trade executions and display the metadata on the window. The thing that remains is backtesting the program to ensure it works correctly, and that is handled in the next section.
Backtesting and Optimization
Upon testing the program, we noticed some oversight that we had missed and that was the release of the program and its components when we removed the program from the chart. The issue was as follows.
From the image, we can see the illustration of the issues. To take care of the issues, we implemented the following logic in the OnDeinit event handler.
//--- Deinitialize Expert Advisor void OnDeinit(const int reason) { Comment(""); delete(_ea); //--- Delete EA instance delete(AskFunc); //--- Delete Ask function delete(BidFunc); //--- Delete Bid function }
We managed the cleanup process for the program using the OnDeinit event handler, which is triggered when the Expert Advisor is removed or the terminal is shut down. We began by clearing any chart comments with the Comment function, ensuring a clean chart display. Next, we released memory by deleting the "_ea" object, which represents the main "EA" instance, using the delete operator. We also deleted the "AskFunc" and "BidFunc" objects, which handle price data retrieval, to free up resources.
This concise process ensured proper deinitialization, preventing memory leaks and maintaining system efficiency when the program was stopped. That solved everything and upon thorough optimization and backtesting, using the default settings and changing the trade or risk module settings to using 1%, 5, 30, 10, 60 inputs respectively, we have the following results.
Backtest graph:
Backtest report:
Conclusion
In conclusion, we have developed an MQL5 implementation of the Envelopes Trend Bounce Scalping Strategy, enabling automated trade execution driven by precise signals from Envelopes, Moving Averages, and Relative Strength Index indicators, with integrated risk management for controlled position sizing and loss protection. Our modular framework, featuring dynamic signal evaluation, trade execution, and real-time order monitoring, provides a scalable foundation for scalping. You can further refine this program by adjusting signal thresholds, optimizing risk parameters, or incorporating additional indicators to align with your trading goals.
Disclaimer: This implementation is for educational purposes only. Trading involves substantial financial risks, and market volatility can lead to significant losses. Thorough backtesting and prudent risk management are essential before using this program in live trading environments.
By mastering these techniques, we can enhance the program’s adaptability and robustness or leverage its structure to develop other trading strategies, advancing the program's trading algorithm.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use