English Русский 中文 Español 日本語 Português
preview
Multibot in MetaTrader: Starten mehrerer Roboter von einem einzigen Chart aus

Multibot in MetaTrader: Starten mehrerer Roboter von einem einzigen Chart aus

MetaTrader 5Beispiele | 1 Juni 2023, 13:49
445 0
Evgeniy Ilin
Evgeniy Ilin

Inhalt


Einführung

In der Welt der Finanzmärkte sind automatisierte Handelssysteme zu einem festen Bestandteil des Entscheidungsprozesses geworden. Diese Systeme können so konfiguriert werden, dass sie den Markt analysieren, Einstiegs- und Ausstiegsentscheidungen treffen und Geschäfte nach vordefinierten Regeln und Algorithmen ausführen. Das Einrichten und Ausführen von Robotern auf mehreren Charts kann jedoch eine zeitraubende Aufgabe sein. Jeder Roboter muss für jeden Chart einzeln konfiguriert werden, was zusätzlichen Aufwand bedeutet.

In diesem Artikel zeige ich Ihnen meine Implementierung einer einfachen Vorlage, mit der Sie einen universellen Roboter für mehrere Charts in MetaTrader 4 und 5 erstellen können. Unsere Vorlage ermöglicht es Ihnen, den Roboter an einen Chart anzuhängen, während die restlichen Charts innerhalb des EAs verarbeitet werden. So vereinfacht unsere Vorlage den Prozess der Einrichtung und Ausführung von Robotern auf mehreren Charts erheblich und spart Händlern Zeit und Mühe. In diesem Artikel werde ich den Prozess der Erstellung eines solchen Roboters in MQL5 von der Idee bis zum Test im Detail betrachten.


Problemstellung und Grenzen der Anwendbarkeit

Diese Idee kam mir vor nicht allzu langer Zeit, obwohl ich ähnliche Entscheidungen von professionellen Verkäufern schon seit langem beobachte. Mit anderen Worten, ich bin nicht der erste und nicht der letzte, der auf diesem Gebiet eine Idee hat, aber wie immer müssen einige Bedingungen erfüllt sein, damit der Programmierer zu solchen Entscheidungen kommt. Der Hauptgrund für die Entwicklung solcher Expert Advisors im MQL5-Speicher ist der Wunsch nach Nutzerkomfort. In meinem Fall gab es jedoch eine etwas andere Motivation. Meine Motivation war, dass ich zunächst entweder mehrere Strategien gleichzeitig für mehrere Instrumente oder dieselbe Strategie testen musste, um ihre Mehrwährungseigenschaften zu sehen.

Darüber hinaus ist ein sehr wichtiger Faktor beim Testen einer Strategie im Tester, insbesondere im Multiwährungsmodus, eine allgemeine Gewinn- und Verlustkurve, die die Grundlage für jede Bewertung von automatischen Handelssystemen beim Backtesting auf historischen Daten ist. Wenn Handelssysteme separat für ein Instrument getestet werden, ist es recht schwierig, solche Berichte später zu kombinieren. Mir sind keine solchen Tools bekannt, zumindest nicht für MetaTrader 5. Für die vierte Version des Terminals gibt es ein inoffizielles Tool für solche Manipulationen. Ich habe sie zumindest in einem Artikel verwendet, aber natürlich ist ein solcher Ansatz nicht empfehlenswert.

Neben dem Testprozess gibt es einen ebenso wichtigen Prozess des automatischen Handels selbst und der Synchronisierung ähnlicher EAs, die unabhängig voneinander arbeiten, jeder auf seinem eigenen Chart. Wenn es zu viele solcher Charts gibt, können zusätzliche Computerressourcen benötigt werden , die die Handelsleistung verlangsamen oder verschlechtern und zu unerwarteten Fehlern und anderen unangenehmen Vorfällen führen, die sich nachteilig auf das endgültige Handelsergebnis auswirken können. Für jeden solchen EA müssen wir uns eindeutige Auftragskennungen, einen Schutz gegen hochfrequente Serveranfragen und viele andere Dinge einfallen lassen, die auf den ersten Blick nicht ersichtlich sind.

Die Bearbeitung des grafischen Teils des EA ist ein separates und sehr sensibles Thema. Nun machen alle mehr oder weniger geschickten EA-Schöpfer zumindest eine Minimalversion einer Anzeige auf dem Chart, an den der EA angehängt ist. Auf diese Weise wirkt der EA seriöser und erweckt mehr Vertrauen, und schließlich erlaubt die Anzeige einiger Informationen auf dem Chart für den Nutzer manchmal eine effektivere Kontrolle über den EA-Handelsprozess. Außerdem ist es möglich, bei Bedarf Elemente zur manuellen Steuerung hinzuzufügen. All dies wird als Nutzeroberfläche bezeichnet. Bei der Verteilung solcher EAs über Charts steigt die Belastung durch die Aktualisierung der grafischen, textlichen und numerischen Informationen in den Schnittstellen exponentiell an. Bei der Verwendung eines Multi-Templates haben wir natürlich eine Schnittstelle, die ein Minimum an Ressourcen auf dem Terminal benötigt.

Natürlich löst eine solche Vorlage nicht alle Probleme, aber dennoch hilft sie mir bei meinen Projekten sehr. Ich verwende verschiedene Roboter, und im Allgemeinen haben alle Ansätze ihre Daseinsberechtigung, aber ich denke, dass dieses Muster für viele Programmieranfänger nützlich sein könnte. Es ist nicht notwendig, sie vollständig zu kopieren, aber wenn Sie es wünschen, können Sie sie leicht an Ihre Bedürfnisse anpassen. Mein Ziel ist es nicht, Ihnen etwas Außergewöhnliches zu bieten, sondern eine der Möglichkeiten zur Lösung eines solchen Problems aufzuzeigen und zu erklären.


Unterschiede zwischen den Terminals MetaTrader 4 und MetaTrader 5 in Bezug auf die Verwendung eines Multibots

Was mir am neuesten MetaTrader 5 gefällt, ist die Leistungsfähigkeit seines Testers, der Ihnen alle Funktionen bietet, die Sie benötigen, um mehrere Instrumente gleichzeitig zu testen, vorausgesetzt, Sie verwenden den oben genannten Ansatz zur EA-Entwicklung. Der Tester synchronisiert die Kurse automatisch nach der Zeit und bietet Ihnen eine klar synchronisierte Gewinnkurven auf einer Zeitskala. MetaTrader 4 hat keine solche Funktion. Ich denke, das ist sein größter Nachteil. Nichtsdestotrotz ist es erwähnenswert, dass MetaQuotes sein Bestes tut, um das Terminal 4 zu unterstützen, und seine Popularität ist immer noch hoch. Als aktiver Nutzer von MetaTrader 4 kann ich sagen, dass diese Mängel nicht so gravierend sind, wie sie erscheinen mögen.

Die Sprache MQL4 wurde vor einer Weile auf MQL5weiterentwickelt. Das bedeutet, dass wir beim Schreiben ähnlicher Vorlagen wie der unseren ein Minimum an Unterschieden im Code haben werden. Es ist meine gute Tradition, zu versuchen, Dinge für beide Terminals zu implementieren, daher werden Sie eine Vorlage für beide Terminals erhalten. Diese Verbesserungen des alten Terminals ermöglichen es uns unter anderem, die folgenden Funktionen zu nutzen, die wir wirklich brauchen:

  • CopyClose - Abfrage der Schlusskurse der Balken
  • CopyOpen - Abfrage der Eröffnungspreise der Balken
  • CopyHigh - Abfrage der Hochs der Balken
  • CopyLow - Abfrage der Tiefs der Balken
  • CopyTime - Abfrage zur Öffnungszeit der Balken
  • SymbolInfoTick - Abfrage des letzten eingehenden Ticks für das gewünschte Symbol
  • SymbolInfoInteger - Abfrage von Symboldaten, die durch Ganzzahlen und nummerierte Listen beschrieben werden können
  • SymbolInfo******* - andere, benötigte Funktionen

Diese Merkmale sind sowohl in MQL4 als auch in MQL5 vorhanden. Mit diesen Funktionen können Sie die Daten der Balken für jedes Symbol und jeden Zeitraum abrufen. Der einzige unangenehme Unterschied zwischen dem Testern der Versionen 4 und 5 ist die Tatsache, dass diese Funktionen im Terminal 4 nur für das aktuelle Chart funktionieren, auf dem der Test durchgeführt wird, und dass die restlichen Anfragen Sie einfach darüber informieren, dass aufgrund der Besonderheiten des MetaTrader 4-Testers keine Daten vorhanden sind. Wenn Sie unsere Vorlage testen, können Sie daher nur mit dem ausgewählten Symbol und nur mit einer der Gewinnkurven für einen einzelnen Roboter handeln.

Im Terminal 5 erhalten Sie bereits den Handel für alle gewünschten Symbole und die gemeinsame Saldenkurve. Was die Anwendung im Handel angeht, so erhalten Sie beim direkten Handel mit einem solchen Roboter in beiden Terminals die volle Leistung einer solchen Vorlage. Mit anderen Worten: Der Unterschied liegt nur im Tester. Aber auch in solchen Fällen können Sie sich damit abfinden, dass es besser ist, bei der Erstellung eines EA mit der Version für MetaTrader 5 zu beginnen. Nach allen notwendigen Tests können Sie schnell die Version für MetaTrader 4 erstellen.

Natürlich gibt es eine Reihe von Unterschieden, die ich nicht erwähnt habe. Ich möchte nur die Bedeutung einiger davon hervorheben, da diese Nuancen bekannt sein müssen, wenn man eine ausgeklügelte Struktur für eine solche Vorlage erstellt. MetaTrader 5 ist auf jeden Fall besser als sein Vorgänger, aber trotzdem habe ich nicht den Wunsch, das Terminal 4 loszuwerden, denn invielen Situationen ist sein Bedarf an Rechenressourcen im Vergleich zum fünften Terminal nicht so groß. Beide Werkzeuge sind nach wie vor gut.


Die Feinheiten der Erstellung einer universellen Vorlage

Um eine solche Vorlage zu erstellen, sollten Sie verstehen, wie das Terminal funktioniert, was ein Expert Advisor ist und was ein MetaTrader-Chart ist. Darüber hinaus sollten Sie wissen, dass jedes Chart ein separates Objekt ist. Jedes dieser Charts kann mit mehreren Indikatoren und nur einem EA verknüpft werden. Es kann mehrere identische Charts geben. In der Regel werden mehrere Charts erstellt, um mehrere verschiedene EAs in einem Symbolzeitrahmen laufen zu lassen oder um mehrere Kopien eines EAs mit unterschiedlichen Einstellungen laufen zu lassen. Wenn wir diese Feinheiten verstehen, sollten wir zu dem Schluss kommen, dass wir, um auf mehrere Charts zugunsten unserer Vorlage zu verzichten, all dies innerhalb unserer Vorlage implementieren müssen. Dies kann in Form eines Diagramms dargestellt werden:

Struktur der Objekte

Unabhängig davon sollte über Ticks gesprochen werden. Der Nachteil dieses Ansatzes ist, dass wir nicht in der Lage sein werden, die Ereignisbehandlung für das Erscheinen eines neuen Ticks für jeden Chart zu abonnieren. Wir müssen Ticks aus dem Chart anwenden, auf dem unser Roboter arbeitet, oder den Timer verwenden. Letztendlich bedeutet dies unangenehme Momente für Tickroboter, die wie folgt aussehen:

  • Wir müssen eigene Ereignisbehandlungen durch OnTick schreiben
  • Diese Ereignisbehandlungen müssen als ein Derivat von OnTimer implementiert werden
  • Ticks werden nicht perfekt sein, weil OnTimer mit einer Verzögerung arbeitet (der Wert der Verzögerung ist nicht wichtig, aber ihr Vorhandensein ist wichtig)
  • Um Ticks zu erhalten, benötigen Sie die Funktion SymbolInfoTick

Ich denke, für diejenigen, die jede Millisekunde zählen, kann dies ein unwiderstehlicher Moment sein, insbesondere für diejenigen, die Arbitrage lieben. Ich möchte dies jedoch nicht in meiner Vorlage hervorheben. Im Laufe der Jahre, in denen ich verschiedene Systeme entwickelt habe, kam ich zu dem Paradigma des Balkenhandels. Das bedeutet, dass Handelsoperationen und andere Berechnungen größtenteils dann stattfinden, wenn ein neuer Balken erscheint. Dieser Ansatz hat eine Reihe von offensichtlichen Vorteilen:

  • Die Ungenauigkeit bei der Bestimmung des Beginns eines neuen Balkens hat keinen wesentlichen Einfluss auf den Handel
  • Je länger die Dauer des Balkens ist, desto geringer ist dieser Einfluss.
  • Die Diskretisierung in Form von Balken ermöglicht eine Steigerung der Prüfgeschwindigkeit um Größenordnungen
  • Gleiche Qualität der Tests sowohl an echten als auch an künstlichen Ticks

Der Ansatz lehrt ein bestimmtes Paradigma für den Aufbau von EAs. Dieses Paradigma beseitigt viele Probleme, die mit Tick-EAs verbunden sind, beschleunigt den Testprozess, bietet eine höhere mathematische Gewinnerwartung, was das Haupthindernis ist, und spart außerdem viel Zeit und Rechenleistung. Ich denke, wir können noch viele weitere Vorteile finden, aber ich denke, das reicht im Rahmen dieses Artikels aus.

Um unsere Vorlage zu implementieren, ist es nicht notwendig, die gesamte Struktur des Arbeitsbereichs des Handelsterminals innerhalb unserer Vorlage zu implementieren, sondern es reicht aus, für jeden Roboter einen eigenen Chart zu implementieren. Dies ist nicht die optimalste Struktur, aber wenn wir uns darauf einigen, dass jedes einzelne Instrument nur einmal in der Liste der Instrumente vorkommt, dann ist diese Optimierung nicht erforderlich. Es wird so aussehen:

unsere Realisation

Wir haben die einfachste Struktur für die Implementierung von Charts implementiert. Jetzt ist es an der Zeit, über die Eingaben einer solchen Vorlage nachzudenken und, was noch wichtiger ist, darüber, wie die dynamische Anzahl von Charts und EAs für jede Situation im Rahmen der zulässigen Möglichkeiten der MQL5-Sprache berücksichtigt werden kann. Die einzige Möglichkeit, dieses Problem zu lösen, ist die Verwendung von Zeichenketten als Eingabevariablen. In einer Zeichenkette (string) können wir eine sehr große Menge an Daten speichern. Um alle notwendigen Parameter für eine solche Vorlage zu beschreiben, benötigen wir dynamische Arrays in den Eingabedaten. Natürlich wird niemand solche Dinge umsetzen, weil nur wenige Menschen solche Möglichkeiten nutzen würden. Die Zeichenkette ist unser dynamisches Array, in das wir alles schreiben können, was wir wollen. Nutzen wir sie also. Für meine einfachste Vorlage habe ich beschlossen, drei Variablen wie folgt einzuführen:

  • Charts - unsere Charts (Liste)
  • Chart Lots - Losgrößen für den Handel (Liste)
  • Chart Timeframes - Zeitrahmen der Charts (Liste)

Im Allgemeinen können wir all diese Daten in einer einzigen Zeichenkette zusammenfassen, aber dann wird ihre Struktur komplex und es wird für einen potenziellen Nutzer schwierig sein, herauszufinden, wie man die Daten korrekt beschreibt. Außerdem kann man beim Ausfüllen sehr leicht Fehler machen, und bei der Verwendung können sehr unangenehme Dinge passieren, ganz zu schweigen von der unglaublichen Komplexität der Konvertierungsfunktion, die diese Daten aus den Zeichenketten herausnimmt. Ich habe bei den Verkäufern ähnliche Lösungen gesehen, und im Allgemeinen haben sie alles richtig gemacht. Alle Daten werden einfach durch Kommata getrennt aufgelistet. Zu Beginn des EA werden diese Daten mit speziellen Funktionen aus einer Zeichenkette entnommen und in die entsprechenden dynamischen Arrays gefüllt, die dann im Code verwendet werden. Auch wir werden diesen Weg gehen. Wir können weitere ähnliche Zeichenfolgen mit identischen Enumerationsregeln hinzufügen. Ich beschloss ":" als Trennzeichen zu verwenden. Wenn wir ein Komma verwenden, ist nicht klar, wie wir mit Arrays vom Typ double wie Chart Lots umgehen sollen. Es ist möglich, mehr solcher String-Variablen hinzuzufügen, und im Allgemeinen ist es möglich, eine noch vollständigere und vielseitigere Vorlage zu erstellen, aber meine Aufgabe hier ist es nur zu zeigen, wie man dies implementiert und Ihnen die erste Version der Vorlage zu geben, die Sie schnell und einfach ändern können.

Es reicht nicht aus, solche Arrays zu implementieren, sondern es müssen z. B. auch gemeinsame Variablen implementiert werden:

  • Work Timeframe For Unsigned - ein Chartzeitrahmen, der nicht angegeben ist
  • Fix Lot For Unsigned - eine Losgröße, die nicht angegeben ist

Die Liste der Charts sollte nicht leer sein. Die gleiche Aktion ist optional für Chart Lots und Chart Timeframes. Zum Beispiel können wir einzelne Losgrößen für alle Charts und denselben Zeitrahmen für alle Charts nehmen. Eine ähnliche Funktionsweise wird in unserer Vorlage implementiert. Es ist wünschenswert, solche Umsetzungsregeln anzuwenden, wo immer dies möglich ist, um bei der Festlegung der Eingabeparameter eines EA, der auf der Grundlage solcher Vorlagen erstellt wurde, Kürze und Klarheit zu gewährleisten. 

Lassen Sie uns nun einige weitere wichtige Variablen für eine minimale Implementierung eines solchen Musters definieren:

  • Last Bars Count - die Anzahl der letzten Balken eines Charts, die wir für jedes Chart speichern
  • Deposit For Lot - Einlage für die Nutzung einer bestimmten Losgröße
  • First Magic - eindeutige ID für den Handel des jeweiligen EAs

Ich denke, die erste Variable ist ziemlich klar. Die zweite Variable ist weitaus schwieriger zu fassen. Auf diese Weise reguliere ich die automatische Losgröße in meinen EAs. Wenn ich sie auf „0“ setze, teile ich dem Algorithmus mit, dass er nur eine feste Losgröße handeln soll, das in der entsprechenden Zeichenkette oder in einer gemeinsamen Variablen, die wir oben betrachtet haben, angegeben ist. Andernfalls stelle ich die erforderliche Kaution so ein, dass das in den Einstellungen angegebene Losgröße angewendet werden kann. Es ist leicht zu verstehen, dass sich der Wert dieses Loses bei einer kleineren oder größeren Einzahlung entsprechend der Gleichung ändert:

  • Lot = Input Lot * ( Current Deposit / Deposit For Lot )

Ich denke, jetzt sollte alles klar sein. Wenn wir eine feste Losgröße wollen, stellen wir Null ein, und in anderen Fällen passen wir die Kaution in den Eingabeeinstellungen an die Risiken an. Ich denke, so ist es recht und billig. Falls erforderlich, können Sie den Ansatz der Risikobewertung für die automatische Losgröße ändern, aber ich persönlich mag diese Option, es macht keinen Sinn, sie zu überdenken.

Es ist erwähnenswert, dass die Synchronisierung und insbesondere die Einstellung der MagicNumber des Experten ein Thema ist. Beim Handel mit EAs oder sogar in gemischter Form schenken alle Programmierer, die etwas auf sich halten, dieser besonderen Variable besondere Aufmerksamkeit. Wenn Sie mehrere EAs verwenden, ist es sehr wichtig, dass jeder EA eine eindeutige ID hat. Andernfalls entsteht bei der Arbeit mit schwebenden Aufträgen, Deals oder Positionen ein völliges Durcheinander, und Ihre Strategien funktionieren nicht mehr richtig, in den meisten Fällen sogar überhaupt nicht mehr. Ich hoffe, ich muss nicht erklären, warum. Jedes Mal, wenn ein EA auf dem Chart platziert wird, müssen wir diese IDs konfigurieren und sicherstellen, dass sie sich nicht wiederholen. Schon ein einziger Fehler kann katastrophale Folgen haben. Wenn Sie den Chart mit dem EA versehentlich schließen, müssen Sie ihn außerdem neu konfigurieren. Dadurch wird die Wahrscheinlichkeit eines Fehlers stark erhöht. Darüber hinaus ist sie auch in vielerlei anderer Hinsicht sehr unangenehm. Sie schließen zum Beispiel das Chart und vergessen, welche ID dort verwendet wurde. In diesem Fall müssen Sie in der Handelsgeschichte danach suchen. Ohne die ID funktioniert der neu gestartete EA möglicherweise nicht richtig und es können viele weitere unangenehme Dinge passieren.

Die Verwendung einer Vorlage wie der meinen entbindet uns von dieser Kontrolle und minimiert mögliche Fehler, da wir nur die Start-ID in den EA-Einstellungen festlegen müssen, während die übrigen IDs automatisch mit Hilfe eines Inkrements generiert und den entsprechenden Kopien der EAs zugewiesen werden. Dieser Vorgang wird bei jedem Neustart automatisch durchgeführt. Jedenfalls ist es viel einfacher, sich nur eine Start-ID zu merken als irgendeine zufällige ID auf halber Wegstrecke.


Schreiben einer universellen Vorlage

Es ist an der Zeit, die Vorlage zu implementieren. Ich werde versuchen, überflüssige Elemente wegzulassen, damit jeder, der diese Vorlage braucht, den Rest im Quellcode sehen kann. Hier werde ich nur Dinge zeigen , die direkt mit unseren Ideen zu tun haben. Stopp-Level und andere Parameter werden von den Nutzern festgelegt. Sie können meine Implementierung im Quellcode finden. Definieren wir zunächst unsere Eingabevariablen, die wir auf jeden Fall brauchen werden:

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts
input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots
input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes
input int LastBars=10;//Last Bars Count
input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned
input double RepurchaseLotE=0.01;//Fix Lot For Unsigned
input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix)
input int MagicE=156;//First Magic

Hier sehen Sie ein Beispiel für die nicht leeren Zeichenketten-Variablen, die unsere dynamischen Arrays widerspiegeln, genau wie das Beispiel der gemeinsamen Variablen. Übrigens sieht dieser Code sowohl in MQL4 als auch in MQL5 gleich aus. Ich habe versucht, alles so ähnlich wie möglich zu gestalten.

Lassen Sie uns nun entscheiden, wie wir unsere Zeichenkettendaten erhalten wollen. Dies wird von der entsprechenden Funktion erledigt, aber zunächst werden wir Arrays erstellen, in die unsere Funktion die aus den Zeichenketten gewonnenen Daten einfügen wird:

//+------------------------------------------------------------------+
//|Arrays                                                            |
//+------------------------------------------------------------------+
string S[];// Symbols array
double L[];//Lots array
ENUM_TIMEFRAMES T[];//Timeframes array

Die folgende Funktion füllt diese Arrays aus:

//+------------------------------------------------------------------+
//| Fill arrays                                                      |
//+------------------------------------------------------------------+
void ConstructArrays()
   {
      int SCount=1;
      for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools
         {
         if (SymbolsE[i] == ':')
            {
            SCount++;
            }
         }
      ArrayResize(S,SCount);//set the size of the character array
      ArrayResize(CN,SCount);//set the size of the array to use bars for each character
      int Hc=0;//found instrument index
      for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools
         {
         if (i == 0)//if we just started
            {
            int LastIndex=-1;
            for (int j = i; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i,LastIndex);
               Hc++;
               }
            else
               {
               S[Hc]=SymbolsE;
               Hc++;
               }
            }          
         if (SymbolsE[i] == ':')
            {
            int LastIndex=-1;
            for (int j = i+1; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1));
               Hc++;
               }
            else
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1));
               Hc++;
               }               
            }
         }
      for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars
         {
         CN[i]=LastBars;
         }
      ConstructLots();
      ConstructTimeframe();         
   }

Kurz gesagt, hier wird die Menge der Daten in einer Zeichenkette dank der Trennzeichen berechnet. Auf der Grundlage des ersten Arrays wird die Größe aller anderen Arrays ähnlich wie bei einem Array mit Symbolen festgelegt, woraufhin die Symbole zuerst gefüllt werden, gefolgt von Funktionen wie ConstructLots() und ConstructTimeframe(). Ihre Implementierung ähnelt der Implementierung dieser Funktion mit einigen Unterschieden. Sie können deren Implementierung im Quellcode sehen. Ich habe sie nicht in den Artikel aufgenommen, um denselben Code nicht noch ein zweites Mal zu zeigen.

Nun müssen wir die entsprechenden Klassen für das virtuelle Chart und den damit verknüpften virtuellen Roboter erstellen. Wir beginnen mit der Definition, dass virtuelle Charts und EAs in Arrays gespeichert werden:

//+------------------------------------------------------------------+
//| Charts & experts pointers                                        |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];

Beginnen wir mit der Klasse für die Charts:

//+------------------------------------------------------------------+
//| Chart class                                                      |
//+------------------------------------------------------------------+
class Chart
   {
   public:
   datetime TimeI[];
   double CloseI[];
   double OpenI[];
   double HighI[];
   double LowI[];
   string BasicSymbol;//the base instrument that was extracted from the substring
   double ChartPoint;//point size of the current chart
   double ChartAsk;//Ask
   double ChartBid;//Bid
   datetime tTimeI[];//auxiliary array to control the appearance of a new bar
   static int TCN;//tcn
   string CurrentSymbol;//symbol
   ENUM_TIMEFRAMES Timeframe;//timeframe
   int copied;//how much data is copied
   int lastcopied;//last amount of data copied
   datetime LastCloseTime;//last bar time
   MqlTick LastTick;//last tick fos this instrument
   
   Chart()
      {
      ArrayResize(tTimeI,2);
      }
   
   void ChartTick()//this chart tick
      {
      SymbolInfoTick(CurrentSymbol,LastTick);
      ArraySetAsSeries(tTimeI,false);
      copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI);
      ArraySetAsSeries(tTimeI,true);
      if ( copied == 2 && tTimeI[1] > LastCloseTime )
         {
         ArraySetAsSeries(CloseI,false);                        
         ArraySetAsSeries(OpenI,false);                           
         ArraySetAsSeries(HighI,false);                        
         ArraySetAsSeries(LowI,false);                              
         ArraySetAsSeries(TimeI,false);                                                            
         lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI);
         lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI);   
         lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI);   
         lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI);
         lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI);
         ArraySetAsSeries(CloseI,true);
         ArraySetAsSeries(OpenI,true);
         ArraySetAsSeries(HighI,true);                        
         ArraySetAsSeries(LowI,true);
         ArraySetAsSeries(TimeI,true);         
         LastCloseTime=tTimeI[1];
         }
      ChartBid=LastTick.bid;
      ChartAsk=LastTick.ask;
      ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT);
      }
   };
int Chart::TCN = 0;

Die Klasse hat nur eine Funktion, die die Aktualisierung von Ticks und Balken steuert, sowie die notwendigen Felder zur Identifizierung einiger notwendiger Parameter eines bestimmten Charts. Dort fehlen einige Parameter. Falls gewünscht, können Sie die fehlenden Elemente hinzufügen, indem Sie sie aktualisieren, z. B. wie bei der Aktualisierung von ChartPoint. Die Arrays der Balken sind in MQL4 Stil gemacht. Ich finde es immer noch sehr praktisch, mit vorgegebenen Arrays in MQL4 zu arbeiten. Es ist sehr praktisch, wenn Sie wissen, dass der Null-Balken der aktuelle Balken ist. Jedenfalls ist das nur meine Vision. Es steht Ihnen frei, Ihrer Eigenen zu folgen.

Nun müssen wir die Klasse eines separaten virtuellen EA beschreiben:

//+------------------------------------------------------------------+
//| Bot instance class                                               |
//+------------------------------------------------------------------+
class BotInstance//expert advisor object
   {
   public:
   CPositionInfo  m_position;// trade position object
   CTrade         m_trade;// trading object   
   ///-------------------this robot settings----------------------
   int MagicF;//Magic
   string CurrentSymbol;//Symbol
   double CurrentLot;//Start Lot
   int chartindex;//Chart Index
   ///------------------------------------------------------------   
      
   
   ///constructor
   BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index
      {
      chartindex=chartindex0;
      MagicF=MagicE+index;
      CurrentSymbol=Charts[chartindex].CurrentSymbol;
      CurrentLot=L[index];
      m_trade.SetExpertMagicNumber(MagicF);
      }
   ///
   
   void InstanceTick()//bot tick
      {
      if ( bNewBar() ) Trade();
      }
      
   private:
   datetime Time0;
   bool bNewBar()//new bar
      {
      if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 )
         {
         if (Time0 != 0)
            {
            Time0=Charts[chartindex].TimeI[1];
            return true;
            }
         else
            {
            Time0=Charts[chartindex].TimeI[1];
            return false;
            }
         }
      else return false;
      }
      
   //////************************************Main Logic********************************************************************
   void Trade()//main trade function
      {
      //Close[0]   -->   Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart
      //Open[0]   -->   Charts[chartindex].OpenI[0] -----------------------------------------------------------------------
      //High[0]   -->   Charts[chartindex].HighI[0] -----------------------------------------------------------------------
      //Low[0]   -->   Charts[chartindex].LowI[0] -------------------------------------------------------------------------
      //Time[0]   -->   Charts[chartindex].TimeI[0] -----------------------------------------------------------------------      

      if ( true )
         {
            CloseBuyF();
            //CloseSellF();       
         }
      if ( true )
         {
            BuyF();
            //SellF(); 
         }

      }
      
   double OptimalLot()//optimal lot calculation
      {
      if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE);
      else return CurrentLot;
      }
      
   //here you can add functionality or variables if the trading function turns out to be too complicated
   //////*******************************************************************************************************************
   
   ///trade functions
   int OrdersG()//the number of open positions / orders of this virtual robot
      {
      ulong ticket;
      bool ord;
      int OrdersG=0;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol )
            {
            OrdersG++;
            }
         }
      return OrdersG;
      }
   
   /////////********/////////********//////////***********/////////trade function code block
   void BuyF()//buy market
      {
      double DtA;
      double CorrectedLot;
   
      DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime
      if ( (DtA > 0 || DtA < 0) )
         {
         CorrectedLot=OptimalLot(Charts[chartindex]);
         if ( CorrectedLot > 0.0 )
            {
            //try buy logic
            }            
         }
      }
      
   void SellF()//sell market
      {
      //Same logic
      }

   void CloseSellF()//close sell position
      {
      ulong ticket;
      bool ord;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL 
         && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol )
            {
            //Close Sell logic
            }
         }    
      }
      
   void CloseBuyF()//close buy position
      {
      //same logic 
      }        
      
   bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot
      {
      int MagicT[];
      ArrayResize(MagicT,magiccount);
      for ( int i=0; i<magiccount; i++ )
         {
         MagicT[i]=MagicE+i;
         }
      for ( int i=0; i<ArraySize(MagicT); i++ )
         {
         if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true;
         }
      return false;
      }
   /////////********/////////********//////////***********/////////end trade function code block
   };

Ich habe einen Teil der sich wiederholenden Logik entfernt, um die Menge des Codes zu reduzieren. Dies ist die Klasse, in der der gesamte Algorithmus Ihres EAs implementiert werden soll. Die wichtigsten Funktionen in dieser Klasse:

  • Trade() - Haupt-Handelsfunktion, die in der Ereignisbehandlung der Balken für das entsprechende Chart aufgerufen wird
  • BuyF() - Kauf nach Marktfunktion
  • SellF() - Verkaufen nach Marktfunktion
  • CloseBuyF() - Funktion zum Schließen von Kaufpositionen nach Markt
  • CloseSellF() - Funktion zum Schließen von Verkaufspositionen nach Markt

Dies ist der Mindestsatz an Funktionen zur Demonstration des Handels auf der Basis von Balken. Für diese Demonstration brauchen wir nur eine beliebige Position zu eröffnen und sie beim nächsten Balken zu schließen. Dies ist im Rahmen dieses Artikels ausreichend. Es gibt einige zusätzliche Funktionen in dieser Klasse, die das Verständnis ergänzen sollten:

  • OrdersG() - Zählen der Positionen, die für ein bestimmtes, mit dem Chart verknüpftes Symbol offen sind
  • OptimalLot() - Vorbereitung eine Losgröße, bevor sie an die Handelsfunktion gesendet wird (Auswahl einer festen Losgröße oder Berechnung einer automatisierten Losgröße)
  • bOurMagic() - Überprüfung der Transaktionen aus der Historie auf Übereinstimmung mit der Liste der erlaubten Transaktionen (nur zum Aussortieren einer nutzerdefinierten Historie)

Diese Funktionen können für die Umsetzung der Handelslogik erforderlich sein. Es wäre auch sinnvoll, an die Ereignisbehandlung bei einem neuen Balken zu erinnern:

  • InstanceTick() - Tick-Simulation auf einer separaten EA-Instanz
  • bNewBar() - Überprüfung des Auftretens eines neuen Balkens (wird innerhalb von InstanceTick verwendet)

Wenn die Funktion einen neuen Balken meldet, wird die Handelsfunktion ausgelöst. Dies ist die Funktion, in der die Haupthandelslogik festgelegt werden soll. Die Verbindung mit dem entsprechenden Chart wird über die Variable chartindex hergestellt, die bei der Erstellung der Instanz zugewiesen wird. So weiß jede EA-Instanz, von welchem Chart sie ein den Preis nehmen soll.

Betrachten wir nun den Prozess der Erstellung der virtuellen Charts und EAs selbst. Zunächst werden virtuelle Charts erstellt:

//+------------------------------------------------------------------+
//| Creation of graph objects                                        |
//+------------------------------------------------------------------+
void CreateCharts()
   {
   bool bAlready;
   int num=0;
   string TempSymbols[];
   string Symbols[];
   ConstructArrays();//array preparation
   int tempcnum=CN[0];
   Chart::TCN=tempcnum;//required number of stored bars for all instruments
   for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph
      {
      Charts[j] = new Chart();
      Charts[j].lastcopied=0;
      ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays
      ArrayResize(Charts[j].OpenI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].HighI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].LowI,tempcnum+2);//-----------------------------------
      ArrayResize(Charts[j].TimeI,tempcnum+2);//----------------------------------
      Charts[j].CurrentSymbol = S[j];//symbol
      Charts[j].Timeframe = T[j];//timeframe
      }
   ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots      
   }

Nachdem wir Charts erstellt und die Größe des Arrays mit virtuellen EAs festgelegt haben, müssen wir die Instanzen der EAs selbst erstellen und die Verbindung der virtuellen EAs mit Charts implementieren:

//+------------------------------------------------------------------+
//| create and hang all virtual robots on charts                     |
//+------------------------------------------------------------------+
void CreateInstances()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      for (int j = 0; j < ArraySize(Charts); j++)
         {
         if ( Charts[j].CurrentSymbol == S[i] )
            {
            Bots[i] = new BotInstance(i,j);
            break;
            } 
         }
      }
   }

Die Verbindung erfolgt über den Index "j", der in jeder Instanz der virtuellen EA bei deren Erstellung festgelegt wurde. Die entsprechende Variable ist dort hervorgehoben. Natürlich kann all dies auf viele Arten und viel eleganter durchgeführt werden, aber ich denke, das Wichtigste ist, dass die allgemeine Idee klar ist.

Jetzt muss nur noch gezeigt werden, wie die Ticks auf jedem Chart und dem damit verbundenen EA simuliert werden:

//+------------------------------------------------------------------+
//| All bcharts & all bots tick imitation                            |
//+------------------------------------------------------------------+
void AllChartsTick()
   {
   for (int i = 0; i < ArraySize(Charts); i++)
      {
      Charts[i].ChartTick();
      }
   }

void AllBotsTick()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick();
      }
   }

Das Einzige, was ich anmerken möchte, ist, dass diese Vorlage durch die Überarbeitung meiner komplexeren Vorlage entstanden ist, die für viel ernsthaftere Zwecke gedacht war, sodass es hier und da übertriebene Elemente geben kann. Ich denke, man kann sie leicht entfernen und den Code übersichtlicher gestalten, wenn man will.

Zusätzlich zu der Vorlage gibt es eine einfache Schnittstelle, die meiner Meinung nach auch sehr nützlich sein kann, wenn man zum Beispiel einen Auftrag als Freiberufler oder für andere Zwecke schreibt:


Ich habe in dieser Schnittstelle freien Platz gelassen, er reicht für drei Einträge, falls Sie nicht genug Platz haben. Bei Bedarf können Sie die Struktur leicht erweitern oder komplett ändern. Wenn wir die drei fehlenden Felder in diesem speziellen Beispiel hinzufügen wollen, müssen wir die folgenden Stellen im Code finden:

//+------------------------------------------------------------------+
//| Reserved elements                                                |
//+------------------------------------------------------------------+

   "template-UNSIGNED1",//UNSIGNED1
   "template-UNSIGNED2",//UNSIGNED2
   "template-UNSIGNED3",//UNSIGNED3

   //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1
   //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2
   //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3

   ////////////////////////////
   //TempText="UNSIGNED1 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED2 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED3 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);
   //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText);
   ///////////////////////////
 

Die ersten drei Einträge weisen Namen neuer Elemente auf der Schnittstelle zu, die zweiten drei werden bei der Erstellung der Schnittstelle zu Beginn des EA verwendet, während die letzten drei in der Funktion zur Aktualisierung von Informationen über die Schnittstelle verwendet werden. Nun ist es an der Zeit, die Leistung der beiden Vorlagen zu testen. Für eine visuelle Demonstration ist der Tester-Visualisierer ausreichend. Ich zeige nur die Option für MetaTrader 5, weil der Visualizer viel besser ist. Außerdem wird das Ergebnis der Arbeit alles deutlich machen, was zur Bestätigung der Effizienz erforderlich ist:

Überprüfung mit MetaTrader 5 Tester visueller Modus


Wie Sie sehen können, haben wir alle sieben Charts für die wichtigsten Forex-Paare hochgeladen. Das Visualisierungsprotokoll zeigt, dass der Handel für alle aufgeführten Symbole am Laufen ist. Der Handel wird je nach Bedarf unabhängig durchgeführt. Mit anderen Worten: Die EAs handeln jeweils auf ihrem eigenen Chart und interagieren überhaupt nicht


Schlussfolgerung

In diesem Artikel haben wir die wichtigsten Nuancen der Erstellung von universellen Templates für die Terminals MetaTrader 4 und MetaTrader 5 besprochen, ein einfaches, aber funktionierendes Template erstellt, die wichtigsten Punkte seiner Arbeit analysiert und auch seine Funktionsfähigkeit mit dem MetaTrader 5 Tester Visualizer bestätigt. Ich denke, es ist inzwischen ziemlich offensichtlich, dass eine solche Vorlage gar nicht so kompliziert ist. Im Allgemeinen können Sie verschiedene Implementierungen solcher Vorlagen vornehmen, aber es ist offensichtlich, dass solche Vorlagen völlig unterschiedlich sein können und dennoch anwendbar bleiben. Die Hauptsache ist, dass man die grundlegenden Elemente für den Bau solcher Strukturen versteht. Bei Bedarf können Sie die Vorlagen für den persönlichen Gebrauch überarbeiten.


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/12434

Beigefügte Dateien |
MultiTemplate.mq4 (93.94 KB)
MultiTemplate.mq5 (91.41 KB)
Wie man MetaTrader 5 mit PostgreSQL verbindet Wie man MetaTrader 5 mit PostgreSQL verbindet
Dieser Artikel beschreibt vier Methoden zur Verbindung von MQL5-Code mit einer Postgres-Datenbank und bietet eine Schritt-für-Schritt-Anleitung zum Einrichten einer Entwicklungsumgebung für eine dieser Methoden, eine REST-API, unter Verwendung des Windows Subsystem For Linux (WSL). Eine Demo-Anwendung für die API wird zusammen mit dem entsprechenden MQL5-Code zum Einfügen von Daten und Abfragen der entsprechenden Tabellen sowie einem Demo-Expert Advisor zum Abrufen dieser Daten bereitgestellt.
Implementierung des Janus-Faktors in MQL5 Implementierung des Janus-Faktors in MQL5
Gary Anderson entwickelte eine Marktanalysemethode, die auf einer Theorie beruht, die er Janus-Faktor nannte. Die Theorie beschreibt eine Reihe von Indikatoren, mit denen sich Trends aufzeigen und Marktrisiken bewerten lassen. In diesem Artikel werden wir diese Werkzeuge in mql5 implementieren.
Experimente mit neuronalen Netzen (Teil 5): Normalisierung der Eingaben zur Weitergabe an ein neuronales Netz Experimente mit neuronalen Netzen (Teil 5): Normalisierung der Eingaben zur Weitergabe an ein neuronales Netz
Neuronale Netze sind ein ultimatives Instrument im Werkzeugkasten der Händler. Prüfen wir, ob diese Annahme zutrifft. MetaTrader 5 ist als autarkes Medium für den Einsatz neuronaler Netze im Handel konzipiert. Dazu gibt es eine einfache Erklärung.
Kategorientheorie in MQL5 (Teil 6): Monomorphe Pullbacks und epimorphe Pushouts Kategorientheorie in MQL5 (Teil 6): Monomorphe Pullbacks und epimorphe Pushouts
Die Kategorientheorie ist ein vielfältiger und expandierender Zweig der Mathematik, der erst seit kurzem in der MQL5-Gemeinschaft Beachtung findet. In dieser Artikelserie sollen einige der Konzepte und Axiome erforscht und untersucht werden, mit dem übergeordneten Ziel, eine offene Bibliothek einzurichten, die Einblicke gewährt und hoffentlich auch die Nutzung dieses bemerkenswerten Bereichs für die Strategieentwicklung von Händlern fördert.