English Русский Español Deutsch 日本語
preview
Validação cruzada combinatoriamente simétrica no MQL5

Validação cruzada combinatoriamente simétrica no MQL5

MetaTrader 5Sistemas de negociação | 26 abril 2024, 09:58
440 0
Francis Dube
Francis Dube

Introdução

Às vezes, ao criar uma estratégia automatizada, começamos descrevendo regras baseadas em indicadores arbitrários que precisam ser de alguma forma refinados. Este processo envolve vários testes com diferentes parâmetros para os indicadores selecionados. Dessa forma, podemos encontrar quer sejam valores de indicador que maximizam o lucro ou um valor de interesse. O problema aqui é que introduzimos uma certa dose de viés otimista devido ao ruído predominante nas séries temporais financeiras. Este fenômeno é chamado de sobreajuste, ou overfitting.

Embora o overfitting não possa ser completamente evitado, seu grau de manifestação pode variar de uma estratégia para outra. Por esse motivo, seria útil poder determinar seu grau. A verificação cruzada combinatória simétrica (combinatorially symmetric cross-validation, CSCV) é um método apresentado no artigo científico "The Probability of Backtest Overfitting" de David H. Bailey et al. Esta verificação pode ser usada para avaliar o grau de ajuste durante a otimização dos parâmetros de uma estratégia.

Neste artigo, demonstraremos a implementação do CSCV no MQL5 e, através de um exemplo, mostraremos como ele pode ser aplicado a um EA.


Método CSCV

Nesta seção, descreveremos passo a passo o CSCV, começando pelos aspectos preliminares relacionados aos dados que precisam ser coletados de acordo com os critérios de desempenho selecionados.

O método CSCV pode ser aplicado em várias áreas além do desenvolvimento e análise de estratégias, mas neste artigo, nos concentramos no contexto da otimização de estratégias. Ou seja, temos uma estratégia definida por um conjunto de parâmetros que precisam ser ajustados com precisão por meio de numerosos testes com diferentes configurações de parâmetros.

Antes de começarmos qualquer cálculo, primeiro precisamos decidir quais critérios de desempenho usaremos para avaliar a estratégia. O método CSCV é flexível, pois permite o uso de qualquer medição de desempenho, desde lucro simples até medidas baseadas em coeficientes.

Os critérios de desempenho selecionados também definirão os dados brutos que serão usados nos cálculos. Estes são dados detalhados não processados que serão coletados durante todas as execuções de teste. Por exemplo, se decidirmos usar o índice de Sharpe como uma medida de desempenho, precisaremos obter valores de retorno por barra a cada teste executado. Se usássemos lucro simples, precisaríamos de lucro ou perda por barras. É necessário garantir que o volume de dados coletados para cada execução seja consistente. Assim, garantimos a existência de uma medida para cada ponto de dados correspondente em todos os testes executados.

  1. O primeiro passo começa com a coleta de dados durante a otimização, quando diferentes opções de parâmetros são testadas. 
  2. Após a otimização, combinamos todos os dados coletados durante os testes em uma matriz. Cada linha desta matriz conterá todos os valores de desempenho por barra que serão usados para medir o desempenho do trading para o teste correspondente.
  3. A matriz terá tantas linhas quanto as combinações de parâmetros testadas, e o número de colunas será igual ao número de colunas que compõem todo o período de teste. Essas colunas são então divididas em um número arbitrário de conjuntos pares. Digamos que sejam N conjuntos.
  4. Esses conjuntos são submatrizes que serão usadas para formar combinações de grupos de tamanho N/2. É criado um total de N combinações, tomado N/2 de cada vez, ou seja, N C n/2 . De cada uma dessas combinações, criamos um conjunto na amostra (In-Sample-Set, ISS), combinando submatrizes N/2, bem como um conjunto correspondente fora da amostra (Out-Of-Sample-Set, OOSS) das submatrizes restantes não incluídas no ISS.
  5. Para cada linha das matrizes ISS e OOSS, calculamos a métrica de desempenho correspondente, e observamos a linha na matriz ISS com os melhores valores. Esta é a configuração ótima de parâmetros. A linha correspondente na matriz OOSS é usada para calcular o posto relativo, contando o número de testes de parâmetros fora da amostra com desempenho inferior em comparação com aquele alcançado usando a configuração de parâmetros ótima, e apresentando esse número como uma fração de todos os conjuntos de parâmetros testados.
  6. Ao passar por todas as combinações, acumulamos o número de valores de posto relativo que são menores ou iguais a 0,5. Este é o número de configurações de parâmetros fora da amostra cujo desempenho é inferior ao observado ao usar o conjunto de parâmetros ótimo. Após processar todas as combinações, esse número é apresentado como uma fração de todas as combinações + 1, representando a probabilidade de superajuste do teste retrospectivo (Probability of Backtest Overfitting, PBO).

 Abaixo está a representação dos passos descritos com N = 4.

Representação da matriz de dados

Submatrizes

Conjuntos dentro da amostra e fora da amostra

Combinações

Na próxima seção, consideraremos como podemos implementar os passos descritos no código. Primeiramente, consideraremos o método principal CSCV, e o código relacionado à coleta de dados será deixado para um exemplo que será demonstrado no final do artigo.


Implementação do CSCV no MQL5

A classe Ccsvc, contida em CSCV.mqh, encapsula o algoritmo CSCV. CSCV.mqh começa com a inclusão de subfunções da biblioteca padrão MQL5 Mathematics.

//+------------------------------------------------------------------+
//|                                                         CSCV.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include <Math\Stat\Math.mqh>

O ponteiro da função Criterion define o tipo de função para calcular o desempenho usando um array como parâmetro de entrada.

#include <Math\Stat\Math.mqh>
typedef double (*Criterion)(const double &data[]); // function pointer for performance criterion

Na Ccscv, existe apenas um método que os usuários precisam conhecer. Ele pode ser chamado após a inicialização de uma instância da classe. Este método CalculateProbabilty() retorna o valor do PBO em caso de sucesso. Se um erro for encontrado, o método retorna -1. Abaixo está a descrição de seus parâmetros de entrada:

//+------------------------------------------------------------------+
//| combinatorially symmetric cross validation class                 |
//+------------------------------------------------------------------+
class Cscv
  {
   ulong             m_perfmeasures;         //granular performance measures
   ulong             m_trials;               //number of parameter trials
   ulong             m_combinations;         //number of combinations

   ulong  m_indices[],           //array tracks combinations
          m_lengths[],           //points to number measures for each combination
          m_flags  [];           //tracks processing of combinations
   double m_data   [],           //intermediary holding performance measures for current trial
          is_perf  [],           //in sample performance data
          oos_perf [];           //out of sample performance data


public:
                     Cscv(void);                   //constructor
                    ~Cscv(void);                  //destructor

   double            CalculateProbability(const ulong blocks, const matrix &in_data,const Criterion criterion, const bool maximize_criterion);
  };
  • blocks é primeiro parâmetro de entrada. Ele corresponde ao número de conjuntos (N conjuntos) em que as colunas da matriz serão divididas.
  • in_data é a matriz com o número de linhas igual ao número total de variantes de parâmetros testadas durante a otimização, e o número de colunas igual ao número de colunas que compõem todo o histórico selecionado para otimização.
  • criterion é um ponteiro para a função que será usada para calcular a medida do desempenho selecionada. A função deve retornar um valor do tipo double e aceitar um array do tipo double como dados de entrada.
  •  maximize_criterion está relacionado a criterion, pois permite especificar se a melhor das medidas de desempenho selecionadas é determinada pelo valor máximo ou mínimo. Por exemplo, se usar rebaixamento como critério de desempenho, é melhor usar o menor valor, logo maximize_criterion deve ser false.
double Cscv::CalculateProbability(const ulong blocks, const matrix &in_data,const Criterion criterion, const bool maximize_criterion)
  {
//---get characteristics of matrix
   m_perfmeasures = in_data.Cols();
   m_trials = in_data.Rows();
   m_combinations=blocks/2*2;
//---check inputs
   if(m_combinations<4)
      m_combinations = 4;
//---memory allocation
   if(ArrayResize(m_indices,int(m_combinations))< int(m_combinations)||
      ArrayResize(m_lengths,int(m_combinations))< int(m_combinations)||
      ArrayResize(m_flags,int(m_combinations))<int(m_combinations)   ||
      ArrayResize(m_data,int(m_perfmeasures))<int(m_perfmeasures)    ||
      ArrayResize(is_perf,int(m_trials))<int(m_trials)               ||
      ArrayResize(oos_perf,int(m_trials))<int(m_trials))
     {
      Print("Memory allocation error ", GetLastError());
      return -1.0;
     }
//---

Em ComputeProbability, começamos obtendo o número de colunas e linhas da matriz in_data, além de verificar blocks para garantir que seja um número par. Obter as dimensões da matriz de entrada é necessário para determinar o tamanho dos buffers internos da instância.

   int is_best_index ;               //row index of oos_best parameter combination
   double oos_best, rel_rank ;   //oos_best performance and relative rank values
//---
   ulong istart = 0 ;
   for(ulong i=0 ; i<m_combinations ; i++)
     {
      m_indices[i] = istart ;        // Block starts here
      m_lengths[i] = (m_perfmeasures - istart) / (m_combinations-i) ; // It contains this many cases
      istart += m_lengths[i] ;       // Next block
     }
//---
   ulong num_less =0;                    // Will count the number of time OOS of oos_best <= median OOS, for prob
   for(ulong i=0; i<m_combinations; i++)
     {
      if(i<m_combinations/2)        // Identify the IS set
         m_flags[i]=1;
      else
         m_flags[i]=0;               // corresponding OOS set
     }
//---

Assim que a memória é alocada com sucesso para os buffers internos, começamos a nos preparar para dividir as colunas de acordo com m_combinations. O array m_indices é preenchido com os índices das colunas iniciais para uma seção específica, e m_lengths conterá o número correspondente de colunas contidas em cada uma delas. num_less mantém a contagem de quantas vezes o desempenho do melhor teste na amostra é menor do que o desempenho dos outros testes fora da amostra. m_flags é um array de inteiros, cujos valores podem conter 1 ou 0. Isso ajuda a identificar subconjuntos marcados como incluídos e não incluídos na amostra, ao iterar todas as combinações possíveis.

ulong ncombo;
   for(ncombo=0; ; ncombo++)
     {
      //--- in sample performance calculated in this loop
      for(ulong isys=0; isys<m_trials; isys++)
        {
         int n=0;
         for(ulong ic=0; ic<m_combinations; ic++)
           {
            if(m_flags[ic])
              {
               for(ulong i=m_indices[ic]; i<m_indices[ic]+m_lengths[ic]; i++)
                  m_data[n++] = in_data.Flat(isys*m_perfmeasures+i);
              }
           }
         is_perf[isys]=criterion(m_data);
        }
      //--- out of sample performance calculated here
      for(ulong isys=0; isys<m_trials; isys++)
        {
         int n=0;
         for(ulong ic=0; ic<m_combinations; ic++)
           {
            if(!m_flags[ic])
              {
               for(ulong i=m_indices[ic]; i<m_indices[ic]+m_lengths[ic]; i++)
                  m_data[n++] = in_data.Flat(isys*m_perfmeasures+i);
              }
           }
         oos_perf[isys]=criterion(m_data);
        }

Neste ponto, começa o loop principal, que itera todas as combinações de conjuntos na amostra e fora da amostra. Dois loops internos são usados para calcular o desempenho simulado na amostra e fora da amostra, chamando a função criterion e salvando esse valor nos arrays is_perf e oos_perf, respectivamente.

//--- get the oos_best performing in sample index
      is_best_index = maximize_criterion?ArrayMaximum(is_perf):ArrayMinimum(is_perf);
      //--- corresponding oos performance
      oos_best = oos_perf[is_best_index];

O índice do melhor valor de desempenho no array is_perf é calculado de acordo com maximize_criterion. O valor correspondente de desempenho fora da amostra é salvo na variável oos_best.

//--- count oos results less than oos_best
      int count=0;
      for(ulong isys=0; isys<m_trials; isys++)
        {
         if(isys == ulong(is_best_index) || (maximize_criterion && oos_best>=oos_perf[isys]) || (!maximize_criterion && oos_best<=oos_perf[isys]))
            ++count;
        }

Vamos percorrer o array oos_perf e contar quantas vezes o valor de oos_best foi igual ou melhor.

//--- calculate the relative rank
      rel_rank = double (count)/double (m_trials+1);
      //--- cumulate num_less
      if(rel_rank<=0.5)
         ++num_less;

A contagem é usada para calcular o posto relativo. Finalmente, num_less é somado, se o posto relativo calculado for menor que 0,5.

//---move calculation on to new combination updating flags array along the way
      int n=0;
      ulong iradix;
      for(iradix=0; iradix<m_combinations-1; iradix++)
        {
         if(m_flags[iradix]==1)
           {
            ++n;
            if(m_flags[iradix+1]==0)
              {
               m_flags[iradix]=0;
               m_flags[iradix+1]=0;
               for(ulong i=0; i<iradix; i++)
                 {
                  if(--n>0)
                     m_flags[i]=1;
                  else
                     m_flags[i]=0;
                 }
               break;
              }
           }
        }

O último loop interno é usado para passar para os próximos conjuntos de dados na amostra e fora da amostra.

if(iradix == m_combinations-1)
        {
         ++ncombo;
         break;
        }
     }
//--- final result
   return double(num_less)/double(ncombo);
  }


O último bloco if determina quando sair do loop externo principal antes de retornar o valor final do PBO, dividindo num_less por ncombo.

Antes de considerarmos um exemplo de aplicação da classe Ccscv, precisamos entender o que este algoritmo diz sobre uma estratégia específica.


Interpretação dos resultados

O algoritmo CSCV que implementamos produz uma métrica, o PBO. David Bailey e coautores observam que o PBO define a probabilidade de que um conjunto de parâmetros que proporcionou o melhor desempenho durante a otimização no conjunto de dados na amostra atinja um desempenho abaixo da mediana dos resultados de desempenho usando conjuntos de parâmetros não ótimos no conjunto de dados fora da amostra.

Quanto maior esse valor, maior é o grau de ajuste. Em outras palavras, há uma maior probabilidade de que a estratégia seja ineficaz se aplicada fora da amostra. Um PBO ideal deve ser inferior a 0,1.

O valor alcançado do PBO dependerá principalmente da diversidade dos conjuntos de parâmetros testados durante a otimização. É importante garantir que os conjuntos de parâmetros selecionados sejam representativos dos que podem ser aplicados em condições reais. A inclusão intencional de combinações de parâmetros que são improváveis de serem escolhidas ou que dominem combinações próximas ou distantes do ótimo apenas distorcerá o resultado final.


Exemplo

Nesta seção, mostraremos a aplicação da classe Ccscv para um EA. Vamos modificar o EA padrão Moving Average, incluindo nele o cálculo do PBO. Para uma implementação eficaz do método CSCV, usaremos frames para coletar dados por barras. Após a otimização, os dados de cada execução serão coletados em uma matriz. Isso significa que pelo menos os manipuladores de eventos OnTester e OnTesterDeinit() precisam ser adicionados ao código do EA. Finalmente, o EA selecionado deve ser submetido a uma otimização completa usando a opção de algoritmo completo lento no testador de estratégias.  

//+------------------------------------------------------------------+
//|                                    MovingAverage_CSCV_DemoEA.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Returns.mqh>
#include <CSCV.mqh>
#include <Trade\Trade.mqh>

Começaremos incluindo os arquivos CSCV.mqh e Returns.mqh, que contêm a definição da classe CReturns. CReturns será útil para coletar retornos por barras, com os quais podemos calcular o índice de Sharpe, o retorno médio ou o retorno total. Podemos usar qualquer um desses parâmetros como critério para determinar o desempenho ótimo. Como mencionado no início do artigo, a métrica de desempenho escolhida não importa; qualquer uma pode ser usada.

sinput uint  NumBlocks          = 4;


Foi adicionado um novo parâmetro não otimizável chamado NumBlocks, que determina o número de seções que serão usadas pelo algoritmo CSCV. Mais tarde veremos como alterar esse parâmetro afetará o PBO.  

CReturns colrets;
ulong numrows,numcolumns;

Uma instância de CReturns é declarada globalmente. Aqui também são declaradas numrows e numcolumns, que usaremos para inicializar a matriz.

//+------------------------------------------------------------------+
//| TesterInit function                                              |
//+------------------------------------------------------------------+
void OnTesterInit()
  {
   numrows=1;
//---
   string name="MaximumRisk";
   bool enable;
   double par1,par1_start,par1_step,par1_stop;
   ParameterGetRange(name,enable,par1,par1_start,par1_step,par1_stop);
   if(enable)
      numrows*=ulong((par1_stop-par1_start)/par1_step)+1;

//---
   name="DecreaseFactor";
   double par2,par2_start,par2_step,par2_stop;
   ParameterGetRange(name,enable,par2,par2_start,par2_step,par2_stop);
   if(enable)
      numrows*=ulong((par2_stop-par2_start)/par2_step)+1;

//---
   name="MovingPeriod";
   long par3,par3_start,par3_step,par3_stop;
   ParameterGetRange(name,enable,par3,par3_start,par3_step,par3_stop);
   if(enable)
      numrows*=ulong((par3_stop-par3_start)/par3_step)+1;

//---
   name="MovingShift";
   long par4,par4_start,par4_step,par4_stop;
   ParameterGetRange(name,enable,par4,par4_start,par4_step,par4_stop);
   if(enable)
      numrows*=ulong((par4_stop-par4_start)/par4_step)+1;
  }

Adicionaremos o manipulador OnTesterInit(), onde contamos o número de conjuntos de parâmetros testados.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   colrets.OnNewTick();
//---
   if(SelectPosition())
      CheckForClose();
   else
      CheckForOpen();
//---
  }

No manipulador de eventos OnTick(), chamamos o método OnNewtick() de CReturns.

//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---
   double ret=0.0;
   double array[];
//---
   if(colrets.GetReturns(ENUM_RETURNS_ALL_BARS,array))
     {
      //---
      ret = MathSum(array);
      if(!FrameAdd(IntegerToString(MA_MAGIC),long(MA_MAGIC),double(array.Size()),array))
        {
         Print("Could not add frame ", GetLastError());
         return 0;
        }
      //---
     }
//---return
   return(ret);
  }

Dentro de OnTester(), coletamos um array de retornos usando nossa instância globalmente declarada de CReturns. E, finalmente, adicionamos esses dados ao frame usando a chamada FrameAdd().

//+------------------------------------------------------------------+
//| TesterDeinit function                                            |
//+------------------------------------------------------------------+
void OnTesterDeinit()
  {
//---prob value
   numcolumns = 0;
   double probability=-1;
   int count_frames=0;
   matrix data_matrix=matrix::Zeros(numrows,1);
   vector addvector=vector::Zeros(1);
   Cscv cscv;
//---calculate
   if(FrameFilter(IntegerToString(MA_MAGIC),long(MA_MAGIC)))
     {
      //---
      ulong pass;
      string frame_name;
      long frame_id;
      double passed_value;
      double passed_data[];
      //---
      while(FrameNext(pass,frame_name,frame_id,passed_value,passed_data))
        {
         //---
         if(!numcolumns)
           {
            numcolumns=ulong(passed_value);
            addvector.Resize(numcolumns);
            data_matrix.Resize(numrows,numcolumns);
           }
         //---
         if(addvector.Assign(passed_data))
           {
            data_matrix.Row(addvector,pass);
            count_frames++;
           }
         //---
        }
     }
   else
      Print("Error retrieving frames ", GetLastError());
//---results
   probability = cscv.CalculateProbability(NumBlocks,data_matrix,MathSum,true);
//---output results
   Print("cols ",data_matrix.Cols()," rows ",data_matrix.Rows());
   Print("Number of passes processed: ", count_frames, " Probability: ",probability);
//---
  }

É em OnTesterDeinit() que encontramos a maior parte das adições feitas ao EA. Aqui declaramos uma instância de Ccscv junto com variáveis do tipo matriz e vetor. Iteramos por todos os frames e passamos seus dados para a matriz. O vetor é usado como um intermediário para adicionar uma nova linha de dados para cada frame.

O método CalculateProbability() do Ccscv é chamado antes de exibir os resultados na aba "Experts" do terminal. Neste exemplo, passamos a função MathSum() para o método, o que significa que o retorno total é usado para determinar o conjunto ótimo de parâmetros. A saída também fornece uma visão geral do número de frames processados para confirmar que todos os dados foram capturados.

Aqui estão alguns resultados do nosso EA modificado com diferentes configurações em várias escalas de tempo. O resultado do PBO é exibido na aba "Experts" do terminal.

MovingAverage_CSCV_DemoEA (EURUSD,H1)   Number of passes processed: 23520 Probability: 0.3333333333333333
NumBlocks
Timeframe
Probabilidade de Overfitting no Backtest (PBO)
4
W1
0,3333
4
D1
0,6666
4
H12
0,6666
8
W1
0,2
8
D1
0,8
8
H12
0,6
16
W1
0,4444
16
D1
0,8888
16
H12
0,6666

O melhor resultado que obtivemos foi 0,2. Os outros foram significativamente piores. Isso indica uma alta probabilidade de que o EA exibirá um fraco desempenho quando aplicado a qualquer conjunto de dados fora da amostra. Também vemos que os maus resultados do PBO persistem em diferentes escalas de tempo. Alterar o número de seções usadas na análise não melhorou a avaliação inicialmente pobre.

Configuração do Testador de Estratégias de Negociação


Parâmetros de Entrada Selecionados


Considerações finais

Demonstramos como implementar o método de verificação cruzada combinatoriamente simétrica para avaliar o ajuste após o procedimento de otimização. Comparado ao uso de permutações de Monte Carlo para avaliação quantitativa do ajuste, o CSCV é relativamente mais rápido. O método também permite o uso eficiente dos dados históricos disponíveis. No entanto, existem potenciais armadilhas das quais se deve estar ciente. O fato de confiarmos ou não neste método depende exclusivamente dos dados utilizados.

Em particular, foi testado o grau de variação dos parâmetros. Usar menos variações de parâmetros pode levar a uma subestimação do ajuste. Por outro lado, incluir um grande número de combinações de parâmetros irrealistas pode levar a estimativas exageradas. A escala de tempo escolhida para o período de otimização pode afetar a escolha dos parâmetros aplicados à estratégia. Isso implica que o valor final do PBO pode variar em diferentes escalas de tempo. Ao testar, devemos considerar o maior número possível de variações de parâmetros.

Um dos maiores defeitos deste teste é que ele não é facilmente aplicável a EAs cujo código fonte não está disponível. Teoricamente, seria possível realizar backtests separados para cada possível variação de parâmetros, mas isso seria tão trabalhoso quanto usar métodos de Monte Carlo.
 
Uma descrição detalhada do CSCV e a interpretação do PBO podem ser encontradas no artigo original. O link é fornecido no segundo parágrafo deste artigo. O código-fonte de todos os programas mencionados no artigo é fornecido abaixo.

Nome do arquivo
Descrição
Mql5\Include\Returns.mqh
Define a classe CReturns para coletar dados de retorno ou equity em tempo real.
Mql5\Include\CSCV.mqh
Contém a definição da classe Ccscv, que implementa a verificação cruzada combinatória simétrica.
Mql5\Experts\MovingAverage_CSCV_DemoEA.mq5
EA Moving Average modificado, demonstrando a aplicação da classe Ccscv.


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13743

Arquivos anexados |
CSCV.mqh (6.9 KB)
Returns.mqh (9.58 KB)
Mql5.zip (7.37 KB)
Padrões de projeto no MQL5 (Parte 2): Padrões estruturais Padrões de projeto no MQL5 (Parte 2): Padrões estruturais
Neste artigo, continuaremos a estudar os padrões de projeto que permitem aos desenvolvedores criar aplicativos expansíveis e confiáveis não apenas no MQL5, mas também em outras linguagens de programação. Desta vez, falaremos sobre outro tipo: modelos estruturais. Aprenderemos a projetar sistemas usando as classes disponíveis para formar estruturas maiores.
Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar
Este artigo é uma introdução a uma série completa de artigos sobre programação. Aqui supomos que o leitor nunca teve contato com programação antes. Por isso, começo pelo básico, com nível de conhecimento de programação: iniciante absoluto.
Criação de um Expert Advisor simples em várias moedas usando MQL5 (Parte 4): Média móvel triangular — Sinais do indicador Criação de um Expert Advisor simples em várias moedas usando MQL5 (Parte 4): Média móvel triangular — Sinais do indicador
Neste artigo, por EA multimoeda, entendemos um robô investidor, ou um robô de negociação, que pode negociar (abrir/fechar ordens, gerenciar ordens, por exemplo, do tipo trailing stop-loss e trailing profit) mais de um par de moedas em um gráfico. Desta vez, usaremos apenas um indicador, em particular a média móvel triangular em um ou mais timeframes, ou escalas de tempo.
Rede neural na prática: Mínimos Quadrados Rede neural na prática: Mínimos Quadrados
Aqui neste artigo, veremos algumas coisas, entre elas: Como muitas vezes fórmulas matemáticas parecem mais complicadas, quando a olhamos, do que quando a implementamos em código. Além deste fato, também será mostrado, como você pode ajustar o quadrante do gráfico, assim como uma coisa sinistra, que pode acontecer no seu código MQL5. Algo que sinceramente não sei como explicar, por não ter entendido. Apesar de mostrar como corrigir no código.