Русский Español Português
preview
Developing a Multi-Currency Expert Advisor (Part 28): Adding a Position Closing Manager

Developing a Multi-Currency Expert Advisor (Part 28): Adding a Position Closing Manager

MetaTrader 5Examples |
283 0
Yuriy Bykov
Yuriy Bykov

Introduction

In Part 12, we added a risk manager module to the multi-currency EA to limit daily and overall drawdown. It does not increase profits, but is critical to protecting funds in adverse conditions. It is based on prop trading rules with flexible settings: drawdown in currency, as a percentage of the balance, or from the start of the day.

The module is implemented as the CVirtualRiskManager class with methods for tracking balance, profit, and checking limitations. A profit-locking function is also provided: once the target is reached, all positions are closed and trading stops.

For regular accounts, it would be preferable for trading to restart automatically once the profit target has been reached. Currently, this requires manual intervention. It is time to automate this as well.

I have considered two options of restarting trading strategies when reaching the target profit:

  • expand the current risk manager,
  • create a separate module.

I have chosen the second path because the current risk manager operates independently of strategies: it closes only real positions without affecting virtual ones. Changing this logic would complicate the architecture and violate modular independence.

The risk manager also creates additional testing overhead, so it is better to move the new functionality into a separate module — it can be used even without the risk manager running.

The new goal is a module that can restart all strategies when specified conditions (profit, loss, time, etc.) are met, without relying on trading history and without manual intervention. I will call the new module the closing manager, as it is a separate optional module, but its addition can improve the results, and it manages the process of completely closing all positions, both real and virtual.


Initial requirements

Let's formulate more clearly the closing manager's responsibilities and parameters. 

The closing manager should:

  1. Take profit, which means closing all virtual positions when the specified profit is reached. In this case, real positions are also automatically closed. Let's introduce three parameters to achieve that:
    • Basic balance. The amount of funds in the trading account that serves as the reference balance for calculating profit or loss.
    • Profit calculation method. It can take one of several possible values, for example, as a percentage of the base balance or a fixed amount in the deposit currency.
    • Profit value. The number used to calculate profit using the chosen method.
  2. Limit loss, which means closing all virtual positions when a specified loss is reached. This process also requires three parameters, one or two of which can be shared with the profit-taking parameters:
    • Basic balance. The amount of funds in the trading account that serves as the reference balance for calculating profit or loss.
    • Loss calculation method. It can also take one of several possible values, as for the method of calculating profit.
    • Loss value. The number used to calculate the loss using the selected method.
  3. Enable profit trailing  upon reaching the specified profit, virtual positions are not closed, but a certain lower profit level is saved the positions will be actually closed at. If profits grow, then this level should also increase. The increase can occur either continuously or in steps with some increment. The following parameters can be added for this process:
    • Enabling trailing (Yes / No).
    • Level setting method. In this parameter, we can select the preferred method for setting the trailing enable level. For example, the level can be set as a percentage of the fixed profit or as an absolute value in the trading account currency.
    • Trailing start level. The number used to calculate the trailing start level for the selected method.
    • Step sizeThe level at which the trailing threshold is moved. For calculation, we can use the same method as for the trailing start level.
  4. Enable breakeven level  when the profit reaches this value, we save a certain small positive profit level the positions will be closed at. With a further increase in profit, this level, unlike trailing, will not increase. The parameters that control this process can be as follows:
    • Enabling breakeven (Yes / No).
    • Level setting method. This parameter is similar to the same-name parameter for trailing meaning that it can also be either relative or absolute.
    • Breakeven enabling levelThe number used to calculate the breakeven level for the selected method.
Let's focus for now on this basic functionality, which can be developed further. It is possible that during implementation we will need to add something to the parameters or otherwise change their composition. But initially we will focus on this description of the task.


Project repository

In Part 25, we added a new strategy and looked at how to create a project to automatically optimize the selected strategy and create a final EA that includes multiple instances of trading strategies with different parameters. The entire code was divided into two parts  library and project. For the library section, Part 26 already features the Adwizard public code repository in the MQL5 Algo Forge storage. However, for the project one this has not yet been done.

Let's fix this and create the new SimpleCandles repository. This repository will contain the project part for creating the final EA using strategies with the same name. Beyond the  main branch, we will also introduce a development branch named develop. If this project is dedicated to several articles, then edits related to different articles will be distributed across different branches generated from the develop branch. As they are ready, they will be merged back into the develop and main branches.

Let's create a local folder to contain the project folder, for example, MQL5/Experts/Articles/17608. We clone this repository into the selected folder and create the Include folder in it. In this folder, we will place the repository of the library part this project depends on. The Include  folder receives the clone of the  Adwizard library repository.

Eventually, we get approximately the following folder structure in the terminal folder:

Fig. 1. The folder structure in the project repository after cloning the project and library parts

In the cloned folder of the Adwizard repository, switch to the develop branch. It will be common to all articles. However, as we work on this project, we will be making changes to the Adwizard library, so in this repository we will create a new branch generated from the develop one.

After that, create a separate branch for working on this article in the SimpleCandles project repository and start development.


Preparing the library code

Let's prepare the ground for the closing manager implementation. First of all, we should note that the latest MetaTrader builds have added stricter variable type checking, which is why previously compiled code now produces errors of the following type:

parameter convertion type 'short[260]' to 'ushort[] &' is not allowed   MTTester.mqh    
   int user32::GetClassNameW(long,ushort&[],int)        winuser.mqh     

Luckily, this occurred only once in the used code and was fixed by changing the array type: 

static string GetClassName( const HANDLE Handle )
  {
    string Str = NULL;

    ushort Buffer[MAX_PATH] = {0};

    if (user32::GetClassNameW(Handle, Buffer, ::ArraySize(Buffer)))
      Str = ::ShortArrayToString(Buffer);

    return(Str);
  }

However, after the next terminal update, this file was completely replaced with the latest version from the MultiTester library to fix incorrect behavior that has some other cause.

The next change is related to the need for the closing manager to initiate the closing of all positions. Let's add a separate method for closing all positions to the CVirtualAdvisor EA class, so that the closing manager can call it if necessary.

To implement this method, we already have everything we need: every strategy inherited from CVirtualStrategy has a method of closing all its virtual positions. Therefore, in the EA class, we only need to call this method for each strategy:

//+------------------------------------------------------------------+
//| Close positions of all strategies                                |
//+------------------------------------------------------------------+
void CVirtualAdvisor::Close(void) {
// For all strategies, we call the method for closing virtual positions
   FOREACH(m_strategies) ((CVirtualStrategy *)m_strategies[i]).Close();
}

In Part 27, we created a component for displaying multi-line text in a window that expands to cover the entire chart to which the EA is attached. It was made as part of another project, but it will be useful to us here as well. So let's move it to the Adwizard library placing the file with the CConsoleDialog class in the Adwizard/Utils folder. To use it, we will add the creation of an object of this class in the Adwizard/Experts/Expert.mqh file of the EA class:

CConsoleDialog      *dialog;             // Dialog for displaying text with results

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

// Create and launch a dialog to display the results
   dialog = new CConsoleDialog();
   dialog.Create(__NAME__ + ":" + (string) magic_);
   dialog.Run();

// Successful initialization
   return(INIT_SUCCEEDED);
}

In the function for handling a new tick in the same file, we will add the setting of new text for this object. We will receive the text itself from the CVirtualAdvisor class calling its Text() method we are going to implement further on:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   expert.Tick();

// ...

// Display text with information about the EA operation
   if (IsNewBar(Symbol(), PERIOD_M1)) {
      dialog.Text(expert.Text());
   }
}

To prevent the lines for opening virtual positions from being drawn against the background of the text, we will temporarily disable their visualization by making the CVirtualChartOrder::Show() method empty:

//+------------------------------------------------------------------+
//| Show virtual position (order)                                    |
//+------------------------------------------------------------------+
void CVirtualChartOrder::Show() {
   return;

   // ...
}


IsActive property for all descendants of CFactorable

When optimization is performed on trading instruments that include cryptocurrencies, and the launch is performed at a broker that does not support cryptocurrencies, an error may occur when launching the final EA. It involves trying to obtain the trading history and properties of a symbol that is not included in the Market Watch. In this case, if the final EA contains a large number of trading strategy instances working on the available symbols, we can simply disable strategies for the instruments not included in the Market Watch.

Currently, all trading strategies are descendants of the CFactorable class, which makes it possible to create objects of these strategies from the initialization string. This class provides for the possibility that the initialization string may not be entirely correct. Then this object and all previous objects from the common initialization string will be considered invalid. In this situation, the EA will not be able to initialize and resume its work.

What we would like is for a certain type of "error" in the initialization string to allow us to simply ignore part of the initialization string, ultimately creating an EA object from the full initialization string. To achieve this, let's add a new property to the CFactorable class named m_isActive , as well as the IsActive() method for reading its value:

//+------------------------------------------------------------------+
//| Base class of objects created from a string                      |
//+------------------------------------------------------------------+
class CFactorable {
private:
   // ...

protected:
   // ...
   
   bool              m_isActive; // Is the object active?

   // ...

public:
   // ...

   bool              IsActive();                         // Is the object active?

   // ...
};

Such a property already existed for some classes, such as the CVirtualRiskManager risk manager class, so in these classes we will remove its declaration, since it will be made in the base class. This also applies to the future closing manager class, which will also use this property to check whether it is active.

At the same time, we made it optional to specify the risk manager and closing manager in the initialization string by adding a check for their presence when launching the EA in the CVirtualAdvisor class constructor:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVirtualAdvisor::CVirtualAdvisor(string p_params) {
// Save the initialization string
   m_params = p_params;

// Read the initialization string of the strategy group object
   string groupParams = ReadObject(p_params);

// Read the initialization string of the risk manager object
   string riskManagerParams = NULL;

   if(IsObjectOf(p_params, "CVirtualRiskManager")) {
      riskManagerParams = ReadObject(p_params);
   }

// Read the initialization string of the closing manager object
   string closeManagerParams = NULL;
   if(IsObjectOf(p_params, "CVirtualCloseManager")) {
      closeManagerParams = ReadObject(p_params);
   }

// Read the magic number
   ulong p_magic = ReadLong(p_params);

// Read the EA name
   string p_name = ReadString(p_params);

// Read the work flag only at the bar opening
   m_useOnlyNewBar = (bool) ReadLong(p_params);

// If there are no read errors,
   if(IsValid()) {
// Create a strategy group
      CREATE(CVirtualStrategyGroup, p_group, groupParams);

      // Initialize the symbol monitor with a static symbol monitor
      m_symbols = CSymbolsMonitor::Instance();

      // Initialize the receiver with the static receiver
      m_receiver = CVirtualReceiver::Instance(p_magic);

      // Initialize the interface with the static interface
      m_interface = CVirtualInterface::Instance(p_magic);

      // Form the name of the EA database file for saving the state from the EA name and parameters
      m_fileName = FileName(p_name, p_magic);

      // Save the work (test) start time
      m_fromDate = TimeCurrent();

      // Reset the last save time
      m_lastSaveTime = 0;

      // Add the contents of the group to the EA
      Add(p_group);

      // Remove the group object
      delete p_group;

      // Create the risk manager object
      if(riskManagerParams != NULL) {
         m_riskManager = NEW(riskManagerParams);
      }

      // Create the closing manager object
      if(closeManagerParams != NULL) {
         m_closeManager = NEW(closeManagerParams);
         m_closeManager.Expert(&this);
      }
   }
}

After making the changes, let's move on to the main part  creating the closing manager.


Creating the closing manager

To begin, let's highlight several possible states that the closing manager can be in. In a normal state, neither the planned profit nor the maximum loss has yet been achieved. While in this state, the closing manager only needs to wait for the transition to one of several subsequent states. Upon reaching a specified profit or loss, a transition to two corresponding states will be carried out. In these states, the close manager should close all positions, remember the new levels of the specified profit and loss, and return to the normal state.

If trailing is enabled, then when the specified profit level is reached, the closing manager will switch to another state. The transition back to a normal state will require more complex actions, so we will not describe them in detail for now.

We implement all states as the ENUM_CM_STATE enumerated type.

To define the methods for calculating planned profit and loss, we will also create two separate enumeration types: ENUM_CM_CALC_LOSS and ENUM_CM_CALC_PROFIT. Let's consider two options: a fixed value in monetary terms and a relative value as a percentage of a certain basic balance.

// Possible states of the closing manager
enum ENUM_CM_STATE {
   CM_STATE_OK,            // Limits are not exceeded
   CM_STATE_LOSS,          // Overall limit exceeded
   CM_STATE_PROFIT,        // Total profit reached
   CM_STATE_TRAIL_PROFIT   // Profit trailing
};

// Possible methods for calculating total loss
enum ENUM_CM_CALC_LOSS {
   CM_CALC_LOSS_MONEY_BB,           // [$] Fixed Money
   CM_CALC_LOSS_PERCENT_BB,         // [%] of Base Balance
};

// Possible methods for calculating total profit
enum ENUM_CM_CALC_PROFIT {
   CM_CALC_PROFIT_MONEY_BB,           // [$] Fixed Money
   CM_CALC_PROFIT_PERCENT_BB,         // [%] of Base Balance
};

The closing manager class itself will be inherited from the CFactorable base class to provide the ability to create the closing manager object from the initialization string. At the same time, it will immediately have an inherited activity property for easily enabling or disabling the closing manager.

To be able to carry out this task, the closing manager will need to remember the base balance level, from which the resulting profit or loss will be calculated. When taking a profit or loss, this level should also change to the value of the current account balance reached after closing all positions. This is the difference between this parameter and the parameter of the same name in the risk manager. There the level of the basic balance always remains unchanged.

The next group of properties will be used to select the calculation method and the calculation itself of the planned profit and loss. They will be used in the LossMoney() and ProfitMoney() calculation methods returning a value in monetary terms.

To close positions, the closing manager should be able to request the EA's object to close. Therefore, we will add the pointer to the EA object and the method for setting it to the list of properties of the closing manager.

We will add one more property to store the current state of the closing manager object.

Creating based on CFactorable requires placing the constructor in a private area and adding two special macros as described in Part 24.

As a result, we get a description of the closing manager class that looks something like this:

//+------------------------------------------------------------------+
//| Closing manager class (profit and loss taking)                   |
//+------------------------------------------------------------------+
class CVirtualCloseManager : public CFactorable {
protected:
// Main constructor parameters
   double            m_baseBalance;          // Base balance

   ENUM_CM_CALC_LOSS m_calcLossLimit;        // Method of calculating the maximum overall loss
   double            m_maxLossLimit;         // Parameter of calculating the maximum total loss

   ENUM_CM_CALC_PROFIT m_calcProfitLimit;    // Method for calculating maximum overall profit
   double            m_maxProfitLimit;       // Parameter for calculating the maximum overall profit

   CVirtualAdvisor*  m_expert;               // Pointer to the EA object

// Current state
   ENUM_CM_STATE     m_state;                // State

// Updated values
   double            m_balance;              // Current balance
   double            m_equity;               // Current equity
   double            m_profit;               // Current floating profit
   double            m_overallProfit;        // Current total profit relative to base balance

// Protected methods
   double            LossMoney();            // Maximum total loss
   double            ProfitMoney();          // Maximum profit

   void              UpdateProfit();         // Update current profit values
   void              CheckLimits();          // Check whether acceptable profit/loss levels have been achieved 
  
   CVirtualCloseManager(string p_params);    // Private constructor

public:
   STATIC_CONSTRUCTOR(CVirtualCloseManager); // Static object creation method
   virtual void      Tick();                 // Handle tick in the closing manager

   virtual string    Text();                 // Information about the current state

   // Bind the EA to the closing manager
   void              Expert(CVirtualAdvisor* p_expert);

   virtual bool      Save();      // Save status
   virtual bool      Load();      // Load status

   virtual string    operator~() override;   // Convert object to string
};

REGISTER_FACTORABLE_CLASS(CVirtualCloseManager); // Register a new CFactorable child

Let's look at the two main class methods: the constructor and the tick handling method.

In the constructor, as usual, we read the parameter values sequentially from the initialization string and assign them to the corresponding properties, set the current state to normal, update the current profit values, and save the current balance value as the base one if it was not set directly:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVirtualCloseManager::CVirtualCloseManager(string p_params) {
// Save the initialization string
   m_params = p_params;

// Read the initialization string and set the property values
   m_isActive = (bool) ReadLong(p_params);
   m_baseBalance = ReadDouble(p_params);
   m_calcLossLimit = (ENUM_CM_CALC_LOSS) ReadLong(p_params);
   m_maxLossLimit = ReadDouble(p_params);
   m_calcProfitLimit = (ENUM_CM_CALC_PROFIT) ReadLong(p_params);
   m_maxProfitLimit = ReadDouble(p_params);

// Set the state: Limits are not exceeded
   m_state = CM_STATE_OK;

// Update the current profit values
   UpdateProfit();

// Adjust the base balance if it is not set
   if(m_baseBalance == 0) {
      m_baseBalance = m_balance;
   }
}

In the basic tick handling method, we analyze the current state and, depending on it, either check whether the target profit or loss levels have been reached if the manager was in a normal state, or initiate the closure of all positions and then transition to a normal state:

//+------------------------------------------------------------------+
//| Tick processing in the risk manager                              |
//+------------------------------------------------------------------+
void CVirtualCloseManager::Tick() {
// If the risk manager is inactive, exit
   if(!m_isActive) {
      return;
   }

// Update the current profit values
   UpdateProfit();

// If the manager is in the trailing state,
   if(m_state == CM_STATE_TRAIL_PROFIT) {
      // immediately take the profit
      // switching the manager to the corresponding state
      if(true) {
         m_state = CM_STATE_PROFIT;
      }
   }

// If the manager is in normal condition,
   if(m_state == CM_STATE_OK) {
      // Check for exceeding loss and profit limits
      CheckLimits();
   }

// If the manager is in a state of achieved loss or profit,
   if(m_state == CM_STATE_LOSS || m_state == CM_STATE_PROFIT) {
      // Close all positions
      m_expert.Close();

      // If all positions are closed,
      if(PositionsTotal() == 0) {
         // Switch to normal state
         m_state = CM_STATE_OK;
         
         // Update the base balance value
         m_baseBalance = m_balance;
      } else {
         // Wait for all positions to close
      }

      // Save the EA state
      m_expert.Save();
   }
}

To begin with, we decided to limit ourselves to this functionality of the closing manager, so the profit trailing state is not used for now.


Passing inputs

After creating the closing manager class, we need to connect it to the EA. To do this, we need to add inputs through which we can control the creation of the initialization string for the closing manager. This needs to be done in the Adwizard/Experts/Expert.mqh file:

// ...

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Use a strategy group"
sinput int        groupId_       = 0;     // - ID of the group from the new library (0 - last)
sinput bool       useAutoUpdate_ = true;  // - Use auto update?

input group "::: Money management"
sinput double expectedDrawdown_  = 10;    // - Maximum risk (%)
sinput double fixedBalance_      = 10000; // - Used deposit (0 - use all) in the account currency
input  double scale_             = 1.00;  // - Group scaling multiplier

input group ":::  Closing manager"
input bool        cmIsActive_                = true;  // - Active?
input double      cmStartBaseBalance_        = 0;     // - Basic balance
input ENUM_CM_CALC_LOSS
cmCalcLossLimit_           = CM_CALC_LOSS_MONEY_BB;   // - Loss calculation method
input double      cmLossLimit_       = 100;           // - Threshold loss value
input ENUM_CM_CALC_PROFIT
cmCalcProfitLimit_                    = CM_CALC_PROFIT_MONEY_BB;  // - Method for calculating total profit
input double      cmProfitLimit_   = 1000000;                     // - Profit target

// ...

This file is included when compiling the final EA, so the added inputs will become available in it. By default, we set the values of taken profit and loss to be set in money (in the deposit currency). We still have to select the values themselves, so what is specified in the default values is not important for now.


Initial testing

Let's see what we got. First, let's check the correct operation of the mechanism for closing positions without regard to the profit received. If everything works correctly, then at the next stage we can start optimizing the profit we receive.

Let's use the database of the final EA obtained in Part 25 to launch the final EA in the tester. We then carried out accelerated optimization on several intervals lasting 1 year and obtained twelve groups of strategies stored in the strategy_groups table:

The database file was called SimpleCandles-27183.test.db.sqliteIn order for the final EA to be able to use this database, this file should be located in the common data folder of the MetaTrader 5 terminals in the Files subfolder. In addition, the final EA should be named SimpleCandles.ex5 and the value of the magic number in the inputs should be left equal to 27183.

Let's first launch the EA without using the closing manager with the very first group of strategies with id_group=20. To do this, let's set the following values for the inputs:

We will use the same interval during which auto optimization was carried out as the test interval, i.e., the entire year of 2022. We get the following results:

Fig. 2. Results of the final EA with id_group=20 without a closing manager for 2022

As you can see, the optimization found quite good combinations of parameters for different instances of simple trading strategies to ensure a significant profit over a given interval while remaining within the specified drawdown of 10%.

Now let's turn on the closing manager and set a small value, for example USD 10, as the expected profit for taking:


Let's run the EA in visual testing mode. Since we have added the display of operation data to the final EA, in this mode we can see which symbols and how many strategies are used in the group with the ID 20 from the EA database (three symbols GBPUSD, EURUSD, EURGBP and 48 strategies), the value of the base balance of the closing manager, as well as the target level of profit and loss for closing.

Fig. 3. Starting EA visual test with the closing manager enabled

Figure 3 shows that the closing manager's base balance has already reached USD 10009.89, meaning that all positions have been closed once the target profit of USD 10 has been reached.

We see the following line in the log:

2022.01.03 02:31:00   CVirtualCloseManager::CheckLimits | CLOSE PROFIT Profit = 12.94 | OverallProfit = 10.54 (10.00)

The closing manager was triggered when the total profit (OverallProfit = 10.54) relative to the initial base balance of USD 10,000 exceeded USD 10. Due to the testing mode only at the beginning of each minute bar (1 minute OHLC), closing all open positions was extended over two adjacent minutes, so the recorded new base level turned out to be slightly less than USD 10,010. We no longer observe such discrepancies when every tick mode is enabled.

Let's now test the loss limiting closing manager. Let's set a small value for sustaining a loss, for example USD 20, while making the value for taking a profit large, so that we most probably sustain a loss rather than take a profit.

In other parameters, we will disable working only at the opening of the bar, so that when the simulation mode for every tick is enabled, the EA performs all the required actions on every tick, and not just at the beginning of the minute bar:


We will launch testing on a short interval of one day (2022.01.03). By filtering the log messages, we select only the lines displayed when the specified loss of USD 20 is reached:

2022.01.03 17:11:33   CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -33.13 | OverallProfit = -20.06 (-20.00)
2022.01.03 17:30:39   CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -20.51 | OverallProfit = -20.51 (-20.00)
2022.01.03 19:13:31   CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -21.20 | OverallProfit = -20.11 (-20.00)

We can see that this occurred three times during the testing day. In every tick mode, the total profit (Overall Profit), which triggers the closing of positions upon reaching a specified loss, is much closer to the value specified in the parameters. 

Please note that the first log entry above contains the following part:

Profit = -33.13

This is the value of the current profit on open positions (negative profit is a loss). In this case, it differs from the USD -20 value because several positions were initially closed with a profit of about USD 13. Therefore, a loss of USD 20 relative to the initial base account balance was achieved with exactly this value of profit on open positions.

So, initial testing has shown that the developed closing manager can already perform the basic part of its work.


Conclusion

We will take a short break here and continue developing the closing manager further in one of the next parts. Plans for further development of its functionality primarily include adding a profit trailing feature for open positions and the ability to set a breakeven level.

The improvements do not stop there. For example, the closing manager currently checks whether all positions have been closed by simply waiting for the number of open positions to reach zero. But this can also happen if there are open virtual positions, so let's see if we need to use a more reliable verification method here. We may also have to arrange interaction between the risk manager and the closing manager: when closing, the risk manager's status should be updated and vice versa.

Nevertheless, the first version has been made and the next step will not be made from scratch.

Thank you for your attention! See you soon!


Important warning

All results presented in this article and all previous articles in the series are based only on historical testing data and are not a guarantee of any profit in the future. The work within this project is of a research nature. All published results can be used by anyone at their own risk.

Archive contents

#
 Name
Version  Description  Recent changes
  SimpleCandles     Project working folder (should be inside MQL5/Experts)  
SimpleCandles.mq5
1.01
Final EA for parallel operation of several groups of model strategies. The parameters will be taken from the built-in group library.
Part 25
  └ Optimization
  Project optimization EAs folder  
2    CreateProject.mq5 1.02 EA script for creating a project with stages, jobs and optimization tasks.
Part 25
3    Optimization.mq5 1.00
EA for projects auto optimization
 
4    Stage1.mq5 1.02
Trading strategy single instance optimization EA (stage 1)
Part 25
5    Stage2.mq5 1.01
Trading strategies instances group optimization EA (stage 2)
Part 25
6 Stage3.mq5 1.01
The EA that saves a generated standardized group of strategies to an EA database with a given name.  Part 25
  └ Strategies   Project strategies folder
Part 25
7    SimpleCandlesStrategy.mqh
1.01
SimpleCandles trading strategy class
Part 25
  └ Include/Adwizard   Adwizard library folder  
    └ Base
  Base classes other project classes inherit from    
8       Advisor.mqh 1.04 EA base class Part 10
9       Factorable.mqh
1.06
Base class of objects created from a string
Part 28
10       FactorableCreator.mqh
1.00 Class of creators that bind names and static constructors of CFactorable descendant classes Part 24
11       Interface.mqh 1.01
Basic class for visualizing various objects
Part 4
12       Receiver.mqh
1.04  Base class for converting open volumes into market positions
Part 12
13       Strategy.mqh
1.04
Trading strategy base class
Part 10
     └ Database
  Files for handling all types of databases used by project EAs
 
14       Database.mqh 1.12 Class for handling the database Part 25
15       db.adv.schema.sql 1.00
Final EA's database structure Part 22
16       db.cut.schema.sql
1.00 Structure of the truncated optimization database
Part 22
17       db.opt.schema.sql
1.05  Optimization database structure
Part 22
18       Storage.mqh   1.01
Class for handling the Key-Value storage for the final EA in the EA database
Part 23
     └ Experts
  Files with common parts of used EAs of different type
 
19       Expert.mqh  1.24 The library file for the final EA. Group parameters can be taken from the EA database
Part 28
20       Optimization.mqh  1.04 Library file for the EA that manages the launch of optimization tasks
Part 23
21       Stage1.mqh
1.19 Library file for the single instance trading strategy optimization EA (Stage 1)
Part 23
22       Stage2.mqh 1.04 Library file for the EA optimizing a group of trading strategy instances (Stage 2)   Part 23
23       Stage3.mqh
1.04 Library file for the EA saving a generated standardized group of strategies to an EA database with a given name. Part 23
     └ Optimization
  Classes responsible for auto optimization
 
24       OptimizationJob.mqh 1.00 Optimization project stage job class
Part 25
25       OptimizationProject.mqh 1.00 Optimization project class Part 25
26       OptimizationStage.mqh 1.00 Optimization project stage class Part 25
27       OptimizationTask.mqh 1.00 Optimization task class (creation) Part 25
28       Optimizer.mqh
1.03  Class for the project auto optimization manager
Part 22
29       OptimizerTask.mqh
1.03
Optimization task class (conveyor)
Part 22
     └ Strategies    Examples of trading strategies used to demonstrate how the project works
 
24       HistoryStrategy.mqh 
1.00 Class of the trading strategy for replaying the history of deals
Part 16
25       SimpleVolumesStrategy.mqh
1.11
Class of trading strategy using tick volumes
Part 22
     └ Utils
  Auxiliary utilities, macros for code reduction

26        ConsoleDialog.mqh 1.01 Class for displaying text data on a chart Part 28
26       ExpertHistory.mqh 1.00 Class for exporting trade history to file Part 16
27       Macros.mqh 1.07 Useful macros for array operations Part 26
28        MTTester.mqh 
File for working with the strategy tester from the MultiTester library
Part 28
29       NewBarEvent.mqh 1.00  Class for defining a new bar for a specific symbol  Part 8
30       SymbolsMonitor.mqh  1.01 Class for obtaining information about trading instruments (symbols) Part 28
     └ Virtual
  Classes for creating various objects united by the use of a system of virtual trading orders and positions

31       Money.mqh 1.01  Basic money management class
Part 12
32       TesterHandler.mqh  1.07 Optimization event handling class  Part 23
33       VirtualAdvisor.mqh  1.12  Class of the EA handling virtual positions (orders) Part 28
34       VirtualChartOrder.mqh  1.02  Graphical virtual position class Part 28
35       VirtualCloseManager.mqh 1.00 Closing manager class Part 28
36       VirtualHistoryAdvisor.mqh 1.00  Trade history replay EA class  Part 16
37       VirtualInterface.mqh  1.00  EA GUI class  Part 4
38       VirtualOrder.mqh 1.09  Class of virtual orders and positions  Part 22
39       VirtualReceiver.mqh 1.04 Class for converting open volumes to market positions (receiver)  Part 23
40       VirtualRiskManager.mqh  1.06 Risk management class (risk manager)  Part 28
41       VirtualStrategy.mqh 1.09  Class of a trading strategy with virtual positions  Part 23
42       VirtualStrategyGroup.mqh  1.04  Class of trading strategies group(s) Part 28
43       VirtualSymbolReceiver.mqh  1.00 Symbol receiver class  Part 3
  Common/Files   MetaTrader 5 terminal data shared folder  
44 SimpleCandles-27183.test.db.sqlite Final EA database Part 25

The source code is also available in SimpleCandles and Adwizard


How to use open source repositories

As we gradually transition to the new Algo Forge storage, we are still working on the best and most convenient way to use it. The MetaEditor restriction to using a single repository corresponding to the MQL5 root folder is not particularly convenient. Other repositories will only be available as subfolders of the Shared Projects folder, which makes things a bit more manageable, but not enough to warrant using this approach right away.

Moreover, during the transition, the only main repository was created on behalf of the superadmin twice, and the second time, for some reason, its creation destroyed other additional repositories previously created by the user. Fortunately, having a local copy of the repository allows us to re-upload it to the server, but this kind of forced action is not particularly desirable. Therefore, for now we will wait for further development of the MetaEditor functionality for handling repositories without using them directly.

There is nothing complicated about this: while continuing to work in MetaEditor, we will simply move all storage-related operations to external applications for now.

For example, we can get a copy of all the files containing the code for this article on our local computer by running the following script in the console, after first setting a folder within the MQL5 folder of the desired MetaTrader terminal as the current one:

# Create a project folder
mkdir SimpleCandles

# Go to the project folder
cd SimpleCandles

# Clone the project repository into the current folder
git clone https://forge.mql5.io/antekov/SimpleCandles.git .

# Switch the repository to the desired branch (for this article - "article-17608-close-manager")
git checkout article-17608-close-manager

# Make sure we have switched to the branch
git status

# Create a folder for the library part
mkdir Include

# Move on to it
cd Include

# Clone the Adwizard repository into the library folder
git clone https://forge.mql5.io/antekov/Adwizard.git 

# Go to the created folder
cd Adwizard

# Switch the repository to the desired branch (for this article - "article-17608-close-manager")
git checkout article-17608-close-manager

# Make sure we have switched to the branch
git status

# Go up two levels to the original project folder
cd ./../..

The only thing missing from these repositories is a file with the database of the final EA, since the code in the repository allows us to obtain it by performing automatic optimization. If necessary, this file can be taken from the archive to the article and placed in the common terminal folder in the Files folder.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/17608

Attached files |
SimpleCandles.zip (483.1 KB)
Features of Custom Indicators Creation Features of Custom Indicators Creation
Creation of Custom Indicators in the MetaTrader trading system has a number of features.
MQL5 Wizard Techniques you should know (Part 93): Using Suffix Automation and an Auto Encoder in a Custom Money Management Class MQL5 Wizard Techniques you should know (Part 93): Using Suffix Automation and an Auto Encoder in a Custom Money Management Class
For this article we switch to a custom MQL5 Wizard class implementation that explores Money Management. We are labelling our custom class ‘CMoneySuffixAE’ that we derive by combining the Suffix Automaton algorithm with an Autoencoder neural network. As always, this formulation is testable with MQL5 Wizard Assembled Expert Advisors that can be tuned with various entry signals and trailing stop approaches.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Price Action Analysis Toolkit Development (Part 71): Weekend Gap Structure Mapping in MQL5 Price Action Analysis Toolkit Development (Part 71): Weekend Gap Structure Mapping in MQL5
The article delivers an object-based MQL5 implementation that detects weekend gaps from time discontinuities and renders them directly on the chart. It manages graphical objects, tracks state transitions (fresh, partial, reaction, filled), and preserves completed gaps as historical zones. The result is a reproducible framework for monitoring how price revisits and fills weekend gap structures.