![Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 09): Combinação de agrupamento k-médias com ondas fractais](https://c.mql5.com/2/62/midjourney_image_13915_50_439_5_600x314.jpg)
Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 09): Combinação de agrupamento k-médias com ondas fractais
Introdução
Continuamos a explorar ideias simples que podem ser implementadas e testadas com o assistente MQL5. Desta vez, vamos falar sobre o agrupamento k-médias. Assim como o agrupamento hierárquico, que discutimos em um artigo anterior, ele é uma forma de aprendizado não supervisionado aplicado à classificação de dados.
Antes de começarmos, talvez seja útil relembrar o agrupamento hierárquico e sua diferença em relação ao k-médias. O algoritmo de agrupamento hierárquico aglomerativo inicialmente considera cada ponto de dados no conjunto a ser classificado como um cluster. O algoritmo então os combina iterativamente em clusters com base em sua proximidade. Normalmente, o número de clusters não é definido de antemão, mas o analista pode definir quantos ao visualizar o dendrograma gerado, que é o resultado final quando todos os pontos de dados são combinados em um único cluster. No entanto, alternativamente, como visto neste artigo, se o analista tiver um determinado número de clusters em mente, o dendrograma de saída será finalizado no nível/altura correspondente ao valor inicial do analista. Na verdade, dependendo de onde o dendrograma é cortado diferentes números de clusters podem ser obtidos.
Já no k-médias começamos com a escolha aleatória dos centros dos clusters (centroides) com base em um valor pré-estabelecido pelo analista. Então, a variância de cada ponto de dados em relação ao centro mais próximo é determinada, e ajustes iterativos são feitos nos valores do centro/centroide até que a variância seja mínima dentro de cada cluster.
Por padrão, o método k-médias é bem lento e ineficiente, muitas vezes chamado de "ingênuo" porque existem implementações mais rápidas. Isso acontece em parte por causa da escolha aleatória de centroides iniciais no início da otimização. Além disso, uma vez escolhidos os centroides aleatórios, geralmente se usa o algoritmo de Lloyd para encontrar o centroide correto e, assim, conseguir os valores das categorias. Existem complementos e alternativas ao algoritmo de Lloyd, incluindo: quebras naturais de Jenks (Jenks’ Natural Breaks), que se concentram na média do cluster, em vez da distância aos centroides escolhidos; k-medianas, que, como o nome sugere, usam a mediana do cluster, em vez do centroide ou média, como intermediário para atingir uma classificação ideal; k-medoides, que usam pontos de dados reais dentro de cada cluster como possíveis centroides, tornando-os mais robustos contra ruídos e valores atípicos, segundo a Wikipédia; e, finalmente, agrupamento difuso , em que os limites dos clusters são difusos, e os pontos de dados tendem a pertencer a mais de um cluster. Este último formato é interessante porque, em vez de "classificar" cada ponto de dados, usa um peso regressivo que quantifica a medida em que cada ponto de dados pertence a cada um dos clusters aplicáveis.
Nosso objetivo neste artigo é demonstrar uma implementação mais eficiente do k-médias: k-means++. Este algoritmo se baseia em métodos de Lloyd, como os k-médias ingênuos por padrão, mas difere na abordagem inicial de escolha dos centroides aleatórios. Esta abordagem não é tão "aleatória" quanto os k-médias ingênuos, e, por isso, tende a convergir muito mais rápido e eficientemente.
Comparação de algoritmos
K-médias vs k-medianas
K-médias minimizam os quadrados das distâncias euclidianas entre os pontos do cluster e seu centroide, enquanto k-medianas minimizam a soma das distâncias absolutas dos pontos até a mediana dentro de um dado cluster (L1-Norm). Acredita-se que esta diferença faz com que as k-medianas sejam menos afetadas por valores atípicos e faça com que o cluster seja mais representativo de todos os pontos de dados, já que o centro do cluster é a mediana de todos os pontos, e não a média. A abordagem dos cálculos também é diferente, pois as k-medianas se baseiam em algoritmos fundamentados na L1-Norm, enquanto as k-médias utilizam k-means++ e o algoritmo de Lloyd. Assim, as k-médias são melhores para conjuntos de dados esféricos ou uniformemente distribuídos, enquanto as k-medianas podem ser mais adequadas para dados de formas irregulares. Finalmente, as k-medianas são mais claras quando se trata de interpretação, já que as medianas geralmente representam melhor o cluster do que os valores médios.
K-médias vs Quebras Naturais de Jenks
O algoritmo de Quebras Naturais de Jenks, como as k-médias, visa minimizar a distância de um ponto de dados ao centroide, com a diferença menos óbvia sendo que este algoritmo também busca afastar ao máximo esses grupos para uma melhor distinção. Isso é conseguido ao identificar "agrupamentos naturais" dos dados. Esses "agrupamentos naturais" são identificados dentro dos clusters em pontos onde a variância aumenta significativamente, e esses pontos são chamados de quebras, daí o nome do algoritmo. As quebras são enfatizadas graças à minimização da variância dentro de cada cluster. O algoritmo é mais adequado para conjuntos de dados classificatórios do que para tipos regressivos ou contínuos. Além disso, como o algoritmo das k-medianas, ele é mais sensível a valores atípicos, e mais fácil de interpretar em comparação com as k-médias típicas.
K-médias vs k-medoides
Como já mencionado, as k-medoides inicialmente se baseiam em dados reais, ao invés de pontos de centroide hipotéticos. Nesse aspecto, o algoritmo é muito semelhante à classificação hierárquica aglomerativa, mas sem criar dendrogramas. Os pontos de dados escolhidos como centroides são aqueles com a menor distância para todos os outros pontos de dados no cluster. Essa escolha também pode usar diferentes métodos de medição de distância, incluindo distância de Manhattan ou coeficiente de cosseno. Como os centroides são pontos de dados reais, pode-se argumentar que, assim como as Quebras Naturais de Jenks e as k-medianas, são mais representativos dos dados subjacentes do que as k-médias, mas são menos eficientes em termos computacionais, especialmente ao lidar com grandes conjuntos de dados.
K-médias vs Agrupamento Difuso
Como mencionado anteriormente, o agrupamento difuso fornece um peso regressivo para cada ponto de dados, que terá um formato vetorial dependendo do número de clusters envolvidos. Esse peso estará na faixa de 0,0–1,0 para cada cluster ao usar um protótipo difuso (função de pertencimento) ao invés de um centroide definido nas k-médias. Dessa forma, o algoritmo fornece mais informações e, portanto, melhor representa os dados. Ele supera as k-médias típicas em todos os pontos mencionados acima, com o principal inconveniente sendo a intensidade computacional.
K-means++
Para tornar a clusterização k-médias ingênua mais eficiente, geralmente é usada a inicialização k-means++, na qual os centroides iniciais são menos aleatórios, mas mais proporcionalmente distribuídos pelos dados. Os resultados dos testes mostraram uma tomada de decisão mais rápida e uma convergência para os centroides alvo. Melhora-se a qualidade geral do cluster e a sensibilidade não apenas a pontos de dados atípicos, mas também à escolha inicial dos pontos de centroide.
Dados
Assim como no artigo sobre clusterização hierárquica aglomerativa, usaremos as classes prontas de k-médias da AlgLib para desenvolver um algoritmo simples semelhante ao que usamos para aquele artigo, e veremos se conseguimos obter um resultado de validação cruzada. O símbolo testado será GBPUSD. Realizaremos testes de 01/01/2022 a 01/02/2023, e depois faremos a transição dessa data até 01/10/2023. Usaremos o intervalo diário e faremos as execuções finais em ticks reais durante o período de teste.
Estrutura
A estrutura dos dados usada para organizar os clusters é idêntica à do artigo sobre clusterização hierárquica aglomerativa, e as ideias de procedimentos e sinais realmente usados são praticamente as mesmas. A principal diferença é que quando usamos a clusterização aglomerativa, precisávamos executar uma função para extrair os clusters no nível que correspondia ao nosso número alvo de clusters, e por isso chamamos a função ClusterizerGetKClusters. Aqui, não faremos isso. Além disso, precisávamos garantir que a estrutura realmente recebesse informações sobre o preço, e, para isso, verificamos muitos números inválidos, como pode ser visto neste breve trecho de código abaixo:
double _dbl_min=-1000.0,_dbl_max=1000.0; for(int i=0;i<m_training_points;i++) { for(int ii=0;ii<m_point_features;ii++) { double _value=m_close.GetData(StartIndex()+i)-m_close.GetData(StartIndex()+ii+i+1); if(_dbl_min>=_value||!MathIsValidNumber(_value)||_value>=_dbl_max){ _value=0.0; } m_data.x.Set(i,ii,_value); matrix _m=m_data.x.ToMatrix();if(_m.HasNan()){ _m.ReplaceNan(0.0); }m_data.x=CMatrixDouble(_m); } if(i>0)//assign classifier only for data points for which eventual bar range is known { double _value=m_close.GetData(StartIndex()+i-1)-m_close.GetData(StartIndex()+i); if(_dbl_min>=_value||!MathIsValidNumber(_value)||_value>=_dbl_max){ _value=0.0; } m_data.y.Set(i-1,_value); vector _v=m_data.y.ToVector();if(_v.HasNan()){ _v.ReplaceNan(0.0); }m_data.y=CRowDouble(_v); } }
ALGLIB
A biblioteca AlgLib já foi mencionada várias vezes nesta série de artigos, então passaremos diretamente ao código para formar nossos clusters. Nosso foco estará em duas funções na biblioteca: SelectInitialCenters, que é fundamental para acelerar todo o processo, pois, como mencionado, uma escolha inicial de clusters muito aleatória tende a aumentar o tempo de convergência para os clusters desejados. Após executar esta função, usaremos o algoritmo de Lloyd para ajustar precisamente a escolha inicial dos clusters, e para isso, chamaremos a função KMeansGenerateInternal.
A escolha dos clusters iniciais com a função disponível pode ser feita de três maneiras: aleatoriamente, com k-means++ ou com inicialização gananciosa rápida. Vamos dar uma olhada rápida em cada uma. Quando escolhemos os clusters de maneria aleatória, assim como nos outros dois casos, os clusters resultantes são armazenados na matriz de saída chamada ct, aqui cada linha representa um cluster, e o número de linhas de ct correspondente ao número esperado de clusters, enquanto as colunas refletem as características ou a cardinalidade do vetor de cada ponto de dados no conjunto. A escolha aleatória simplesmente atribui cada linha de ct a um ponto de dados escolhido aleatoriamente do conjunto de entrada. Isso é mostrado abaixo:
//--- Random initialization if(initalgo==1) { for(i=0; i<k; i++) { j=CHighQualityRand::HQRndUniformI(rs,npoints); ct.Row(i,xy[j]+0); } return; }
Ao usar o K-means++, começamos com a escolha de um centro aleatório, mas apenas para o primeiro cluster. Antes, fazíamos isso para todos os clusters. Depois, medimos a distância entre cada ponto do conjunto e o centro do cluster escolhido, registrando a soma quadrada dessas distâncias para cada linha (ou cluster potencial). Se essa soma for zero, escolhemos um centroide aleatório. Para as somas não nulas na variável s, escolhemos o ponto mais distante do nosso cluster inicial. O código pode ser complexo, mas com comentários fica mais fácil de entender:
//--- k-means++ initialization if(initalgo==2) { //--- Prepare distances array. //--- Select initial center at random. initbuf.m_ra0=vector<double>::Full(npoints,CMath::m_maxrealnumber); ptidx=CHighQualityRand::HQRndUniformI(rs,npoints); ct.Row(0,xy[ptidx]+0); //--- For each newly added center repeat: //--- * reevaluate distances from points to best centers //--- * sample points with probability dependent on distance //--- * add new center for(cidx=0; cidx<k-1; cidx++) { //--- Reevaluate distances s=0.0; for(i=0; i<npoints; i++) { v=0.0; for(j=0; j<=nvars-1; j++) { vv=xy.Get(i,j)-ct.Get(cidx,j); v+=vv*vv; } if(v<initbuf.m_ra0[i]) initbuf.m_ra0.Set(i,v); s+=initbuf.m_ra0[i]; } // //--- If all distances are zero, it means that we can not find enough //--- distinct points. In this case we just select non-distinct center //--- at random and continue iterations. This issue will be handled //--- later in the FixCenters() function. // if(s==0.0) { ptidx=CHighQualityRand::HQRndUniformI(rs,npoints); ct.Row(cidx+1,xy[ptidx]+0); continue; } //--- Select point as center using its distance. //--- We also handle situation when because of rounding errors //--- no point was selected - in this case, last non-zero one //--- will be used. v=CHighQualityRand::HQRndUniformR(rs); vv=0.0; lastnz=-1; ptidx=-1; for(i=0; i<npoints; i++) { if(initbuf.m_ra0[i]==0.0) continue; lastnz=i; vv+=initbuf.m_ra0[i]; if(v<=vv/s) { ptidx=i; break; } } if(!CAp::Assert(lastnz>=0,__FUNCTION__": integrity error")) return; if(ptidx<0) ptidx=lastnz; ct.Row(cidx+1,xy[ptidx]+0); } return; }
A AlgLib tem alguma documentação de referência pública.
Finalmente, para a inicialização rápida gananciosa, inspirada no k-means++ realizamos várias rodadas. Em cada uma, calculamos a distância mais próxima ao centroide selecionado no momento. Em seguida, fazemos uma amostragem de aproximadamente metade do tamanho esperado do cluster, onde a probabilidade de escolher um ponto é proporcional à sua distância do centroide atual. Repetimos isso até que o número de pontos seja o dobro do necessário para preencher o cluster. Então, fazemos uma "escolha gananciosa" até atingir o tamanho de amostra menor, priorizando os pontos mais distantes dos centroides. O processo é muito intensivo e confuso. O código com comentários está abaixo:
//--- "Fast-greedy" algorithm based on "Scalable k-means++". //--- We perform several rounds, within each round we sample about 0.5*K points //--- (not exactly 0.5*K) until we have 2*K points sampled. Before each round //--- we calculate distances from dataset points to closest points sampled so far. //--- We sample dataset points independently using distance xtimes 0.5*K divided by total //--- as probability (similar to k-means++, but each point is sampled independently; //--- after each round we have roughtly 0.5*K points added to sample). //--- After sampling is done, we run "greedy" version of k-means++ on this subsample //--- which selects most distant point on every round. if(initalgo==3) { //--- Prepare arrays. //--- Select initial center at random, add it to "new" part of sample, //--- which is stored at the beginning of the array samplesize=2*k; samplescale=0.5*k; CApServ::RMatrixSetLengthAtLeast(initbuf.m_rm0,samplesize,nvars); ptidx=CHighQualityRand::HQRndUniformI(rs,npoints); initbuf.m_rm0.Row(0,xy[ptidx]+0); samplescntnew=1; samplescntall=1; initbuf.m_ra1=vector<double>::Zeros(npoints); CApServ::IVectorSetLengthAtLeast(initbuf.m_ia1,npoints); initbuf.m_ra0=vector<double>::Full(npoints,CMath::m_maxrealnumber); //--- Repeat until samples count is 2*K while(samplescntall<samplesize) { //--- Evaluate distances from points to NEW centers, store to RA1. //--- Reset counter of "new" centers. KMeansUpdateDistances(xy,0,npoints,nvars,initbuf.m_rm0,samplescntall-samplescntnew,samplescntall,initbuf.m_ia1,initbuf.m_ra1); samplescntnew=0; //--- Merge new distances with old ones. //--- Calculate sum of distances, if sum is exactly zero - fill sample //--- by randomly selected points and terminate. s=0.0; for(i=0; i<npoints; i++) { initbuf.m_ra0.Set(i,MathMin(initbuf.m_ra0[i],initbuf.m_ra1[i])); s+=initbuf.m_ra0[i]; } if(s==0.0) { while(samplescntall<samplesize) { ptidx=CHighQualityRand::HQRndUniformI(rs,npoints); initbuf.m_rm0.Row(samplescntall,xy[ptidx]+0); samplescntall++; samplescntnew++; } break; } //--- Sample points independently. for(i=0; i<npoints; i++) { if(samplescntall==samplesize) break; if(initbuf.m_ra0[i]==0.0) continue; if(CHighQualityRand::HQRndUniformR(rs)<=(samplescale*initbuf.m_ra0[i]/s)) { initbuf.m_rm0.Row(samplescntall,xy[i]+0); samplescntall++; samplescntnew++; } } } //--- Run greedy version of k-means on sampled points initbuf.m_ra0=vector<double>::Full(samplescntall,CMath::m_maxrealnumber); ptidx=CHighQualityRand::HQRndUniformI(rs,samplescntall); ct.Row(0,initbuf.m_rm0[ptidx]+0); for(cidx=0; cidx<k-1; cidx++) { //--- Reevaluate distances for(i=0; i<samplescntall; i++) { v=0.0; for(j=0; j<nvars; j++) { vv=initbuf.m_rm0.Get(i,j)-ct.Get(cidx,j); v+=vv*vv; } if(v<initbuf.m_ra0[i]) initbuf.m_ra0.Set(i,v); } //--- Select point as center in greedy manner - most distant //--- point is selected. ptidx=0; for(i=0; i<samplescntall; i++) { if(initbuf.m_ra0[i]>initbuf.m_ra0[ptidx]) ptidx=i; } ct.Row(cidx+1,initbuf.m_rm0[ptidx]+0); } return; }
Esse processo garante centroides representativos e eficiência na próxima etapa.
Após escolher os centroides iniciais, passamos para o algoritmo de Lloyd, a função principal em KMeansGenerateInternal. A implementação da AlgLib pode parecer complexa, mas os fundamentos do algoritmo de Lloyd envolvem encontrar iterativamente o centroide de cada cluster e, em seguida, redefinir cada cluster movendo pontos de dados para minimizar a distância dentro de cada cluster entre seu centroide e os pontos que o compõem.
Neste artigo, assim como no sobre dendrogramas, os pontos do conjunto de dados são as mudanças no preço de fechamento do símbolo, que no nosso teste foi o GBPUSD.
Previsão
K-médias, assim como a clusterização hierárquica aglomerativa, são técnicas essencialmente não supervisionadas. Então, como antes, se quisermos fazer alguma regressão ou previsão, precisamos adicionar dados na coluna "y", que estão atrasados em relação ao nosso conjunto de dados clusterizado. Esses dados "y" também serão as mudanças no preço de fechamento, mas com 1 barra à frente dos dados clusterizados. Isso ajuda a rotular os clusters de forma eficaz e, para eficiência, o conjunto de dados "y" é preenchido pelo mesmo laço for que preenche a matriz de dados x para clusterização. Veja o exemplo no código abaixo:
if(i>0)//assign classifier only for data points for which eventual bar range is known { double _value=m_close.GetData(StartIndex()+i-1)-m_close.GetData(StartIndex()+i); if(_dbl_min>=_value||!MathIsValidNumber(_value)||_value>=_dbl_max){ _value=0.0; } m_data.y.Set(i-1,_value); vector _v=m_data.y.ToVector();if(_v.HasNan()){ _v.ReplaceNan(0.0); }m_data.y=CRowDouble(_v); }
Depois que a matriz "x" e o array "y" são preenchidos com dados, a determinação do cluster segue os passos já mencionados acima. Em seguida, determinamos o cluster das alterações atuais no preço de fechamento ou a linha superior da matriz "x". Como ela é processada para clusterização junto com outros pontos de dados, terá um índice de cluster. Usando este índice, comparamos com os pontos de dados já "rotulados", para os quais conhecemos a possível mudança no preço de fechamento. Isso nos permite obter a soma dessas possíveis mudanças. Com essa soma, podemos calcular facilmente a média da mudança, que, quando normalizamos o intervalo atual (ou volatilidade), nos dá um peso na faixa de 0 a 1.
//+------------------------------------------------------------------+ //| "Voting" that price will fall. | //+------------------------------------------------------------------+ int CSignalKMEANS::ShortCondition(void) { ... double _output=GetOutput(); int _range_size=1; double _range=m_high.GetData(m_high.MaxIndex(StartIndex(),StartIndex()+_range_size))-m_low.GetData(m_low.MinIndex(StartIndex(),StartIndex()+_range_size)); _output/=fmax(_range,m_symbol.Point()); _output*=100.0; if(_output<0.0){ result=int(fmax(-100.0,round(_output)))*-1; } ... }
As funções LongCondition e ShortCondition retornam valores na faixa de 0 a 100, então nosso valor normalizado precisa ser multiplicado por 100.
Avaliação e resultados
Pelos resultados dos testes históricos no período de 01/01/2022 a 01/02/2023, obtivemos o seguinte relatório:
O relatório foi baseado nos seguintes dados de entrada, obtidos durante a otimização:
No forward-test com essas configurações de 02/02/2023 a 01/10/2023, obtivemos o seguinte relatório:
Parece promissor para um período de teste tão curto, mas, como sempre, recomenda-se mais diligência e testes por períodos mais longos.
Implementação com ondas fractais
Agora vamos dar uma olhada em uma variação que usa dados do indicador de fractais em vez de mudanças no preço de fechamento. O indicador fractal é um pouco complicado de usar direto da forma como é, especialmente quando você está tentando implementá-lo em um EA. Isso porque, quando atualizado, os buffers não têm valores de indicadores ou preços para cada índice. Então, é necessário verificar cada índice do buffer para ver se há um "fractal" (um preço) de verdade, e se não houver, o valor padrão preenchido é o máximo double. Veja como preparamos os dados fractais na função GetOutput atualizada:
//+------------------------------------------------------------------+ //| Get k-means cluster output from identified cluster. | //+------------------------------------------------------------------+ double CSignalKMEANS::GetOutput() { ... int _size=m_training_points+m_point_features+1,_index=0; for(int i=0;i<m_fractals.Available();i++) { double _0=m_fractals.GetData(0,i); double _1=m_fractals.GetData(1,i); if(_0!=DBL_MAX||_1!=DBL_MAX) { double _v=0.0; if(_0!=DBL_MAX){_v=_0;} if(_1!=DBL_MAX){_v=_1;} if(!m_loaded){ m_wave[_index]=_v; _index++; } else { for(int i=_size-1;i>0;i--){ m_wave[i]=m_wave[i-1]; } m_wave[0]=_v; break; } } if(_index>=int(m_wave.Size())){ break; } } if(!m_loaded){ m_loaded=true; } if(m_wave[_size-1]==0.0){ return(0.0); } ... ... }
Para obter os fractais de preços reais, primeiro precisamos atualizar corretamente o objeto do indicador fractal. Feito isso, precisamos obter o número total de índices de buffer disponíveis, que nos diz quantos índices precisamos percorrer no nosso laço for ao procurar pontos de preço fractal. Lembre-se de que o indicador fractal tem dois buffers: o índice 0 para fractais altos e o índice 1 para fractais baixos. Isso significa que nosso laço for estará verificando esses dois buffers simultaneamente para pontos de preço fractal. Sempre que um deles registrar um preço (apenas um pode registrar um preço por vez), adicionamos esse valor ao nosso vetor m_wave.
Normalmente, o número de índices fractais disponíveis, que serve como nosso limite para procurar preços fractais, é limitado. Assim, mesmo se quisermos um buffer de onda de 12 índices, podemos acabar obtendo apenas 3 na primeira execução ou na primeira barra de preço. Isso significa que nosso buffer de onda precisa funcionar como um buffer real, armazenando todos os índices de preço que pode obter e esperando até que um novo preço fractal esteja disponível para ser adicionado ao buffer. Esse processo continua até que o buffer esteja cheio. Como o buffer ainda não está cheio nem inicializado, o EA não pode processar nenhum sinal e está essencialmente na fase de inicialização.
Bem, o tamanho do buffer usado para extrair fractais é muito importante. Como esses fractais são inseridos no algoritmo de k-médias em nosso sistema, que usa mudanças nos preços fractais, isso significa que o tamanho desse buffer representa a soma do número de pontos de treinamento, do número de características, mais um. Adicionamos um no final porque, embora nossa matriz de dados de entrada precise apenas dos pontos de treinamento e características, a linha adicional é para os pontos atuais que ainda não foram submetidos à regressão, ou seja, pontos para os quais não temos o valor "y".
Essa precaução é necessária, mas, uma vez superada essa limitação, obteremos informações sobre os preços que são classificadas de forma ondulatória. As mudanças entre os picos de cada onda, os pontos de preço fractal, podem substituir as mudanças no preço de fechamento que usamos em nossa primeira implementação.
Ironicamente, ao testar este novo EA, não pudemos evitar usar saídas pelo preço da posição (TP e SL), como fazíamos com mudanças no preço de fechamento. Em vez disso, tivemos que testar com TP. Após os testes, apesar dos resultados promissores, não conseguimos obter um teste de avanço lucrativo com os melhores resultados de otimização, como tínhamos com mudanças no preço de fechamento. Os relatórios estão apresentados abaixo.
Se olharmos para o gráfico contínuo dessas negociações, veremos claramente que o teste de avanço não é a melhor solução, apesar do promissor primeiro teste.
Basicamente, isso significa que a ideia precisa ser revisada. Um ponto de partida pode ser a revisão do indicador fractal e, possivelmente, criar uma versão própria que, primeiro, seja mais eficiente, pois contém apenas pontos de preço fractal, e, segundo, pode ser ajustada com algumas entradas que orientem ou quantifiquem o movimento mínimo de preço entre cada ponto fractal.
Considerações finais
Examinamos o k-médias e como a implementação pronta fornecida pela AlgLib pode ser realizada em duas variações: com preços de fechamento brutos e com dados de preços fractais.
A validação cruzada na fase preliminar mostrou diferentes resultados: o sistema de preços de fechamento bruto mostrou-se mais promissor do que a aplicação de preços fractais. Compartilhamos algumas razões para esses resultados. O código-fonte está anexado abaixo.
Referências
Anexo
Antes de usar os arquivos anexados, recomendo ler o artigo sobre o assistente MQL5.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13915
![Indicador de posições históricas no gráfico em forma de diagrama de lucro/prejuízo](https://c.mql5.com/2/62/midjourney_image_13911_52_463__3-logo.png)
![Redes neurais de maneira fácil (Parte 68): Otimização off-line de políticas baseada em preferências](https://c.mql5.com/2/62/midjourney_image_13912_49_444__1-logo.png)
![Ciência de dados e aprendizado de máquina (Parte 17): O dinheiro cresce em árvores? Florestas aleatórias no trading de forex](https://c.mql5.com/2/63/midjourney_image_13765_54_491__3-logo.png)
![Desenvolvendo um sistema de Replay (Parte 51): Complicando as coisas (III)](https://c.mql5.com/2/79/Desenvolvendo_um_sistema_de_Replay_Parte_51____LOGO.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso