Auswahl- und Navigationsprogramm in MQL5 und MQL4: Hinzufügen einer automatischen Suche nach Mustern und das Darstellen der gefundenen Symbole

Roman Klymenko | 25 Februar, 2019

Einführung

Viele Handelsstrategien erfordern eine ständige Analyse der Symbole bei der Suche nach verschiedenen Mustern für eine Positionseröffnung. Während die Suche und Analyse von Symbolen nützlich sein kann, um einen Einblick in den Markt zu erhalten, kann es manchmal sinnvoll sein, dass Ihnen eine Liste von Symbolen mit einem notwendigen Parameter einfach angezeigt wird. In diesem Artikel werden wir versuchen, ein solches Instrument für einige Intraday-Handelsmuster zu entwickeln.

Genauer gesagt, heute werden wir die Fähigkeiten unseres Hilfsprogramms erweitern, indem wir die automatische Sortierung von Symbolen nach bestimmten Parametern hinzufügen. Dazu werden wir eine Reihe von Registerkarten erstellen, die nach Symbolen suchen, die derzeit bestimmte Handelsmuster aufweisen: Parabel, Luftniveaus (Seitwärtsbewegungen), Kurslücken, etc. Wir werden auch lernen, wie man nutzerdefinierte Registerkarten hinzufügt, vorausgesetzt, Sie kennen die Grundlagen der MQL-Programmierung.

Wie im vorherigen Artikel, wird unser Hilfsprogramm sowohl in MQL4 als auch in MQL5 funktionieren. Mit Blick auf die Zukunft sollte ich sagen, dass das Öffnen automatisch sortierter Tabs in MQL5 langsamer ist als in MQL4. Dies geschieht, wenn das angeforderte Symbol keine Historie im gewünschten Umfang hat. In solchen Fällen fordert MetaTrader 5 die Historie vom Handelsserver an und erstellt die erforderlichen Zeitrahmen.

Wenn also nichts passiert, nachdem Sie auf die Registerkarte geklickt haben oder wenn alle Tasten verschwinden, nicht in Panik verfallen, einfach warten. In fünf, zehn oder zwanzig Sekunden werden die notwendigen Symbole angezeigt. Es ist immer noch schneller, als die Charts von Hunderten von Symbolen manuell zu starten. Auf der anderen Seite müssen Sie in MetaTrader 4 die Geschichte der notwendigen Symbole und Zeitrahmen selbstständig erstellen, was auch Zeit und Aufmerksamkeit für solche Kleinigkeiten erfordert.

Wenn außerdem Ihr Computer nur einen kleinen RAM-Speicher hat (oder Sie mit einer virtuellen Maschine arbeiten, die über eine begrenzte Menge an RAM verfügt, z.B. 1 GB), dann kann in MQL5 der EA-Vorgang durch den Fehler wegen unzureichender Speichergröße beim Öffnen der automatischen Sortierregisterkarten unterbrochen werden. MQL4 hat kein solches Problem, da der gesamte Zeitrahmenverlauf unabhängig und in verschiedenen Tiefen hochgeladen wird. In MQL5 kann das Problem gelöst werden, indem man den Parameter "Max. Balken im Chart" begrenzt.

Hinzufügen von Funktionen zu den automatisch sortierten Registerkarten

Zuerst definieren wir, wie wir die automatisch sortierten Registerkarte zum Hilfsprogramm hinzufügen können. Um dies zu erreichen, müssen wir verstehen, wie der Mechanismus dieser Registerkarten von innen heraus gestaltet ist.

Wir haben bereits im vorherigen Artikel die Möglichkeit implementiert, Registerkarten hinzufügen. Wie Sie sich vielleicht erinnern, machen wir das mit einer Schleife. Die Namen der Hausaufgaben-Registerkarten werden im Array gespeichert. Die Namen der Registerkarten für die automatische Sortierung werden ebenfalls in einem separaten Array gespeichert:

string panelNamesAddon[9]={"Air Level", "Parabolic", "Gap", "4 weeks Min/Max", "365 days Min/Max", "Round levels", "Mostly Up/Down", "All time high/low", "High=Close"};

Mit anderen Worten, um eine nutzerdefinierte Registerkarte hinzuzufügen, müssen wir die Array-Größe um 1 erhöhen und den Tab-Namen am Ende der Tabelle hinzufügen. Danach erscheint die neue Registerkarte im Hilfsprogramm.

Die Funktion show_panel_buttons ist für die Anzeige von Tabs zuständig. Der Code wurde geändert, alle Registerkarten für die automatische Sortierung werden zusätzlich zu den Registerkarten für Hausaufgaben angezeigt:

void show_panel_buttons(){
   int btn_left=0;
   // Definieren des maximal möglichen X-Koordinate zur Darstellung der Tabs.
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   string tmpName="";
   
   for( int i=0; i<ArraySize(panelNames); i++ ){
      // Wenn die Start-Koordinate einer neuen Taste das Maximum überschreitet,
      // wird es in die nächste Zeile verschoben.
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      // Wenn die "Hausaufgaben" Symbole enthält, wird ihre Anzahl
      // dem Tabellennamen hinzugefügt
      tmpName=panelNames[i];
      if(i>0 && arrPanels[i].Total()>0 ){
         tmpName+=" ("+(string) arrPanels[i].Total()+")";
      }
      
      // Anzeige der Tasten der Tabs
      ObjectCreate(0, exprefix+"panels"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_BGCOLOR,clrSilver); 
      ObjectSetString(0,exprefix+"panels"+(string) i,OBJPROP_TEXT,tmpName);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_SELECTABLE,false);
      // wenn die Tab-Taste aktuell aktiv ist,
      // mach sie "gedrückt"
      if( cur_panel == i ){
         ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_STATE, true);
      }
      
      btn_left+=BTN_WIDTH;
   }
   // Wenn das Darstellen der selbst-sortierenden Registerkarten durch die Eingabeparameter erlaubt ist, werden sie angezeigt
   if(useAddonsLevels){
      for( int i=0; i<ArraySize(panelNamesAddon); i++ ){
         if( btn_left>btn_right-BTN_WIDTH ){
            btn_line++;
            btn_left=0;
         }
         tmpName=panelNamesAddon[i];
         // Wenn die Registerkarte Gap genannt wird, zeige den aktuellen Wert des Eingabeparameters,
         // der den %-Satz der Kurslücke festlegt
         if(tmpName=="Gap"){
            StringAdd(tmpName, " ("+(string) gap_min+"%)");
         }
         
         ObjectCreate(0, exprefix+"panels"+(string) (i+ArraySize(panelNames)), OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XDISTANCE,btn_left); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_FONTSIZE,8); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_COLOR,clrBlack); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_BGCOLOR,clrLightSteelBlue); 
         ObjectSetString(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_TEXT,tmpName);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_SELECTABLE,false);
         if( cur_panel == i+ArraySize(panelNames) ){
            ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_STATE, true);
         }
         
         btn_left+=BTN_WIDTH;
      }
   }

   // Anzeigen des Kommentars, wenn so bestimmt:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      if(from_txt){
         StringAdd(tmpCMT, ", from txt");
      }
      ObjectCreate(0, exprefix+"title", OBJ_EDIT, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XDISTANCE,btn_left+11); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XSIZE,133); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_COLOR,clrGold); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BGCOLOR,clrNONE); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BORDER_COLOR,clrBlack);
      ObjectSetString(0,exprefix+"title",OBJPROP_TEXT,tmpCMT);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_SELECTABLE,false);
   }
   

}

Wie wir im Funktionscode sehen können, werden Registerkarten für die automatische Sortierung nur angezeigt, wenn dies durch die Eingabe von addons_infowatch erlaubt ist. Zusätzlich dazu fügen wir zwei weitere Parameter hinzu, um die automatisch sortierten Registerkarten zu konfigurieren:

sinput string        delimeter_05="";      // --- Zusätzliche Registerkarten ---
input bool           useAddonsLevels=true; // Anzeige zusätzlicher Registerkarten
input bool           addons_infowatch=true;// Ausblenden des Symbols, da es nicht in der Marktübersicht ist
input int            addon_tabs_scale=3;   // Aufteilen der zusätzlichen Registerkarten (0-5)

Ich glaube, nur der Parameter addons_infowatch erfordert hier eine zusätzliche Klärung. Wenn diese Option aktiviert ist, werden nur die Symbole sortiert, die in der Marktübersicht angezeigt werden. Andernfalls wird die Sortierung für alle von Ihrem Broker angebotenen Symbole durchgeführt.

Infolgedessen können Sie beim Start einer neuen Version des Hilfsprogramms neue Registerkarten für die automatische Sortierung neben den Hausaufgaben sehen:

Hinzufügen von Registerkarten für die automatische Sortierung

Aber zurück zu unserem Code. Jetzt wissen wir, wie man automatisch sortierte Registerkarten hinzufügt. Beim bisherigen Anklicken werden jedoch keine Symbole angezeigt, da die Symbolanzeige noch nicht implementiert ist. Alle Schaltflächen der aktuell geöffneten Registerkarte werden mit der Funktion show_symbols angezeigt. Wir werden hier die Anzeige der automatisch sortierte Registerkarten hinzufügen. Dadurch sieht die Funktion wie folgt aus:

void show_symbols(){
   
   // Initialisieren der Variablen für die X- und Y-Koordinaten
   int btn_left=0;
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   btn_line++;
   
   
   
   if( cur_panel==0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear All");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"showpos", OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"showpos",OBJPROP_TEXT,"Show Pos");
      btn_left+=BTN_WIDTH;
   }else if( cur_panel<ArraySize(arrPanels) && arrPanels[cur_panel].Total()>0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"new_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"new_"+(string) cur_panel,OBJPROP_TEXT,"Open All");
      btn_left+=BTN_WIDTH;
   }
   
   // Wenn der aktuelle Indes der geöffneten Registerkarten die Anzahl der Elemente des Arrays der Hausaufgaben
   // und die des Arrays mit allen Symbolen,
   // dann ist diese die selbst-sortierende Registerkarte
   if(cur_panel>(ArraySize(arrPanels)-1)){
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int tmpNumAddon=cur_panel-ArraySize(arrPanels);
      addonArr.Resize(0);
      arrTT.Resize(0);
      string addonName;
      
      CArrayString tmpSymbols;
      if( tmpNumAddon==0 && air_only_home ){
         for( int j=1; j<ArraySize(arrPanels); j++ ){
            for( int k=0; k<arrPanels[j].Total(); k++ ){
               string curName=arrPanels[j].At(k);
               bool isYes=false;
               for( int r=0; r<tmpSymbols.Total(); r++ ){
                  if(tmpSymbols.At(r)==curName){
                     isYes=true;
                     break;
                  }
               }
               if(!isYes){
                  tmpSymbols.Add(arrPanels[j].At(k));
               }
            }
         }
      }else{
         if( ArraySize(result)>1 ){
            for(int j=0;j<ArraySize(result);j++){
               StringReplace(result[j], " ", "");
               if(StringLen(result[j])<1){
                  continue;
               }
               tmpSymbols.Add(onlySymbolsPrefix+result[j]+onlySymbolsSuffix);
            }
         }else{
            for( int i=0; i<SymbolsTotal(addons_infowatch); i++ ){
               tmpSymbols.Add(SymbolName(i, addons_infowatch));
            }
         }
      }
      
      switch(tmpNumAddon){
         case 0: // Luft-Level
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 1: // Parabel
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 2: // Kurslücke
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 3: //4-wöchiges Hoch/Tief
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 4: //365-Tages Hoch/Tief
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 5: // gerundete Preise
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 6: // meist steigend/fallend
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 7: // Allzeit Hoch/Tief
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
         case 8: // Hoch=Schlusskurs
            // Code zur Anzeige des Inhalts der Registerkarte
            break;
      }
      
      // eine Schaltfläche wird für jedes Symbol im Array auf dem Chart gezeichnet
      // der Symbolname wird auf die Schaltfläche geschrieben
      for( int i=0; i<addonArr.Total(); i++ ){
         
         if( btn_left>btn_right-BTN_WIDTH ){
            btn_line++;
            btn_left=0;
         }
         
         ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); 
         ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,addonArr.At(i));    
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);
         if( arrTT.At(i)>0 ){
            ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TOOLTIP,(string) arrTT.At(i));    
         }
   
         if( checkSYMBwithPOS(addonArr.At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
         
         btn_left+=BTN_WIDTH;
      }
      
      // da alle Schaltflächen gezeichnet sind, Funktion verlassen
      return;
   }
   
   // Tasten auf dem Chart für jedes Symbol im Array anzeigen
   // der aktuell aktive Registerkarte
   // der Symbolname wird auf die Schaltfläche geschrieben
   for( int i=0; i<arrPanels[cur_panel].Total(); i++ ){
      
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      
      ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,arrPanels[cur_panel].At(i));    
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);

      if( !noSYMBwithPOS || cur_panel>0 ){
         if( checkSYMBwithPOS(arrPanels[cur_panel].At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
      }
      
      btn_left+=BTN_WIDTH;
   }
   
}

Wie wir sehen können, befindet sich der Code zur Anzeige des Inhalts der Registerkarte innerhalb des Operators switch. Der Index des Array-Elements, das den Namen der Registerkarte enthält, wird im Operator case angegeben. Um also eine nutzerdefinierte Registerkarte hinzuzufügen, nachdem Sie deren Namen dem Array hinzugefügt haben, fügen Sie einfach ein neues case mit einem Index der um 1 größer ist als der letzte, angewandte Index.

Wir werden uns die Code-Beispiele für die automatische Sortierung von Symbolen nach bestimmten Parametern ansehen, wenn wir bestimmte Registerkarten betrachten. Aber wir können bereits sehen, dass der Code aller Registerkarten in ähnlicher Weise beginnt.

Die Liste aller zu sortierenden Symbole ist bereits im Array tmpSymbols vorhanden. Daher beginnt der Code jeder Registerkarte mit der Schleife for:

            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];
               // Code zum Bestimmen, ob ein Symbol angezeigt werden soll
            }

Luftstände (Seitwärtsbewegung)

Beim Handel von Levels aus werden alle Positionseröffnungen in der Nähe der Bandbreite der Seitwärtsbewegung durchgeführt, d.h. wenn der Preis den gleichen oder annähernd den gleichen Preis durch sein Hoch oder Tief an jedem Barren berührt. Das Beispiel ist im unteren Bild zu sehen:

Beispiel einer Seitwärtsbewegung

Dies ist vielleicht nicht das perfekte Beispiel, da das erreichte Niveau ständig zu falschen Durchbrüche führt. Aber auf der anderen Seite wird angenommen, dass das Vorhandensein von falschen Durchbrüchen das Niveau verstärkt. =)

Die Suche nach solchen Situationen ist eine ziemlich mühsame Aufgabe, also versuchen wir, sie zu automatisieren.

Die Suche nach Seitwärtsbewegungen wird in der Regel auf M5-Charts durchgeführt, ist aber keine zwingende Bedingung. Einige Händler arbeiten mit M15 oder M30 Charts. Es wird angenommen, dass je höher der Zeitrahmen, in dem ein flacher Bereich erkannt wird, desto besser ist die potenzielle Positionseröffnung.

Lassen Sie uns daher die Eingabeparameter hinzufügen, die es uns ermöglichen, die notwendigen Zeitrahmen zu definieren:

sinput string        delimeter_06=""; // --- Zusätzliche Level der flachen Seitwärtsbewegung ---
input bool           air_level_m5=true; // Suche nach Seitwärtslevel für M5
input bool           air_level_m15=true; // Suche nach Seitwärtslevel für M15
input bool           air_level_m30=false; // Suche nach Seitwärtslevel für M30
input bool           air_level_h1=false; // Suche nach Seitwärtslevel für H1
input bool           air_level_h4=false; // Suche nach Seitwärtslevel für H4
input bool           air_level_d1=false; // Suche nach Seitwärtslevel für D1

Es ist zu beachten, dass der Suchprozess umso langsamer abläuft, je mehr Zeitrahmen Sie auswählen. Daher ist es besser, die Suche auf ein oder zwei Zeiträume einzuschränken.

Außerdem sollten wir noch einige andere Eingaben hinzufügen:

input uchar          air_level_count=4; // Anzahl der Bars der Seitwärtsbewegung
input uchar          air_level_offset=3; // Abstand relativ zum Level in Points
input bool           air_level_cur_day=true; // Nur die Level in Richtung des aktuellen Tages
input bool           air_level_prev_day=true; // Nur die Level in Richtung des vorherigen Tages
input bool           air_only_home=false; // Such nur in den "Hausaufgaben"

Der Parameter Number of level bars erlaubt es, die Anzahl der Balken anzugeben, die einen nahe gelegenen Preis berühren sollen, um davon auszugehen, dass es sich um eine flache Seitwärtsbewegung handelt. Der optimale Wert für diesen Parameter ist 4 Balken. Je größer die Anzahl der Balken, desto höher ist die Qualität der flachen Seitwärtsbewegung, aber desto seltener tritt sie auf.

Der Parameter Offset relative to level in points ermöglicht die Angabe der Pointspanne, innerhalb dessen der Preis als "nahe" angenommen werden soll. Mit anderen Worten, wenn der Parameter 0 ist, sollten die Balken genau den gleichen Preis bis zu Cent erreichen. Im Allgemeinen wird es weniger solcher Fälle geben, insbesondere bei höheren Zeitrahmen. In den meisten Fällen kann der Preis das angegebene Niveau nicht erreichen, da er an einigen wenigen Stellen davon abhängt, weshalb der Standardparameterwert 3 Punkte beträgt.

Wenn Sie nur in Richtung des aktuellen und/oder des Vortages handeln, erlauben Only levels in the current day's direction und Only levels in the previous day's direction das Sortieren der Symbole, bei denen eine erkannte, flache Seitwärtsbewegung die Richtung entgegengesetzt zur Preisbewegung hat.

Die Preisbewegungsrichtung wird durch das Verhältnis eines engen Preises zu einem offenen Preis definiert. Wenn der Schlusskurs der gestrigen Bar höher ist als der Eröffnungskurs, dann suchen wir nur nach Kauf-Gelegenheiten. Das heißt, diejenigen, die nahe zum Tief sind.

Abschließend werfen wir einen Blick auf den letzten Parameter - Search only in "homework". Wenn Sie nur mit Symbolen arbeiten, die im Laufe des Tages zu den Registerkarten Homework hinzugefügt wurden, legen Sie diesen Parameter so fest, dass das Hilfsprogramm nur nach Symbolen sucht, die derzeit zu den Registerkarten Hausaufgabe hinzugefügt werden.

Betrachten wir nun endlich den Code, der die Symbole sortiert, die derzeit eine flache Seitwärtsbewegung aufweisen. Der Code für den M5-Zeitrahmen ist unten aufgeführt:

               if(air_level_m5 && CopyRates(addonName, PERIOD_M5, 0, air_level_count+1, rates)==air_level_count+1){
                  if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)<=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)<=0) && rates[0].high<rates[1].high ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].high-rates[j+1].high) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].high);
                     }
                  }else if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)>=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)>=0) && rates[0].low>rates[1].low ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].low-rates[j+1].low) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].low);
                     }
                  }
               }

Wie in allen anderen Code-Beispielen der automatischen Sortierregisterkarten beginnen wir auch hier mit den Ergebnissen des Funktionsaufrufs CopyRates unmittelbar nach dem Aufruf selbst zu arbeiten. Das ist nicht ganz richtig. Die MQL-Hilfe empfiehlt, eine Weile zu warten, bis das Hilfsprogramm Daten empfangen und in das Array schreiben kann. Aber selbst wenn wir 50 Millisekunden warten, bedeutet die Überprüfung von 100 Symbolen eine zusätzliche Verzögerung von 5 Sekunden, während viele Broker Hunderte von Börsensymbolen anbieten. Daher kann die Anzeige eines Tab-Inhalts bei Verwendung einer Verzögerung deutlich mehr Zeit in Anspruch nehmen.

Deshalb beginnen wir sofort, mit dem Array der Ergebnisse zu arbeiten. In der Praxis gibt es keine Probleme damit, außer vielleicht, dass das Ausbleiben einer Verzögerung welche verursachen könnte.

Tatsache ist, dass die eigentlichen Daten aufgrund der Funktion CopyRates nicht immer an das Array übertragen werden. Manchmal wird dann auf Grund veralteter Daten sortiert. In diesem Fall aktualisieren Sie einfach die Registerkarte (Taste R), um die entsprechende Symbolliste zu erhalten.

Kommen wir auf den Code zurück. Wenn Sie sich entscheiden, eine nutzerdefinierte, automatische Sortierfunktion hinzuzufügen, achten Sie darauf, wie die Auswahl der Symbole erfolgt.

Wenn das Symbol unseren Bedingungen entspricht, setzen wir seinen Namen in das Array addonArr. Außerdem können wir den Zeitrahmen angeben, der beim Öffnen eines Symboldiagramms anstelle eines Standardzeitrahmens in Klammern verwendet werden soll.

Wir müssen auch den Wert dem Array arrTT zuweisen. Wenn wir den Wert 0 dem Array zuweisen, passiert nichts. Aber wenn wir stattdessen einen Preis hinzufügen, wird die horizontale Linie beim Öffnen eines Charts mit dem entsprechenden Symbol auf dem angegebenen Preisniveau gezeichnet. Dies geschieht aus Gründen der Bequemlichkeit, so dass Sie sofort den Preis sehen können, zu dem die flache Seitwärtsbewegung erkannt wird.

Parabel

Eine Parabel entsteht bei einer Richtungsänderung, wenn sich der Kurs in die entgegengesetzte Richtung bewegt, und jeder neue Balken höher oder niedriger ist als der vorherige. Es wird angenommen, dass in diesem Fall der Preis eher in Richtung eines Anstiegs des Tiefstwerts oder eines Rückgangs des Höchstwerts geht. In diesem Fall ist es möglich, einen engeren Stop-Loss zu verwenden.

Mit anderen Worten, nachdem der Preis gesunken ist, beginnt sich der Preis nach oben zu bewegen, und das Tief jedes Balkens ist höher als das des vorherigen. In diesem Fall kaufen Sie, und setzen Sie einen Stopp unter mindestens einem der Tiefs der vorherigen Balken.

Betrachten Sie die folgende Tabelle als Beispiel:

Beispiel einer Parabel

Die Beispiele für Parabeln sind mit Pfeilen gekennzeichnet.

Für die Parabel verwenden wir folgenden Code:

               if(CopyRates(addonName, PERIOD_M5, 0, 6, rates)==6){
                  if( rates[0].low>rates[1].low && rates[1].low>rates[2].low && rates[2].low<=rates[3].low && rates[3].low<=rates[4].low && rates[4].low<=rates[5].low ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }else if( rates[0].high<rates[1].high && rates[1].high<rates[2].high && rates[2].high>=rates[3].high && rates[3].high>=rates[4].high && rates[4].high>=rates[5].high ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Das heißt, wenn das Tief des aktuellen Balkens größer als das Tief des vorherigen ist. Gleichzeitig ist das Tief des vorherigen Balkens größer als das Tief des vorherigen, während die Tiefs der restlichen 3 vorherigen Balken entweder gleich oder kleine sind als der andere. Mit anderen Worten, wenn 3 Balken zuerst nach unten und dann 2 Balken nach oben gehen, betrachten wir dies als Beginn einer steigenden Parabel.

Kurslücken

Wenn Sie Handelsstrategien verwenden, die mit Aktien mit Kurslücken in die eine oder andere Richtung arbeiten, hilft Ihnen die Registerkarte Gap bei der Auswahl der benötigten Aktien. Es werden Symbole mit Kurslücken während des aktuellen Tages angezeigt. Sie können die kleinst Lücke (in Prozent des aktuellen Preises) über den Eingabeparameter Minimum gap size ändern.

Standardmäßig werden nur Symbole mit Lücken von mindestens 1% angezeigt.

Der Quellcode dieser Registerkarte ist einfach:

               if(CopyRates(addonName, PERIOD_D1, 0, 2, rates)==2){
                  if( rates[0].open>rates[1].close+(rates[0].open*(gap_min/100)) || rates[0].open<rates[1].close-(rates[0].open*(gap_min/100)) ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Vierwöchiges Hoch/Tief

Die Registerkarte 4 weeks Min/Max enthält die Liste der Symbole, deren Preis sich nahe dem Hoch/Tief der letzten vier Wochen bewegt. Ihr Code ist wie folgt:

               if(CopyRates(addonName, PERIOD_W1, 0, 4, rates)==4){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<4; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Hoch/-Tief des Jahres

Ähnlich wie die vorherige Registerkarte werden hier die aktuellen Symbole gezeigt, deren Preis sich nahe dem Hoch/Tief innerhalb des Jahres bewegt.

               if(CopyRates(addonName, PERIOD_W1, 0, 52, rates)==52){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<52; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Preise nahe gerundeten Zahlen

Es wird vermutet, dass gerundete Aktienkurse "natürliche" Unterstützungs-/Widerstandsniveaus sind. Daher konzentrieren sich einige Handelssysteme auf Instrumente, die aktuell nahe gerundeter Preise gehandelt werden.

Ein gerundeter Preis ist einer, der mit 0 oder 50 Cent endet, z.B. 125 Dollar 0 Cent oder 79 Dollar 50 Cent.

Daraus ergibt sich folgender Code:

               switch((int) SymbolInfoInteger(addonName, SYMBOL_DIGITS)){
                  case 0:
                     break;
                  case 2:
                     if(CopyRates(addonName, PERIOD_M5, 0, 1, rates)==1){
                        double tmpRound=rates[0].close - (int) rates[0].close;
                        if( (tmpRound>0.46 && tmpRound<0.54) || tmpRound>0.96 || tmpRound<0.04 ){
                           if(!skip_symbol(addonName)){
                              addonArr.Add(addonName+" (M5)");
                              arrTT.Add(0);
                           }
                        }
                     }
                     break;
               }
Mit anderen Worten, wir werden gerundete Preise nur für Symbole definieren, deren Preise zwei Dezimalstellen aufweisen. Wenn Sie mit anderen Instrumenten arbeiten, fügen Sie einfach in ähnlicher Weise Ihre eigene Prüfung hinzu.

Die meiste Zeit steigend/fallend

Jene Instrumente, die die meiste Zeit steigen oder fallen, könnten ebenfalls von besonderem Interesse sein. Lassen Sie uns die folgenden Eingabeparameter hinzufügen, um sie zu finden:

sinput string        delimeter_08=""; // --- Zusätzliche, meist steigende/fallende Registerkarte ---
input int            mostly_count=15; // Prüfen der zuletzt angegebenen Anzahl von Tagen
input int            mostly_percent=90; // Angegebener, zu überschreitender Prozentsatz in einer Richtung

Der Parameter Check the last specified number of days ermöglicht die Definition der Anzahl der Tage, in denen wir nach notwendigen Instrumenten suchen. Mit anderen Worten, wir werden nach unidirektionalen Bewegungen suchen, meist auf D1.

Der Parameter Specified percentage in one direction exceeded ermöglicht die Angabe des minimalen Prozentsatzes, um den eine Bewegungsrichtung die andere übersteigt. Der Standardwert ist 90. Das bedeutet, dass sich der Preis, um zu dieser Registerkarte zu gelangen, für 90% der Tage des gesamten Analysezeitraums in eine bestimmte Richtung bewegen sollte.

Im Allgemeinen ist die Anzahl solcher Symbole gering. Daher kann es notwendig sein, diesen Prozentsatz zu reduzieren.

Der Code der Registerkarte lautet wie folgt:

               if(CopyRates(addonName, PERIOD_D1, 1, mostly_count, rates)==mostly_count){
                     int mostlyLong=0;
                     int mostlyShort=0;
                     for( int j=0; j<mostly_count; j++ ){
                        if(rates[j].close>rates[j].open){
                           mostlyLong++;
                        }else if(rates[j].close<rates[j].open){
                           mostlyShort++;
                        }
                     }
                     if( !mostlyLong || !mostlyShort ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyLong*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyShort*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Jede Bar mit einem neuen Hoch/Tief.

Diese Registerkarte ermöglicht es, den Akkumulationsprozess innerhalb eines Symbols zu erkennen, d.h. den Zeitraum, in dem sich der Preis langsam aber stetig in eine Richtung bewegt. Im Allgemeinen endet die Akkumulation mit einem Durchbruch (große Balken) in ihrer Richtung.

Die folgenden Eingaben helfen uns, unidirektionale Bewegungen zu erkennen:

sinput string        delimeter_09=""; // --- Zusätzliche Registerkarte Allzeit Hoch/Tief ---
input ENUM_TIMEFRAMES alltime_period=PERIOD_D1; // Periode
input int            alltime_count=15; // Prüfen der zuletzt angegebenen Anzahl von Balken

Der Code für das Sortieren ist wie folgt:

               if(CopyRates(addonName, alltime_period, 1, alltime_count, rates)==alltime_count){
                     bool alltimeHigh=true;
                     bool alltimeLow=true;
                     for( int j=1; j<alltime_count; j++ ){
                        if(rates[j].high>rates[j-1].high){
                           alltimeHigh=false;
                        }
                        if(rates[j].low<rates[j-1].low){
                           alltimeLow=false;
                        }
                     }
                     if( alltimeHigh || alltimeLow ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Sitzung geschlossen bei den Höchst-/Tiefstpreis des Tages

Dies ist die letzte Registerkarte, die wir hinzufügen werden. Abhängig von der Zeit des Anrufs können Sie folgendes erkennen:

Es wird angenommen, dass, wenn der Preis auf dem Tageshoch/Tiefstpreis schloss, dann hatte ein Käufer/Verkäufer noch keine Zeit, seine Pläne umzusetzen. Das bedeutet, dass der Preis am nächsten Tag in die gleiche Richtung geht.

Die folgenden Parameter helfen uns bei der Suche nach geeigneten Symbolen:

sinput string        delimeter_10=""; // --- Zusätzliche Registerkarte Hoch=Schlusskurs ---
input ENUM_TIMEFRAMES highclose_period=PERIOD_D1; // Periode
input int            highclose_offset=0; // Abstand zu Hoch/Tief in Points

Bei der Suche nach dem Tageshoch/-tief muss der Kurs nicht unbedingt direkt mit dem Extremum schließen. Eine Korrektur von einigen wenigen oder sogar einem Dutzend Punkten von einem Grenzpreis ist möglich. Der Parameter Error margin of highs/lows in points ermöglicht die Definition einer zulässigen Korrektur in Punkten.

Der Code der Registerkarte lautet wie folgt:

               if(CopyRates(addonName, highclose_period, 0, 1, rates)==1){
                     if( rates[0].close+highclose_offset >= rates[0].high ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( rates[0].close-highclose_offset <= rates[0].low ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Hinzufügen der nutzerdefinierten Registerkarten

Natürlich haben wir nicht alle möglichen Muster berücksichtigt. Wenn Sie andere Muster verwenden und über Programmierkenntnisse in MQL verfügen, können Sie dem Hilfsprogramm leicht eigene Registerkarten hinzufügen. Sie können gerne die Codes Ihrer eigenen Registerkarten mit Beschreibungen der Muster, nach denen Sie suchen, unten in den Kommentaren posten.

Vorschläge zur Verbesserung des obigen Codes sind ebenfalls willkommen.

Abschließend möchte ich Sie daran erinnern, wie Sie Ihre eigene Registerkarte für die automatische Sortierung zum Hilfsprogramm hinzufügen können. Dies geschieht in zwei Schritten.

Zuerst fügen Sie den Namen der neuen Registerkarte zum Array der Namen der Registerkarten panelNamesAddon hinzu. Vergessen Sie nicht, die Array-Größe um 1 zu erhöhen.

Zweitens sollte der Operator switch der Funktion show_symbols einen neuen Eintrag mit case aufweisen, deren Wert den höchsten der angewandten um 1 übersteigt. Der Code zur Überprüfung, ob das aktuelle Symbol den Bedingungen entspricht, ist innerhalb des Operators case implementiert. Das Template des Codes sieht wie folgt aus:

         case index: // tab name
            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];

               // Prüf-Code
            }
            
            break;

Schlussfolgerung

Wir haben die Funktionen unseres Hilfsprogramms weiter ausgebaut. Ich glaube, es ist so für Händler nützlicher geworden.

In diesem Artikel haben wir den Code in Abhängigkeit von der MQL-Sprachversion nicht neu geschrieben, da er sowohl in MQL4 als auch in MQL5 funktioniert.

Wie Sie sehen können, ist die Entwicklung plattformübergreifender Hilfsprogramme in MQL keine große Herausforderung. Die meisten der MQL5-Funktionen werden in MQL4 in ähnlicher Weise unterstützt. Daher kann es sich lohnen, alle Arten von Klassen und andere einzigartige Funktionen von MQL5 vorübergehend zu vergessen und Ihre Arbeit so vielen Händlern wie möglich zur Verfügung zu stellen.

Natürlich erkläre ich den Klassen keinen Krieg. Klassen gibt es bereits in MQL4 und Programme mit ihnen erscheinen in der CodeBase. Ich schlage nur vor, diese Funktion der MQL5-Sprache für eine Weile zu verschieben.