
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:
//+------------------------------------------------------------------+ //| Экспорт массива стратегий в заданную базу данных эксперта | //| как новой группы стратегий | //+------------------------------------------------------------------+ void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) { // Подключаемся к нужной базе данных эксперта if(DB::Connect(p_advFileName, DB_TYPE_ADV)) { string fromDate = ""; // Дата начала интервала оптимизации string toDate = ""; // Дата конца интервала оптимизации // Создаём запись для новой группы стратегий 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.
//+------------------------------------------------------------------+ //| Экспорт массива стратегий в заданную базу данных эксперта | //| как новой группы стратегий | //+------------------------------------------------------------------+ void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) { // Создаём объект задачи оптимизации COptimizerTask task(s_fileName); // Загружаем в него данные текущей задачи оптимизации task.Load(CTesterHandler::s_idTask); // Подключаемся к нужной базе данных эксперта if(DB::Connect(p_advFileName, DB_TYPE_ADV)) { string fromDate = task.m_params.from_date; // Дата начала интервала оптимизации string toDate = task.m_params.to_date; // Дата конца интервала оптимизации // Создаём запись для новой группы стратегий 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/ru/users/antekov | //+------------------------------------------------------------------+ #property copyright "Copyright 2024-2025, Yuriy Bykov" #property link "https://www.mql5.com/ru/articles/16913" #property description "Итоговый советник, объединяющий много экземпляров торговых стратегий:" #property description " " #property description "Стратегии открывают рыночный или отложенный ордер в тот момент," #property description "когда тиковый объем свечи превышает средний объем в направлении текущей свечи." #property description "Если ордера еще не превратились в позиции, то они удаляются по времени истечения." #property description "Открытые позиции закрываются только по SL или 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/ru/users/antekov | //+------------------------------------------------------------------+ #property copyright "Copyright 2024-2025, Yuriy Bykov" #property link "https://www.mql5.com/ru/articles/16913" #property description "Советник открывает рыночный или отложенный ордер в тот момент," #property description "когда тиковый объем свечи превышает средний объем в направлении текущей свечи." #property description "Если ордера еще не превратились в позиции, то они удаляются по времени истечения." #property description "Открытые позиции закрываются только по SL или TP." #property version "1.01" //+------------------------------------------------------------------+ //| Объявление имени итогового советника. | //| При компиляции будет использоваться функция формирования | //| строки инициализации из текущего файла | //+------------------------------------------------------------------+ #define __NAME__ MQLInfoString(MQL_PROGRAM_NAME) //+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "::: Тестирование истории сделок" input string historyFileName_ = "SimpleVolumesExpert.1.19 [2021.01.01 - 2022.12.30]" " [10000, 34518, 1294, 3.75].history.csv"; // Файл с историей //+------------------------------------------------------------------+ //| Функция формирования строки инициализации стратегии | //| из входных параметров | //+------------------------------------------------------------------+ 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.
// Если не задана константа с именем итогового советника, то #ifndef __NAME__ // Задаём её равной названию файла советника #define __NAME__ MQLInfoString(MQL_PROGRAM_NAME) //+------------------------------------------------------------------+ //| Функция формирования строки инициализации стратегии | //| из входных параметров по умолчанию (если не было задано имя). | //| Импортирует строку инициализации из базы данных советника | //| по идентификатору группы стратегий | //+------------------------------------------------------------------+ string GetStrategyParams() { // Берём строку инициализации из новой библиотеки для выбранной группы // (из базы данных эксперта) string strategiesParams = CVirtualAdvisor::Import( CVirtualAdvisor::FileName(__NAME__, magic_), groupId_ ); // Если группа стратегий из библиотеки не задана, то прерываем работу 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() { // ... // Строка инициализации с наборами параметров стратегий string strategiesParams = NULL; // Берём строку инициализации из новой библиотеки для выбранной группы // (из базы данных эксперта) strategiesParams = GetStrategyParams(); // Если группа стратегий из библиотеки не задана, то прерываем работу if(strategiesParams == NULL) { return INIT_FAILED; } // ... // Успешная инициализация 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:
//+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "::: Использовать группу стратегий" sinput int groupId_ = 0; // - ID группы из новой библиотеки (0 - последняя) sinput bool useAutoUpdate_ = true; // - Использовать автообновление? input group "::: Управление капиталом" sinput double expectedDrawdown_ = 10; // - Максимальный риск (%) sinput double fixedBalance_ = 10000; // - Используемый депозит (0 - использовать весь) в валюте счета input double scale_ = 1.00; // - Масштабирующий множитель для группы // ...
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(groupId_ == 0 // - не задан конкретный идентификатор группы && useAutoUpdate_ // - разрешено автообновление && IsNewBar(Symbol(), PERIOD_D1) // - наступил новый день && expert.CheckUpdate() // - обнаружена новая группа стратегий ) { // Сохраняем текущее состояние эксперта expert.Save(); // Удаляем объект эксперта delete expert; // Вызываем функцию инициализации советника для загрузки новой группы стратегий 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():
//+------------------------------------------------------------------+ //| Проверка наличия новой группы стратегий в базе данных эксперта | //+------------------------------------------------------------------+ bool CVirtualAdvisor::CheckUpdate() { // Запрос на получение стратегий заданной группы либо последней группы string query = StringFormat("SELECT MAX(id_group) FROM strategy_groups" " WHERE to_date <= '%s'", TimeToString(TimeCurrent(), TIME_DATE)); // Открываем базу данных эксперта if(DB::Connect(m_fileName, DB_TYPE_ADV)) { // Выполняем запрос int request = DatabasePrepare(DB::Id(), query); // Если нет ошибки if(request != INVALID_HANDLE) { // Структура данных для чтения одной строки результата запроса struct Row { int groupId; } row; // Читаем данные из первой строки результата while(DatabaseReadBind(request, row)) { // Запоминаем идентификатор группы стратегий // в статическом свойстве класса эксперта return s_groupId < row.groupId; } } else { // Сообщаем об ошибке при необходимости PrintFormat(__FUNCTION__" | ERROR: request \n%s\nfailed with code %d", query, GetLastError()); } // Закрываем базу данных эксперта 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:
//+------------------------------------------------------------------+ //| Получение строки инициализации группы стратегий | //| из базы данных эксперта с заданным идентификатором | //+------------------------------------------------------------------+ string CVirtualAdvisor::Import(string p_fileName, int p_groupId = 0) { string params[]; // Массив для строк инициализации стратегий // Запрос на получение стратегий заданной группы либо последней группы 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) + "')")); // Открываем базу данных эксперта if(DB::Connect(p_fileName, DB_TYPE_ADV)) { // ... } // Строка инициализации группы стратегий string groupParams = NULL; // Общее количество стратегий в группе int totalStrategies = ArraySize(params); // Если стратегии есть, то if(totalStrategies > 0) { // Соединяем их строки инициализации через запятую JOIN(params, groupParams, ","); // Создаём строку инициализации группы стратегий groupParams = StringFormat("class CVirtualStrategyGroup([%s], %.5f)", groupParams, totalStrategies); } // Возвращаем строку инициализации группы стратегий 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.
//+------------------------------------------------------------------+ //| Загрузка состояния | //+------------------------------------------------------------------+ bool CVirtualAdvisor::Load() { bool res = true; ulong groupId = 0; // Загружаем состояние, если: if(true // файл существует && FileIsExist(m_fileName, FILE_COMMON) // и сейчас не оптимизация && !MQLInfoInteger(MQL_OPTIMIZATION) // и сейчас не тестирование либо сейчас визуальное тестирование && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE)) ) { // Если подключение к базе данных эксперта установлено if(CStorage::Connect(m_fileName)) { // Если время последних изменений загружено и меньше текущего времени 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(CStorage::Get("CVirtualAdvisor::s_groupId", groupId)) { // Загружаем все стратегии, игнорируя возможные ошибки FOREACH(m_strategies, { res &= ((CVirtualStrategy*) m_strategies[i]).Load(); }); if(groupId != s_groupId) { // Действия при запуске эксперта с новой группой стратегий. PrintFormat(__FUNCTION__" | UPDATE Group ID: %I64u -> %I64u", s_groupId, groupId); // Сбрасываем возможный признак ошибки при загрузке стратегий res = true; string symbols[]; // Массив для названий символоа // Получаем список всех используемых предыдущей группой символов CStorage::GetSymbols(symbols); // Для всех символов создаём символьный получатель. // Это нужно для корректного закрытия виртуальных позиций // старой группы стратегий сразу после загрузки новой FOREACH(symbols, m_receiver[symbols[i]]); } // ... } } else { // Если время последних изменений не найдено или находится в будущем, // то начинаем работу с чистого листа PrintFormat(__FUNCTION__" | NO LAST SAVE [%s] - Clear Storage", TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS)); CStorage::Clear(); m_lastSaveTime = 0; } // Закрываем соединение 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.





- 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