Desenvolvendo um EA multimoeda (Parte 23): Colocando em ordem o pipeline de etapas da otimização automática de projetos (II)
Introdução
Uma das partes anteriores da série já foi dedicada a essa questão. Ela permitiu definir um vetor mais adequado para o desenvolvimento do projeto. Em vez de executar manualmente todas as tarefas dentro de um projeto no banco de dados de otimização, agora temos uma ferramenta mais conveniente: um script de criação de projeto de otimização. Na verdade, trata-se de um modelo que pode ser facilmente adaptado para a criação de projetos de otimização de diferentes estratégias de trading.
Nesta parte alcançamos uma solução totalmente funcional, que possibilita a execução dos projetos de otimização criados, com a exportação dos novos grupos de estratégias de trading diretamente para um novo banco de dados. Esse banco foi chamado de banco de dados do especialista, para diferenciá-lo do banco de dados de otimização, usado anteriormente (nas versões completa e reduzida). O banco de dados do especialista pode ser utilizado por algum EA em execução em uma conta de trading, atualizando as configurações das estratégias utilizadas sem a necessidade de recompilação. Ainda precisamos verificar se esse mecanismo está correto. No entanto, já podemos afirmar que essa abordagem simplifica o funcionamento do pipeline como um todo. Afinal, até então, planejávamos adicionar mais alguns estágios aos três já existentes do pipeline:
- Exportar a biblioteca para gerar ExportedGroupsLibrary.mqh na pasta de dados (Stage4).
- Copiar o arquivo para a pasta de trabalho (Stage5, Python ou DLL) ou adaptar a etapa anterior para exportar diretamente para a pasta de trabalho.
- Compilar o EA final (Stage6, Python).
- Executar o terminal com a nova versão do EA final.
Agora, essas etapas não são mais necessárias. Além disso, eliminamos outra grande desvantagem: não seria possível verificar, no testador de estratégias, o correto funcionamento desse mecanismo de atualização automática de configurações. A recompilação é incompatível com o fato de que o código compilado do EA não pode ser alterado durante uma única execução do testador.
O mais importante, porém, é que tentaremos dar um passo significativo na direção de simplificar o uso de todo o código já escrito para a otimização de estratégias arbitrárias, descrevendo um algoritmo passo a passo para isso.
Traçando o caminho
Vamos começar implementando mudanças há muito necessárias na estrutura de arquivos do projeto. Atualmente, todos eles estão em uma única pasta, o que facilita a transferência e o uso do código em um novo projeto. Porém, durante o desenvolvimento contínuo, acabamos com várias pastas de projetos quase idênticas para diferentes estratégias de trading, e cada uma precisa ser atualizada separadamente.
Por isso, vamos dividir todo o código em uma biblioteca, que será comum a todos os projetos, e em um projeto, que conterá o código específico de cada um. Em seguida, vamos implementar a verificação para que, se durante a execução do EA final surgirem novos grupos de estratégias, ele consiga carregar os parâmetros atualizados corretamente e continuar funcionando. Como de costume, começaremos simulando o comportamento desejado no EA executado no testador de estratégias.
Se os resultados forem satisfatórios, poderemos avançar para usar isso em EAs finais fora do testador. O que precisamos para isso? Na parte anterior, não implementamos o salvamento, no banco de dados do especialista, das informações sobre as datas de término do intervalo de otimização e de finalização da execução do pipeline de otimização.
Agora, precisaremos dessas informações; caso contrário, durante uma execução no testador, o EA final não conseguirá entender se um determinado grupo de estratégias já foi formado em uma data simulada específica ou não. Também será necessário adaptar o EA final para que ele consiga se reiniciar quando houver novos grupos de estratégias em seu banco de dados. Atualmente, essa funcionalidade simplesmente não existe. Seria útil, pelo menos, ter alguma forma de exibir informações sobre o grupo atual de estratégias de trading para verificarmos claramente a troca de um grupo para outro.
O ideal seria visualizar essas informações diretamente no gráfico em que o EA está em execução, mas também é possível usar a saída normal no log do terminal.
E, por fim, apresentaremos a descrição do algoritmo geral de trabalho com as ferramentas já desenvolvidas até o momento.
Vamos começar!
Mudança para outra estrutura de arquivos Em todas as etapas anteriores, o desenvolvimento foi feito em uma única pasta de trabalho do projeto. Os arquivos existentes nela eram modificados e, de tempos em tempos, novos arquivos eram adicionados. Às vezes, arquivos sem relevância para o projeto eram apagados ou simplesmente "esquecidos". Para trabalhar com uma única estratégia de trading (como a usada nos artigos, chamada SimpleVolumes), essa abordagem funcionava bem. No entanto, ao expandir o mecanismo de otimização automática para outras estratégias de trading, era necessário criar uma cópia completa da pasta de trabalho do projeto e alterar apenas uma pequena parte dos arquivos.
Com o crescimento do número de estratégias de trading conectadas dessa forma (e com o aumento do número de diferentes pastas de trabalho), manter o código atualizado em todas elas se tornava cada vez mais trabalhoso. Por isso, movemos a parte do código que deve ser comum a todas as pastas de trabalho para uma pasta de biblioteca separada, localizada em MQL5/Include. O nome escolhido para a pasta geral de arquivos de biblioteca foi Advisor, e, para evitar conflito com uma possível pasta já existente com esse nome, adicionamos um identificador único. Agora, os arquivos de biblioteca ficam em MQL5/Include/antekov/Advisor/.
Após transferir todos os arquivos para lá, passamos à próxima etapa de sistematização. Decidimos distribuir todos os arquivos em subpastas, de acordo com um propósito comum de organização dos arquivos nelas contidos. Isso exigiu ajustes nas diretivas de inclusão de arquivos, já que a posição relativa deles havia mudado. Mas, no fim, conseguimos compilar com sucesso tanto os EAs quanto todos os arquivos de biblioteca individualmente.
A estrutura de arquivos, após a modificação, passou a ser assim:

Fig. 1. Estrutura de arquivos da biblioteca Advisor
Como se pode ver, separamos alguns grupos de arquivos em subpastas distintas:
- Base. Classes básicas, a partir das quais outras classes do projeto são herdadas.
- Database. Arquivos para trabalhar com todos os tipos de banco de dados usados pelos EAs do projeto.
- Experts. Arquivos com as partes comuns dos EAs utilizados, de diferentes tipos.
- Optimization. Classes responsáveis pelo funcionamento da otimização automática.
- Strategies. Exemplos de estratégias de trading, usadas para demonstrar o funcionamento do projeto.
- Utils. Utilitários auxiliares, macros para redução de código.
- Virtual. Classes para criação de diversos objetos, unificados pelo uso do sistema de ordens e posições virtuais de trading.
Entre todas as subpastas listadas, apenas uma merece uma observação especial: Isso é a pasta Experts. Se compararmos a composição de arquivos na figura 1 com a lista de arquivos da parte anterior, notaremos que apenas nessa pasta surgiram arquivos que não existiam antes. À primeira vista, pode parecer que apenas renomeamos parcialmente os arquivos dos EAs utilizados e os movemos para cá, mas repare que a extensão deles não é .mq5, mas sim .mqh. Mas antes de analisá-los em detalhes, vejamos o que permanece na pasta de um projeto individual para uma determinada estratégia de trading.
Vamos criar uma pasta separada dentro de MQL5/Experts/, que pode receber qualquer nome. Nela ficarão os arquivos de todos os EAs utilizados:

Fig. 2. Estrutura de arquivos do projeto, utilizando a biblioteca Advisor
A finalidade desses arquivos é a seguinte:
- CreateProject.mq5 — EA responsável pela criação de um projeto de otimização automática no banco de dados de otimização. Cada projeto no banco de dados é representado em forma de três estágios, compostos por um ou mais trabalhos. Cada trabalho, por sua vez, é formado por uma ou mais tarefas de otimização, executadas pelos EAs correspondentes a cada estágio.
- HistoryReceiverExpert.mq5 — EA para reprodução de histórico de trades previamente salvo. Já faz tempo que não o utilizamos, pois ele foi desenvolvido apenas para verificar a repetibilidade dos resultados ao trocar de corretora. Para a otimização automática ele não é necessário, portanto pode ser removido sem problemas, se desejado.
- Optimization.mq5 — EA que executa as tarefas de um projeto de otimização automática. O próprio processo de execução sequencial dessas tarefas nós chamamos de pipeline de otimização automática.
- SimpleVolumes.mq5 — EA final, que reúne múltiplas instâncias individuais da estratégia de trading SimpleVolumes. As informações sobre a composição dessas instâncias serão obtidas a partir do banco de dados do especialista. A responsável por inserir essas informações nesse banco será o EA do terceiro estágio do pipeline de otimização automática.
- Stage1.mq5 — EA do primeiro estágio do pipeline de otimização automática. Realiza a otimização de uma instância individual de uma estratégia de trading.
- Stage2.mq5 — EA do segundo estágio do pipeline de otimização automática. Durante a otimização, seleciona, dentre muitas boas instâncias individuais obtidas no primeiro estágio, um pequeno grupo (geralmente 8 ou 16), que em conjunto apresenta os melhores resultados em termos de lucro normalizado.
- Stage3.mq5 — EA do terceiro estágio do pipeline de otimização automática. Une todos os grupos obtidos no segundo estágio, normaliza o tamanho das posições e salva o grupo final com um fator de escala no banco de dados do especialista definido nas configurações.
Entre os arquivos listados, apenas CreateProject.mq5 manteve seu conteúdo original. Todos os outros EAs contêm basicamente apenas o comando de inclusão do respectivo arquivo .mqh da biblioteca Advisor, localizada em MQL5/Include/antekov/Advisor/Experts. Vale destacar que SimpleVolumes.mq5 e HistoryReceiverExpert.mq5 utilizam o mesmo arquivo incluído Expert.mqh. Como mostrou a prática, não precisamos escrever códigos diferentes para os EAs dos estágios dois e três quando se trata de diferentes estratégias de trading. E no caso do EA do primeiro estágio, a única diferença está nos parâmetros de entrada e na montagem da linha de inicialização a partir dos seus valores. Todo o restante também é idêntico.
Assim, apenas CreateProject.mq5 exigirá, por enquanto, uma modificação mais significativa ao migrarmos para um projeto com outra estratégia de trading. No futuro, também tentaremos extrair dele uma parte comum.
Vejamos agora quais alterações precisam ser feitas nos arquivos da biblioteca para implementar a atualização automática do EA final.
Datas de término
Lembremos que executamos quatro projetos de otimização quase idênticos na parte anterior. A única diferença entre eles era a data de término do intervalo de otimização das instâncias individuais das estratégias de trading. A composição dos instrumentos, os timeframes e os demais parâmetros eram iguais. Como resultado, foram adicionados os seguintes registros à tabela strategy_groups do banco de dados do especialista:

Como incluímos a data de término do intervalo de otimização no nome do grupo, conseguimos identificar a qual data cada grupo corresponde. Mas, é necessário que o EA também consiga entender isso. Para isso, criamos dois campos propositalmente nessa tabela para salvar essas datas, que devem ser preenchidos no momento da criação dos registros, e até reservamos no código o ponto exato onde isso deveria ser feito:
//+------------------------------------------------------------------+ //| 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); // ... } }
As datas de início e de término do intervalo de otimização são armazenadas no banco de dados, na tabela stages. Portanto, seria possível obtê-las de lá, executando no código o SQL correspondente nesse ponto. No entanto, esse método não se mostrou o mais eficiente, pois já havíamos escrito código que executa consultas SQL para obter, entre outras coisas, essas datas. Isso acontecia no EA de otimização automática. Ele precisava recuperar informações do banco de dados sobre a tarefa de otimização em andamento. Entre essas informações, obrigatoriamente estavam incluídas as datas de que precisamos. Vamos aproveitar isso.
Será necessário criar um objeto da classe COptimizerTask, passando ao construtor o nome do banco de dados de otimização. Esse nome já está disponível no campo estático CTesterHandler::s_fileName. Já em outro campo estático, CTesterHandler::s_idTask, temos o identificador da tarefa de otimização atual. Esse identificador será passado ao método de carregamento dos dados da tarefa. Depois disso, poderemos acessar as datas necessárias a partir dos respectivos campos da estrutura m_params do objeto da tarefa.
//+------------------------------------------------------------------+ //| 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); // ... } }
As alterações feitas devem ser salvas no arquivo TesterHandler.mqh, localizado na subpasta Virtual da biblioteca.
Vamos recriar alguns projetos usando o EA CreateProject.ex5. Para acelerar o processo, definiremos um intervalo de otimização pequeno (4 meses). A cada novo projeto, a data de início e término do intervalo de otimização será deslocada em um mês para frente. Como resultado, teremos o seguinte:

Agora, cada grupo no banco de dados do especialista contém a data de término do intervalo de otimização. Vale destacar que essa data é retirada do intervalo da tarefa do terceiro estágio. Para que tudo esteja correto, as datas dos intervalos nos três estágios devem ser idênticas. Isso é garantido pelo EA de criação de projetos.
Modificação do EA final
Antes de implementar a atualização automática do grupo de estratégias utilizado no EA final, vejamos as mudanças decorrentes da nova estrutura de arquivos do projeto. Como já mencionado, o EA final agora está representado em dois arquivos. O arquivo principal fica na pasta do projeto e se chama SimpleVolumes.mq5. Eis o código completo:
//+------------------------------------------------------------------+ //| 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> //+------------------------------------------------------------------+
Nesse código, basicamente existe apenas um comando de importação do arquivo de biblioteca do EA final. É justamente um daqueles casos em que o mais importante não é o que há, mas sim o que não há. Compare com o código do segundo EA, HistoryReceiverExpert.mq5:
//+------------------------------------------------------------------+ //| 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> //+------------------------------------------------------------------+
Três blocos, destacados em cores, estão presentes no arquivo SimpleVolumes.mq5. A presença deles é tratada no arquivo de biblioteca do EA final, Experts/Expert.mqh, da seguinte forma: se não for definida uma constante com o nome do EA final, então é declarada uma função responsável por formar a string de inicialização, que será obtida do banco de dados do especialista. Caso o nome esteja definido, essa função deverá ser declarada no arquivo superior, que inclui esse arquivo de biblioteca.
// 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
Depois, no arquivo de biblioteca Experts/Expert.mqh,, dentro da função de inicialização do EA, é usado um dos possíveis modos de formação da string de inicialização:
//+------------------------------------------------------------------+ //| 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); }
Assim, podemos criar, se desejarmos, um EA final que não utilize a carga da string de inicialização a partir do banco de dados do especialista. Para isso, basta declarar no arquivo .mq5 a constante __NAME__ e a função com a assinatura correspondente.
string GetStrategyParams() Agora sim, podemos partir para a atualização automática.
Atualização automática
A primeira versão da implementação da atualização automática talvez não seja a mais elegante, mas é perfeitamente aceitável para começar. O mais importante é que funciona. As modificações necessárias no arquivo de biblioteca do EA final foram divididas em duas partes.
Em primeiro lugar, alteramos um pouco a composição dos parâmetros de entrada, removendo da antiga biblioteca a enumeração com o número do grupo e substituindo-a pelo identificador do grupo do banco de dados do especialista, além de acrescentarmos um parâmetro lógico para ativar o autoatualização:
//+------------------------------------------------------------------+ //| 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 // ...
Em segundo lugar, adicionamos o seguinte código à função de processamento de novo tick, logo após a linha destacada em cores:
//+------------------------------------------------------------------+ //| 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(); } }
Dessa forma, a atualização automática funcionará somente se os parâmetros de entrada groupId_=0 e useAutoUpdate_=true. Caso seja indicado algum identificador de grupo diferente de zero, será utilizada exatamente essa configuração durante todo o intervalo de teste. Nesse caso, não há nenhuma limitação quanto ao momento em que o EA final poderá executar operações.
Com a atualização automática ativada, o EA final realizará operações apenas após a data de término do intervalo de otimização do grupo mais antigo presente no banco de dados do especialista. Esse mecanismo será implementado em um novo método da classe CVirtualAdvisor::CheckUpdate():
//+------------------------------------------------------------------+ //| 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; }
Nesse método, obtemos do banco de dados do especialista o maior identificador de grupo cuja data de término do intervalo de otimização seja menor ou igual à data atual. Assim, mesmo que já exista fisicamente no banco de dados o registro de um novo grupo, se o momento do seu surgimento (>= data de término do intervalo de otimização) estiver no futuro em relação ao tempo simulado do testador de estratégias, ele não será retornado pelo SQL executado.
Durante a inicialização, o EA memoriza o identificador do grupo de estratégias carregado no campo estático CVirtualAdvisor::s_groupId. Portanto, conseguimos detectar o surgimento de um novo grupo comparando o identificador recém-obtido no banco de dados do especialista com o identificador do grupo carregado anteriormente. Se o novo for maior, significa que surgiu um grupo novo.
No método que obtém a string de inicialização do banco de dados do especialista, o qual interage diretamente com o banco, aplicamos a mesma condição sobre a data de término do intervalo de otimização do grupo quando a atualização automática está ativada:
//+------------------------------------------------------------------+ //| 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; }
Por fim, vale mencionar um último detalhe: a complementação do método de carregamento do estado do EA após a transição para um novo grupo de estratégias. O motivo é que as novas estratégias do grupo recém-carregado não encontrarão no banco de dados do especialista suas configurações, já que o método Save() ainda não foi chamado para elas, e assim será exibida uma mensagem de erro de carregamento. Mas esse erro deve ser ignorado.
E há ainda mais um complemento, relacionado à necessidade de fechar as posições virtuais das estratégias antigas imediatamente após o carregamento das novas. Para isso, é suficiente criar objetos receptores de símbolos para todos os símbolos utilizados pelas estratégias antigas. Esses objetos, no próximo tick, farão a correção dos volumes das posições abertas. Caso isso não seja feito, a correção dos volumes ocorrerá apenas conforme as novas estratégias forem abrindo posições virtuais. E se alguma das novas estratégias deixar de usar um símbolo que era utilizado anteriormente, as posições desse símbolo permanecerão abertas indefinidamente.
//+------------------------------------------------------------------+ //| 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; }
Depois disso, já podemos partir para a verificação da funcionalidade do carregamento automático de novos grupos. Infelizmente, o sucesso não veio de imediato, pois foi necessário corrigir erros que surgiram. Por exemplo, descobriu-se que o EA entrava em loop infinito se o banco de dados do especialista estivesse vazio. Ou que o teste simplesmente não iniciava, caso a data de início fosse definida nem que fosse apenas um dia antes da data de surgimento do primeiro grupo de estratégias. Mas, no fim, todos os erros identificados foram corrigidos.
Vamos agora observar o algoritmo de utilização da biblioteca criada como um todo e os resultados da verificação da atualização automática.
Algoritmo de uso da biblioteca Advisor
Desta vez, descreveremos o algoritmo de uso da biblioteca Advisor para a otimização automática da estratégia modelo SimpleVolumes, que faz parte da biblioteca, e sua execução no testador pelo EA final.
- Instalamos a biblioteca em Include (fig. 1).
- Criamos uma pasta de projeto e transferimos para ela os arquivos dos EAs (fig. 2).
- Fazemos as alterações nos arquivos do EA do primeiro estágio e no arquivo do EA de criação de projeto. Ao usar a estratégia modelo, nenhuma alteração é necessária, pois ela já está atualizada. Compilamos todos os EAs da pasta do projeto.
- Executamos o EA de criação de projeto, configurando os parâmetros desejados (ou deixando os padrões).
Como resultado, deve aparecer na pasta comum de dados do terminal um banco de dados de otimização, preenchido com tarefas para o projeto. Na descrição do projeto, pode-se indicar qualquer coisa, como as datas de início e término do intervalo de otimização. Até esse ponto, é apenas a criação de uma tarefa para execução do pipeline. A execução será feita por outro EA. - Se desejado, podemos repetir o passo anterior quantas vezes quisermos, alterando os parâmetros. Assim, por exemplo, é possível criar vários projetos de otimização automática em diferentes intervalos de tempo.
- Executamos o EA de otimização e aguardamos. O tempo necessário para concluir todos os projetos adicionados ao banco de dados depende da quantidade deles, bem como da quantidade de símbolos e timeframes incluídos, da duração do intervalo de teste/otimização e da complexidade da estratégia de trading implementada. Esse tempo também depende do número de agentes de teste disponíveis e utilizados na otimização.
Como resultado, será gerado um arquivo com o banco de dados do especialista na pasta comum. O nome desse arquivo vem das configurações.
No banco de dados do especialista estarão armazenados os grupos de estratégias selecionados. - Executamos o EA final. É fundamental que seu nome e número mágico coincidam com os indicados durante a otimização. Caso contrário, ele criará um banco de dados do especialista vazio e ficará aguardando que algo seja adicionado a ele. Se o EA final encontrar seu banco, ele tentará carregar o grupo de estratégias com o identificador especificado ou o último grupo adicionado, caso o identificador seja 0. Se o parâmetro de atualização automática estiver ativado, então, uma vez por dia, o EA verificará se surgiu no banco de dados do especialista um novo grupo de estratégias disponível de acordo com a data. Se houver, ele substituirá o grupo anteriormente em uso.
Testando a atualização automática
Então, após a conclusão da otimização de todos os projetos adicionados ao banco de dados com diferentes datas de término, teremos um banco de dados do especialista contendo vários grupos de estratégias com composições distintas. Também diferem entre si pela data de término do intervalo de otimização. E temos o EA final, que pode, durante o teste, carregar do banco um novo grupo de estratégias assim que o tempo simulado ultrapassar a data de término do intervalo de otimização desse novo grupo.
É importante levar em conta que o salvamento e o carregamento de parâmetros do EA funcionam apenas quando ele é executado em um gráfico ou em modo de teste visual. Portanto, para verificar a atualização automática no testador, é obrigatório utilizar o modo visual.
Executemos o EA final indicando explicitamente o identificador de grupo groupId_=1. Nesse caso, independentemente do valor do parâmetro useAutoUpdate_, será utilizada somente essa configuração. Esse grupo foi otimizado no intervalo 2022.09.01 — 2023.01.01, por isso, iniciaremos o teste a partir de 2022.09.01 (período principal) e, a partir de 2023.01.01, começaremos o período de forward até 2024.01.01.

Período principal 2022.09.01 — 2023.01.01

Período de forward 2023.01.01 — 2024.01.01

Fig. 3. Resultados do EA final com parâmetros groupId_=1 no intervalo 2022.09.01 — 2024.01.01
Como se pode ver, no período principal, que coincide com o intervalo de otimização, o EA apresenta resultados satisfatórios, mas no período de forward o cenário é totalmente diferente. Observa-se um rebaixamento muito maior e ausência de crescimento consistente na curva de saldo. Claro, seria preferível ver algo mais atrativo, mas esse resultado não é inesperado. Afinal, utilizamos um intervalo de otimização muito pequeno, poucos símbolos e poucos timeframes. Isso fez com que, no trecho conhecido, os parâmetros se ajustassem bem demais especificamente para esse curto intervalo. Já no trecho desconhecido, o EA não conseguiu manter o mesmo desempenho.
Por curiosidade, vejamos se uma situação semelhante será observada para outro grupo de estratégias de trading. Executemos o EA final indicando o identificador de grupo groupId_=3. Esse grupo foi otimizado no intervalo 2022.11.01 — 2023.03.01, portanto iniciaremos o teste a partir de 2022.11.01 (período principal) e, a partir de 2023.03.01, começaremos o período de forward até 2024.01.01.

Período principal 2022.11.01 — 2023.03.01

Período de forward 2023.03.01 — 2024.01.01

Fig. 4. Resultados do EA final com parâmetros groupId_=3 no intervalo 2022.11.01 — 2024.01.01
Sim, os resultados foram os mesmos da primeira grupo. Para ambas, em maio-junho, observa-se uma forte retração. Pode-se pensar que esse foi um período desfavorável para a estratégia. No entanto, ao verificar o grupo otimizado exatamente nesse intervalo, vemos que ali também foi possível selecionar parâmetros das estratégias que proporcionaram bons resultados. Nesse caso, o gráfico apresenta o mesmo crescimento suave e consistente.
Se executarmos o EA final a partir de 2023.01.01, com os parâmetros groupId_=0, useAutoUpdate=false, obteremos o mesmo resultado do período de forward da primeira grupo, já que, nesse caso, será carregada a primeira grupo (ela já “existe” na data de início da simulação). Contudo, como a atualização automática está desativada, ela não será substituída por grupos com datas posteriores de surgimento.
E por fim, executemos o EA final no intervalo 2023.01.01 — 2024.01.01 com atualização automática, definindo os parâmetros groupId_=0, useAutoUpdate=true.


Fig. 5. Resultados do EA final com parâmetros groupId_=0, useAutoUpdate=true no intervalo 2023.01.01 — 2024.01.01
Os resultados de trading em si não são relevantes, pois, para reduzir o tempo da otimização automática, foi utilizado um período de apenas 4 meses. O objetivo agora era apenas demonstrar a funcionalidade do mecanismo de atualização automática dos grupos de estratégias utilizados. E isso, conforme mostram os registros no log e o fechamento automático das posições no início de cada mês, funciona exatamente como planejado:
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
Considerações finais
Vamos resumir alguns pontos. Finalmente organizamos todo o código reutilizável na pasta Include no formato da biblioteca Advisor. Agora, será possível conectá-la a projetos que utilizam diferentes estratégias de trading. E as atualizações futuras da biblioteca serão automaticamente propagadas para todos os projetos em que ela for utilizada.
A cada nova etapa, está ficando cada vez mais simples criar e executar um projeto de otimização automática. Agora também simplificamos o mecanismo de integração dos resultados da otimização no EA final. Basta indicar o nome correto do banco de dados do especialista nas configurações do terceiro estágio da otimização, e os resultados serão gravados nele, de onde o EA final poderá buscá-los.
No entanto, ainda há vários pontos que exigem atenção. Um deles é o desenvolvimento do algoritmo para inclusão de um novo tipo de estratégia de trading e a possibilidade de incorporar ao EA final grupos que contenham diferentes tipos de estratégias. Mas isso já é assunto para a próxima vez.
Obrigado pela atenção, até a próxima!
Aviso importante
Todos os resultados apresentados neste artigo e em todos os anteriores da série baseiam-se exclusivamente em dados de testes em histórico e não representam garantia de obtenção de qualquer lucro no futuro. O trabalho neste projeto tem caráter de pesquisa. Todos os resultados publicados podem ser utilizados por qualquer pessoa, por sua própria conta e risco.
Conteúdo do arquivo
| # | Nome | Versão | Descrição | Últimas alterações |
|---|---|---|---|---|
| MQL5/Experts/Article.16913 | Pasta de trabalho do projeto | |||
| 1 | CreateProject.mq5 | 1.01 | EA-script para criação de projeto com estágios, trabalhos e tarefas de otimização. | Parte 23 |
| 2 | HistoryReceiverExpert.mq5 | 1.01 | EA de reprodução de histórico de operações com gerenciador de risco | Parte 23 |
| 3 | Optimization.mq5 | 1.00 | EA para otimização automática de projetos | Parte 23 |
| 4 | SimpleVolumesExpert.mq5 | 1.22 | EA final para execução paralela de vários grupos de estratégias modelo. Os parâmetros serão obtidos da biblioteca interna de grupos. | Parte 23 |
| 5 | Stage1.mq5 | 1.22 | EA de otimização de uma instância individual de estratégia de trading (Estágio 1) | Parte 23 |
| 6 | Stage2.mq5 | 1.00 | EA de otimização de grupo de instâncias de estratégias de trading (Estágio 2) | Parte 23 |
| 7 | Stage3.mq5 | 1.00 | EA que salva o grupo normalizado de estratégias formado no banco de dados do especialista com o nome especificado. | Parte 23 |
| MQL5/Include/antekov/Advisor/Base | Classes base, das quais herdam outras classes do projeto | |||
| 8 | Advisor.mqh | 1.04 | Classe base do especialista | Parte 10 |
| 9 | Factorable.mqh | 1.05 | Classe base de objetos criados a partir de uma string | Parte 22 |
| 10 | Interface.mqh | 1.01 | Classe base de visualização de diferentes objetos | Parte 4 |
| 11 | Receiver.mqh | 1.04 | Classe base de conversão de volumes abertos em posições de mercado | Parte 12 |
| 12 | Strategy.mqh | 1.04 | Classe base de estratégia de trading | Parte 10 |
| MQL5/Include/antekov/Advisor/Database | Arquivos para trabalhar com todos os tipos de bancos de dados usados pelos EAs do projeto | |||
| 13 | Database.mqh | 1.10 | Classe para trabalhar com banco de dados | Parte 22 |
| 14 | db.adv.schema.sql | 1.00 | Esquema do banco de dados do EA final | Parte 22 |
| 15 | db.cut.schema.sql | 1.00 | Esquema do banco de dados reduzido de otimização | Parte 22 |
| 16 | db.opt.schema.sql | 1.05 | Esquema do banco de dados de otimização | Parte 22 |
| 17 | Storage.mqh | 1.01 | Classe para trabalhar com o armazenamento Key-Value do EA final no banco de dados do especialista | Parte 23 |
| MQL5/Include/antekov/Advisor/Experts | Arquivos com partes comuns dos EAs utilizados, de diferentes tipos | |||
| 18 | Expert.mqh | 1.22 | Arquivo de biblioteca para o EA final. Os parâmetros dos grupos podem ser obtidos do banco de dados do especialista | Parte 23 |
| 19 | Optimization.mqh | 1.04 | Arquivo de biblioteca para o EA que gerencia a execução das tarefas de otimização | Parte 23 |
| 20 | Stage1.mqh | 1.19 | Arquivo de biblioteca para o EA de otimização de uma instância individual de estratégia de trading (Estágio 1) | Parte 23 |
| 21 | Stage2.mqh | 1.04 | Arquivo de biblioteca para o EA de otimização de grupo de instâncias de estratégias de trading (Estágio 2) | Parte 23 |
| 22 | Stage3.mqh | 1.04 | Arquivo de biblioteca para o EA que salva o grupo normalizado de estratégias no banco de dados do especialista com o nome especificado | Parte 23 |
| MQL5/Include/antekov/Advisor/Optimization | Classes responsáveis pelo funcionamento da otimização automática | |||
| 23 | Optimizer.mqh | 1.03 | Classe para o gerenciador de otimização automática de projetos | Parte 22 |
| 24 | OptimizerTask.mqh | 1.03 | Classe para a tarefa de otimização | Parte 22 |
| MQL5/Include/antekov/Advisor/Strategies | Exemplos de estratégias de trading usadas para demonstrar o funcionamento do projeto | |||
| 25 | HistoryStrategy.mqh | 1.00 | Classe de estratégia de trading para reprodução de histórico de operações | Parte 16 |
| 26 | SimpleVolumesStrategy.mqh | 1.11 | Classe de estratégia de trading utilizando volumes em ticks | Parte 22 |
| MQL5/Include/antekov/Advisor/Utils | Utilitários auxiliares, macros para redução de código | |||
| 27 | ExpertHistory.mqh | 1.00 | Classe para exportar o histórico de operações para arquivo | Parte 16 |
| 28 | Macros.mqh | 1.05 | Macros úteis para operações com arrays | Parte 22 |
| 29 | NewBarEvent.mqh | 1.00 | Classe para detecção de novo candle (barra) em um símbolo específico | Parte 8 |
| 30 | SymbolsMonitor.mqh | 1.00 | Classe para obtenção de informações sobre instrumentos de trading (símbolos) | Parte 21 |
| MQL5/Include/antekov/Advisor/Virtual | Classes para criação de diversos objetos, unificados pelo uso do sistema de ordens e posições virtuais de trading | |||
| 31 | Money.mqh | 1.01 | Classe base de gestão de capital | Parte 12 |
| 32 | TesterHandler.mqh | 1.07 | Classe para processamento de eventos de otimização | Parte 23 |
| 33 | VirtualAdvisor.mqh | 1.10 | Classe de especialista que trabalha com posições virtuais (ordens) | Parte 23 |
| 34 | VirtualChartOrder.mqh | 1.01 | Classe de posição virtual gráfica | Parte 18 |
| 35 | VirtualFactory.mqh | 1.04 | Classe fábrica de objetos | Parte 16 |
| 36 | VirtualHistoryAdvisor.mqh | 1.00 | Classe de especialista para reprodução de histórico de operações | Parte 16 |
| 37 | VirtualInterface.mqh | 1.00 | Classe de interface gráfica do EA | Parte 4 |
| 38 | VirtualOrder.mqh | 1.09 | Classe de ordens e posições virtuais | Parte 22 |
| 39 | VirtualReceiver.mqh | 1.04 | Classe de conversão de volumes abertos em posições de mercado (receptor) | Parte 23 |
| 40 | VirtualRiskManager.mqh | 1.04 | Classe de gerenciamento de risco (risk manager) | Parte 23 |
| 41 | VirtualStrategy.mqh | 1.09 | Classe de estratégia de trading com posições virtuais | Parte 23 |
| 42 | VirtualStrategyGroup.mqh | 1.02 | Classe de grupo de estratégias de trading ou grupos de estratégias | Parte 23 |
| 43 | VirtualSymbolReceiver.mqh | 1.00 | Classe de receptor de símbolo | Parte 3 |
| MQL5/Common/Files | Pasta comum dos terminais | |||
| 44 | SimpleVolumes-27183.test.db.sqlite | — | Banco de dados do especialista com grupos de estratégias adicionados |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/16913
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Gestão de capital no trading e programa de contabilidade pessoal do trader com banco de dados
Redes neurais em trading: Modelos híbridos de sequências de grafos (GSM++)
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 9): Consultor Especializado em Múltiplas Estratégias (II)
Otimização por herança sanguínea — Blood Inheritance Optimization (BIO)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso