English Русский Español Deutsch 日本語
preview
Ciência de Dados e Aprendizado de Máquina (Parte 16): Uma nova perspectiva sobre árvores de decisão

Ciência de Dados e Aprendizado de Máquina (Parte 16): Uma nova perspectiva sobre árvores de decisão

MetaTrader 5Negociação | 23 maio 2024, 10:00
85 0
Omega J Msigwa
Omega J Msigwa

Visão geral

Em um dos artigos anteriores desta série, falei sobre árvores de decisão. Aprendemos o que são árvores de decisão e criamos um algoritmo para classificar dados meteorológicos. Entretanto, o código e as explicações apresentados naquele artigo aparentemente não foram claros o suficiente, pois continuo recebendo mensagens solicitando uma abordagem mais eficaz para a construção de árvores de decisão. Por isso, achei que seria útil escrever um segundo artigo e fornecer um código de melhor qualidade. Além disso, um bom entendimento das árvores de decisão é muito importante para a transição para o próximo estágio — os algoritmos de floresta aleatória, que discutiremos nos próximos artigos.

O que é uma árvore de decisão?

Uma árvore de decisão é uma estrutura em forma de árvore, semelhante a um fluxograma, onde cada nó interno representa uma verificação de um atributo (ou característica), cada ramo representa o resultado da verificação e cada nó folha representa a classe ou valor contínuo. O nó mais alto em uma árvore de decisão é conhecido como "raiz", e as folhas são os resultados ou previsões.

O que é um nó?

Em uma árvore de decisão, um nó é um componente fundamental que representa um ponto de tomada de decisão baseado em uma característica ou atributo específico. Existem dois tipos principais de nós em uma árvore de decisão: internos e folhas.

Nó interno

  • Um nó interno é um ponto de tomada de decisão na árvore onde é realizada uma verificação de uma característica específica. A verificação é baseada em uma condição determinada, como se o valor da característica excede um valor limite ou se pertence a uma determinada categoria.
  • Os nós internos têm ramos (arestas) que levam a nós filhos. O resultado da verificação determina qual ramo seguir.
  • Os nós internos, que representam dois nós filhos à esquerda e à direita, são nós dentro do nó central da árvore.

Nó folha (ou nó terminal)
  • Um nó folha é o ponto final na árvore, onde é tomada a decisão final ou previsão. Ele indica a classe na tarefa de classificação ou o valor previsto na tarefa de regressão.
  • Os nós folhas não têm ramos saindo deles, sendo os pontos finais do processo de tomada de decisão.
  • No código, vamos representá-los por uma variável do tipo double.
    class Node
    {
      public:
        // for decision node
           
        uint feature_index;
        double threshold;
        double info_gain;
         
        // for leaf node
         
        double leaf_value;   
          
        Node *left_child;  //left child Node
        Node *right_child; //right child Node
    
        Node() : left_child(NULL), right_child(NULL) {} // default constructor
    
        Node(uint feature_index_, double threshold_=NULL, Node *left_=NULL, Node *right_=NULL, double info_gain_=NULL, double value_=NULL)
            : left_child(left_), right_child(right_)
        {
            this.feature_index = feature_index_;
            this.threshold = threshold_;
            this.info_gain = info_gain_;
            this.value = value_;
        }
        
       void Print()
        {
          printf("feature_index: %d \nthreshold: %f \ninfo_gain: %f \nleaf_value: %f",feature_index,threshold, info_gain, value);
        }    
    };
    

    Ao contrário de alguns algoritmos de aprendizado de máquina que codificamos do zero nesta série, uma árvore de decisão pode ser complicada de representar em código e, por vezes, confusa, pois a implementação das árvores requer o uso de classes e funções recursivas. Por minha experiência, pode ser muito difícil representar isso em qualquer linguagem que não seja Python.

    Componentes do nó:

    Um nó em uma árvore de decisão geralmente contém as seguintes informações:

    01. Condições de verificação

    Os nós internos têm condições de verificação baseadas em uma característica específica e um valor limiar ou categoria. Essa condição determina como os dados são divididos nos nós filhos.

    Você não vê a condição de verificação nesta classe Node. Nós a implementamos dentro da função build_tree, que se integra com a classe do nó, pois retorna uma instância da classe nó.
    Node *build_tree(matrix &data, uint curr_depth=0);

    02. Característica e valor limiar

    Indica qual característica é verificada no nó, bem como o valor limiar ou a categoria usada para a divisão.

    uint feature_index;
    double threshold;
    

    03. Rótulo ou valor da classe

    Um nó folha armazena o rótulo da classe prevista (para classificação) ou o valor (para regressão).

    double leaf_value;   

    04. Nós filhos

    Os nós internos têm nós filhos, correspondentes a diferentes resultados da condição de verificação. Cada nó filho representa um subconjunto de dados que satisfaz a condição.

    Node *left_child;  //left child Node
    Node *right_child; //right child Node
    

    Exemplo:

    Vamos considerar uma árvore de decisão simples para classificar se uma fruta é uma maçã ou uma laranja, com base em sua cor.

    [Nó]

    Característica: cor

    Condição de verificação: a cor é vermelha?

    Se True, seguimos para o nó filho esquerdo, e se False, para o nó filho direito.

    [Nó folha — maçã]

    -Rótulo da classe: maçã

    [Nó folha — laranja]

    -Rótulo da classe: laranja

    Tipos de árvores de decisão:

    CART (Classification and Regression Trees) — usadas tanto para tarefas de classificação quanto de regressão. Dividem os dados com base no critério de Gini para classificação e no erro quadrático médio para regressão.

    ID3 (Iterative Dichotomiser 3) — usado principalmente para tarefas de classificação. Utiliza o conceito de entropia e ganho de informação para tomar decisões.

    C4.5 — versão aprimorada do ID3, usada para classificação. Usa a razão de ganho para eliminar o viés em relação a atributos com muitos níveis.

    Como vamos usar uma árvore de decisão para fins de classificação, construiremos o algoritmo ID3, que opera com base no ganho de informação, cálculo de critérios e características categóricas:


    ID3 (Iterative Dichotomiser 3)

    ID3 usa o critério de ganho de informação para escolher a melhor divisão dos dados em cada nó interno da árvore. O critério de ganho de informação mede a redução da entropia ou incerteza após a divisão do conjunto de dados.
    double CDecisionTree::information_gain(vector &parent, vector &left_child, vector &right_child)
     {  
        double weight_left = left_child.Size() / (double)parent.Size(),
               weight_right = right_child.Size() / (double)parent.Size();
        
        double gain =0;    
        switch(m_mode)
          {
           case  MODE_GINI:
             gain = gini_index(parent) - ( (weight_left*gini_index(left_child)) + (weight_right*gini_index(right_child)) );
             break;
           case MODE_ENTROPY:
             gain = entropy(parent) - ( (weight_left*entropy(left_child)) + (weight_right*entropy(right_child)) );
             break;
          }
        
       return gain;
     }
    

    Entropia — medida de incerteza ou desordem em um conjunto de dados. O algoritmo ID3 busca reduzir a entropia, escolhendo divisões de características que resultem em subconjuntos com rótulos de classe mais homogêneos.

    double CDecisionTree::entropy(vector &y)
     {    
       vector class_labels = matrix_utils.Unique_count(y);
         
       vector p_cls = class_labels / double(y.Size());
      
       vector entropy = (-1 * p_cls) * log2(p_cls);
      
      return entropy.Sum();
     }
    

    Para obter mais flexibilidade, pode-se escolher entre a entropia e o critério de Gini, que também é frequentemente usado em árvores de decisão e realiza a mesma função que a entropia. Ambos avaliam impurezas ou desordem em um conjunto de dados.

    double CDecisionTree::gini_index(vector &y)
     {
       vector unique = matrix_utils.Unique_count(y);
       
       vector probabilities = unique / (double)y.Size();
       
       return 1.0 - MathPow(probabilities, 2).Sum();
     }
    

    Fórmulas para calcular esses valores:


    O algoritmo ID3 é especialmente adequado para características categóricas, e a escolha das características e dos valores de limiar é baseada na redução da entropia para a separação em categorias. Veremos esse princípio em ação com um exemplo de algoritmo de árvore de decisão abaixo.


    Algoritmo da árvore de decisão

    01. Critérios de divisão

    Os critérios padrão para separar os dados durante a classificação são o índice de Gini e a entropia, enquanto para tarefas de regressão é o erro quadrático médio. Vamos focar nas funções de divisão do algoritmo da árvore de decisão, começando pela estrutura que armazena informações dos dados a serem divididos.

    //A struct containing splitted data information
    struct split_info
      {
       uint feature_index;
       double threshold;
       matrix dataset_left,
              dataset_right;
       double info_gain;
      };
    

    Usando um valor de limiar, dividimos os dados e colocamos os objetos com valores menores que esse limiar na matriz dataset_left, e os demais em dataset_right. Depois disso, retornamos uma instância da estrutura Split_info. 

    split_info CDecisionTree::split_data(const matrix &data, uint feature_index, double threshold=0.5)
     {
       int left_size=0, right_size =0;
       vector row = {};
       
       split_info split;
       
       ulong cols = data.Cols();
       
       split.dataset_left.Resize(0, cols);
       split.dataset_right.Resize(0, cols);
          
        for (ulong i=0; i<data.Rows(); i++)
         {       
           row = data.Row(i);
           
           if (row[feature_index] <= threshold)
            {
              left_size++;
              split.dataset_left.Resize(left_size, cols);
              split.dataset_left.Row(row, left_size-1); 
            }
           else
            {
             right_size++;
             split.dataset_right.Resize(right_size, cols);
             split.dataset_right.Row(row, right_size-1);         
            }
         }
       return split;
     }
    

    Dentre as diversas divisões, o algoritmo precisa determinar a melhor, ou seja, a que fornece o maior ganho de informação.

    split_info CDecisionTree::get_best_split(matrix &data, uint num_features)
      {
      
       double max_info_gain = -DBL_MAX;
       vector feature_values = {};
       vector left_v={}, right_v={}, y_v={};
       
    //---
       
       split_info best_split;
       split_info split;
       
       for (uint i=0; i<num_features; i++)
         {
           feature_values = data.Col(i);
           vector possible_thresholds = matrix_utils.Unique(feature_values); //Find unique values in the feature, representing possible thresholds for splitting.
                      
             for (uint j=0; j<possible_thresholds.Size(); j++)
                {              
                  split = this.split_data(data, i, possible_thresholds[j]);
                  
                  if (split.dataset_left.Rows()>0 && split.dataset_right.Rows() > 0)
                    {
                      y_v = data.Col(data.Cols()-1);
                      right_v = split.dataset_right.Col(split.dataset_right.Cols()-1);
                      left_v = split.dataset_left.Col(split.dataset_left.Cols()-1);
                      
                      double curr_info_gain = this.information_gain(y_v, left_v, right_v);
                                        
                      if (curr_info_gain > max_info_gain) // Check if the current information gain is greater than the maximum observed so far.
                        {             
                          #ifdef DEBUG_MODE
                            printf("split left: [%dx%d] split right: [%dx%d] curr_info_gain: %f max_info_gain: %f",split.dataset_left.Rows(),split.dataset_left.Cols(),split.dataset_right.Rows(),split.dataset_right.Cols(),curr_info_gain,max_info_gain);
                          #endif 
                          
                          best_split.feature_index = i;
                          best_split.threshold = possible_thresholds[j];
                          best_split.dataset_left = split.dataset_left;
                          best_split.dataset_right = split.dataset_right;
                          best_split.info_gain = curr_info_gain;
                          
                          max_info_gain = curr_info_gain;
                        }
                    }
                }    
         }
         
        return best_split;
      }
    

    A função busca características comuns e valores de limiar possíveis para encontrar a melhor divisão, que maximiza o ganho de informação. O resultado é a estrutura split_info, contendo informações sobre o objeto, valor de limiar e subconjuntos relacionados à melhor divisão.



    02. Construção da árvore

    As árvores de decisão são construídas por meio da divisão recursiva do conjunto de dados com base nas características até que uma condição de parada seja atendida (por exemplo, alcançar uma profundidade específica ou número mínimo de amostras).

    Node *CDecisionTree::build_tree(matrix &data, uint curr_depth=0)
     {
        matrix X;
        vector Y;
          
        matrix_utils.XandYSplitMatrices(data,X,Y); //Split the input matrix into feature matrix X and target vector Y.
        
        ulong samples = X.Rows(), features = X.Cols(); //Get the number of samples and features in the dataset.
            
        Node *node= NULL; // Initialize node pointer
                
        if (samples >= m_min_samples_split && curr_depth<=m_max_depth)
          {
             split_info best_split = this.get_best_split(data, (uint)features);
             
             #ifdef DEBUG_MODE
              Print("best_split left: [",best_split.dataset_left.Rows(),"x",best_split.dataset_left.Cols(),"]\nbest_split right: [",best_split.dataset_right.Rows(),"x",best_split.dataset_right.Cols(),"]\nfeature_index: ",best_split.feature_index,"\nInfo gain: ",best_split.info_gain,"\nThreshold: ",best_split.threshold);
             #endif 
                      
             if (best_split.info_gain > 0)
               {
                 Node *left_child = this.build_tree(best_split.dataset_left, curr_depth+1);
                 Node *right_child = this.build_tree(best_split.dataset_right, curr_depth+1);
                              
                 node = new Node(best_split.feature_index,best_split.threshold,left_child,right_child,best_split.info_gain);
                 return node;
               }
          }      
         
         node = new Node();
         node.leaf_value = this.calculate_leaf_value(Y);
              
         return node;
     }
    

    if (best_split.info_gain > 0):

    A linha de código acima verifica se há ganho de informação.

    Dentro desse bloco ocorre o seguinte:

    Node *left_child = this.build_tree(best_split.dataset_left, curr_depth+1);

    Criamos recursivamente o nó filho esquerdo.

    Node *right_child = this.build_tree(best_split.dataset_right, curr_depth+1);

    Criamos recursivamente o nó filho direito.

    node = new Node(best_split.feature_index, best_split.threshold, left_child, right_child, best_split.info_gain);
    
    

    Criamos um nó de decisão com informações da melhor divisão.

    node = new Node();      

    Se não for necessária uma divisão adicional, criamos um novo nó folha.

    node.value = this.calculate_leaf_value(Y);

    Definimos o valor do nó folha usando a função calculate_leaf_value.

    return node;

    Retornamos o nó representando a divisão atual ou a folha.

    Para facilitar o trabalho com as funções, a função build_tree pode ser deixada dentro da função fit, que é geralmente usada em módulos de aprendizado de máquina no Python.

    void CDecisionTree::fit(matrix &x, vector &y)
     {
       matrix data = matrix_utils.concatenate(x, y, 1);
       
       this.root = this.build_tree(data);
     }
    

    Fazemos previsões durante o treinamento e teste do modelo

    vector CDecisionTree::predict(matrix &x)
     {
        vector ret(x.Rows());
        
        for (ulong i=0; i<x.Rows(); i++)
           ret[i] = this.predict(x.Row(i));
           
       return ret;
     }
    

    Fazemos previsões em tempo real

    double CDecisionTree::predict(vector &x)
     {     
       return this.make_predictions(x, this.root);
     }
    

    Todo o trabalho pesado é feito na função make_predictions:

    double CDecisionTree::make_predictions(vector &x, const Node &tree)
     {
        if (tree.leaf_value != NULL) // This is a leaf leaf_value
          return tree.leaf_value;
          
        double feature_value = x[tree.feature_index];
        double pred = 0;
        
        #ifdef DEBUG_MODE
          printf("Tree.threshold %f tree.feature_index %d leaf_value %f",tree.threshold,tree.feature_index,tree.leaf_value);
        #endif 
        
        if (feature_value <= tree.threshold)
          {
           pred = this.make_predictions(x, tree.left_child);  
          }
        else
         {
           pred = this.make_predictions(x, tree.right_child);
         }
         
       return pred;
     }
    

    Mais detalhes sobre essa função:

    Verificamos se o valor da característica é menor ou igual ao valor de limiar do nó atual.
    if (feature_value <= tree.threshold): 

    Dentro desse bloco ocorre o seguinte:

    Chamamos recursivamente a função make_predictions para o nó filho esquerdo.

    pred = this.make_predictions(x, *tree.left_child);

    Caso contrário, se (Else, If) o valor da característica for maior que o valor de limiar:

    Chamamos recursivamente a função make_predictions para o nó filho direito.

    pred = this.make_predictions(x, *tree.right_child);
    return pred;

     Retornamos a previsão.

    Calculamos os valores da folha

    A função abaixo calcula o valor da folha:

    double CDecisionTree::calculate_leaf_value(vector &Y)
     {   
       vector uniques = matrix_utils.Unique_count(Y);
       vector classes = matrix_utils.Unique(Y);
       
       return classes[uniques.ArgMax()];
     }
    

    A função retorna o elemento de Y com o maior número, ou seja, encontra o elemento mais comum na lista.


    Tudo isso se resume à classe CDecisionTree.

    enum mode {MODE_ENTROPY, MODE_GINI};
    
    class CDecisionTree
      {
    CMatrixutils   matrix_utils;
    
    protected:
          
       Node *build_tree(matrix &data, uint curr_depth=0);
       double  calculate_leaf_value(vector &Y);
       
    //---
       
       uint m_max_depth;
       uint m_min_samples_split;
       
       mode m_mode;
       
       double  gini_index(vector &y);
       double  entropy(vector &y);
       double  information_gain(vector &parent, vector &left_child, vector &right_child);
       
       
       split_info  get_best_split(matrix &data, uint num_features);
       split_info  split_data(const matrix &data, uint feature_index, double threshold=0.5);
       
       double make_predictions(vector &x, const Node &tree);
       void delete_tree(Node* node);
       
    public:
                         Node *root;
                         
                         CDecisionTree(uint min_samples_split=2, uint max_depth=2, mode mode_=MODE_GINI);
                        ~CDecisionTree(void);
                        
                         void fit(matrix &x, vector &y);
                         void print_tree(Node *tree, string indent=" ",string padl="");
                         double predict(vector &x);
                         vector predict(matrix &x);
      };
    

    Agora, vamos ver como tudo funciona na prática, como construir a árvore e como usá-la para previsão durante o treinamento e teste, bem como no trading em tempo real. Vamos trabalhar com a amostra de dados iris-CSV e testar o funcionamento da classe.

    Treinaremos o modelo da árvore de decisão a cada inicialização do EA, começando com o carregamento dos dados de treinamento do arquivo CSV:

    int OnInit()
      {
      matrix dataset = matrix_utils.ReadCsv("iris.csv"); //loading iris-data
      
      decision_tree = new CDecisionTree(3,3, MODE_GINI); //Initializing the decision tree
      
      matrix x; vector y;
      matrix_utils.XandYSplitMatrices(dataset,x,y); //split the data into x and y matrix and vector respectively
      
      
      decision_tree.fit(x, y);  //Building the tree
      decision_tree.print_tree(decision_tree.root); //Printing the tree
      
      vector preds = decision_tree.predict(x); //making the predictions on a training data
      
      Print("Train Acc = ",metrics.confusion_matrix(y, preds)); //Measuring the accuracy
      
       return(INIT_SUCCEEDED);
      }
    

    É assim que a matriz do conjunto de dados se vê na saída. A última coluna passou pelo codificador.aA última coluna foi passada por um codificador. Um (1) significa Setosa, dois (2) significa Versicolor e três (3) significa Virginica.

    Print("iris-csv\n",dataset);
    MS      0       08:54:40.958    DecisionTree Test (EURUSD,H1)   iris-csv
    PH      0       08:54:40.958    DecisionTree Test (EURUSD,H1)   [[5.1,3.5,1.4,0.2,1]
    CO      0       08:54:40.958    DecisionTree Test (EURUSD,H1)    [4.9,3,1.4,0.2,1]
    ...
    ...
    
    NS      0       08:54:40.959    DecisionTree Test (EURUSD,H1)    [5.6,2.7,4.2,1.3,2]
    JK      0       08:54:40.959    DecisionTree Test (EURUSD,H1)    [5.7,3,4.2,1.2,2]
    ...
    ...
    
    NQ      0       08:54:40.959    DecisionTree Test (EURUSD,H1)    [6.2,3.4,5.4,2.3,3]
    PD      0       08:54:40.959    DecisionTree Test (EURUSD,H1)    [5.9,3,5.1,1.8,3]]
    

    Saída da árvore

    Se observarmos atentamente o código, podemos notar a função print_tree, que aceita a raiz da árvore como um dos argumentos. Essa função tenta imprimir uma visão geral da árvore no log. Vamos olhar mais de perto seu funcionamento.

    void CDecisionTree::print_tree(Node *tree, string indent=" ",string padl="")
      {
         if (tree.leaf_value != NULL)
            Print((padl+indent+": "),tree.leaf_value); 
         else //if we havent' reached the leaf node keep printing child trees
           {
             padl += " ";
             
             Print((padl+indent)+": X_",tree.feature_index, "<=", tree.threshold, "?", tree.info_gain);
             
             print_tree(tree.left_child, "left","--->"+padl);
             
             print_tree(tree.right_child, "right","--->"+padl);
           }
      }  
    

    Mais detalhes sobre essa função:

    Estrutura do nó:

    A função assume que a classe Node representa a árvore de decisão. Cada nó pode ser um nó de decisão ou um nó terminal. Os nós de decisão têm índice de característica feature_index, limiar threshold, ganho de informação info_gain e valor da folha leaf_value.

    Saída do nó de decisão:

    Se o nó atual não for um nó terminal (ou seja, tree.leaf_value tem valor NULL), a função imprime informações sobre o nó de decisão. Imprime a condição para divisão, como "X_2 <= 1.9 ? 0.33" e o nível de indentação.

    Saída do nó folha:

    Se o nó atual for um nó folha (ou seja, tree.leaf_value não é NULL), imprime o valor final junto com o nível de indentação. Por exemplo, "left: 0.33".

    Recursão:

    A função então chama recursivamente a si mesma para o filho esquerdo e direito do nó atual Node. O argumento padl adiciona indentação à mensagem, tornando a estrutura em árvore mais legível.

    Saída de print_tree para a árvore de decisão construída dentro da função OnInit:

    CR      0       09:26:39.990    DecisionTree Test (EURUSD,H1)     : X_2<=1.9?0.3333333333333334
    HO      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   ---> left: 1.0
    RH      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->  right: X_3<=1.7?0.38969404186795487
    HP      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->   left: X_2<=4.9?0.08239026063100136
    KO      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->    left: X_3<=1.6?0.04079861111111116
    DH      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->--->    left: 2.0
    HM      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: 3.0
    HS      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->    right: X_3<=1.5?0.2222222222222222
    IH      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->--->    left: 3.0
    QM      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: 2.0
    KP      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->   right: X_2<=4.8?0.013547574039067499
    PH      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->    left: X_0<=5.9?0.4444444444444444
    PE      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->--->    left: 2.0
    DP      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: 3.0
    EE      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   --->--->--->   right: 3.0
    

    Impressionante.

    Abaixo é mostrada a precisão do nosso modelo treinado:

      vector preds = decision_tree.predict(x); //making the predictions on a training data
      
      Print("Train Acc = ",metrics.confusion_matrix(y, preds)); //Measuring the accuracy
    

    Resultado

    PM      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   Confusion Matrix
    CE      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   [[50,0,0]
    HR      0       09:26:39.990    DecisionTree Test (EURUSD,H1)    [0,50,0]
    ND      0       09:26:39.990    DecisionTree Test (EURUSD,H1)    [0,1,49]]
    GS      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   
    KF      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   Classification Report
    IR      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   
    MD      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   _    Precision  Recall  Specificity  F1 score  Support
    EQ      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   1.0    50.00     50.00     100.00       50.00     50.0
    HR      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   2.0    51.00     50.00     100.00       50.50     50.0
    PO      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   3.0    49.00     50.00     100.00       49.49     50.0
    EH      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   
    PR      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   Accuracy                                   0.99
    HQ      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   Average   50.00    50.00    100.00      50.00    150.0
    DJ      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   W Avg     50.00    50.00    100.00      50.00    150.0
    LG      0       09:26:39.990    DecisionTree Test (EURUSD,H1)   Train Acc = 0.993
    

    Alcançamos uma precisão de 99,3%, indicando a implementação bem-sucedida da nossa árvore de decisão. Essa precisão é consistente com o que esperaríamos de um modelo Scikit-Learn ao resolver um problema simples com o conjunto de dados.

    Vamos continuar treinando e testando o modelo em dados fora da amostra.

      matrix train_x, test_x;
      vector train_y, test_y;
      
      matrix_utils.TrainTestSplitMatrices(dataset, train_x, train_y, test_x, test_y, 0.8, 42); //split the data into training and testing samples
    
      
      decision_tree.fit(train_x, train_y);  //Building the tree
      decision_tree.print_tree(decision_tree.root); //Printing the tree
      
      vector preds = decision_tree.predict(train_x); //making the predictions on a training data
      
      Print("Train Acc = ",metrics.confusion_matrix(train_y, preds)); //Measuring the accuracy
      
    //---
    
      preds = decision_tree.predict(test_x); //making the predictions on a test data
      
      Print("Test Acc = ",metrics.confusion_matrix(test_y, preds)); //Measuring the accuracy
    

    Resultado

    QD      0       14:56:03.860    DecisionTree Test (EURUSD,H1)     : X_2<=1.7?0.34125
    LL      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   ---> left: 1.0
    QK      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   --->  right: X_3<=1.6?0.42857142857142855
    GS      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   --->--->   left: X_2<=4.9?0.09693877551020412
    IL      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   --->--->--->   left: 2.0
    MD      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   --->--->--->    right: X_3<=1.5?0.375
    IS      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   --->--->--->--->    left: 3.0
    QR      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: 2.0
    RH      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   --->--->  right: 3.0
    HP      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   Confusion Matrix
    FG      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   [[42,0,0]
    EO      0       14:56:03.860    DecisionTree Test (EURUSD,H1)    [0,39,0]
    HK      0       14:56:03.860    DecisionTree Test (EURUSD,H1)    [0,0,39]]
    OL      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   
    KE      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   Classification Report
    QO      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   
    MQ      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   _    Precision  Recall  Specificity  F1 score  Support
    OQ      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   1.0    42.00     42.00     78.00       42.00     42.0
    ML      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   3.0    39.00     39.00     81.00       39.00     39.0
    HK      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   2.0    39.00     39.00     81.00       39.00     39.0
    OE      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   
    EO      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   Accuracy                                   1.00
    CG      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   Average   40.00    40.00    80.00      40.00    120.0
    LF      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   W Avg     40.05    40.05    79.95      40.05    120.0
    PR      0       14:56:03.860    DecisionTree Test (EURUSD,H1)   Train Acc = 1.0
    CD      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   Confusion Matrix
    FO      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   [[9,2,0]
    RK      0       14:56:03.861    DecisionTree Test (EURUSD,H1)    [1,10,0]
    CL      0       14:56:03.861    DecisionTree Test (EURUSD,H1)    [2,0,6]]
    HK      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   
    DQ      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   Classification Report
    JJ      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   
    FM      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   _    Precision  Recall  Specificity  F1 score  Support
    QM      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   2.0    12.00     11.00     19.00       11.48     11.0
    PH      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   3.0    12.00     11.00     19.00       11.48     11.0
    KD      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   1.0    6.00     8.00     22.00       6.86     8.0
    PP      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   
    LJ      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   Accuracy                                   0.83
    NJ      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   Average   10.00    10.00    20.00      9.94    30.0
    JR      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   W Avg     10.40    10.20    19.80      10.25    30.0
    HP      0       14:56:03.861    DecisionTree Test (EURUSD,H1)   Test Acc = 0.833
    

    O modelo tem 100% de precisão nos dados de treinamento e 83% de precisão nos dados fora da amostra.


    Árvore de decisão e IA no trading 

    Por outra parte, estamos interessados em uma aplicação específica dos modelos baseados em árvore de decisão — o trading. Para usar esse modelo no trading, precisamos formular o problema a ser resolvido.

    Problema:

    Usaremos o modelo de IA da árvore de decisão para fazer previsões na barra atual sobre a direção do mercado: para cima ou para baixo.

    Como em qualquer modelo, forneceremos ao nosso modelo um conjunto de dados para treinamento. Para isso, utilizaremos dois tipos de indicadores osciladores: o indicador RSI e o oscilador estocástico. Essencialmente, precisamos que o modelo entenda os padrões entre esses dois indicadores e como eles determinam o movimento do preço na barra atual.

    Estrutura de dados:

    Abaixo está a estrutura onde são armazenados os dados coletados para treinamento e teste. O mesmo se aplica aos dados usados para previsão em tempo real.

    struct data{
       vector stoch_buff, 
              signal_buff, 
              rsi_buff, 
              target;
    } data_struct;
    
    


    Coleta de dados, treinamento e teste da árvore de decisão

    void TrainTree()
     {
      matrix dataset(train_bars, 4);
      vector v;
    
    //--- Collecting indicator buffers
    
      data_struct.rsi_buff.CopyIndicatorBuffer(rsi_handle, 0, 1, train_bars);
      data_struct.stoch_buff.CopyIndicatorBuffer(stoch_handle, 0, 1, train_bars);
      data_struct.signal_buff.CopyIndicatorBuffer(stoch_handle, 1, 1, train_bars);
      
    //--- Preparing the target variable
       
      MqlRates rates[];
      ArraySetAsSeries(rates, true); 
      int size = CopyRates(Symbol(), PERIOD_CURRENT, 1,train_bars, rates);
      
      data_struct.target.Resize(size); //Resize the target vector
      
      for (int i=0; i<size; i++)
        {
          if (rates[i].close > rates[i].open)
            data_struct.target[i] = 1;
          else 
            data_struct.target[i] = -1;
        }
      
      dataset.Col(data_struct.rsi_buff, 0);
      dataset.Col(data_struct.stoch_buff, 1);
      dataset.Col(data_struct.signal_buff, 2);
      dataset.Col(data_struct.target, 3);
       
      decision_tree = new CDecisionTree(min_sample,max_depth_, tree_mode); //Initializing the decision tree
      
    
      matrix train_x, test_x;
      vector train_y, test_y;
      
      matrix_utils.TrainTestSplitMatrices(dataset, train_x, train_y, test_x, test_y, 0.8, 42); //split the data into training and testing samples
    
      
      decision_tree.fit(train_x, train_y);  //Building the tree
      decision_tree.print_tree(decision_tree.root); //Printing the tree
      
      vector preds = decision_tree.predict(train_x); //making the predictions on a training data
      
      Print("Train Acc = ",metrics.confusion_matrix(train_y, preds)); //Measuring the accuracy
      
    //---
    
      preds = decision_tree.predict(test_x); //making the predictions on a test data
      
      Print("Test Acc = ",metrics.confusion_matrix(test_y, preds)); //Measuring the accuracy
     }
    

    Definimos a amostra mínima (parâmetro min-sample) como 3 e a profundidade máxima (max-depth) como 5.

    Resultado

    KR      0       16:26:53.028    DecisionTree Test (EURUSD,H1)     : X_0<=65.88930872549261?0.0058610536710859695
    CN      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->  left: X_0<=29.19882857713344?0.003187469522387243
    FK      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->   left: X_1<=26.851851851853503?0.030198175526895188
    RI      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->    left: X_2<=7.319205739522295?0.040050858232676456
    KG      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->     left: X_0<=23.08345903222593?0.04347468770545693
    JF      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      left: X_0<=21.6795921184317?0.09375
    PF      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: -1.0
    ER      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: -1.0
    QF      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      right: X_2<=3.223853479489069?0.09876543209876543
    LH      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: -1.0
    FJ      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: 1.0
    MM      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: -1.0
    MG      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->   right: 1.0
    HH      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->   right: X_0<=65.4606831930956?0.0030639039663222234
    JR      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->    left: X_0<=31.628407983040333?0.00271101025966336
    PS      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->     left: X_0<=31.20436037455599?0.0944903581267218
    DO      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      left: X_2<=14.629981942657205?0.11111111111111116
    EO      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: 1.0
    IG      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: -1.0
    EI      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->     right: 1.0
    LO      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->     right: X_0<=32.4469112469684?0.003164795835173595
    RO      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      left: X_1<=76.9736842105244?0.21875
    RO      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: -1.0
    PG      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: 1.0
    MO      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      right: X_0<=61.82001028403415?0.0024932856070305487
    LQ      0       16:26:53.028    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: -1.0
    EQ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: 1.0
    LE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->    right: X_2<=84.68660541575225?0.09375
    ED      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->    left: -1.0
    LM      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: -1.0
    NE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->  right: X_0<=85.28191275702572?0.024468404842877933
    DK      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->   left: X_1<=25.913621262458935?0.01603292204455742
    LE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->    left: X_0<=72.18709160232456?0.2222222222222222
    ED      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->     left: X_1<=15.458937198072245?0.4444444444444444
    QQ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->     left: 1.0
    CS      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->     right: -1.0
    JE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: -1.0
    QM      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->    right: X_0<=69.83504428897093?0.012164425148527835
    HP      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->     left: X_0<=68.39798826749553?0.07844460227272732
    DL      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      left: X_1<=90.68322981366397?0.06611570247933873
    DO      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: 1.0
    OE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: 1.0
    LI      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      right: X_1<=88.05704099821516?0.11523809523809525
    DE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: 1.0
    DM      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: -1.0
    LG      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->     right: X_0<=70.41747488780877?0.015360959832756427
    OI      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->     left: 1.0
    PI      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      right: X_0<=70.56490391752676?0.02275277028755862
    CF      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: -1.0
    MO      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: 1.0
    EG      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->   right: X_1<=97.0643939393936?0.10888888888888892
    CJ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->   left: 1.0
    GN      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->    right: X_0<=90.20261550045987?0.07901234567901233
    CP      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->     left: X_0<=85.94461490761033?0.21333333333333332
    HN      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->     left: -1.0
    GE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->      right: X_1<=99.66856060606052?0.4444444444444444
    GK      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      left: -1.0
    IK      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->--->--->      right: 1.0
    JM      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   --->--->--->--->    right: -1.0
    KE      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Confusion Matrix
    DO      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   [[122,271]
    QF      0       16:26:53.029    DecisionTree Test (EURUSD,H1)    [51,356]]
    HS      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   
    LF      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Classification Report
    JR      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   
    ND      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   _    Precision  Recall  Specificity  F1 score  Support
    GQ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   1.0    173.00     393.00     407.00       240.24     393.0
    HQ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   -1.0    627.00     407.00     393.00       493.60     407.0
    PM      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   
    OG      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Accuracy                                   0.60
    EO      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Average   400.00    400.00    400.00      366.92    800.0
    GN      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   W Avg     403.97    400.12    399.88      369.14    800.0
    LM      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Train Acc = 0.598
    GK      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Confusion Matrix
    CQ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   [[75,13]
    CK      0       16:26:53.029    DecisionTree Test (EURUSD,H1)    [86,26]]
    NI      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   
    RP      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Classification Report
    HH      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   
    LR      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   _    Precision  Recall  Specificity  F1 score  Support
    EM      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   -1.0    161.00     88.00     112.00       113.80     88.0
    NJ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   1.0    39.00     112.00     88.00       57.85     112.0
    LJ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   
    EL      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Accuracy                                   0.51
    RG      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Average   100.00    100.00    100.00      85.83    200.0
    ID      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   W Avg     92.68    101.44    98.56      82.47    200.0
    JJ      0       16:26:53.029    DecisionTree Test (EURUSD,H1)   Test Acc = 0.505
    

    O modelo mostrou uma precisão de 60% durante o treinamento e 50,5% durante o teste. O resultado, para dizer o mínimo, não é muito bom. Podem haver muitas razões, incluindo a qualidade dos dados usados para construir o modelo ou a presença de preditores ruins. A causa mais comum pode ser que os parâmetros do modelo foram definidos incorretamente.

    Para corrigir isso, é necessário ajustar os parâmetros para encontrar os mais adequados para os objetivos específicos. 

    Agora, vamos escrever uma função que fará previsões em tempo real. 

    int desisionTreeSignal()
     {
    //--- Copy the current bar information only
    
       data_struct.rsi_buff.CopyIndicatorBuffer(rsi_handle, 0, 0, 1);
       data_struct.stoch_buff.CopyIndicatorBuffer(stoch_handle, 0, 0, 1);
       data_struct.signal_buff.CopyIndicatorBuffer(stoch_handle, 1, 0, 1);
       
       x_vars[0] = data_struct.rsi_buff[0];
       x_vars[1] = data_struct.stoch_buff[0];
       x_vars[2] = data_struct.signal_buff[0];
       
       return int(decision_tree.predict(x_vars));
     }
    

    Usaremos uma lógica simples para trading:

    Se a árvore de decisão prever -1, indicando que a vela fechará em baixa, abriremos uma posição de venda. Se prever a classe 1, indicando que a vela fechará em alta, abriremos uma posição de compra.

    void OnTick()
      {
    //---
        if (!train_once)              // You want to train once during EA lifetime 
          TrainTree();
        train_once = true;
        
        if (isnewBar(PERIOD_CURRENT)) // We want to trade on the bar opening 
          {
            int signal = desisionTreeSignal();
            double min_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
            SymbolInfoTick(Symbol(), ticks);
            
             if (signal == -1)
               {
                  if (!PosExists(MAGICNUMBER, POSITION_TYPE_SELL)) // If a sell trade doesnt exist
                    m_trade.Sell(min_lot, Symbol(), ticks.bid, ticks.bid+stoploss*Point(), ticks.bid - takeprofit*Point());
               }
             else
               {
                 if (!PosExists(MAGICNUMBER, POSITION_TYPE_BUY))  // If a buy trade doesnt exist
                   m_trade.Buy(min_lot, Symbol(), ticks.ask, ticks.ask-stoploss*Point(), ticks.ask + takeprofit*Point());
               }
          }
      }
    

    Realizei um teste no período de um mês, de 01.01.2023 a 01.02.2023, com preços de abertura, apenas para ver se tudo funcionaria.


    Perguntas frequentes sobre árvores de decisão no trading:

    Pergunta Resposta
    A normalização dos dados de entrada é importante para árvores de decisão? Não, a normalização geralmente não é fundamental para árvores de decisão. As árvores de decisão se dividem com base nos valores de limiar dos atributos, e a escala dos atributos não afeta a estrutura da árvore. No entanto, é recomendado verificar o impacto da normalização no desempenho do modelo.
    Como as árvores de decisão lidam com variáveis categóricas nos dados de trading? As árvores de decisão podem lidar naturalmente com variáveis categóricas. Elas realizam divisões binárias com base no cumprimento de uma condição, incluindo condições para variáveis categóricas. A árvore determinará os pontos ótimos de divisão para os atributos categóricos.
    É possível usar árvores de decisão para previsão de séries temporais no trading? Embora as árvores de decisão possam ser usadas para previsão de séries temporais no trading, elas não capturam tão efetivamente padrões temporais complexos como, por exemplo, redes neurais recorrentes (RNN) e outros modelos semelhantes. Métodos de conjunto, como florestas aleatórias, podem fornecer maior confiabilidade.
    As árvores de decisão são suscetíveis ao overfitting? Árvores de decisão, especialmente as profundas, podem ser propensas ao overfitting devido ao ruído nos dados de treinamento. Para prevenir o overfitting em programas de trading, métodos como poda e limitação da profundidade da árvore podem ser utilizados.
    As árvores de decisão são adequadas para a análise da importância das características em modelos de trading? Sim, as árvores de decisão fornecem uma maneira natural de avaliar a importância das características. As características que mais contribuem para as decisões de divisão no topo da árvore são geralmente mais importantes. Essa análise pode fornecer insights sobre os fatores que influenciam as decisões de trading.
    As árvores de decisão são sensíveis a valores atípicos nos dados? As árvores de decisão podem ser sensíveis a eles, especialmente se a árvore for profunda. Valores atípicos podem levar a certas divisões devido ao ruído. Para reduzir essa sensibilidade, pode-se aplicar etapas de pré-processamento, durante as quais esses valores atípicos são detectados e removidos.
    Existem hiperparâmetros específicos ajustáveis para árvores de decisão em modelos de trading?

    Sim, os principais hiperparâmetros ajustáveis incluem:

    • profundidade da árvore
    • amostra mínima na folha
    • critério de divisão (por exemplo, critério de Gini ou entropia).

    Pode-se usar validação cruzada para encontrar os valores ótimos dos hiperparâmetros para conjuntos de dados específicos.

     As árvores de decisão podem fazer parte de uma abordagem de ensemble? Sim, as árvores de decisão podem fazer parte de métodos de ensemble, como florestas aleatórias, que combinam várias árvores para melhorar a eficiência geral da previsão. Os métodos de ensemble são frequentemente robustos e eficazes em aplicações de trading.


    Vantagens das árvores de decisão:

    Interpretabilidade:

    • As árvores de decisão são fáceis de entender e interpretar. A representação gráfica da estrutura em árvore permite visualizar claramente os processos de tomada de decisão.

    Trabalho com não-linearidade:

    • As árvores de decisão podem capturar relações não lineares nos dados, tornando-as adequadas para resolver problemas onde os limites de decisão não são lineares.

    Trabalho com tipos de dados mistos:

    • As árvores de decisão podem aceitar dados numéricos e categóricos sem a necessidade de pré-processamento significativo.

    Importância das características:

    • As árvores de decisão fornecem uma maneira natural de avaliar a importância das características, ajudando a identificar os fatores críticos que influenciam a variável alvo.

    Não fazem suposições sobre a distribuição dos dados:

    • Isso as torna bastante universais e aplicáveis a diferentes conjuntos de dados.

    Resistência a valores atípicos:

    • As árvores de decisão são relativamente resistentes a valores atípicos, pois as divisões são baseadas em comparações relativas e não são influenciadas por valores absolutos.

    Seleção automática de variáveis:

    • O processo de construção da árvore inclui a seleção automática de variáveis, reduzindo a necessidade de seleção manual de características.

    Podem lidar com valores ausentes:

    • As árvores de decisão podem lidar com valores ausentes nos objetos sem a necessidade de imputação, pois as divisões são feitas com base nos dados disponíveis.


    Desvantagens das árvores de decisão:

    Overfitting:

    • As árvores de decisão tendaem a overfitting, especialmente se forem profundas e capturarem o ruído nos dados de treinamento. Para resolver esse problema, técnicas como a poda são utilizadas.

    Instabilidade:

    • Pequenas mudanças nos dados podem levar a alterações significativas na estrutura da árvore, tornando as árvores de decisão instáveis.

    Propensão a classes dominantes:

    • Em conjuntos de dados com classes desequilibradas, as árvores de decisão podem ser tendenciosas em relação à classe dominante, resultando em desempenho subótimo para classes menos representadas.

    Solução global e local:

    • As árvores de decisão buscam divisões localmente ótimas em cada nó, o que pode não levar a uma solução globalmente ótima.

    Expressividade limitada:

    • As árvores de decisão nem sempre conseguem expressar relações complexas nos dados em comparação com modelos mais sofisticados, como redes neurais.

    Não são adequadas para saídas contínuas:

    • Embora as árvores de decisão sejam adequadas para tarefas de classificação, elas não são ideais para tarefas que requerem saídas contínuas.

    Sensíveis a dados ruidosos:

    • As árvores de decisão podem ser sensíveis a dados ruidosos, e valores atípicos podem levar a divisões baseadas em ruído, em vez de padrões significativos.

    Propensão a características dominantes:

    • Características com muitos níveis ou categorias podem parecer mais importantes devido à forma como as divisões são feitas, potencialmente levando a vieses. Esse problema pode ser mitigado com métodos como a normalização das características.

    Isso é tudo, obrigado pela atenção.

    Você pode acompanhar o desenvolvimento do projeto e contribuir com o algoritmo de árvore de decisão e muitas outras modelos de IA no meu repositório do GitHub: https://github.com/MegaJoctan/MALE5/tree/master

    Conteúdo do anexo:

    tree.mqh Arquivo de inclusão principal. Código da árvore de decisão que discutimos principalmente no artigo.
    metrics.mqh Funções e código para medir o desempenho de modelos de aprendizado de máquina.
    matrix_utils.mqh Funções auxiliares para trabalhar com matrizes.
    preprocessing.mqh Biblioteca para pré-processamento de dados brutos, tornando-os utilizáveis em modelos de aprendizado de máquina.
    DecisionTree Test.mq5(EA) Arquivo principal. EA para executar a árvore de decisão.




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

    Arquivos anexados |
    Code.zip (20.2 KB)
    Desenvolvendo um sistema de Replay (Parte 50): Complicando as coisas (II) Desenvolvendo um sistema de Replay (Parte 50): Complicando as coisas (II)
    Vamos resolver a questão da ID do gráfico, mas ao mesmo tempo, vamos começar a garantir que o usuário possa fazer uso de um template pessoal, voltado para analisar o ativo que ele gostaria de efetuar estudos e simulações. O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como sendo, uma aplicação cuja finalidade não venha a ser o aprendizado e estudo dos conceitos mostrados.
    Algoritmos de otimização populacional: simulação de têmpera isotrópica (Simulated Isotropic Annealing, SIA). Parte II Algoritmos de otimização populacional: simulação de têmpera isotrópica (Simulated Isotropic Annealing, SIA). Parte II
    A primeira parte do artigo foi dedicada ao conhecido e popular algoritmo de têmpera simulada, onde foram analisadas suas vantagens e descritos detalhadamente os pontos fracos. A segunda parte do artigo é dedicada a uma transformação radical do algoritmo, seu renascimento em um novo algoritmo de otimização, a simulação de têmpera isotrópica, SIA.
    Filtragem e extração de características no domínio da frequência Filtragem e extração de características no domínio da frequência
    Neste artigo, vamos explorar a aplicação de filtros digitais em séries temporais representadas no domínio da frequência, com o objetivo de extrair características únicas que podem ser úteis para modelos de previsão.
    EA de grid-hedge modificado em MQL5 (Parte I): Criando um EA de hedge simples EA de grid-hedge modificado em MQL5 (Parte I): Criando um EA de hedge simples
    Criaremos um EA de hedge simples como base para nosso EA Grid-Hedge mais avançado, que será uma mistura de estratégias clássicas de grade e de hedge clássicas. Ao final deste artigo, você saberá como criar uma estratégia de hedge simples e o que as pessoas estão dizendo sobre a lucratividade dessa estratégia.