Русский 中文 Español Português
preview
Developing a multi-currency Expert Advisor (Part 23): Putting in order the conveyor of automatic project optimization stages (II)

Developing a multi-currency Expert Advisor (Part 23): Putting in order the conveyor of automatic project optimization stages (II)

MetaTrader 5Tester |
1 577 0
Yuriy Bykov
Yuriy Bykov

Introduction

One of the previous parts of the series has already been devoted to this issue. It allowed us to choose a more correct vector for the further development of the project. Instead of manually creating all the tasks performed within a single project in the optimization database, we now have a more convenient tool — an optimization project creation script. More precisely, it is more of a template that can be easily adapted to create projects for optimizing various trading strategies.

In this article, we have a fully functional solution that allows us to launch created optimization projects with the export of selected new groups of trading strategies directly to a new database. This database was named the EA database, so that it can be distinguished from the optimization database, which was used previously (in full and abbreviated versions). The EA database can be used by any final EA running on a trading account by updating the settings of the trading systems used without recompilation. We have yet to test the correctness of this mechanism. However, it can already be said that this approach simplifies the operation of the conveyor as a whole. Before this, we had planned to add several more stages to the three existing conveyor stages:

  • Exporting the library to get ExportedGroupsLibrary.mqh in the data folder (Stage4).
  • Copying the file to the working folder (Stage5, Python or DLL) or modifying the previous stage to export directly to the working folder.
  • Compiling the final EA (Stage6, Python).
  • Launching the terminal with the new version of the final EA.

Now these stages are no longer necessary. At the same time, we also got rid of a major drawback: it would be impossible to check the correct operation of such an auto update mechanism in the strategy tester. The presence of a recompilation stage is incompatible with the fact that during one tester pass the compiled code of the EA cannot change.

But most importantly, we will try to take an important step towards simplifying the use of all written code for optimizing arbitrary strategies and will try to describe a step-by-step algorithm of actions.


Mapping out the path

Let's start by implementing long-overdue changes to the project file structure. Currently, they are located in a single folder, which, on the one hand, simplifies the transfer and use of all the code in a new project, but on the other hand, in the process of continuous development, we end up with several almost identical working project folders for different trading strategies, each of which needs to be updated separately. Therefore, we will divide all the code into a library part, which will be the same for all projects, and a project part, which will contain code specific to different projects.

Next, we implement a check to ensure that if new strategy groups appear during the final EA's operation, it will be able to correctly load the updated parameters and continue working. Let's start, as usual, with modeling the desired behavior in an EA running in the strategy tester. If the results there are satisfactory, then it will be possible to move on to using it in final EAs that no longer work in the tester.

What do we need for this? In the previous section, we have not implemented saving information about the end dates of the optimization interval and the completion of the optimization conveyor execution in the EA database. Now we need this information, otherwise, when running the tester, the final EA will not be able to determine whether this group of strategies has already been formed on a specific simulated date or not.

The final EA will also need to be modified so that it can perform its own re-initialization when new strategy groups appear in its EA database. Currently, it simply does not have such functionality. Here, it would be useful to have at least some information about the current group of trading strategies, so that one could clearly see the successful transition from one group to another. It would be more convenient to see this information directly on the chart, on which the EA is running, but you can, of course, use the regular output to the terminal log for this purpose.

Finally, we will provide a description of the general algorithm for working with the tools developed to date.

Let's get started!


Transition to a different file structure

In all previous parts, development was carried out in one working project folder. Existing files in it were modified, and new ones were added from time to time. Sometimes files that were no longer relevant to the project were deleted or "forgotten". This approach was justified when working with a single possible trading strategy (for example, the SimpleVolumes strategy used as an example in the articles). But when extending the auto optimization mechanism to other trading strategies, it was necessary to create a complete copy of the project working folder and then change only a small portion of the files in it.

As the number of trading strategies connected in this way grew (and as the number of different working folders grew), keeping the code in all of them up to date became more and more labor-intensive. Therefore, we will move the part of the code that should be the same for all working folders into a separate library folder located in MQL5/Include. The name of the shared folder for library files has been set to Advisor, and to prevent it from conflicting with a possible existing folder with the same name, a unique component was added to it. Now the library files will be located in MQL5/Include/antekov/Advisor/.

Having transferred all the files into it, we began further systematization. It was decided to distribute all files into subfolders that reflect some common purpose for the files located within them. This required some work with the directives for including some files into others, since their relative locations had changed. But in the end, we managed to achieve successful compilation of both the EAs and all the library files separately.

This is what the file structure looked like after the modification:

Fig. 1. Advisor library file structure

As you can see, we have selected several groups of files, placing them in the following subfolders:

  • Base. Base classes other project classes inherit from.
  • Database. Files for handling all types of databases used by project EAs.
  • Experts. Files with common parts of used EAs of different type.
  • Optimization. Classes responsible for auto optimization.
  • Strategies. Examples of trading strategies used to demonstrate how the project works.
  • Utils. Auxiliary utilities, macros for code reduction.
  • Virtual. Classes for creating various objects united by the use of a system of virtual trading orders and positions.

Of all the subfolders listed above, only one stands out and deserves special mention. This is the Experts folder. If we compare the composition of the files in Fig. 1 with the composition of files from the previous part, we can notice that only this folder contains files that were not there before. At first, you might think that we simply partially renamed the files of the used EAs and moved them here, but note that their extension is not *.mq5, but *.mqh. But before we look at them in more detail, let's look at what will remain in the folder of a separate project for a certain trading strategy.

We will make a separate folder inside MQL5/Experts/ and name it whatever we want. It will contain files of all used EAs:

Fig. 2. File structure of a project using the EA library

The purpose of these files is as follows:

  • CreateProject.mq5— EA for creating an auto optimization project in the optimization database. Each project in the database is presented as three stages, consisting of one or more tasks. Each job consists of one or more optimization tasks performed by stage EAs.

  • HistoryReceiverExpert.mq5— EA for reproducing previously saved transaction history. We have not used it for a long time, as it was created only for the purpose of checking the repeatability of results when changing brokers. It is not required for auto optimization to work, so you can safely remove it if you wish.

  • Optimization.mq5— EA that runs tasks from an auto optimization project. We called the process of sequentially performing such tasks the auto optimization conveyor.

  • SimpleVolumes.mq5 — a final EA that combines many single instances of trading strategies of the SimpleVolumes type. It will take information about the composition of these specimens from the EA's database. This information, in turn, will be placed into the EA's database by the third stage EA of the auto optimization conveyor.

  • Stage1.mq5— EA of the first stage of the auto optimization conveyor. It optimizes a single instance of a trading strategy.

  • Stage2.mq5— EA of the second stage of the auto optimization conveyor. During the optimization, it selects from many good single instances obtained in the first stage a small group of instances (usually 8 or 16) that, when working together, shows the best results in terms of standardized profit.

  • Stage3.mq5 — EA of the third stage of the auto optimization conveyor. It combines all groups obtained in the second stage, normalizes position sizes, and saves the resulting group with a scaling factor in the EA database specified in the settings.

Among the listed EA files, only CreateProject.mq5 saved its contents. All other EAs contain basically only a command to include the corresponding mqh file from the Advisor library located in MQL5/Include/antekov/Advisor/Experts. Notably, SimpleVolumes.mq5 and HistoryReceiverExpert.mq5 use the same Expert.mqh include file. As practice has shown, we do not need to write different code for second- and third-stage EAs for different trading strategies. For the first-stage EA, the only difference is the different inputs and the creation of the required initialization string from their values. Everything else will be the same too.

So, only CreateProject.mq5 will require more significant modification when switching to a project with a different trading strategy. In the future, we will try to extract the common part from it as well.

Let's now look at what changes need to be made to the library files to implement auto update of the final EA.


Completion dates

Let us recall that in the previous part we launched four almost identical optimization projects. The difference between them was in the end date of the optimization interval of single instances of trading strategies. The composition of trading instruments, timeframes and other parameters did not differ. As a result, the following entries appeared in the strategy_groups table of the EA database:

Since we added the end date of the optimization interval to the group name, this information allows us to understand which group corresponds to which end date. But the EA should be able to understand this as well. We specifically created two fields in this table to store these dates, which need to be filled in when creating records, and even prepared a place in the code where this needs to be done:

//+------------------------------------------------------------------+
//| Export an array of strategies to the specified EA database       |
//| as a new group of strategies                                     |
//+------------------------------------------------------------------+
void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) {
// Connect to the required EA database
   if(DB::Connect(p_advFileName, DB_TYPE_ADV)) {
      string fromDate = "";   // Start date of the optimization interval
      string toDate = "";     // End date of the optimization interval

      // Create an entry for a new strategy group
      string query = StringFormat("INSERT INTO strategy_groups VALUES(NULL, '%s', '%s', '%s', NULL)"
                                  " RETURNING rowid;",
                                  p_groupName, fromDate, toDate);
      ulong groupId = DB::Insert(query);

      // ...
   }
}

The start and end dates of the optimization interval are stored in the stages table of the database. Therefore, we can get them from there by executing the corresponding SQL query at this point in the code. But this approach turned out to be less than optimal, because we had already implemented the code that executed an SQL query to obtain information about these dates, among other things. This happened in the auto optimization EA. It was supposed to receive information from the database about the next optimization task. This information must include the dates we need. Let's take advantage of this. 

We will need to create an object of the COptimizerTask class by passing the name of the optimization database to its constructor. It is present in the CTesterHandler::s_fileName static class field. Another static field CTesterHandler::s_idTask features the current optimization task ID. We will pass it to the method for loading optimization problem data. After this, the required dates can be obtained from the corresponding fields of the m_params structure of the task object.

//+------------------------------------------------------------------+
//| Export an array of strategies to the specified EA database       |
//| as a new group of strategies                                     |
//+------------------------------------------------------------------+
void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) {
// Create an optimization task object
   COptimizerTask task(s_fileName);
// Load the data of the current optimization task into it
   task.Load(CTesterHandler::s_idTask);

// Connect to the required EA database
   if(DB::Connect(p_advFileName, DB_TYPE_ADV)) {
      string fromDate = task.m_params.from_date; // Start date of the optimization interval
      string toDate = task.m_params.to_date;     // End date of the optimization interval

      // Create an entry for a new strategy group
      string query = StringFormat("INSERT INTO strategy_groups VALUES(NULL, '%s', '%s', '%s', NULL)"
                                  " RETURNING rowid;",
                                  p_groupName, fromDate, toDate);
      ulong groupId = DB::Insert(query);

      // ...
   }
}

Let's save the changes made to the TesterHandler.mqh file in the Virtual subfolder of the library.

Let's recreate several projects using the CreateProject.ex5 EA. To speed up the process, we will make the optimization interval small (4 months). We will move the start and end dates of the optimization interval for each subsequent project forward by one month. As a result, we get the following:

As you can see, each group in the EA database now contains the end date of the optimization interval. Note that this date is taken from the interval for the third stage task. For everything to be correct, the dates of the intervals of all three stages should be the same. This is provided in the project creation EA.


Modifying the final EA

Before we begin implementing auto updates to the strategy group used in the final EA, let's look at the changes caused by the transition to the new project file structure. As already noted, the final EA is now presented in the form of two files. The main file is located in the project folder and is called SimpleVolumes.mq5. Here is its full code:

//+------------------------------------------------------------------+
//|                                                SimpleVolumes.mq5 |
//|                                 Copyright 2024-2025, Yuriy Bykov |
//|                            https://www.mql5.com/en/users/antekov |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024-2025, Yuriy Bykov"
#property link      "https://www.mql5.com/en/articles/16913"
#property description "The final EA, combining multiple instances of trading strategies:"
#property description " "
#property description "Strategies open a market or pending order when,"
#property description "the candle tick volume exceeds the average volume in the direction of the current candle."
#property description "If orders have not yet turned into positions, they are deleted at expiration time."
#property description "Open positions are closed only by SL or TP."
#property version "1.22"

#include <antekov/Advisor/Experts/Expert.mqh>

//+------------------------------------------------------------------+

In this code, there is essentially only one command to import the library file of the final EA. This is exactly the case when what is not is more important than what is. Let's compare it with the code of the second HistoryReceiverExpert.mq5 EA:

//+------------------------------------------------------------------+
//|                                        HistoryReceiverExpert.mq5 |
//|                                 Copyright 2024-2025, Yuriy Bykov |
//|                            https://www.mql5.com/en/users/antekov |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024-2025, Yuriy Bykov"
#property link      "https://www.mql5.com/en/articles/16913"
#property description "The EA opens a market or pending order when,"
#property description "the candle tick volume exceeds the average volume in the direction of the current candle."
#property description "If orders have not yet turned into positions, they are deleted at expiration time."
#property description "Open positions are closed only by SL or TP."
#property version "1.01"

//+------------------------------------------------------------------+
//| Declare the name of the final EA.                                |
//| During the compilation, the function of generating               |
//| the initialization string from the current file will be used     |
//+------------------------------------------------------------------+
#define  __NAME__ MQLInfoString(MQL_PROGRAM_NAME)

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Testing the deal history"
input string historyFileName_    = "SimpleVolumesExpert.1.19 [2021.01.01 - 2022.12.30]"
                                   " [10000, 34518, 1294, 3.75].history.csv";    // File with history

//+------------------------------------------------------------------+
//| Function for generating the strategy initialization string       |
//| from the inputs                                                  |
//+------------------------------------------------------------------+
string GetStrategyParams() {
   return StringFormat("class CHistoryStrategy(\"%s\")\n", historyFileName_);
}

#include <antekov/Advisor/Experts/Expert.mqh>

//+------------------------------------------------------------------+

Three blocks that are not in the SimpleVolumes.mq5 file are highlighted in color. Their presence is taken into account in the Experts/Expert.mqh library file of the final EA: if a constant with the name of the final EA is not specified, then a function for generating the initialization string is declared, which will receive it from the EA's database. If the name is specified, then such a function must be declared in the parent file the library file is included to.

// If the constant with the name of the final EA is not specified, then
#ifndef __NAME__
// Set it equal to the name of the EA file 
#define  __NAME__ MQLInfoString(MQL_PROGRAM_NAME)

//+------------------------------------------------------------------+
//| Function for generating the strategy initialization string       |
//| from the default inputs (if no name was specified).              | 
//| Import the initialization string from the EA database            |
//| by the strategy group ID                                         |
//+------------------------------------------------------------------+
string GetStrategyParams() {
// Take the initialization string from the new library for the selected group
// (from the EA database)
   string strategiesParams = CVirtualAdvisor::Import(
                                CVirtualAdvisor::FileName(__NAME__, magic_),
                                groupId_
                             );

// If the strategy group from the library is not specified, then we interrupt the operation
   if(strategiesParams == NULL && useAutoUpdate_) {
      strategiesParams = "";
   }

   return strategiesParams;
}
#endif

Next, in the EA initialization function of the Experts/Expert.mqh library file, one of the possible options for the initialization string generation function is used:

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

// Initialization string with strategy parameter sets
   string strategiesParams = NULL;

// Take the initialization string from the new library for the selected group
// (from the EA database)
   strategiesParams = GetStrategyParams();

// If the strategy group from the library is not specified, then we interrupt the operation
   if(strategiesParams == NULL) {
      return INIT_FAILED;
   }

// ...

// Successful initialization
   return(INIT_SUCCEEDED);
}

Thus, we can, if desired, create a final EA that will not use loading the initialization string from the EA database. To do this, declare the __NAME__ constant and the signature function in the *.mq5 file. 

string GetStrategyParams()

Now we can proceed with auto update.


Auto update

The first option for implementing auto updates may not be the most pretty, but it will do for a start. The main thing is that it works. The required changes to the final EA library file consisted of two parts.

First, we slightly changed the composition of the inputs, removing the enumeration with the group number from the old library, replacing it with the group ID from the EA database, and adding a logical parameter that enables auto update:

//+------------------------------------------------------------------+
//| 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

// ...

Second, we added the following code to the new tick handling function, after the highlighted string:

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

// If both are executed at the same time:
   if(groupId_ == 0                       // - no specific group ID specified
         && useAutoUpdate_                // - auto update enabled
         && IsNewBar(Symbol(), PERIOD_D1) // - a new day has arrived
         && expert.CheckUpdate()          // - a new group of strategies discovered
     ) {
      // Save the current EA state
      expert.Save();

      // Delete the EA object
      delete expert;

      // Call the EA initialization function to load a new strategy group 
      OnInit();
   }
}

Thus, the auto update will only work if groupId_=0 and useAutoUpdate_=true. If we specify a non-zero group ID, then this group will be used throughout the entire test interval. In this case, there is no limitation on when the final EA can make trades.

When auto update is enabled, the resulting EA will only execute trades after the end date of the optimization interval of the earliest group existing in the EA database. This mechanism will be implemented in the new method of the CVirtualAdvisor::CheckUpdate() class:

//+------------------------------------------------------------------+
//| Check the presence of a new strategy group in the EA database    |
//+------------------------------------------------------------------+
bool CVirtualAdvisor::CheckUpdate() {
// Request to get strategies of a given group or the last group
   string query = StringFormat("SELECT MAX(id_group) FROM strategy_groups"
                               " WHERE to_date <= '%s'",
                               TimeToString(TimeCurrent(), TIME_DATE));

// Open the EA database
   if(DB::Connect(m_fileName, DB_TYPE_ADV)) {
// Execute the request
      int request = DatabasePrepare(DB::Id(), query);

      // If there is no error
      if(request != INVALID_HANDLE) {
         // Data structure for reading a single string of a query result 
         struct Row {
            int      groupId;
         } row;

         // Read data from the first result string
         while(DatabaseReadBind(request, row)) {
            // Remember the strategy group ID
            // in the static property of the EA class
            return s_groupId < row.groupId;
         }
      } else {
         // Report an error if necessary
         PrintFormat(__FUNCTION__" | ERROR: request \n%s\nfailed with code %d", query, GetLastError());
      }

      // Close the EA database
      DB::Close();
   }

   return false;
}

In this method, we obtain from the EA database the largest group ID, for which the optimization interval end date is no greater than the current date. Thus, even if a record of an entry is already physically present in the database, but the time of its appearance (>= the end time of the optimization interval) is located in the future relative to the current simulated time of the strategy tester, then it will not be obtained as a result of the SQL query used.

When initialized, the EA stores the ID of the loaded strategy group in the static field of the CVirtualAdvisor::s_groupId class. Therefore, we can detect the appearance of a new group by comparing the ID just obtained from the EA database with the ID of the previously loaded group. If the first one is larger, then a new group has appeared. 

In the method for obtaining the initialization string from the EA database, which already directly interacts with the database, we use the same condition on the end date of the group test interval with auto update enabled:

//+------------------------------------------------------------------+
//| Get the strategy group initialization string                     |
//| from the EA database with the given ID                           |
//+------------------------------------------------------------------+
string CVirtualAdvisor::Import(string p_fileName, int p_groupId = 0) {
   string params[];   // Array for strategy initialization strings

// Request to get strategies of a given group or the last group
   string query = StringFormat("SELECT id_group, params "
                               "  FROM strategies"
                               " WHERE id_group = %s;",
                               (p_groupId > 0 ? (string) p_groupId 
                                : "(SELECT MAX(id_group) FROM strategy_groups WHERE to_date <= '"
                                + TimeToString(TimeCurrent(), TIME_DATE) +
                                "')"));

// Open the EA database
   if(DB::Connect(p_fileName, DB_TYPE_ADV)) {
      // ...
   }

// Strategy group initialization string
   string groupParams = NULL;

// Total number of strategies in the group
   int totalStrategies = ArraySize(params);

// If there are strategies, then
   if(totalStrategies > 0) {
      // Concatenate their initialization strings with commas
      JOIN(params, groupParams, ",");

      // Create a strategy group initialization string
      groupParams = StringFormat("class CVirtualStrategyGroup([%s], %.5f)",
                                 groupParams,
                                 totalStrategies);
   }

// Return the strategy group initialization string
   return groupParams;
}

The last thing worth mentioning here is the addition of a method for loading the EA state after switching to a new group of strategies. The point is that new strategies from the new group will not find their settings in the EA database, since the Save() method has not yet been called for them, and report the download error. But this error should be ignored.

Another addition is related to the need to close virtual positions of old strategies immediately after loading new ones. To do this, it is necessary and sufficient to create symbol receiver objects for all symbols used by the old strategies. These objects will correct the volumes of open positions on the next tick. If this is not done, then the volume correction will only occur as virtual positions are opened with new strategies. If new strategies stop using one of the previously used symbols, then positions on it will remain open.

//+------------------------------------------------------------------+
//| Load status                                                      |
//+------------------------------------------------------------------+
bool CVirtualAdvisor::Load() {
   bool res = true;
   ulong groupId = 0;

// Load status if:
   if(true
// file exists
         && FileIsExist(m_fileName, FILE_COMMON)
// currently, there is no optimization
         && !MQLInfoInteger(MQL_OPTIMIZATION)
// and there is no testing at the moment or there is a visual test at the moment
         && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))
     ) {
      // If the connection to the EA database is established
      if(CStorage::Connect(m_fileName)) {
         // If the last modified time is loaded and less than the current time
         if(CStorage::Get("CVirtualReceiver::s_lastChangeTime", m_lastSaveTime)
               && m_lastSaveTime <= TimeCurrent()) {

            PrintFormat(__FUNCTION__" | LAST SAVE at %s",
                        TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS));

            // If the saved strategy group ID is loaded
            if(CStorage::Get("CVirtualAdvisor::s_groupId", groupId)) {
               // Load all strategies ignoring possible errors
               FOREACH(m_strategies, {
                  res &= ((CVirtualStrategy*) m_strategies[i]).Load();
               });

               if(groupId != s_groupId) {
                  // Actions when launching an EA with a new group of strategies.
                  PrintFormat(__FUNCTION__" | UPDATE Group ID: %I64u -> %I64u", s_groupId, groupId);
                  
                  // Reset a possible error flag when loading strategies
                  res = true;
                  
                  string symbols[]; // Array for symbol names
                  
                  // Get the list of all symbols used by the previous group
                  CStorage::GetSymbols(symbols);
                  
                  // For all symbols, create a symbolic receiver.
                  // This is necessary for the correct closing of virtual positions 
                  // of the old strategy group immediately after loading the new one
                  FOREACH(symbols, m_receiver[symbols[i]]);
               }

               // ...
            }
         } else {
            // If the last modified time is not found or is in the future,
            // then start work from scratch
            PrintFormat(__FUNCTION__" | NO LAST SAVE [%s] - Clear Storage",
                        TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS));
            CStorage::Clear();
            m_lastSaveTime = 0;
         }

         // Close the connection
         CStorage::Close();
      }
   }

   return res;
}

After this, you can begin checking the functionality of auto loading new groups. Unfortunately, success was not achieved immediately, as it was necessary to correct the errors that had appeared. For example, it turned out that the EA falls into an infinite loop if the EA database suddenly turns out to be empty. Or a test does not start if the test start date is set even one day before the date of the first group of strategies appearing. Eventually, all the errors found were corrected.

Let's now look at the algorithm for using the created library as a whole and the results of the auto update check.


Algorithm for using the Advisor library

This time we will describe the algorithm for using the Advisor library for auto optimization of the SimpleVolumes model strategy, which is part of the library and launch in the tester of the final EA.

  1. Set the library to Include (Fig. 1).

  2. Create a project folder and transfer the EA files into it (Fig. 2).

  3. We make changes to the files of the first stage EA and the file of the project creation EA. When using a model strategy, no changes are required as they are up to date. Compile all the EAs in the project folder.

  4. Launch the project creation EA, setting the desired parameter values (you can leave them at default).
    The output should be an optimization database filled with tasks for the project in the shared terminal data folder. You can specify anything in the project description, such as the start and end dates of the optimization interval. For now, this is just creating a task to launch the conveyor. The launch will be performed by another EA.

  5. If desired, you can repeat the previous step any number of times, changing the parameters. For example, you can create several projects at once for auto optimization at different time intervals.

  6. Launch the optimization EA and wait. The time required to complete all projects added to the optimization database depends on their number, as well as on the number of symbols and timeframes in the projects, on the duration of the test/optimization time interval, and on the complexity of the implemented trading strategy. This time also depends on the number of test agents involved in the optimization.
    The output is a file with the EA database in a shared folder. Its name is taken from the settings.
    The EA database will contain saved strategy groups.

  7. Let's launch the final EA. It is important that its name and magic number match those specified during optimization. Otherwise, it will create an empty EA database and wait for something to appear in it. If the final EA finds its base, it tries to load the strategy group with the specified ID or the last added group if the ID is 0. If the auto update option is set, the EA will check once a day whether a new group of strategies available by date has appeared in the EA database. If it appears, it replaces the previously used group.


Testing auto updates

So, after the optimization of all projects added to the database with different completion dates is finished, we will have an EA database with several groups of strategies of different compositions. They also differ in the end date of the optimization interval. And we have a final EA that can, as testing progresses, take a new group of strategies from the database when the simulated current time exceeds the end time of the optimization interval for this new group.

Please note that saving and loading EA parameters only works when the EA is launched on a chart or in visual testing mode. Therefore, to check auto updating in the tester, it is necessary to use visual mode.

Let's run the final EA specifying a certain group groupId_=1. In this case, regardless of the useAutoUpdate_ parameter value, only this group will be used. It was optimized for the interval 2022.09.01 — 2023.01.01, so we will launch the tester starting from the date 2022.09.01 (the main period), and from the date of 2023.01.01 we will start the forward period up to 2024.01.01.

Main period 2022.09.01 — 2023.01.01

Forward period 2023.01.01 — 2024.01.01

Fig. 3. Results of the final EA with parameters groupId_=1 on the interval of 2022.09.01 — 2024.01.01

As we can see, the EA shows good results on the main period, which coincides with the optimization interval, but on the forward period the picture is completely different. There is a much larger drawdown and no significant growth in the equity curve. Well, of course, I would like to see something more beautiful, but this result is not unexpected. After all, we used a very small interval during optimization, few symbols and few timeframes. Therefore, we found that in a known section the parameters were selected too well for this particular short section. The EA was unable to prove itself in such a way in an unknown area.

Let's see if a similar pattern is observed for another group of trading strategies out of interest. Let's run the resulting EA specifying the groupId_=3. This group was optimized for the interval 2022.11.01 — 2023.03.01, so we will launch the tester starting from 2022.11.01 (main period), and from the date of 2023.03.01 we will also start the forward period up to 2024.01.01.

Main period 2022.11.01 — 2023.03.01

Forward period 2023.03.01 — 2024.01.01

Fig. 4. Results of the final EA with parameters groupId_=3 on the interval of 2022.11.01 — 2024.01.01

Yes, the results were the same as for the first group. For both groups, a large drawdown is observed in May-June. You might think that this is an unfortunate period for the strategy. But if we take a group that was optimized on this range, we will see that here too the same parameters of the strategies from the group were successfully selected. It shows the same smooth and beautiful growth of the chart. 

If we run the final EA starting from the date of 2023.01.01 with the parameters groupId_=0, useAutoUpdate=false, then we will get the same result as in the forward period for the first group, since in this case the first group will be loaded (it already "exists" as of the start date of the passage). However, due to the auto update being disabled, it will not be replaced by groups with a later appearance time.

And finally, let's run the final EA on the interval 2023.01.01 — 2024.01.01 with auto updating, specifying the groupId_=0useAutoUpdate=true.

Fig. 5. Results of the final EA with the parameters groupId_=0, useAutoUpdate=true on the interval of 2023.01.01 — 2024.01.01

The trading results themselves are not of interest, since in order to reduce the time for auto optimization, a very short period of optimization was used (only 4 months). Now we just wanted to demonstrate the functionality of the mechanism for automatically updating the strategy groups used. And judging by the log entries and the automatic closing of positions at the beginning of each month, this works as intended: 

SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualReceiver::Get | OK, Strategy orders: 3 from 144 total
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 2.44, total strategies = 1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 48.00, total groups = 48
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 1.00, total groups = 1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualRiskManager::UpdateBaseLevels | DAILY UPDATE: Balance = 0.00 | Equity = 0.00 | Level = 0.00 | depoPart = 0.10 = 0.10 * 1.00 * 1.00
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualAdvisor::Load | LAST SAVE at 2023.01.31 20:32:00
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualAdvisor::Load | UPDATE Group ID: 1 -> 2
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:59   CSymbolNewBarEvent::IsNewBar | Register new event handler for GBPUSD PERIOD_D1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:01:00   CSymbolNewBarEvent::IsNewBar | Register new event handler for EURUSD PERIOD_D1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:01:00   CSymbolNewBarEvent::IsNewBar | Register new event handler for EURGBP PERIOD_D1


Conclusion

Let's sum up some results. We have finally put all the reusable code in the Include folder in the form of the Advisor library. Now it will be possible to connect it to projects working with different trading strategies. Subsequent library updates will be automatically distributed to all projects where it is used.

It is becoming easier and easier to create and launch an auto optimization project. We have now simplified the mechanism for implementing optimization results into the final EA. Simply specify the desired EA database name in the settings for the third stage of optimization, and the results will be stored where the final EA can retrieve them.

However, there are still quite a few points that require attention. One of them is to develop an algorithm for adding a new type of trading strategy and including groups containing different types of trading strategies in the final EA. But more about that next time.

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
  MQL5/Experts/Article.16913   Project working folder  
1 CreateProject.mq5 1.01
EA script for creating a project with stages, jobs and optimization tasks.
Part 23
2 HistoryReceiverExpert.mq5 1.01
EA for replaying the history of deals with the risk manager
Part 23
3 Optimization.mq5
1.00 EA for projects auto optimization  Part 23
4 SimpleVolumesExpert.mq5
1.22 Final EA for parallel operation of several groups of model strategies. The parameters will be taken from the built-in group library.
Part 23
5 Stage1.mq5 1.22  Trading strategy single instance optimization EA (stage 1)
Part 23
6 Stage2.mq5
1.00 Trading strategies instances group optimization EA (stage 2)
Part 23
Stage3.mq5
1.00 The EA that saves a generated standardized group of strategies to an EA database with a given name.
Part 23
  MQL5/Include/antekov/Advisor/Base
  Base classes other project classes inherit from  
8 Advisor.mqh 1.04 EA base class Part 10
9 Factorable.mqh
1.05
Base class of objects created from a string
Part 22
10 Interface.mqh 1.01
Basic class for visualizing various objects
Part 4
11 Receiver.mqh
1.04  Base class for converting open volumes into market positions
Part 12
12 Strategy.mqh
1.04
Trading strategy base class
Part 10
  MQL5/Include/antekov/Advisor/Database
  Files for handling all types of databases used by project EAs  
13 Database.mqh 1.10 Class for handling the database Part 22
14 db.adv.schema.sql 1.00
Final EA's database structure Part 22
15 db.cut.schema.sql
1.00 Structure of the truncated optimization database
Part 22
16 db.opt.schema.sql
1.05  Optimization database structure
Part 22
17 Storage.mqh   1.01
Class for handling the Key-Value storage for the final EA in the EA database
Part 23
  MQL5/Include/antekov/Advisor/Experts
  Files with common parts of used EAs of different type
 
18 Expert.mqh  1.22 The library file for the final EA. Group parameters can be taken from the EA database
Part 23
19 Optimization.mqh  1.04 Library file for the EA that manages the launch of optimization tasks
Part 23
20 Stage1.mqh
1.19 Library file for the single instance trading strategy optimization EA (Stage 1)
Part 23
21 Stage2.mqh 1.04 Library file for the EA optimizing a group of trading strategy instances (Stage 2)   Part 23
22 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
  MQL5/Include/antekov/Advisor/Optimization
  Classes responsible for auto optimization  
23 Optimizer.mqh
1.03  Class for the project auto optimization manager
Part 22
24 OptimizerTask.mqh
1.03
Optimization task class
Part 22
  MQL5/Include/antekov/Advisor/Strategies    Examples of trading strategies used to demonstrate how the project works
 
25 HistoryStrategy.mqh 
1.00 Class of the trading strategy for replaying the history of deals
Part 16
26 SimpleVolumesStrategy.mqh
1.11
Class of trading strategy using tick volumes
Part 22
  MQL5/Include/antekov/Advisor/Utils
  Auxiliary utilities, macros for code reduction
 
27 ExpertHistory.mqh 1.00 Class for exporting trade history to file Part 16
28 Macros.mqh 1.05 Useful macros for array operations Part 22
29 NewBarEvent.mqh 1.00  Class for defining a new bar for a specific symbol  Part 8
30 SymbolsMonitor.mqh  1.00 Class for obtaining information about trading instruments (symbols) Part 21
  MQL5/Include/antekov/Advisor/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.10  Class of the EA handling virtual positions (orders) Part 23
34 VirtualChartOrder.mqh  1.01  Graphical virtual position class Part 18
35 VirtualFactory.mqh 1.04  Object factory class  Part 16
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.04 Risk management class (risk manager)  Part 23
41 VirtualStrategy.mqh 1.09  Class of a trading strategy with virtual positions  Part 23
42 VirtualStrategyGroup.mqh  1.02  Class of trading strategies group(s) Part 23
43 VirtualSymbolReceiver.mqh  1.00 Symbol receiver class  Part 3
  MQL5/Common/Files   Shared terminal folder   
44 SimpleVolumes-27183.test.db.sqlite EA database with added strategy groups  

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

Attached files |
MQL5.zip (421.44 KB)
Market Positioning Codex for VGT with Kendall's Tau and Distance Correlation Market Positioning Codex for VGT with Kendall's Tau and Distance Correlation
In this article, we look to explore how a complimentary indicator pairing can be used to analyze the recent 5-year history of Vanguard Information Technology Index Fund ETF. By considering two options of algorithms, Kendall’s Tau and Distance-Correlation, we look to select not just an ideal indicator pair for trading the VGT, but also suitable signal-pattern pairings of these two indicators.
Market Simulation (Part 07): Sockets (I) Market Simulation (Part 07): Sockets (I)
Sockets. Do you know what they are for or how to use them in MetaTrader 5? If the answer is no, let's start by studying them. In today's article, we'll cover the basics. Since there are several ways to do the same thing, and we are always interested in the result, I want to show that there is indeed a simple way to transfer data from MetaTrader 5 to other programs, such as Excel. However, the main idea is not to transfer data from MetaTrader 5 to Excel, but the opposite, that is, to transfer data from Excel or any other program to MetaTrader 5.
MetaTrader 5 Machine Learning Blueprint (Part 6): Engineering a Production-Grade Caching System MetaTrader 5 Machine Learning Blueprint (Part 6): Engineering a Production-Grade Caching System
Tired of watching progress bars instead of testing trading strategies? Traditional caching fails financial ML, leaving you with lost computations and frustrating restarts. We've engineered a sophisticated caching architecture that understands the unique challenges of financial data—temporal dependencies, complex data structures, and the constant threat of look-ahead bias. Our three-layer system delivers dramatic speed improvements while automatically invalidating stale results and preventing costly data leaks. Stop waiting for computations and start iterating at the pace the markets demand.
Neural Networks in Trading: Hierarchical Dual-Tower Transformer (Final Part) Neural Networks in Trading: Hierarchical Dual-Tower Transformer (Final Part)
We continue to build the Hidformer hierarchical dual-tower transformer model designed for analyzing and forecasting complex multivariate time series. In this article, we will bring the work we started earlier to its logical conclusion — we will test the model on real historical data.