Colorindo os resultados da otimização de estratégias de negociação

Dmitry Fedoseev | 16 abril, 2019

Introdução

Após a otimização, nós precisamos selecionar apenas um de uma grande variedade de conjuntos de parâmetros. Não há uma resposta clara sobre qual critério usar ao selecionar tal conjunto: rentabilidade, rebaixamento, fator de recuperação ou alguma combinação desses ou de outros parâmetros. Mas como avaliar a combinação dos parâmetros?

Neste artigo nós vamos realizar um experimento: nós vamos colorir os resultados da otimização. A cor é determinada por três parâmetros: os níveis de vermelho, verde e azul (RGB). Existem outros métodos de codificação de cores, que também usam três parâmetros. Assim, três parâmetros de teste podem ser convertidos em uma cor, que representa visualmente os valores. No final deste artigo, nós descobriremos se essa representação pode ser útil.

Dados iniciais

No artigo Analisando resultados de negociação usando relatórios HTML, nós criamos uma biblioteca de funções para analisar os arquivos do relatório, HTMLReport.mqh. A biblioteca inclui a função OptimizerXMLReportToStruct(), projetada para operações com resultados de otimização. Nós vamos usar essa função, na qual dois parâmetros são passados para a função:

  • string aFileName é o nome do arquivo com o relatório de otimização. O arquivo deve estar disponível na pasta MQL5/Files do diretório de dados do terminal.
  • SOptimization & aOptimization são passados por referência. Após a execução da função, os dados extraídos do relatório estarão localizados nessa estrutura.

A estrutura SOptimisation:

struct SOptimization{
   string ParameterName[];
   SPass Pass[];
};

A estrutura inclui dois arrays: string ParameterName[] e SPass Pass[]. Os nomes dos parâmetros a serem otimizados estão localizados em ParameterName[]. Nós estamos interessados principalmente no segundo array SPass[]: um elemento desse array contém dados sobre um passo de otimização.

A estrutura do SPass:

struct SPass{
   string Pass;
   string Result;
   string Profit;
   string ExpectedPayoff;
   string ProfitFactor;
   string RecoveryFactor;
   string SharpeRatio;
   string Custom;
   string EquityDD_perc;
   string Trades;
   string Parameters[];
};

Os campos da estrutura:

  • Pass — o número do passo da otimização;
  • Result — o saldo final após a otimização;
  • Profit — valor de lucro resultante;
  • ExpectedPayoff — o valor do retorno esperado;
  • ProfitFactor — o valor do fator de lucro;
  • RecoveryFactor — fator de recuperação;
  • SharpeRatio — Sharpe ratio;
  • Custom — um parâmetro personalizado;
  • EquityDD_perc — rebaixamento em porcentagem;
  • Trades — número de negociações;
  • Parameters[] — um array com os valores dos parâmetros otimizados.

Abaixo estão os parâmetros mais populares para analisar os resultados da negociação:

  • Lucratividade, o lucro médio por negociação
  • Rebaixamento, a diminuição do capital em relação ao valor máximo
  • Fator de recuperação, a relação entre o lucro absoluto e o rebaixamento máximo

Primeiramente, nós vamos usar esses parâmetros. No entanto, o relatório inclui outros valores e, portanto, nós precisamos fornecer a possibilidade de usá-los. 

Para ativar a seleção de parâmetros arbitrários, nós criaremos uma estrutura adicional para substituir o SPass. Os parâmetros de otimização estarão localizados no array do tipo double dentro dessa estrutura. Nós não iremos reescrever completamente a estrutura, mas nós usaremos as possibilidades de herança. Vamos prosseguir com a implementação:

1. Crie o arquivo ColorOptimization.mqh. Todas as funções para a criação de relatórios coloridos estarão localizadas neste arquivo.

2. Inclua o arquivo HTMLReport.mqh no início do arquivo ColorOptimization.mqh:

#include <HTMLReport.mqh>

3 Crie uma nova estrutura, que herde todos os campos da estrutura SPass e adicione os arrays factor[] e dParameters[] a ela:

struct SPass2:SPass{
   double factor[9];
   double dParameters[];  
};

Ambas os arrays são do tipo double. Nove valores resultantes estarão localizados no array factor[], ou seja, todos, exceto Pass (número de pass) e os parâmetros a serem otimizados. Os valores dos parâmetros de otimização estão localizados no array sParameters[]. Embora todos os dados já estejam disponíveis na estrutura, eles são apresentados em um formato de string, portanto, nós precisaríamos convertê-los em números toda vez que os dados forem usados. O array permite ter os dados em um formato conveniente.

4 Crie a estrutura final para os dados de otimização:

struct SOptimization2{
   string ParameterName[];
   SPass2 Pass[];
};

5. Crie uma função para converter os dados da estrutura de SOptimization para SOptimization2:

void ConvertOptimizationStruct(SOptimization & src,SOptimization2 & dst){

   ArrayCopy(dst.ParameterName,src.ParameterName);
   int cnt=ArraySize(src.Pass);
   ArrayResize(dst.Pass,cnt);   
   for(int i=0;i<cnt;i++){
      ArrayCopy(dst.Pass[i].Parameters,src.Pass[i].Parameters);
      
      dst.Pass[i].Pass=src.Pass[i].Pass;
      dst.Pass[i].Result=src.Pass[i].Result;
      dst.Pass[i].Profit=src.Pass[i].Profit;
      dst.Pass[i].ExpectedPayoff=src.Pass[i].ExpectedPayoff;
      dst.Pass[i].ProfitFactor=src.Pass[i].ProfitFactor;
      dst.Pass[i].RecoveryFactor=src.Pass[i].RecoveryFactor;
      dst.Pass[i].SharpeRatio=src.Pass[i].SharpeRatio;
      dst.Pass[i].Custom=src.Pass[i].Custom;
      dst.Pass[i].EquityDD_perc=src.Pass[i].EquityDD_perc;
      dst.Pass[i].Trades=src.Pass[i].Trades;

      dst.Pass[i].factor[0]=StringToDouble(src.Pass[i].Result);
      dst.Pass[i].factor[1]=StringToDouble(src.Pass[i].Profit);
      dst.Pass[i].factor[2]=StringToDouble(src.Pass[i].ExpectedPayoff);
      dst.Pass[i].factor[3]=StringToDouble(src.Pass[i].ProfitFactor);
      dst.Pass[i].factor[4]=StringToDouble(src.Pass[i].RecoveryFactor);
      dst.Pass[i].factor[5]=StringToDouble(src.Pass[i].SharpeRatio);
      dst.Pass[i].factor[6]=StringToDouble(src.Pass[i].Custom);
      dst.Pass[i].factor[7]=StringToDouble(src.Pass[i].EquityDD_perc);
      dst.Pass[i].factor[8]=StringToDouble(src.Pass[i].Trades);
      
      int pc=ArraySize(src.Pass[i].Parameters);
      
      ArrayResize(dst.Pass[i].dParameters,pc);
      
      for(int j=0;j<pc;j++){
         if(src.Pass[i].Parameters[j]=="true"){
            dst.Pass[i].dParameters[j]=1;
         }
         else if(src.Pass[i].Parameters[j]=="false"){
            dst.Pass[i].dParameters[j]=0;         
         }
         else{
            dst.Pass[i].dParameters[j]=StringToDouble(src.Pass[i].Parameters[j]);
         }
      }
   }   
}

A estrutura de dados com os dados é passada para a função como o primeiro parâmetro e uma nova estrutura é retornada por referência como o segundo parâmetro. O loop através de todos os passes de otimização é executado na função; também alguns campos da estrutura são copiados e, para alguns tipos, é executado a conversão do tipo. O processo geral não é complicado e pode ser entendido a partir do código da função.

Os elementos do array factor[] serão acessados usando a enumeração:

enum EOptimizatrionFactor{
   Result=0,
   Profit=1,
   ExpectedPayoff=2,
   ProfitFactor=3,
   RecoveryFactor=4,
   SharpeRatio=5,
   Custom=6,
   EquityDD_perc=7,
   Trades=8
};

Os valores da opção de enumeração começam com zero e são incrementados por 1 e, portanto, pode não ser necessário especificar os valores. No entanto, os valores são indicados, pois é importante fornecer a correspondência com o array factor[]. Isso ajudará a evitar possíveis erros em novas revisões e adições ao programa. 

6. Crie uma função para carregar o arquivo do relatório na estrutura SOptimization2, que é semelhante ao OptimizerXMLReportToStruct() do HTMLReport.mqh:

bool OptimizerXMLReportToStruct2(string aFileName,SOptimization2 & aOptimization){
   SOptimization tmp;
   if(!OptimizerXMLReportToStruct(aFileName,tmp)){
      return(false);
   }
   ConvertOptimizationStruct(tmp,aOptimization);
   return(true);
}

O nome do arquivo de relatório é passado para a função como o primeiro parâmetro e a estrutura SOptimization2 preenchida é retornada como o segundo parâmetro.

Agora tudo está pronto para resolver a tarefa principal do artigo. 

Criando um relatório colorido

As funções para a criação do relatório colorido estarão localizadas em ColorOptimization.mqh. A chamada dessas funções será realizada a partir de um script.

1. Vamos criar um script ColorOptimization.mq5.

2. Inclua o ColorOptimization.mqh para ColorOptimization.mq5.

#include <ColorOptimization.mqh>

3 Adicione os parâmetros externos ao script. Primeiro nós vamos adicionar uma propriedade indicando que existe uma janela de propriedades e então adicionaremos as variáveis.

Property:

#property script_show_inputs

Variáveis ​​externas:

input string               ReportName     =  "*.xml";
input string               OutputName     =  "ColorOptimization1-1.htm";
input EOptimizatrionFactor Factor1        =  Profit;
input EOptimizatrionFactor Factor2        =  EquityDD_perc;
input EOptimizatrionFactor Factor3        =  RecoveryFactor;
input bool                 Factor1Invert  =  false;
input bool                 Factor2Invert  =  true;
input bool                 Factor3Invert  =  false;
input bool                 Sort           =  true;

Descrição das variáveis:

  • ReportName — o nome do arquivo do relatório de otimização de origem;
  • OutputName — o nome do arquivo para o relatório criado pelo script;
  • Factor1 — o primeiro fator, com base na qual a cor do relatório é determinada;
  • Factor2 — o segundo fator, com base no qual a cor do relatório é determinada;
  • Factor3 — o terceiro fator, com base no qual a cor do relatório é determinada;
  • Factor1Invert — invertendo o primeiro fator;
  • Factor2Invert — invertendo o segundo fator;
  • Factor3Invert — invertendo o terceiro fator;
  • Sort — permite ordenar o relatório final de acordo com a indicação da cor;

4 Na função OnStart() do script, nós declaramos uma variável do tipo SOptimisation2 e recebemos os dados do relatório de origem:

SOptimization2 opt;

if(!OptimizerXMLReportToStruct2(ReportName,opt)){
   Alert("Error OptimizerXMLReportToStruct2");
   return;
}

5. Como o RGB é apenas um dos muitos modelos de cores diferentes, vamos fornecer a possibilidade de novas modificações na biblioteca, em particular, a adição de outros modelos de cores. É por isso que nós começaremos com o cálculo de valores abstratos de 0 a 1, em vez de calcular os valores dos componentes RGB. Em seguida, nós converteremos esses valores em componentes RGB de 0 a 255. Uma indicação de cor separada é usada para cada passagem de otimização, então nós precisamos adicionar ao SPass2 três campos para os componentes de cor. Em vez de adicionar três campos, nós adicionaremos um array de três elementos:

double ColorComponent[3];

6. A função SolveColorComponents() no ColorOptimization.mqh irá calcular os componentes da cor. Os seguintes parâmetros devem ser passados para a função:

  • SOptimization2 & aOpt — dados do relatório de otimização de origem
  • int i1, int i2, int i3 — índices dos valores do relatório de otimização de origem (o array factor[9] da estrutura SPass)
  • bool r1=false, bool r2=false, bool r3=false — variáveis para inverter os valores

Após a execução da função, o array ColorComponents[3] no array de estruturas SPass será preenchido com os valores. 

Para o cálculo dos componentes coloridos, nós precisamos encontrar os valores mínimos e máximos para cada um dos parâmetros e, em seguida, calcular o valor no intervalo de 0 a 1. O código inteiro da função SolveColorComponents() é exibido abaixo:

void SolveColorComponents(  SOptimization2 & aOpt,
                              int i1,int i2,int i3,
                              bool r1=false,bool r2=false,bool r3=false){
   
   double mx[3]={0,0,0};
   double mn[3]={DBL_MAX,DBL_MAX,DBL_MAX};
   
   int size=ArraySize(aOpt.Pass);
   
   for(int i=0;i<size;i++){
      mx[0]=MathMax(mx[0],aOpt.Pass[i].factor[i1]);
      mx[1]=MathMax(mx[1],aOpt.Pass[i].factor[i2]);
      mx[2]=MathMax(mx[2],aOpt.Pass[i].factor[i3]);
      mn[0]=MathMin(mn[0],aOpt.Pass[i].factor[i1]);
      mn[1]=MathMin(mn[1],aOpt.Pass[i].factor[i2]);
      mn[2]=MathMin(mn[2],aOpt.Pass[i].factor[i3]);      
   }

   double c1,c2,c3,d;
   
   for(int i=0;i<size;i++){      
   
      c1=0;
      c2=0;
      c3=0;
   
      d=mx[0]-mn[0];
      if(d!=0){
         c1=(aOpt.Pass[i].factor[i1]-mn[0])/d;
      }
      
      d=mx[1]-mn[1];
      if(d!=0){
         c2=(aOpt.Pass[i].factor[i2]-mn[1])/d; 
      }
      
      d=mx[2]-mn[2];
      if(d!=0){
         c3=(aOpt.Pass[i].factor[i3]-mn[2])/d;       
      }
      
      if(r1)c1=1.0-c1;
      if(r2)c2=1.0-c2;
      if(r3)c3=1.0-c3;
      
      aOpt.Pass[i].ColorComponent[0]=c1;
      aOpt.Pass[i].ColorComponent[1]=c2;      
      aOpt.Pass[i].ColorComponent[2]=c3;   
   }

}

Como chamar esta função do script:

SolveColorComponents(opt,Factor1,Factor2,Factor3,Factor1Invert,Factor2Invert,Factor3Invert);

7. Se a ordenação estiver ativada nos parâmetros do script externo, nós precisamos calcular o fator para a ordenação e executar essa ordenação. O melhor passo de otimização é aquele, durante o qual todos os parâmetros combinados têm o valor máximo. Se esses parâmetros corresponderem ao RGB, a melhor opção é a cor branca. Assim, o fator de classificação deve ser calculado como a média aritmética de três componentes.

Vamos adicionar mais um campo à estrutura SPass2:

double SortFactor; 

As funções para calcular o fator de ordenação devem ser adicionadas ao arquivo ColorOptimization.mqh:

void SolveSortFactor(SOptimization2 & aOpt){

   int size=ArraySize(aOpt.Pass);
   
   for(int i=0;i<size;i++){
      aOpt.Pass[i].SortFactor=0;
      for(int j=0;j<3;j++){
         aOpt.Pass[i].SortFactor+=aOpt.Pass[i].ColorComponent[j];
      }
      aOpt.Pass[i].SortFactor/=3;
   }
}

Abaixo está a função de ordenação (usando o método de ordenação bubble sort):

void SortFactorSort(SOptimization2 & aOpt){
   int size=ArraySize(aOpt.Pass);
   for(int i=size-1;i>0;i--){
      for(int j=0;j<i;j++){
         if(aOpt.Pass[j].SortFactor<aOpt.Pass[j+1].SortFactor){
            SPass2 tmp=aOpt.Pass[j];
            aOpt.Pass[j]=aOpt.Pass[j+1];
            aOpt.Pass[j+1]=tmp;
         }
      }
   }
}

Chame essas funções do script. O fator de ordenação é usado não apenas para classificar a tabela, portanto o SolveSortFactor() será chamado independentemente do valor da variável Sort:

SolveSortFactor(opt);
if(Sort){   
   SortFactorSort(opt);
}

Agora tudo está pronto para a criação dos relatórios. O relatório será composto de duas partes. A primeira é uma cópia da tabela de dados de otimização com um botão colorido adicional (Fig. 1). A segunda parte consiste de vários planos coloridos (tabelas) para cada par de parâmetros otimizados, cada célula exibirá o gradiente refletindo mudanças nos resultados do teste para o par de parâmetros otimizados (Fig. 2).

Tabela com indicação de cor

A criação de uma tabela com uma indicação de cor adicional é executada na função TableContent(). Essa função está localizada no arquivo ColorOptimization.mqh e retorna o código HTML da tabela.

A criação de uma tabela HTML é uma tarefa simples. A cor de uma célula de indicação de cor é definida especificando o estilo da célula, o atributo 'background-color'. Os componentes de cor variando entre 0 e 1 podem ser facilmente convertidos em componentes que variam de 1 a 255, multiplicando os valores. Para fornecer mais informações visuais na tabela, vamos adicionar detalhes sobre qual parâmetro de otimização corresponde a essa ou aquela cor. Estes dados serão especificados na célula superior da coluna do indicador de cor, e as células superiores dos parâmetros terão a cor apropriada (Fig. 1).

Fragmento do relatório com a indicação de cor
Fig. 1. Fragmento do relatório com a indicação de cor

A função TableContent() inteira é mostrada abaixo:

string TableContent(SOptimization2 & aOpt,int i1,int i2,int i3){
   
   int size=ArraySize(aOpt.Pass);
     
   int pc=ArraySize(aOpt.ParameterName);

   int nc=ArraySize(co_names);
   
   string s="<table>";
   
   s=s+"<tr>";
   s=s+"<th>Pass</td>";
   
   for(int i=0;i<nc;i++){
      s=s+"<th"+HStyle(i,i1,i2,i3)+">"+co_names[i]+"</th>";   
   }
   
   s=s+"<th>"+ColorCollHeader(i1,i2,i3)+"</th>";  
   
   for(int j=0;j<pc;j++){
      s=s+"<th>"+aOpt.ParameterName[j]+"</th>";       
   }
   s=s+"</tr>";     
   
   int r,g,b;
   
   for(int i=0;i<size;i++){    
   
      ComponentsToRGB(aOpt.Pass[i].ColorComponent[0],
                      aOpt.Pass[i].ColorComponent[1],
                      aOpt.Pass[i].ColorComponent[2],
                      r,g,b);
   
      s=s+"<tr>";
   
      s=s+"<td>"+aOpt.Pass[i].Pass+"</td>";
      s=s+"<td>"+aOpt.Pass[i].Result+"</td>";   
      s=s+"<td>"+aOpt.Pass[i].Profit+"</td>";         
      s=s+"<td>"+aOpt.Pass[i].ExpectedPayoff+"</td>";   
      s=s+"<td>"+aOpt.Pass[i].ProfitFactor+"</td>";               
      s=s+"<td>"+aOpt.Pass[i].RecoveryFactor+"</td>";        
      s=s+"<td>"+aOpt.Pass[i].SharpeRatio+"</td>";               
      s=s+"<td>"+aOpt.Pass[i].Custom+"</td>";   
      s=s+"<td>"+aOpt.Pass[i].EquityDD_perc+"</td>";        
      s=s+"<td>"+aOpt.Pass[i].Trades+"</td>";               
      
      string cs=RGBToStr(r,g,b);
      s=s+"<td title='"+cs+"' style='background-color: "+cs+"'>&nbsp</td>";        
      
      for(int j=0;j<pc;j++){
         s=s+"<td>"+aOpt.Pass[i].Parameters[j]+"</td>";       
      }

      s=s+"</tr>";   
   
   }
   
   s=s+"</table>";   

   return(s);
   
}

Vamos considerar essa função com mais detalhes. Nós recebemos o número de passes de otimização para a variável 'size', o número de parâmetros otimizados é recebido para a variável pc e o tamanho do array com os nomes dos parâmetros (que é declarado no nível global) é recebido para a variável nc:

int size=ArraySize(aOpt.Pass);
     
int pc=ArraySize(aOpt.ParameterName);

int nc=ArraySize(co_names);

Global array co_names[]:

string co_names[]={"Result","Profit","Expected Payoff",
                   "Profit Factor","Recovery Factor",
                   "Sharpe Ratio","Custom","Equity DD","Trades"};

O código HTML da tabela será adicionado à variável s durante a sua formação, assim durante a declaração da variável vamos adicionar o tag de início da tabela:

string s="<table>";

Em seguida, adicione a tag de início de linha e a primeira célula do cabeçalho com o texto "Pass":

s=s+"<tr>";
s=s+"<th>Pass</th>";

A coluna "Pass" é seguida por colunas com parâmetros, qualquer um dos quais pode ser usado para formar a indicação de cor. Firmando o código HTML para as células:

for(int i=0;i<nc;i++){
   s=s+"<th"+HStyle(i,i1,i2,i3)+">"+co_names[i]+"</th>";   
}

Se necessário, a função HStyle() forma um código que altera a cor de fundo da célula:

string HStyle(int i,int i1,int i2,int i3){
   if(i==i1)return(" style='background-color: rgb(255,0,0);'");
   if(i==i2)return(" style='background-color: rgb(0,255,0);'");
   if(i==i3)return(" style='background-color: rgb(0,0,255);'");
   return("");
}

Formando o texto para a célula com o cabeçalho de indicação de cor:

s=s+"<th>"+ColorCollHeader(i1,i2,i3)+"</th>";

O código da função ColorCollHeader():

string ColorCollHeader(int i1,int i2,int i3){
   return(co_names[i1]+"-R,<br>"+co_names[i2]+"-G,<br>"+co_names[i3]+"-B");
}

Em seguida, nós formamos o código HTML para as células que contêm os nomes dos parâmetros otimizados e o fim da linha da tabela:

for(int j=0;j<pc;j++){
   s=s+"<th>"+aOpt.ParameterName[j]+"</th>";       
}
s=s+"</tr>";     

Depois disso, três variáveis auxiliares são declaradas: r, g, b. Isso é seguido por um loop no qual o código em HTML de todas as linhas do relatório é formado. Os valores do componente RGB são calculados no início de cada loop:

ComponentsToRGB(aOpt.Pass[i].ColorComponent[0],
                aOpt.Pass[i].ColorComponent[1],
                aOpt.Pass[i].ColorComponent[2],
                r,g,b);

O código da função ComponentsToRGB():

void ComponentsToRGB(double c1,double c2,double c3,int & r,int & g,int & b){
   r=(int)(c1*255.0);
   g=(int)(c2*255.0);
   b=(int)(c3*255.0);
}

Em seguida, o código HTML de uma linha é formada com as células que contêm os resultados do teste:

s=s+"<tr>";
   
s=s+"<td>"+aOpt.Pass[i].Pass+"</td>";
s=s+"<td>"+aOpt.Pass[i].Result+"</td>";   
s=s+"<td>"+aOpt.Pass[i].Profit+"</td>";         
s=s+"<td>"+aOpt.Pass[i].ExpectedPayoff+"</td>";   
s=s+"<td>"+aOpt.Pass[i].ProfitFactor+"</td>";               
s=s+"<td>"+aOpt.Pass[i].RecoveryFactor+"</td>";        
s=s+"<td>"+aOpt.Pass[i].SharpeRatio+"</td>";               
s=s+"<td>"+aOpt.Pass[i].Custom+"</td>";   
s=s+"<td>"+aOpt.Pass[i].EquityDD_perc+"</td>";        
s=s+"<td>"+aOpt.Pass[i].Trades+"</td>";      

Depois vem a célula de indicação da cor. Primeiro, os componentes RGB são convertidos em uma string usando a função RGBToStr(); depois disso, o código da célula é formado:

string cs=RGBToStr(r,g,b);
s=s+"<td title='"+cs+"' style='background-color: "+cs+"'>&nbsp</td>"; 

Código da função RGBToStr():

string RGBToStr(int r,int g,int b){
   return("rgb("+(string)r+","+(string)g+","+(string)b+")");
}

As células com os valores dos parâmetros sob otimização são mostradas no final da linha:

for(int j=0;j<pc;j++){
   s=s+"<td>"+aOpt.Pass[i].Parameters[j]+"</td>";       
}

s=s+"</tr>"

A tabela está fechada e o conteúdo da variável s é retornado no final da função:

s=s+"</table>";   

return(s);

Planos com parâmetros otimizados

O plano pode ser desenhado quando há dois ou mais parâmetros otimizados. O plano é mostrada na Fig. 2.


Fig. 2. Plano dos parâmetros otimizados

A primeira linha mostra quais parâmetros correspondem aos eixos do plano: os valores de Inp_Signal_MACD_PeriodSlow são mostrados ao longo do eixo X (horizontalmente) e os valores Inp_Signal_MACD_PeriodFast são exibidos ao longo do eixo Y. O gradiente nas células mostra como os resultados do teste foram alterados para este par de valores dos parâmetros X e Y quando outros parâmetros foram alterados. A cor do pior valor é mostrada à esquerda e a melhor está à direita. As melhores e as piores variantes são determinadas com base no fator de classificação mencionado anteriormente, que é calculado como a média aritmética dos componentes abstratos da cor.

O código HTML dos planos é formado na função Color2DPlanes(). Todas as combinações possíveis de dois parâmetros otimizados são encontradas nessa função, e o código do plano HTML é gerado para cada par. O código da função Color2DPlanes():

string Color2DPlanes(SOptimization2 & aOpt){
   string s="";
   int pc=ArraySize(aOpt.ParameterName);
   for(int y=0;y<pc;y++){
      for(int x=y+1;x<pc;x++){
         s=s+Color2DPlane(aOpt,x,y);         
      }   
   }
   return(s);
}

O código HTML de um plano é formado na função Color2DPlane():

string Color2DPlane(SOptimization2 & aOpt,int xi,int yi){

   double xa[];
   double ya[];
   
   int cnt=ArraySize(aOpt.Pass);

   ArrayResize(xa,cnt);
   ArrayResize(ya,cnt);
   
   for(int i=0;i<cnt;i++){
      xa[i]=aOpt.Pass[i].dParameters[xi];
      ya[i]=aOpt.Pass[i].dParameters[yi];      
   }
   
   ArraySort(xa);
   ArraySort(ya);
   
   int xc=1;
   int yc=1;
   
   for(int i=1;i<cnt;i++){
      if(xa[i]!=xa[i-1]){
         xa[xc]=xa[i];
         xc++;
      }
      if(ya[i]!=ya[i-1]){
         ya[xc]=ya[i];
         yc++;
      }
   }   

   string s="<hr><h3>X - "+aOpt.ParameterName[xi]+", Y - "+aOpt.ParameterName[yi]+"</h3><table>";


   s=s+"<tr>";   
      s=s+"<td>&nbsp;</td>";
      for(int x=0;x<xc;x++){
         s=s+"<td>"+(string)xa[x]+"</td>";
      }
   s=s+"</tr>";   
   for(int y=0;y<yc;y++){
      
      s=s+"<tr>";
      
      s=s+"<td>"+(string)ya[y]+"</td>";
      for(int x=0;x<xc;x++){

         double mx=0;
         double mn=DBL_MAX;
         int mxi=0;
         int mni=0; 
         
         for(int i=0;i<cnt;i++){
            if(aOpt.Pass[i].dParameters[yi]==ya[y] && 
               aOpt.Pass[i].dParameters[xi]==xa[x]
            ){
               if(aOpt.Pass[i].SortFactor>mx){
                  mx=aOpt.Pass[i].SortFactor;
                  mxi=i;
               }
               if(aOpt.Pass[i].SortFactor<mn){
                  mn=aOpt.Pass[i].SortFactor;
                  mni=i;
               }
            }
         }
         
         int mnr,mng,mnb;
         int mxr,mxg,mxb;
         
         ComponentsToRGB(aOpt.Pass[mni].ColorComponent[0],
                         aOpt.Pass[mni].ColorComponent[1],
                         aOpt.Pass[mni].ColorComponent[2],
                         mnr,mng,mnb);
                         
         ComponentsToRGB(aOpt.Pass[mxi].ColorComponent[0],
                         aOpt.Pass[mxi].ColorComponent[1],
                         aOpt.Pass[mxi].ColorComponent[2],
                         mxr,mxg,mxb);         
        
         string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
               
         int digits[]={2,2,6,6,6,6,6,4,0};

         for(int k=0;k<ArraySize(co_names);k++){
            title=title+co_names[k]+": "+DoubleToString(aOpt.Pass[mni].factor[k],digits[k])+
            "/"+DoubleToString(aOpt.Pass[mxi].factor[k],digits[k])+"\n";
         }

         s=s+"<td title='"+title+"' style='background: linear-gradient(to right, rgb("+
         (string)mnr+","+(string)mng+","+(string)mnb+"), rgb("+
         (string)mxr+","+(string)mxg+","+(string)mxb+"));'>&nbsp;"+
         (string)mni+"-"+(string)mxi+"</td>";
      }
      s=s+"</tr>";
   }
   
   s=s+"<table>";   

   return(s);

}

Vamos considerar a função Color2DPlane() em mais detalhes. O seguinte é a entrada para a função: a estrutura SOptimization2 contendo todos os dados do relatório de otimização e duas variáveis do tipo int xi e yi, que são os índices do par de parâmetros otimizados para os quais um plano será criado. Primeiro, nós coletaremos todos os valores possíveis de cada par de parâmetros nos arrays. Para isso, nós declaramos dois arrays, alteramos seus tamanhos de acordo com o número de passes de otimização e os preenchemos com todos os valores possíveis:

double xa[];
double ya[];

int cnt=ArraySize(aOpt.Pass);

ArrayResize(xa,cnt);
ArrayResize(ya,cnt);

for(int i=0;i<cnt;i++){
   xa[i]=aOpt.Pass[i].dParameters[xi];
   ya[i]=aOpt.Pass[i].dParameters[yi];      
}

Somente os valores exclusivos dos parâmetros devem ser usados, então vamos ordenar os arrays e mover os valores exclusivos para o início dos arrays:

ArraySort(xa);
ArraySort(ya);

int xc=1;
int yc=1;

for(int i=1;i<cnt;i++){
   if(xa[i]!=xa[i-1]){
      xa[xc]=xa[i];
      xc++;
   }
   if(ya[i]!=ya[i-1]){
      ya[xc]=ya[i];
      yc++;
   }
}   

Depois disso, a variável xc contém o número de valores exclusivos de um parâmetro e o yc contém aqueles do outro parâmetro. O código HTML do plano será adicionado à variável s durante a sua formação. Vamos adicionar as informações sobre os nomes das variáveis e a tag de abertura da tabela imediatamente durante a declaração da variável s:

string s="<hr><h3>X - "+aOpt.ParameterName[xi]+", Y - "+aOpt.ParameterName[yi]+"</h3><table>";

Vamos criar a primeira linha da tabela contendo os valores do parâmetro x:

s=s+"<tr>";   
   s=s+"<td>&nbsp;</td>";
   for(int x=0;x<xc;x++){
      s=s+"<td>"+(string)xa[x]+"</td>";
   }
s=s+"</tr>"; 

Depois desse loop por todas as variantes do parâmetro y:

for(int y=0;y<yc;y++){

Neste loop, inicie uma linha em cada passe e adicione uma célula com o valor do parâmetro y:

s=s+"<tr>";
      
s=s+"<td>"+(string)ya[y]+"</td>";

Em seguida, adicione as células com o gradiente (elas são adicionadas em um loop por todos as variantes do parâmetro x):

for(int x=0;x<xc;x++){

Para criar um gradiente, é necessário encontrar os melhores e piores passes de otimização:

double mx=0;
double mn=DBL_MAX;
int mxi=0;
int mni=0; 

for(int i=0;i<cnt;i++){
   if(aOpt.Pass[i].dParameters[yi]==ya[y] && 
      aOpt.Pass[i].dParameters[xi]==xa[x]
   ){
      if(aOpt.Pass[i].SortFactor>mx){
         mx=aOpt.Pass[i].SortFactor;
         mxi=i;
      }
      if(aOpt.Pass[i].SortFactor<mn){
         mn=aOpt.Pass[i].SortFactor;
         mni=i;
      }
   }
}

Após a execução desta parte do código, as variáveis mxi e mni conterão os índices do melhor e pior passo de otimização. 

Componentes abstratos de cor precisam ser convertidos para RGB:

ComponentsToRGB(aOpt.Pass[mni].ColorComponent[0],
                aOpt.Pass[mni].ColorComponent[1],
                aOpt.Pass[mni].ColorComponent[2],
                mnr,mng,mnb);
                         
ComponentsToRGB(aOpt.Pass[mxi].ColorComponent[0],
                aOpt.Pass[mxi].ColorComponent[1],
                aOpt.Pass[mxi].ColorComponent[2],
                mxr,mxg,mxb);  

Para uma análise mais eficiente dos planos, vamos adicionar as dicas da ferramenta (podem ser adicionadas usando o atributo HTML 'title'):

string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
      
int digits[]={2,2,6,6,6,6,6,4,0};

for(int k=0;k<ArraySize(co_names);k++){
   title=title+co_names[k]+": "+DoubleToString(aOpt.Pass[mni].factor[k],digits[k])+
   "/"+DoubleToString(aOpt.Pass[mxi].factor[k],digits[k])+"\n";
}

Os títulos ('title') são mostrados na Fig. 3.


Fig. 3. Uma dica de ferramenta para uma célula do plano

A dica contém todos os dados sobre o pior e melhor passe de otimização (o pior/melhor). Os valores do componente do gradiente RGB são mostrados na primeira linha da dica de ferramenta. 

Agora, prossiga para a parte mais importante, para o gradiente:

s=s+"<td title='"+title+"' style='background: linear-gradient(to right, rgb("+
(string)mnr+","+(string)mng+","+(string)mnb+"), rgb("+
(string)mxr+","+(string)mxg+","+(string)mxb+"));'>&nbsp;"+
(string)mni+"-"+(string)mxi+"</td>";

A exibição do gradiente foi verificada nos seguintes navegadores da Web: Opera, Google Chrome, Yandex browser e Microsoft-Edge. Ele funciona bem em todos eles.

Adicione a tag de fim de linha ao final de cada linha:

s=s+"</tr>";

No final da tabela, adicione a tag final da tabela e retorne o código HTML formado:

s=s+"<table>";   

return(s);

Agora vamos chamar as funções do script:

string report=HTMLStart("Color Optimization","style2.css")+
TableContent(opt,Factor1,Factor2,Factor3)+
Color2DPlanes(opt)+HTMLEnd();
    

Eu usei as funções HTMLStart() e HTMLEnd() do artigo Analisando resultados de negociação usando relatórios HTML. O arquivo de estilos do mesmo artigo foi ligeiramente alterado e renomeado para style2.css.

Os arquivos prontos estão anexados abaixo: ColorOptimization.mqh e o script ColorOptimization.mq5. 

Modificação do modelo de cores

O código em ColorOptimization.mqh é estruturado para que você possa alterá-lo facilmente para um modelo de cores diferente. Vamos tentar adicionar o modelo de cores CMY. Para isso, nós precisamos realizar algumas etapas preliminares.

1. Copie o ColorOptimization.mqh e ColorOptimization.mq5 e salve-os como ColorOptimization2.mqh e ColorOptimization2.mq5. 

2. Adicione ao ColorOptimization2.mqh duas constantes para os dois tipos de modelo de cores e uma variável global, que determinará o modelo de cores:

#define MODEL_RGB 0
#define MODEL_CMY 1

int co_ColorModel;

3 Adicione uma enumeração e uma variável externa, usando o que o usuário selecionará no modelo de cores:

enum EColorModel{
   RGB=MODEL_RGB,
   CMY=MODEL_CMY
};

input EColorModel          ColorModel     =  RGB;

No início da função OnStart() do script, atribua o valor selecionado na janela de propriedades à variável co_ColorModel:

co_ColorModel=ColorModel;

As principais alterações são realizadas nas funções do arquivo ColorOptimization2.mqh. Primeiramente, nós precisamos mudar a ComponentsToRGB(). Os valores dos componentes no modelo CMY variam de 0 a 1 e, portanto, os valores dos componentes da estrutura de dados do relatório correspondem aos componentes CMY e podem ser recalculados para RGB. Aqui está a estrutura ComponentsToRGB():

void ComponentsToRGB(double c1,double c2,double c3,int & r,int & g,int & b){
   if(co_ColorModel==MODEL_RGB){
      r=(int)(c1*255.0);
      g=(int)(c2*255.0);
      b=(int)(c3*255.0);
   }
   else if(co_ColorModel==MODEL_CMY){
      CMYtoRGB(c1,c2,c3,r,g,b);
   }
}

A transformação do modelo CMY em RGB é implementada em uma função separada:

void CMYtoRGB(double C,double M,double Y,int & R,int & G,int & B){
   R=(int)((1.0-C)*255.0);
   G=(int)((1.0-M)*255.0);
   B=(int)((1.0-Y)*255.0);
}

Outras modificações referem-se apenas aos elementos do relatório auxiliar. Revisão da função HStyle() para uma coloração adequada das células da linha de cabeçalho da tabela:

string HStyle(int i,int i1,int i2,int i3){
   if(co_ColorModel==MODEL_RGB){
      if(i==i1)return(" style='background-color: rgb(255,0,0);'");
      if(i==i2)return(" style='background-color: rgb(0,255,0);'");
      if(i==i3)return(" style='background-color: rgb(0,0,255);'");
   }
   else if(co_ColorModel==MODEL_CMY){
      if(i==i1)return(" style='background-color: rgb(0,255,255);'");
      if(i==i2)return(" style='background-color: rgb(255,0,255);'");
      if(i==i3)return(" style='background-color: rgb(255,255,0);'");      
   }
   return("");
}

Revisão da função ColorCollHeader() para um cabeçalho correto da coluna de indicação de cor:

string ColorCollHeader(int i1,int i2,int i3){
   if(co_ColorModel==MODEL_RGB){
      return(co_names[i1]+"-R,<br>"+co_names[i2]+"-G,<br>"+co_names[i3]+"-B");
   }
   else if(co_ColorModel==MODEL_CMY){
      return(co_names[i1]+"-C,<br>"+co_names[i2]+"-M,<br>"+co_names[i3]+"-Y");   
   }
   return "";
}

Em seguida, algumas alterações precisam ser feitas para as dicas de ferramenta da tabela principal e dos planos de cor. Para a tabela principal, nós precisamos alterar o valor do atributo 'title' na TableContent(). As seguintes linhas:

string cs=RGBToStr(r,g,b);
s=s+"<td title='"+cs+"' style='background-color: "+cs+"'>&nbsp</td>";   

deve ser alterado da seguinte forma:

string ts="",cs=RGBToStr(r,g,b);

if(co_ColorModel==MODEL_RGB){    
   ts=cs;
}
else if(co_ColorModel==MODEL_CMY){
   ts=CMYToStr(aOpt.Pass[i].ColorComponent[0],
               aOpt.Pass[i].ColorComponent[1],
               aOpt.Pass[i].ColorComponent[2]);
}
s=s+"<td title='"+ts+"' style='background-color: "+cs+"'>&nbsp</td>";     

O atributo 'title' na função Color2DPlane() deve ser alterado para definir os títulos apropriados para os planos. A linha:

string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";

deve ser alterada da seguinte forma:

string title="";

if(co_ColorModel==MODEL_RGB){
   title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
}
else if(co_ColorModel==MODEL_CMY){         
   title=CMYToStr(aOpt.Pass[mni].ColorComponent[0],
                  aOpt.Pass[mni].ColorComponent[1],
                  aOpt.Pass[mni].ColorComponent[2])+"/"+
         CMYToStr(aOpt.Pass[mxi].ColorComponent[0],
                  aOpt.Pass[mxi].ColorComponent[1],
                  aOpt.Pass[mxi].ColorComponent[2])+"\n";                            
}

Agora, o tipo de modelo de cores pode ser selecionado durante o lançamento do script. A diferença entre CMY e RGB é que em CMY os melhores valores são mostrados em preto e outras cores também serão diferentes (Figuras 4, 5).


Fig. 4. Fragmento de um relatório criado usando o modelo de cores CMY


Fig. 5. O plano de cores no modelo de cores CMY

Como interpretar as indicações de cor

As melhores opções em RGB estão próximas do branco e em CMY estão próximas do preto. Para uma interpretação adequada de outras cores, nós precisamos entender como os componentes individuais dentro do modelo de cores são combinados e como a cor resultante é formada.

Vamos ver o modelo RGB em mais detalhes. Quando os valores de todos os componentes são iguais a 0, nós obtemos a cor preta. Quando todos os componentes são iguais ao valor máximo, a cor é branca. Todas as outras combinações fornecem cores diferentes. Se um dos componentes tiver o valor mais alto e os outros dois forem iguais a 0, nós obtemos a cor clara do componente apropriado: vermelho, verde ou azul. Se dois componentes tiverem valores máximos e o terceiro for zero, a cor resultante também será clara. Resultado vermelho e verde em amarelo, verde e azul fornecem ciano, vermelho e azul são mostrados como magenta. A Figura 6 mostra várias combinações de componentes RGB.


Fig. 7. Combinações básicas de componentes RGB

Com base na sombra, nós podemos entender qual indicador de parâmetro contribui mais positivamente para o resultado do teste. Se for vermelho, então, é o primeiro parâmetro; se for a cor amarela, o primeiro e o segundo; verde significa o terceiro parâmetro, etc.

As cores no modelo RGB são adicionadas de forma semelhante às luzes coloridas. No modelo CMY, os valores são subtraídos do branco, portanto, o valor máximo de todos os componentes corresponde ao preto. O modelo CMY é semelhante à mistura de tintas: se não houver tintas, temos uma folha de papel branca; se você misturar muitas tintas diferentes, terá uma cor preta (ou melhor, uma cor suja ao lidar com tintas reais). A figura 8 mostra as combinações básicas de componentes CMY.


Fig. 7. Combinações básicas de componentes CMY

As cores em CMY são deslocadas em comparação com RGB. Aqui está a interpretação: ciano é o primeiro parâmetro, azul significa o primeiro e o segundo, magenta representa o segundo, vermelho aparece para o segundo e terceiro valores, amarelo é usado para o terceiro e verde é usado para o primeiro e terceiro parâmetros.

Como você pode ver, não há diferença fundamental no uso do modelo RGB ou CMY. 

Conclusão

A percepção de cores é um processo subjetivo e, portanto, é difícil tirar conclusões claras sobre a conveniência e os benefícios da representação de cores. Pelo menos uma indicação visual, o grau de luz (que significa proximidade ao branco no RGB), permite avaliar a combinação dos três parâmetros. Isso pode simplificar a análise do relatório. Quando a seleção é automatizada, como neste artigo, a decisão baseada em uma tabela classificada é feita de acordo com a média aritmética dos três valores. Este pode ser considerado o primeiro passo na área da lógica difusa, em que o uso do valor final pode ser calculado não como uma simples média aritmética, mas de uma maneira mais complexa. No entanto, nós precisamos de experimentos mais práticos para avaliar a eficácia desse método.

Anexos

Todos os arquivos, exceto os relatórios criados pelos scripts, são organizados em pastas, pois devem estar localizados nas pastas do terminal. Abra a pasta de dados do terminal e copie a pasta MQL5 para ela.