Otimização Walk Forward Contínua (Parte 3): Método de Adaptação de um Robô ao Otimizador Automático

6 abril 2020, 08:59
Andrey Azatskiy
0
1 009

Introdução

Este é o terceiro artigo de uma série dedicada à criação de um otimizador automático para a otimização Walk Forward Contínua. Os dois artigos anteriores estão disponíveis nos seguintes links:

  1. Otimização Walk Forward Contínua (parte 1): Trabalhando com os Relatórios de Otimização
  2. Otimização Walk Forward Contínua (Parte 2): Mecanismo para a criação de um relatório de otimização para qualquer robô

O primeiro artigo desta série é dedicado à criação de um mecanismo para trabalhar e formar arquivos de relatórios de negociação, necessários para que o otimizador automático opere. O segundo artigo apresentou os principais objetos que implementam a obtenção do histórico de negociação e a criação de relatórios de negociação com base nos dados obtidos. O artigo atual serve como uma ponte entre as duas partes anteriores: descreve o mecanismo de interação com a DLL considerada no primeiro artigo e os objetos para baixar os relatórios, descritos no segundo artigo.

Nós analisaremos o processo de criação de um wrapper para uma classe que é importada da DLL e que forma um arquivo XML com o histórico de negociação. Nós também consideraremos um método para interagir com este wrapper. O artigo também contém descrições de duas funções que baixam o histórico de negociação detalhado e generalizado para uma posterior análise. Na conclusão, eu apresentarei um modelo pronto para uso que pode funcionar com o otimizador automático. Eu também mostrarei um exemplo do algoritmo padrão do conjunto de experts, que demonstra como qualquer algoritmo existente pode ser alterado para interagir com o otimizador automático. 

Download do histórico de negociação acumulado

Às vezes, nós precisamos fazer o download do histórico de negociação em um arquivo para análise posterior do histórico e para outros fins. Infelizmente, essa interface ainda não está disponível no terminal, no entanto, esta tarefa pode ser implementada usando as classes descritas no artigo anterior. O diretório em que os arquivos das classes descritas estão localizados, possui dois outros arquivos, "ShortReport.mqh" e "DealsHistory.mqh", que baixam os dados generalizados e detalhados.

Vamos começar com o arquivo ShortReport.mqh. Este arquivo contém funções e macros, a principal delas é a função SaveReportToFile. Primeiramente, vamos considerar a função 'write' que grava os dados em um arquivo. 

//+------------------------------------------------------------------+
//| File writer                                                      |
//+------------------------------------------------------------------+
void writer(string fileName,string headder,string row)
  {
   bool isFile=FileIsExist(fileName,FILE_COMMON); // Flag of whether the file exists
   int file_handle=FileOpen(fileName,FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_SHARE_WRITE|FILE_SHARE_READ); // Open file
   if(file_handle) // If the file has opened
     {
      FileSeek(file_handle,0,SEEK_END); // Move cursor to the file end
      if(!isFile) // If it is a newly created file, write a header
         FileWrite(file_handle,headder);
      FileWrite(file_handle,row); // Write message
      FileClose(file_handle); // Close the file
     }
  }

Os dados são gravados no sandbox de arquivos localizado em Terminal/Common/Files. A ideia da função é gravar os dados em um arquivo adicionando linhas a ele, é por isso que nós abrimos o arquivo, obtemos o seu manipulador e movemos para o final do arquivo. Se o arquivo acabou de ser criado, nós escrevemos os cabeçalhos passados, caso contrário, ignoramos esse parâmetro.

Quanto à macro, ela se destina apenas à adição facilitada dos parâmetros do robô ao arquivo.

#define WRITE_BOT_PARAM(fileName,param) writer(fileName,"",#param+";"+(string)param);

Nesta macro, nós utilizamos a vantagem das macros e adicionamos uma linha contendo o nome da macro e o seu valor. Uma variável como parâmetro é inserida na função. Mais detalhes serão mostrados no exemplo de uso da macro. 

O método principal SaveReportToFile é bastante demorado, é por isso que eu apenas forneço algumas partes do código. O método cria uma instância da classe CDealHistoryGetter e recebe uma matriz com o histórico de negociação acumulado, no qual uma linha indica um negócio.

DealDetales history[];
CDealHistoryGetter dealGetter(_comission_manager);
dealGetter.getDealsDetales(history,0,TimeCurrent());

Em seguida, ele verifica se o histórico não está vazio, cria a instância da classe CReportCreator e recebe estruturas com os principais coeficientes:

if(ArraySize(history)==0)
   return;

CReportCreator reportCreator(_comission_manager);
reportCreator.Create(history,0);

TotalResult totalResult;
reportCreator.GetTotalResult(totalResult);
PL_detales pl_detales;
reportCreator.GetPL_detales(pl_detales);

Em seguida, ele salva os dados do histórico em um loop, usando a função 'writer'. No final do ciclo, são adicionados os campos com os seguintes coeficientes e valores:

  • PL
  • Total trades
  • Consecutive wins
  • Consecutive Drawdowns
  • Recovery factor
  • Profit factor
  • Payoff
  • Drawdown by pl

writer(fileName,"","==========================================================================================================");
writer(fileName,"","PL;"+DoubleToString(totalResult.total.PL)+";");
int total_trades=pl_detales.total.profit.orders+pl_detales.total.drawdown.orders;
writer(fileName,"","Total trdes;"+IntegerToString(total_trades));
writer(fileName,"","Consecutive wins;"+IntegerToString(pl_detales.total.profit.dealsInARow));
writer(fileName,"","Consecutive DD;"+IntegerToString(pl_detales.total.drawdown.dealsInARow));
writer(fileName,"","Recovery factor;"+DoubleToString(totalResult.total.recoveryFactor)+";");
writer(fileName,"","Profit factor;"+DoubleToString(totalResult.total.profitFactor)+";");
double payoff=MathAbs(totalResult.total.averageProfit/totalResult.total.averageDD);
writer(fileName,"","Payoff;"+DoubleToString(payoff)+";");
writer(fileName,"","Drawdown by pl;"+DoubleToString(totalResult.total.maxDrawdown.byPL)+";");

A operação do método é concluída aqui. Agora vamos considerar uma maneira fácil de baixar o histórico: vamos adicionar esse recurso a um Expert Advisor a partir do pacote da biblioteca padrão, Experts/Examples/Moving Average/Moving Average.mq5. Primeiramente, nós precisamos conectar o nosso arquivo:

#include <History manager/ShortReport.mqh>

Em seguida, adicionamos às entradas as variáveis que definem a comissão e o slippage personalizados:

input double custom_comission = 0; // Custom commission;
input int custom_shift = 0; // Custom shift;

Se nós quisermos que nossa comissão e slippage sejam definidas diretamente e não condicionalmente (veja a descrição da classe CDealHistoryGetter no artigo anterior), antes de incluir o arquivo, nós precisamos determinar o parâmetro ONLY_CUSTOM_COMISSION, como é mostrado abaixo:

#define ONLY_CUSTOM_COMISSION
#include <History manager/ShortReport.mqh>

Em seguida, criamos a amostra da classe CCCM. No método OnInit, nós adicionamos a comissão e o slippage a essa instância de classe, que armazena as comissões.

CCCM _comission_manager_;

...

int OnInit(void)
  {
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);  

...

  }

Em seguida, nós adicionamos as seguintes linhas de código no método OnDeinit:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string file_name = __FILE__+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);

      WRITE_BOT_PARAM(file_name,MaximumRisk);      // Maximum Risk in percentage
      WRITE_BOT_PARAM(file_name,DecreaseFactor);   // Decrease factor
      WRITE_BOT_PARAM(file_name,MovingPeriod);     // Moving Average period
      WRITE_BOT_PARAM(file_name,MovingShift);      // Moving Average shift
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift
     }
  }

Esse código executa uma verificação após a remoção da instância do robô: a condição dele estar em execução no testador. Se o robô estiver em execução no testador, uma função será salva no histórico de negociação do robô em um arquivo chamado "Compiled_file_name Report.csv". Depois que todos os dados foram escritos no arquivo, adicionamos mais 6 linhas nos parâmetros de entrada desse arquivo. Toda vez após o lançamento do Expert Advisor no Strategy Tester no modo de teste, nós receberemos um arquivo com a descrição dos negócios realizados pelo EA. Este arquivo será substituído sempre que nós iniciarmos um novo teste. O arquivo será armazenado na sandbox de arquivos, no diretório Common/Files.

Baixando o histórico de negociações dividido por negócios

Agora vamos ver como fazer o download de um relatório de negociação detalhado, ou seja, um relatório no qual todos os negócios estão agrupados em posições. Para esse fim, nós usaremos o arquivo DealsHistory.mqh que já contém a inclusão do arquivo ShortReport.mqh. Isso significa que, ao incluir o único arquivo DealsHistory.mqh, nós podemos usar os dois métodos ao mesmo tempo.

O arquivo contém duas funções. A primeira é uma função comum, que nos permite somar as linhas:

void AddRow(string item, string &str)
  {
   str += (item + ";");
  }

A segunda função grava os dados em um arquivo usando a função 'writer', que foi considerada anteriormente. Sua implementação completa é exibida abaixo.

void WriteDetalesReport(string fileName,CCCM *_comission_manager)
  {

   if(FileIsExist(fileName,FILE_COMMON))
     {
      FileDelete(fileName,FILE_COMMON);
     }

   CDealHistoryGetter dealGetter(_comission_manager);

   DealKeeper deals[];
   dealGetter.getHistory(deals,0,TimeCurrent());

   int total= ArraySize(deals);

   string headder = "Asset;From;To;Deal DT (Unix seconds); Deal DT (Unix miliseconds);"+
                    "ENUM_DEAL_TYPE;ENUM_DEAL_ENTRY;ENUM_DEAL_REASON;Volume;Price;Comission;"+
                    "Profit;Symbol;Comment";

   for(int i=0; i<total; i++)
     {
      DealKeeper selected = deals[i];
      string asset = selected.symbol;
      datetime from = selected.DT_min;
      datetime to = selected.DT_max;

      for(int j=0; j<ArraySize(selected.deals); j++)
        {
         string row;
         AddRow(asset,row);
         AddRow((string)from,row);
         AddRow((string)to,row);

         AddRow((string)selected.deals[j].DT,row);
         AddRow((string)selected.deals[j].DT_msc,row);
         AddRow(EnumToString(selected.deals[j].type),row);
         AddRow(EnumToString(selected.deals[j].entry),row);
         AddRow(EnumToString(selected.deals[j].reason),row);
         AddRow((string)selected.deals[j].volume,row);
         AddRow((string)selected.deals[j].price,row);
         AddRow((string)selected.deals[j].comission,row);
         AddRow((string)selected.deals[j].profit,row);
         AddRow(selected.deals[j].symbol,row);
         AddRow(selected.deals[j].comment,row);

         writer(fileName,headder,row);

        }

      writer(fileName,headder,"");
     }


  }

Após receber os dados das negociações e criar o cabeçalho, nós avançamos para a escrita do relatório de negociação detalhado. Para esse propósito, nós implementamos um loop com um loop aninhado: a iteração principal trabalha com as posições e a iteração aninhada através de negócios dentro dessas posições. Após escrever cada nova posição (ou seja, a série de negócios que constituem a posição), as posições são separadas usando o caractere espaço. Isso garante uma leitura eficiente do arquivo resultante. Nós não precisamos fazer mudanças drásticas para adicionar esse recurso ao robô, mas realizar apenas uma chamada na OnDeinit:

void OnDeinit(const int reason)
  {
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string file_name = __FILE__+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);

      WRITE_BOT_PARAM(file_name,MaximumRisk);      // Maximum Risk in percentage
      WRITE_BOT_PARAM(file_name,DecreaseFactor);   // Descrease factor
      WRITE_BOT_PARAM(file_name,MovingPeriod);     // Moving Average period
      WRITE_BOT_PARAM(file_name,MovingShift);      // Moving Average shift
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(__FILE__+" Deals Report.csv", &_comission_manager_);
     }
  }

Para demonstrar em detalhes como o download dos dados é realizado, aqui está um modelo de EA vazio com os métodos adicionados para baixar o relatório:

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define ONLY_CUSTOM_COMISSION
#include <History manager/DealsHistory.mqh>

input double custom_comission   = 0;       // Custom commission;
input int    custom_shift       = 0;       // Custom shift;

CCCM _comission_manager_;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string arr[];
      StringSplit(__FILE__,'.',arr);
      string file_name = arr[0]+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(arr[0]+" Deals Report.csv", &_comission_manager_);
     }
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Ao adicionar qualquer lógica desejada ao modelo acima, nós podemos ativar a geração de um relatório de negociação após a conclusão dos testes do EA no Strategy Tester.

Wrapper de DLL para a criação do histórico de negociação acumulado

O primeiro artigo desta série foi dedicada à criação de uma DLL na linguagem C# para trabalhar com os relatórios de otimização. Como o formato mais conveniente para a otimização Walk Forward contínua é o XML, nós criamos uma DLL que pode ler, gravar e gerar o relatório resultante. No Expert Advisor, nós precisamos apenas da funcionalidade de gravação de dados. No entanto, como as operações com as funções puras são menos convenientes que os objetos, foi criado uma classe wrapper para a função de download de dados. Este objeto está localizado no arquivo XmlHistoryWriter.mqh, ele é chamado de СXmlHistoryWriter. Além do objeto, ele define a estrutura dos parâmetros do EA. Essa estrutura será usada ao passar a lista de parâmetros do EA para o objeto. Vamos considerar todos os detalhes da implementação. 

Para habilitar a criação do relatório de otimização, incluímos o arquivo ReportCreator.mqh. Para usar os métodos static da classe da DLL descrita no primeiro artigo, vamos importá-la (a biblioteca já deve estar disponível no diretório MQL5/Libraries).

#include "ReportCreator.mqh"
#import "ReportManager.dll"

Depois de adicionar os links necessários, asseguramos a adição conveniente dos parâmetros do robô à coleção de parâmetros, que é então passada para a classe de destino. 

struct BotParams
  {
   string            name,value;
  };

#define ADD_TO_ARR(arr, value) \
{\
   int s = ArraySize(arr);\
   ArrayResize(arr,s+1,s+1);\
   arr[s] = value;\
}

#define APPEND_BOT_PARAM(Var,BotParamArr) \
{\
   BotParams param;\
   param.name = #Var;\
   param.value = (string)Var;\
   \
   ADD_TO_ARR(BotParamArr,param);\
}

Como nós vamos operar com uma coleção de objetos, será mais fácil trabalhar com arrays dinâmicos. A macro ADD_TO_ARR foi criada para a adição conveniente de elementos ao array dinâmico. A macro altera o tamanho da coleção e adiciona o item que foi passado a ela. A macro fornece uma solução universal. E assim, agora nós podemos adicionar rapidamente os valores de qualquer tipo ao array dinâmico.

A próxima macro trabalha diretamente com os parâmetros. Essa macro cria uma instância da estrutura BotParams e a adiciona ao array inserindo apenas o array, que a descrição do parâmetro deve ser adicionada e uma variável que armazena esse parâmetro. A macro atribuirá um nome apropriado para o parâmetro com base no nome da variável e atribuirá o valor do parâmetro convertido para um formato do tipo string.

O formato da sequência garante a correspondência adequada das configurações nos arquivos *.set e dos dados que estão sendo salvos no arquivo *.xml. Como já descrito nos artigos anteriores, os arquivos set armazenam os parâmetros de entrada do EA no formato de valor-chave, no qual o nome da variável, como no código, é aceito como chave e o valor atribuído ao parâmetro de entrada é aceito como valor. Todas as enumerações (num) devem ser especificadas como um tipo int e não como resultado da função EnumToString(). A macro descrita converte todos os parâmetros para o tipo de string necessário, enquanto todas as enumerações são primeiro convertidas em int e depois no formato de string necessário.

Nós também declaramos uma função, que permite copiar o array de parâmetros do robô para outro array.

void CopyBotParams(BotParams &dest[], const BotParams &src[])
  {
   int total = ArraySize(src);
   for(int i=0; i<total; i++)
     {
      ADD_TO_ARR(dest,src[i]);
     }
  }

Nós precisamos disso porque a função ArrayCopy padrão não funciona com o array de estruturas. 

A classe do wrapper é declarada da seguinte maneira:

class CXmlHistoryWriter
  {
private:
   const string      _path_to_file,_mutex_name;
   CReportCreator    _report_manager;

   string            get_path_to_expert();//

   void              append_bot_params(const BotParams  &params[]);//
   void              append_main_coef(PL_detales &pl_detales,
                                      TotalResult &totalResult);//
   double            get_average_coef(CoefChartType type);
   void              insert_day(PLDrawdown &day,ENUM_DAY_OF_WEEK day);//
   void              append_days_pl();//

public:
                     CXmlHistoryWriter(string file_name,string mutex_name,
                     CCCM *_comission_manager);//
                     CXmlHistoryWriter(string mutex_name,CCCM *_comission_manager);
                    ~CXmlHistoryWriter(void) {_report_manager.Clear();} //

   void              Write(const BotParams &params[],datetime start_test,datetime end_test);//
  };

Dois campos do tipo stringo e constante são declarados para a escrita em um arquivo:

  • _path_to_file
  • _mutex_name

O primeiro campo contém o caminho para um arquivo no qual os dados serão gravados. O segundo campo contém o nome do mutex que foi usado. A implementação do mutex nomeado é fornecido na DLL da C#. Nós precisamos desse mutex porque o processo de otimização será implementado em diferentes threads, em diferentes núcleos e em diferentes processos (um robô em execução = um processo). Portanto, pode ocorrer uma situação na qual duas otimizações foram concluídas e dois ou mais processos simultaneamente tentam gravar o resultado no mesmo arquivo, o que é inaceitável. Para eliminar essa situação, nós usamos um objeto de sincronização baseado no núcleo do sistema operacional, ou seja, um mutex nomeado. 

A instância da classe CReportCreator é necessária como um campo, porque outras funções acessarão esse objeto e, portanto, seria ilógico criá-lo sempre que necessário. Agora vamos ver a implementação de cada método.

Vamos começar com o construtor da classe.

CXmlHistoryWriter::CXmlHistoryWriter(string file_name,
                                     string mutex_name,
                                     CCCM *_comission_manager) : _mutex_name(mutex_name),
   _path_to_file(TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\"+file_name),
   _report_manager(_comission_manager)
  {
  }
CXmlHistoryWriter::CXmlHistoryWriter(string mutex_name,
                                     CCCM *_comission_manager) : _mutex_name(mutex_name),
   _path_to_file(TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\"+MQLInfoString(MQL_PROGRAM_NAME)+"_"+"Report.xml"),
   _report_manager(_comission_manager)
  {
  }

A classe contém dois construtores. Preste atenção ao segundo construtor, que define o nome do arquivo no qual o relatório de otimizações é armazenado. No otimizador automático, que será considerado no próximo artigo, será possível definir os gerenciadores de otimização personalizados. O gerenciador por padrão já possui um contrato implementado sobre a nomeação de arquivos de relatório gerados pelo robô. Assim, o segundo construtor define esse tipo de tratamento. De acordo com ele, o nome do arquivo deve começar com o nome do EA, seguido pelo sublinhado e pelo sufixo "_Report.xml". Embora uma DLL possa gravar um arquivo de relatório em qualquer lugar do PC, para preservar as informações que o arquivo pertence à operação da plataforma, o armazenaremos sempre ocorrerá no diretório comum da sandbox da MetaTrader 5. 

O método que recebe o caminho para o Expert Advisor:

string CXmlHistoryWriter::get_path_to_expert(void)
  {
   string arr[];
   StringSplit(MQLInfoString(MQL_PROGRAM_PATH),'\\',arr);
   string relative_dir=NULL;

   int total= ArraySize(arr);
   bool save= false;
   for(int i=0; i<total; i++)
     {
      if(save)
        {
         if(relative_dir== NULL)
            relative_dir=arr[i];
         else
            relative_dir+="\\"+arr[i];
        }

      if(StringCompare("Experts",arr[i])==0)
         save=true;
     }

   return relative_dir;
  }

O caminho do EA é necessário para o lançamento automatizado do Expert Advisor selecionado. Para esse propósito, o seu caminho deve ser especificado no arquivo ini que é passado no início da plataforma. O caminho deve ser especificado em relação à pasta Experts e não ao caminho completo, obtido pela função que recebe o caminho para o EA atual. Portanto, nós primeiro precisamos dividir o caminho obtido em componentes, nos quais o separador é uma barra. Então, em um loop, buscar a pasta Experts, começando com a primeira pasta. Uma vez encontrada, gerar o caminho para o robô começando com a próxima pasta (ou o arquivo do EA, se estiver localizado diretamente na raiz do diretório desejado).

O método append_bot_params:

Este método é um wrapper para o método importado com o mesmo nome. Sua implementação é a seguinte:

void CXmlHistoryWriter::append_bot_params(const BotParams &params[])
  {

   int total= ArraySize(params);
   for(int i=0; i<total; i++)
     {
      ReportWriter::AppendBotParam(params[i].name,params[i].value);
     }
  }

O array de parâmetros do EA mencionado anteriormente é inserido nesse método. Em seguida, chamamos o método importado da nossa DLL para cada um dos parâmetros do EA.

A implementação do método append_main_coef é fácil, portanto, nós não vamos considerá-la aqui. Ele aceita estruturas da classe CReportCreator como parâmetros de entrada.

O método get_average_coef destina-se ao cálculo dos valores médios dos coeficientes por um método simples da MA com base nos gráficos dos coeficientes passados. El é usado para calcular o fator de lucro médio e o fator de recuperação médio.  

O método insert_day é um wrapper chamado para o método importado ReportWriter::AppendDay. O método append_days_pl já usa o wrapper mencionado anteriormente.

Entre todos esses métodos de wrapper, existe um método público que atua como o principal — é o método 'Write', que aciona todo o mecanismo para salvar os dados.

void CXmlHistoryWriter::Write(const BotParams &params[],datetime start_test,datetime end_test)
  {
   if(!_report_manager.Create())
     {
      Print("##################################");
      Print("Can`t create report:");
      Print("###################################");
      return;
     }
   TotalResult totalResult;
   _report_manager.GetTotalResult(totalResult);
   PL_detales pl_detales;
   _report_manager.GetPL_detales(pl_detales);

   append_bot_params(params);
   append_main_coef(pl_detales,totalResult);

   ReportWriter::AppendVaR(totalResult.total.VaR_absolute.VAR_90,
                           totalResult.total.VaR_absolute.VAR_95,
                           totalResult.total.VaR_absolute.VAR_99,
                           totalResult.total.VaR_absolute.Mx,
                           totalResult.total.VaR_absolute.Std);

   ReportWriter::AppendMaxPLDD(pl_detales.total.profit.totalResult,
                               pl_detales.total.drawdown.totalResult,
                               pl_detales.total.profit.orders,
                               pl_detales.total.drawdown.orders,
                               pl_detales.total.profit.dealsInARow,
                               pl_detales.total.drawdown.dealsInARow);
   append_days_pl();

   string error_msg=ReportWriter::MutexWriter(_mutex_name,get_path_to_expert(),AccountInfoString(ACCOUNT_CURRENCY),
                    _report_manager.GetBalance(),
                    (int)AccountInfoInteger(ACCOUNT_LEVERAGE),
                    _path_to_file,
                    _Symbol,(int)Period(),
                    start_test,
                    end_test);
   if(StringCompare(error_msg,"")!=0)
     {
      Print("##################################");
      Print("Error while creating (*.xml) report file:");
      Print("_________________________________________");
      Print(error_msg);
      Print("###################################");
     }
  }

Se uma tentativa de criar um relatório falhar, uma entrada apropriada será adicionada aos logs. Se o relatório for criado com êxito, nós passamos a receber os coeficientes desejados e depois chamamos os métodos mencionados acima, um por um. De acordo com o primeiro artigo, esses métodos adicionam os parâmetros solicitados em uma classe C#. Então, é chamado um método que grava os dados em um arquivo. Se a escrita falhar, error_msg irá conter o texto do erro, que é gravado nos logs do testador. 

A classe resultante pode gerar o relatório de negociação, bem como a gravar os dados em um arquivo após a chamada do método 'Write'. No entanto, eu quero simplificar ainda mais o processo, para que nós lidemos apenas com os parâmetros de entrada e nada mais. A classe a seguir foi criada para esse fim.

A classe CAutoUpLoader gera automaticamente um relatório de negociação após a conclusão do teste. Está localizado no arquivo AutoLoader.mqh. Para a operação da classe, nós devemos adicionar um link à classe descrita anteriormente, que gera o relatório no formato XML.

#include <History manager/XmlHistoryWriter.mqh>

A assinatura da classe é simples:

class CAutoUploader
  {
private:

   datetime          From,Till;
   CCCM              *comission_manager;
   BotParams         params[];
   string            mutexName;

public:
                     CAutoUploader(CCCM *comission_manager, string mutexName, BotParams &params[]);
   virtual          ~CAutoUploader(void);

   virtual void      OnTick();

  };

A classe possui um método OnTick de sobrecarga, além de um destruidor virtual. Isso garante que a classe possa ser aplicada usando agregação e herança. Aqui está o que eu quero dizer. O objetivo desta classe é sobrescrever o tempo de conclusão do teste em cada tick e lembrar o horário de início do teste. Esses dois parâmetros são necessários para usar o objeto descrito anteriormente. Existem várias abordagens para sua aplicação: nós podemos instanciar esse objeto em algum lugar da classe do robô (se o robô for desenvolvido usando POO) ou no escopo global — essa solução pode ser usada para a programação do tipo C.

Depois disso, o método OnTick() da instância da classe é chamado nessa função. Após destruir o objeto de classe, o relatório de negociação será descarregado em seu destruidor. A segunda maneira de aplicar a classe é herdar uma classe do EA dela. O destruidor virtual e o método de sobrecarga da OnTick() são criados para esse fim. Como resultado da aplicação do segundo método, nós trabalharemos diretamente com o Expert Advisor. A implementação desta classe é simples, pois ela delega a operação para a classe CXmlHistoryWriter:

void CAutoUploader::OnTick(void)
  {
   if(MQLInfoInteger(MQL_OPTIMIZATION)==1 ||
      MQLInfoInteger(MQL_TESTER)==1)
     {
      if(From == 0)
         From = iTime(_Symbol,PERIOD_M1,0);
      Till=iTime(_Symbol,PERIOD_M1,0);
     }
  }
CAutoUploader::CAutoUploader(CCCM *_comission_manager,string _mutexName,BotParams &_params[]) : comission_manager(_comission_manager),
   mutexName(_mutexName)
  {
   CopyBotParams(params,_params);
  }
CAutoUploader::~CAutoUploader(void)
  {
   if(MQLInfoInteger(MQL_OPTIMIZATION)==1 ||
      MQLInfoInteger(MQL_TESTER)==1)
     {
      CXmlHistoryWriter historyWriter(mutexName,
                                      comission_manager);

      historyWriter.Write(params,From,Till);
     }
  }

Vamos adicionar a funcionalidade descrita ao nosso EA modelo:

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define ONLY_CUSTOM_COMISSION
#include <History manager/DealsHistory.mqh>
#include <History manager/AutoLoader.mqh>

class CRobot;

input double custom_comission   = 0;       // Custom commission;
input int    custom_shift       = 0;       // Custom shift;

CCCM _comission_manager_;
CRobot *bot;
const string my_mutex = "My Mutex Name for this expert";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);

   BotParams params[];

   APPEND_BOT_PARAM(custom_comission,params);
   APPEND_BOT_PARAM(custom_shift,params);

   bot = new CRobot(&_comission_manager_,my_mutex,params);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string arr[];
      StringSplit(__FILE__,'.',arr);
      string file_name = arr[0]+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(arr[0]+" Deals Report.csv", &_comission_manager_);
     }

   delete bot;
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   bot.OnTick();
  }
//+------------------------------------------------------------------+


//+------------------------------------------------------------------+
//| Main Robot class                                                 |
//+------------------------------------------------------------------+
class CRobot : CAutoUploader
  {
public:
                     CRobot(CCCM *_comission_manager, string _mutexName, BotParams &_params[]) : CAutoUploader(_comission_manager,_mutexName,_params)
     {}

   void              OnTick() override;
  };

//+------------------------------------------------------------------+
//| Robot logic triggering method                                    |
//+------------------------------------------------------------------+
void CRobot::OnTick(void)
  {
   CAutoUploader::OnTick();

   Print("This should be the robot logic start");
  }
//+------------------------------------------------------------------+

Assim, a primeira coisa a fazer é adicionar uma referência para um arquivo em que a classe wrapper para download automático do relatório em XML está armazenada. A classe do robô é predeterminada, porque é mais fácil implementá-la e descrevê-la no final do projeto. Geralmente, eu crio algoritmos como projetos em MQL5, embora isso seja muito mais conveniente do que a abordagem de um arquivo, porque a classe com o robô e as classes relacionadas são divididas em arquivos. No entanto, para maior comodidade, tudo foi colocado em um arquivo.
Então, a classe é descrita. Neste exemplo, ela é uma classe vazia com um método OnTick sobrecarregado. Assim, a segunda maneira de aplicação do CAutoUploader, é a herança. Observe que, no método OnTick sobrecarregado, é necessário chamar explicitamente o método OnTick da classe base para que o cálculo das datas não pare. Isso é essencial para toda a operação do otimizador automático.

O próximo passo é criar um ponteiro para a classe com o robô, porque é mais conveniente preenchê-lo pelo método OnInit, e não pelo escopo global. Além disso, é criado uma variável que armazena o nome do mutex.

O robô é instanciado no método OnInit, e é excluído no OnDeinit. Para garantir a passagem de um novo tick para o robô, nós chamamos o método OnTick() sobrecarregado no ponteiro do robô. Feito isso, escrevemos o robô na classe CRobot.

As variantes para o download do relatório por agregação ou pela criação de uma instância do CAutoUpLoader no escopo global são semelhantes. Se você tiver alguma dúvida, não hesite em entrar em contato comigo. 

Portanto, usando esse modelo de robô ou adicionando as chamadas apropriadas aos algoritmos existentes, você pode usá-las juntamente com o otimizador automático, que será discutido no próximo artigo.

Conclusão

No primeiro artigo, foi analisado o mecanismo de operação com arquivos de relatório em XML e a criação da estrutura de arquivos. A criação de relatórios foi considerada no segundo artigo. O mecanismo de geração de relatório foi examinado, começando com o objeto de download do histórico e terminando com os objetos que geram o relatório. Ao estudar os objetos envolvidos no processo de criação do relatório, a parte do cálculo foi analisada em detalhes. O artigo também continha as principais fórmulas de coeficiente, bem como a descrição de possíveis problemas de cálculo.

Como foi mencionado na introdução a este artigo, os objetos descritos nesta parte servem como uma ponte entre o mecanismo de download de dados e o de geração de relatórios. Além das funções que salvam os arquivos de relatórios de negociação, o artigo contém a descrição das classes que participam do descarregamento de relatórios XML, bem como a descrição dos modelos de robô que podem usar automaticamente esses recursos. O artigo também descreveu como adicionar os recursos criados a um algoritmo existente. Isso significa que os usuários do otimizador automático podem otimizar algoritmos antigos e novos.   

Duas pastas estão disponíveis no arquivo anexado. Descompacte os dois no diretório MQL/Include. A biblioteca ReportManager.dll deve ser adicionada à MQL5/Libraries. Você pode fazer o download dele no artigo anterior.

Os seguintes arquivos estão incluídos no anexo:

  1. CustomGeneric
    • GenericSorter.mqh
    • ICustomComparer.mqh
  2. History manager
    • AutoLoader.mqh
    • CustomComissionManager.mqh
    • DealHistoryGetter.mqh
    • DealsHistory.mqh
    • ReportCreator.mqh
    • ShortReport.mqh
    • XmlHistoryWriter

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/7490

Arquivos anexados |
Include.zip (29.82 KB)
Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXX): ordens de negociação pendentes, gerenciamento de objetos-ordens Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXX): ordens de negociação pendentes, gerenciamento de objetos-ordens

No último artigo, criamos classes de objetos-ordens pendentes que correspondem ao conceito geral de objetos de biblioteca. Hoje, trataremos de classes que permitem gerenciar objetos de ordens pendentes.

Redes Neurais de Maneira Fácil Redes Neurais de Maneira Fácil

A inteligência artificial é frequentemente associada a algo fantasticamente complexo e incompreensível. Ao mesmo tempo, a inteligência artificial é cada vez mais mencionada na vida cotidiana. Notícias sobre conquistas relacionadas ao uso de redes neurais geralmente aparecem em diferentes mídias. O objetivo deste artigo é mostrar que qualquer pessoa pode criar facilmente uma rede neural e usar as conquistas da IA na negociação.

Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXXI): ordens de negociação pendentes, abertura de posições por condições Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXXI): ordens de negociação pendentes, abertura de posições por condições

A partir deste artigo, criaremos um recurso que permite negociar através de solicitações pendentes de acordo com uma determinada condição: se atingirmos/ou ultrapassarmos uma determinada hora, se ultrapassarmos um lucro predeterminado ou se for registrado um evento de fechamento de posição por stop-loss.

Abordagem econométrica para a busca de padrões de mercado: Autocorrelação, Mapas de Calor e Gráficos de Dispersão Abordagem econométrica para a busca de padrões de mercado: Autocorrelação, Mapas de Calor e Gráficos de Dispersão

O artigo apresenta um estudo extenso das características sazonais: autocorrelação, mapas de calor e gráficos de dispersão. O objetivo do artigo é mostrar que a "memória de mercado" é de natureza sazonal, na qual ela é expressa através da correlação maximizada de incrementos de ordem arbitrária.