Hilfen zur Auswahl und Navigation in MQL5 und MQL4: Tabs für "Hausaufgaben" und das Sichern grafischer Objekte

Roman Klymenko | 8 Februar, 2019

Einführung

Im vorigen Artikel haben wir das Hilfsprogramm zum Sortieren und Auswählen von Symbolen mit einem erfolgversprechenden Einstiegspunkt entwickelt. Wir lernten, wie man Symbole nach verschiedenen Parametern sortiert und wie man mit speziell entwickelten Tasten durch Symbole navigiert. Allerdings ist das Bild in Bezug auf die Symbolauswahl nicht so rosig. Derzeit müssen wir die Ticker der ausgewählten Instrumente auf ein Blatt Papier schreiben, was sich sehr negativ auf die Waldpopulationen des Planeten auswirkt.

In diesem Artikel werden wir Bäume vor der Zerstörung bewahren und lernen, wie man automatisch grafische Objekte, die in einem Diagramm erstellt wurden, speichert, so dass man sie in Zukunft nicht ständig neu erstellen muss.

Verwendung bedingter Kompilierungsfunktionen

Erstens, lassen Sie uns die Portierung des Hilfsprogramms auf die MQL4-Sprache vereinfachen. Im vorherigen Artikel haben wir einen Codeblock durch einen anderen ersetzt, damit das Programm in MQL4 funktioniert. Jetzt stehen wir vor einer schwierigeren Aufgabe. Wir können entweder die Entwicklung in einer einzigen Sprache, z.B. MQL5, durchführen und dann ständig Codeblöcke, die nicht in MQL4 funktionieren, durch die notwendigen ersetzen, oder wir können gleichzeitig zwei Programme entwickeln: in MQL5 und MQL4.

Keine der beiden Optionen ist optimal. Wir sollten entweder ständig die Blöcke (in jeder Version) ersetzen, die nicht in MQL4 funktionieren, oder die Teile des Codes beachten, die wir geändert haben, um diese Änderungen in das Hilfsprogramm in einer anderen Sprache zu implementieren.

Deshalb werden wir einen anderen Ansatz verfolgen. Sowohl MQL5 als auch MQL4 unterstützen bedingte Kompilierungsanweisungen, die die Ausführung eines oder mehrerer Codeblöcke abhängig von den Bedingungen ermöglichen. Unter den Direktiven gibt es eine Konstruktion, die in Abhängigkeit von der aktuellen MQL-Sprachversion ausgeführt wird. Deren Hauptsyntax lautet wie folgt:

      #ifdef __MQL5__ 
         // Codeblock, der nur in MQL5 ausgeführt wird
      #else 
         // Codeblock, der nur in MQL4 ausgeführt wird
      #endif 

Wir verwenden das, damit unsere Funktion checkSYMBwithPOS sowohl auf MQL5 als auch auf MQL4 korrekt funktioniert, ohne dass wir ständig ersetzen müssen:

bool checkSYMBwithPOS(string name){
   // Ausblenden der Symbole mit Positionen und Aufträge
   bool isskip=false;
   if( noSYMBwithPOS ){
      #ifdef __MQL5__ 
         // Anzeigen der Liste aller offenen Positionen
         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // auslassen, wenn es eine Position gibt für das aktuelle Symbol
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               break;
            }
         }
         if(!isskip){
            int cntMyPosO=OrdersTotal();
            if(cntMyPosO>0){
               for(int ti=cntMyPosO-1; ti>=0; ti--){
                  ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;
                     break;
                  }
               }
            }
         }
      #else 
         // Anzeigen der Liste aller offenen Positionen
         int cntMyPos=OrdersTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if(OrderSymbol() == name ){
               isskip=true;
               break;
            }
         }
      #endif 
   }
   return isskip;
}

Weiter unten im Artikel werden die Codeblöcke direkt portiert, die in MQL4 mit dieser Konstruktion nicht funktionieren.

Hausaufgaben-Tabs

Um die Wälder der Erde zu retten, werden wir drei Tabs (Registerkarten) erstellen, die nur die zuvor ausgewählten Symbole anzeigen sollen. Benennen wir diese Tabs Long, Short und Range. Natürlich müssen wir dort nicht nur Symbole auf-, ab- oder seitwärts hinzufügen. Sie können sie nach eigenem Ermessen verwenden.

Infolgedessen wird das Diagramm, das unser Hilfsprogramm bietet, in einer weiteren Zeile mit vier Schaltflächen gestartet: die Schaltfläche All und die drei zuvor beschriebenen.

Die Schaltfläche All ist standardmäßig gedrückt, d.h. die Liste aller Symbole, die zu unseren Filtern passen, soll unten angezeigt werden:

Neue Tabs zur Auswahl der Hausaufgaben

Damit sind unsere Ziele festgelegt. Jetzt müssen wir sie nur umsetzen. Um dies zu tun, müssen wir einen kleinen Teil unseres Hilfsprogramms neu schreiben.

Arrays zum Speichern der Tab-Inhalte. Zuerst fügen wir Variablen hinzu, um den Inhalt unserer Registerkarten zu speichern. Bisher hatten wir nur einen Tab und sein Inhalt wurde in der Variablen arrPanel1 gespeichert. Wir werden ähnliche Variablen für andere Registerkarten hinzufügen:

// Array-Symbole, die in der entsprechenden Registerkarte angezeigt werden:
CArrayString arrPanel1;
CArrayString arrPanel2;
CArrayString arrPanel3;
CArrayString arrPanel4;

Zusätzlich werden wir ein weiteres Array erstellen, um in einer Schleife auf die Tabs zugreifen zu können. Das Array speichert die Pointer auf alle vier zuvor erstellten Arrays:

// Array, der alles zusammenfasst
CArrayString *arrPanels[4];

Initialisieren wir das Array in der Funktion OnInit():

   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;

Tab headers. Da die Arbeit mit den Tabs in einer Schleife erfolgen soll, wäre es sinnvoll, die Namen der Tabs dort zu speichern und auch von der Schleife aus darauf zuzugreifen. Lassen Sie uns daher das Array mit Tab-Namen erstellen:

// Array der Tab-Namen
string panelNames[4]={"All", "LONG", "SHORT", "Range"};

Hilfsvariablen. Eine weitere Änderung betrifft die Variable panel1val. Wir haben seinen Namen geändert in panelval. Dies ist ein rein kosmetischer Änderungsantrag, der jedoch zur Kenntnis genommen werden sollte.

Der Parameter cur_panel mit dem Index der aktuell aktiven Registerkarte wurde ebenfalls hinzugefügt. Der Variablentyp ist uchar. Das bedeutet, dass er Werte von 0 bis 255 annehmen kann, was völlig ausreichend ist, da wir nur 4 Tabs haben.

Standardmäßig ist die erste Registerkarte (mit dem Index 0 im Array) aktiv. Daher werden wir die Zeichenkette hinzufügen, die der Variablen in der Funktion OnInit() den Wert 0 zuweist. Schließlich nimmt die Funktion OnInit() ihre endgültige Form an:

int OnInit()
  {
   // Index des aktuell aktiven Tabs
   cur_panel=0;
   // Initialisieren des Arrays der Tabs
   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;
   
   start_symbols();

//--- Timer erstellen
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }

Andere Änderungen Eine Auflistung aller implementierten Änderungen ist unnötig, da viele von ihnen zu unbedeutend sind. Kommen wir also zu den wichtigsten Änderungen. Was kleine Änderungen betrifft, so können Sie sie selbst erkennen, indem Sie den Quellcode des neuen Hilfsprogramms mit dem des vorherigen Artikels vergleichen.

Die wichtigsten Änderungen sind vor allem diejenigen, die mit der Anzeige unserer neuen Tabs zusammenhängen. Wir haben uns entschieden, mit unseren Tabs in einer Schleife zu arbeiten. Mal sehen, wie wir das machen werden.

Da wir eine Zeile mit Tabs haben, müssen wir sie irgendwie anzeigen. Um dies zu erreichen, schaffen wir eine eigene Funktion. Der Code ist unten aufgeführt:

void show_panel_buttons(){
   int btn_left=0;
   // Definieren des maximal möglichen x-Achse 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); 
      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;
   }

}

Wir werden die Funktion in der Funktion start_symbols aufrufen, bevor wir die Funktion show_symbols mit den Symboltasten aufrufen.

Die Funktion show_symbols selbst hat sich ebenfalls geändert, wenn auch nur leicht. Jetzt zeigen wir nur noch die Schaltflächen der Symbole an, die sich auf der aktuell aktiven Registerkarte befinden:

   // Anzeige einer Schaltfläche auf dem Chart für jedes Symbol im Array der
   // aktuell aktive Registerkarte.
   // Wir werden einen Symbolnamen auf die Schaltfläche schreiben.
   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;
   }

Schaltflächen zum Hinzufügen von Symbolen zu den Registerkarten. Jetzt müssen wir Symbole zu einer ausgewählten Registerkarte hinzufügen. Wir werden dies mit Hilfe der neuen Schaltflächen Add LONG, Add SHORT und Add Range auf dem geöffneten Chart tun.

Wenn Sie sich erinnern, haben wir im letzten Artikel die Möglichkeit implementiert, auf den gewünschten Symboltaste zu klicken. Nach dem Anklicken eines Symbols wird ein Chart mit dem Schaltflächenblock zur Navigation durch die gesamte Symbolliste in der linken unteren Ecke geöffnet. Wir werden unsere Schaltflächen zu diesem Block hinzufügen. Je nachdem, ob sich dieses Symbol in der entsprechenden Registerkarte befindet, fügt die Schaltfläche es entweder der Registerkarte hinzu oder löscht es von dort.

Der Tastenblock wird mit der Funktion createBTNS angezeigt. Füge die Schleife des Hinzufügens neuer Buttons hinzu:

   for( int i=ArraySize(panelNames)-1; i>0; i-- ){
      isyes=false;
      if(arrPanels[i].Total()){
         for(int j=0; j<arrPanels[i].Total(); j++){
            if( arrPanels[i].At(j)==name ){
               isyes=true;
               break;
            }
         }
      }
      if( isyes ){
         ObjectCreate(CID, exprefix+"_p_btn_panelfrom"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_TEXT,"Del "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_BGCOLOR,clrPink); 
      }else{
         ObjectCreate(CID, exprefix+"_p_btn_panelto"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_TEXT,"Add "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_BGCOLOR,clrHoneydew); 
      }
      tmpHeight+=25;
   }

Block der Navigationstasten

Um das Drücken der neuen Tasten zu aktivieren, fügen Sie den Prüfungscode für den Tastenstatus zur Funktion OnTimer() hinzu. Alles ist so, wie wir es im letzten Artikel gemacht haben:

            for( uchar i=1; i<ArraySize(panelNames); i++ ){
               // Wenn das Entfernen von der Registerkarte gedrückt wurde, wird zuerst das Symbol entfernt und das Chart des Symbols erneut geöffnet.
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_STATE)==true ){
                  delToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  curchart();
                  return;
               }
               // wenn die Tab-Taste Hinzufügen gedrückt wurde, wird erst das Symbol hinzugefügt und dann das Chart geöffnet
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelto"+(string) i,OBJPROP_STATE)==true ){
                  addToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  nextchart();
                  return;
               }
            }

Die Funktion delToPanel entfernt zunächst das Symbol aus der ausgewählten Registerkarte und aktualisiert dann entweder alle Symboltasten auf dem Chart, bei dem das Hilfsprogramm gestartet wird, oder nur die Kopftasten:

void delToPanel(uchar num, string name){
   // Bewegung entlang des gesamten Arrays und Entfernen des ersten Elements
   // mit einem Namen ähnlich unserem Symbol
   for(int i=0; i<arrPanels[num].Total(); i++){
      if( arrPanels[num].At(i)==name ){
         arrPanels[num].Delete(i);
         break;
      }
   }
   // Wenn Tab ist aktuell geöffnet,
   if(num==cur_panel){
      initial_btn_line();
      // Entfernen vorher erstellter Symboltasten vom Chart:
      ObjectsDeleteAll(0, exprefix);
      // Anzeigen der aktualisierten Symbolliste:
      show_panel_buttons();
      show_symbols();
   }else{
      // wenn irgendein anderes Tab geöffnet ist, werden einfach nur Headertasten aktualisiert
      upd_panel_title();
   }
   
   
}

Die Funktion addToPanel ist das Gegenteil von dem, was wir gerade in Betracht gezogen haben. Sie fügt der Registerkarte ein Symbol hinzu. Außerdem wird geprüft, ob das Symbol auf anderen Registerkarten vorhanden ist. Wenn das Symbol vorhanden ist, wird es von dort entfernt:

void addToPanel(uchar num, string name){
   // ein Symbol zur Registerkarte hinzufügen
   arrPanels[num].Add(name);
   // ein Symbol aus anderen Registerkarten entfernen
   // wenn vorhanden
   for( int j=1; j<ArraySize(arrPanels); j++ ){
      if(j==num) continue;
      for(int i=0; i<arrPanels[j].Total(); i++){
         if( arrPanels[j].At(i)==name ){
            if( panelval==i && i>0 ){
               panelval--;
            }
            arrPanels[j].Delete(i);
            break;
         }
      }
   }
   if(num==cur_panel){
      initial_btn_line();
      // Entfernen vorher erstellter Symboltasten vom Chart:
      ObjectsDeleteAll(0, exprefix);
      // Anzeige der Symbolliste
      show_panel_buttons();
      show_symbols();
   }else{
      upd_panel_title();
   }
}

Speichern der Inhalte der Registerkarten zwischen den Starts des Hilfsprogramms. Was passiert, wenn wir versehentlich den EA schließen? Dann werden alle unsere Bemühungen umsonst gewesen sein. Sollen wir anfangen, alles wieder hinzuzufügen? Stellen wir sicher, dass die Symbollisten, die wir zu den Hausaufgaben-Registerkarten hinzufügen, in der Datei gespeichert und beim weiteren Öffnen wiederhergestellt werden.

Wir haben die Objekte vom Typ CArrayString verwendet, um die Listen der ausgewählten Symbole aus einem bestimmten Grund zu speichern. Einer der vielen Vorteile von Objekten dieses Typs sind Standardmethoden, die es einfach machen, sowohl den gesamten Inhalt eines Arrays an eine Datei zu senden als auch ein Array aus einer Datei wiederherzustellen. Verwenden wir sie, um den Inhalt der Arrays in einer Datei zu speichern, bevor wir das Hilfsprogramm schließen. Mit anderen Worten, wir sollten den Aufruf unserer neuen Funktion savePanels zur Standardfunktion OnDeinit() hinzufügen:

void savePanels(){
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

Der Inhalt des Arrays wird in der Standardfunktion OnInit() wiederhergestellt:

   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Load(fh);
         FileClose(fh); 
      }
   }

Hinzufügen eines Headers zur Identifizierung der aktuellen Parameter

Wenn Sie in verschiedenen Märkten handeln, müssen Sie das Hilfsprogramm beim Wechsel von einem Markt zum anderen ständig anpassen. Denn wenn Sie derzeit die amerikanische Börse handeln wollen, die in den ersten anderthalb Stunden nach der Marktöffnung auf einen Durchbruch wartet, dann brauchen Sie keine Aktien des europäischen oder russischen Marktes, die lange vorher eröffnet wurden. Und wenn Sie auf dem russischen Markt handeln, benötigen Sie keine US-Aktien.

Um nicht abgelenkt zu werden und sich ausschließlich auf die notwendigen Symbole zu konzentrieren, wäre es sinnvoll, getrennte Parametersätze für Märkte verschiedener Länder sowie für den Devisenmarkt zu erstellen und jede einzelne Set-Datei hochzuladen, wenn es notwendig ist. Das ist ganz einfach und dauert nur wenige Sekunden. Es ist jedoch schwer zu verstehen, welche Einstellungen im Moment hochgeladen werden.

Um den verwendeten Parametersatz zu sehen, werden wir den Eingabeparameter Input cmt hinzufügen, wo wir eine Erklärung über den Markt, mit dem wir derzeit arbeiten, aufschreiben:

input string         cmt=""; //Parameter für (eng)

Dieser Kommentar wird in einer Zeile mit Schaltflächen unserer Registerkarten angezeigt:

Anzeige der aktuellen Einstellungen des Headers

Um dies zu erreichen, fügen wir der Funktion show_panel_buttons nach der Darstellung aller Schaltflächen den folgenden Codeblock hinzu:

   // Anzeige eines Kommentars, wenn angegeben:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      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);
   }

Neben der Identifizierung des aktuellen Parametersatzes hilft uns die Eingabe von cmt, die Symbollisten in den Registerkarten der Hausaufgaben zu trennen. Wenn wir nämlich das Symbol zu einer Hausaufgabenliste für die Arbeit auf einem US-Markt hinzufügen, brauchen wir dieses Symbol nicht, wenn wir auf dem russischen Aktienmarkt arbeiten. Set-Dateien mit unterschiedlichen Parametersätzen sollten auch separate Listen für die Hausaufgaben-Tabs enthalten.

Um sie zu implementieren, müssen wir den Code, der Arrays in der Datei speichert und aus der Datei wiederherstellt, leicht modifizieren. Betrachten wir beispielsweise die modifizierte Funktion zum Speichern in eine Datei:

void savePanels(){
   string tmpCmt=cmt;
   StringReplace(tmpCmt, " ", "_");
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+tmpCmt+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

Speichern von grafischen Objekten

Ein weiteres Problem, das wir lösen müssen, um mit Diagrammen zu arbeiten, ist die automatische Speicherung und Wiederherstellung von grafischen Objekten, die wir auf dem Diagramm erstellt haben. Wenn wir einen Level auf dem Chart festlegen, erwarten wir, dass er nach dem Schließen des Chart Fensters und dem erneuten Öffnen wieder zu sehen ist. Wir wollen sicher nicht jedes Mal, wenn wir ein Symboldiagramm öffnen, Dutzende von Ebenen platzieren.

Der Code, den wir bisher geschrieben haben, funktioniert sowohl in MQL5 als auch in MQL4 gleichermaßen gut. Dies ist bei den Funktionen zum Speichern und Wiederherstellen von grafischen Objekten nicht der Fall. In MQL4 werden der grafische Objekttyp und seine separaten Eigenschaften durch Konstanten vom numerischen Typ beschrieben, während MQL5 dafür Enumerationen (Enum-Typ) verwendet. Deshalb ist es recht schwierig, sie in einer Datei zu speichern und wiederherzustellen. Zumindest konnte ich diese Aufgabe im allgemeinen Sinne nicht bewältigen. Die Funktionsweise der Speicherung von grafischen Objekten für MQL4 hilft uns hier mehr. Theoretisch kann jedes grafische Objekt gespeichert werden (ich habe es nicht mit allen Objekten getestet, so dass Ausnahmen möglich sind). Die MQL5-Funktionen erlauben es, nur mit horizontalen Linien, Labels und Textfeldern zu arbeiten. Wenn man andere grafische Objekte speichern möchte, muss man das selbst implementieren.

MQL4. Da der Code zum Speichern von grafischen Objekten für MQL4 einfacher ist, beginnen wir mit Funktionen für diese Sprache. Die Funktion zum Speichern von geografischen Objekten in einer Datei:

   void savechart(ulong id){
      // sichern von Grafikobjekten nur, wenn 
      // der Parameter "Save created graphical objects" = true
      if(saveGraphics){
         // Anrufen des Symbolnamens
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         // löschen des Arrays der Grafikobjekte
         saveG.Resize(0);
         
         // Hinzufügen aller nutzerdefinierten Grafikobjekte mit deren Eigenschaften zum Array
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         // Sichern des Arrayinhalts in der Datei
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }

Wie Sie sehen können, behalten wir nicht alle Eigenschaften des Objekts, sondern nur OBJPROP_COLOR, OBJPROP_STYLE, OBJPROP_WIDTH, OBJPROP_TIME, OBJPROP_TIMEFRAMES, OBJPROP_TIMEFRAMES, OBJPROP_ANCHOR, OBJPROP_XDISTANCE, OBJPROP_YDISTANCE, OBJPROP_STATE, OBJPROP_XSIZE, OBJPROP_YSIZE, OBJPROP_XOFFSET, OBJPROP_YOFFSET, OBJPROP_BGCOLOR, OBJPROP_BORDER_COLOR, OBJPROP_PRICE und OBJPROP_TEXT. Wenn eines der angewandten grafischen Objekte während der Arbeit mit dem Hilfsprogramm falsch gespeichert wird, bedeutet dies, dass nicht alle angewandten Eigenschaften gespeichert wurden. In diesem Fall fügen Sie einfach das Speichern fehlender Eigenschaften zu dieser Funktion hinzu, so dass auch diese Art von grafischen Objekten unterstützt wird.

Werfen wir nun einen Blick auf die Funktion, die grafische Objekte aus einer Datei hochlädt und auf dem Chart anzeigt:

   void loadchart(ulong id){
      // anzeigen von Grafikobjekten nur, wenn 
      // der Parameter "Save created graphical objects" = true
      if(saveGraphics){
         // Anrufen des Symbolnamens
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         // löschen des Arrays der Grafikobjekte
         saveG.Resize(0);
         // lesen der Liste der Grafikobjekte aus der Datei in das Array
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         // Anzeige der Grafikobjekte nacheinander auf dem Chart
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // Der Objekttyp steht immer am Anfang der Zeile
                        // sodass wir das Objekt immer zuerst erstellen und dann seine Eigenschaften bilden
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           ObjectCreate(id, tmpObjName, (int) tmpObjSubLine[3], 0, 0, 0);
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           ObjectSetInteger(id, tmpObjName, (int) tmpObjSubLine[2], (int) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           ObjectSetDouble(id, tmpObjName, (int) tmpObjSubLine[2], (double) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           ObjectSetString(id, tmpObjName, (int) tmpObjSubLine[2], tmpObjSubLine[3]);
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

MQL5. Wie bereits erwähnt sind diese Funktionen in MQL5 nicht sehr effizient.

   void savechart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         saveG.Resize(0);
         
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               // wir können nur mit den Objekttypen OBJ_HLINE, OBJ_TEXT and OBJ_LABEL arbeiten,
               //daher werden andere Objekttypen übergangen
               if( ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_HLINE && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_TEXT && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_LABEL ){
                  continue;
               }
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }
   void loadchart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         saveG.Resize(0);
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // Erstellen eines Objekts abhängig vom Typ
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           switch((int) tmpObjSubLine[3]){
                              case 1:
                                 ObjectCreate(id, tmpObjName, OBJ_HLINE, 0, 0, 0);
                                 break;
                              case 101:
                                 ObjectCreate(id, tmpObjName, OBJ_TEXT, 0, 0, 0);
                                 break;
                              case 102:
                                 ObjectCreate(id, tmpObjName, OBJ_LABEL, 0, 0, 0);
                                 break;
                           }
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_COLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STYLE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STYLE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_WIDTH"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_WIDTH, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIME"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIME, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIMEFRAMES"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIMEFRAMES, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_ANCHOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_ANCHOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STATE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STATE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BGCOLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BGCOLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BORDER_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BORDER_COLOR, (int) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_PRICE"){
                              ObjectSetDouble(id, tmpObjName, OBJPROP_PRICE, (double) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_TEXT"){
                              ObjectSetString(id, tmpObjName, OBJPROP_TEXT, tmpObjSubLine[3]);
                           }
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

Wenn wir genauer hinsehen, werden wir feststellen, dass wir eine separate Zeichenkette verwenden müssen, um ein Objekt für Objekte verschiedener Typen zu erstellen, während in MQL4 eine Zeichenkette für alle Objekte ausreicht. Dies gilt auch für Objekteigenschaften. In MQL4 haben wir eine Zeichenkette zur Erstellung von Eigenschaften pro Typ verwendet (Zeichenkette, real oder integer). In MQL5 benötigt jede Eigenschaft eine separate Zeichenkette ihrer Erstellung.

Sprachen kombinieren. Verwenden wir die bedingte Kompilierung, damit das EA die notwendige Version der Funktionen je nach Sprache anwendet:

#ifdef __MQL5__ 
   void savechart(ulong id){
      // MQL5-Funktionen
   }
   void loadchart(ulong id){
      // ...
   }
#else 
   void savechart(ulong id){
      // MQL4 Funktion
   }
   void loadchart(ulong id){
      // ...
   }
#endif 

Anwendung der Funktionen. Fügen wir nun den Aufruf der Funktionen zu den entsprechenden Teilen des Programms hinzu.

Der Aufruf der Funktion loadchart wird der Funktion showcharts hinzugefügt, die das Chart gemäß der von uns gedrückten Schaltfläche öffnet.

Der Aufruf der Diagrammspeicherung soll zu den Codeblöcken hinzugefügt werden, die sich auf die Reaktion auf das Drücken der Navigationstasten der Karte beziehen: Next chart, Prev chart und Close chart, sowie die Schaltflächen zum Hinzufügen/Entfernen eines Symbols zur Registerkarte der Hausaufgaben.

Nutzung der Website finviz.com zur Vorauswahl von Aktien

Im vorherigen Artikel haben wir erwähnt, dass die Symbolliste für die Filtration nicht nur aus der Liste der vom Broker angebotenen Symbole, sondern auch aus einer Eingabe übernommen werden kann. Dies ermöglicht es zunächst, nur den begrenzten Satz von Symbolen in der erforderlichen Reihenfolge anzuzeigen. Zweitens kann der nutzerdefinierte Satz von Symbolen verwendet werden, um eine Vorfiltration auf der finviz.com oder einer ähnlichen Website durchzuführen.

Im vorherigen Artikel haben wir einen Satz von mehreren Eingaben gebildet, die es den Nutzern ermöglichen, Symbole nach Preis, ATR usw. zu sortieren. Diese Funktionen sind jedoch im Vergleich zum Screener der Website von finviz.com. Am wichtigsten ist, dass MQL4 keine Möglichkeit bietet, Symbole nach einem realen Volumen zu sortieren, während dies bei vielen Strategien, die auf Handelsebenen basieren, ein sehr wichtiger Indikator ist. Auf der Website finviz.com können Sie nach dem durchschnittlichen Volumen der Aktie sowie nach dem Volumen des aktuellen Tages sortieren.

Hinzufügen der Möglichkeit, die Symbolliste aus dem Eingabeparameter zu erhalten. Um eine Symbolliste eines Drittanbieters zu verwenden, fügen wir dem Hilfsprogramm drei zusätzliche Eingaben hinzu:

input string         ""; // nur Symbole (Trennzeichen - ; oder Leerzeichen)
input string         ""; // Prefix zum Symbol hinzufügen
input string         ""; // Suffix zum Symbol hinzufügen

Wir benötigen die Parameter onlySymbolsPrefix und onlySymbolsSuffix, wenn die Namen der von Ihrem Broker angebotenen Symbole von den offiziellen Tickern abweichen. Einige Broker fügen das Suffix .us für US-Aktien und das Suffix .eu für europäische Aktien hinzu, während einige Broker das Suffix m für alle Ticker hinzufügen. Broker können auch ein # zu Beginn von Börsenticker hinzufügen.

Hinzufügen der Möglichkeit, Symbole aus einer Datei zu importieren. Mit Blick auf die Zukunft möchte ich gleich sagen, dass wir ein Problem mit dem Import von Symbolen aus der Eingabe haben werden. Dieses Problem betrifft die maximale Zeichenkettenlänge. Bei der Verwendung der Eingabe sind wir auf maximal 15-20 Ticker begrenzt. Daher kann die Eingabe nur dazu verwendet werden, Arbeitssymbole durch eine kleine Anzahl von Symbolen zu begrenzen.

Daher können man zusätzlich zur Eingabe auch die notwendigen Symbole in die Datei symbols.txt einfügen, die im Ordner Files erstellt wurde.

Implementierung in den Code. Teilen wir den Prozess der Bildung der Liste der Symbole für die Registerkarte All in zwei Blöcke.

Der erste Block prüft, ob es Symbole in der Datei oder Eingabe gibt. Wenn ja, werden sie dem Array result zugewiesen. Das Array wird der Funktion OnInit() hinzugefügt:

         // wenn die Eingabe "Symbols only (separator - ; or space)"
         // beliebige Daten hat
         if( StringLen(onlySymbols)>0 ){
            // wird die Zeile der Eingabe in die Array-Elemente aufgeteilt
            // da die Elemente sind durch ; getrennt sind
            StringSplit(onlySymbols,StringGetCharacter(";",0),result); 
            if( ArraySize(result)>1 ){
            }else{
               // wenn es aufgrund einer Arrayaufteilung nur ein Element gibt
               // dann ist die Aufteilung fehlgeschlagen, und anscheinend ist das Trennzeichen in der Zeile das Leerzeichen
               // deshalb teilen wir jetzt die Zeile von der Eingabe in Arrayelemente auf
               // und Leerzeichen werden als Trennzeichen für die Elemente verwendet
               StringSplit(onlySymbols,StringGetCharacter(" ",0),result); 
            }
         // andernfalls wird geprüft, ob der Dateiordner die Datei symbols.txt enthält
         }else if( FileIsExist("symbols.txt") ){
            // gibt es die Datei, wird deren Inhalt der temporären Variable 'outfile' zugewiesen
            int filehandle=FileOpen("symbols.txt",FILE_READ|FILE_TXT); 
            if(filehandle>=0){
               string outfile=FileReadString(filehandle);
               // wenn die Variable 'outfile' eine Zeichenkette enthält,
               // versuchen wir, sie in die Array-Elemente aufzuteilen.
               // Erstens, durch die Verwendung des Trennzeichens ; gefolgt vom Leerzeichen
               if(StringLen(outfile)>0){
                  StringSplit(outfile,StringGetCharacter(";",0),result); 
                  if( ArraySize(result)>1 ){
                  }else{
                     StringSplit(outfile,StringGetCharacter(" ",0),result); 
                  }
                  if( ArraySize(result)>1 ){
                     from_txt=true;
                  }
               }
               FileClose(filehandle);
            }
         }

In der Funktion prepare_symbols prüfen wir zunächst, ob das Array result irgendwelche Daten enthält und verwenden sie in diesem Fall. Andernfalls verwenden wir entweder alle vom Broker angebotenen oder die im Panel Marktübersicht existierenden Symbole für die weitere Sortierung:

   // Wenn das Array mehr als zwei Symbole enthält, verwenden wir diese
   // und ergänzen das notwendige Suffix oder Präfix falls nötig.
   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);
      }
   // andernfalls werden Symbole des Brokers verwendet
   }else{
      for( int i=0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
         tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
      }
   }

Erstellen einer Symbolliste mit dem Screener von finviz.com. Abschließend werfen wir einen Blick darauf, wie man die auf finviz.com ausgewählten Ticker in unser Hilfsprogramm importiert.

Ganz einfach. Nach der Sortierung gehen wir auf die Registerkarte Tickers auf der Seite des Screeners. Wir sehen die Cloud mit den Namen der ausgewählten Ticker. Wir markieren alle, kopieren und fügen sie entweder in die Datei symbols.txt oder in die Eingabe ein. Wenn es mehrere Seiten mit Sortierergebnissen gibt, gehen wir zur nächsten Seite und machen dasselbe.

Schlussfolgerung

Wir haben eine Menge Arbeit geleistet und unser Hilfsprogramm funktioneller gemacht. Jetzt können wir es einfach verwenden, um Aktien auszuwählen, ohne die Papiernotizen zu vergessen. Ich hoffe, die Wälder des Planeten werden das zu schätzen wissen. =)