Uso Prático das Redes Neurais de Kohonen na Negociação Algorítmica. Parte II. Otimização e previsão

Stanislav Korotky | 1 março, 2019

Neste artigo, nós continuamos considerando as redes de Kohonen como uma ferramenta de negociação. Na Parte I, nós corrigimos e melhoramos as classes da rede neural publicamente disponível, adicionando os algoritmos necessários. Agora é hora de colocá-los em prática. Neste artigo, nós vamos usar os mapas de Kohonen na solução de problemas, como a seleção dos parâmetros ótimos do EA e previsão das séries temporais.


Busca pelos parâmetros ótimos do EA

Princípios Comuns

O problema de otimização dos robôs é resolvido em muitas plataformas de negociação, incluindo a MetaTrader. O testador incorporado oferece uma ampla variedade de ferramentas, algoritmos avançados, cálculos distribuídos e avaliações estatísticas refinadas. No entanto, do ponto de vista do usuário, há sempre mais um último estágio crítico na otimização, ou seja, o estágio de escolha dos parâmetros finais, com base na análise dessa enxurrada de informações geradas pelo programa. Nos artigos anteriores que tratam dos mapas de Kohonen e publicados neste site, foram fornecidos exemplos de análise visual dos resultados da otimização. No entanto, isso sugere que o usuário realiza uma análise do expert sozinho. Idealmente, nós gostaríamos de obter recomendações mais específicas da rede neural. Em suma, a negociação algorítmica é negociada por um programa sem envolver o usuário.

Ao concluir a otimização, geralmente nós recebemos um relatório longo do testador com muitas opções. Dependendo da coluna para ordenação, nós extraímos de suas profundidades configurações absolutamente diferentes que significam otimalidade em um critério relevante, como lucro, Sharpe ratio, etc. Mesmo se nós determinarmos um critério em que nós confiamos mais, o sistema geralmente oferece várias configurações com o mesmo resultado. Como escolher?

Alguns traders praticam seu próprio critério sintético que incluem várias métricas padrão nos cálculos — com essa abordagem, é realmente menos possível receber strings iguais no relatório. No entanto, eles, de fato, traduzem o assunto para a área da meta-otimização do critério mencionado (como escolher sua fórmula corretamente?). E este é um tópico separado. Então, nós voltaremos a analisar os resultados da otimização padrão.

Na minha opinião, a seleção do conjunto ideal de parâmetros do EA deve ser baseada na busca pelo maior "platô" duradouro dentro da área dos valores de uma função alvo com o nível mínimo pré-definido daquele "platô", em vez de buscar pelo máximo de tal função. No contexto de negociação, o nível do "platô" pode ser comparado à rentabilidade média, enquanto o seu comprimento pode ser comparado à confiabilidade, ou seja, à robustez e estabilidade do sistema.

Depois de considerarmos propositalmente algumas técnicas de análise de dados na Parte I, sugere-se que tais "platôs" possam ser buscados usando a clusterização.

Infelizmente, não há métodos unificados ou universais para obter clusters com as características necessárias. Particularmente, se o número de clusters for grande, eles se tornarão pequenos e demonstrarão todos os sintomas de reaprendizagem — eles generalizam informações de maneira insuficiente. Se há poucos clusters, então eles são menos treinados, então eles recebem em si mesmos as amostras que são fundamentalmente diferentes. O termo "grande/pouco" não possui definições claras, pois existe um limite específico para cada tarefa, número e estrutura de dados. Portanto, geralmente é sugerido realizar vários experimentos.

O número de clusters é logicamente relacionado ao tamanho do mapa e à tarefa aplicada. No nosso caso, o fator anterior funciona em apenas uma direção, já que nós decidimos anteriormente a definição do tamanho pela fórmula (7). Assim, conhecendo esse tamanho, nós obtemos um limite superior para o número de clusters — dificilmente haverá mais deles do que o tamanho de um lado. Por outro lado, com base na tarefa aplicada, apenas um par de clusters provavelmente seria adequado para nós: configurações "boas" e "ruins". Este é o intervalo dentro do qual os experimentos podem ser realizados. Tudo isso é aplicável apenas a algoritmos baseados em uma indicação clara do número de clusters, como o K-Means. Nosso algoritmo alternativo não tem tal configuração, no entanto, devido à organização dos clusters por qualidade, nós podemos simplesmente excluir todos os clusters com números acima do determinado da nossa consideração.

Em seguida, nós tentaremos realizar a clusterização usando a rede de Kohonen. No entanto, nós temos que discutir um ponto mais delicado, antes de irmos para a prática.

Muitos robôs são otimizados em todo o espaço de parâmetros em larga escala. portanto, a otimização é realizada por um algoritmo genético. Ele economiza tempo e recursos. No entanto, ele tem uma característica especial de "cair" em áreas rentáveis. A princípio, essa era a intenção. No entanto, em termos dos mapas de Kohonen, isso não é muito bom. A questão é que os mapas de Kohonen são sensíveis à distribuição de dados no espaço de entrada e, na verdade, eles refletem isso na topologia resultante. Como as versões ruins dos parâmetros são excluídas pelo algoritmo genético em estágios iniciais, eles ocorrem de forma mais rara do que as boas que são herdadas pelo algoritmo genético em muitos detalhes. Como resultado, a rede de Kohonen pode omitir a observação dos perigosos vales da função alvo perto das supostamente boas versões encontradas. Como as características do mercado sempre oscilam, é essencial evitar esses parâmetros, nos quais um passo à esquerda ou à direita leva a perdas.

Estas são as maneiras de resolver o problema:

  1. Desistir da otimização genética em favor da completa; uma vez que nem sempre é possível em toda a extensão, é permitido implementar a abordagem hierárquica, ou seja, realizar a otimização genética com um grande passo primeiro, localizar áreas interessantes e depois executar a otimização completa nelas (e então analisar usando a rede de Kohonen); também se pensa que a super-extensão da lista de parâmetros a serem otimizados fornece ao sistema o número de graus de liberdade, devido ao qual ele se torna, primeiro, instável; e segundo, a otimização se traduz em adaptação; portanto, recomenda-se selecionar valores permanentes para a maior parte dos parâmetros, com base em seu significado físico e na análise fundamental (por exemplo, os períodos devem ser selecionados de acordo com o tipo de estratégia: Um dia para as estratégias intraday, uma semana para médio prazo, etc.); então é possível reduzir o espaço de otimização e abandonar a otimização genética;
  2. Repetir a otimização genética várias vezes, usando como critério os máximos e mínimos, e zeros da função alvo; Por exemplo, você pode executar a triplicação da otimização:
    • Por fator de lucro (PF), como de costume;
    • Por sua quantidade inversa, 1/PF;
    • Pela fórmula (min(PF, 1/PF) / max(PF, 1/PF)) que coletaria estatísticas em torno de 1;
    Depois disso, para consolidar os resultados de todas as otimizações e analisá-las pela rede como um todo unificado;
  3. É uma medida paliativa que ainda vale a pena pesquisar: Construir mapas de Kohonen em uma métrica excluindo o índice otimizado (na verdade, todos os índices econômicos que não são os parâmetros do EA); em outras palavras, no aprendizado da rede, a medida de similaridade entre os pesos e inputs dos neurônios deve ser calculada apenas pelos componentes selecionados relevantes para os parâmetros do EA; a métrica reversa também é interessante, onde as medidas de proximidade são calculadas apenas por índices econômicos, e provavelmente vemos a dispersão topológica nos planos dos parâmetros, o que fornece evidência da instabilidade do sistema; Em ambos os casos, os pesos dos neurônios são ajustados de maneira completa — por todos os componentes.

A última versão N3 significa que a topologia da rede refletirá apenas a distribuição dos parâmetros do EA (ou índices econômicos, dependendo da direção). De fato, quando nós treinamos a rede nas linhas completas da tabela de otimização, as colunas, como lucro, rebaixamento e número de negócios, formam a distribuição total de neurônios na medida em que eles não forem menores do que a dos parâmetros do EA. Isso pode ser bom para que o problema de análise visual alcance um entendimento geral e tire conclusões de quais parâmetros afetam principalmente quais índices. Mas isso é ruim para analisar nossos parâmetros, uma vez que sua distribuição real é alterada por índices econômicos.

A princípio, para o caso de analisar os resultados de otimização, é óbvio dividir o vetor de entrada em dois componentes separados logicamente: As entradas do EA e seus índices (saída). Ao treinar uma rede de Kohonen em um vetor completo, estamos tentando identificar a dependência de "entradas" e "saídas" (um relacionamento bidirecional incondicional). Quando a rede aprende somente uma parte das características, nós podemos tentar e ver as relações direcionadas: como as "saídas" são agrupadas dependendo das "entradas" ou, vice-versa, como as "entradas" são agrupadas dependendo das "saídas". Vamos considerar as duas opções.


Desenvolvimento adicional do SOM-Explorer

Para implementar este modo de aprendizagem com o mascaramento de alguns recursos, será necessário o desenvolvimento adicional das classes CSOM e CSOMNode.

As distâncias são calculadas na classe CSOMNode. A exclusão dos componentes específicos dos cálculos é unificada para todos os objetos da classe, portanto, nós faremos a estatística das variáveis relevantes:

    static int dimensionMax;
    static ulong dimensionBitMask;

dimensionMax permite definir a quantidade máxima de pesos do neurônio a ser calculado. Por exemplo, se a dimensionalidade do espaço for 5, então a dimensionMax igual a 4 significará que o último componente do vetor é excluído dos cálculos.

dimensionBitMask permite excluir os componentes organizados aleatoriamente usando uma máscara de bits: Se o enésimo bit for igual a 1, o componente i é processado; se for igual a 0, não há processamento.

Vamos adicionar o método estático para colocar as variáveis:

static void CSOMNode::SetFeatureMask(const int dim = 0, const ulong bitmask = 0)
{
  dimensionMax = dim;
  dimensionBitMask = bitmask;
}

Agora, nós vamos alterar os cálculos da distância usando novas variáveis:

double CSOMNode::CalculateDistance(const double &vector[]) const
{
  double distSqr = 0;
  if(dimensionMax <= 0 || dimensionMax > m_dimension) dimensionMax = m_dimension;
  for(int i = 0; i < dimensionMax; i++)
  {
    if(dimensionBitMask == 0 || ((dimensionBitMask & (1 << i)) != 0))
    {
      distSqr += (vector[i] - m_weights[i]) * (vector[i] - m_weights[i]);
    }
  }
  return distSqr;
}

Agora, nós temos apenas que ter certeza de que a classe CSOM coloca as limitações nos neurônios de maneira apropriada. Vamos adicionar um método público semelhante à classe CSOM:

void CSOM::SetFeatureMask(const int dim, const ulong bitmask)
{
  m_featureMask = 0;
  m_featureMaskSize = 0;
  if(bitmask != 0)
  {
    m_featureMask = bitmask;
    Print("Feature mask enabled:");
    for(int i = 0; i < m_dimension; i++)
    {
      if((bitmask & (1 << i)) != 0)
      {
        m_featureMaskSize++;
        Print(m_titles[i]);
      }
    }
  }
  CSOMNode::SetFeatureMask(dim == 0 ? m_dimension : dim, bitmask);
}

No teste do EA, nós criaremos o parâmetro de string FeatureMask, no qual o usuário pode definir uma máscara de recurso e analisá-la quanto à disponibilidade dos símbolos '1' e '0':

    ulong mask = 0;
    if(FeatureMask != "")
    {
      int n = MathMin(StringLen(FeatureMask), 64);
      for(int i = 0; i < n; i++)
      {
        mask |= (StringGetCharacter(FeatureMask, i) == '1' ? 1 : 0) << i;
      }
    }
    KohonenMap.SetFeatureMask(0, mask);

Tudo isso é feito imediatamente antes do lançamento do método Train e, portanto, afeta os cálculos de distâncias tanto durante o aprendizado quanto no estágio de cálculo da U-Matrix e dos clusters. No entanto, em alguns casos, será interessante para nós a realização da clusterização por outras regras, como treinar a rede sem qualquer máscara e aplicar a máscara para encontrar apenas os clusters. Para isso, nós introduziremos o parâmetro de controle ApplyFeatureMaskAfterTraining igual a 'false' por padrão. No entanto, se estiver definido como 'true', nós chamaremos de SetFeatureMask após o Train.

Enquanto nós estamos aprimorando nossas ferramentas novamente, nós cobriremos mais um ponto relacionado ao fato de que usaremos as configurações de trabalho dos robôs de negociação como vetores de entrada.

Seria conveniente fazer o upload dos valores dos parâmetros do EA para a rede analisar diretamente a partir do arquivo de configuração (*.set). Para este propósito, nós escrevemos a seguinte função:

bool LoadSettings(const string filename, double &v[])
{
  int h = FileOpen(filename, FILE_READ | FILE_TXT);
  if(h == INVALID_HANDLE)
  {
    Print("FileOpen error ", filename, " : ",GetLastError());
    return false;
  }
  
  int n = KohonenMap.GetFeatureCount();
  ArrayResize(v, n);
  ArrayInitialize(v, EMPTY_VALUE);
  int count = 0;

  while(!FileIsEnding(h))
  {
    string line = FileReadString(h);
    if(StringFind(line, ";") == 0) continue;
    string name2value[];
    if(StringSplit(line, '=', name2value) != 2) continue;
    int index = KohonenMap.FindFeature(name2value[0]);
    if(index != -1)
    {
      string values[];
      if(StringSplit(name2value[1], '|', values) > 0)
      {
        v[index] = StringToDouble(values[0]);
        count++;
      }
    }
  }
  
  Print("Settings loaded: ", filename, "; features found: ", count);
  
  ulong mask = 0;
  for(int i = 0; i < n; i++)
  {
    if(v[i] != EMPTY_VALUE)
    {
      mask |= (1 << i);
    }
    else
    {
      v[i] = 0;
    }
  }
  if(mask != 0)
  {
    KohonenMap.SetFeatureMask(0, mask);
  }
 
  FileClose(h);
  return count > 0;
}

Nele, nós analisamos as linhas do arquivo de configuração e encontramos os nomes dos parâmetros, que correspondem aos recursos de uma rede treinada. As configurações coincidentes são salvas no vetor, enquanto as não coincidentes são puladas. A máscara de recurso é preenchida com unidades apenas para os componentes disponíveis. A função retorna o recurso lógico do fato de que as configurações foram lidas.

Agora, quando nós temos esta função à nossa disposição, para baixar os parâmetros dos arquivos de configurações, nós vamos inserir as seguintes linhas na ramificação do operador if onde há a verificação de DataFileName != "":

      // processa o arquivo .set como um caso especial do padrão
      if(StringFind(DataFileName, ".set") == StringLen(DataFileName) - 4)
      {
        double v[];
        if(LoadSettings(DataFileName, v))
        {
          KohonenMap.AddPattern(v, "SETTINGS");
          ArrayPrint(v);
          double y[];
          CSOMNode *node = KohonenMap.GetBestMatchingFeatures(v, y);
          Print("Matched Node Output (", node.GetX(), ",", node.GetY(),
            "); Hits:", node.GetHitsCount(), "; Error:", node.GetMSE(),
            "; Cluster N", node.GetCluster(), ":");
          ArrayPrint(y);
          KohonenMap.CalculateOutput(v, true);
          hasOneTestPattern = true;
        }
      }

As configurações serão marcadas com SETTINGS.


EA em funcionamento

Agora, nós estamos nos aproximando do problema de seleção dos parâmetros ótimos.

Para testar as teorias de clusterização dos resultados de otimização, primeiramente, nós temos que criar um EA funcionando. Eu o criei usando o Assistente MQL5 e alguns módulos padrão e chamei de WizardTest (o seu código-fonte está anexado no final deste artigo). Aqui está a lista dos parâmetros de entrada:

input string             Expert_Title                 ="WizardTest"; // Nome do documento
ulong                    Expert_MagicNumber           =17897;        // 
bool                     Expert_EveryTick             =false;        // 
//--- entradas para o sinal principal
input int                Signal_ThresholdOpen         =10;           // Valor do limiar do sinal para abertura [0...100]
input int                Signal_ThresholdClose        =10;           // Valor do limiar do sinal para encerramento [0...100]
input double             Signal_PriceLevel            =0.0;          // Nível do preço para executar um negócio
input double             Signal_StopLevel             =50.0;         // Nível do Stop Loss (em pontos)
input double             Signal_TakeLevel             =50.0;         // Nível do Take Profit (em pontos)
input int                Signal_Expiration            =4;            // Expiração das ordens pendentes (em barras)
input int                Signal_RSI_PeriodRSI         =8;            // Relative Strength Index(8,...) Período de cálculo
input ENUM_APPLIED_PRICE Signal_RSI_Applied           =PRICE_CLOSE;  // Relative Strength Index(8,...) Série de preços
input double             Signal_RSI_Weight            =1.0;          // Relative Strength Index(8,...) Peso [0...1.0]
input int                Signal_Envelopes_PeriodMA    =45;           // Envelopes(45,0,MODE_SMA,...) Período da média
input int                Signal_Envelopes_Shift       =0;            // Envelopes(45,0,MODE_SMA,...) Deslocamento temporal
input ENUM_MA_METHOD     Signal_Envelopes_Method      =MODE_SMA;     // Envelopes(45,0,MODE_SMA,...) Método da média
input ENUM_APPLIED_PRICE Signal_Envelopes_Applied     =PRICE_CLOSE;  // Envelopes(45,0,MODE_SMA,...) Série de preços
input double             Signal_Envelopes_Deviation   =0.15;         // Envelopes(45,0,MODE_SMA,...) Desvio
input double             Signal_Envelopes_Weight      =1.0;          // Envelopes(45,0,MODE_SMA,...) Peso [0...1.0]
input double             Signal_AO_Weight             =1.0;          // Peso do Awesome Oscillator [0...1.0]
//--- entradas para o stop móvel
input double             Trailing_ParabolicSAR_Step   =0.02;         // Incremento da velocidade
input double             Trailing_ParabolicSAR_Maximum=0.2;          // Taxa máxima
//--- entradas para o financeiro
input double             Money_FixRisk_Percent=5.0;          // Risco em percentual

Para o propósito de nossos estudos, nós vamos otimizar apenas alguns parâmetros, não todos eles.

Signal_ThresholdOpen
Signal_ThresholdClose
Signal_RSI_PeriodRSI
Signal_Envelopes_PeriodMA
Signal_Envelopes_Deviation
Trailing_ParabolicSAR_Step
Trailing_ParabolicSAR_Maximum

Eu realizei a otimização genética do EA de janeiro a junho de 2018 (20180101-20180701) no EURUSD D1, M1 OHLC, por lucro. o arquivo de configuração com as configurações dos parâmetros a serem otimizados está anexado no final deste artigo (WizardTest-1.set). Os resultados da otimização foram salvos no arquivo Wizard2018plus.csv (anexado no final deste documento, também), na qual os outliers foram excluídos, particularmente aqueles com a quantidade de negócios abaixo de 5 e aqueles altíssimos e Sharpe ratio irreais. Além disso, como a otimização genética produziu, por definição, uma mudança para passagens lucrativas, decidi excluir completamente as perdedoras e apenas mantive as entradas onde o lucro foi de pelo menos 10000.

Eu também removi um par de colunas do índice, pois elas são realmente dependentes das outras, como Saldo ou Retorno Esperado, e há outros candidatos, como PF (Fator de Lucro), RF (Fator de Rali) e Sharpe Ratio — altamente inter-relacionados, além disso, o RF é inversamente correlacionado com o rebaixamento, mas eu os mantive no local para demonstrar essa dependência nos mapas.

Selecionar a estrutura de entradas com o conjunto mínimo de componentes independentes que podem suportar o máximo de informações é uma condição crítica para o uso eficiente das redes neurais.

Tendo aberto o arquivo csv contendo os resultados da otimização, nós veremos o problema questionável que mencionei acima: Uma enorme quantidade de linhas contendo índices igualmente lucrativos e parâmetros diferentes. Vamos tentar e exercitar uma opção razoável.

Para ter um ponto de referência para avaliar nossa opção selecionada no futuro, vamos dar uma olhada no forward test para o conjunto de parâmetros da primeira linha dos resultados de otimização (veja WizardTest-1.set). As datas dos testes são de 1 de julho de 2018 até 1 de dezembro de 2018.

Relatório do testador na primeira seleção de configurações listadas

Relatório do testador na primeira seleção de configurações listadas

Lembre-se deles, na verdade, não foram números muito bons, nós vamos levar para à rede.


Análise da rede neural

O número de entradas no arquivo csv é de aproximadamente 2000. Portanto, o tamanho da rede de Kohonen calculado pela fórmula (7) será 15.

Nós lançamos o CSOM-Explorer, inserimos o nome do arquivo de dados (Wizard2018plus.csv) no DataFileName, 15 — no CellsX e no CellsY, e mantemos o EpochNumber igual a 100. Para visualizar todos os planos simultaneamente, nós selecionamos imagens pequenas: 210 para cada um dos ImageW e ImageH e 6 para MaxPictures. Como resultado da operação do EA, nós obteremos substancialmente o seguinte:

Ensinando a rede de Kohonen nos resultados de otimização do EA

Ensinando a rede de Kohonen nos resultados de otimização do EA

A linha superior consiste inteiramente nos mapas de métricas econômicas, a segunda linha e o primeiro mapa da terceira linha são os parâmetros de funcionamento do EA e, finalmente, os últimos 5 mapas são mapas especiais construídos na Parte I.

Vamos dar uma olhada nos mapas:

Lucro, PF, RF e os três primeiros parâmetros de entrada do EA

Lucro, PF, RF e os três primeiros parâmetros de entrada do EA

Sharpe ratio, rebaixamento, número de negócios e os segundos três parâmetros do EA

Sharpe ratio, rebaixamento, número de negócios e os segundos três parâmetros do EA

O último parâmetro do EA, bem como o contador de ocorrências, U-Matrix, erros de quantização, clusters e a saída da rede

O último parâmetro do EA, bem como o contador de ocorrências, U-Matrix, erros de quantização, clusters e a saída da rede

Vamos realizar a análise especializada dos mapas obtidos, por assim dizer, manualmente.

Em termos de lucros, nós podemos ver duas áreas adequadas com valores aproximadamente iguais (em vermelho) no canto superior direito e no canto superior esquerdo, sendo o direito maior e destacado de forma mais intensa. No entanto, a análise paralela dos planos com o PF, RF e Sharpe ratio nos convence de que o canto superior direito é a melhor escolha. Isto também é confirmado pelo menor rebaixamento.

Vamos para os componentes que são relevantes para os parâmetros do EA, prestando atenção ao canto superior direito. Nos mapas dos primeiros cinco parâmetros, este ponto é colorido de forma estável, o que nos permite nomear com confiança os valores ótimos (ponto com o cursor do mouse no neurônio superior direito em cada mapa para ver o valor relevante na extremidade):

Signal_ThresholdOpen = 40
Signal_ThresholdClose = 85
Signal_RSI_PeriodRSI = 73
Signal_Envelopes_PeriodMA = 20
Signal_Envelopes_Deviation = 0.5

os mapas dos dois parâmetros restantes mudam sua cor ativamente no canto superior direito, portanto, não está claro quais valores escolher. A julgar pelo plano com o contador de acertos, o neurônio superior direito é preferível. Também é razoável certificar-se de que é uma "atmosfera silenciosa" para ele em U-Matrix e no mapa de erros. Como está tudo bem, nós selecionamos esses valores:

Trailing_ParabolicSAR_Step = 0.15
Trailing_ParabolicSAR_Maximum = 1.59

Vamos executar o EA com esses parâmetros (veja WizardTest-plus-allfeatures-manual.set) no mesmo período de forward até 1 de dezembro de 2018. Nós obteremos o seguinte resultado.

Relatório do testador sobre as configurações selecionadas com base na análise visual dos mapas de Kohonen

Relatório do testador sobre as configurações selecionadas com base na análise visual dos mapas de Kohonen

O resultado é notavelmente melhor que o selecionado aleatoriamente na primeira linha. No entanto, ele ainda pode decepcionar os profissionais, por isso vale a pena fazer uma observação aqui.

Este é um EA de teste, não um graal. Sua tarefa é gerar dados de origem para demonstrar como os algoritmos baseados em rede neural funcionam. Depois que o artigo for publicado, será possível testar as ferramentas em uma grande quantidade de sistemas reais de negociação e, talvez, adaptá-las para um melhor uso.

Vamos comparar o resultado atual com os valores principais que poderiam ser obtidos para todas as opções de configurações de arquivos csv dentro do mesmo intervalo de datas.

Resultados de todos os forward tests

Resultados de todos os forward tests

Estatísticas da distribuição de lucro em testes futuros

Estatísticas da distribuição de lucro em testes futuros

As estatísticas são as seguintes: a média é 1.007, o desvio padrão é 1.444, a mediana é 1.085, e os valores mais baixos e mais altos são -3.813.78 e 4.202.82, respectivamente. Assim, utilizando avaliação do EA, nós obtivemos o lucro ainda maior do que a média mais o desvio padrão.

Nossa tarefa é aprender como fazer a mesma escolha ou qualitativamente similar de maneirar automática. Para fazer isso, nós usaremos a clusterização. Como os clusters são numerados na ordem de seu uso preferencial, nós consideraremos o cluster 0 (embora, basicamente, os clusters 1-5 possam ser usados para negociação com um portfólio de configurações).

As cores dos clusters no mapa estão sempre relacionadas aos seus números de indexação, conforme listado abaixo:

{clrRed, clrGreen, clrBlue, clrYellow, clrMagenta, clrCyan, clrGray, clrOrange, clrSpringGreen, clrDarkGoldenrod}

Isso pode ajudar a identificá-los em imagens menores, onde é difícil distinguir os números.

A SOM-Explorer exibe no log as coordenadas dos centros de cluster, por exemplo, para o mapa atual:

Clusters [20]:
[ 0] "Profit"                        "Profit Factor"                 "Recovery Factor"               "Sharpe Ratio"                 
[ 4] "Equity DD %"                   "Trades"                        "Signal_ThresholdOpen"          "Signal_ThresholdClose"        
[ 8] "Signal_RSI_PeriodRSI"          "Signal_Envelopes_PeriodMA"     "Signal_Envelopes_Deviation"    "Trailing_ParabolicSAR_Step"   
[12] "Trailing_ParabolicSAR_Maximum"
N0 [3,2]
[0] 18780.87080     1.97233     3.60269     0.38653    16.76746    63.02193    20.00378
[7]    65.71576    24.30473    19.97783     0.50024     0.13956     1.46210
N1 [1,4]
[0] 18781.57537     1.97208     3.59908     0.38703    16.74359    62.91901    20.03835
[7]    89.61035    24.59381    19.99999     0.50006     0.12201     0.73983
...

No começo, há a legenda de recursos. Então, para cada cluster, há seu número, coordenadas X e Y e os valores dos recursos.

Aqui, para o 0º cluster, nós temos os seguintes parâmetros de entrada do EA:

Signal_ThresholdOpen=20
Signal_ThresholdClose=66
Signal_RSI_PeriodRSI=24
Signal_Envelopes_PeriodMA=20
Signal_Envelopes_Deviation=0.5
Trailing_ParabolicSAR_Step=0.14
Trailing_ParabolicSAR_Maximum=1.46

Nós vamos executar um forward test com esses parâmetros (WizardTest-plus-allfeatures-auto-nomasks.set) e receber o relatório:

Relatório do testador para as configurações selecionadas de forma automática dos clusters dos mapas de Kohonen sem a máscara de recurso

Relatório do testador para as configurações selecionadas de forma automática dos clusters dos mapas de Kohonen sem a máscara de recurso

O resultado não é melhor do que o primeiro teste aleatório, e é notavelmente pior do que o primeiro. Tudo se resume a qualidade da clusterização. No momento, ele está sendo executado por todos os recursos de uma vez, incluindo as métricas econômicas e os parâmetros do EA. No entanto, nós devemos procurar pelo "platô", em termos de números de negociação apenas. Vamos aplicar à clusterização a máscara de recursos que considera apenas eles, ou seja, os primeiros seis. Para fazer isso, defina:

FeatureMask=1111110000000
FeatureMaskAfterTraining=true

E então reinicie o aprendizado. A clusterização no SOM-Explorer é executada como o estágio final de aprendizado, e os clusters obtidos são salvos no arquivo da rede. Isso é necessário para que a rede carregada posteriormente seja usada imediatamente para "reconhecer" novas amostras. Essas amostras podem conter menos que todos os índices (isto é, o vetor pode estar incompleto), como analisar os arquivos de configuração — ele contém apenas os parâmetros do EA, mas não há métricas econômicas (e eles não fazem sentido). Portanto, para essas amostras, sua própria máscara de recurso é construída na função LoadSettings, cuja máscara corresponde aos componentes existentes do vetor e, em seguida, essa máscara é aplicada à rede. Assim, a máscara de clusterização deve estar implicitamente presente na rede para não entrar em conflito com a máscara do vetor a ser "reconhecida".

Mas vamos voltar ao aprendizado usando a nova máscara. Isso mudará o plano de U-Matrix e dos clusters. O padrão de clusters mudará significativamente (a imagem da esquerda é antes da aplicação da máscara, enquanto a da direita é após a aplicação da máscara).

Clusters nos mapas de Kohonen construídos sem (à esquerda) e com (à direita) aplicação da máscara por métricas econômicas

Clusters nos mapas de Kohonen construídos sem (à esquerda) e com (à direita) aplicação da máscara por métricas econômicas

Agora, os valores do cluster zero são diferentes.

N0 [14,1] 
[0] 17806.57263     2.79534     6.78011     0.48506    10.70147    49.90295    40.00000
[7]    85.62392    73.51490    20.00000     0.49750     0.13273     1.29078

Se você se lembrar, nós selecionamos em nossa avaliação especializada o neurônio localizado no canto superior direito, ou seja, que tem coordenadas [14,0]. Agora, o sistema nos oferece o neurônio vizinho [14,1]. Seus pesos não são muito diferentes. Vamos mover as configurações propostas para os parâmetros do EA.

Signal_ThresholdOpen=40
Signal_ThresholdClose=86
Signal_RSI_PeriodRSI=74
Signal_Envelopes_PeriodMA=20
Signal_Envelopes_Deviation=0.5
Trailing_ParabolicSAR_Step=0.13
Trailing_ParabolicSAR_Maximum=1.29

We will obtain the following results:

Relatório do testador para as configurações selecionadas de forma automática dos clusters dos mapas de Kohonen, usando a máscara de métricas econômicas

Relatório do testador para as configurações selecionadas de forma automática dos clusters dos mapas de Kohonen, usando a máscara de métricas econômicas

Eles são idênticos aos resultados do EA, independentemente de algumas diferenças nos parâmetros.

Para facilitar a movimentação dos resultados de clusterização para as configurações do EA, nós escreveremos uma função auxiliar que gerará o arquivo de configuração com os nomes e valores dos recursos do cluster selecionado (zero, por padrão). A função chama-se SaveSettings e entra na operação se o novo parâmetro SaveClusterAsSettings contiver o índice do cluster a ser exportado. Por padrão, esse parâmetro contém -1, o que significa que não há necessidade de gerar o arquivo de configuração. É claro que essa função não "sabe" sobre o sentido aplicado dos recursos e salva-os todos nomeados no arquivo set, portanto, as métricas de negociação, como Lucro, Fator de Lucro, etc., estarão lá também. O usuário pode copiar do arquivo gerado apenas os recursos que correspondem aos parâmetros reais do EA. Os valores dos parâmetros são salvos como números reais, então eles terão que ser corrigidos para os parâmetros do tipo inteiro.

Agora que nós podemos salvar as configurações encontradas em formato portátil, vamos criar as configurações para o cluster 0 (Wizard2018plusCluster0.set) e carregá-las de volta para o SOM-Explorer (deve ser lembrado que nosso utilitário já foi capaz de ler os arquivos de configuração). No parâmetro NetFileName, é necessário especificar o nome da rede criada no estágio de aprendizado anterior (deve ser Wizard2018plus.som, já que nós usamos os dados de Wizard2018plus.csv — em cada ciclo de aprendizado, ele é salvo no arquivo, o nome é o da entrada, mas com a extensão *.som). No parâmetro DataFileName, nós vamos especificar o nome do arquivo de configuração gerado. A marca SETTINGS será sobreposta com o centro do cluster C0.

Nós vamos renomear o arquivo som com a rede, para que ele não seja reescrito durante os experimentos subsequentes.

O primeiro experimento será o seguinte. Nós treinaremos a rede com uma máscara "reversa" — pelos parâmetros do EA, excluindo os valores de negociação. Para fazer isso, nós vamos especificar novamente o arquivo Wizard2018plus.csv no parâmetro DataFileName, limpar o parâmetro NetFileName e definir 

FeatureMask=0000001111111

Observe que FeatureMaskAfterTraining ainda está definido como 'true', ou seja, a máscara afeta apenas a clusterização.

Após o término do aprendizado, nós carregaremos a rede ensinada e testaremos o arquivo de configuração nela. Para fazer isso, nós vamos mover o nome do arquivo de rede criado Wizard2018plus.som para o parâmetro NetFileName e copiar o Wizard2018plusCluster0.set para DataFileName novamente. O conjunto total de mapas permanecerá inalterado, enquanto a U-Matrix e, portanto, os clusters serão diferentes.

O resultado da clusterização é exibido na imagem abaixo, à esquerda:

Clusters em redes de Kohonen construídos com uma máscara nos parâmetros do EA: somente no estágio de clusterização (à esquerda) e no estágio de aprendizado da rede e clusterização (à direita)

Clusters em mapas de Kohonen construídos com uma máscara nos parâmetros do EA: somente no estágio de clusterização (à esquerda) e no estágio de aprendizado da rede e clusterização (à direita)

A seleção de configurações é confirmada pelo fato de que elas chegam ao cluster zero e são maiores em tamanho.

Como o segundo experimento, nós treinaremos a rede mais uma vez com a mesma máscara (pelos parâmetros do EA). No entanto, nós vamos estendê-lo na fase de aprendizado também:

FeatureMaskAfterTraining=false

O conjunto completo de mapas é mostrado abaixo, enquanto as mudanças nos clusters são mostradas em uma escala maior na imagem acima, à direita. Aqui, os neurônios são agrupados apenas pela similaridade dos parâmetros. Esses mapas devem ser lidos da seguinte forma: Quais índices econômicos devem aparecer nos parâmetros selecionados? Embora as configurações de teste tenham caído no cluster número 2 (é pior que 2, mas não muito), seu tamanho é um dos maiores, o que é uma característica positiva.

Mapas de Kohonen construídos com uma máscara pelos parâmetros do EA

Mapas de Kohonen construídos com uma máscara pelos parâmetros do EA

Um leitor interessado notará que os mapas com as métricas econômicas não são mais de natureza topológica expressa. No entanto, isso não é necessário agora, pois a essência da verificação é identificar a estrutura dos clusters com base nos parâmetros e garantir que as configurações selecionadas estejam bem localizadas nesse sistema de clusters.

Uma busca "inversa" seria o aprendizado completo e a clusterização com a máscara pelas métricas econômicas:

FeatureMask=1111110000000
FeatureMaskAfterTraining=false

Este é o resultado:

Mapas de Kohonen construídos com uma máscara por índices econômicos

Mapas de Kohonen construídos com uma máscara por índices econômicos

Agora, as cores de contraste estão concentradas nos primeiros seis planos das métricas econômicas, enquanto os mapas dos parâmetros são bastante amorfos. Particularmente, nós podemos ver que os valores dos parâmetros Trailing_ParabolicSAR_Step e Trailing_ParabolicSAR_Maximum praticamente não se alteram (os mapas são monótonos), isso significa que eles podem ser excluídos da otimização, tendo como base algo médio, como 0.11 e 1.14, respectivamente. Entre o mapa de parâmetros, Signal_ThresholdOpen se destaca com o seu contraste, e torna-se claro a partir dele que é necessário escolher Signal_ThresholdOpen igual a 0.4 para uma negociação de sucesso. O mapa U-Matrix é obviamente dividido em dois "agrupamentos", o superior e o inferior, onde o superior é para o sucesso e o inferior é para a falha. O mapa de ocorrências é muito escasso e não uniforme (a maior parte do espaço são lacunas, enquanto os neurônios ativos têm um grande valor no contador), uma vez que os lucros são agrupados por vários níveis óbvios no EA sob pesquisa.

Esses mapas devem ser lidos da seguinte maneira: Quais parâmetros devem aparecer nos índices econômicos selecionados?

Finalmente, para o último experimento, vamos desenvolver um pouco o SOM-Explorer para poder nomear os clusters razoavelmente. Vamos escrever a função SetClusterLabels que analisará os valores dos componentes específicos dos vetores de código dentro de cada cluster e os comparará ao intervalo de valores desses componentes. Assim que o valor do peso em um neurônio se aproximar do valor mais alto ou mais baixo, esse será o motivo para marcá-lo com o nome do componente relevante. Por exemplo, se o peso do 0º relacionamento (correspondente ao lucro) exceder os pesos em outros clusters, isso significa que esse cluster é caracterizado por alto lucro. Deve-se notar que o sinal do extremo, máximo ou mínimo, é determinado pelo significado da indicação: Para o lucro, quanto maior o valor, melhor; e vice-versa para o rebaixamento. Neste contexto, nós introduzimos uma entrada especial, FeatureDirection, na qual nós vamos marcar as indicações com efeito positivo com o sinal de '+' e aquelas com o efeito negativo com o sinal de '-', enquanto aquelas que não são importante ou não tem sentido será ignorado usando o sinal de ','. Note que, no nosso caso, é razoável marcar apenas métricas econômicas, uma vez que os valores dos parâmetros de entrada do EA podem ser qualquer um e não são interpretados como bons ou ruins dependendo de sua proximidade com os limites do intervalo de definições. Portanto, vamos definir o valor de FeatureDirection apenas para os seis primeiros recursos:

FeatureDirection=++++-+

É assim que os rótulos procuram a rede com aprendizado sobre o vetor completo e clusterização pelas métricas econômicas (à esquerda) e para a rede com aprendizado e clusterização com uma máscara nos parâmetros do EA.

Rótulos de cluster, considerando os casos de: Usando uma máscara pelas métricas econômicas no estágio de clusterização (à esquerda) e usando uma máscara por parâmetros nos estágios de aprendizado e de clusterização (à direita)

Rótulos de cluster, considerando os casos de: Usando uma máscara pelas métricas econômicas no estágio de clusterização (à esquerda) e usando uma máscara por parâmetros nos estágios de aprendizado e de clusterização (à direita)

Nós podemos ver que, em ambos os casos, as configurações selecionadas se enquadram nos clusters de Fator de Lucro, portanto, nós podemos esperar a maior eficiência nessa indicação. No entanto, deve-se levar em consideração que todos os rótulos são positivos aqui, uma vez que os dados de origem são obtidos da otimização genética que atua como um filtro para a seleção de opções aceitáveis. Se os dados da otimização completa forem analisados, nós podemos identificar os clusters que são negativos por natureza.

Assim, nós consideramos em termos gerais a busca pelos parâmetros ótimos do EA usando a clusterização nos mapas de Kohonen. Como essa abordagem é flexível, ela é complicada. Ela implica o uso de vários métodos de treino da clusterização e da rede, bem como pré-processar as entradas. A busca por opções ótimas também depende muito da especificidade do robô em funcionamento. A ferramenta apresentada, SOM-Explorer, permite-nos começar a estudar esta fatia do processamento de dados, bem como expandi-lo e melhorá-lo.


Classificação colorida dos resultados da otimização

Como os métodos e qualidade da clusterização afetam fortemente a busca correta das configurações ótimas do EA, vamos tentar resolver o mesmo problema por meio de um método mais simples, sem avaliar a proximidade dos neurônios.

Vamos mostrar os pesos dos recursos em um mapa de Kohonen dentro do modelo de cores RGB e selecionar o ponto mais claro. Como o modelo de cores RGB consiste em três planos, nós podemos visualizar exatamente três recursos dessa maneira. Se houver mais ou menos deles, nós mostraremos o brilho nas gradações de cinza, em vez das cores RGB, mas ainda encontraremos o ponto mais claro.

Devemos lembrar que, ao selecionar os recursos, é desejável escolher os mais independentes (o que já foi mencionado anteriormente).

Ao implementar a nova abordagem, nós demonstraremos simultaneamente a possibilidade de expandir as classes CSOM com as nossas próprias. Vamos criar a classe CSOMDisplayRGB, na qual nós apenas sobrescrevemos alguns métodos virtuais da classe pai CSOMDisplay e obtemos através disso o mapa RGB que será exibido em vez do último plano DIM_OUTPUT.

class CSOMDisplayRGB: public CSOMDisplay
{
  protected:
    int indexRGB[];
    bool enabled;
    bool showrgb;

    void CalculateRGB(const int ind, const int count, double &sum, int &col) const;
  
  public:
    void EnableRGB(const int &vector[]);
    void DisableRGB() { enabled = false; };
    virtual void RenderOutput() override;
    virtual string GetNodeAsString(const int node_index, const int plane) const override;
    virtual void Reset() override;
};

o código completo está anexado aqui.

Para usar esta versão, vamos criar uma modificação do SOM-Explorer-RGB e fazer as seguintes alterações.

Adicione uma entrada para ativar o modo RGB:

input bool ShowRGBOutput = false;

O objeto do mapa se tornará a classe derivada:

CSOMDisplayRGB KohonenMap;

Nós vamos implementar a exibição direta do plano RGB em uma ramificação de código separada:

  if(ShowRGBOutput && StringLen(FeatureDirection) > 0)
  {
    int rgbmask[];
    ArrayResize(rgbmask, StringLen(FeatureDirection));
    for(int i = 0; i < StringLen(FeatureDirection); i++)
    {
      rgbmask[i] = -(StringGetCharacter(FeatureDirection, i) - ',');
    }
    KohonenMap.EnableRGB(rgbmask);
    KohonenMap.RenderOutput();
    KohonenMap.DisableRGB();
  }

Ele usa o parâmetro FeatureDirection já familiar para nós. Ao usá-lo, nós poderemos escolher os recursos específicos (de todo o conjunto de recursos) a serem incluídos no espaço RGB. Por exemplo, para o nosso exemplo com o Wizard2018plus, basta escrever:

FeatureDirection=++,,-

para os dois primeiros recursos, lucro e PF, para entrar diretamente no mapa (eles correspondem aos sinais de '+'), enquanto o quinto recurso, rebaixamento, é um valor invertido (corresponde ao sinal de '-'). Características correspondentes a ',' são ignorados. Todos as características subsequentes não são consideradas. O arquivo WizardTest-rgb.set com configurações está anexado aqui (o arquivo de rede Wizard2018plus.som deve estar disponível desde o estágio anterior).

É assim que o espaço RGB aparece para essas configurações (à esquerda).

O espaço RGB para as características (lucro, PF e rebaixamento) e (PF, rebaixamento e negócios)

O espaço RGB para as características (lucro, PF e rebaixamento) e (PF, rebaixamento e negócios)

As cores são relevantes para os recursos na sequência de prioridade: o lucro é vermelho, o PF é verde e o rebaixamento é azul. O neurônio mais brilhante é marcado com 'RGB'.

No log, são exibidas as coordenadas do neurônio 'mais pálido' e os valores de suas características que podem atuar como uma opção das melhores configurações.

Como o lucro e o PF estão fortemente correlacionados, nós substituiremos a máscara por outra:

FeatureDirection=,+,,-+

Nesse caso, o lucro é ignorado, mas o número de negócios é adicionado (algumas corretoras oferecem bônus por volume). o resultado está na imagem acima (à direita). Aqui, a correspondência de cores é diferente: PF é vermelho, rebaixamento é verde e o número de negócios é azul.

Se nós selecionarmos apenas as características PF e rebaixamento, nós obteremos um mapa na escala de cinza (à esquerda):

Espaço RGB para características (PF e rebaixamento) e (lucro)

Espaço RGB para características (PF e rebaixamento) e (lucro)

para a verificação, nós podemos selecionar apenas uma característica, como o lucro, e garantir que o modo preto e branco corresponda ao primeiro plano (veja acima, à direita).


Análise do forward test

O Testador da MetaTrader é conhecido por permitir a complementação da otimização do EA com testes avançados nas configurações selecionadas. Seria tolice não tentar usar essas informações para análise usando os mapas de Kohonen e selecionar a opção específica para as configurações dessa maneira. Conceitualmente, os clusters ou áreas coloridas na seção transversal de lucros elevados no passado e no futuro devem incluir configurações que forneçam a renda mais estável.

Para fazer isso, vamos considerar os resultados de otimização do WizardTest juntamente com os forward tests (devo lembrar que a otimização foi realizada para os lucros dentro do intervalo de 20180101-20180701, enquanto o forward test foi executado em 20180701-20181201), remova todas as indicações dele, exceto os lucros no passado e no futuro, e obtenha o novo arquivo de entrada para a rede — Wizard2018-with-forward.csv (anexado a este). O arquivo com configurações, WizardTest-with-forward.set, também está anexado.

Treinar a rede por todas as características, incluindo os parâmetros do EA, fornece o seguinte resultado:

Mapas de Kohonen com a análise de forward test por todos as características, ou seja  fator de lucro e os parâmetros do EA

Os mapas de Kohonen com análise do forward test por todas as características, ou seja, fatores de lucro e os parâmetros do EA

Nós podemos ver que a área com o lucro futuro é significativamente menor que a do lucro anterior, e o cluster zero (vermelho) contém esse local. Além disso, incluí a análise de cores pela máscara de duas indicações (FeatureDirection=++), e o ponto mais claro no último mapa também entra neste cluster. No entanto, nem o centro do cluster nem o ponto pálido contêm configurações super-lucrativas — embora os resultados do Testador sejam positivos, eles são duas vezes piores do que os obtidos anteriormente.

Vamos tentar construir um mapa de Kohonen usando a máscara nas duas primeiras características, uma vez que apenas essas são as indicações. Para fazer isso, vamos aplicar as outras configurações com FeatureMask=110000000 (veja WizardTest-with-forward-mask11.set) e anexe à rede. O resultado é o seguinte.

Mapas de Kohonen com a análise do forward test por fatores de lucro

Mapas de Kohonen com a análise do forward test por fatores de lucro

Aqui nós podemos ver que os dois primeiros planos obtiveram a melhor estrutura espacial expressa e cruzaram as áreas com valores altos (lucros) no canto inferior esquerdo que também é destacado no último mapa. Tendo tomado as configurações desse neurônio, nós obteremos o seguinte resultado:

Relatório do testador para as configurações selecionadas com base no princípio de "estabilidade do lucro"

Relatório do testador para as configurações selecionadas com base no princípio de "estabilidade do lucro"

Isso, novamente, é pior do que o lucro obtido anteriormente.

Assim, esta experiência prática não nos permitiu encontrar configurações ótimas. Ao mesmo tempo, incluir os resultados dos testes futuros na análise da rede neural parece estar correto e provavelmente deve se tornar uma abordagem a ser recomendada. Quais nuances faltaram exatamente para ter sucesso e se o sucesso é garantido em todos os casos, nós propomos discutir nos comentários aqui. Talvez, o resultado obtido apenas ateste que o nosso EA de teste não serve para uma negociação estável.


Previsão de séries temporais

Visão global

As redes de Kohonen são usadas com mais frequência para realizar a análise visual e a clusterização de dados. No entanto, eles também podem ser usados na previsão. Este problema implica que o vetor de dados de entrada representa marcas de tick, e que sua parte principal normalmente dimensionada (n - 1), n sendo o tamanho do vetor, deve ser usada para prever o final, que normalmente é a marca tick mais recente. Essencialmente, esse problema é semelhante ao de restituir o ruído ou dos dados parcialmente perdidos. No entanto, ele tem sua especificidade.

Existem diferentes abordagens para a previsão usando as redes de Kohonen. Aqui estão apenas algumas delas.

Uma rede de Kohonen (A) aprende nos dados com vetores incompletos dimensionados (n - 1), ou seja, sem o último componente. Para cada neurônio i da primeira rede, é treinado um mapa adicional de Kohonen (Bi) para um conjunto truncado de vetores completos de tamanho n, que correspondem àqueles incompletos que foram exibidos naquele neurônio da rede A. Baseado nos neurônios na rede Bi, é calculado a probabilidade dos valores específicos da última coordenada n. Posteriormente, no estágio de operação, a futura marca tick é simulada de acordo com as probabilidades obtidas da rede Bi, assim que o vetor a ser previsto entra no neurônio da rede A.

Outra opção. O mapa de Kohonen A é treinado em vetores completos. O segundo mapa de Kohonen (B) é treinado em vetores modificados, nos quais os incrementos são tomados ao invés dos valores, ou seja,

yk = xk+1 - xk, k = 1 .. n - 1

onde yk e xk são os componentes do vetor inicial e do vetor modificado, respectivamente. Obviamente, o tamanho do vetor modificado é menor que o inicial por 1. Em seguida, alimentando a entrada de ambas as redes com os dados de treinamento, nós calculamos o número de exibições simultâneas do vetor completo para o neurônio i na primeira rede e para o neurônio j na segunda. Assim, obtemos as probabilidades condicionais pij do vetor y entrando no neurônio j (na rede B), desde que o vetor x relevante para ele tenha entrado no neurônio i (na rede A). No estágio de operação, a rede A é alimentada com o vetor incompleto x- (sem o último componente), o neurônio mais próximo i* por (n - 1) componentes é encontrado, e então o incremento potencial é gerado de acordo com as probabilidades pi*. Este método, como alguns outros, está relacionado ao método de Monte Carlo que consiste em gerar valores aleatórios para o componente a ser previsto e encontrar o resultado mais provável de acordo com as estatísticas de popular os neurônios com os dados da seleção de ensino.

Essa lista pode continuar por muito mais tempo, pois inclui os chamados SOMs parametrizados e modelos auto-regressivos nos neurônios do SOM, e até mesmo a clusterização de atratores estocásticos no espaço de incorporação.

No entanto, todos eles são mais ou menos baseados no fenômeno de quantização de vetores que caracteriza as redes de Kohonen. Como a rede é treinada, os pesos dos neurônios tendem assintoticamente para os valores médios das classes (subconjuntos das entradas) que eles representam. Cada um desses valores médios fornece a melhor avaliação dos valores que faltam (ou futuros, no caso da previsão) nos novos dados com a mesma lei estatística da seleção de ensino. Quanto mais compacto e melhor dividido as classes, mais precisa é a avaliação.

Nós vamos usar o método de previsão mais simples baseado na quantização de vetores, que envolve a instância única da rede de Kohonen. No entanto, mesmo este apresenta algumas dificuldades.

Nós vamos alimentar a entrada da rede com os vetores completos de uma série temporal. No entanto, as distâncias serão calculadas apenas para os primeiros componentes (n - 1). Os pesos serão adaptados na íntegra e para todos os componentes. Então, para fins de previsão, nós alimentaremos a entrada da rede com o vetor incompleto, encontraremos o melhor neurônio para o componente (n - 1) e faremos a leitura do valor do peso da última enésima sinapse. Esta será a previsão.

Nós temos tudo pronto para isso na classe CSOM. Existe o método SetFeatureMask, que define a máscara nas dimensões do espaço dos recursos envolvidos no cálculo das distâncias. No entanto, antes de implementar o algoritmo, nós devemos decidir o que exatamente nós vamos prever.


Indicador de cluster Unity

Entre os traders, são reconhecidas séries de cotações para representar um processo de tempo não trivial. Ele contém muita aleatoriedade, frequentes mudanças de fase e um grande número de fatores de influência, e isso é, em princípio, um sistema aberto.

Para simplificar o problema, nós selecionaremos para análise um dos maiores tempos gráficos, o diário. Nesse tempo gráfico, o ruído fornece menos influência do que nos menores. Além disso, nós selecionaremos para previsão do instrumento(s), sobre o qual as notícias fundamentalistas fortes, que podem mover o mercado incalculavelmente, aparecem raramente. Na minha opinião, os melhores são metais, ou seja, ouro e prata. Não sendo um instrumento de pagamento de qualquer país específico e atuando tanto como matéria-prima quanto como ativo de proteção, eles são menos voláteis, por um lado, e estão inextricavelmente conectados com as moedas, por outro lado.

Teoricamente, seus movimentos futuros devem considerar as cotações atuais e responder às cotações do Forex.

Assim, nós precisamos de uma maneira de receber as alterações síncronas nos preços dos metais e moedas básicas, representadas no agregado. Em qualquer momento específico, este deve ser um vetor com n componentes, cada um dos quais corresponde ao custo relativo de uma moeda ou de um metal. E o objetivo da previsão consiste em prever por tal vetor o próximo valor de um dos componentes.

Para isso, foi criado o indicador original de cluster chamado Unity (seu código-fonte está anexado aqui). A essência de sua operação é descrita com o seguinte algoritmo. Vamos considerar isso exemplificado da maneira mais simples possível, ou seja, por um par de moedas, EURUSD e ouro, XAUUSD.

Cada marca de tick (preços correntes desde o início/fim do dia) é descrita por fórmulas óbvias:

EUR / USD = EURUSD

XAU / USD = XAUUSD

onde as variáveis EUR, USD e XAU são determinados "custos" independentes dos ativos, enquanto o EURUSD e XAUUSD são constantes (as cotações conhecidas).

Para encontrar as variáveis, vamos adicionar mais uma equação ao sistema, limitando a soma dos quadrados das variáveis por uma unidade:

EUR*EUR + USD*USD + XAU*XAU = 1

Daí o nome do indicador, Unity.

Usando uma substituição simples, nós obtemos:

EURUSD*USD*EURUSD*USD + USD*USD + XAUUSD*USD*XAUUSD*USD = 1

De onde, nós encontramos USD:

USD = sqrt(1 / (1 + EURUSD*EURUSD + XAUUSD*XAUUSD))

e depois todas as outras variáveis.

Ou, representado de forma mais generalizada:

x0 = sqrt(1 / (1 + sum(C(xi, x0)**2))), i = 1..n

xi = C(xi, x0) * x0, i = 1..n

onde n é o número de variáveis, C(xix0) é a cotação do iésimo par que inclui as variáveis relevantes. Note que o número de variáveis é maior por 1 que o dos instrumentos.

Como os coeficientes C envolvidos nos cálculos são cotações que geralmente variam profundamente; no indicador, eles são adicionalmente multiplicados pelo tamanho do contrato: Assim, obtêm-se valores que são mais ou menos comparáveis (ou, pelo menos, são da mesma magnitude). Para vê-los na janela do indicador (apenas para a informação de alguém), há uma entrada chamada AbsoluteValues que deve ser definida como true. Por padrão, é claro que é igual a false e o indicador calcula sempre os incrementos das variáveis:

yi = xi0 / xi1 - 1,

onde xi0 e xi1 são valores nas últimas e segundas últimas barras, respectivamente.

Não vamos considerar as especificidades técnicas da implementação do indicador aqui — você pode estudar seu código-fonte de forma independente.

Assim, nós obtemos os seguintes resultados:

Indicador de Cluster Unity (multi moedas), XAUUSD

Indicador de Cluster Unity (multi moedas), XAUUSD

As linhas dos ativos que compõem o instrumento de trabalho do gráfico atual (neste caso, XAU e USD) são exibidas largamente, enquanto outras linhas são de espessura normal.

O seguinte deve ser mencionado entre outras entradas do indicador:

Por exemplo, para preparar um arquivo com os dados para prever as mudanças no preço do ouro, você deve especificar no parâmetro Instruments: "EURUSD, GBPUSD, USDCHF, USDJPY, AUDUSD, USDCAD, NZDUSD, XAUUSD" (é importante especificar XAUUSD como o último), enquanto 'true' deve ser especificado em ShiftLastBuffer. Nós obteremos um arquivo csv com a seguinte estrutura:

           datetime;      EUR;     USD;      GBP;      CHF;      JPY;      AUD;      CAD;      NZD;      XAU; FORECAST
2016.12.20 00:00:00; 0.001825;0.000447;-0.000373; 0.000676;-0.004644; 0.003858; 0.004793; 0.000118;-0.004105; 0.000105
2016.12.21 00:00:00; 0.000228;0.003705;-0.001081; 0.002079; 0.002790;-0.002885;-0.003052;-0.002577; 0.000105;-0.000854
2016.12.22 00:00:00; 0.002147;0.003368;-0.003467; 0.003427; 0.002403;-0.000677;-0.002715; 0.002757;-0.000854; 0.004919
2016.12.23 00:00:00; 0.000317;0.003624;-0.002207; 0.000600; 0.002929;-0.007931;-0.003225;-0.003350; 0.004919; 0.004579
2016.12.27 00:00:00;-0.000245;0.000472;-0.001075;-0.001237;-0.003225;-0.000592;-0.005290;-0.000883; 0.004579; 0.003232

Por favor, note que as últimas 2 colunas contêm os mesmos números com um deslocamento de uma linha. Assim, na linha de 20 de dezembro de 2016, nós podemos ver o incremento de XAU nesse dia e seu incremento em relação a 21 de dezembro, na coluna FORECAST.


SOM-Forecast

Está na hora de implementar o mecanismo de previsão, baseado na rede de Kohonen. Primeiramente, para entender como ele funciona, nós vamos nos basear no já conhecido SOM-Explorer e adaptá-lo ao problema de previsão.

As mudanças centram-se em torno das entradas. Vamos remover tudo o que estiver relacionado à configuração das máscaras: FeatureMask, ApplyFeatureMaskAfterTraining e FeatureDirection, pois a máscara para previsão é conhecida - se as marcas de tamanho do vetor estiverem disponíveis, somente as primeiras (n - 1) devem estar envolvidas no cálculo da distância. Mas nós vamos adicionar uma opção lógica especial, ForecastMode, que nos permitiria desabilitar essa máscara, quando necessário, e usar os recursos analíticos representados classicamente da rede de Kohonen. Nós vamos precisar disso para explorar o mercado, ou melhor, o sistema dos instrumentos especificados em unidade, em estado estático, ou seja, ver correlações dentro do mesmo dia.

No caso de ForecastMode ser igual a 'true', nós colocamos a máscara. Se for "false", não há máscara.

    if(ForecastMode)
    {
      KohonenMap.SetFeatureMask(KohonenMap.GetFeatureCount() - 1, 0);
    }

Assim que a rede já tiver sido ensinada e o arquivo csv com dados de teste tiver sido especificado na entrada DataFileName, nós verificaremos a qualidade da previsão no ForecastMode da seguinte forma:

      if(ForecastMode)
      {
        int m = KohonenMap.GetFeatureCount();
        KohonenMap.SetFeatureMask(m - 1, 0);
        
        int n = KohonenMap.GetDataCount();
        double vector[];
        double forecast[];
        double future;
        int correct = 0;
        double error = 0;
        double variance = 0;
        for(int i = 0; i < n; i++)
        {
          KohonenMap.GetPattern(i, vector);
          future = vector[m - 1]; // preserva o futuro
          vector[m - 1] = 0;      // torna o futuro desconhecido para a rede (não é usado de qualquer maneira devido à máscara)
          KohonenMap.GetBestMatchingFeatures(vector, forecast);
          
          if(future * forecast[m - 1] > 0) // verifica se as direções correspondem
          {
            correct++;
          }
          
          error += (future - forecast[m - 1]) * (future - forecast[m - 1]);
          variance += future * future;
        }
        Print("Correct forecasts: ", correct, " out of ", n, " => ", DoubleToString(correct * 100.0 / n, 2), "%, error => ", error / variance);
      }

Aqui, cada vetor de teste é apresentado à rede usando GetBestMatchingFeatures, e a resposta é o vetor 'forecast'. Seu último componente é comparado ao valor correto do vetor de teste. As direções correspondentes são contadas na variável 'correct', o 'erro' total da previsão é acumulado, conforme relacionado à dispersão dos dados em si.

No caso do conjunto de validação (parâmetro ValidationSetPercent ser preenchido) ser especificado para aprendizado e ReframeNumber ser acima de zero, a nova função da classe CSOM, TrainAndReframe, está envolvida na operação. Isso nos permite aumentar o tamanho da rede de forma gradeada e rastrear as alterações no erro de aprendizado no conjunto de validação, para interromper esse processo assim que o erro deixar de cair e começar a crescer. Este é o momento em que adaptar os pesos a vetores específicos, devido ao aprimoramento das capacidades computacionais da rede, leva-o a perder sua capacidade de generalização e trabalhar com dados desconhecidos.

Tendo selecionado o tamanho, nós redefinimos ValidationSetPercent e ReframeNumber como 0 e treinamos a rede, como de costume, aplicando o método Train.

Finalmente, nós vamos alterar ligeiramente a função de marcação em clusters SetClusterLabels. Como os ativos serão as características e cada um deles pode demonstrar ambos o extremo positivo e o negativo, nós incluiremos o sinal de movimento no rótulo. Assim, uma e a mesma característica pode ser encontrada no mapa duas vezes, ambos com mais e com menos.

void SetClusterLabels()
{
  const int nclusters = KohonenMap.GetClusterCount();

  double min, max, best;
  
  double bests[][3]; // [][0 - value; 1 - feature index; 2 - direction]
  ArrayResize(bests, nclusters);
  ArrayInitialize(bests, 0);
  
  int n = KohonenMap.GetFeatureCount();
  for(int i = 0; i < n; i++)
  {
    int direction = 0;
    KohonenMap.GetFeatureBounds(i, min, max);
    if(max - min > 0)
    {
      best = 0;
      double center[];
      for(int j = nclusters - 1; j >= 0; j--)
      {
        KohonenMap.GetCluster(j, center);
        double value = MathMin(MathMax((center[i] - min) / (max - min), 0), 1);
        
        if(value > 0.5)
        {
          direction = +1;
        }
        else
        {
          direction = -1;
          value = 1 - value;
        }
        
        if(value > bests[j][0])
        {
          bests[j][0] = value;
          bests[j][1] = i;
          bests[j][2] = direction;
        }
      }
    }
  }

  // ...
  
  for(int j = 0; j < nclusters; j++)
  {
    if(bests[j][0] > 0)
    {
      KohonenMap.SetLabel(j, (bests[j][2] > 0 ? "+" : "-") + KohonenMap.GetFeatureTitle((int)bests[j][1]));
    }
  }
}

Então, vamos considerar o SOM-Forecast como pronto. Nós vamos agora tentar alimentar o valor de entrada do indicador Unity com ele.


Análise

Primeiro, vamos tentar analisar todo o mercado (o conjunto dos ativos selecionados) em estático, ou melhor, no contexto estatístico, ou seja, nos dados exportados pelo indicador Unity estritamente por dias — cada string corresponde com as indicações em uma barra D1, sem qualquer coluna adicional do 'futuro'.

Para este propósito, nós especificaremos no indicador o conjunto de instrumentos Forex, ouro e prata, e o nome do arquivo em SaveToFile, enquanto nós definimos ShiftLastBuffer como 'false'. Um arquivo exemplar, unity500-noshift.csv, obtido dessa maneira é anexado ao final deste artigo.

Tendo treinado a rede sobre esses dados usando o SOM-Forecast (veja som-forecast-unity500-noshift.set), nós obteremos o seguinte mapa:

Análise visual do mercado Forex, ouro e prata nos mapas de Kohonen pelo indicador Unity para D1

Análise visual do mercado Forex, ouro e prata nos mapas de Kohonen pelo indicador Unity para D1

Duas linhas superiores são os mapas dos ativos. Compará-los nos permite identificar as ligações permanentes que estão funcionando há pelo menos 500 dias. Particularmente, dois pontos multicoloridos nos centros chamam a atenção: ele é azul para GBP e amarelo para AUD. Isso significa que esses ativos estavam frequentemente na fase inversa, e isso se refere apenas a movimentos fortes, ou seja, as vendas do GBPAUD prevaleceram ao romper do nível médio de variância do mercado. Essa tendência pode persistir e fornecer uma opção para colocar as ordens pendentes nessa direção.

O crescimento de XAG é observado no canto superior direito, enquanto o CHF cai lá. E é vice-versa no canto inferior esquerdo: o CHF cresce e o XAG cai. Esses ativos sempre divergem em movimentos fortes, de modo que nós podemos negociá-los por rompimento em ambas as direções. Como esperado, EUR e CHF são semelhantes.

Algumas outras características específicas podem ser encontradas nos mapas. De fato, mesmo antes de nós começarmos a prever, nós tínhamos a chance de ver adiante até certo ponto.

Vamos dar uma olhada mais de perto nos clusters.

Clusters de Forex, ouro e prata nos mapas de Kohonen pelo indicador Unity para D1

Clusters de Forex, ouro e prata nos mapas de Kohonen pelo indicador Unity para D1

Eles também fornecem algumas informações, por exemplo, sobre o que geralmente não acontece (ou raramente é o caso), a saber: EUR não cresce com a queda de JPY ou CAD. Frequentemente, o GBP cresce em simultâneo com a queda do CHF e vice-versa.

A julgar pelo tamanho dos clusters, as moedas EUR, GBP e CHF são mais voláteis, ou seja, seus movimentos mais frequentes substituem os movimentos de outras moedas, enquanto o USD surpreendentemente perde, a esse respeito, tanto o JPY quanto o CAD (lembre-se, nós calculamos as alterações em %). Se nós assumirmos que os números dos clusters são importantes também (nós temos nossos clusters classificados), então os primeiros três, ou seja, +CHF, -JPY e +GBP, aparentemente, descrevem os movimentos diários mais frequentes (sem tendência, mas se refere exatamente a frequência, pois mesmo durante o período 'lateralizado' um número maior de 'passos' para cima pode ser compensado por um grande 'passo' para baixo).

Agora, nós finalmente, vamos ao problema de previsão.


Previsão

Vamos especificar o conjunto de instrumentos, incluindo Forex e ouro ("EURUSD, GBPUSD, USDCHF, USDJPY, AUDUSD, USDCAD, NZDUSD, XAUUSD") no indicador, número de barras em BarLimit (500 por padrão), nome do arquivo em SaveToFile (como unity500xau.csv) e definir a flag ShiftLastBuffer como "true" (modo de criar uma coluna adicional para previsão). Como é um indicador de várias moedas, o número de dados disponíveis é limitado ao menor histórico de todos os instrumentos. No servidor da MetaQuotes-Demo, há um número totalmente suficiente de barras, pelo menos 2.000 barras, para o período de tempo D1. Então, aqui, seria útil considerar outro ponto: se é razoável ensinar a rede a uma profundidade tão grande, já que o mercado provavelmente se alterou de forma considerável. Um histórico de 2 a 3 anos ou até 1 ano (250 barras em D1) provavelmente seria mais adequado para identificar as regularidades atuais.

O arquivo de exemplo unity500xau.csv está anexado no final deste documento.

Para carregá-lo no SOM-Forecast, nós definiremos as seguintes entradas: DataFileName — unity500xau, ForecastMode — true, ValidationSetPercent — 10 e, o que é importante: ReframeNumber — 10. Dessa maneira, nós dois executaremos o ensino da rede com o tamanho 10*10 (valores padrão) e habilitaremos a verificação do erro em uma seleção de validação e continuaremos a treiná-lo aumentando gradualmente o tamanho da rede, enquanto o erro diminui. Até 10 aumentos no tamanho ocorrerão no método TrainAndReframe, 2 neurônios em cada dimensão. Assim, nós vamos detectar o tamanho da rede ideal para as entradas. O arquivo de configurações (som-forecast-unity500xau.set) está anexado.

Enquanto o treino é gradual e a rede aumenta, substancialmente o seguinte é exibido no log (fornecido de forma resumida):

FileOpen OK: unity500xau.csv
HEADER: (11) datetime;EUR;USD;GBP;CHF;JPY;AUD;CAD;NZD;XAU;FORECAST
Training 10*10 hex net starts
...
Exit by validation error at iteration 104; NMSE[old]=0.4987230270708455, NMSE[new]=0.5021707785446128, set=50
Training stopped by MSE at pass 104, NMSE=0.384537545433749
Training 12*12 hex net starts
...
Exit by validation error at iteration 108; NMSE[old]=0.4094350709134669, NMSE[new]=0.4238670029035179, set=50
Training stopped by MSE at pass 108, NMSE=0.3293719049246978
...
Training 24*24 hex net starts
...
Exit by validation error at iteration 119; NMSE[old]=0.3155973731785412, NMSE[new]=0.3177587737459486, set=50
Training stopped by MSE at pass 119, NMSE=0.1491464262340352
Training 26*26 hex net starts
...
Exit by validation error at iteration 108; NMSE[old]=0.3142964426509741, NMSE[new]=0.3156342534501801, set=50
Training stopped by MSE at pass 108, NMSE=0.1669971604289485
Exit map size increments due to increased MSE
...
Map file unity500xau.som saved

Assim, o processo parou na rede de tamanho 26*26. Aparentemente, nós devemos selecionar 24*24, onde o erro foi o menor. Mas, na verdade, quando se trabalha com redes neurais, nós sempre jogamos com chances. Se você se lembrar, um dos parâmetros, RandomSeed, foi responsável pela inicialização do gerador de dados aleatórios usado para definir os pesos iniciais na rede. Sempre que mudarmos o RandomSeed, nós teremos uma nova rede com novas características. E, como todos os outros fatores são iguais, ele aprende melhor ou pior do que outras instâncias. Portanto, para selecionar o tamanho da rede, bem como outras configurações, incluindo o tamanho da seleção do treinamento, geralmente temos que fazer muitas tentativas e cometer muitos erros. Então, nós seguiremos esse princípio para diminuir a seleção de dados de treino e reduzir o tamanho da rede para 15*15. Além disso, às vezes eu utilizo a modificação não canônica do método descrito de quantização temporal de vetores. A modificação consiste em treinar a rede com o indicador ForecastMode sendo desativado e ativando-o somente na previsão. Em alguns casos, isso produz um efeito positivo. Trabalhar com redes neurais nunca sugere "refeições prontas", apenas receitas. E experimentos não são proibidos.

Antes de treinar, o arquivo de origem (no nosso caso, unity500xau.csv) deve ser dividido em dois. A questão é que, ao treinar, nós precisaremos verificar a qualidade da previsão em alguns dados. Não faz qualquer sentido fazê-lo nos mesmos dados que são usados pela rede para aprendizagem (para ser mais exato, faz sentido apenas para ver por si mesmo que a porcentagem de respostas corretas é irreal - você pode verificar isso). Portanto, vamos copiar 50 vetores para testá-los em um arquivo separado, enquanto nós treinamos a nova rede usando os que sobraram. Você pode encontrar em anexo os arquivos relevantes, unity500xau-training.csv e unity500xau-validation.csv.

Vamos especificar unity500xau-training no parâmetro DataFileName (a extensão *.csv está implícita), os valores de CellsX e CellsY devem ser 24 cada, ValidationSetPercent é 0, ReframeNumber é 0 e ForecastMode ainda é 'true' (veja som-forecast-unity500xau-training.set). Como resultado do treinamento, nós obteremos o mapa da seguinte forma:

Rede de Kohonen para prever os movimentos do ouro em D1

Rede de Kohonen para prever os movimentos do ouro em D1

Isso não é previsão ainda, mas uma confirmação visual de que a rede aprendeu alguma coisa. O que exatamente, nós vamos verificar se alimentamos a entrada com nosso arquivo de validação.

Para fazer isso, vamos inserir as seguintes configurações. O nome do arquivo unity500xau-training agora será transferido para o parâmetro NetFileName (este é o nome do arquivo para a rede já ensinado, cujo arquivo tem a extensão *.som implícita). No parâmetro DataFileName, nós especificamos unity500xau-validation. A rede irá prever todos os vetores e exibir as estatísticas no log:

Map file unity500xau-training.som loaded
FileOpen OK: unity500xau-validation.csv
HEADER: (11) datetime;EUR;USD;GBP;CHF;JPY;AUD;CAD;NZD;XAU;FORECAST
Correct forecasts: 24 out of 50 => 48.00%, error => 1.02152123166208

Infelizmente, a precisão da previsão está no nível de adivinhação aleatória. Poderia um graal resultar de uma escolha tão simples de entradas? Improvável. Além disso, nós escolhemos a profundidade histórica como um palpite, enquanto este assunto também deve ser investigado. Nós devemos também brincar com o conjunto de instrumentos, como adicionar prata, e, em geral, esclarecer, qual dos ativos depende mais dos outros, para facilitar a previsão. Abaixo, nós vamos considerar um exemplo usando prata.

Para introduzir mais contexto nas entradas, eu adicionei ao indicador Unity a opção de formar um vetor baseado em apenas um dia e em vários últimos. Para este propósito, o parâmetro BarLookback foi adicionado, igual a 1 por padrão, o que corresponde ao modo anterior. No entanto, se inserirmos, por exemplo, 5 aqui, o vetor conterá as marcas de todos os ativos em 5 dias (fundamentalmente, uma semana é um dos períodos básicos). No caso de 9 ativos (8 moedas e ouro), 46 valores são armazenados em cada sequência do arquivo csv. Isso aumenta bastante, os cálculos ficam mais lentos, e analisar os mapas se torna mais difícil: mesmo os mapas pequenos dificilmente cabem na tela, enquanto os maiores podem ficar sem histórico no gráfico.

Nota. Ao exibir um número tão grande de mapas no modo de objetos, como OBJ_BITMAP (MaxPictures = 0), pode não haver barras suficientes no gráfico: elas estão ocultas, mas ainda são usadas para vincular imagens.

Para demonstrar o desempenho do método, eu decidi em 3 dias de visualização e na rede de tamanho 15*15. Os arquivos unity1000xau3-training.csv e unity1000xau3-validation.csv estão anexados aqui. Tendo treinado a rede no primeiro arquivo e validado no segundo, nós obteremos 58% de previsões precisas.

Map file unity1000xau3-training.som loaded
FileOpen OK: unity1000xau3-validation.csv
HEADER: (29) datetime;EUR3;USD3;GBP3;CHF3;JPY3;AUD3;CAD3;NZD3;XAU3;EUR2;USD2;GBP2;CHF2;JPY2;AUD2;CAD2;NZD2;XAU2;EUR1;USD1;GBP1;CHF1;JPY1;AUD1;CAD1;NZD1;XAU1;FORECAST
Correct forecasts: 58 out of 100 => 58.00%, error => 1.131192104823076

Isso é bom o suficiente, mas nós não devemos esquecer a natureza aleatória dos processos nas redes: com outras entradas e outra inicialização, obteremos ótimos resultados, assim como eles podem ser piores. Todo o sistema precisa ser verificado novamente e reconfigurado várias vezes. Na prática, várias instâncias são geradas e, em seguida, as melhores são selecionadas. Além disso, ao tomar nossas decisões, nós podemos usar um comitê, não apenas uma rede.


Unity-Forecast

Para fins de previsão, não é necessário exibir os mapas na tela. Nos programas em MQL, como EAs ou indicadores, nós podemos usar a classe CSOM em vez de CSOMDisplay. Vamos criar o indicador Unity-Forecast que é semelhante ao Unity, mas tem um buffer adicional para exibir a previsão. A rede de Kohonen usada para obter os valores "futuros" pode ser treinada separadamente (em SOM-Forecast) e depois carregada no indicador. Ou nós podemos treinar a rede 'on-the-go', diretamente no indicador. Vamos implementar os dois modos.

Para carregar a rede, vamos adicionar o NetFileName de entrada. Para ensinar a rede, vamos adicionar o grupo de parâmetros semelhante ao contido no SOM-Forecast: CellsX, CellsY, HexagonalCell, UseNormalization, EpochNumber, ShowProgress e RandomSeed.

Nós não precisamos do parâmetro AbsoluteValues aqui, então vamos substituí-lo pela constante 'false'. O ShiftLastBuffer também não faz sentido, já que o novo indicador sempre significa previsão. A exportação para o arquivo csv é excluída, portanto, vamos remover o parâmetro SaveToFile. Em vez disso, nós adicionaremos a flag SaveTrainedNetworks: Se for 'true', o indicador salvará as redes ensinadas nos arquivos para que possamos estudar seus mapas no SOM-Forecast.

O parâmetro BarLimit será usado como o número de barras a serem exibidas na inicialização e o número de barras usadas para treinar a rede.

O novo parâmetro, RetrainingBars, permite especificar o número de barras, após o qual a rede deve ser novamente treinada.

Vamos incluir um arquivo de cabeçalho:

#include <CSOM/CSOM.mqh>

No processador de ticks, vamos verificar uma nova barra disponível (a sincronização por barras em todos os instrumentos é necessária - não é feita neste projeto de demonstração) e, se for a hora de mudar a rede, carregamos do arquivo NetFileName ou treinamos ela sobre os dados do próprio indicador na função TrainSOM. Depois disso, nós vamos prever usando a função ForecastBySOM.

  if(LastBarCount != rates_total || prev_calculated != rates_total)
  {
    static int prev_training = 0;
    if(prev_training == 0 || prev_calculated - prev_training > RetrainingBars)
    {
      if(NetFileName != "")
      {
        if(!KohonenMap.Load(NetFileName))
        {
          Print("Map loading failed: ", NetFileName);
          initDone = false;
          return 0;
        }
      }
      else
      {
        TrainSOM(BarLimit);
      }
      prev_training = prev_calculated > 0 ? prev_calculated : rates_total;
    }
    ForecastBySOM(prev_calculated == 0);
  }

As funções TrainSOM e ForecastBySOM são fornecidas abaixo (de forma simplificada). O código fonte completo está anexado aqui.

CSOM KohonenMap;

bool TrainSOM(const int limit)
{
  KohonenMap.Reset();

  LoadPatterns(limit);
  
  KohonenMap.Init(CellsX, CellsY, HexagonalCell);
  KohonenMap.SetFeatureMask(KohonenMap.GetFeatureCount() - 1, 0);
  KohonenMap.Train(EpochNumber, UseNormalization, ShowProgress);

  if(SaveTrainedNetworks)  
  {
    KohonenMap.Save("online-" + _Symbol + CSOM::timestamp() + ".som");
  }
  
  return true;
}

bool ForecastBySOM(const bool anew = false)
{
  double vector[], forecast[];
  
  int n = workCurrencies.getSize();
  ArrayResize(vector, n + 1);
  
  for(int j = 0; j < n; j++)
  {
    vector[j] = GetBuffer(j, 1); // 1-st bar is the latest completed
  }
  vector[n] = 0;
  
  KohonenMap.GetBestMatchingFeatures(vector, forecast);
  
  buffers[n][0] = forecast[n];
  if(anew) buffers[n][1] = GetBuffer(n - 1, 1);
  
  return true;
}

Por favor, note que nós estamos, de fato, prevendo o preço de fechamento da barra atual (0) no momento em que ela é aberta. Portanto, não há deslocamento do buffer 'future' no indicador, como relacionado a outros buffers.

Desta vez, vamos tentar prever o comportamento da prata e visualizar todo o processo no Testador. O indicador vai aprender 'on-the-go' nos últimos 250 dias (1 ano) e realizar um novo treinamento a cada 20 dias (1 mês). O arquivo com configurações, unity-forecast-xag.set, é fornecido no final deste artigo. É importante notar que o conjunto de instrumentos é estendido: EURUSD, GBPUSD, USDCHF, USDJPY, AUDUSD, USDCAD, NZDUSD, XAUUSD, XAGUSD. Assim, nós projetamos o XAG com base nas cotações do Forex e na própria prata e ouro.

É assim que os testes aparecem no período de 1 de julho de 2018 até 1 de dezembro de 2018.

Unity-Forecast: Previsão dos movimentos da prata no Forex e cluster de ouro no Testador da MetaTrader 5

Unity-Forecast: Previsão dos movimentos da prata no Forex e cluster de ouro no Testador da MetaTrader 5

Às vezes, a precisão chega a 60%. Nós podemos concluir que, basicamente, o método funciona, mesmo que seja necessário selecionar fundamentalmente o objeto de previsão, preparar entradas, períodos prolongados, configurar de forma meticulosa.

Atenção! O treinamento de uma rede no modo 'on-the-go' bloqueia o indicador (assim como outros indicadores com o mesmo símbolo/período de tempo), o que não é recomendado. Na realidade, este código deve ser executado em um EA. Aqui, ele é colocado em um indicador para fins ilustrativos. Para atualizar a rede no indicador, você pode usar um EA ajudando a gerar mapas por agendamento, enquanto este indicador os recarregará pelo nome especificado em NetFileName.

As seguintes opções podem ser consideradas para melhorar a qualidade da previsão: Adicionando fatores externos, como números de dias da semana às entradas ou implementando métodos mais sofisticados baseados em várias redes de Kohonen ou em uma rede de Kohonen junto com as redes de outros tipos.


Conclusão

Este artigo tratou do uso prático das redes de Kohonen na solução de alguns problemas de um trader. A tecnologia baseada em rede neural é uma ferramenta poderosa e flexível que permite envolver vários métodos de processamento de dados. Ao mesmo tempo, eles exigem cuidadosamente a seleção de variáveis de entrada, profundidade do histórico e combinações de parâmetros, para que seu uso bem-sucedido seja largamente determinado pelo conhecimento e habilidades do usuário. As classes consideradas aqui permitem que você teste as redes de Kohonen na prática, ganhe a experiência necessária para usá-las e as adapte às suas próprias tarefas.