MetaTrader 5 herunterladen

Die Eröffnung durch Indikatoren bestimmen lassen

18 Dezember 2017, 08:46
Dmitriy Gizlyk
0
359

Einführung

Sagen Sie mir, wenn Sie eine Reihe profitabler Positionen eines erfolgreichen Händlers betrachten, haben Sie nicht den Wunsch, seine Strategie zu wiederholen? Oder haben Sie, wenn Sie Ihre Handelshistorie durchgesehen haben, darüber nachgedacht, wie Sie unprofitable Positionen vermeiden können? Ich denke, dass viele von Ihnen mindestens eine Frage positiv beantworten hätten. In diesem Artikel möchte ich eine Technik vorschlagen, die die vergangenen, geschlossenen Positionen mit Indikatoren verbindet, und ich werde auch diskutieren, wie man jene Indikatoren auswählt, die helfen werden, die Effizienz des Handels zu verbessern.

1. Problemdefinition

In meinem vorherigen Artikel habe ich über den Aufbau eines Expert Advisors auf der Grundlage des Kalman-Filters gesprochen. Während der Tests zeigte er einen Gewinn, aber er zeigte auch 2 Probleme der Strategie: Spätes Schließen und eine Reihe von Verlustpositionen im Zuge einer Seitwärtsbewegung.

Unsere Aufgabe besteht also darin, die Anzahl der Verlustpositionen dieser Strategie zu reduzieren. Dafür werden wir die Werte einiger Indikatoren zum Zeitpunkt der Eröffnung der Positionen sichern. Dann analysieren und vergleichen wir die Werte der Indikatoren mit den Ergebnissen der Transaktionen. Wir werden dann die Indikatoren auswählen müssen, die helfen können, die Handelsergebnisse zu verbessern.

Zuerst erstellen wir einen Plan wie wir vorgehen werden.

  1. Bestimmen der Testperiode. Testen und den Ergebnisbericht sichern.
  2. Analysieren des Testberichts und Erstellen eines Arrays der Positionen (mit deren Ergebnissen).
  3. Bestimmen der Liste der zu verwendenden Indikatoren und dem Format für die Datenspeicherung. Vorbereiten der Klassen für eine weitere Anwendung.
  4. Vorbereiten des Berichtsformats für die Ergebnisausgabe.
  5. Erstellen des zu untersuchenden EAs.
  6. Starten des zu untersuchenden EAs im Strategietester und der Analyse des Ergebnisses.
  7. Hinzufügen der benötigten Indikatoren zum EA.
  8. Testen des veränderten EAs und Vergleichen der Ergebnisse.

2. Der erste Test des zu untersuchenden EAs

Im oben erwähnten Artikel, handelte der EA 150 mal innerhalb eines Testmonats. Dies reicht für eine statistische Analyse nicht aus. Für den Erhalt valider Ergebnisse muss die Testperiode um das achtfache verlängert werden. Ohne jede Optimierung, verwenden wir einen Zeitraum für die Errichtung einer autoregressiven Funktion von 3.120 Bars (etwa 3 Monate) und starten den Test.

Der erste Test.Der erste Test

Die Testergebnissen ergeben offensichtlich herbe Verluste, nach den ersten profitablen Positionen. Im Allgemeinen betrug der Anteil der profitablen Positionen etwas weniger als 34%. Obwohl der durchschnittliche Gewinn den durchschnittlichen Verlust um 45% übersteigt. Dies reicht nicht aus, um während des gesamten Testzeitraums Gewinne zu erzielen.

Der erste Test

Das Preischart zeigt, dass der EA, wenn ein klarer Trend fehlt (den Seitwärtsbewegungen) Positionen öffnet und sie wieder mit Verlust und schließt. Unsere Aufgabe ist es, die Anzahl solcher Positionen zu reduzieren und, wenn möglich, ganz auszuschließen.

Testchart

Zunächst sollte der Testbericht zur weiteren Verarbeitung gespeichert werden. Es gibt jedoch eine Besonderheit: Aus Sicherheitsgründen wird in MQL5 der Umgang mit Dateien streng kontrolliert. Stellen Sie sicher, dass sich die Dateien für die Ausführung von Operationen mit Hilfe von MQL5-Werkzeugen in der "Sandbox" befinden. Daher muss der Bericht dort gespeichert werden. Aber da wir das Programm im Strategietester starten, müssen wir berücksichtigen, dass jeder Agent in seiner "Sandbox" arbeitet. Folglich, damit das Programm während des Testens bei jedem Agenten Zugriff auf den Bericht erhält, speichern wir ihn im "Gemeinsamen Dateiordner" des Terminals.

Um den Pfad zum "Gemeinsamen Dateiordner" des Terminals herauszufinden, öffnen Sie das Menü "Datei" im MetaEditor und wählen Sie "Gemeinsamen Dateiordner öffnen".

Pfad zur "Sandbox"

Im geöffneten Fenster, öffnen wir das Verzeichnis "Files".

Pfad zur "Sandbox"

Dann können wir den ganze Pfad zum mittels "Ctrl+C" in die Zwischenablage kopieren.

Pfad zur "Sandbox"

Der Pfad in die "Sandbox" ist jetzt bekannt und wir können den Testbericht sichern. Dazu wählen wir "Strategie Tester" die Registerkarte "Ergebnis" und klicken an beliebiger Stelle auf die rechte Maustaste. Im angezeigten Menü wählen wir "Bericht" -> "HTML (Internet Explorer)".

Sichern des Berichts.

Nach all dem öffnet sich ein Fenster, um Dateien zu sichern. Zuerst muss der Pfad zur "Sandbox" in das Eingabefeld eingefügt werden, gefolgt von einem "Sichern". Dieses Vorgehen ändert das Verzeichnis für die zu speichernden Dateien.

Sichern des Berichts

Im folgenden Schritt muss der Name, unter dem der Testbericht gesichert werden soll, eingegeben werden. Dann wir die Datei gesichert.

Sichern des Berichts

Nachdem der Bericht in der "Sandbox" gespeichert wurde, kommen wir zum nächsten Schritt - wir erstellen eine Reihe von Positionen für die spätere Analyse.

3. Erstellen des Handelsarrays

3.1. Allgemeines Konzept der Datenanalyse

Im vorherigen Abschnitt haben wir den Testbericht des Expert Advisors gespeichert. Jetzt müssen wir ein Array der Positionen bilden, die für die Verarbeitung geeignet sind. Im Browser sehen wir eine Liste der Positionen, aber MQL5-Programme können nicht direkt einen Array aus den Daten einer HTML-Datei laden. Daher müssen wir den Bericht analysieren.

Positionenliste des Berichts.

Im Wesentlichen ist die HTML-Datei durch Kennzeichnungen getrennt, die ihre Formatierung und ihr Design beschreiben. Nachdem öffnen des Berichts in einem Texteditor, sieht man zwei dieser Kennzeichnungen: "<table>", die alle Daten im Bericht in 2 Datentabellen unterteilen. Informationen zu den Positionen finden sich in der zweiten Tabelle. Der Anfang enthält Informationen über die Aufträge und dann - die Informationen über die Transaktionen.

HTML-Ansicht des Berichts.

Tabellenzeilen werden mit "<tr>...</tr>" gekennzeichnet. Innerhalb einer Zeile werden die Zellen durch "<td>...</td>" von einender getrennt.

3.2. Die Klasse zur Sicherung der Daten der Positionen

Wir haben das Format der Daten im Bericht festgelegt. Kommen wir nun zum Format der zu sichernden Daten in unserem Array. Sofern der analysierte EA nur mit einem Symbol arbeitet, muss der Symbolname nicht gespeichert werden. Trotzdem werden wir ihn für die Initialisierung der Indikatoren benötigen. Insgesamt wird die Datenstruktur der Positionen die folgenden Elemente haben:

  • Position, Eröffnungszeit;
  • Position, Eröffnungsvolumen;
  • Positionsrichtung;
  • Position, Volumen beim Schließen;
  • Kommission;
  • Swap;
  • Gewinn.

Wir haben die wesentlichen Aspekte dieser Arbeitsphase entschieden. Fangen wir an, den Code zu schreiben. Erstellen wir zuerst die Klasse CDeal der Positionen.

class CDeal       :  public CObject
  {
private:
   datetime          OpenTime;         // Time of open position
   double            OpenedVolume;     // Volume of opened position
   ENUM_POSITION_TYPE Direct;          // Direct of opened position
   double            ClosedVolume;     // Closed volume
   double            Comission;        // Comission to position
   double            Swap;             // Swap of position
   double            Profit;           // Profit of position
   
public:
                     CDeal();
                    ~CDeal();
  };

Wir initialisieren die Klasse beim Sichern einer neuen offenen Position, wenn der Zeitpunkt der Eröffnung der Position, das Volumen und die Richtung der Transaktion bereits bekannt sind. Daher werden wir der Initialisierungsfunktion die bekannten Werte übergeben und ggf. auch die Kommission. Die restlichen Parameter werden mit Null initialisiert. Daher sieht die Initialisierungsfunktion der Klasse folgendermaßen aus:

CDeal::CDeal(ENUM_POSITION_TYPE type,datetime time,double volume,double comission=0.0)  : ClosedVolume(0),
                                                                                          Swap(0),
                                                                                          Profit(0)
  {
   OpenTime      = time;
   OpenedVolume  = volume;
   Direct        = type;
   Comission     = comission;
  }

In Zukunft müssen wir den Status bereits gespeicherter Transaktionen überprüfen. Erstellen wir dazu die Funktion IsClosed, die überprüft, ob ein Positionen in der Datenbank bereits geschlossen ist. Sie vergleicht das Volumen der Eröffnung mit dem der Schließung. Wenn sie gleich sind, wird der Trade geschlossen und die Funktion gibt True zurück. Wenn die Transaktion nicht abgeschlossen wird, gibt die Funktion 'false' und das verbleibende, noch offene Volumen zurück.

bool CDeal::IsClosed(double &opened_volume)
  {
   opened_volume=OpenedVolume-ClosedVolume;
   return (opened_volume<=0);
  }

Falls nur der Zustand der Position überprüft werden muss und keine Notwendigkeit besteht, teilweise noch offene Volumina zu suchen, kann eine weitere Funktion gleichen Namens erstellt werden.

bool CDeal::IsClosed(void)
  {
   double opened_volume;
   return IsClosed(opened_volume);
  }

Um einen Position richtig zu schließen, müssen wir ihren Typ kennen. Wir erstellen dazu die Funktion "Type", die den Wert der 'private' Variablen "Direct" zurückgibt. Die Funktion ist ziemlich kurz, so dass sie direkt in den Klasse geschrieben werden kann.

ENUM_POSITION_TYPE Type(void) {  return Direct; }

Nachdem der Status verifiziert wurde, müssen die noch nicht geschlossenen Positionen geschlossen werden. Wir erstellen dazu die Funktion "Close". Folgende Parameter werden übergeben: das Volumen beim Schließen, der Gewinn der Position, die Kommission und der kumulierte Swap. Die Funktion gibt 'false' zurück, wenn das übergebene Volumen das Volumen der nicht geschlossene Position überschreitet. In anderen Fällen werden die übergebenen Parameter in den entsprechenden Klassenvariablen gespeichert und die Funktion gibt 'true' zurück.

bool CDeal::Close(double volume,double profit,double comission=0.0,double swap=0.0)
  {
   if((OpenedVolume-ClosedVolume)<volume)
      return false;
   ClosedVolume   += volume;
   Profit         += profit;
   Comission      += comission;
   Swap           += swap;
   return true;
  }

In der Zukunft benötigen wir für die Analyse von Positionen eine Funktion, die den Gewinn einer Position bei Bedarf zurückgibt. Nennen wir diese Funktion GetProfit.

double CDeal::GetProfit(void)
  {
   return (Comission+Swap+Profit);
  }

Um zeitnahe Daten über den Status von Indikatoren zu erhalten, müssen wir den Eröffnungszeitpunkt der Position kennen. Zu diesem Zweck erstellen wir die Funktion "GetTime".

datetime          GetTime(void)  {  return OpenTime;  }

3.3. Die Klasse für die Analyse des Testberichts

Nachdem wir eine Klasse erstellt haben, um Informationen von jeder Position zu speichern, fahren wir unverzüglich mit der Analyse des Berichts fort. Erstellen wir dazu die Klasse "CParsing". Wir deklarieren in der Klasse:

  • Objekt der Klasse CArrayObj - zum Speichern des Arrays der Positionen;
  • Objekt der Klasse CFileTxt - um mit der Berichtsdatei zu arbeiten;
  • Variable vom Typ 'string' - zum Speichern des Symbolnamens.

Neben den Funktionen der Initialisierung und Deinitialisierung befinden sich zwei weitere Funktionen in der Klasse:

  • ReadFile — sofort zum Analysieren;
  • GetSymbol — Rückgabe des Symbolnamens, auf Anfrage.

class CParsing
  {
private:
   CArrayObj        *car_Deals;     //Array of deals
   CFileTxt         *c_File;        //File to parsing
   
   string            s_Symbol;      //Symbol of deals
   
public:
                     CParsing(CArrayObj *&array);
                    ~CParsing();
                    
   bool              ReadFile(string file_name);
   string            GetSymbol(void)   {  return s_Symbol;  }
  };

Der Hauptzweck dieser Klasse besteht darin, ein Array der Positionen für die nachfolgende Verarbeitung zu erstellen. Daher muss das erstellte Array für die Arbeit im Hauptprogramm verfügbar sein. Dazu deklarieren wir ein Objekt der Klasse CArrayObj im Hauptprogramm, um die Positionen zu speichern, es wird ein Link zu dem Objekt bei der Initialisierung übergeben. Letztendlich schaut die Initialisierungsfunktion wird wie folgt aus:

CParsing::CParsing(CArrayObj *&array)  :  s_Symbol(NULL)
  {
   if(CheckPointer(array)==POINTER_INVALID)
     {
      array=new CArrayObj();
     }
   car_Deals=array;
  }

In der Deinitialisierungsfunktion wird das Objekt der Klasse CFileTxt gelöscht. Das Schließen der Datei wird in die Deinitialisierungsfunktion der Elternklasse CFile geschrieben, wir werden sie hier nicht anführen.

CParsing::~CParsing()
  {
   if(CheckPointer(c_File)!=POINTER_INVALID)
      delete c_File;
  }

Kommen wir jetzt direkt zur Analyse. Beim Aufruf der Funktion ReadFile muss der Dateiname der Berichtsdatei übergeben werden. Als erstes wird in der Funktion überprüft, ob dieser Parameter leer ist. Dann wird die Verfügbarkeit des Arrays zum Speichern der Informationen der Positionen sichergestellt. Ist auch nur ein Bedingung nicht erfüllt, beendet sich die Funktion und gibt 'false' zurück.

bool CParsing::ReadFile(string file_name)
  {
   //---
   if(file_name==NULL || file_name=="" || CheckPointer(car_Deals)==POINTER_INVALID)
      return false;

Dann wird das Objekt der Klasse CFileTxt initialisiert und die Datei das angegebenen Namens geöffnet. Tritt ein Fehler auf, beendet sich die Funktion und gibt 'false' zurück.

   if(CheckPointer(c_File)==POINTER_INVALID)
     {
      c_File=new CFileTxt();
      if(CheckPointer(c_File)==POINTER_INVALID)
         return false;
     }
   //---
   if(c_File.Open(file_name,FILE_READ|FILE_COMMON)<=0)
      return false;

Nach dem Öffnen der Datei wird der gesamte Inhalt eingelesen und einer Variablen des Typs 'string' zugewiesen. Ist die Datei leer, beendet sich die Funktion und gibt 'false' zurück.

   string html_report=NULL;
   while(!c_File.IsEnding())
      html_report+=c_File.ReadString();
   c_File.Close();
   if(html_report==NULL || html_report=="")
      return false;

Als Nächstes wird das Zeichen gesucht, das im Berichtstext nicht existiert und vielleicht als Trennzeichen dient. Wird so ein Zeichen nicht gefunden, beendet sich die Funktion und gibt 'false' zurück.

   string delimiter  =  NULL;
   ushort separate   =  0;
   for(uchar tr=1;tr<255;tr++)
     {
      string temp =  CharToString(tr);
      if(StringFind(html_report,temp,0)>0)
         continue;
      delimiter   =  temp;
      separate    =  tr;
      break;
     }
   if(delimiter==NULL)
      return false;

Wie oben bereist im Zuge der Struktur der html-Datei erwähnt, Tabellen werden durch "</table>" beendet. Ersetzen wir dieses Kennung durch unser Trennzeichen und spalten damit dann den gesamten Bericht in Zeilen. Auf diesen Weise befinden sich die benötigten Tabellen in einzelne Zeilen.

   if(StringReplace(html_report,"</table>",delimiter)<=0)
      return false;
   //---
   s_Symbol=NULL;
   car_Deals.Clear();
   //---
   string html_tables[];
   int size=StringSplit(html_report,separate,html_tables);
   if(size<=1)
      return false;

Durch dies wiederholte Vorgehen mit "</tr>” unterteilen wir die Tabelle in Zeilen.

   if(StringReplace(html_tables[size-2],"</tr>",delimiter)<=0)
      return false;
   size=StringSplit(html_tables[size-2],separate,html_tables);
   if(size<=1)
      return false;

Bearbeiten wir nun das Array der Zeichenketten in einer Schleife. Zuerst über über alle mit Informationen über die Aufträge. Wir orientieren uns dazu an den Zeilen mit "Deals", das in dem Bericht die Aufträge von den Positionen unterscheidet.

   bool found_start=false;
   double opened_volume=0;
   for(int i=0;i<size;i++)
     {
      //---
      if(!found_start)
        {
         if(StringFind(html_tables[i],"Deals",0)>=0)
            found_start=true;
         continue;
        }

Danach teilen wir jede Zeile in Zellen auf und wandeln die Informationen in unser Format um.

      string columns[];
      int temp=StringFind(html_tables[i],"<td>",0);
      if(temp<0)
         continue;
      if(temp>0)
         html_tables[i]=StringSubstr(html_tables[i],temp);
      StringReplace(html_tables[i],"<td>","");
      StringReplace(html_tables[i],"</td>",delimiter);
      temp=StringSplit(html_tables[i],separate,columns);
      if(temp<13)
         continue;
      //---
      ENUM_POSITION_TYPE   e_direction =  (ENUM_POSITION_TYPE)(columns[3]=="buy" ? POSITION_TYPE_BUY : columns[3]=="sell" ?
 POSITION_TYPE_SELL : -1);
      if(e_direction==-1)
         continue;
      //---
      datetime             dt_time     =  StringToTime(columns[0]);
      StringReplace(columns[5]," ","");
      double               d_volume    =  StringToDouble(columns[5]);
      StringReplace(columns[8]," ","");
      double               d_comission =  StringToDouble(columns[8]);
      StringReplace(columns[9]," ","");
      double               d_swap      =  StringToDouble(columns[9]);
      StringReplace(columns[10]," ","");
      double               d_profit    =  StringToDouble(columns[10]);
      if(s_Symbol==NULL || s_Symbol=="")
        {
         s_Symbol=columns[2];
         StringTrimLeft(s_Symbol);
         StringTrimRight(s_Symbol);
        }

Als Nächstes prüfen wir, ob eine Position gerade geschlossen werden soll. Falls ja, wird die Position nach der Methode FIFO geschlossen.

      if(opened_volume>0 && StringFind(columns[4],"out",0)>=0)
        {
         int total=car_Deals.Total();
         double total_volume=MathMin(opened_volume,d_volume);
         for(int d=0;(d<total && e_direction!=(-1) && total_volume>0);d++)
           {
            CDeal *deal=car_Deals.At(d);
            if(CheckPointer(deal)==POINTER_INVALID)
               continue;
            //---
            if(deal.Type()==e_direction)
               continue;
            //---
            double deal_unclosed=0;
            if(deal.IsClosed(deal_unclosed))
               continue;
            double close_volume     =  MathMin(deal_unclosed,total_volume);
            double close_comission  =  d_comission/d_volume*close_volume;
            double close_swap       =  d_swap/total_volume*close_volume;
            double close_profit     =  d_profit/total_volume*close_volume;
            if(deal.Close(close_volume,close_profit,close_comission,close_swap))
              {
               opened_volume  -= close_volume;
               d_volume       -= close_volume;
               total_volume   -= close_volume;
               d_comission    -= close_comission;
               d_swap         -= close_swap;
               d_profit       -= close_profit;
              }
           }
        }

Dann wird geprüft, ob eine Position neu eröffnet wurde. Wenn notwendig wird eine neue Position in unsere Basis hinzugefügt.

      if(d_volume>0 && StringFind(columns[4],"in",0)>=0)
        {
         CDeal *deal = new CDeal(e_direction,dt_time,d_volume,d_comission);
         if(CheckPointer(deal)==POINTER_INVALID)
            return false;
         if(!car_Deals.Add(deal))
            return false;
         opened_volume  += d_volume;
        }
     }

Falls zumindest eine Position gespeichert wurde, gibt die Funktion am Ende 'true' zurück, andernfalls 'false'.

   return (car_Deals.Total()>0);
  }

Kommen wir zur nächsten Phase.

4. Vorbereitungen der Klassen für die Arbeit mit den Indikatoren

Wie bereits erwähnt, besteht eine unserer Aufgaben darin, Verlustpositionen zu vermeiden, wenn kein klar definierter Trend vorliegt. Die Frage nach der Trendfeststellung wird regelmäßig gestellt, auch auf diesen Websiten (z.B. Artikel[3] und[4]). Ich gebe nicht vor, eine außergewöhnliche Methoden der Trendfindung entdeckt zu haben. Ich möchte nur eine Technologie des Vergleichs von ausgeführten Positionen und Indikatorwerten für die anschließende Analyse und bewusste Optimierung von Handelssystemen vorschlagen. Betrachten wir daher die am häufigsten verwendeten Indikatoren, die bereits als Standard im Paket des Terminals enthalten sind.

4.1. Die Klasse für die Integration des ATR-Indikators

Der Oszillator "Average True Range" wird als erstes behandelt. Wie wir wissen steigt die Volatilität, wenn ein Trend existiert. Dann steigt auch der Wert dieses Oszillators und kann das anzeigen. Welche Werte sollen wir sichern? Da der EA immer nur arbeiten, wenn sich eine neue Kerze öffnet, sollten wir den Indikatorwert der letzten geschlossenen Kerze sichern, zusammen mit dessen Verhältnis zum vorherigen. Der erste Wert zeigt die aktuelle Volatilität und der zweite dessen Veränderung.

Dieser Indikator ist typisch für jene mit nur einem Indikatorpuffer. Es ist daher sinnvoll, eine Klasse für die Arbeit mit diesem Indikator zu erstellen.

Der Ansatz für die Speicherung der Indikatorwerte wird analog zu der der Positionen sein: Zunächst werden wir eine Klasse für die Speicherung der Indikatorwerte einer Position bilden, dann werden wir eine Klasse der oberen Ebene für die sofortige Arbeit mit dem Indikator für externe Anfragen und Datenspeicherung in Arrays bilden.

Die erste Klasse nennen wir "CValue". Sie verfügt über drei 'privte' Variablen: "Value", um die Indikatorwerte zu speichern, "Dinamic" für um das Verhältnis der beiden letzten Indikatorwerte und "Deal_Ticket" für die Ticketnummer der jeweiligen Position. Wir benötigen die Ticketnummer für spätere Vergleiche der Indikatorwerte mit den Aufträgen währen der Analyse. Alle Werte, die gespeichert werden sollen, werden der Klasse bei ihrer Initialisierung übergeben. Um die benötigten Informationen zu erhalten, erstellen wir die Funktionen "GetTicket", "GetValue" und "GetDinamic", die jeweils die entsprechenden Werte zurückgeben. Weiters erstellen wir die Funktion "GetValues", die den Indikatorwert und dessen "Dinamic" zugleich zurückgeben.

class CValue       : public CObject
  {
private:
   double            Value;            //Indicator's value
   double            Dinamic;          //Dinamics value of indicator
   long              Deal_Ticket;      //Ticket of deal 
   
public:
                     CValue(double value, double dinamic, long ticket);
                    ~CValue(void);
   //---
   long              GetTicket(void)   {  return Deal_Ticket;  }
   double            GetValue(void)    {  return Value;        }
   double            GetDinamic(void)  {  return Dinamic;      }
   void              GetValues(double &value, double &dinamic);
  };

Dann erstellen wir in der obersten Eben eine Klasse, um das Datenarray COneBufferArray zu speichern. Im Block 'privat' enthält sie das Array mit gespeicherten Daten und einem Indikator-Handle. Ich möchte daran erinnern, dass wir beschlossen haben, eine universelle Klasse zu schaffen, die mit allen Ein-Puffer-Indikatoren arbeitet. Aber das Aufrufen verschiedener Indikatoren wird von einem variierenden Satz von Parametern begleitet. Deshalb wird es meiner Meinung nach die einfachste Variante sein, ein Indikator im Hauptprogramm zu initialisieren und erst danach die Klasse zu initialisieren und ihr das Handle des gewünschten Indikators zu übergeben. Für die spätere Identifikation des Indikators wird die Variable "s_Name" in den Bericht geschrieben.

class COneBufferArray   :  CObject
  {
private:
   CArrayObj        *IndicatorValues;     //Array of indicator's values
   
   int               i_handle;            //Handle of indicator
   string            s_Name;
   string            GetIndicatorName(int handle);
   
public:
                     COneBufferArray(int handle);
                    ~COneBufferArray();
   //---
   bool              SaveNewValues(long ticket);
   //---
   double            GetValue(long ticket);
   double            GetDinamic(long ticket);
   bool              GetValues(long ticket, double &value, double &dinamic);
   int               GetIndyHandle(void)  {  return i_handle;     }
   string            GetName(void)        {  return (s_Name!= NULL ? s_Name : "...");       }
  };

Um Daten durch externe Anforderung zu speichern, erstellen wir die Funktion SaveNewValues, die nur einen Parameter enthält - die Ticketnummer. Am Anfang der Funktion überprüfen wir den Zustand des Arrays für die Datenspeicherung und den Indikator-Handle. Im Fehlerfall gibt die Funktion den Wert 'false' zurück.

bool COneBufferArray::SaveNewValues(long ticket)
  {
   if(CheckPointer(IndicatorValues)==POINTER_INVALID)
      return false;
   if(i_handle==INVALID_HANDLE)
      return false;

Danach erhalten wir die Daten vom Indikator. Wenn der Indikator keine Werte erhält, gibt die Funktion 'false' zurück.

   double ind_buffer[];
   if(CopyBuffer(i_handle,0,1,2,ind_buffer)<2)
      return false;

Im nächsten Schritt erstellen Sie die Instanz der Klasse "CValue" und übergeben ihr erforderlichen Werte. Im Falle eines Fehlers bei der Erstellung gibt die Funktion 'false' zurück.

   CValue *object=new CValue(ind_buffer[1], (ind_buffer[0]!=0 ? ind_buffer[1]/ind_buffer[0] : 1), ticket);
   if(CheckPointer(object)==POINTER_INVALID)
      return false;

Falls der Klasse der Indikatorname noch unbekannt ist, könne wir ihn mit der Funktion "GetIndicatorName" vom Chart holen (der Code der Funktion ist unten beigefügt).

   if(s_Name==NULL)
      s_Name=GetIndicatorName(i_handle);

Fügen wir schließlich die neu erstellte Instanz der Datenklasse zum Array und verlassen die Funktion mit der Rückgabe des Ergebnisses.

   return IndicatorValues.Add(object);
  }

Um Daten aus dem Array auf Anforderung zurückzugeben, erstellen wir die Funktionen "GetValue", "GetDinamic" und "GetValues", die die erforderlichen Werte entsprechend der Ticketnummer zurückgeben. 

Der vollständige Code der Klassen ist im Anhang enthalten.

Es wurde diese Klasse verwendet, um Daten der Indikatoren CCI, Volumes, Force, Chaikin Oszillator und der Standardabweichung zu abzufragen.

4.2. Die Klasse für die Integration des MACD-Indikators

Wir ergänzen unsere Kollektion um einen weiteren Standardindikator - den MACD. Wie wir wissen, wird er zur Bestimmung der Kraft und Richtung eines Trends verwendet.

Im Gegensatz zu früher betrachteten Indikatoren verfügt der MACD über 2 Indikatorpuffer (Main und Signal). Daher werden wir auch die Informationen in zwei Zeilen speichern. Unter Verwendung des für die obigen Indikatoren angegebenen Algorithmus lautet der Code der Klasse zum Speichern der Daten wie folgt:

class CMACDValue      : public CObject
  {
private:
   double            Main_Value;        //Main line value
   double            Main_Dinamic;      //Dinamics value of main lime
   double            Signal_Value;      //Signal line value
   double            Signal_Dinamic;    //Dinamics value of signal lime
   long              Deal_Ticket;       //Ticket of deal 
   
public:
                     CMACDValue(double main_value, double main_dinamic, double signal_value, double signal_dinamic, long ticket);
                    ~CMACDValue(void);
   //---
   long              GetTicket(void)         {  return Deal_Ticket;     }
   double            GetMainValue(void)      {  return Main_Value;      }
   double            GetMainDinamic(void)    {  return Main_Dinamic;    }
   double            GetSignalValue(void)    {  return Signal_Value;    }
   double            GetSignalDinamic(void)  {  return Signal_Dinamic;  }
   void              GetValues(double &main_value, double &main_dinamic, double &signal_value, double &signal_dinamic);
  };

In der Klasse wurden die Teile zum Arbeiten mit dem Daten abgeändert. Im Gegensatz zur universellen Klasse, die in Abschnitt 4.1 beschrieben wird, arbeitet diese Klasse mit einem bestimmten Indikator. Wenn also die Klasse initialisiert wird, übergibt sie nicht den Indikator-Handle, sondern die Parameter, die für ihre Initialisierung notwendig sind. Der Indikator wird direkt in der Klasse initialisiert.

class CMACD
  {
private:
   CArrayObj        *IndicatorValues;     //Array of indicator's values
   
   int               i_handle;            //Handle of indicator
   
public:
                     CMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price);
                    ~CMACD();
   //---
   bool              SaveNewValues(long ticket);
   //---
   double            GetMainValue(long ticket);
   double            GetMainDinamic(long ticket);
   double            GetSignalValue(long ticket);
   double            GetSignalDinamic(long ticket);
   bool              GetValues(long ticket, double &main_value, double &main_dinamic, double &signal_value, double &signal_dinamic);                
  };

Die ganze Logik der Funktionen bleibt unverändert, die Änderungen betreffen nur die Menge der Indikatorpuffer und der Variablen.

bool CMACD::SaveNewValues(long ticket)
  {
   if(CheckPointer(IndicatorValues)==POINTER_INVALID)
      return false;
   if(i_handle==INVALID_HANDLE)
      return false;
   double main[], signal[];
   if(!CopyBuffer(i_handle,0,1,2,main)<2 || !CopyBuffer(i_handle,1,1,2,signal)<2)
      return false;
   CMACDValue *object=new CMACDValue(main[1], (main[0]!=0 ? main[1]/main[0] : 1), signal[1], (signal[0]!=0 ? signal[1]/signal[0] : 1), ticket);
   if(CheckPointer(object)==POINTER_INVALID)
      return false;
   return IndicatorValues.Add(object);
  }

Eine ähnliche Logik der Skalierung gilt für jede beliebige Anzahl von Indikatorpuffern. Wenn wir nur ausgewählte Indikatorpuffer sichern wollen, genügt es, diese in der Funktion "SaveNewValues" der jeweiligen Klasse zu beschreiben. Ich würde jedoch nicht empfehlen, dies zum jetzigen Zeitpunkt zu tun. Bis jetzt wissen wir noch nicht, ob es eine Verbindung zwischen Positionen mit Gewinn und Werten bestimmter Indikatorpuffer gibt und, wenn es sie gibt, wie stark die Verbindung ist.

Um sozusagen das Material zu vervollständigen, möchte ich ein weiteres Beispiel für die Speicherung von Indikatordaten mit 3 Datenpuffer anführen.

4.3. Die Klasse für die Integration des ADX-Indikators

Der ADX-Indikator wird häufig zur Bestimmung von Kraft und Richtung eines Trends verwendet. Das entspricht unserer Aufgabe und wird daher zu Recht in unsere "Sparbüchse" aufgenommen.

Er hat 3 Indikatorpuffer und entsprechend der oben vorgeschlagenen Skalierungsmethode erhöhen wir die Anzahl der gespeicherten Variablen. Die Klasse zu Datenspeicherung sieht also wie folgt aus:

class CADXValue      : public CObject
  {
private:
   double            ADX_Value;        //ADX value
   double            ADX_Dinamic;      //Dinamics value of ADX
   double            PDI_Value;        //+DI value
   double            PDI_Dinamic;      //Dinamics value of +DI
   double            NDI_Value;        //-DIvalue
   double            NDI_Dinamic;      //Dinamics value of -DI
   long              Deal_Ticket;      //Ticket of deal 
   
public:
                     CADXValue(double adx_value, double adx_dinamic, double pdi_value, double pdi_dinamic, double ndi_value, double ndi_dinamic, long ticket);
                    ~CADXValue(void);
   //---
   long              GetTicket(void)         {  return Deal_Ticket;     }
   double            GetADXValue(void)       {  return ADX_Value;       }
   double            GetADXDinamic(void)     {  return ADX_Dinamic;     }
   double            GetPDIValue(void)       {  return PDI_Value;       }
   double            GetPDIDinamic(void)     {  return PDI_Dinamic;     }
   double            GetNDIValue(void)       {  return NDI_Value;       }
   double            GetNDIDinamic(void)     {  return NDI_Dinamic;     }
   void              GetValues(double &adx_value, double &adx_dinamic, double &pdi_value, double &pdi_dinamic, double &ndi_value, double &ndi_dinamic);
  };

 Ein Erhöhen der gespeicherten Daten führt auch zu Änderungen in der Klasse, die mit dem Array arbeitet.

class CADX
  {
private:
   CArrayObj        *IndicatorValues;     //Array of indicator's values
   
   int               i_handle;            //Handle of indicator
   
public:
                     CADX(string symbol, ENUM_TIMEFRAMES timeframe, uint period);
                    ~CADX();
   //---
   bool              SaveNewValues(long ticket);
   //---
   double            GetADXValue(long ticket);
   double            GetADXDinamic(long ticket);
   double            GetPDIValue(long ticket);
   double            GetPDIDinamic(long ticket);
   double            GetNDIValue(long ticket);
   double            GetNDIDinamic(long ticket);
   bool              GetValues(long ticket,double &adx_value,double &adx_dinamic,double &pdi_value,double &pdi_dinamic,double &ndi_value,double &ndi_dinamic);
  };
bool CADX::SaveNewValues(long ticket)
  {
   if(CheckPointer(IndicatorValues)==POINTER_INVALID)
      return false;
   if(i_handle==INVALID_HANDLE)
      return false;
   double adx[], pdi[], ndi[];
   if(!CopyBuffer(i_handle,0,1,2,adx)<2 || !CopyBuffer(i_handle,1,1,2,pdi)<2 || !CopyBuffer(i_handle,1,1,2,ndi)<2)
      return false;
   CADXValue *object=new CADXValue(adx[1], (adx[0]!=0 ? adx[1]/adx[0] : 1), pdi[1], (pdi[0]!=0 ? pdi[1]/pdi[0] : 1), ndi[1], (ndi[0]!=0 ? ndi[1]/ndi[0] : 1), ticket);
   if(CheckPointer(object)==POINTER_INVALID)
      return false;
   return IndicatorValues.Add(object);
  }

Ich glaube, dass jetzt jeder das Prinzip der Klassenbildung für die Arbeit mit den Indikatoren versteht. Deshalb werden wir den Code für die folgenden Indikatoren nicht beschreiben, um das Volumen des Artikels nicht aufzublähen. Ähnlich wie bei der "Sparbüchse" für die Analyse habe ich noch BW MFI und Alligator hinzugefügt. Jeder, der will, kann sich mit dem vollständigen Code der Klasse im Anhang vertraut machen.

5. Vorbereiten des Berichtsformats für die Ergebnisausgabe

Nach dem Einholen der Werte der entsprechenden Indikatoren zum Zeitpunkt der Auftragserteilung ist es an der Zeit, über die Analyse der gewonnenen Daten nachzudenken. Meiner Meinung nach wird es am deutlichsten sein, Diagramme der Abhängigkeit der Gewinne der Positionen von den jeweiligen Indikatorwerten zu erstellen. Ich schlage vor, Diagramme nach der von Victor im Artikel[2] vorgeschlagenen Technologie zu erstellen.

Halten wir fest: Die Optimierung des Handels, geschieht in Abhängigkeit des Gewinns von den Indikatorwerten. Wenn ein Leser versucht, einen Handel zu wiederholen, muss er Abhängigkeiten zwischen der Anzahl der Handelsgeschäfte und den Indikatorwerten suchen.

Erstellen wir zuerst die Klassen, die die Informationen über jeden Indikator vorbereiten.

5.1. Die universelle Klasse für die Indikatoren mit nur einem Puffer

Zuerst wird eine Klasse für die Arbeit mit Indikatoren mit nur einem Puffer erstellt. Welche Informationen können wir analysieren? Denken wir daran, dass wir den Wert des Indikatorpuffers und die Dynamik seiner Veränderung gespeichert haben. Deshalb können wir Folgendes analysieren:

  • Die Abhängigkeit des Gewinns aus den durchgeführten Operationen von den Indikatorwerten zum Zeitpunkt der Eröffnung der Position,
  • die Auswirkung der Bewegungsrichtung der Indikatorlinie bei einem Gewinn,
  • sowie die komplexe Auswirkungen des Indikatorwertes und seiner Dynamik auf das Ergebnis der durchgeführten Operationen.

Für das Zeichnen der Diagramme legen wir die Klasse CStaticOneBuffer an. Diese Klasse erhält einen Pointer auf das gespeicherte Datenarray, "DataArray", das Array "Value" mit den Indikatorwerten mit dem voreingestellten Schrittweite "d_Step", und den beiden Arrays des Gesamtgewinns getrennt für Kauf- und Verkaufsposition. Achtung: Die Arrays für die Berechnung des Gesamtgewinns sind zweidimensional. Die Größe der ersten Messung entspricht der Größe des Arrays "Value". Die zweite Messung enthält drei Elemente: Erstens - für fallende Indikatorwerte, zweitens - für eine horizontale Indikatorbewegung und drittens - für steigende Werte.

Bei der Klasseninitialisierung wird in den Parametern eine Referenz auf das Datenarray und die Schrittweite für Indikatorwerte angegeben.

class CStaticOneBuffer  :  CObject
  {
private:
   COneBufferArray  *DataArray;
   
   double            d_Step;                    //Step in values Array
   double            Value[];                   //Array of values
   double            Long_Profit[][3];          //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   double            Short_Profit[][3];         //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   
   bool              AdValues(double value, double dinamic, double profit, ENUM_POSITION_TYPE type);
   int               GetIndex(double value);
   bool              Sort(void);
   
public:
                     CStaticOneBuffer(COneBufferArray *data, double step);
                    ~CStaticOneBuffer();
   bool              Ad(long ticket, double profit, ENUM_POSITION_TYPE type);
   string            HTML_header(void);
   string            HTML_body(void);
  };

In der Initialisierungs-Funktion werden die übergebenen Werte gespeichert und die verwendeten Arrays zurückgesetzt.

CStaticOneBuffer::CStaticOneBuffer(COneBufferArray *data,double step)
  {
   DataArray   =  data;
   d_Step      =  step;
   ArrayFree(Value);
   ArrayFree(Long_Profit);
   ArrayFree(Short_Profit);
  }

Um statistische Informationen zu sammeln, erstellen wir die Funktion Ad, mit der wir Informationen einer Position übermitteln. Innerhalb der Funktion werden die entsprechenden Parameter des Indikators lokalisiert und die Daten werden in den entsprechenden Elementen des Array gespeichert.

bool CStaticOneBuffer::Ad(long ticket,double profit,ENUM_POSITION_TYPE type)
  {
   if(CheckPointer(DataArray)==POINTER_INVALID)
      return false;

   double value, dinamic;
   if(!DataArray.GetValues(ticket,value,dinamic))
      return false;
   value = NormalizeDouble(value/d_Step,0)*d_Step;
   return AdValues(value,dinamic,profit,type);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CStaticOneBuffer::AdValues(double value,double dinamic,double profit,ENUM_POSITION_TYPE type)
  {
   int index=GetIndex(value);
   if(index<0)
      return false;
   
   switch(type)
     {
      case POSITION_TYPE_BUY:
        if(dinamic<1)
           Long_Profit[index,0]+=profit;
        else
           if(dinamic==1)
              Long_Profit[index,1]+=profit;
           else
              Long_Profit[index,2]+=profit;
        break;
      case POSITION_TYPE_SELL:
        if(dinamic<1)
           Short_Profit[index,0]+=profit;
        else
           if(dinamic==1)
              Short_Profit[index,1]+=profit;
           else
              Short_Profit[index,2]+=profit;
        break;
     }
   return true;
  }

Für die Darstellung der Diagramme erstellen wir die Funktionen "HTML_header" und "HTML_body", in denen Teile des Codes des HTML-Seitenkopfes und des HTML-Inhalts generiert werden. Die Prinzipien der Erstellung von Code von HTML-Seiten sind im Artikel[2] ausführlich beschrieben, ich werde nicht weiter darauf eingehen. Der vollständige Code der Funktion ist im Anhang enthalten.

5.2. Die Klasse für die Anzeige der Daten des MFI-Indikators von Bill Williams

Als nächstes betrachten wir den MFI-Indikator von Bill Williams. Durch die Art und Weise der Darstellung auf dem Chart ähnelt er den Indikatoren mit einem Puffer, aber es gibt einen Unterschied: Der BW MFI hat auch einen Puffer für Farbe, der ebenfalls einen Wert hat. Gleichzeitig sind wir im Gegensatz zu den Indikatoren mit zwei Puffern nicht an der Dynamik der Farbveränderung interessiert. Deshalb werden zu den oben vorgeschlagenen Diagrammen der Indikatoren mit einem Puffer die Diagramme der Gewinnabhängigkeit von der Indikatorfarbe, sowie die Diagramme der komplexen Auswirkungen der Werte und der Indikatorendynamik in Abhängigkeit von der aktuellen Indikatorfarbe hinzugefügt.

Für die Erhebung statistischer Daten und die Erstellung analytischer Diagramme erstellen wir die Klasse CStaticBWMFI. Die Struktur der Klasse ist analog zu denen oben. Die Änderungen betreffen die Arrays der Gewinnberechnung, die hat jetzt drei Dimensionen. Die dritte Dimension enthält 4 Elemente gemäß der Anzahl der verwendeten Farben.

class CStaticBWMFI  :  CObject
  {
private:
   CBWMFI           *DataArray;
   
   double            d_Step;                       //Step in values Array
   double            Value[];                      //Array of values
   double            Long_Profit[][3][4];          //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   double            Short_Profit[][3][4];         //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   
   bool              AdValues(double value, double _color, double dinamic, double profit, ENUM_POSITION_TYPE type);
   int               GetIndex(double value);
   bool              Sort(void);
   
public:
                     CStaticBWMFI(CBWMFI *data, double step);
                    ~CStaticBWMFI();
   bool              Ad(long ticket, double profit, ENUM_POSITION_TYPE type);
   string            HTML_header(void);
   string            HTML_body(void);
  };

Der gesamte Code der Klasse findet sich im Anhang.

5.3. Die Klasse für die Anzeige des MACD-Indikators

Betrachten wir im Weiteren den MACD. Wie man weiß hat er zwei Puffer: Das Histogramm und die Signallinie. Nach den Regeln der Interpretation dieser Indikatorsignale sind der Wert des Histogramms und die Bewegungsrichtung sowie die Position der Signallinie (oberhalb oder unterhalb des Histogramms) wichtig. Für eine umfassende Analyse werden wir eine Reihe von Diagrammen erstellen.

  • Die Abhängigkeit des Gewinns einer Position von den Werten des Histogramms und seiner Richtung (getrennt und komplex).
  • Die Abhängigkeit des Gewinns einer Position von den Werten der Signallinie und ihrer Richtung.
  • Die Abhängigkeit des Gewinns einer Position von der Signallinie in Bezug auf das Histogramm.
  • Die Abhängigkeit des Gewinns von der gemeinsamen Wirkung der Werte des Histogramms, seiner Richtung und der Position der Signallinie in Bezug auf das Histogramm.
Für die Datenanalyse legen wir die Klasse CStaticMACD an. Beim Erstellen der Klasse werden die gleichen Prinzipien angewendet wie beim Erstellen der statistischen Klassen von oben. Es wird eine dreidimensionale Gliederung der Gewinne nach den Werten der Histogramme geben, aber im Gegensatz zur vorherigen Klasse enthält die dritte Dimension 3 Elemente entsprechend der Position der Signallinie in Bezug auf das Histogramm (darunter, gleich und darüber). Wir ergänzen außerdem ein weiteres zweidimensionales Array für die Berechnung des Gewinns durch die Werte der Signallinie.

class CStaticMACD  :  CObject
  {
private:
   CMACD            *DataArray;
   
   double            d_Step;                       //Step in values Array
   double            Value[];                      //Array of values
   double            SignalValue[];                //Array of values
   double            Long_Profit[][3][3];          //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   double            Short_Profit[][3][3];         //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   double            Signal_Long_Profit[][3];      //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   double            Signal_Short_Profit[][3];     //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2
   
   bool              AdValues(double main_value, double main_dinamic, double signal_value, double signal_dinamic, double profit, ENUM_POSITION_TYPE type);
   int               GetIndex(double value);
   int               GetSignalIndex(double value);
   bool              Sort(void);
   
public:
                     CStaticMACD(CMACD *data, double step);
                    ~CStaticMACD();
   bool              Ad(long ticket, double profit, ENUM_POSITION_TYPE type);
   string            HTML_header(void);
   string            HTML_body(void);
  };

Wie man sieht, bleiben die Struktur der Klasse, der Name und die Bestimmung der Funktionen gleich. Die Änderungen betrafen nur den Inhalt der Funktionen, die dem Anhang beigefügt sind.

5.4. Die Klasse für die Anzeige des ADX-Indikators

Kommen wir jetzt zur Klasse CStaticADX. Sie sammelt die Werte des ADX. Die Regeln der Interpretation des Indikatorsignals: Die Linie +DI zeigt den steigenden Trend, -DI den negativen und der ADX - den Trend insgesamt. Ausgehend von diesen Regeln erstellen wir die Diagramme der Abhängigkeiten:

  • Die Abhängigkeit des Gewinns vom +DI-Wert, seiner Richtung und der Lage in Bezug auf den ADX;
  • Die Abhängigkeit des Gewinns vom -DI-Wert, seiner Richtung und der Lage in Bezug auf den ADX.

Bei der Erstellung der Klasse für das Sammeln von Statistiken habe ich mich entschlossen, ein wenig mehr Daten zu sammeln. Infolge werden folgende Informationen gesichert:

  • Indikatorwert
  • Richtung der Linie;
  • Lage in Bezug der anderen Trendlinie;
  • Richtung der anderen Trendlinie;
  • Lage in Bezug zur Linie des ADX;
  • Richtung des ADX.
Bei einer solchen Aufgliederung der Informationen und den Ansätzen, die in früheren Klassen verwendet werden sollten, wird ein sechsdimensionaler Array benötigt. Arrays dieser Größe werden in MQL jedoch nicht unterstützt. Um diese Aufgabe zu lösen, wurde die Hilfsklasse CProfitData angelegt, in der alle notwendigen Informationen gespeichert werden.

class CProfitData
  {
   public:
   double         Value;
   double         LongProfit[3]/*UppositePosition*/[3]/*Upposite Direct*/[3]/*ADX position*/[3]/*ADX direct*/;
   double         ShortProfit[3]/*UppositePosition*/[3]/*Upposite Direct*/[3]/*ADX position*/[3]/*ADX direct*/;
   
                  CProfitData(void) 
                  {  ArrayInitialize(LongProfit,0); ArrayInitialize(ShortProfit,0);  }
                 ~CProfitData(void) {};
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CStaticADX  :  CObject
  {
private:
   CADX             *DataArray;
   
   double            d_Step;           //Step in values Array
   CProfitData      *PDI[][3];         //Array of values +DI
   CProfitData      *NDI[][3];         //Array of values -DI
   
   bool              AdValues(double adx_value, double adx_dinamic, double pdi_value, double pdi_dinamic, double ndi_value, double ndi_dinamic, double profit, ENUM_POSITION_TYPE type);
   int               GetPDIIndex(double value);
   int               GetNDIIndex(double value);
   bool              Sort(void);
   
public:
                     CStaticADX(CADX *data, double step);
                    ~CStaticADX();
   bool              Ad(long ticket, double profit, ENUM_POSITION_TYPE type);
   string            HTML_header(void);
   string            HTML_body(void);
  };

In anderer Hinsicht blieben die Ansätze und Preise der früheren Klassen erhalten. Der gesamte Code der Klasse findet sich im Anhang.

5.5. Die Klasse für die Anzeige des Alligator-Indikators

Am Ende dieses Teils erstellen wir eine Klasse für das Sammeln der Statik des Alligator-Indikators. Die Signale dieses Indikators basieren auf drei gleitenden Durchschnitten mit unterschiedlichen Perioden. Daher sind die Indikatorwerte selbst für uns nicht von Bedeutung. Viel wichtiger sind die Richtungen und die relative Positionen der Linien.

Um diese Signale des Indikators spezifischer zu machen, führen wir die Trendbestimmung durch die Position der Linien ein. Wenn die Linie der LIPS über der der TEETH liegt, während zugleich die der TEETH über der des von JAW ist - gilt das Aufwärtstrend. Liegt LIPS unter den TEETH, und TEETH unter JAW - gilt das als Abwärtstrend. Liegt diese Ordnung der Linien nicht vor, ist eine Trendrichtung unbestimmt, der Markt ist 'flat'.

Entsprechend werden Abhängigkeitsdiagramme aus Trendrichtungssignalen und der Dynamik aus den Indikatorlinien aufgebaut.

Nach den oben angegebenen Eingabedaten erstellen wir die Klasse CStaticAlligator. Die Prinzipien des Klassenaufbaus wurden aus früheren Klassen übernommen.

class CStaticAlligator  :  CObject
  {
private:
   CAlligator             *DataArray;
   
   double            Long_Profit[3]/*Signal*/[3]/*JAW direct*/[3]/*TEETH direct*/[3]/*LIPS direct*/;  //Array of long deals profit
   double            Short_Profit[3]/*Signal*/[3]/*JAW direct*/[3]/*TEETH direct*/[3]/*LIPS direct*/; //Array of short feals profit
   
   bool              AdValues(double jaw_value, double jaw_dinamic, double teeth_value, double teeth_dinamic, double lips_value, double lips_dinamic, double profit, ENUM_POSITION_TYPE type);
   
public:
                     CStaticAlligator(CAlligator *data);
                    ~CStaticAlligator();
   bool              Ad(long ticket, double profit, ENUM_POSITION_TYPE type);
   string            HTML_header(void);
   string            HTML_body(void);
  };

Der gesamte Code der Klasse findet sich im Anhang.

6. Erstellen des EAs für das Sammeln und Analysieren der Daten

Nun, da alle vorbereitenden Arbeiten abgeschlossen sind, erstellen wir einen EA, der sofort im Strategietester für das Sammeln und Analysieren der Daten gestartet wird. In den Eingabeparametern des EAs geben wir zunächst den Namen der Datei für den Testbericht für die Analyse an. Weiter den verwendeten Zeitraum und alle notwendigen Parameter der verwendeten Indikatoren.

input string            FileName          =  "Kalman_test.html"   ;
input ENUM_TIMEFRAMES   Timefarame        =  PERIOD_CURRENT       ;
input string            s1                =  "ADX"                ;  //---
input uint              ADX_Period        =  14                   ;
input string            s2                =  "Alligator"          ;  //---
input uint              JAW_Period        =  13                   ;
input uint              JAW_Shift         =  8                    ;
input uint              TEETH_Period      =  8                    ;
input uint              TEETH_Shift       =  5                    ;
input uint              LIPS_Period       =  5                    ;
input uint              LIPS_Shift        =  3                    ;
input ENUM_MA_METHOD    Alligator_Method  =  MODE_SMMA            ;
input ENUM_APPLIED_PRICE Alligator_Price  =  PRICE_MEDIAN         ;
input string            s3                =  "ATR"                ;  //---
input uint              ATR_Period        =  14                   ;
input string            s4                =  "BW MFI"             ;  //---
input ENUM_APPLIED_VOLUME BWMFI_Volume    =  VOLUME_TICK          ;
input string            s5                =  "CCI"                ;  //---
input uint              CCI_Period        =  14                   ;
input ENUM_APPLIED_PRICE CCI_Price        =  PRICE_TYPICAL        ;
input string            s6                =  "Chaikin"            ;  //---
input uint              Ch_Fast_Period    =  3                    ;
input uint              Ch_Slow_Period    =  14                   ;
input ENUM_MA_METHOD    Ch_Method         =  MODE_EMA             ;
input ENUM_APPLIED_VOLUME Ch_Volume       =  VOLUME_TICK          ;
input string            s7                =  "Force Index"        ;  //---
input uint              Force_Period      =  14                   ;
input ENUM_MA_METHOD    Force_Method      =  MODE_SMA             ;
input ENUM_APPLIED_VOLUME Force_Volume    =  VOLUME_TICK          ;
input string            s8                =  "MACD"               ;  //---
input uint              MACD_Fast         =  12                   ;
input uint              MACD_Slow         =  26                   ;
input uint              MACD_Signal       =  9                    ;
input ENUM_APPLIED_PRICE MACD_Price       =  PRICE_CLOSE          ;
input string            s9                =  "Standart Deviation" ;  //---
input uint              StdDev_Period     =  14                   ;
input uint              StdDev_Shift      =  0                    ;
input ENUM_MA_METHOD    StdDev_Method     =  MODE_SMA             ;
input ENUM_APPLIED_PRICE StdDev_Price     =  PRICE_CLOSE          ;
input string            s10               =  "Volumes"            ;  //---
input ENUM_APPLIED_VOLUME Applied_Volume  =  VOLUME_TICK          ;

Then declare instances of all the above described classes.

CArrayObj         *Deals;
CADX              *ADX;
CAlligator        *Alligator;
COneBufferArray   *ATR;
CBWMFI            *BWMFI;
COneBufferArray   *CCI;
COneBufferArray   *Chaikin;
COneBufferArray   *Force;
CMACD             *MACD;
COneBufferArray   *StdDev;
COneBufferArray   *Volume;
CStaticOneBuffer  *IndicatorsStatic[];
CStaticBWMFI      *BWMFI_Stat;
CStaticMACD       *MACD_Stat;
CStaticADX        *ADX_Stat;
CStaticAlligator  *Alligator_Stat;

6.1. Die Initialisierungsfunktion des EAs

Soweit unser EA für die Datenanalyse im Strategietester vorgesehen ist, prüfen wir zunächst die Umgebung, in der er gestartet wird. Beginnt der Start außerhalb Tester, muss die Initialisierung abgebrochen werden.

int OnInit()
  {
//---
   if(!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION))
      return INIT_FAILED;

Dann werden wir die Daten aus der Datei des Testberichts analysieren. Nach dem Lesen der Daten aus dem Bericht wird die Instanz der Klasse für diese Analyse nicht mehr benötigt und wir entfernen sie aus dem Speicher.

   CParsing *Parsing =  new CParsing(Deals);
   if(CheckPointer(Parsing)==POINTER_INVALID)
      return INIT_FAILED;
   if(!Parsing.ReadFile(FileName) || CheckPointer(Deals)==POINTER_INVALID || Deals.Total()<=0)
     {
      delete Parsing;
      return INIT_FAILED;
     }
   delete Parsing;

Danach werden die Klassen der Indikatoren initialisiert.

//---
   ADX =  new CADX(_Symbol,Timefarame,ADX_Period);
   if(CheckPointer(ADX)==POINTER_INVALID)
      return INIT_FAILED;
//---
   Alligator =  new CAlligator(_Symbol,Timefarame,JAW_Period,JAW_Shift,TEETH_Period,TEETH_Shift,LIPS_Period,LIPS_Shift,Alligator_Method,Alligator_Price);
   if(CheckPointer(Alligator)==POINTER_INVALID)
      return INIT_FAILED;
//---
   int handle=iATR(_Symbol,Timefarame,ATR_Period);
   if(handle>0)
     {
      ATR      =  new COneBufferArray(handle);
      if(CheckPointer(ATR)==POINTER_INVALID)
         return INIT_FAILED;
     }
//---
   BWMFI    =  new CBWMFI(_Symbol,Timefarame,BWMFI_Volume);
   if(CheckPointer(BWMFI)==POINTER_INVALID)
      return INIT_FAILED;
//---
   handle=iCCI(_Symbol,Timefarame,CCI_Period,CCI_Price);
   if(handle>0)
     {
      CCI      =  new COneBufferArray(handle);
      if(CheckPointer(CCI)==POINTER_INVALID)
         return INIT_FAILED;
     }
//---
   handle=iChaikin(_Symbol,Timefarame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume);
   if(handle>0)
     {
      Chaikin  =  new COneBufferArray(handle);
      if(CheckPointer(Chaikin)==POINTER_INVALID)
         return INIT_FAILED;
     }
//---
   handle=iForce(_Symbol,Timefarame,Force_Period,Force_Method,Force_Volume);
   if(handle>0)
     {
      Force    =  new COneBufferArray(handle);
      if(CheckPointer(Force)==POINTER_INVALID)
         return INIT_FAILED;
     }
//---
   MACD     =  new CMACD(_Symbol,Timefarame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price);
   if(CheckPointer(MACD)==POINTER_INVALID)
      return INIT_FAILED;
//---
   handle=iStdDev(_Symbol,Timefarame,StdDev_Period,StdDev_Shift,StdDev_Method,StdDev_Price);
   if(handle>0)
     {
      StdDev   =  new COneBufferArray(handle);
      if(CheckPointer(StdDev)==POINTER_INVALID)
         return INIT_FAILED;
     }
//---
   handle=iVolumes(_Symbol,Timefarame,Applied_Volume);
   if(handle>0)
     {
      Volume   =  new COneBufferArray(handle);
      if(CheckPointer(Volume)==POINTER_INVALID)
         return INIT_FAILED;
     }

Am Ende der Funktion OnInit wird der Zähler der Positionen auf 0 gestellt, und die Funktion verlassen.

   cur_ticket   =  0;
//---
   return(INIT_SUCCEEDED);
  }

6.2. Das Sammelnd der statistischen Daten

In der Funktion OnTick werden die Daten der Indikatoren gesammelt. Zu Beginn wird geprüft, ob alle Informationen der Positionen vorliegen. Wenn ja, wird die Funktion verlassen.

void OnTick()
  {
   if(cur_ticket>=Deals.Total())
      return;

Im nächsten Schritt wird der Zeitpunkt der analysierten Position mit dem Zeitpunkt der Verarbeitung des Ticks verglichen. Wenn die Eröffnungszeit der Position noch nicht gekommen ist, wird die Funktion verlassen.

   CDeal *object  =  Deals.At(cur_ticket);
   if(object.GetTime()>TimeCurrent())
      return;

Wenn die vorherige Prüfungen bestanden wurden, wird der Status der Instanzen der Indikatoren geprüft und die notwendigen Informationen durch den Aufruf der Funktion SaveNewValues ​​für jeden Indikator gespeichert.

   if(CheckPointer(ADX)!=POINTER_INVALID)
      ADX.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(Alligator)!=POINTER_INVALID)
      Alligator.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(ATR)!=POINTER_INVALID)
      ATR.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(BWMFI)!=POINTER_INVALID)
      BWMFI.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(CCI)!=POINTER_INVALID)
      CCI.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(Chaikin)!=POINTER_INVALID)
      Chaikin.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(Force)!=POINTER_INVALID)
      Force.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(MACD)!=POINTER_INVALID)
      MACD.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(StdDev)!=POINTER_INVALID)
      StdDev.SaveNewValues(cur_ticket);
   //---
   if(CheckPointer(Volume)!=POINTER_INVALID)
      Volume.SaveNewValues(cur_ticket);

Am Ende der Funktion wird der Zähler der bearbeiteten Positionen erhöht und die Funktion verlassen.

   cur_ticket++;
   return;
  }

6.3. Die Ausgabe der Grafiken für die Analyse

Die Datenanalysieren und die Ausgabe des Berichtes wird in der Funktion OnTester implementiert. Wird die Funktion ausgeführt, prüft sie die Anzahl der Positionen für die Analyse.

double OnTester()
  {
   double ret=0.0;
   int total=Deals.Total();

Wenn eine Analyse erforderlich ist, werden die statistischen Klassen initialisiert.

Um die nachfolgende Verarbeitung zu erleichtern, werden die die Klassen der Indikatoren mit einem Puffer in einem Array gesammelt. Daher zählen wir parallel zur Initialisierung die verwendeten Ein-Puffer-Indikatoren.

   int total_indy=0;
   if(total>0)
     {
      if(CheckPointer(ADX)!=POINTER_INVALID)
         ADX_Stat=new CStaticADX(ADX,1);
      //---
      if(CheckPointer(Alligator)!=POINTER_INVALID)
         Alligator_Stat=new CStaticAlligator(Alligator);
      //---
      if(CheckPointer(ATR)!=POINTER_INVALID)
        {
         CStaticOneBuffer *indy=new CStaticOneBuffer(ATR,_Point*10);
         if(CheckPointer(indy)!=POINTER_INVALID)
           {
            if(ArrayResize(IndicatorsStatic,total_indy+1)>0)
              {
               IndicatorsStatic[total_indy]=indy;
               total_indy++;
              }
           }
        }
      //---
      if(CheckPointer(BWMFI)!=POINTER_INVALID)
         BWMFI_Stat=new CStaticBWMFI(BWMFI,_Point*100);
      //---
      if(CheckPointer(CCI)!=POINTER_INVALID)
        {
         CStaticOneBuffer *indy=new CStaticOneBuffer(CCI,10);
         if(CheckPointer(indy)!=POINTER_INVALID)
            if(ArrayResize(IndicatorsStatic,total_indy+1)>0)
              {
               IndicatorsStatic[total_indy]=indy;
               total_indy++;
              }
        }
      //---
      if(CheckPointer(Chaikin)!=POINTER_INVALID)
        {
         CStaticOneBuffer *indy=new CStaticOneBuffer(Chaikin,100);
         if(CheckPointer(indy)!=POINTER_INVALID)
            if(ArrayResize(IndicatorsStatic,total_indy+1)>0)
              {
               IndicatorsStatic[total_indy]=indy;
               total_indy++;
              }
        }
      //---
      if(CheckPointer(Force)!=POINTER_INVALID)
        {
         CStaticOneBuffer *indy=new CStaticOneBuffer(Force,0.1);
         if(CheckPointer(indy)!=POINTER_INVALID)
            if(ArrayResize(IndicatorsStatic,total_indy+1)>0)
              {
               IndicatorsStatic[total_indy]=indy;
               total_indy++;
              }
        }
      //---
      if(CheckPointer(MACD)!=POINTER_INVALID)
         MACD_Stat=new CStaticMACD(MACD,_Point*10);
      //---
      if(CheckPointer(StdDev)!=POINTER_INVALID)
        {
         CStaticOneBuffer *indy=new CStaticOneBuffer(StdDev,_Point*10);
         if(CheckPointer(indy)!=POINTER_INVALID)
            if(ArrayResize(IndicatorsStatic,total_indy+1)>0)
              {
               IndicatorsStatic[total_indy]=indy;
               total_indy++;
              }
        }
      //---
      if(CheckPointer(Volume)!=POINTER_INVALID)
        {
         CStaticOneBuffer *indy=new CStaticOneBuffer(Volume,100);
         if(CheckPointer(indy)!=POINTER_INVALID)
            if(ArrayResize(IndicatorsStatic,total_indy+1)>0)
              {
               IndicatorsStatic[total_indy]=indy;
               total_indy++;
              }
        }
     }

Als nächstes werden die Indikatordaten mit den relevanten Positionen verglichen und die Informationen über die Richtungen, die für die Ausgabe von grafischen Berichten erforderlich sind, verglichen. Dazu wird in jeder statistischen Klasse die Funktion "Ad" aufgerufen und über ihre Parametern die Informationen der Position übergeben.

   for(int i=0;i<total;i++)
     {
      CDeal               *deal     =  Deals.At(i);
      ENUM_POSITION_TYPE   type     =  deal.Type();
      double               d_profit =  deal.GetProfit();
      
      for(int ind=0;ind<total_indy;ind++)
         IndicatorsStatic[ind].Ad(i,d_profit,type);
      if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID)
         BWMFI_Stat.Ad(i,d_profit,type);
      if(CheckPointer(MACD_Stat)!=POINTER_INVALID)
         MACD_Stat.Ad(i,d_profit,type);
      if(CheckPointer(ADX_Stat)!=POINTER_INVALID)
         ADX_Stat.Ad(i,d_profit,type);
      if(CheckPointer(Alligator_Stat)!=POINTER_INVALID)
         Alligator_Stat.Ad(i,d_profit,type);
     }

Nachdem die Daten gruppiert wurden, wird die Berichtsdatei Report.html erstellt und im "Gemeinsamen Dateiordner" des Terminals gespeichert.

   if(total_indy>0 || CheckPointer(BWMFI_Stat)!=POINTER_INVALID || CheckPointer(MACD_Stat)!=POINTER_INVALID
      || CheckPointer(ADX_Stat)!=POINTER_INVALID || CheckPointer(Alligator_Stat)!=POINTER_INVALID )
     {
      int handle=FileOpen("Report.html",FILE_WRITE|FILE_TXT|FILE_COMMON);
      if(handle<0)
         return ret;

Zuerst wird der Seitenkopf der HTLM-Datei geschrieben.

      FileWrite(handle,"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
      FileWrite(handle,"<html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
      FileWrite(handle,"<title>Deals to Indicators</title> <!-- - -->");
      FileWrite(handle,"<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js\" type=\"text/javascript\"></script>");
      FileWrite(handle,"<script src=\"https://code.highcharts.com/highcharts.js\" type=\"text/javascript\"></script>");
      FileWrite(handle,"<!-- - --> <script type=\"text/javascript\">$(document).ready(function(){");

Dann werden die Funktionen für den Seitenkopf von allen statistischen Klassen aufgerufen, damit sie zum Zeichnen der Diagramme alles in die Datei schreiben.

      for(int ind=0;ind<total_indy;ind++)
         FileWrite(handle,IndicatorsStatic[ind].HTML_header());
      if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID)
         FileWrite(handle,BWMFI_Stat.HTML_header());
      if(CheckPointer(MACD_Stat)!=POINTER_INVALID)
         FileWrite(handle,MACD_Stat.HTML_header());
      if(CheckPointer(ADX_Stat)!=POINTER_INVALID)
         FileWrite(handle,ADX_Stat.HTML_header());
      if(CheckPointer(Alligator_Stat)!=POINTER_INVALID)
         FileWrite(handle,Alligator_Stat.HTML_header());

Danach wird die Funktion "HTML_body" jeder statistischen Klasse einzeln aufgerufen und ein Muster für die Ausgabe erstellt. Hinweis: Wird diese Funktion aufrufen, wird mit der statistischen Klasse gearbeitet, die dann gelöscht werden, um den Speicher freizugeben.

      FileWrite(handle,"});</script> <!-- - --> </head> <body>");
      for(int ind=0;ind<total_indy;ind++)
        {
         FileWrite(handle,IndicatorsStatic[ind].HTML_body());
         delete IndicatorsStatic[ind];
        }
      if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID)
        {
         FileWrite(handle,BWMFI_Stat.HTML_body());
         delete BWMFI_Stat;
        }
      if(CheckPointer(MACD_Stat)!=POINTER_INVALID)
        {
         FileWrite(handle,MACD_Stat.HTML_body());
         delete MACD_Stat;
        }
      if(CheckPointer(ADX_Stat)!=POINTER_INVALID)
        {
         FileWrite(handle,ADX_Stat.HTML_body());
         delete ADX_Stat;
        }
      if(CheckPointer(Alligator_Stat)!=POINTER_INVALID)
        {
         FileWrite(handle,Alligator_Stat.HTML_body());
         delete Alligator_Stat;
        }

Zuletzt werden die abschließenden Kennungen eingetragen, die Datei geschlossen, die Arrays gelöscht und die Funktion verlassen.

      FileWrite(handle,"</body> </html>");
      FileFlush(handle);
      FileClose(handle);
     }
//---
   ArrayFree(IndicatorsStatic);
//---
   return(ret);
  }

Es darf nicht vergessen werden, die verbliebenen Klassen in der Funktion OnDeinit zu löschen.

7. Die Analyse der Information

Der Artikel kommt langsam zu seinem logischen Ende. Es wird Zeit, sich die Ergebnisse anzuschauen. Um dies zu tun, kehren wir zum Strategietester zurück, wiederholen alle Einstellungen, die wir beim Testen des EAs verwendet haben, der im zweiten Abschnitt unseres Artikels untersucht wurde, und starten den Test unseres neu geschaffenen analytischen EAs.

Nach dem Testende öffnen wir das "Gemeinsame Dateiverzeichnis" und finden dort Report.html. Wir öffnen die Datei im Browser. Zusätzlich werde ich Beispiele aus meinem Bericht anführen.

7.1. ATR

ATR reports

Die Analyse der Gewinnabhängigkeit vom ATR zeigt keine Erfolg versprechenden Bereiche, das bedeutet, dass ein Filter keinen Sinn machen würde.

7.2. CCI

CCI-Bericht

Die Diagramme der Gewinnabhängigkeit vom CCI erlauben, etwas Gewinn aus Kaufposition und einen CCI über 200 zu ziehen und bei einer steigenden Linie des Indikators. Aber für Verkauf-Positionen ist das nicht möglich.

7.3. Chaikin

Chaikin-Bericht

Der Chaikin-Oszillator, zeigt, wie der ATR, keine die Beziehung zwischen den Indikatorwerten und dem Gewinn der Positionen.

7.4. Force-Indikator

Force-Indikator-Bericht.

Auch die Analyse des Force-Indikators zeigt keine Abhängigkeit.

7.5. Standardabweichung

SndDev-Bericht

Die Analyse der Abhängigkeit vom StdDev-Indikator lässt einige Problembereiche für Kauf-Positionen erkennen, aber keine für Verkaufspositionen.

7.6. Volume-Indikator

Gewinnabhängigkeit vom Volumen.

Auch hier lässt sich keine Abhängigkeit von diesem Indikator erkennen.

7.7. Bill Williams MFI

BW MFI

Der Indikator BW MFI erlaubt Gewinne zu erzielen, wenn Kaufpositionen bei der Farbe 0 eröffnet werden. Aber es gibt keine Abhängigkeiten für Verkaufspositionen.

7.8. MACD

MACD-BerichtMACD-Bericht

Die Signale des MACD ermöglichen das Filtern von profitablen Kaufposition. Es ist möglich, wenn Positionen zum Kauf eröffnet werden und die Signallinie über dem Histogramm liegt. Aber die Analyse zeigt keine profitablen Zonen für Verkaufspositionen. Gleichzeitig ermöglicht es der Indikator, unprofitable Positionen zu reduzieren, wenn Verkaufsposition bei einem steigenden Histogramm und einer Signallinie, die kleiner oder gleich dem Histogramm ist, vermieden werden.

7.9. ADX

Nach der Analyse erlauben die Signale des ADX keine Filterung von Transaktionen.

7.10. Alligator

Alligator-BerichtAlligator-Bericht

Die Verwendung des Alligators, um die Positionen zu filtern, besitzt meiner Meinung nach die besten Aussichten. Die Muster für die Eröffnung von Positionen können in Kombinationen von Lage und Richtung der Linien gefunden werden. So können profitable Kaufposition eröffent werden, wenn:

  • Die Indikatorlinie einen Verkaufstrend zeigt, aber die Linien von LIPS oder JAW nach oben zeigen;
  • Die Indikatorlinie einen Kauftrend zeigt, und die Linien von LIPS und TEETH nach oben zeigen;
  • Der Trend ist undefiniert und die Linien von TEETH und JAW nach unten zeigen. 

Für Verkaufsposition gelten die spiegelbildlichen Signale.

8. Die Verbesserung des anfänglichen EAs

Wir haben die Positionen unseres EA sehr intensiv analysiert. Nun wollen wir sehen, wie sich dies auf die Performance unserer Strategie auswirken wird. Zu diesem Zweck fügen wir dem Signal-Modul aus dem Artikel[1] die Indikatoren mit Filterregeln nach der oben genannten Analyse. Ich schlage vor, den MACD und den Alligator dem Modul hinzuzufügen.

Ich würde empfehlen, die Indikatorfilter sequentiell und zyklisch hinzuzufügen, um das Verfahren zum Aufteilen der Positionen in die Indikatoren nach dem Hinzufügen jedes Filters durchzuführen. Dadurch kann der Einflusses des jeweiligen Filters auf die gesamte Strategie besser verstanden werden und dazu beitragen, ihren komplexen Einfluss zu bewerten. Wenn die Analyse in der ersten Phase es nicht erlaubt, die Abhängigkeit des Gewinns von den Werten irgendeines Indikators zu erkennen, so bedeutet das keineswegs, dass diese Abhängigkeit bei den nachfolgenden Iterationen nicht gesehen werden kann. Das geschieht jetzt aber nicht, da so der Artikel noch weiter aufgebläht würde, obwohl er bereits recht lang ist.

Zuerst werden die Parameter der Indikator der Beschreibung hinzugefügt.

//| Parameter=JAW_Period,uint,13,JAW Period                                   |
//| Parameter=JAW_Shift,uint,8,JAW Shift                                      |
//| Parameter=TEETH_Period,uint,8,TEETH Period                                |
//| Parameter=TEETH_Shift,uint,5,TEETH Shift                                  |
//| Parameter=LIPS_Period,uint,5,LIPS Period                                  |
//| Parameter=LIPS_Shift,uint,3,LIPS_Shift                                    |
//| Parameter=Alligator_Method,ENUM_MA_METHOD,MODE_SMMA,Method                |
//| Parameter=Alligator_Price,ENUM_APPLIED_PRICE,PRICE_MEDIAN,Alligator Price |
//| Parameter=MACD_Fast,uint,12,MACD Fast                                     |
//| Parameter=MACD_Slow,uint,26,MACD Slow                                     |
//| Parameter=MACD_Signal,uint,9,MACD Signal                                  |
//| Parameter=MACD_Price,ENUM_APPLIED_PRICE,PRICE_CLOSE,MACD Price            |


Jetzt werden die Variablen der Parameter im 'private'-Block hinzugefügt, und die Funktion, diese zu speichern, im 'public'-Block eingetragen wird.

   uint              ci_MACD_Fast;
   uint              ci_MACD_Slow;
   uint              ci_MACD_Signal;
   ENUM_APPLIED_PRICE ce_MACD_Price;
   uint              ci_JAW_Period;
   uint              ci_JAW_Shift;
   uint              ci_TEETH_Period;
   uint              ci_TEETH_Shift;
   uint              ci_LIPS_Period;
   uint              ci_LIPS_Shift;
   ENUM_MA_METHOD    ce_Alligator_Method;
   ENUM_APPLIED_PRICE ce_Alligator_Price;
   void              JAW_Period(uint value)                 {  ci_JAW_Period  =  value;   }
   void              JAW_Shift(uint value)                  {  ci_JAW_Shift   =  value;   }
   void              TEETH_Period(uint value)               {  ci_TEETH_Period=  value;   }
   void              TEETH_Shift(uint value)                {  ci_TEETH_Shift =  value;   }
   void              LIPS_Period(uint value)                {  ci_LIPS_Period =  value;   }
   void              LIPS_Shift(uint value)                 {  ci_LIPS_Shift  =  value;   }
   void              Alligator_Method(ENUM_MA_METHOD value) {  ce_Alligator_Method  =  value;   }
   void              Alligator_Price(ENUM_APPLIED_PRICE value) {  ce_Alligator_Price=  value;   }
   void              MACD_Fast(uint value)                  {  ci_MACD_Fast   =  value;   }
   void              MACD_Slow(uint value)                  {  ci_MACD_Slow   =  value;   }
   void              MACD_Signal(uint value)                {  ci_MACD_Signal =  value;   }
   void              MACD_Price(ENUM_APPLIED_PRICE value)   {  ce_MACD_Price  =  value;   }

Außerdem müssen Klassen hinzugefügt werden, die mit den Indikatoren und deren Initialisierungsfunktionen arbeiten, um die erforderlichen Daten einzusammeln. Für die Arbeit mit dem MACD wurde eine Standardklasse verwendet. Da es für den Alligator keine Standardklasse gibt, wurde sie durch drei Klassen mit gleitenden Durchschnitten ersetzt, die ihren Namen entsprechend den Namen der Indikatorlinien erhalten.

protected:
   CiMACD            m_MACD;           // object-oscillator
   CiMA              m_JAW;
   CiMA              m_TEETH;
   CiMA              m_LIPS;
     
   //--- method of initialization of the indicators
   bool              InitMACD(CIndicators *indicators);
   bool              InitAlligator(CIndicators *indicators);
   //--- methods of getting data
   double            Main(int ind)                     { return(m_MACD.Main(ind));      }
   double            Signal(int ind)                   { return(m_MACD.Signal(ind));    }
   double            DiffMain(int ind)                 { return(Main(ind+1)!=0 ? Main(ind)-Main(ind+1) : 0); }
   int               AlligatorTrend(int ind);
   double            DiffJaw(int ind)                  { return(m_JAW.Main(ind+1)!=0 ? m_JAW.Main(ind)/m_JAW.Main(ind+1) : 1); }
   double            DiffTeeth(int ind)                { return(m_TEETH.Main(ind+1)!=0 ? m_TEETH.Main(ind)/m_TEETH.Main(ind+1) : 1); }
   double            DiffLips(int ind)                 { return(m_LIPS.Main(ind+1)!=0 ? m_LIPS.Main(ind)/m_LIPS.Main(ind+1) : 1); }

Der nächste Schritt besteht darin, die Funktion "InitIndicators" anzupassen, um unsere Indikatoren der Bibliothek des Expert Advisors hinzuzufügen.

bool CSignalKalman::InitIndicators(CIndicators *indicators)
  {
//--- initialization of indicators and timeseries of additional filters
   if(!CExpertSignal::InitIndicators(indicators))
      return(false);
//--- initialize close serias
   if(CheckPointer(m_close)==POINTER_INVALID)
     {
      if(!InitClose(indicators))
         return false;
     }
//--- create and initialize MACD oscilator
   if(!InitMACD(indicators))
      return(false);
//--- create and initialize Alligator
   if(!InitAlligator(indicators))
      return(false);
//--- create and initialize Kalman Filter
   if(CheckPointer(Kalman)==POINTER_INVALID)
      Kalman=new CKalman(ci_HistoryBars,ci_ShiftPeriod,m_symbol.Name(),ce_Timeframe);
   
//--- ok
   return(true);
  }

Dann muss die Entscheidungsfunktion erweitert werden. Es darf nicht vergessen werden, dass die hinzugefügten Indikatoren als Filter fungieren. Daher werden die Indikatoren erst nach dem Auftreten des Hauptsignals abgefragt.

int CSignalKalman::LongCondition(void)
  {
   if(!CalculateIndicators())
      return 0;
   int result=0;
   //--- 
   if(cd_correction>cd_forecast)
     {
      if(Signal(1)>Main(1))
         result=80;
      else
        {
         switch(AlligatorTrend(1))
           {
            case 1:
              if(DiffLips(1)>1 && DiffTeeth(1)>1 && DiffJaw(1)<=1)
                 result=80;
              break;
            case -1:
              if(DiffLips(1)>1 || DiffJaw(1)>1)
                 result=80;
              break;
            case 0:
              if(DiffJaw(1)<1)
                {
                 if(DiffLips(1)>1)
                    result=80;
                 else
                    if(DiffTeeth(1)<1)
                       result=80;
                }
              break;
           }
        }
     }
   return result;
  }

Ähnliche Änderungen werden an der Funktion "ShortCondition" vorgenommen. Der vollständige Code des Moduls für die Handelsentscheidungen findet sich im Anhang.

9. Testen des EAs nach den Änderungen

Nachdem die Änderungen in das Modul mit den Handelsentscheidungen eingegeben wurden, erstellen wir einen neuen EA (eine detaillierte Beschreibung der EA-Erstellung mit Hilfe des Trading Signal Moduls befindet sich im Artikel[5]). Testen wir jetzt den neu erstellten EA mit den Parametern, die denen aus dem ersten Test in Abschnitt 2 ähneln.

Wie die Testergebnisse zeigen, konnte, ohne die Parameter des EAs zu verändern, durch den Einsatz der Filter der Profit-Faktor von 0,75 auf 1,12 erhöht werden. D.h. mit den Parametern, die bei dem ursprünglichen EA zu Verlusten führten, gelang es, Gewinne zu erzielen. Lassen Sie mich daran erinnern, dass bewusst keine optimalen Parameter des ursprünglichen EA verwendet wurden.

Wiederholter TestWiederholter TestWiederholter Test

Schlussfolgerung

In diesem Artikel wurde die Technologie die Aufgliederung der Positionshistorie für eine Analyse von Indikatoren demonstriert, die es ermöglichte, ein Filtersystem auf der Basis von Standardindikatoren aufzubauen. Nach den Testergebnissen zeigte dieses System ein greifbares Ergebnis in der Rentabilität des analysierten Expert Advisors. Das vorgeschlagene System kann nicht nur bei der Optimierung des bestehenden Handelssystems verwendet werden, sondern auch beim Versuch, ein neues zu erstellen.

Referenzen

  1. Verwendung des Kalman-Filters für die Prognose der Preisrichtung
  2. Diagramme in HTML
  3. Wie lang ist der Trend?
  4. Verschiedene Wege zur Ermittlung eines Trends in MQL5
  5. Wir betrachten die adaptive Trendfolgemethode in der Praxis

Die Programm dieses Artikels:

#
 Name
Typ 
Beschreibung 
1 Kalman.mqh  Klassenbibliothek  Die Klasse des Kalman-Filters
2 SignalKalman.mqh  Klassenbibliothek  Das Modul der Handelssignale des Kalman-Filters
3 SignalKalman+Filters.mqh  Klassenbibliothek  Das Modul der Handelssignale des Kalman-Filters nach dem Hinzufügen der Indikator-Filter
4 Kalman_expert.mq5  Expert Adviser  Der originale Expert Adviser mit der Strategie des Kalman-Filters
5 Kalman+Filters.mq5  Expert Adviser  Angepasster Expert Adviser mit der Strategie der Kalman-Filters
6 Deals_to_Indicators.mq5  Expert Adviser  Expert Advisor, um die Positionen der Handelshistorie aufzulösen
7 Deal.mqh   Klassenbibliothek  Die Klasse zum Sichern der Informationen der Positionen
8 Parsing.mqh  Klassenbibliothek  Die Klasse für die Handelshistorie des Testberichts
9 Value.mqh   Klassenbibliothek  Die Klasse, um die Werte der Indikatorpuffer zu sichern
10 OneBufferArray.mqh  Klassenbibliothek  Die Klasse zum Sichern der Werte der Ein-Puffer-Indikatoren
11 StaticOneBuffer.mqh  Klassenbibliothek  Die Klasse zum Sammeln und Analysieren der Ein-Puffer-Indikatoren
    12 ADXValue.mqh  Klassenbibliothek  Die Klasse zum Speichern der Werte des ADX
13 ADX.mqh  Klassenbibliothek  Die Klasse zum Speichern historischen Daten des ADX
14 StaticADX.mqh  Klassenbibliothek  Die Klasse zum Sammeln und Analysieren der Statistik des ADX
15 AlligatorValue.mqh  Klassenbibliothek  Die Klasse zum Speichern der Werte des Alligators
16 Alligator.mqh  Klassenbibliothek  Die Klasse zum Speichern historischen Daten des Alligators
17 StaticAlligator.mqh  Klassenbibliothek  Die Klasse zum Sammeln und Analysieren der Statistik des Alligators
18 BWMFIValue.mqh  Klassenbibliothek  Die Klasse zum Speichern der Werte des BW MFI
19 BWMFI.mqh  Klassenbibliothek  Die Klasse zum Speichern historischen Daten des BW MFI
20 StaticBWMFI.mqh  Klassenbibliothek  Die Klasse zum Sammeln und Analysieren der Statistik des BW MFI
21 MACDValue.mqh  Klassenbibliothek  Die Klasse zum Speichern der Werte des MACD
22 MACD.mqh  Klassenbibliothek  Die Klasse zum Speichern historischen Daten des MACD
23 StaticMACD.mqh  Klassenbibliothek  Die Klasse zum Sammeln und Analysieren der Statistik des MACD
24  Reports.zip  Archive  Das Archiv enthält die Ergebnisse nach den Tests im Strategietester und des Berichtes mit der Analyse.

Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/3968

Beigefügte Dateien |
Reports.zip (360.75 KB)
Verwendung des Kalman-Filters für die Prognose der Preisrichtung Verwendung des Kalman-Filters für die Prognose der Preisrichtung

Für einen erfolgreichen Handel benötigen wir fast immer Indikatoren, die die Hauptpreisbewegung vom Hintergrundrauschen trennen können. In diesem Artikel betrachten wir einen der vielversprechendsten digitalen Filter, den Kalman-Filter. Der Artikel beschreibt, wie Sie den Filter zeichnen und verwenden können.

R-Quadrat als Gütemaß der Saldenkurve einer Strategie R-Quadrat als Gütemaß der Saldenkurve einer Strategie

Dieser Artikel beschreibt die Konstruktion des benutzerdefinierten Optimierungskriterium R². Anhand dieses Kriteriums kann die Qualität der Saldenkurve einer Strategie abgeschätzt und die bestgeeignete Strategie ausgewählt werden. Die Arbeit diskutiert die Grundsätze der Konstruktion und die statistischen Methoden, die in der Schätzung der Eigenschaften und Qualität dieser Metrik.

Wie man an einer externen Börse für Kryptowährungen über MetaTrader 5 handelt Wie man an einer externen Börse für Kryptowährungen über MetaTrader 5 handelt

Die Funktionalität von MQL5 wurde durch eine neue Möglichkeit ergänzt — die Erstellung benutzerdefinierter Symbole und Charts. Der Artikel beschäftigt sich mit der Anwendung dieser Möglichkeit für den Handel an einer externen Börse für Kryptowährungen über das MetaTrader 5 Terminal.

Das Erstellen einer neuen Handelsstrategie und sich die Positionseröffnungen durch Indikatoren bestimmen lassen Das Erstellen einer neuen Handelsstrategie und sich die Positionseröffnungen durch Indikatoren bestimmen lassen

Der Artikel schlägt eine Technologie vor, die jedem helfen kann, eine eigene Handelsstrategie durch die individuelle Auswahl von Indikatoren sowie den zu entwickelnden Signalen für die Positionseröffnung zu entwickeln.