English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Verwendung von Indikatoren in MetaTrader 5 mit dem Machine Learning Framework ENCOG für die Prognostizierung von Zeitreihen

Verwendung von Indikatoren in MetaTrader 5 mit dem Machine Learning Framework ENCOG für die Prognostizierung von Zeitreihen

MetaTrader 5Beispiele | 7 April 2016, 10:35
2 332 0
investeo
investeo

Einleitung

In diesem Beitrag wird MetaTrader 5 mit ENCOG vertraut gemacht, einem von Heaton Research entwickelten erweiterten neuronalen Netzwerk und Machine Learning Framework. Es existieren bereits beschriebene Methoden, von denen ich weiß, die es MetaTrader ermöglichen, mit Machine-Learning-Techniken zu arbeiten: FANN, NeuroSolutions, Matlab und NeuroShell. Ich hoffe, dass ENCOG eine gute ergänzende Lösung sein wird, da es ein robuster und gut konzipierter Code ist.

Warum habe ich mich für ENCOG entschieden? Es gibt mehrere Gründe.

  1. ENCOG wird in zwei weiteren Softwarepaketen für kommerziellen Handel verwendet. Eines basiert auf C#, das zweite auf Java. Das bedeutet, das ENCOG bereits für die Prognostizierung von Daten von finanziellen Zeitreihen getestet wurde. 
  2. ENCOG ist kostenlos und Open Source. Wenn Sie sehen möchten, was im Inneren eines neuronalen Netzwerks geschieht, können Sie sich den Quellcode ansehen. Genau das habe ich getan, um bestimmte Teile der Aufgabe der Prognostizierung von Zeitreihen zu verstehen. C# ist eine saubere und leicht verständliche Programmiersprache.
  3. ENCOG ist sehr gut dokumentiert. Mr. Heaton, der Gründer von Heaton Research, bietet einen kostenlosen Online-Kurs zu neuronalen Netzwerken, maschinellem Lernen und der Verwendung von ENCOG zum Prognostizieren zukünftiger Daten an. Vor dem Verfassen dieses Beitrags habe ich mir viele seiner Lektionen angesehen. Sie haben mir sehr geholfen, künstliche neuronale Netzwerke zu verstehen. Zusätzlich stehen auf der Webseite von Heaton Research E-Books zur Programmierung von ENCOG in Java und C# zur Verfügung. Die vollständige ENCOG-Dokumentation ist online verfügbar.
  4. ENCOG ist kein totes Projekt. Während ich diesen Beitrag verfasse, wird ENCOG 2.6 noch entwickelt. Eine Roadmap für ENCOG 3.0 wurde kürzlich veröffentlicht.
  5. ENCOG ist robust. Es ist gut konzipiert und kann mehrere Prozessorkerne und Multithreading nutzen, um die Berechnungen eines neuronalen Netzwerks zu beschleunigen. Teile des Codes werden bereits nach OpenCL für GPU-fähige Berechnungen geportet.
  6. ENCOG unterstützt derzeit die folgenden Funktionen: 

Methoden für maschinelles Lernen

Neuronale Netzwerkarchitekturen

EinlerntechnikenAktivierungsfunktionenRandomisierungstechniken
  • Range Randomization
  • Gaussian Random Numbers
  • Fan-In
  • Nguyen-Widrow

Geplante Funktionen:

Sie sehen, dass die Liste der Funktionen ziemlich lang ist.

Dieser einführende Beitrag konzentriert sich auf die Feedforward-Architektur von neuronalen Netzwerken mit Einlernung durch Resilient Propagation (RPROP). Er behandelt auch die Grundlagen der Datenvorbereitung – Timeboxing und Normalisierung für die zeitliche Prognostizierung von Zeitreihen.

Das Wissen, das es mir möglich gemacht hat, diesen Beitrag zu verfassen, basiert auf Tutorials von der Webseite von Heaton Research und unlängst veröffentlichten Beiträgen zur Prognostizierung von finanziellen Zeitserien in NinjaTrader. Bitte beachten Sie, dass ENCOG auf Java und C# basiert. Dieser Beitrag könnte ohne meine vorhergehende Arbeit nicht verfasst werden: Offenlegen von C#-Code in MQL5 mithilfe nicht gemanagter Exporte. Diese Lösung ermöglichte die Nutzung von C#-DLLs als Brücke zwischen einem Indikator in MetaTrader 5 und der Zeitreihenprognostizierung von ENCOG.



1. Nutzung von Werten technischer Indikatoren als Eingabeparameter eines neuronalen Netzwerks

Das künstliche neuronale Netzwerk ist ein vom Menschen erschaffener Algorithmus, der versucht, das Neuronennetzwerk eines Gehirns zu emulieren.

Es sind unterschiedliche Typen von neuronalen Algorithmen verfügbar und es existiert eine Vielzahl von Architekturen für neuronale Netzwerke. Dieser Forschungsbereich ist so breit gefächert, dass es ganze Bücher gibt, die sich einem einzigen Typ von neuronalem Netzwerk widmen. Da solche Details die Grenzen dieses Beitrags sprengen würden, kann ich Ihnen nur empfehlen, die Tutorials von Heaton Research durchzugehen oder ein Buch zu diesem Thema zu lesen. Ich werde mich auf die Ein- und Ausgaben des neuronalen Feedforward-Netzwerks konzentrieren und versuchen, praktische Beispiele für die Prognostizierung von finanziellen Zeitreihen zu beschreiben.

Um mit dem Prognostizieren von finanziellen Zeitreihen zu beginnen, müssen wir uns überlegen, was wir einem neuronalen Netzwerk zur Verfügung stellen sollten und welche Ergebnisse wir erwarten können. Abstrahiert betrachtet, erzielen wir einen Gewinn oder Verlust, indem wir lange oder kurze Positionen für den Kontrakt eines bestimmten Wertpapiers eröffnen und das Geschäft nach bestimmter Zeit abschließen.

Durch die Beobachtung vergangener Preise eines Wertpapiers und der Werte der technischen Indikatoren versuchen wir, die zukünftige Stimmung oder Richtung der Preise zu prognostizieren, um einen Kontrakt zu kaufen oder zu verkaufen und sicherzustellen, dass unsere Entscheidung nicht auf einem Münzwurf basiert. Die Situation sieht in etwa so aus:

Abbildung 1. Prognose von finanziellen Zeitreihen mithilfe von technischen Indikatoren

Abbildung 1. Prognose von finanziellen Zeitreihen mithilfe von technischen Indikatoren 

Das Gleiche versuchen wir durch künstliche Intelligenz zu erreichen. Das neuronale Netzwerk versucht, Indikatorwerte zu erkennen, und bewertet die Chance, dass der Preis steigen oder sinken wird. Wie schaffen wir das? Da wir finanzielle Zeitreihen mithilfe der Feedforward-Architektur für neuronale Netzwerke prognostizieren werden, glaube ich, dass wir uns mit dieser Architektur vertraut machen müssen.

Das neuronale Feedforward-Netzwerk besteht aus Neuronen, die in Schichten gruppiert sind. Es muss mindestens 2 Schichten geben: eine Eingabeschicht, die Eingabeneuronen enthält, und eine Ausgabeschicht, die Ausgabeneuronen enthält. Es kann auch versteckte Schichten geben, die sich zwischen der Eingabe- und Ausgabeschicht befinden. Die Eingabeschicht können Sie sich einfach als Array von double-Werten vorstellen. Die Ausgabeschicht kann aus einem oder mehreren Neuronen bestehen, die ebenfalls ein Array aus double-Werten bilden. Sehen wir uns eine Abbildung an:

 Abbildung 2. Schichten eines neuronalen Feedforward-Netzwerks

Abbildung 2. Schichten eines neuronalen Feedforward-Netzwerks 

Um die Zeichnung zu vereinfachen, wurden die Verbindungen zwischen den Neuronen nicht eingezeichnet. Jedes Neuron aus der Eingabeschicht ist mit einem Neuron in der verborgenen Schicht verbunden. Jedes Neuron aus der verborgenen Schicht ist mit einem Neuron in der Ausgabeschicht verbunden.

Jede Verbindung hat ein gewisses Gesicht, das ebenfalls einen double-Wert und eine Aktivierungsfunktion mit einem Schwellenwert darstellt, der für die Aktivierung eines Neurons und die Übergabe der Informationen an das nächste Neuron zuständig ist. Deshalb wird diese Art von Netzwerk als 'Feedforward'-Netzwerk bezeichnet – Informationen auf Basis der Ausgabe aktivierter Neuronen werden von einer Neuronenschicht zur anderen vorgeschoben (engl. "fed forward"). Detaillierte Einführungsvideos zu neuronalen Feedforward-Netzwerken finden Sie unter den folgenden Links [Anm. d. Übers.: Zum Zeitpunkt der Übersetzung sind diese Seiten nicht erreichbar]:

Nachdem Sie mehr über die Architektur des neuronalen Netzwerks und dessen Mechanismen erfahren haben, sind sie womöglich immer noch verwirrt.

Die Hauptprobleme sind:

  1. Welche Daten sollen wir an ein neuronales Netzwerk übergeben?
  2. Wie übergeben wir sie?
  3. Wie werden Eingabedaten für ein neuronales Netzwerk vorbereitet? 
  4. Wie wird eine Architektur für ein neuronales Netzwerk ausgewählt? Wie viele Eingabeneuronen, verborgene Neuronen und Ausgabeneuronen brauchen wir?
  5. Wie wird das Netzwerk eingelernt?
  6. Welches Ergebnis erwarten wird?

 



2. Welche Daten an ein neuronales Netzwerk übergeben werden müssen

Da wir finanzielle Prognosen auf Basis von Indikatorausgaben behandeln, sollten wir die Ausgabewerte des Indikators an das Netzwerk übergeben. Für diesen Beitrag habe ich als Eingabeparameter Stochastic %K, Stochastic Slow %D und Williams %R gewählt.

Abbildung 3. Für die Prognose verwendete technische Indikatoren

Abbildung 3. Für die Prognose verwendete technische Indikatoren

Zum Extrahieren der Werte der Indikatoren können wir die MQL5-Funktionen iStochastic und iWPR nutzen:

double StochKArr[], StochDArr[], WilliamsRArr[];

ArraySetAsSeries(StochKArr, true);   
ArraySetAsSeries(StochDArr, true);   
ArraySetAsSeries(WilliamsRArr, true);

int hStochastic = iStochastic(Symbol(), Period(), 8, 5, 5, MODE_EMA, STO_LOWHIGH);
int hWilliamsR = iWPR(Symbol(), Period(), 21);
   
CopyBuffer(hStochastic, 0, 0, bufSize, StochKArr);
CopyBuffer(hStochastic, 1, 0, bufSize, StochDArr);
CopyBuffer(hWilliamsR, 0, 0, bufSize, WilliamsRArr);

Nach der Ausführung dieses Codes müssen die drei Arrays StochKArr, StochDArr und WilliamsRArr mit Ausgabewerten von Indikatoren befüllt werden. Je nach Größe der für das Einlernen genutzten Stichprobe können dies mehrere tausend Werte sein. Bitte denken Sie daran, dass diese zwei Indikatoren nur zu Lernzwecken gewählt wurden.

Sie können gerne mit jedem beliebigen Indikator experimentieren, den Sie als für die Prognostizierung geeignet betrachten. Sie können Gold- und Ölpreise an das Netzwerk übergeben, um Aktienindizes zu prognostizieren, oder Sie können korrelierende Forex-Paare nutzen, um ein anderes Währungspaar zu prognostizieren. 

 



3. Timeboxing-Eingabedaten

Nachdem wir Eingabedaten von mehreren Indikatoren gesammelt haben, müssen wir die Eingabe vorbereiten ('timeboxen'), bevor wir sie an das neuronale Netzwerk übergeben. Timeboxing ist eine Technik, die die Darstellung von Eingabeparametern für das Netzwerk als bewegliche Datenstücke ermöglicht. Stellen Sie sich einen Wagen voller Eingabedaten vor, der sich auf der Zeitachse vorwärts bewegt. Im Wesentlichen besteht dieser Vorgang aus zwei Schritten:

1. Sammeln von Eingabedaten aus jedem Indikatorpuffer. Wir müssen INPUT_WINDOW Elemente von der Startposition zur Zukunft hin kopieren. INPUT_WINDOW steht für die Anzahl der Balken, die für die Prognose verwendet werden. 

 Abbildung 4. Sammeln von Eingabefenster-Daten aus dem Indikatorpuffer

Abbildung 4. Sammeln von Eingabefenster-Daten aus dem Indikatorpuffer 

Wie Sie im oben aufgeführten Beispiel sehen können, entspricht INPUT_WINDOW 4 Balken, und wir haben Elemente in das Array I1 kopiert. I1[0] ist das erste Element, I1[3] das letzte. Genauso müssen Daten aus anderen Indikatoren in Arrays der Größe INPUT_WINDOW kopiert werden. Diese Abbildung gilt für Zeitreihen-Arrays mit AS_SERIES-Flag auf true. 

2. Kombinieren von INPUT_WINDOW Arrays in einem Array, das an die Eingabeschicht des neuronalen Netzwerks übergeben wird. 

Abbildung 5 Timeboxed input window arrays

Abbildung 5. Timeboxed input window arrays

Wir verfügen über 3 Indikatoren. Zuerst nehmen wir den ersten Wert jedes Indikators, dann den zweiten und fahren so fort, bis das Eingabefenster befüllt ist, wie in der oberen Abbildung dargestellt. Ein solches kombiniertes Array aus Indikatorausgaben kann an die Eingabeschicht unseres neuronalen Netzwerks übergeben werden. Wenn ein neuer Balken erscheint, verschieben sich die Daten um ein Element und das gesamte Verfahren wird wiederholt. Wenn Sie einen detaillierteren Einblick in die Vorbereitung von Daten für die Prognostizierung erhalten möchten, können Sie sich zu diesem Thema ein Video ansehen.

 

4. Normalisieren von Eingabedaten

Damit das neuronale Netzwerk effektiv arbeiten kann, müssen wir die Daten normalisieren. Dies ist für die korrekte Berechnung der Aktivierungsfunktionen erforderlich. Die Normalisierung ist ein mathematischer Prozess, der Daten in den Bereich 0..1 oder -1..1 konvertiert. Normalisierte Daten können denormalisiert, das heißt, in ihren ursprünglichen Bereich zurückkonvertiert werden.

Die Denormalisierung ist erforderlich, um die Ausgabe des neuronalen Netzwerks in eine menschlich lesbare Form zu dekodieren. Glücklicherweise übernimmt ENCOG die Normalisierung und Denormalisierung, deshalb müssen wir sie nicht implementieren. Wenn Sie wissen möchten, wie es funktioniert, können Sie den folgenden Code analysieren:

/**
         * Normalize the specified value.
         * @param value The value to normalize.
         * @return The normalized value.
         */
        public static double normalize(final int value) {
                return ((value - INPUT_LOW) 
                                / (INPUT_HIGH - INPUT_LOW))
                                * (OUTPUT_HIGH - OUTPUT_LOW) + OUTPUT_LOW;
        }
        
        /**
         * De-normalize the specified value.
         * @param value The value to denormalize.
         * @return The denormalized value.
         */
        public static double deNormalize(final double data) {
                double result = ((INPUT_LOW - INPUT_HIGH) * data - OUTPUT_HIGH
                                * INPUT_LOW + INPUT_HIGH * OUTPUT_LOW)
                                / (OUTPUT_LOW - OUTPUT_HIGH);
                return result;
        }

und für weitere Details einen Artikel zur Normalisierung lesen. 

 



5. Auswahl der Netzwerkarchitektur und der Anzahl von Neuronen

Für einen Neueinsteiger ist die Auswahl der korrekten Netzwerkarchitektur schwierig. In diesem Beitrag grenze ich die Architektur des neuronalen Feedforward-Netzwerks auf drei Schichten ein: eine Eingabeschicht, eine verborgene Schicht und eine Ausgabeschicht. Sie können gerne mit mehr Schichten experimentieren.

Für die Ein- und Ausgabeschicht können wir die erforderliche Anzahl von Neuronen genau zählen. Für die verborgene Schicht versuchen wir, den Fehler des neuronalen Netzwerks mithilfe eines "forward selection"-Algorithmus zu verringern. Sie können gerne andere Methoden nutzen. Möglicherweise gibt es genetische Algorithmen für die Berechnung der Anzahl von Neuronen.

Eine weitere von ENCOG genutzte Methode ist der Algorithmus backward selection oder Pruning. Im Wesentlichen bewertet sie die Verbindungen zwischen den Schichten und entfernt verborgene Neuronen mit null gewichteten Verbindungen. Auch diese Methode können Sie gerne ausprobieren.

5,1. Eingabe-Neuronenschicht

Aufgrund des Timeboxings sollte die Anzahl der Neuronen in der Eingabeschicht der Anzahl von Indikatoren mal der Anzahl der für die Prognostizierung des nächsten Balkens genutzten Balken entsprechen. Wenn wir 3 Indikatoren als Eingaben nutzen und die Größe des Eingabefensters 6 Balken beträgt, wird die Eingabeschicht aus 18 Neuronen bestehen. An die Eingabeschicht werden die per Timeboxing vorbereiteten Daten übergeben.

5,2. Verborgene Neuronenschicht

Die Anzahl der verborgenen Netzwerke muss auf Basis der Performance des eingelernten neuronalen Netzwerks geschätzt werden. Für die Anzahl der verborgenen Neuronen gibt es keine geradlinige mathematische Gleichung. Vor dem Verfassen dieses Beitrags habe ich es mit mehreren Trial-and-Error-Ansätzen versucht und auf der Webseite von Heaton Research einen Algorithmus gefunden, der zum Verständnis des forward-selection-Algorithmus beiträgt:

Abbildung 6 Forward-selection-Algorithmus für die Anzahl der verborgenen Neuronen

Abbildung 6. Forward-selection-Algorithmus für die Anzahl der verborgenen Neuronen 

5,3. Ausgabe-Neuronenschicht

Für unsere Zwecke ist die Anzahl der Ausgabeneuronen gleich der Anzahl der Balken, die wir prognostizieren möchten. Bitte denken Sie daran, dass es mit zunehmender Anzahl verborgener und Ausgabeneuronen länger dauert, das Netzwerk einzulernen. In diesem Beitrag versuche ich, einen Balken in der Zukunft zu prognostizieren, deshalb besteht die Ausgabeschicht aus einem Neuron.

 



6. Exportieren von Einlerndaten aus MetaTrader 5 nach ENCOG

Encog akzeptiert eine CSV-Datei für das Einlernen des neuronalen Netzwerks.

Ich habe das Dateiformat angesehen, das aus anderer Handelssoftware nach ENCOG exportiert wird, und ein MQL5-Script implementiert, das für das Einlernen das gleiche Dateiformat erstellt. Ich lege zuerst den Export von einem Indikator dar und fahre später mit mehreren Indikatoren fort. 

Die erste Datenzeile ist eine kommagetrennte Kopfzeile:

DATE,TIME,CLOSE,Indicator_Name1,Indicator_Name2,Indicator_Name3

Die ersten drei Spalten beinhalten Datum, Zeit und Schließungswerte, die nächsten Spalten beinhalten die Indikatornamen. Die nächsten Zeilen der Einlerndatei enthalten kommagetrennte Daten. Indikatorwerte müssen im wissenschaftlichen Format geschrieben sein:  

20110103,0000,0.93377000,-7.8970208860e-002

Nachfolgend sehen Sie das vorbereitete Script für einen Indikator.

//+------------------------------------------------------------------+
//|                                                ExportToEncog.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

// Export Indicator values for NN training by ENCOG
extern string IndExportFileName = "mt5export.csv";
extern int  trainSize = 400;
extern int  maPeriod = 210;

MqlRates srcArr[];
double expBullsArr[];

void OnStart()
  {
//---
   ArraySetAsSeries(srcArr, true);   
   ArraySetAsSeries(expBullsArr, true);      
         
   int copied = CopyRates(Symbol(), Period(), 0, trainSize, srcArr);
   
   if (copied!=trainSize) { Print("Not enough data for " + Symbol()); return; }
   
   int hBullsPower = iBullsPower(Symbol(), Period(), maPeriod);
   
   CopyBuffer(hBullsPower, 0, 0, trainSize, expBullsArr);
   
   int hFile = FileOpen(IndExportFileName, FILE_CSV | FILE_ANSI | FILE_WRITE | FILE_REWRITE, ",", CP_ACP);
   
   FileWriteString(hFile, "DATE,TIME,CLOSE,BullsPower\n");
   
   Print("Exporting indicator data to " + IndExportFileName);
   
   for (int i=trainSize-1; i>=0; i--)
      {
         string candleDate = TimeToString(srcArr[i].time, TIME_DATE);
         StringReplace(candleDate,".","");
         string candleTime = TimeToString(srcArr[i].time, TIME_MINUTES);
         StringReplace(candleTime,":","");
         FileWrite(hFile, candleDate, candleTime, DoubleToString(srcArr[i].close), DoubleToString(expBullsArr[i], -10));
      }
      
   FileClose(hFile);   
     
   Print("Indicator data exported."); 
  }
//+------------------------------------------------------------------+

 Die resultierende Datei, die für Einlernzwecke verwendet werden kann, sollte folgendermaßen aussehen: 

DATE,TIME,CLOSE,BullsPower
20110103,0000,0.93377000,-7.8970208860e-002
20110104,0000,0.94780000,-6.4962292188e-002
20110105,0000,0.96571000,-4.7640374727e-002
20110106,0000,0.96527000,-4.4878854587e-002
20110107,0000,0.96697000,-4.6178012364e-002
20110110,0000,0.96772000,-4.2078647318e-002
20110111,0000,0.97359000,-3.6029181466e-002
20110112,0000,0.96645000,-3.8335729509e-002
20110113,0000,0.96416000,-3.7054869514e-002
20110114,0000,0.96320000,-4.4259373120e-002
20110117,0000,0.96503000,-4.4835729773e-002
20110118,0000,0.96340000,-4.6420936126e-002
20110119,0000,0.95585000,-4.6868984125e-002
20110120,0000,0.96723000,-4.2709941621e-002
20110121,0000,0.95810000,-4.1918330800e-002
20110124,0000,0.94873000,-4.7722659418e-002
20110125,0000,0.94230000,-5.7111591557e-002
20110126,0000,0.94282000,-6.2231529077e-002
20110127,0000,0.94603000,-5.9997865295e-002
20110128,0000,0.94165000,-6.0378312069e-002
20110131,0000,0.94414000,-6.2038328069e-002
20110201,0000,0.93531000,-6.0710334438e-002
20110202,0000,0.94034000,-6.1446445012e-002
20110203,0000,0.94586000,-5.2580791504e-002
20110204,0000,0.95496000,-4.5246755566e-002
20110207,0000,0.95730000,-4.4439392954e-002

Wenn wir zum ursprünglichen Beispiel des Beitrags mit den Indikatoren Stochastic und Williams' R zurückblicken, müssen wir drei kommagetrennte Spalten exportieren. Jede Spalte beinhaltet separate Indikatorwerte, deshalb müssen wir die Datei erweitern und zusätzliche Puffer hinzufügen:

//+------------------------------------------------------------------+
//|                                                ExportToEncog.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

// Export Indicator values for NN training by ENCOG
extern string IndExportFileName = "mt5export.csv";
extern int  trainSize = 2000;

MqlRates srcArr[];
double StochKArr[], StochDArr[], WilliamsRArr[];

void OnStart()
  {
//---
   ArraySetAsSeries(srcArr, true);   
   ArraySetAsSeries(StochKArr, true);   
   ArraySetAsSeries(StochDArr, true);   
   ArraySetAsSeries(WilliamsRArr, true);
         
   int copied = CopyRates(Symbol(), Period(), 0, trainSize, srcArr);
   
   if (copied!=trainSize) { Print("Not enough data for " + Symbol()); return; }
   
   int hStochastic = iStochastic(Symbol(), Period(), 8, 5, 5, MODE_EMA, STO_LOWHIGH);
   int hWilliamsR = iWPR(Symbol(), Period(), 21);
   
   
   CopyBuffer(hStochastic, 0, 0, trainSize, StochKArr);
   CopyBuffer(hStochastic, 1, 0, trainSize, StochDArr);
   CopyBuffer(hWilliamsR, 0, 0, trainSize, WilliamsRArr);
    
   int hFile = FileOpen(IndExportFileName, FILE_CSV | FILE_ANSI | FILE_WRITE | FILE_REWRITE, ",", CP_ACP);
   
   FileWriteString(hFile, "DATE,TIME,CLOSE,StochK,StochD,WilliamsR\n");
   
   Print("Exporting indicator data to " + IndExportFileName);
   
   for (int i=trainSize-1; i>=0; i--)
      {
         string candleDate = TimeToString(srcArr[i].time, TIME_DATE);
         StringReplace(candleDate,".","");
         string candleTime = TimeToString(srcArr[i].time, TIME_MINUTES);
         StringReplace(candleTime,":","");
         FileWrite(hFile, candleDate, candleTime, DoubleToString(srcArr[i].close), 
                                                 DoubleToString(StochKArr[i], -10),
                                                 DoubleToString(StochDArr[i], -10),
                                                 DoubleToString(WilliamsRArr[i], -10)
                                                 );
      }
      
   FileClose(hFile);   
     
   Print("Indicator data exported."); 
  }
//+------------------------------------------------------------------+

Die resultierende Datei muss alle Indikatorwerte enthalten:

DATE,TIME,CLOSE,StochK,StochD,WilliamsR
20030707,0000,1.37370000,7.1743119266e+001,7.2390220187e+001,-6.2189054726e-001
20030708,0000,1.36870000,7.5140977444e+001,7.3307139273e+001,-1.2500000000e+001
20030709,0000,1.35990000,7.3831775701e+001,7.3482018082e+001,-2.2780373832e+001
20030710,0000,1.36100000,7.1421933086e+001,7.2795323083e+001,-2.1495327103e+001
20030711,0000,1.37600000,7.5398313027e+001,7.3662986398e+001,-3.9719626168e+000
20030714,0000,1.37370000,7.0955352856e+001,7.2760441884e+001,-9.6153846154e+000
20030715,0000,1.38560000,7.4975891996e+001,7.3498925255e+001,-2.3890784983e+000
20030716,0000,1.37530000,7.5354107649e+001,7.4117319386e+001,-2.2322435175e+001
20030717,0000,1.36960000,7.1775345074e+001,7.3336661282e+001,-3.0429594272e+001
20030718,0000,1.36280000,5.8474576271e+001,6.8382632945e+001,-3.9778325123e+001
20030721,0000,1.35400000,4.3498596819e+001,6.0087954237e+001,-5.4946524064e+001
20030722,0000,1.36130000,2.9036761284e+001,4.9737556586e+001,-4.5187165775e+001
20030723,0000,1.34640000,1.6979405034e+001,3.8818172735e+001,-6.5989159892e+001
20030724,0000,1.34680000,1.0634573304e+001,2.9423639592e+001,-7.1555555556e+001
20030725,0000,1.34400000,9.0909090909e+000,2.2646062758e+001,-8.7500000000e+001
20030728,0000,1.34680000,1.2264922322e+001,1.9185682613e+001,-8.2705479452e+001
20030729,0000,1.35250000,1.4960629921e+001,1.7777331716e+001,-7.2945205479e+001
20030730,0000,1.36390000,2.7553336360e+001,2.1035999930e+001,-5.3979238754e+001
20030731,0000,1.36990000,4.3307839388e+001,2.8459946416e+001,-4.3598615917e+001
20030801,0000,1.36460000,5.6996412096e+001,3.7972101643e+001,-5.2768166090e+001
20030804,0000,1.34780000,5.7070193286e+001,4.4338132191e+001,-8.1833910035e+001
20030805,0000,1.34770000,5.3512705531e+001,4.7396323304e+001,-8.2006920415e+001
20030806,0000,1.35350000,4.4481132075e+001,4.6424592894e+001,-7.1972318339e+001
20030807,0000,1.35020000,3.3740028156e+001,4.2196404648e+001,-7.7681660900e+001
20030808,0000,1.35970000,3.0395426394e+001,3.8262745230e+001,-6.1245674740e+001
20030811,0000,1.35780000,3.4155781326e+001,3.6893757262e+001,-6.4532871972e+001
20030812,0000,1.36880000,4.3488943489e+001,3.9092152671e+001,-4.5501730104e+001
20030813,0000,1.36690000,5.1160443996e+001,4.3114916446e+001,-4.8788927336e+001
20030814,0000,1.36980000,6.2467599793e+001,4.9565810895e+001,-2.5629290618e+001
20030815,0000,1.37150000,6.9668246445e+001,5.6266622745e+001,-2.1739130435e+001
20030818,0000,1.38910000,7.9908906883e+001,6.4147384124e+001,-9.2819614711e+000

Sie können das zweite Beispiel modifizieren, um einfach an ein Script zu gelangen, das Ihre Anforderungen erfüllt.



7. Einlernen des neuronalen Netzwerks

Die Einlernung des Netzwerks wurde bereits durch Heaton Research in C# vorbereitet. ENCOG 2.6 implementiert den Namensraum Encog.App.Quant, der eine Basis für die Prognostizierung von finanziellen Zeitreihen bildet. Das Einlernscript ist äußerst flexibel und lässt sich einfach an jede beliebige Anzahl von Eingabeindikatoren anpassen. Sie müssen nur in der Konstante DIRECTORY das Verzeichnis von MetaTrader 5 ändern.

Die Netzwerkarchitektur und Einlernparameter lassen sich durch eine Änderung der folgenden Variablen einfach anpassen:

        /// <summary>
        /// The size of the input window.  This is the number of bars used to predict the next bar.
        /// </summary>
        public const int INPUT_WINDOW = 6;        

        /// <summary>
        /// The number of bars forward we are trying to predict.  This is usually just 1 bar.  The future indicator used in step 1 may
        /// well look more forward into the future. 
        /// </summary>
        public const int PREDICT_WINDOW = 1;

        /// <summary>
        /// The number of bars forward to look for the best result.
        /// </summary>
        public const int RESULT_WINDOW = 5;

        /// <summary>
        /// The number of neurons in the first hidden layer.
        /// </summary>
        public const int HIDDEN1_NEURONS = 12;

        /// <summary>
        /// The target error to train to.
        /// </summary>
        public const double TARGET_ERROR = 0.01;

Der Code selbst ist selbsterklärend, deshalb ist es am besten, ihn sorgfältig durchzulesen:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Encog.App.Quant.Normalize;
using Encog.Util.CSV;
using Encog.App.Quant.Indicators;
using Encog.App.Quant.Indicators.Predictive;
using Encog.App.Quant.Temporal;
using Encog.Neural.NeuralData;
using Encog.Neural.Data.Basic;
using Encog.Util.Simple;
using Encog.Neural.Networks;
using Encog.Neural.Networks.Layers;
using Encog.Engine.Network.Activation;
using Encog.Persist;

namespace NetworkTrainer
{
    public class Program
    {
        /// <summary>
        /// The directory that all of the files will be stored in.
        /// </summary>
        public const String DIRECTORY = "d:\\mt5\\MQL5\\Files\\";

        /// <summary>
        /// The input file that starts the whole process.  This file should be downloaded from NinjaTrader using the EncogStreamWriter object.
        /// </summary>
        public const String STEP1_FILENAME = DIRECTORY + "mt5export.csv";

        /// <summary>
        /// We apply a predictive future indicator and generate a second file, with the additional predictive field added.
        /// </summary>
        public const String STEP2_FILENAME = DIRECTORY + "step2_future.csv";

        /// <summary>
        /// Next the entire file is normalized and stored into this file.
        /// </summary>
        public const String STEP3_FILENAME = DIRECTORY + "step3_norm.csv";

        /// <summary>
        /// The file is time-boxed to create training data.
        /// </summary>
        public const String STEP4_FILENAME = DIRECTORY + "step4_train.csv";

        /// <summary>
        /// Finally, the trained neural network is written to this file.
        /// </summary>
        public const String STEP5_FILENAME = DIRECTORY + "step5_network.eg";
       
        /// <summary>
        /// The size of the input window.  This is the number of bars used to predict the next bar.
        /// </summary>
        public const int INPUT_WINDOW = 6;        

        /// <summary>
        /// The number of bars forward we are trying to predict.  This is usually just 1 bar.  The future indicator used in step 1 may
        /// well look more forward into the future. 
        /// </summary>
        public const int PREDICT_WINDOW = 1;

        /// <summary>
        /// The number of bars forward to look for the best result.
        /// </summary>
        public const int RESULT_WINDOW = 5;

        /// <summary>
        /// The number of neurons in the first hidden layer.
        /// </summary>
        public const int HIDDEN1_NEURONS = 12;

        /// <summary>
        /// The target error to train to.
        /// </summary>
        public const double TARGET_ERROR = 0.01;

        static void Main(string[] args)
        {
            // Step 1: Create future indicators
            Console.WriteLine("Step 1: Analyze MT5 Export & Create Future Indicators");
            ProcessIndicators ind = new ProcessIndicators();
            ind.Analyze(STEP1_FILENAME, true, CSVFormat.DECIMAL_POINT);
            int externalIndicatorCount = ind.Columns.Count - 3;
            ind.AddColumn(new BestReturn(RESULT_WINDOW,true)); 
            ind.Process(STEP2_FILENAME);          
            Console.WriteLine("External indicators found: " + externalIndicatorCount);
            //Console.ReadKey();

            // Step 2: Normalize
            Console.WriteLine("Step 2: Create Future Indicators");
            EncogNormalize norm = new EncogNormalize();
            norm.Analyze(STEP2_FILENAME, true, CSVFormat.ENGLISH);
            norm.Stats[0].Action = NormalizationDesired.PassThrough; // Date
            norm.Stats[1].Action = NormalizationDesired.PassThrough; // Time
            
            norm.Stats[2].Action = NormalizationDesired.Normalize; // Close
            norm.Stats[3].Action = NormalizationDesired.Normalize; // Stoch K
            norm.Stats[4].Action = NormalizationDesired.Normalize; // Stoch Dd
            norm.Stats[5].Action = NormalizationDesired.Normalize; // WilliamsR
       
            norm.Stats[6].Action = NormalizationDesired.Normalize; // best return [RESULT_WINDOW]

            norm.Normalize(STEP3_FILENAME);

            // neuron counts
            int inputNeurons = INPUT_WINDOW * externalIndicatorCount;
            int outputNeurons = PREDICT_WINDOW;

            // Step 3: Time-box
            Console.WriteLine("Step 3: Timebox");
            //Console.ReadKey();
            TemporalWindow window = new TemporalWindow();
            window.Analyze(STEP3_FILENAME, true, CSVFormat.ENGLISH);
            window.InputWindow = INPUT_WINDOW;
            window.PredictWindow = PREDICT_WINDOW;
            int index = 0;
            window.Fields[index++].Action = TemporalType.Ignore; // date
            window.Fields[index++].Action = TemporalType.Ignore; // time
            window.Fields[index++].Action = TemporalType.Ignore; // close
            for(int i=0;i<externalIndicatorCount;i++)
                window.Fields[index++].Action = TemporalType.Input; // external indicators
            window.Fields[index++].Action = TemporalType.Predict; // PredictBestReturn

            window.Process(STEP4_FILENAME);

            // Step 4: Train neural network
            Console.WriteLine("Step 4: Train");
            Console.ReadKey();
            INeuralDataSet training = (BasicNeuralDataSet)EncogUtility.LoadCSV2Memory(STEP4_FILENAME, inputNeurons, 
                                                                                      outputNeurons, true, CSVFormat.ENGLISH);

            BasicNetwork network = new BasicNetwork();
            network.AddLayer(new BasicLayer(new ActivationTANH(), true, inputNeurons));
            network.AddLayer(new BasicLayer(new ActivationTANH(), true, HIDDEN1_NEURONS));
            network.AddLayer(new BasicLayer(new ActivationLinear(), true, outputNeurons));
            network.Structure.FinalizeStructure();
            network.Reset();

            //EncogUtility.TrainToError(network, training, TARGET_ERROR);
            EncogUtility.TrainConsole(network, training, 3);

            // Step 5: Save neural network and stats
            EncogMemoryCollection encog = new EncogMemoryCollection();
            encog.Add("network", network);
            encog.Add("stat", norm.Stats);
            encog.Save(STEP5_FILENAME);
            Console.ReadKey();
        }
    }
}

Ihnen könnte aufgefallen sein, dass ich eine Zeile auskommentiert und die Einlernfunktion von EncogUtility.TrainToError() in EncogUtility.TrainConsole() geändert habe.

EncogUtility.TrainConsole(network, training, 3);

Die Methode TrainConsole legt eine Anzahl von Minuten zum Einlernen des Netzwerks fest. Im aufgeführten Beispiel lerne ich das Netzwerk drei Minuten lang ein. Abhängig von der Komplexität des Netzwerks und der Größe der Einlerndaten kann das Einlernen des Netzwerks einige Minuten, Stunden oder sogar Tage dauern. Ich empfehle, auf der Webseite von Heaton Research oder in einem beliebigen sonstigen Buch zu diesem Thema mehr über die Fehlerberechnung und Einlernalgorithmen zu lesen.

Die Methode EncogUtility.TrainToError() beendet das Einlernen des Netzwerks, nachdem ein festgelegter Netzwerkfehler erzielt wurde. Sie können EncogUtility.TrainConsole() kommentieren und den Kommentar bei EncogUtility.TrainToError() entfernen, um das Netzwerk bis zu einem gewünschten Fehler einzulernen, wie es im ursprünglichen Beispiel der Fall ist. 

EncogUtility.TrainToError(network, training, TARGET_ERROR);

Bitte beachten Sie, dass das Netzwerk manchmal nicht bis zu einem bestimmten Fehler eingelernt werden kann, weil die Anzahl der Neuronen möglicherweise zu klein ist.

8. Verwendung des eingelernten neuronalen Netzwerks für die Erstellung eines neuronalen Indikators in MetaTrader 5

Das eingelernte Netzwerk kann von einem neuronalen Netzwerkindikator genutzt werden, der versuchen wird, die beste Rendite zu prognostizieren.

Der neuronale Indikator ENCOG für MetaTrader 5 besteht aus zwei Teilen. Ein Teil ist in MQL5 geschrieben und nutzt grundsätzlich die gleichen Indikatoren wie jene, mit denen das Netzwerk eingelernt wurde, und übergibt Indikatorwerte des Eingabefensters an das Netzwerk. Der zweite Teil ist in C# geschrieben und timeboxt Eingabedaten und übermittelt die Ausgabe des neuronalen Netzwerks an MQL5. Der C#-Teil des Indikators basiert auf meinem vorherigen Artikel Offenlegen von C#-Code in MQL5.

using System;
using System.Collections.Generic;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
using Encog.Neural.Networks;
using Encog.Persist;
using Encog.App.Quant.Normalize;
using Encog.Neural.Data;
using Encog.Neural.Data.Basic;

namespace EncogNeuralIndicatorMT5DLL
{

    public class NeuralNET
    {
        private EncogMemoryCollection encog;
        public BasicNetwork network;
        public NormalizationStats stats;

        public NeuralNET(string nnPath)
        {
            initializeNN(nnPath);
        }

        public void initializeNN(string nnPath)
        {
            try
            {
                encog = new EncogMemoryCollection();
                encog.Load(nnPath);
                network = (BasicNetwork)encog.Find("network");
                stats = (NormalizationStats)encog.Find("stat");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }
        }
    };

   class UnmanagedExports
   {

      static NeuralNET neuralnet; 

      [DllExport("initializeTrainedNN", CallingConvention = CallingConvention.StdCall)]
      static int initializeTrainedNN([MarshalAs(UnmanagedType.LPWStr)]string nnPath)
      {
          neuralnet = new NeuralNET(nnPath);

          if (neuralnet.network != null) return 0;
          else return -1;
      }

      [DllExport("computeNNIndicator", CallingConvention = CallingConvention.StdCall)]
      public static int computeNNIndicator([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t1,
                                           [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t2,
                                           [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t3, 
                                           int len, 
                                           [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] double[] result,
                                           int rates_total)
      {
          INeuralData input = new BasicNeuralData(3 * len);
          
          int index = 0;
          for (int i = 0; i <len; i++)
          {
              input[index++] = neuralnet.stats[3].Normalize(t1[i]);
              input[index++] = neuralnet.stats[4].Normalize(t2[i]);
              input[index++] = neuralnet.stats[5].Normalize(t3[i]);
          }

          INeuralData output = neuralnet.network.Compute(input);
          double d = output[0];
          d = neuralnet.stats[6].DeNormalize(d);        
          result[rates_total-1]=d;

          return 0;
      }  
   }
}

Wenn Sie mehr oder weniger als drei Indikatoren nutzen möchten, müssen Sie die Methode computeNNIndicator() an Ihre Anforderungen anpassen. 

 [DllExport("computeNNIndicator", CallingConvention = CallingConvention.StdCall)]
      public static int computeNNIndicator([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t1,
                                         [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t2,
                                         [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t3, 
                                         int len, 
                                         [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] double[] result,
                                         int rates_total)

In diesem Fall sind die ersten drei Eingabeparameter Tabellen, die Eingabewerte des Indikators enthalten, der vierte Parameter ist die Länge des Eingabefensters.

Der Wert SizeParamIndex = 3 deutet auf die Variable der Länge des Eingabefensters, da der Zähler der Eingabevariablen ab 0 steigt. Der fünfte Parameter ist eine Tabelle, die die Ergebnisse des neuronalen Netzwerks enthält. 

Der MQL5-Teil des Indikators muss die C#-Bibliothek EncogNNTrain.dll importieren und die aus der DLL exportierten Funktionen initializeTrainedNN() und computeNNIndicator() nutzen.

//+------------------------------------------------------------------+
//|                                         NeuralEncogIndicator.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
#property indicator_separate_window

#property indicator_plots 1
#property indicator_buffers 1
#property indicator_color1 Blue
#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_width1  2

#import "EncogNNTrainDLL.dll"
   int initializeTrainedNN(string nnFile);
   int computeNNIndicator(double& ind1[], double& ind2[],double& ind3[], int size, double& result[], int rates);  
#import


int INPUT_WINDOW = 6;
int PREDICT_WINDOW = 1;

double ind1Arr[], ind2Arr[], ind3Arr[]; 
double neuralArr[];

int hStochastic;
int hWilliamsR;

int hNeuralMA;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0, neuralArr, INDICATOR_DATA);
   
   PlotIndexSetInteger(0, PLOT_SHIFT, 1);

   ArrayResize(ind1Arr, INPUT_WINDOW);
   ArrayResize(ind2Arr, INPUT_WINDOW);
   ArrayResize(ind3Arr, INPUT_WINDOW);
     
   ArrayInitialize(neuralArr, 0.0);
   
   ArraySetAsSeries(ind1Arr, true);   
   ArraySetAsSeries(ind2Arr, true);  
   ArraySetAsSeries(ind3Arr, true);
  
   ArraySetAsSeries(neuralArr, true);   
               
   hStochastic = iStochastic(NULL, 0, 8, 5, 5, MODE_EMA, STO_LOWHIGH);
   hWilliamsR = iWPR(NULL, 0, 21);
 
   Print(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg");
   initializeTrainedNN(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg");
      
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
   int calc_limit;
   
   if(prev_calculated==0) // First execution of the OnCalculate() function after the indicator start
        calc_limit=rates_total-34; 
   else calc_limit=rates_total-prev_calculated;
    
   ArrayResize(neuralArr, rates_total);
  
   for (int i=0; i<calc_limit; i++)     
   {
      CopyBuffer(hStochastic, 0, i, INPUT_WINDOW, ind1Arr);
      CopyBuffer(hStochastic, 1, i, INPUT_WINDOW, ind2Arr);
      CopyBuffer(hWilliamsR,  0, i, INPUT_WINDOW, ind3Arr);    
      
      computeNNIndicator(ind1Arr, ind2Arr, ind3Arr, INPUT_WINDOW, neuralArr, rates_total-i); 
   }
     
  //Print("neuralArr[0] = " + neuralArr[0]);
  
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Hier sehen Sie die Ausgabe des auf Tagesdaten von USDCHF und den Indikatoren Stochastic und Williams %R eingelernten Indikators:

 Abbildung 7. Neuronaler Encog-Indikator

Abbildung 7. Neuronaler Encog-Indikator

Der Indikator zeigt die prognostizierte beste Rendite auf dem nächsten Balken.

Ihnen könnte aufgefallen sein, dass ich den Indikator um einen Balken in die Zukunft verschoben habe:

PlotIndexSetInteger(0, PLOT_SHIFT, 1);

Dies dient der Verdeutlichung, dass es sich um einen prognostizierenden Indikator handelt. Nun, da wir einen neuronalen Indikator erstellt haben, sind wir bereit, einen Expert Advisor auf Basis dieses Indikators zu erstellen.



9. Expert Advisor auf Basis eines neuronalen Indikators

Der Expert Advisor nimmt die Ausgabe des neuronalen Indikators und entscheidet, ob ein Wertpapier gekauft oder verkauft werden soll. Mein erster Eindruck war, dass er kaufen soll, wenn der Indikator über Null liegt, und verkaufen, wenn er unter Null ist. Das bedeutet kaufen, wenn die beste prognostizierte Rendite in einem bestimmten Zeitfenster positiv ist, und verkaufen, wenn die beste prognostizierte Rendite negativ ist.

Nach einigen einleitenden Tests zeigte sich, dass die Performance besser sein könnte. Deshalb habe ich die Variablen 'strong uptrend' und 'strong downtrend' eingeführt, was bedeutet, dass es keinen Grund gibt, vom Abschluss zurückzutreten, wenn wir gemäß der berühmten Regel 'the trend is your friend' in einem starken Trend sind.

Zusätzlich wurde mir im Forum von Heaton Research geraten, ATR für bewegliche Stop Losses zu verwenden, also habe ich den Indikator Chandelier ATR genutzt, den ich im MQL5-Forum gefunden habe. Er führte tatsächlich zu gesteigertem Aktiengewinn beim Backtesting. Den Quellcode des Expert Advisors habe ich nachfolgend kopiert.

//+------------------------------------------------------------------+
//|                                           NeuralEncogAdvisor.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

double neuralArr[];

double trend;
double Lots=0.3;

int INPUT_WINDOW=8;

int hNeural,hChandelier;

//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArrayResize(neuralArr,INPUT_WINDOW);
   ArraySetAsSeries(neuralArr,true);
   ArrayInitialize(neuralArr,0.0);

   hNeural=iCustom(Symbol(),Period(),"NeuralEncogIndicator");
   Print("hNeural = ",hNeural,"  error = ",GetLastError());

   if(hNeural<0)
     {
      Print("The creation of ENCOG indicator has failed: Runtime error =",GetLastError());
      //--- forced program termination
      return(-1);
     }
   else  Print("ENCOG indicator initialized");

   hChandelier=iCustom(Symbol(),Period(),"Chandelier");
   Print("hChandelier = ",hChandelier,"  error = ",GetLastError());

   if(hChandelier<0)
     {
      Print("The creation of Chandelier indicator has failed: Runtime error =",GetLastError());
      //--- forced program termination
      return(-1);
     }
   else  Print("Chandelier indicator initialized");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   long tickCnt[1];
   int ticks=CopyTickVolume(Symbol(),0,0,1,tickCnt);
   if(tickCnt[0]==1)
     {
      if(!CopyBuffer(hNeural,0,0,INPUT_WINDOW,neuralArr)) { Print("Copy1 error"); return; }

      // Print("neuralArr[0] = "+neuralArr[0]+"neuralArr[1] = "+neuralArr[1]+"neuralArr[2] = "+neuralArr[2]);
      trend=0;

      if(neuralArr[0]<0 && neuralArr[1]>0) trend=-1;
      if(neuralArr[0]>0 && neuralArr[1]<0) trend=1;

      Trade();
     }
  }
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---

//---
   return(0.0);
  }
//+------------------------------------------------------------------+

void Trade()
  {
   double bufChandelierUP[2];
   double bufChandelierDN[2];

   double bufMA[2];

   ArraySetAsSeries(bufChandelierUP,true);
   ArraySetAsSeries(bufChandelierUP,true);

   ArraySetAsSeries(bufMA,true);

   CopyBuffer(hChandelier,0,0,2,bufChandelierUP);
   CopyBuffer(hChandelier,1,0,2,bufChandelierDN);

   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(Symbol(),PERIOD_CURRENT,0,3,rates);

   bool strong_uptrend=neuralArr[0]>0 && neuralArr[1]>0 && neuralArr[2]>0 &&
                      neuralArr[3]>0 && neuralArr[4]>0 && neuralArr[5]>0 &&
                       neuralArr[6]>0 && neuralArr[7]>0;
   bool strong_downtrend=neuralArr[0]<0 && neuralArr[1]<0 && neuralArr[2]<0 &&
                        neuralArr[3]<0 && neuralArr[4]<0 && neuralArr[5]<0 &&
                        neuralArr[6]<0 && neuralArr[7]<0;

   if(PositionSelect(_Symbol))
     {
      long type=PositionGetInteger(POSITION_TYPE);
      bool close=false;

      if((type==POSITION_TYPE_BUY) && (trend==-1))

         if(!(strong_uptrend) || (bufChandelierUP[0]==EMPTY_VALUE)) close=true;
      if((type==POSITION_TYPE_SELL) && (trend==1))
         if(!(strong_downtrend) || (bufChandelierDN[0]==EMPTY_VALUE))
            close=true;
      if(close)
        {
         CTrade trade;
         trade.PositionClose(_Symbol);
        }
      else // adjust s/l
        {
         CTrade trade;

         if(copied>0)
           {
            if(type==POSITION_TYPE_BUY)
              {
               if(bufChandelierUP[0]!=EMPTY_VALUE)
                  trade.PositionModify(Symbol(),bufChandelierUP[0],0.0);
              }
            if(type==POSITION_TYPE_SELL)
              {
               if(bufChandelierDN[0]!=EMPTY_VALUE)
                  trade.PositionModify(Symbol(),bufChandelierDN[0],0.0);
              }
           }
        }
     }

   if((trend!=0) && (!PositionSelect(_Symbol)))
     {
      CTrade trade;
      MqlTick tick;
      MqlRates rates[];
      ArraySetAsSeries(rates,true);
      int copied=CopyRates(Symbol(),PERIOD_CURRENT,0,INPUT_WINDOW,rates);

      if(copied>0)
        {
         if(SymbolInfoTick(_Symbol,tick)==true)
           {
            if(trend>0)
              {
               trade.Buy(Lots,_Symbol,tick.ask);
               Print("Buy at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[0]);
              }
            if(trend<0)
              {
               trade.Sell(Lots,_Symbol,tick.bid);
               Print("Sell at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[0]);
              }
           }
        }
     }

  }
//+------------------------------------------------------------------+

Der Expert Advisor wurde auf D1-Daten des Paares USDCHF ausgeführt. Für das Einlernen wurden etwa 50 % der Daten genutzt.



10. Backtesting-Ergebnisse des Expert Advisors

Ich führe nachfolgend die Backtesting-Ergebnisse auf. Das Backtesting wurde von 2000.01.01 bis 2011.03.26 durchgeführt.

Abbildung 8. Backtesting-Ergebnisse des neuronalen Expert Advisors

Abbildung 8. Backtesting-Ergebnisse des neuronalen Expert Advisors

Abbildung 9. Bilanz-Kapital-Backtesting-Diagramm des neuronalen Expert Advisors

Abbildung 9. Bilanz-Kapital-Backtesting-Diagramm des neuronalen Expert Advisors

Denken Sie daran, dass diese Performance für andere Timeframes und Wertpapiere völlig anders ausfallen kann.

Bitte behandeln Sie diesen EA als Übungswerkzeug und nutzen Sie ihn als Grundlage für weitere Forschungen. Meine persönliche Meinung ist, dass das Netzwerk nach jedem bestimmten Zeitraum neu eingelernt werden könnte, um es robuster zu machen. Vielleicht findet jemand eine gute Möglichkeit dafür oder hat sie sogar schon gefunden. Vielleicht gibt es bessere Wege, Kauf-/Verkaufsprognosen auf Basis eines neuronalen Indikators anzustellen. Ich möchte die Leser zum Experimentieren ermutigen.



Fazit

Im vorliegenden Beitrag habe ich eine Möglichkeit für die Erstellung eines neuronalen prognostizierenden Indikators und Expert Advisors auf Basis dieses Indikators mithilfe des Machine-Learning-Frameworks ENCOG vorgestellt. Alle Quellcodes, kompilierten Binärdateien, DLLs und Beispiele für eingelernte Netzwerke sind an diesen Beitrag angehängt.

Aufgrund des "doppelten DLL-Wrappings in .NET" müssen sich die Dateien Cloo.dll, encog-core-cs.dll und log4net.dll im Ordner des Client Terminals befinden.
Die Datei EncogNNTrainDLL.dll muss sich im Ordner \Datenordner des Terminals\MQL5\Libraries befinden.

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

Beigefügte Dateien |
encogcsharp.zip (2202.77 KB)
files.zip (270.14 KB)
libraries.zip (321.62 KB)
experts.zip (1.56 KB)
scripts.zip (1.03 KB)
indicators.zip (2.24 KB)
Nutzung von Pseudo-Templates als Alternative für C++-Templates Nutzung von Pseudo-Templates als Alternative für C++-Templates
Dieser Beitrag beschreibt eine Art der Programmierung ohne Templates, allerdings unter Beibehaltung des ihnen eigenen Programmierstils. Er schildert die Implementierung von Templates mithilfe von benutzerdefinierten Methoden und bietet ein vorgefertigtes, angehängtes Script zur Erstellung eines Codes auf Basis festgelegter Templates.
Alarm und Benachrichtigung für Externe Indikatoren Alarm und Benachrichtigung für Externe Indikatoren
Beim Arbeiten kann ein Händler mit der folgenden Situation konfrontiert werden: es ist notwendig, einen "Alarm" oder eine SMS-Nachricht am Bildschirm (in einem Chartfenster) zu erhalten, um einen Hinweis auf ein erscheinendes Signal eines Indikators zu bekommen. Der Artikel beinhaltet ein Beispiel zur Anzeige von Informationen über grafische Objekte, die durch einen externen Indikator kreiert wurden.
Die Rolle von statistischen Verteilungen für die Arbeit eines Händlers Die Rolle von statistischen Verteilungen für die Arbeit eines Händlers
Dieser Beitrag ist eine logische Fortsetzung meines Beitrags Statistische Verteilungen von Wahrscheinlichkeiten in MQL5, in dem die Klassen für die Arbeit mit einigen theoretischen statistischen Verteilungen dargelegt wurden. Da wir nun über die theoretische Grundlage verfügen, schlage ich vor, dass wir direkt mit realen Datensätzen fortfahren und versuchen, diese Grundlage für Informationszwecke zu nutzen.
Die Wechselwirkung zwischen MеtaTrader 4 und der MATLAB Engine (Virtual MATLAB Maschine) Die Wechselwirkung zwischen MеtaTrader 4 und der MATLAB Engine (Virtual MATLAB Maschine)
Der Artikel enthält Überlegungen hinsichtlich der Errichtung einer DLL-Bibliothek - eines "Wrapper", der die Wechselwirkung zwischen MetaTrader 4 und dem mathematischen Desktop-Paket MATLAB ermöglicht. Er beschreibt "Fallen" und die Wege, um sie zu überwinden. Der Artikel ist für erfahrene C/C++ Programmierer vorgesehen, die den Borland C++ Builder 6 Compiler verwenden.