English Русский 中文 Español 日本語 Português
preview
Datenwissenschaft und maschinelles Lernen (Teil 09): Der Algorithmus K-Nächste-Nachbarn (K-Nearest Neighbors, KNN)

Datenwissenschaft und maschinelles Lernen (Teil 09): Der Algorithmus K-Nächste-Nachbarn (K-Nearest Neighbors, KNN)

MetaTrader 5Handel | 25 November 2022, 10:03
328 0
Omega J Msigwa
Omega J Msigwa

Gleich und gleich gesellt sich gern - die Idee hinter dem KNN-Algorithmus.

Der Algorithmus K-Nächste-Nachbarn ist ein nicht-parametrischer überwachter Lernklassifikator, der die Nähe nutzt, um Klassifizierungen oder Vorhersagen über die Gruppierung eines einzelnen Datenpunktes zu treffen. Er wird häufig als Klassifizierungsalgorithmus verwendet, da er davon ausgeht, dass ähnliche Punkte im Datensatz nahe beieinander liegen. Der Algorithmus K-Nächste-Nachbarn ist einer der einfachsten Algorithmen des überwachten maschinellen Lernens. Wir werden unseren Algorithmus in diesem Artikel als Klassifikator aufbauen.


kNN-Algorithmus

Bildquelle: skicit-learn.org

Ein paar Dinge zu beachten:

  1.  Er wird häufig als Klassifikator verwendet, er kann aber auch für Regressionen eingesetzt werden.
  2.  K-NN ist ein nichtparametrischer Algorithmus, d. h. er macht keine Annahmen über die zugrunde liegenden Daten.
  3.  Der Algorithmus wird oft als "lernfaul" bezeichnet, weil er nicht aus dem Trainingsset lernt, sondern die Daten speichert und während der Zeit der Aktion verwendet.
  4.  Der KNN-Algorithmus geht von der Ähnlichkeit zwischen den neuen Daten und dem vorhandenen Datensatz aus und ordnet die neuen Daten in die Kategorie ein, die den vorhandenen Kategorien am ähnlichsten ist.

Wie funktioniert KNN?

Bevor wir mit dem Schreiben von Code beginnen, sollten wir verstehen, wie der KNN-Algorithmus funktioniert;
  • Schritt 01: Auswahl der Anzahl k der Nachbarn
  • Schritt 02: Berechnung des euklidischen Abstands eines Punktes zu allen Mitgliedern des Datensatzes
  • Schritt 03: Auswahl der k nächsten Nachbarn entsprechend dem euklidischen Abstand
  • Schritt 04: Zählen die Anzahl der Datenpunkte in jeder Kategorie unter diesen nächsten Nachbarn
  • Schritt 05: Ordnen der neuen Datenpunkte der Kategorie zu, für die die Anzahl der Nachbarn am höchsten ist.

Schritt 01: Auswahl der Anzahl k der Nachbarn

Dies ist ein einfacher Schritt, alles was wir tun müssen, ist die Anzahl von k auszuwählen, die wir in unserer Klasse CKNNnearestNeighbors verwenden werden, dies wirft nun die Frage auf, wie wir k wählen werden.

Wie können wir k beziffern?

k ist die Anzahl der nächsten Nachbarn, die zur Abstimmung darüber herangezogen werden, wo der gegebene Wert/Punkt hingehört. Die Wahl einer kleinen Anzahl k führt zu einer Menge Rauschen in den klassifizierten Datenpunkten, was zu einer höheren Anzahl von Verzerrungen führen kann. Eine größere Anzahl k macht den Algorithmus jedoch deutlich langsamer. 
Der k-Wert muss eine ungerade Zahl sein, um zu vermeiden, dass man im Entscheidungsprozess stecken bleibt. Das bedeutet, dass für den Abstimmungsprozess eine k-Anzahl von Nachbarn erforderlich ist, wenn dieser Wert auf 4 gesetzt wird, dann stimmen 2 Mitglieder dafür, dass der gegebene Punkt zur Kategorie A gehört, und die verbleibenden 2 stimmen dafür, dass er zur Kategorie B gehört... wie wollen Sie entscheiden, welche Seite die Entscheidung gewonnen hat?


Dieser Fall tritt am häufigsten auf, wenn es 2 Kategorien zu klassifizieren gibt. Wir werden sehen, was wir tun können, wenn solche Situationen später auftreten, wenn es eine Menge Kategorien für die k Nachbarn gibt.

In unserer Clustering-Bibliothek erstellen wir eine Funktion, um die verfügbaren Klassen aus der Matrix der Datensätze zu erhalten und sie in einem globalen Klassenvektor namens m_classesVector zu speichern

vector CKNNNearestNeighbors::ClassVector()
 {
   vector t_vectors = Matrix.Col(m_cols-1); //target variables are found on the last column in the matrix
   vector temp_t = t_vectors, v = {t_vectors[0]};
   
   for (ulong i=0, count =1; i<m_rows; i++) //counting the different neighbors
    {
      for (ulong j=0; j<m_rows; j++)
         {
            if (t_vectors[i] == temp_t[j] && temp_t[j] != -1000)
               {                   
                  bool count_ready = false;
                  
                  for(ulong n=0;n<v.Size();n++)
                      if (t_vectors[i] == v[n])
			 count_ready = true;
                    
                    if (!count_ready)
                      {
                        count++;
                        v.Resize(count);
                        
                        v[count-1] = t_vectors[i]; 
                        
                        temp_t[j] = -1000; //modify so that it can no more be counted
                      }
                    else 
			break;
                  //Print("t vectors vector ",t_vectors);
               }
             else
		 continue;
         }
    }
   
   return(v);
 } 

CKNNNearestNeighbors::CKNNNearestNeighbors(matrix<double> &Matrix_)
  {
    Matrix.Copy(Matrix_);

    k = (int)round(MathSqrt(Matrix.Rows())); 
    k = k%2 ==0 ? k+1 : k; //make sure the value of k ia an odd number    
    
    m_rows = Matrix.Rows();
    m_cols = Matrix.Cols();
    
   m_classesVector = ClassVector();
   Print("classes vector | Neighbors ",m_classesVector);
  }

Ausgabe:

2022.10.31 05:40:33.825 TestScript      classes vector | Neighbors [1,0]

Wenn Sie auf den Konstruktor geachtet haben, gibt es eine Zeile, die sicherstellt, dass der Wert von k eine ungerade Zahl ist, nachdem er standardmäßig als Quadratwurzel aus der Gesamtzahl der Zeilen im Datensatz/Anzahl der Datenpunkte generiert wurde; Es gibt einen anderen Konstruktor, der die Einstellung des Wertes von k ermöglicht, aber der Wert wird dann überprüft, um sicherzustellen, dass es sich um eine ungerade Zahl handelt; der Wert von k ist in diesem Fall 3, bei 9 Zeilen also √9 = 3 (ungerade Zahl)

CKNNNearestNeighbors:: CKNNNearestNeighbors(matrix<double> &Matrix_, uint k_)
 {
   k = k_;
   
   if (k %2 ==0)
      printf("K %d is an even number, It will be added by One so it becomes an odd Number %d",k,k=k+1);
      
   Matrix.Copy(Matrix_);
   
   m_rows = Matrix.Rows();
   m_cols = Matrix.Cols();
   
   m_classesVector = ClassVector();
   Print("classes vector | Neighbors ",m_classesVector);
 }

Um die Bibliothek zu erstellen, werden wir den unten stehenden Datensatz verwenden. Danach werden wir sehen, wie wir Handelsinformationen verwenden können, um daraus etwas im MetaTrader zu machen.

Hier sehen Sie, wie diese Daten in MetaEditor aussehen;

    matrix Matrix = 
      {//weight(kg) | height(cm) | class
         {51, 167,   1}, //underweight
         {62, 182,   0}, //Normal
         {69, 176,   0}, //Normal
         {64, 173,   0}, //Normal
         {65, 172,   0}, //Normal
         {56, 174,   1}, //Underweight
         {58, 169,   0}, //Normal
         {57, 173,   0}, //Normal
         {55, 170,   0}  //Normal
      };    

Schritt 02: Berechnen des euklidischen Abstands eines Punktes zu allen Mitgliedern des Datensatzes

Angenommen, wir wissen nicht, wie man den Body-Mass-Index berechnet, aber wir wollen wissen, wo eine Person mit einem Gewicht von 57 kg und einer Größe von 170 cm zwischen den Kategorien normal- und untergewichtig liegt.

    vector v = {57, 170};
    
    nearest_neighbors = new CKNNNearestNeighbors(Matrix); //calling the constructor and passing it the matrix
    nearest_neighbors.KNNAlgorithm(v);  //passing this new points to the algorithm

Die Funktion KNNAlgorithm ermittelt zunächst den euklidischen Abstand zwischen dem angegebenen Punkt und allen Punkten des Datensatzes.

   vector vector_2;
   vector euc_dist;
   euc_dist.Resize(m_rows);
   
   matrix temp_matrix = Matrix;
   temp_matrix.Resize(Matrix.Rows(),Matrix.Cols()-1); //remove the last column of independent variables
   
   for (ulong i=0; i<m_rows; i++)
      {
         vector_2 = temp_matrix.Row(i);
         euc_dist[i] = Euclidean_distance(vector_,vector_2);
      }      

Innerhalb der Funktion Euklidischer Abstand:

double CKNNNearestNeighbors:: Euclidean_distance(const vector &v1,const vector &v2)
 {
   double dist = 0;
   
   if (v1.Size() != v2.Size()) 
	Print(__FUNCTION__," v1 and v2 not matching in size");
   else
    {
       double c = 0;
         for (ulong i=0; i<v1.Size(); i++)
               c += MathPow(v1[i] - v2[i], 2); 

        dist = MathSqrt(c);
    } 
    
    return(dist);
 }

Ich habe den Euklidischen Abstand als Methode zur Messung des Abstands zwischen zwei Punkten in dieser Bibliothek gewählt, aber das ist nicht die einzige Methode. Sie können andere Methoden wie Rectilinear Abstand oder den Manhattan-Abstand verwenden, einige wurden in dem vorigen Artikel diskutiert.

Print("Euclidean distance vector\n",euc_dist);

Output  -----------> 

CS      0       19:29:09.057    TestScript   Euclidean distance vector
CS      0       19:29:09.057    TestScript   [6.7082,13,13.41641,7.61577,8.24621,4.12311,1.41421,3,2]

Lassen Sie uns nun den euklidischen Abstand in die letzte Spalte der Matrix eintragen;

   if (isdebug)
      {  
         matrix dbgMatrix = Matrix; //temporary debug matrix
         dbgMatrix.Resize(dbgMatrix.Rows(),dbgMatrix.Cols()+1);
         dbgMatrix.Col(euc_dist,dbgMatrix.Cols()-1);
         
         Print("Matrix w Euclidean Distance\n",dbgMatrix);
         
         ZeroMemory(dbgMatrix);
      }

Ausgabe:

CS      0       19:33:48.862    TestScript   Matrix w Euclidean Distance
CS      0       19:33:48.862    TestScript   [[51,167,1,6.7082]
CS      0       19:33:48.862    TestScript    [62,182,0,13]
CS      0       19:33:48.862    TestScript    [69,176,0,13.41641]
CS      0       19:33:48.862    TestScript    [64,173,0,7.61577]
CS      0       19:33:48.862    TestScript    [65,172,0,8.24621]
CS      0       19:33:48.862    TestScript    [56,174,1,4.12311]
CS      0       19:33:48.862    TestScript    [58,169,0,1.41421]
CS      0       19:33:48.862    TestScript    [57,173,0,3]
CS      0       19:33:48.862    TestScript    [55,170,0,2]]

Ich möchte diese Daten zur leichteren Interpretation in ein Bild übertragen:

Da der Wert von k 3 ist, fallen die 3 nächsten Nachbarn alle in die Klasse Normal, so dass wir manuell wissen, dass der gegebene Punkt in die Kategorie Normal fällt. 

Um die nächsten Nachbarn zu bestimmen und sie aufzuspüren, ist es sehr schwierig, mit Vektoren zu arbeiten, Arrays sind flexibel für die Aufteilung und Umformung, lassen Sie uns diesen Prozess mit ihnen abschließen

   int size = (int)m_target.Size();

   double tarArr[];
   ArrayResize(tarArr, size);
   double eucArray[];
   ArrayResize(eucArray, size);

   for(ulong i=0; i<m_target.Size(); i++)  //convert the vectors to array
     {
      tarArr[i] = m_target[i];
      eucArray[i] = euc_dist[i];
     }

   double track[], NN[];
   ArrayCopy(track, tarArr);


   int max; 
   for(int i=0; i<(int)m_target.Size(); i++)
     {
      if(ArraySize(track) > (int)k)
        {
         max = ArrayMaximum(eucArray);
         ArrayRemove(eucArray, max, 1);
         ArrayRemove(track, max, 1);
        }
     }
   ArrayCopy(NN, eucArray);

   Print("NN ");
   ArrayPrint(NN);
   Print("Track ");
   ArrayPrint(track);

In dem obigen Codeblock werden die nächsten Nachbarn ermittelt und in einem NN-Array gespeichert, außerdem werden ihre Klassenwerte bzw. ihre Klassenzugehörigkeit im globalen Vektor der Zielwerte verfolgt. Darüber hinaus entfernen wir die Maximalwerte aus dem Array, bis wir mit einem k-großen Array kleinerer Werte dastehen (Nächste Nachbarn).

Nachfolgend finden Sie die Ausgabe:

CS      0       05:40:33.825    TestScript    NN 
CS      0       05:40:33.825    TestScript    1.4 3.0 2.0
CS      0       05:40:33.825    TestScript    Track 
CS      0       05:40:33.825    TestScript    0.0 0.0 0.0

Abstimmungsprozess (voting):

//--- Voting process

   vector votes(m_classesVector.Size());

   for(ulong i=0; i<votes.Size(); i++)
     {
      int count = 0;
      for(ulong j=0; j<track.Size(); j++)
        {
         if(m_classesVector[i] == track[j])
            count++;
        }

      votes[i] = (double)count;

      if(votes.Sum() == k)  //all members have voted
         break;
     }
   Print("votes ", votes);

Ausgabe:

2022.10.31 05:40:33.825 TestScript   votes [0,3]

Der Stimmenvektor ordnet die Stimmen auf der Grundlage des globalen Vektors der im Datensatz verfügbaren Klassen an, erinnern Sie sich?

2022.10.31 06:43:30.095 TestScript   classes vector | Neighbors [1,0]

Dies sagt uns nun, dass von den 3 Nachbarn, die zur Stimmabgabe ausgewählt wurden, 3 dafür gestimmt haben, dass die gegebenen Daten zur Klasse der Nullen (0) gehören, und kein Mitglied hat für die Klasse der Einsen (1) gestimmt.

Schauen wir uns an, was passiert wäre, wenn 5 Nachbarn gewählt worden wären, d.h. wenn der Wert von k 5 gewesen wäre.

CS      0       06:43:30.095    TestScript   NN 
CS      0       06:43:30.095    TestScript   6.7 4.1 1.4 3.0 2.0
CS      0       06:43:30.095    TestScript   Track 
CS      0       06:43:30.095    TestScript   1.0 1.0 0.0 0.0 0.0
CS      0       06:43:30.095    TestScript   votes [2,3]

Nun ist die endgültige Entscheidung leicht zu treffen: Die Klasse mit der höchsten Stimmenzahl hat die Entscheidung gewonnen. In diesem Fall gehört das angegebene Gewicht zur normalen Klasse, die mit 0 kodiert ist.

   if(isdebug)
      Print(vector_, " belongs to class ", (int)m_classesVector[votes.ArgMax()]);

Ausgabe:

2022.10.31 06:43:30.095 TestScript      [57,170] belongs to class 0

Großartig, jetzt funktioniert alles prima. Ändern wir den Typ von KNNAlgorithm von void auf int, damit er den Wert der Klasse zurückgibt, zu der der gegebene Wert gehört, das könnte sich im Live-Handel als nützlich erweisen, da wir die neuen Werte einfügen werden, von denen wir eine sofortige Ausgabe vom Algorithmus erwarten.

   int               KNNAlgorithm(vector &vector_);

Testen des Modells und Ermittlung seiner Genauigkeit.

Nun, da wir das Modell haben, müssen wir es wie jedes andere überwachte maschinelle Lernverfahren trainieren und mit Daten testen, die es vorher noch nicht gesehen hat. Der Testprozess hilft uns zu verstehen, wie unser Modell bei verschiedenen Datensätzen abschneiden kann.

float TrainTest(double train_size=0.7)

Standardmäßig werden 70 % des Datensatzes für das Training verwendet, während die restlichen 30 % für die Tests verwendet werden.

Wir müssen den Code für die Funktion zur Aufteilung des Datensatzes für die Trainings- und Testphase erstellen:

^//--- Split the matrix
   
   matrix default_Matrix = Matrix; 
   
   int train = (int)MathCeil(m_rows*train_size),
       test  = (int)MathFloor(m_rows*(1-train_size));
   
   if (isdebug) printf("Train %d test %d",train,test);

   matrix TrainMatrix(train,m_cols), TestMatrix(test,m_cols);
   int train_index = 0, test_index =0;

//---
   
   for (ulong r=0; r<Matrix.Rows(); r++)
      {
         if ((int)r < train)
           {
             TrainMatrix.Row(Matrix.Row(r),train_index);
             train_index++;
           }
         else
           {
             TestMatrix.Row(Matrix.Row(r),test_index);
             test_index++;
           }     
      }

   if (isdebug) Print("TrainMatrix\n",TrainMatrix,"\nTestMatrix\n",TestMatrix);
   

Ausgabe:

CS      0       09:51:45.136    TestScript   TrainMatrix
CS      0       09:51:45.136    TestScript   [[51,167,1]
CS      0       09:51:45.136    TestScript    [62,182,0]
CS      0       09:51:45.136    TestScript    [69,176,0]
CS      0       09:51:45.136    TestScript    [64,173,0]
CS      0       09:51:45.136    TestScript    [65,172,0]
CS      0       09:51:45.136    TestScript    [56,174,1]
CS      0       09:51:45.136    TestScript    [58,169,0]]
CS      0       09:51:45.136    TestScript   TestMatrix
CS      0       09:51:45.136    TestScript   [[57,173,0]
CS      0       09:51:45.136    TestScript    [55,170,0]]

Das Training des des Algorithmus Nächster-Nachbar ist also sehr einfach. Man könnte meinen, dass es überhaupt kein Training gibt, denn wie bereits gesagt, versucht dieser Algorithmus nicht, die Muster im Datensatz zu verstehen, im Gegensatz zu Methoden wie der logistischen Regression oder SVM, sondern speichert die Daten nur während des Trainings, um die Daten dann für Testzwecke zu verwenden.

Training:

   Matrix.Copy(TrainMatrix); //That's it ???

Tests:

//--- Testing the Algorithm
   
   vector TestPred(TestMatrix.Rows());
   vector v_in = {};
   
   for (ulong i=0; i<TestMatrix.Rows(); i++)
     {
        v_in = TestMatrix.Row(i);
        v_in.Resize(v_in.Size()-1); //Remove independent variable
        
        TestPred[i] = KNNAlgorithm(v_in);
        
        Print("v_in ",v_in," out ",TestPred[i]);
     }   

Ausgabe:

CS      0       09:51:45.136    TestScript   v_in [57,173] out 0.0
CS      0       09:51:45.136    TestScript   v_in [55,170] out 0.0

Alle Tests wären umsonst, wenn wir nicht messen, wie genau unser Modell auf dem gegebenen Datensatz ist.

Die Konfusionsmatrix.

Dies wurde bereits im zweiten Artikel dieser Reihe erläutert.

matrix CKNNNearestNeighbors::ConfusionMatrix(vector &A,vector &P)
 {
   ulong size = m_classesVector.Size();
   matrix mat_(size,size);
   
   if (A.Size() != P.Size()) 
      Print("Cant create confusion matrix | A and P not having the same size ");
   else
     {
      
         int tn = 0,fn =0,fp =0, tp=0;
         for (ulong i = 0; i<A.Size(); i++)
            {              
               if (A[i]== P[i] && P[i]==m_classesVector[0])
                  tp++; 
               if (A[i]== P[i] && P[i]==m_classesVector[1])
                  tn++;
               if (P[i]==m_classesVector[0] && A[i]==m_classesVector[1])
                  fp++;
               if (P[i]==m_classesVector[1] && A[i]==m_classesVector[0])
                  fn++;
            }
            
       mat_[0][0] = tn; mat_[0][1] = fp;
       mat_[1][0] = fn; mat_[1][1] = tp;

    }
     
   return(mat_);    
 }

Innerhalb von TrainTest() am Ende der Funktion habe ich den folgenden Code hinzugefügt, um die Funktion abzuschließen und die Genauigkeit zurückzugeben;

   matrix cf_m = ConfusionMatrix(TargetPred,TestPred);
   vector diag = cf_m.Diag();
   float acc = (float)(diag.Sum()/cf_m.Sum())*100;
   
   Print("Confusion Matrix\n",cf_m,"\nAccuracy ------> ",acc,"%");
   
   return(acc);      

Ausgabe:

CS      0       10:34:26.681    TestScript   Confusion Matrix
CS      0       10:34:26.681    TestScript   [[2,0]
CS      0       10:34:26.681    TestScript    [0,0]]
CS      0       10:34:26.681    TestScript   Accuracy ------> 100.0%

Natürlich musste die Genauigkeit bei hundert Prozent liegen, das Modell erhielt nur zwei Datenpunkte zum Testen, die alle der Klasse Null (der Normalklasse) angehörten, was auch stimmt.

Bis zu diesem Punkt haben wir eine voll funktionsfähige Bibliothek für K-Nächste-Nachbarn. Schauen wir uns an, wie wir sie verwenden können, um den Preis verschiedener Deviseninstrumente und Aktien vorherzusagen.

Vorbereiten des Datensatzes.

Denken Sie daran, dass es sich hier um überwachtes Lernen handelt, was bedeutet, dass ein menschliches Eingreifen erforderlich ist, um die Daten zu erstellen und sie zu beschriften, damit die Modelle wissen, was ihre Ziele sind, damit sie die Beziehung zwischen unabhängigen und Zielvariablen verstehen können.

Die unabhängigen Variablen der Wahl sind die Messwerte der ATR und der Volumensindikator, während die Zielvariable auf 1 gesetzt wird, wenn der Markt gestiegen ist, und auf 0, wenn der Markt gesunken ist; dies wird dann das Kauf- bzw. Verkaufssignal beim Testen und Verwenden des Modells zum Handeln.

int OnInit()
  {
//--- Preparing the dataset 

    atr_handle = iATR(Symbol(),timeframe,period);
    volume_handle = iVolumes(Symbol(),timeframe,applied_vol);
    
    CopyBuffer(atr_handle,0,1,bars,atr_buffer);
    CopyBuffer(volume_handle,0,1,bars,volume_buffer); 
    
    Matrix.Col(atr_buffer,0); //Independent var 1
    Matrix.Col(volume_buffer,1); //Independent var 2
    
//--- Target variables

    vector Target_vector(bars);
    
    MqlRates rates[];
    ArraySetAsSeries(rates,true);
    CopyRates(Symbol(),PERIOD_D1,1,bars,rates);
    
    for (ulong i=0; i<Target_vector.Size(); i++) //putting the labels
     {
       if (rates[i].close > rates[i].open)
          Target_vector[i] = 1; //bullish
       else
          Target_vector[i] = 0;
     }

   Matrix.Col(Target_vector,2);
        
//---

Die Logik bei der Suche nach den unabhängigen Variablen besteht darin, dass wenn der Schlusskurs über dem Eröffnungskurs liegt, mit anderen Worten eine Aufwärtskerze, die Zielvariable für die unabhängigen Variablen 1 ist, andernfalls 0.

Denken Sie daran, wir arbeiten mit Tageskerzen, eine einzelne Kerze beinhaltet eine Menge von Preisbewegungen in diesen 24 Stunden, diese Logik kann keine gute sein, wenn Sie versuchen, zu scalpen oder etwas, das in kürzeren Zeiträumen handelt. Es gibt es auch einen kleinen Fehler in der Logik, weil, wenn der Schlusskurs größer ist als der Eröffnungskurs, der Zielvariablen 1 andernfalls 0 zugewiesen wird, aber könnte passieren, dass Eröffnungs- und Schlusskurs gleich sind? Ich verstehe, dass diese Situation bei höheren Zeitrahmen eher selten auftritt, also ist dies meine Art, dem Modell Raum für Fehler zu geben. 

Dies ist übrigens keine Finanz- oder Handelsberatung.

Drucken wir also die letzten 10 Werte der Kerzen, 10 Zeilen unserer Datenmatrix;

CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)   ATR,Volumes,Class Matrix
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)   [[0.01139285714285716,12295,0]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01146428571428573,12055,0]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01122142857142859,10937,0]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01130000000000002,13136,0]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01130000000000002,15305,0]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01097857142857144,13762,1]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.0109357142857143,12545,1]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01116428571428572,18806,1]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01188571428571429,19595,1]
CS      0       12:20:10.449    NearestNeighorsEA (EURUSD,D1)    [0.01137142857142859,15128,1]]


Die Daten wurden anhand ihrer jeweiligen Kerzen und Indikatoren gut klassifiziert, jetzt können wir sie an den Algorithmus weitergeben.

    nearest_neigbors = new CKNNNearestNeighbors(Matrix,k); 
    nearest_neigbors.TrainTest(); 

Ausgabe:


Wir haben eine Genauigkeit von ca. 43,33 %, was nicht schlecht ist, wenn man bedenkt, dass wir uns nicht die Mühe gemacht haben, den optimalen Wert von k zu finden. Wir sollten verschiedene Werte von k in einer Schleife ausprobieren und denjenigen auswählen, der eine bessere Genauigkeit bietet.

    for(uint i=0; i<bars; i++)
      {
        printf("<<< k %d >>>",i);
        nearest_neigbors = new CKNNNearestNeighbors(Matrix,i); 
        nearest_neigbors.TrainTest(); 
        
        delete(nearest_neigbors);
      }

Ausgabe:

......
CS      0       16:22:28.013    NearestNeighorsEA (EURUSD,D1)   <<< k 24 >>>
CS      0       16:22:28.013    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 46.66666793823242%
CS      0       16:22:28.014    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 46.66666793823242%
CS      0       16:22:28.014    NearestNeighorsEA (EURUSD,D1)   <<< k 26 >>>
CS      0       16:22:28.014    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 40.0%
CS      0       16:22:28.014    NearestNeighorsEA (EURUSD,D1)   <<< k 27 >>>
CS      0       16:22:28.015    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 40.0%
CS      0       16:22:28.015    NearestNeighorsEA (EURUSD,D1)   <<< k 28 >>>
CS      0       16:22:28.015    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.016    NearestNeighorsEA (EURUSD,D1)   <<< k 29 >>>
CS      0       16:22:28.016    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.016    NearestNeighorsEA (EURUSD,D1)   <<< k 30 >>>
.....
.....
CS      0       16:22:28.017    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 60.000003814697266%
CS      0       16:22:28.017    NearestNeighorsEA (EURUSD,D1)   <<< k 31 >>> 
CS      0       16:22:28.017    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 60.000003814697266%
CS      0       16:22:28.017    NearestNeighorsEA (EURUSD,D1)   <<< k 32 >>> 
CS      0       16:22:28.018    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.018    NearestNeighorsEA (EURUSD,D1)   <<< k 33 >>> 
CS      0       16:22:28.018    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.018    NearestNeighorsEA (EURUSD,D1)   <<< k 34 >>> 
CS      0       16:22:28.019    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 50.0%
CS      0       16:22:28.019    NearestNeighorsEA (EURUSD,D1)   <<< k 35 >>> 
CS      0       16:22:28.019    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 50.0%
CS      0       16:22:28.019    NearestNeighorsEA (EURUSD,D1)   <<< k 36 >>> 
CS      0       16:22:28.020    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 53.333335876464844%
CS      0       16:22:28.020    NearestNeighorsEA (EURUSD,D1)   <<< k 37 >>> 
CS      0       16:22:28.020    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 53.333335876464844%
CS      0       16:22:28.020    NearestNeighorsEA (EURUSD,D1)   <<< k 38 >>> 
CS      0       16:22:28.021    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.021    NearestNeighorsEA (EURUSD,D1)   <<< k 39 >>> 
CS      0       16:22:28.021    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.021    NearestNeighorsEA (EURUSD,D1)   <<< k 40 >>> 
CS      0       16:22:28.022    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.022    NearestNeighorsEA (EURUSD,D1)   <<< k 41 >>>
.....
....
CS      0       16:22:28.023    NearestNeighorsEA (EURUSD,D1)   <<< k 42 >>>
CS      0       16:22:28.023    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 63.33333206176758%
CS      0       16:22:28.023    NearestNeighorsEA (EURUSD,D1)   <<< k 43 >>>
CS      0       16:22:28.024    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 63.33333206176758%
CS      0       16:22:28.024    NearestNeighorsEA (EURUSD,D1)   <<< k 44 >>>
CS      0       16:22:28.024    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 66.66667175292969%
CS      0       16:22:28.024    NearestNeighorsEA (EURUSD,D1)   <<< k 45 >>>
CS      0       16:22:28.025    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 66.66667175292969%
CS      0       16:22:28.025    NearestNeighorsEA (EURUSD,D1)   <<< k 46 >>>
CS      0       16:22:28.025    NearestNeighorsEA (EURUSD,D1)   Accuracy ------> 56.66666793823242%
CS      0       16:22:28.025    NearestNeighorsEA (EURUSD,D1)   <<< k 47 >>>
....
....

Auch wenn diese Methode zur Bestimmung des Wertes von k nicht die beste ist, kann man mit der Loose-One-Out-Kreuzvalidierungsmethode die optimalen Werte von k finden. Es scheint, dass die Spitzenleistung erreicht wurde, als der Wert von k in den Vierzigern lag. Jetzt ist es an der Zeit, den Algorithmus in der Handelsumgebung einzusetzen.

void OnTick()
  {
  
    vector x_vars(2); //vector to store atr and volumes values
    double atr_val[], volume_val[];
    
    CopyBuffer(atr_handle,0,0,1,atr_val);
    CopyBuffer(volume_handle,0,0,1,volume_val);
    
    x_vars[0] = atr_val[0]; 
    x_vars[1] = volume_val[0];
    
//---
    int signal = 0;
    
    double volume = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
    
    MqlTick ticks;
    SymbolInfoTick(Symbol(),ticks);
    
    double ask = ticks.ask, bid = ticks.bid;
    
    
      if (isNewBar() == true) //we are on the new candle
         {
            signal = nearest_neigbors.KNNAlgorithm(x_vars); //Calling the algorithm
            
            if (signal == 1)
              {
                 if (!CheckPosionType(POSITION_TYPE_BUY))
                  {
                    m_trade.Buy(volume,Symbol(),ask,0,0);
                    if (ClosePosType(POSITION_TYPE_SELL))
                      printf("Failed to close %s Err = %d",EnumToString(POSITION_TYPE_SELL),GetLastError());
                  }
              }
            else 
              {
                if (!CheckPosionType(POSITION_TYPE_SELL))
                  {
                    m_trade.Sell(volume,Symbol(),bid,0,0);
                    if (ClosePosType(POSITION_TYPE_BUY))
                      printf("Failed to close %s Err = %d",EnumToString(POSITION_TYPE_BUY),GetLastError());
                  }
              }
         }
     
  }
  

Nun, da unser EA in der Lage ist, Positionen zu öffnen und zu schließen, wollen wir es auf dem Strategie-Tester versuchen, aber vorher, Dies ist ein Überblick über den Aufruf des Algorithmus im gesamten Expert Advisor;

#include "KNN_nearest_neighbors.mqh";
CKNNNearestNeighbors *nearest_neigbors;

matrix Matrix;
//+------------------------------------------------------------------+
//| Expert initialization function                                     |
//+------------------------------------------------------------------+
int OnInit()
  { 
// gathering data to Matrix has been ignored
    
     nearest_neigbors = new CKNNNearestNeighbors(Matrix,_k); 
 
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

    delete(nearest_neigbors);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+

void OnTick()
  {
  
    vector x_vars(2); //vector to store atr and volumes values

//adding live indicator values from the market has been ignored 

//---
    int signal = 0;

      if (isNewBar() == true) //we are on the new candle
         {
            signal = nearest_neigbors.KNNAlgorithm(x_vars);
            //trading actions
         }
}

Strategietester auf EURUSD: für 2022.06.01 bis 2022.11.03 (Jeder Tick):



Wann sollte KNN verwendet werden?

Es ist sehr wichtig zu wissen, wo dieser Algorithmus eingesetzt werden kann, da nicht jedes Problem mit ihm gelöst werden kann, genau wie jede andere maschinelle Lerntechnik.

  • Wenn der Datensatz gekennzeichnet ist.
  • Wenn der Datensatz rauschfrei ist.
  • Wenn der Datensatz klein ist (dies ist auch aus Leistungsgründen hilfreich)

Vorteile:

  • Es ist sehr einfach zu verstehen und umzusetzen 
  • Es basiert auf lokalen Datenpunkten, was bei Datensätzen mit vielen Gruppen mit lokalen Clustern von Vorteil sein kann.

Nachteile

Alle Trainingsdaten werden jedes Mal verwendet, wenn wir etwas vorhersagen müssen. Das bedeutet, dass alle Daten gespeichert werden müssen und jedes Mal verwendet werden können, wenn es einen neuen Punkt zu klassifizieren gibt.

Abschließende Überlegungen

Wie gesagt, dieser Algorithmus ist ein guter Klassifikator, aber nicht auf einem komplexen Datensatz, sodass ich denke, es könnte bessere Prognosen für Aktien und Indizes geben, aber ich überlasse es Ihnen, dass selbst zu erforschen. Eine Sache, die Sie sehen, wenn Sie diesen Algorithmus in einem Expert Advisor testen, ist, dass er Leistungsprobleme auf der Strategie-Tester verursacht, obwohl nur 50 Bars ausgewählt wurden und den Roboter nur bei einer neuen Bar agiert. Der Tester würde auf jeder Kerze für wie 20 bis 30 Sekunden stecken bleiben, nur um den Algorithmus den gesamten Prozess laufen zu lassen, obwohl der Prozess beim Live-Handel schneller ist, ist es das genaue Gegenteil im Tester, Es gibt immer Raum für Verbesserungen, vor allem unter den folgenden Zeilen des Codes, weil ich die Indikatorwerte in der Funktion OnInit() abrufen konnte, so musste ich sie abrufen, trainieren und verwenden, um den Markt vorherzusagen, alles an einem Ort.

        if (isNewBar() == true) //we are on the new candle
         { 
           if (MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION)) 
             {
               gather_data();      
               nearest_neigbors = new CKNNNearestNeighbors(Matrix,_k); 
               signal = nearest_neigbors.KNNAlgorithm(x_vars);
               
               delete(nearest_neigbors);
             }      

Danke fürs Lesen.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/11678

Beigefügte Dateien |
Neuronale Netze leicht gemacht (Teil 28): Gradientbasierte Optimierung Neuronale Netze leicht gemacht (Teil 28): Gradientbasierte Optimierung
Wir studieren weiterhin das Verstärkungslernen, das Reinforcement Learning. Im vorigen Artikel haben wir die Methode des Deep Q-Learning kennengelernt. Bei dieser Methode wird das Modell so trainiert, dass es die bevorstehende Belohnung in Abhängigkeit von der in einer bestimmten Situation durchgeführten Aktion vorhersagt. Dann wird eine Aktion entsprechend der Strategie und der erwarteten Belohnung durchgeführt. Es ist jedoch nicht immer möglich, die Q-Funktion zu approximieren. Manchmal führt die Annäherung nicht zu dem gewünschten Ergebnis. In solchen Fällen werden Näherungsmethoden nicht auf Nutzenfunktionen, sondern auf eine direkte Handlungspolitik (Strategie) angewendet. Eine dieser Methoden ist die Gradientbasierte Optimierung, engl. „Policy Gradient“.
Algorithmen zur Populationsoptimierung Partikelschwarm (PSO) Algorithmen zur Populationsoptimierung Partikelschwarm (PSO)
In diesem Artikel werde ich den beliebten Algorithmus der Partikelschwarm-Optimierung (PSO) besprechen. Zuvor haben wir wichtige Eigenschaften von Optimierungsalgorithmen wie Konvergenz, Konvergenzrate, Stabilität und Skalierbarkeit erörtert, einen Prüfstand entwickelt und den einfachsten RNG-Algorithmus betrachtet.
DoEasy. Steuerung (Teil 21): SplitContainer-Steuerung. Paneel-Trennlinie DoEasy. Steuerung (Teil 21): SplitContainer-Steuerung. Paneel-Trennlinie
In diesem Artikel werde ich die Klasse eines Hilfstrennlinie für das Paneelobjekt des Steuerelements des SplitContainers erstellen.
Lernen Sie, wie man ein Handelssystem mit den Fraktalen entwickelt Lernen Sie, wie man ein Handelssystem mit den Fraktalen entwickelt
Dieser Artikel ist ein neuer Teil unserer Serie über die Entwicklung eines Handelssystems auf der Grundlage der beliebtesten technischen Indikatoren. Wir werden einen neuen Indikator kennenlernen, den Fraktal-Indikator oder Fractals, und wir werden lernen, wie man ein darauf basierendes Handelssystem entwickelt, das im MetaTrader 5 Terminal ausgeführt werden kann.