English Русский Español 日本語 Português
preview
Multibot im MetaTrader (Teil II): Verbesserte dynamische Vorlage

Multibot im MetaTrader (Teil II): Verbesserte dynamische Vorlage

MetaTrader 5Beispiele | 23 Juli 2024, 10:32
212 0
Evgeniy Ilin
Evgeniy Ilin

Inhalt


Einführung

Im letzten Artikel habe ich mich von einigen der beliebtesten Lösungen auf dem Markt inspirieren lassen, und ich konnte meine eigene Version einer solchen Vorlage (template) erstellen. Bei einigen der Projekte, an denen ich arbeite, stellt sich jedoch heraus, dass diese Lösung nicht optimal ist. Darüber hinaus gibt es noch eine Reihe von Einschränkungen und Unannehmlichkeiten, die mit der Gesamtarchitektur einer solchen Vorlage verbunden sind. Eine solche Vorlage mag für die große Mehrheit der durchschnittlichen Lösungen ausreichen, nicht aber für meine. Der zweite sehr wichtige Punkt ist die Tatsache, dass ich persönlich aus der Sicht eines potenziellen EA-Käufers und Endnutzers ein Maximum an Einfachheit und ein Minimum an Einstellungen anstreben würde. Im Idealfall sollte ein solcher EA keine Re-Optimierung oder andere Manipulationen seitens des Nutzers erfordern. Schließlich zahle ich mein Geld nicht nur für eine funktionierende Lösung, sondern vor allem für eine möglichst freundliche und für jeden verständliche Nutzeroberfläche.


Dynamisches Vorlagenkonzept

Ausgehend von der vorherigen EA sollten wir uns daran erinnern, warum eine solche Vorlage erstellt wurde. Das Hauptziel war die Möglichkeit, einen EA mit unterschiedlichen Einstellungen für jedes Paar „Handelsinstrument - Zeitrahmen“ zu starten. Dies war notwendig, um nicht für jeden Chart einen eigenen EA zu starten. Dies vereinfachte die Verwaltung eines solchen Systems und seine Konfiguration, denn je mehr Kopien desselben EA in einem Terminal vorhanden sind, desto größer ist die Wahrscheinlichkeit, dass ein potenzieller Nutzer einen Fehler begeht. Darüber hinaus könnten Konflikte zwischen solchen EAs entstehen, z. B. aufgrund der Reihenfolge der Magic-Nummern oder aus anderen Gründen, die auf dem menschlichen Faktor beruhen. Lassen Sie uns zunächst die Vor- und Nachteile der ersten Vorlage beschreiben. Dann werde ich dasselbe für die neue Vorlage tun, sodass die Unterschiede und Vorteile des neuen Ansatzes deutlich sichtbar werden.

Statische (Basis-)Vorlage, Vorteile:

  1. Individuelle Einstellung für jedes Handelsinstrument - Zeitrahmen (virtueller EA auf einem virtuellen Chart).
  2. Einfacherer und schnellerer Code (wird schneller ausgeführt).
  3. Der Ansatz ist weit verbreitet (er ist auf dem Markt weit verbreitet und hat seine Tragfähigkeit bewiesen).

    Statische (Basis-)Vorlage, Nachteile:

    1. Komplexität der Einrichtung und hohe Fehlerwahrscheinlichkeit (lange Zeichenketten in den Einstellungen).
    2. Begrenzte Möglichkeiten zur Erweiterung der Funktionalität.
    3. Es ist nicht möglich, virtuelle Roboter ohne manuelle Neukonfiguration hinzuzufügen oder zu entfernen.

      Anhand dieser Daten können wir erkennen, dass die Lösung sehr begrenzt, wenn auch weit verbreitet ist. Es ist möglich, eine viel interessantere Lösung zu finden. Außerdem hat mich die Integration meines EA in meine Lösung, die eine unabhängige dynamische Optimierung außerhalb des MetaTrader-Terminals durchführt, zu einer verbesserten Lösung gebracht. Beginnen wir mit dem Diagramm, das das Wesen der neuen Vorlage zeigt und uns hilft, ihre Vorteile im Vergleich zur vorherigen Version besser zu verstehen.

      neue Vorlagenverwendung

      Achten Sie hier bitte auf das linke untere Element, das CLOSING enthält. Die Vorlage sieht vor, die Einstellungen zu speichern, bei denen die letzte Position auf der entsprechenden virtuellen Karte geöffnet wurde. Dies ist ein besonderer Schutzmechanismus gegen häufige Änderungen der Einstellungen und den Zusammenbruch der Strategie. Wenn wir Positionen nach einer bestimmten Logik eröffnet haben, sind wir daran interessiert, sie nach der gleichen Logik zu schließen, und erst danach können wir die Einstellung auf eine aktuellere ändern. Andernfalls könnten wir in einem absoluten Chaos enden. Ja, das ist nicht immer der Fall, aber es ist besser, solche Momente sofort auf der allgemeinen Logikebene zu blockieren.

      Die neue Vorlage nutzt die Möglichkeiten und die Struktur der Arbeitsverzeichnisse von MetaTrader 4 und MetaTrader 5 zusammen mit dem Dateisystem. Mit anderen Worten: Wir verwalten unsere Vorlage mithilfe von Textdateien, die die erforderlichen Einstellungen für jedes virtuelle Handelspaar enthalten können. Dies gewährleistet ein Höchstmaß an Einfachheit bei der Einrichtung und eine dynamische Umstrukturierung des EA während des Betriebs, ohne dass dieser manuell umkonfiguriert werden muss. Aber das ist nicht alles. Dies bringt auch eine Reihe von Vorteilen mit sich, die ich im Folgenden aufzählen werde.

      Dynamische (neue) Vorlage, Vorteile:

      1. Jede Handelsinstrument-Zeitrahmen wird unabhängig vom Handelsterminal über Textdateien konfiguriert.
      2. Das dynamische Lesen von Einstellungen aus Ordnern und das automatische Öffnen oder Schließen von virtuellen Charts erfolgt zusammen mit den EAs.
      3. Es ist möglich, automatisch mit der Web-API zu synchronisieren (erforderlich über Port 443).
      4. Die Konfiguration erfolgt über den gemeinsamen Ordner des Terminals (*\Common\Files).
      5. Der führende EA wird benötigt, um alle Terminals auf einem Rechner mit der API zu synchronisieren. Es kann auch auf mehreren Rechnern über einen gemeinsamen Ordner im lokalen Netzwerk arbeiten.
      6. Es ist möglich, externe Programme einzubinden, die diese Dateien ebenfalls verwalten können, z. B. erstellen oder löschen sowie Einstellungen ändern.
      7. Es ist möglich, ein Paar kostenpflichtige und kostenlose Vorlagen zu erstellen. Wenn wir die Verbindung zur API weglassen, können wir dies als Demoversion gestalten, die eine kostenpflichtige Version erfordert, um effektiv zu funktionieren.

                Dynamische (neue) Vorlage, Nachteile:

                1. Arbeiten im Dateisystem

                Der Nachteil ist auch hier sehr bedingt, denn die einzige Alternative ist die Web-Integration oder die Kommunikation über andere Methoden (z.B. über RAM), aber in diesem Fall ist unser Trumpf, dass wir keine zusätzlichen Bibliotheken verwenden und unser Code plattformübergreifend wird (geeignet für MQL4 und MQL5 EAs). Dadurch entsprechen solche EAs den Anforderungen des Marktes und können auf Wunsch leicht zum Verkauf angeboten werden. Natürlich müssen Sie einige Dinge hinzufügen und sie Ihren Bedürfnissen entsprechend anpassen, aber das ist mit den hier gegebenen Einblicken sehr einfach zu machen.

                Werfen wir einen Blick auf das Innenleben eines solchen EA und überlegen wir uns, wie wir die oben genannten Dinge visuell und schematisch darstellen können. Ich denke, dass dies anhand des folgenden Diagramms möglich ist:

                vereinfachte Struktur für die Handhabung von Vorlagen

                Das Ganze funktioniert ganz einfach mit Hilfe von zwei unabhängigen Zeitgebern, die jeweils für ihre eigenen Aufgaben zuständig sind. Der erste Timer lädt die Einstellungen von der API hoch, während der zweite die gleichen Einstellungen in den Speicher des EA einliest. Zusätzlich werden zwei Pfeile angezeigt, die die Möglichkeit symbolisieren, diese Einstellungen manuell zu erstellen und zu ändern oder diesen Prozess mithilfe einer Drittanbieteranwendung oder eines anderen Codes zu automatisieren. Theoretisch kann diese Vorlage von jedem anderen Code aus gesteuert werden, z. B. von einem anderen MetaTrader 4 und MetaTrader 5 EA oder Skript.

                Nach dem Lesen der Einstellungen wird festgestellt, ob diese Einstellungen neu sind, ob es neue Einstellungen im Ordner gibt oder ob wir vielleicht einige von ihnen gelöscht haben. Wenn der Vergleich zeigt, dass es keine Änderungen in den Dateien gibt, funktioniert die Vorlage weiterhin, aber wenn sich etwas geändert hat, wäre es viel korrekter, den gesamten Code mit der neuen Konfiguration neu zu starten und weiterzuarbeiten. Wenn wir zum Beispiel einen EA hätten, der nur mit dem Chart arbeitet, auf dem er operiert, müssten wir das alles manuell im Terminal machen. In diesem Fall erfolgt jedoch die gesamte Steuerung vollständig automatisch innerhalb der Vorlage, ohne dass ein manuelles Eingreifen erforderlich ist.


                Grundlegende Einstellungen für dynamische Vorlagen

                Nun können wir uns dem Code zuwenden und seine wichtigsten Punkte analysieren. Zunächst möchte ich Ihnen die Mindestanzahl von Eingaben zeigen, die meiner Meinung nach ausreichen, um den Handel mit der Logik von Balken-zu-Balken zu steuern. Zuvor ist es jedoch notwendig, die wichtigen Paradigmen zu erwähnen, die dieses Muster verwendet, um seinen Code in Zukunft besser zu verstehen.

                • Handelsoperationen und Berechnungen von Eingangssignalen finden statt, wenn ein neuer Balken geöffnet wird (dieser Zeitpunkt wird automatisch auf der Grundlage von Ticks für jedes virtuelle Chart separat berechnet).
                • Es kann jeweils nur eine Position auf einem Chart eröffnet werden (EURUSD M1 und EURUSD M5 werden als unterschiedliche Charts betrachtet).
                • Eine neue Position auf einem separaten Chart kann erst dann eröffnet werden, wenn die vorherige geschlossen ist (ich halte diese Struktur für die einfachste und korrekteste, da alle Arten von Zusatzkäufen und Mittelwertbildung in gewissem Maße bereits Zusatzgewichte sind).

                Unter Berücksichtigung dieser Regeln und Einschränkungen müssen wir nun verstehen, dass wir diesen Code modifizieren können, um mit Averaging oder Martingale zu arbeiten oder um zum Beispiel schwebende Aufträge zu behandeln. Sie können die Struktur bei Bedarf selbst ändern. Schauen wir uns nun die Mindestanzahl von Parametern an, die es uns ermöglichen, den Handel zu steuern.

                //+------------------------------------------------------------------+
                //|                         main variables                           |
                //+------------------------------------------------------------------+
                input bool bToLowerE=false;//To Lower Symbol
                input string SymbolPrefixE="";//Symbol Prefix
                input string SymbolPostfixE="";//Symbol Postfix
                
                input string SubfolderE="folder1";//Subfolder In Files Folder
                input bool bCommonReadE = true;//Read From Common Directory
                
                input bool bWebSyncE = false;//Sync with API
                input string SignalDirectoryE = "folder1";//Signal Name(Folder)
                input string ApiDomen = "https://yourdomen.us";//API DOMEN (add in terminal settings!)
                
                input bool bInitLotControl=true;//Auto Lot
                input double DeltaBarPercent=1.5;//Middle % of Delta Equity Per M1 Bar (For ONE! Bot)
                input double DepositDeltaEquityE=100.0;//Deposit For ONE! Bot
                
                input bool bParallelTradingE=true;//Parallel Trading
                
                input int SLE=0;//Stop Loss Points
                input int TPE=0;//Take Profit Points

                In diesem Beispiel habe ich die Variablen auf der Grundlage ähnlicher Merkmale in verschiedene Blöcke unterteilt. Wie wir sehen können, gibt es nicht viele Variablen. Der erste Block ist so konzipiert, dass er verschiedene Arten der Benennung von Instrumenten für verschiedene Makler handhaben kann. Wenn Ihr Broker z.B. „EURUSD“ hat, dann sollten alle Variablen aus diesem Block so sein, wie sie jetzt sind, aber auch andere Optionen sind z.B. möglich:

                • eurusd
                • EURUSDt
                • tEURUSD
                • _EURUSD
                • eurusd_

                Und so weiter. Ich werde nicht weiter darauf eingehen, ich denke, Sie können es selbst herausfinden. Natürlich sollte eine gute Vorlage die meisten dieser Varianten abdecken.

                Der zweite Unterblock sagt uns, aus welchem Ordner wir die Dateien lesen. Außerdem laden wir Daten aus der API in denselben Ordner. Wenn wir den richtigen Ordnernamen angeben, erstellt MetaTrader das entsprechende Unterverzeichnis und arbeitet mit diesem. Wenn wir nichts angeben, werden alle Dateien im Stammverzeichnis gespeichert. Eine interessante Funktion bietet der gemeinsame Terminal-Ordner: Wenn wir diese Option aktivieren, können wir nicht nur mehrere EAs kombinieren, die in MetaTrader 5 laufen, sondern auch alle Terminals, unabhängig davon, ob sie auf MetaTrader 4 oder MetaTrader 5 laufen. Auf diese Weise können beide Vorlagen identisch mit denselben Einstellungen arbeiten, was eine maximale Integration und Synchronisation gewährleistet.

                Wie Sie vielleicht schon erraten haben, aktiviert/deaktiviert der dritte Subblock den Synchronisationstimer mit Ihrer API, falls vorhanden. Es ist wichtig zu beachten, dass die Kommunikation mit der API nur über die Funktion WebRequest erfolgt, die sowohl in MQL4 als auch in MQL5 funktioniert. Die einzige Einschränkung ist, dass Ihre API auf Port 443 laufen sollte. In MQL5 wurde diese Methode jedoch erweitert, und es besteht die Möglichkeit, eine Verbindung über einen anderen Port herzustellen. In meiner API habe ich diese Idee jedoch aufgegeben, um sicherzustellen, dass meine Vorlagen und darauf aufbauenden Lösungen plattformübergreifend sind.

                Ich habe die API so aufgebaut, dass der Signalname auch das Dateiverzeichnis ist. Auf diese Weise kann ich eine Verbindung zu verschiedenen Signalen herstellen, indem ich ihre Namen kenne. Wir können die Verbindung zum alten Signal jederzeit unterbrechen und uns mit dem neuen Signal verbinden. Natürlich können Sie die Dateien selbst auf diese Weise nicht herunterladen, aber ich habe es ein wenig anders gemacht. Ich erhalte JSON mit dem Inhalt der Datei und erstelle es dann selbst aus dem Code der Vorlage selbst. Dies erfordert zusätzliche Methoden, um die Daten aus Ihrer JSON-Zeichenfolge zu extrahieren, aber ich persönlich hatte damit keine Probleme, da MQL5 uns dies problemlos ermöglicht. Bevor wir unsere API verwenden können, benötigen wir natürlich eine beliebige Domain, die wir in den MetaTrader-Einstellungen zur Liste der zulässigen Verbindungen hinzufügen.

                Der vierte Block ist sehr wichtig, denn er kontrolliert die Risiken und das Eintrittsvolumen. Natürlich können wir die Volumina für jede Handelsinstrument-Zeitrahmen separat mit denselben Textdateien einstellen, aber ich habe mich entschieden, die Volatilitätsdaten des verwendeten Handelspaares für die automatischen Einstellungen zu verwenden. Dieser Ansatz beinhaltet auch eine automatische proportionale Erhöhung des Volumens zusammen mit dem Wachstum unseres Guthabens - dies wird als Autolos bezeichnet.

                Der fünfte Block besteht aus nur einer Variablen. Standardmäßig handeln alle virtuellen EAs, die innerhalb der Vorlage arbeiten, unabhängig voneinander und achten nicht auf Positionen, die von EAs eröffnet wurden, die sich auf anderen virtuellen Charts befinden. Diese Variable stellt sicher, dass nur eine Position in einem EA eröffnet werden kann, andere warten, bis dieser geschlossen wird, und können erst dann ihre eigene eröffnen. In einigen Fällen kann dieser Handelsstil äußerst nützlich sein.

                Der letzte (fünfte) Block enthält nur Haltestelleneinstellungen. Wenn wir sie auf Null setzen, dann handeln wir ohne sie. Im sechsten Block, der noch nicht vorhanden ist, können Sie bei Bedarf Ihre Parameter hinzufügen.


                Regeln für die Benennung von Dateien mit Einstellungen und angewandten Direktiven

                Ich möchte mit den wichtigsten Methoden beginnen, die Einstellungen aus unseren Dateien hochladen, und wie sie gelesen werden, sowie damit, wie diese Methoden genau die Dateien finden, die wir brauchen. Um sicherzustellen, dass solche Dateien korrekt gelesen werden, müssen wir zunächst einfache und verständliche Regeln für die Benennung der Dateien einführen. Zunächst müssen wir diese Richtlinie in unserer Vorlage finden:

                //+------------------------------------------------------------------+
                //|                     your bot unique name                         |
                //+------------------------------------------------------------------+
                #define BotNick "dynamictemplate" //bot

                Diese Direktive setzt mit „BotNick“ einen „Spitznamen“ für den Bot. Unter diesem Spitznamen läuft absolut alles ab:

                1. das Benennen neu erstellter Dateien,
                2. das Lesen von Dateien,
                3. das Erstellen von globalen Terminalvariablen,
                4. das Lesen von globalen Terminalvariablen,
                5. und Sonstiges.

                Unsere Vorlage akzeptiert nur die Dateien „**** dynamictemplate.txt“ als Einstellungen. Mit anderen Worten, wir haben die erste Regel für die Benennung von Dateien mit Einstellungen definiert - der Dateiname sollte vor der Erweiterung immer auf „dynamictemplate“ enden. Es steht Ihnen frei, diesen Namen in einen beliebigen Namen zu ändern. Wenn Sie also zwei EAs mit unterschiedlichen Aliasen erstellen, werden diese die Einstellungen ihres „Geschwisters“ ignorieren und nur mit ihren eigenen Dateien arbeiten.

                In der Nähe befindet sich eine weitere ähnliche Richtlinie, die das gleiche Ziel verfolgt:

                //+------------------------------------------------------------------+
                //|               unique shift for difference of EA                  |
                //+------------------------------------------------------------------+
                #define MagicHelp 0 //bot magic shift

                Der einzige Unterschied besteht darin, dass diese Direktive die Unterscheidung zwischen verschiedenen EAs auf der Ebene der Auftragsmagie sicherstellt, genau wie bei Dateien, sodass zwei oder mehr EAs nicht die Aufträge anderer EAs schließen, wenn Sie sich plötzlich entscheiden, mehrere solcher EAs innerhalb eines Handelskontos zu verwenden. Auf die gleiche Weise sollten wir bei der Erstellung des nächsten EA auch diese Nummer zusammen mit dem Alias ändern. Es ist besser, diese Zahl jedes Mal um eins zu erhöhen, wenn wir einen neuen EA auf der Grundlage dieser Vorlage erstellen.

                Als Nächstes gehen wir zu dem Teil des Dateinamens über, der unserem EA mitteilt, für welchen Chart er bestimmt ist. Aber sehen wir uns zunächst einmal dieses Feld an:

                //+------------------------------------------------------------------+
                //|                        applied symbols                           |
                //+------------------------------------------------------------------+
                string III[] = { 
                   "EURUSD",
                   "GBPUSD",
                   "USDJPY",
                   "USDCHF",
                   "USDCAD",
                   "AUDUSD",
                   "NZDUSD",
                   "EURGBP",
                   "EURJPY",
                   "EURCHF",
                   "EURCAD",
                   "EURAUD",
                   "EURNZD",
                   "GBPJPY",
                   "GBPCHF",
                   "GBPCAD",
                   "GBPAUD",
                   "GBPNZD",
                   "CHFJPY",
                   "CADJPY",
                   "AUDJPY",
                   "NZDJPY",
                   "CADCHF",
                   "AUDCHF",
                   "NZDCHF",
                   "AUDCAD",
                   "NZDCAD",
                   "AUDNZD",
                   "USDPLN",
                   "EURPLN",
                   "USDMXN",
                   "USDZAR",
                   "USDCNH",
                   "XAUUSD",
                   "XAGUSD",
                   "XAUEUR"
                };

                Dieses Array hat eine sehr wichtige Dateifilterfunktion. Mit anderen Worten, die Vorlage lädt nur die Charts und die entsprechenden Einstellungen, die in dieser Liste von Symbolen enthalten sind. Hier sind wir gezwungen, ein paar weitere Regeln festzulegen, die sowohl für die Benennung von Dateien mit Einstellungen als auch für die Anpassung der angegebenen Liste gleichermaßen funktionieren.

                1. Alle Instrumentennamen werden in Großbuchstaben umgewandelt.
                2. Alle Instrumenten-Postfixe und -Präfixe werden entfernt, sodass nur die eigentlichen Instrumentennamen übrig bleiben.

                Schauen wir uns jetzt das Beispiel an. Angenommen, der Name des „EURUSD“-Symbols hat bei Ihrem Broker folgendes Aussehen: „eurusd_“. Das bedeutet, dass Sie die Einstellungsdatei immer noch mit „EURUSD“ benennen, aber zusätzlich in den Einstellungen Folgendes tun:

                //+------------------------------------------------------------------+
                //|                        symbol correction                         |
                //+------------------------------------------------------------------+
                input bool bToLowerE=true;//To Lower Symbol
                input string SymbolPrefixE="";//Symbol Prefix
                input string SymbolPostfixE="_";//Symbol Postfix

                Mit anderen Worten, Sie signalisieren der Vorlage, dass unsere Namen innerhalb des EA in ihre ursprüngliche Form umgewandelt werden, indem die Namen in Kleinbuchstaben umgewandelt werden und das entsprechende Postfix hinzugefügt wird. Dies wird nicht oft vorkommen, da die Broker in der Regel das klassische Schema der Großbuchstaben beibehalten und keine Präfixe oder Postfixe verwenden, weil es einfach keinen Sinn ergibt. Aber auch diese Option ist nicht ausgeschlossen.

                Bisher haben wir nur herausgefunden, wie wir die Datei benennen müssen, damit die Vorlage versteht, dass es sich um die erforderliche „BotNick“-Einstellung handelt, und wie wir sie mit dem richtigen Handelsinstrument abgleichen können. Wir müssen die Einstellung noch mit einem bestimmten Chart-Zeitrahmen abgleichen. Zu diesem Zweck habe ich die folgende Regel eingeführt:

                • Wir setzen nach dem Dateinamen ein Leerzeichen und schreiben den Gegenwert dieser Zeit in Minuten.
                • Der zulässige Bereich für die Chart-Zeitrahmen ist M1... H4.

                Ich denke, es ist wichtig, die Zeitrahmen, die in diesem Bereich liegen, genau aufzulisten. Für mich war es sehr wichtig, dass alle diese Zeitrahmen sowohl im MetaTrader 4 als auch im MetaTrader 5 vertreten sind, dies war der Hauptgrund für die Auswahl der Zeitrahmen. Ein weiterer wichtiger Grund war, dass sehr hohe Zeitrahmen oberhalb von „H4“ in der Regel nicht für den automatischen Handel mit Balken verwendet werden. Auf jeden Fall habe ich keine solchen Beispiele gesehen, sodass die Wahl auf die folgenden Zeiträume fiel:

                • M1 - 1 Minute
                • M5 - 5 Minuten
                • M15 - 15 Minuten
                • M30 - 30 Minuten
                • H1 - 60 Minuten
                • H4 - 240 Minuten

                Diese Zeitrahmen sind völlig ausreichend. Außerdem ist es sehr einfach, für jeden dieser Zeitrahmen das Äquivalent in Minuten zu berechnen, und diese Zahlen sind sehr leicht zu merken. Jetzt können wir die allgemeine Struktur des Dateinamens aufzeigen, nach der Sie die Einstellungen manuell oder automatisch mit dem Code von Drittanbietern oder Ihrer API erstellen werden. Werfen wir zunächst einen Blick auf die allgemeinen Grundzüge:

                • "INSTRUMENT" + " " + "ZEITRAHMEN IN MINUTEN" + " " + BotNick + ".txt"

                Schauen wir uns außerdem die Struktur an, die die Vorlage verwendet, um die Dateien für die Abschlusspositionen zu duplizieren:

                • "CLOSING" + " " + "INSTRUMENT" + " " + "ZEITRAHMEN IN MINUTEN" + " " + BotNick + ".txt"

                Wie Sie sehen können, unterscheiden sich diese beiden Dateien nur durch den Zusatz „CLOSING“ und das anschließende Leerzeichen. Alle Signalzeilen im Namen werden natürlich durch ein einzelnes Leerzeichen getrennt, damit der Template-Interpreter diese Markierungen erkennen und aus dem Dateinamen extrahieren kann. Ob eine Einstellung zu einem bestimmten Chart gehört, wird also nur durch ihren Namen bestimmt. Sehen wir uns nun einige Beispiele für Einstellungen an, die dieser Regel entsprechen:

                • EURUSD 15 dynamictemplate.txt
                • GBPUSD 240 dynamischeVorlage.txt
                • EURCHF 60 dynamischeVorlage.txt
                • CLOSING GBPUSD 240 dynamictemplate.txt

                Für ein Beispiel ist dies natürlich ausreichend. Achten Sie bitte auf den Nachnamen. Diese Datei wird nach dem Muster „GBPUSD 240 dynamictemplate.txt“ kopiert und in den Ordner des Terminals gelegt, in dem der EA die Kopie erstellt hat. Dies geschieht, um zu verhindern, dass verschiedene, aber identische EAs in mehreren Terminals mehrfach in dieselben Dateien schreiben. Wenn wir die Option zum Lesen aus dem freigegebenen Terminalordner deaktivieren, werden auch reguläre Dateien dorthin geschrieben. Dies kann notwendig sein, wenn wir jeden spezifischen EA im entsprechenden Terminal mit einer eigenen, unabhängigen Anzahl von Einstellungen konfigurieren müssen. Ich werde mehrere Dateien als Beispiel neben den Vorlagen stehen lassen, damit es an einem konkreten Beispiel deutlicher wird und Sie damit experimentieren können, indem Sie sie in verschiedene Ordner verschieben. Damit ist die Betrachtung der allgemeinen Aspekte der Nutzung der Einstellungen abgeschlossen.


                Methoden zum Lesen und Erstellen von Dateien

                Um die Funktionalität der Vorlagen vollständig zu beherrschen, ist es ratsam zu verstehen, wie das Lesen und Schreiben von Dateien funktioniert. Dies wird uns letztendlich ermöglichen, sie nicht nur als Marker für Instrumentenperioden zu verwenden, an die wir virtuelle Roboter anhängen wollen, sondern auch jeden von ihnen individuell anzupassen, falls gewünscht. Zu diesem Zweck können wir die folgende Methode in Betracht ziehen.

                //+------------------------------------------------------------------+
                //|                 used for configuration settings                  |
                //+------------------------------------------------------------------+
                bool QuantityConfiguration()
                {
                    FilesGrab(); // Determine the names of valid files
                    
                    // Check if there are changes in the configuration settings (either add or delete)
                    if (bNewConfiguration())
                    {
                        return true;
                    }     
                    return false;
                }

                Diese Methode ermittelt, ob die Menge der Dateien in unserem Arbeitsverzeichnis aktualisiert wurde. Wenn ja, dann nutzen wir diese Methode als Signal, um alle Charts und EAs neu zu starten, um neue Instrumenten-Perioden hinzuzufügen oder unnötige zu entfernen. Werfen wir nun einen Blick auf die Methode FilesGrab.

                //+------------------------------------------------------------------+
                //|   reads all files and forms a list of instruments and periods    |
                //+------------------------------------------------------------------+
                void FilesGrab()
                   {
                   string file;
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; // SubfolderE is the path to the specific subfolder
                   // Returns the handle of the first found file with the specified characteristics, based on whether CommonReadE is True or False
                   long total_files = !bCommonReadE? FileFindFirst(tempsubfolder+"*"+BotNick+".txt", file) :FileFindFirst(tempsubfolder+"*"+BotNick+".txt", file,FILE_COMMON);
                   if(total_files > 0)
                      {
                         ArrayResize(SettingsFileNames,0); // Clear the array from previous values if there are files to be read
                         do
                         {
                            int second_space = StringFind(file, " ", StringFind(file, " ") + 1); // Searches for the index of the second space in the file's name
                            if(second_space > 0) 
                            {
                                string filename = StringSubstr(file, 0, second_space); // Extracts the string/characters from the filename up to the second space
                                ArrayResize(SettingsFileNames, ArraySize(SettingsFileNames) + 1); // Increases the size of the array by one
                                SettingsFileNames[ArraySize(SettingsFileNames) - 1] = filename; // Adds the new filename into the existing array
                            }
                         }
                         while(FileFindNext(total_files, file)); // Repeat for all the files        
                         FileFindClose(total_files); // Close the file handle to free resources
                      }
                   }  

                Diese Methode führt eine vorläufige Sammlung der Namen der Dateien durch, die sich auf unseren EA beziehen, z. B. „EURUSD 60“. Mit anderen Worten, es bleibt nur der Teil des Namens übrig, der später in ein Instrumenten-Zeitrahmen-Paar zerlegt werden soll. Das Einlesen dieser Dateien erfolgt jedoch nicht hier, sondern in jedem virtuellen EA separat. Dazu müssen wir jedoch zunächst die Zeichenkette selbst in ein Symbol und einen Punkt zerlegen. Dem gehen mehrere Punkte voraus. Eine davon lautet wie folgt.

                //+------------------------------------------------------------------+
                //|                        symbol validator                          |
                //+------------------------------------------------------------------+
                bool AdaptDynamicArrays()
                {
                    bool RR=QuantityConfiguration();
                    // If a new configuration of files is detected (new files, changed order, etc.)
                    if (RR)
                    {
                        // Read the settings (returns the count)
                        int Readed = ArraySize(SettingsFileNames); 
                        int Valid =0;
                
                        // Only valid symbol name needs to be populated (filenames are taken from already prepared array)
                        ArrayResize(S, Readed);
                     
                        for ( int j = 0; j < Readed; j++ )
                        {
                            for ( int i = 0; i < ArraySize(III); i++ )
                            {
                                // check the symbol to valid
                                if ( III[i] == BasicNameToSymbol(SettingsFileNames[j]) )
                                {
                                    S[Valid++]=SettingsFileNames[j];
                                    break; // stop the loop
                                }
                            }
                        } 
                        //resize S with the actual valid quantity
                        ArrayResize(S, Valid);
                        return true;
                    }
                    return false;
                }

                Diese Methode ist sehr wichtig, um die Einstellungen (Charts) zu verwerfen, die nicht in unserer Liste der zulässigen Instrumente enthalten sind. Ziel ist es, alle Charts, die durch die Liste gefiltert wurden, in das Array „S“ aufzunehmen, um es für die weitere Verwendung im Code zur Erstellung virtueller Chartobjekte vorzubereiten.

                Ein wichtiger Punkt ist auch die Reservierung von Einstellungen, die beim Lesen der Grundeinstellungen ständig erfolgt. Wenn sich eine Grundeinstellungsposition geöffnet hat, dann stoppen wir die periodische Einstellungsreservierung. Die Sicherungsdatei mit dem Präfix „CLOSING“ wird immer im aktuellen Terminalverzeichnis gespeichert.

                //+------------------------------------------------------------------+
                //|         сopy settings from the main file to a CLOSING file       |
                //+------------------------------------------------------------------+
                void SaveCloseSettings()
                   {
                   string FileNameString=Charts[chartindex].BasicName;
                   bool bCopied;
                   string filenametemp;
                   string filename="";
                   long handlestart;   
                   
                   //Checking if SubfolderE doesn't exist, if yes, assign tempsubfolder to be an empty string
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; 
                
                   //Find the first file in the subfolder according to bCommonReadE and assign the result to handlestart 
                   if (bCommonReadE) handlestart=FileFindFirst(tempsubfolder+"*",filenametemp,FILE_COMMON);
                   else handlestart=FileFindFirst(tempsubfolder+"*",filenametemp);
                   
                   //Check if the start of our found file name matches FileNameString 
                   if ( StringSubstr(filenametemp,0,StringLen(FileNameString)) == FileNameString )
                      {
                      //if yes, complete the file's path 
                      filename=tempsubfolder+filenametemp;
                      }
                     //keep finding the next file while conditions are aligned  
                   while ( FileFindNext(handlestart,filenametemp) )
                      {
                      //if found file's name matches FileNameString then add found file's name to the path
                      if ( StringSubstr(filenametemp,0,StringLen(FileNameString)) == FileNameString )
                         {
                         filename=tempsubfolder+filenametemp;
                         break;
                         }
                      }   
                   //if handlestart is not INVALID_HANDLE then close the handle to release the resources after the search
                   if (handlestart != INVALID_HANDLE) FileFindClose(handlestart); 
                
                   //Perform file copy operation and notice if it was successful 
                   if ( bCommonReadE ) bCopied=FileCopy(filename,FILE_COMMON,tempsubfolder+"CLOSING "+FileNameString+".txt",FILE_REWRITE|FILE_TXT|FILE_ANSI);
                   else bCopied=FileCopy(filename,0,tempsubfolder+"CLOSING "+FileNameString+".txt",FILE_REWRITE|FILE_TXT|FILE_ANSI);
                   }

                An dieser Stelle sei klargestellt, dass die Backup-Einstellung so funktioniert, dass z.B. bei einem Neustart oder einem erneuten Einlesen der Vorlage die Daten daraus gelesen werden. Dies ist nur bei einer offenen Position möglich, die dieser Einstellung entspricht. Wenn eine bestimmte Instanz eines virtuellen EA keine offenen Positionen hat, dann wird die Vorlage mit der allgemeinen Einstellung synchronisiert.


                Erstellen von virtuellen Charts und EAs

                In der nächsten Methode rufen wir dann alle Daten von dort ab, während wir gleichzeitig die erforderlichen virtuellen Charts erstellen.

                //+------------------------------------------------------------------+
                //|                      creates chart objects                       |
                //+------------------------------------------------------------------+
                void CreateCharts()
                   {
                   bool bAlready;
                   int num=0;
                   string TempSymbols[];
                   string Symbols[];
                   ArrayResize(TempSymbols,ArraySize(S)); // Resize TempSymbols array to the size of S array
                   for (int i = 0; i < ArraySize(S); i++) // Populate TempSymbols array with empty strings
                      {
                      TempSymbols[i]="";
                      }
                   for (int i = 0; i < ArraySize(S); i++) // Count the required number of unique trading instruments
                      {
                      bAlready=false;
                      for (int j = 0; j < ArraySize(TempSymbols); j++)
                         {
                         if ( S[i] == TempSymbols[j] ) // If any symbol is already present in TempSymbols from S, then it's not unique
                            {
                            bAlready=true;
                            break;
                            }
                         }
                      if ( !bAlready ) // If the symbol is not found in TempSymbols i.e., it is unique, add it to TempSymbols
                         {
                         for (int j = 0; j < ArraySize(TempSymbols); j++)
                            {
                            if ( TempSymbols[j] == "" )
                               {
                               TempSymbols[j] = S[i];
                               break;
                               }
                            }
                         num++; // Increments num if a unique element is added          
                         }
                      }      
                   ArrayResize(Symbols,num); // Resize the Symbols array to the size of the num
                
                   for (int j = 0; j < ArraySize(Symbols); j++) // Now that the Symbols array has the appropriate size, populate it
                      {
                      Symbols[j]=TempSymbols[j];
                      } 
                   ArrayResize(Charts,num); // Resize Charts array to the size of num
                
                   int tempcnum=0;
                   tempcnum=1000; // Sets all charts to a default of 1000 bars
                   Chart::TCN=tempcnum; 
                   for (int j = 0; j < ArraySize(Charts); j++)
                      {
                      Charts[j] = new Chart();
                      Charts[j].lastcopied=0; // Initializes the array position where the last copy of the chart was stored
                      Charts[j].BasicName=Symbols[j]; 
                      ArrayResize(Charts[j].CloseI,tempcnum+2); // Resizes the CloseI array to store closing price of each bar
                      ArrayResize(Charts[j].OpenI,tempcnum+2); // Resizes the OpenI array for opening prices
                      ArrayResize(Charts[j].HighI,tempcnum+2); // HighI array for high price points in each bar
                      ArrayResize(Charts[j].LowI,tempcnum+2); // LowI array for low price points of each bar
                      ArrayResize(Charts[j].TimeI,tempcnum+2); // TimeI array is resized to store time of each bar
                      string vv = BasicNameToSymbol(Charts[j].BasicName); 
                      StringToLower(vv);
                      // Append prefix and postfix to the basic symbol name to get the specific symbol of the financial instrument 
                      Charts[j].CurrentSymbol = SymbolPrefixE +  (!bToLowerE ? BasicNameToSymbol(Charts[j].BasicName) : vv) + SymbolPostfixE;
                      Charts[j].Timeframe = BasicNameToTimeframe(Charts[j].BasicName); // Extracts the timeframe from the basic name string
                      }
                   ArrayResize(Bots,ArraySize(S)); // Resize Bots array to the size of S array
                   }

                Diese Methode konzentriert sich auf die Erstellung einer Sammlung von sich nicht wiederholenden Instrumentenperioden, auf deren Grundlage die Objekte der entsprechenden Charts erstellt werden. Bitte beachten Sie außerdem, dass die Größe des Balkenarrays für jedes Chart auf knapp über 1000 Balken eingestellt ist. Ich glaube, dass dies mehr als genug ist, um die meisten Strategien umzusetzen. Wenn etwas passiert, können wir diese Menge in die gewünschte Menge ändern. Nun wollen wir das Material mit einer Methode konsolidieren, die virtuelle EA-Objekte erzeugt.

                //+------------------------------------------------------------------+
                //|              attaching all virtual robots to charts              |
                //+------------------------------------------------------------------+
                void CreateInstances()
                   {
                   // iterating over the S array
                   for (int i = 0; i < ArraySize(S); i++)
                      {
                      // iterating over the Charts array
                      for (int j = 0; j < ArraySize(Charts); j++)
                         {
                         // checking if the BasicName of current Chart matches with the current item in S array
                         if ( Charts[j].BasicName == S[i] )
                            {
                            // creating a new Bot instance with indices i, j and assigning it to respective position in Bots array
                            Bots[i] = new BotInstance(i,j);
                            break;
                            } 
                         }
                      }
                   }

                Hier werden virtuelle EAs erstellt und an die entsprechenden Charts angehängt, wobei die ID dieses „j“-Charts innerhalb des EAs gespeichert wird, damit wir in Zukunft wissen, von welchem Chart der virtuelle EA Daten beziehen soll. Was die interne Struktur dieser beiden Klassen angeht, so habe ich dies bereits in einem früheren Artikel erwähnt. In vielerlei Hinsicht ähnelt der neue Kodex dem alten, abgesehen von einigen unbedeutenden Änderungen.


                Dynamisches Lesen und Rekonfigurieren von virtuellen EAs

                Es liegt auf der Hand, dass dieser Abschnitt für uns äußerst wichtig ist, da er gut die Hälfte des gesamten Konzepts der neuen Vorlage ausmacht. Schließlich ist die Erstellung virtueller Charts und EAs nur die halbe Arbeit. Es scheint, als hätten wir es geschafft, virtuelle Charts auf einem minimalen Niveau neu zu erstellen, aber das ist nicht genug. Es ist ratsam, herauszufinden, wie man neue Einstellungen „on the fly“ übernehmen und den EA sofort neu konfigurieren kann, ohne das Handelsterminal zu beeinträchtigen. Um dieses Problem zu lösen, wird eine einfache Zeitschaltuhr verwendet, wie in den Diagrammen am Anfang des Artikels dargestellt.

                //+------------------------------------------------------------------+
                //|             we will read the settings every 5 minutes +          |
                //+------------------------------------------------------------------+
                bool bReadTimer()
                   {
                   if (  TimeCurrent() - LastTime > 5*60 + int((double(MathRand())/32767.0) * 60) )
                      {
                      LastTime=TimeCurrent();
                      int orders=OrdersG();
                      bool bReaded=false;
                      if (orders == 0)  bReaded = ReadSettings(false,Charts[chartindex].BasicName);//reading a regular file
                      else bReaded = ReadSettings(true,Charts[chartindex].BasicName);//reading file to close position
                      if (orders == 0 && bReaded) SaveCloseSettings();//save settings for closing position
                      return bReaded;
                      }
                   return false;
                   }

                Wie Sie sehen können, wird der Timer alle „5“ Minuten ausgelöst, was bedeutet, dass neue Dateien nicht sofort abgeholt werden. Meiner Meinung nach reicht dieses Timing jedoch aus, um die Dynamik zu gewährleisten. Wenn Ihnen das nicht genügt, können Sie die Zeit bis auf eine Sekunde reduzieren. Das Einzige, was Sie wissen sollten, ist, dass die häufige Verwendung von Dateioperationen nicht erwünscht ist und nach Möglichkeit vermieden werden sollte. Achten Sie in diesem Code auf die Methode „ReadSettings“. Sie liest die erforderliche Datei (für jeden virtuellen EA separat) und konfiguriert den EA nach dem Lesen neu. Die Methode ist so konzipiert, dass sie entweder die allgemeinen Einstellungen lesen kann (wenn es keine offenen Positionen in dem ausgewählten virtuellen EA gibt) oder die Aktualisierung der Einstellungen unterbricht und wartet, bis die Position gemäß den Einstellungen, mit denen diese Position erstellt wurde, geschlossen wird.

                //+------------------------------------------------------------------+
                //|                        reading settings                          |
                //+------------------------------------------------------------------+
                bool BotInstance::ReadSettings(bool bClosingFile,string Path)
                   {
                   string FileNameString=Path;
                   int Handle0x;
                   string filenametemp;
                   string filename="";
                   long handlestart;
                            
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; 
                        
                   if (!bClosingFile)//reading a regular file
                      {
                      if (!bCommonReadE)
                         {
                         handlestart=FileFindFirst(tempsubfolder+"*",filenametemp);         
                         int SearchStart=0;
                         
                         if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                            {
                            filename=tempsubfolder+filenametemp;
                            }
                         if (filename != filenametemp || filename == "")
                            {
                            while ( FileFindNext(handlestart,filenametemp) )
                               {
                               if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                                  {
                                  filename=tempsubfolder+filenametemp;
                                  break;
                                  }
                               }         
                            }
                         if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search
                         
                         if (filename != "")
                            {
                            Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI);
                            
                            if ( Handle0x != INVALID_HANDLE )//if the file exists
                               {
                               FileSeek(Handle0x,0,SEEK_SET);
                               ulong size = FileSize(Handle0x);
                               string str = "";
                               for(ulong i = 0; i < size; i++)
                                  {
                                     str += FileReadString(Handle0x);
                                  }
                               if (str != "" && str != PrevReaded)
                                  {
                                  FileSeek(Handle0x,0,SEEK_SET);
                                  //read the required parameters
                                  ReadFileStrings(Handle0x);
                                  //                     
                                  FileClose(Handle0x);
                                  LastRead = TimeCurrent();
                                  RestartParams();
                                  }
                               else
                                  {
                                  FileClose(Handle0x);
                                  }
                               return true;
                               }
                            else
                               {
                               return false;
                               }         
                            }         
                         }
                      else
                         {
                         handlestart=FileFindFirst(tempsubfolder+"*",filenametemp,FILE_COMMON);   
                         int SearchStart=0;
                         
                         if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                            {
                            filename=tempsubfolder+filenametemp;
                            }
                         if (filename != filenametemp || filename == "")
                            {
                            while ( FileFindNext(handlestart,filenametemp) )
                               {
                               if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                                  {
                                  filename=tempsubfolder+filenametemp;
                                  break;
                                  }
                               }         
                            }
                         if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search
                         
                         if (filename != "")
                            {
                            Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI|FILE_COMMON);
                            
                            if ( Handle0x != INVALID_HANDLE )//if the file exists
                               {
                               FileSeek(Handle0x,0,SEEK_SET);
                               ulong size = FileSize(Handle0x);
                               string str = "";
                               for(ulong i = 0; i < size; i++)
                                  {
                                     str += FileReadString(Handle0x);
                                  }
                               if (str != "" && str != PrevReaded)
                                  {
                                  FileSeek(Handle0x,0,SEEK_SET);
                                  //read the required parameters
                                  ReadFileStrings(Handle0x);
                                  //      
                                  FileClose(Handle0x);
                                  LastRead = TimeCurrent();
                                  RestartParams();
                                  }
                               else
                                  {
                                  FileClose(Handle0x);
                                  }
                               return true;
                               }
                            else
                               {
                               return false;
                               }         
                            }         
                         }
                      }         
                   else//reading a file to close a position
                      {
                      handlestart=FileFindFirst(tempsubfolder+"*",filenametemp);   
                      int SearchStart=8;//when the line starts with "CLOSING "
                      
                      if ( StringLen(filenametemp) >= (8 + StringLen(FileNameString)) && StringSubstr(filenametemp,0,8) == "CLOSING " 
                      && StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                         {
                         filename=tempsubfolder+filenametemp;
                         }
                      if (filename != filenametemp || filename == "")
                         {
                         while ( FileFindNext(handlestart,filenametemp) )
                            {
                            if ( StringLen(filenametemp) >= (8 + StringLen(FileNameString)) && StringSubstr(filenametemp,0,8) == "CLOSING " 
                            && StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                               {
                               filename=tempsubfolder+filenametemp;
                               break;
                               }
                            }         
                         }
                      if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search
                      
                      if (filename != "")
                         {
                         Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI);
                         
                         if ( Handle0x != INVALID_HANDLE )//if the file exists
                            {
                            FileSeek(Handle0x,0,SEEK_SET);
                            ulong size = FileSize(Handle0x);
                            string str = "";
                            for(ulong i = 0; i < size; i++)
                               {
                                  str += FileReadString(Handle0x);
                               }
                            if (str != "" && str != PrevReaded)
                               {
                               PrevReaded=str;
                               FileSeek(Handle0x,0,SEEK_SET);
                               //read the required parameters
                               ReadFileStrings(Handle0x);
                               //
                               FileClose(Handle0x);
                               LastRead = TimeCurrent();
                               RestartParams();                  
                               }
                            else
                               {
                               FileClose(Handle0x);
                               }
                            return true;
                            }
                         else
                            {
                            return false;
                            }         
                         }         
                      }         
                   return false;   
                   }

                Zunächst möchte ich betonen, dass diese Methode speziell für das Lesen von zwei Arten von Dateien konzipiert ist. Je nach der übergebenen bClosingFile-Markierung wird entweder die allgemeine Einstellung oder „for closing“ gelesen. Jedes Lesen einer Datei besteht aus mehreren Schritten:

                1. Vergleich des Inhalts der zuletzt gelesenen Datei mit dem der aktuellen Datei;
                2. Wenn der Inhalt anders ist, lesen wir unsere aktualisierten Einstellungen;
                3. Bei Bedarf starten wir unseren virtuellen EA mit neuen Einstellungen neu.

                Die Methode ist so aufgebaut, dass die Bereinigung der Ressourcen und andere Maßnahmen bereits durchdacht sind. Wir müssen nur die nächste Methode implementieren, die in der vorherigen aufgerufen wird. Ich habe versucht, alles so zu gestalten, dass man sich die Mühe dieser Dateioperationen ersparen und sich so weit wie möglich darauf konzentrieren kann, genau den Lesecode zu schreiben, den man braucht. Hier wird das Lesen durchgeführt.

                //+------------------------------------------------------------------+
                //|               read settings from file line by line               |
                //+------------------------------------------------------------------+
                void BotInstance::ReadFileStrings(int handle)
                   {
                   //FileReadString(Handle,0);
                   
                   }

                Es ist nicht erforderlich, die Datei hier zu öffnen oder zu schließen. Alles, was Sie tun müssen, ist, die Datei Zeichenkette für Zeichenkette zu lesen und das Gelesene korrekt den entsprechenden Variablen zuzuweisen. Zu diesem Zweck können wir beide temporären Variablen verwenden und alle Daten sofort in die Variablen schreiben, die wir als Einstellungen für unsere Strategie verwenden werden. Ich würde jedoch empfehlen, die Einstellungen bereits in dieser Methode vorzunehmen, die für diese Zwecke vorgesehen war.

                //+------------------------------------------------------------------+
                //|                function to prepare new parameters                |
                //+------------------------------------------------------------------+
                void BotInstance::RestartParams() 
                {
                   //additional code
                
                   //
                   MagicF=SmartMagic(BasicNameToSymbol(Charts[chartindex].BasicName), Charts[chartindex].Timeframe);
                   CurrentSymbol=Charts[chartindex].CurrentSymbol;
                   m_trade.SetExpertMagicNumber(MagicF);
                }

                Die letzten drei Zeichenketten müssen nicht berührt werden, da sie obligatorisch sind. Das Interessanteste daran ist die Methode SmartMagic, die dazu dient, jedem der virtuellen EAs automatisch die Magic-Nummern zuzuweisen. Alles, was wir zu diesem Zeitpunkt wissen müssen, ist, dass wir diese Logik für die Neuzuweisung der EA-Einstellungen etwas höher schreiben müssen - in den leeren Block. Falls erforderlich, können wir auch die Indikatoren und alles andere, was noch vorhanden ist, neu erstellen.


                Automatische Generierung von Magic-Nummern

                Ohne von der vorherigen Methode abzuweichen, möchte ich Ihnen gleich eine Methode zur Erzeugung eindeutiger Auftrags-IDs vorstellen, die einen unabhängigen Handel für alle virtuellen EAs innerhalb der Vorlage gewährleisten. Dazu habe ich die folgende Methode verwendet. 

                Ich weise zum Beispiel einen Schrittweite von „10000“ zwischen den nächstgelegenen Magic-Nummern zu. Für jedes Instrument notiere ich zunächst seine vorläufige Magic-Nummer, zum Beispiel „10000“ oder „70000“. Das ist jedoch nicht genug, denn das Instrument hat auch einen Zeitrahmen. Deshalb füge ich dieser vorläufigen Magic-Nummer eine weitere Zahl hinzu. 

                Am einfachsten ist es, das Minutenäquivalent des Zeitrahmen hinzuzufügen, genau wie in der Dateilesestruktur. So wird es gemacht.

                //+------------------------------------------------------------------+
                //|              Smart generation of magical numbers                 |
                //|    (each instrument-period has its own fixed magic number)       |
                //+------------------------------------------------------------------+
                int BotInstance::SmartMagic(string InstrumentSymbol,ENUM_TIMEFRAMES InstrumentTimeframe)
                {
                   // initialization
                   int magicbuild=0;
                   
                   // loop through the array
                   for ( int i=0; i<ArraySize(III); i++ )
                   {
                      // check the symbol to assign a magic number
                      if ( III[i] == InstrumentSymbol )
                      {
                          magicbuild=MagicHelp+(i+1)*10000;
                          break; // stop the loop
                      }
                   }  
                   
                   // add identifier for time frame    
                   magicbuild+=InstrumentTimeframe;      
                   return magicbuild;
                }

                Hier scheint, dass unsere zusätzliche Verschiebung der Magic-Nummern die Magic-Nummer eindeutig macht, wenn auch zwischen verschiedenen EAs innerhalb des Terminals.

                //+------------------------------------------------------------------+
                //|               unique shift for difference of EA                  |
                //+------------------------------------------------------------------+
                #define MagicHelp 0 //bot magic shift [0...9999]

                Im Allgemeinen ist alles ganz einfach. Der große Schritt zwischen den Magic-Nummern bietet eine ganze Reihe von Verschiebungsoptionen, was mehr als genug ist, um die erforderliche Anzahl von EAs zu erstellen. Die einzige Bedingung für die Verwendung dieser Struktur ist, dass die Zahl „9999“ nicht überschritten wird. Außerdem müssen wir sicherstellen, dass die Verschiebung nicht mit unseren Zeitrahmenäquivalenten in Minuten übereinstimmt, da es in diesem Fall zu Überschneidungen bei den Magic-Nummer zweier verschiedener Vorlagen kommen kann. Um nicht über solche Optionen nachdenken zu müssen, können wir einfach eine Verschiebung von etwas mehr als „240“ vornehmen, zum Beispiel „241“, „241*2“, „241*3“, „241*N“.

                Zusammenfassend lässt sich sagen, dass diese Struktur uns vollständig von der Festlegung der Magic-Nummern befreit, was eines der unausgesprochenen Zwischenziele dieser Lösung war. Der einzige Nachteil besteht darin, dass es nicht möglich ist, zwei oder mehr unabhängige virtuelle EAs miteinander zu verbinden, da ihre Magic-Nummern zusammenfallen, was letztlich zur Interaktion dieser Strategien und damit zum Zusammenbruch ihrer Logik führt. Aber ich wüsste wirklich nicht, wer solche Besonderheiten brauchen würde. Außerdem fällt dies nicht unter die ursprünglich vorgesehene Funktionalität. Vielleicht füge ich sie im nächsten Artikel hinzu, falls jemand daran interessiert ist.


                System zur Volumen-Normalisierung

                Wenn ich eine Vorlage habe, die einfach und leicht anpassbar ist, dann ist es äußerst wichtig, die richtige Methode für die Festlegung der Handelsvolumina zu wählen. Es ist sehr interessant, dass die endgültigen Schlussfolgerungen aus meinen Artikeln zur Wahrscheinlichkeitstheorie, insbesondere aus diesem Artikel, mir geholfen haben, einen einfachen und effektiven Volumenausgleich durchzuführen. Ich beschloss, die Volumina auszugleichen, wobei ich davon ausging, dass die durchschnittliche Laufzeit und die Höhe des absoluten Werts des finanziellen Endergebnisses der Position ähnlich sein sollten.

                Die durchschnittliche Steigerungs- oder Verringerungsrate des Kapitals des endgültigen Handelscharts sollte von jedem der in der endgültigen Zusammenstellung enthaltenen EAs (unabhängiges Handelsinstrument - Zeitrahmen) gleichermaßen bereitgestellt werden. Alle erforderlichen Werte sollten berechnet werden, ohne Daten aus den Instrumentenchart zu verwenden, die nicht in der Liste unserer virtuellen Charts erscheinen. Die Volumina sollten nicht nur im Verhältnis zur Anzahl der EAs, sondern auch im Verhältnis zur Einlage (Auto lot) verteilt werden.

                Zu diesem Zweck ist es wichtig, die folgenden Definitionen und Gleichungen sofort einzuführen. Zu Beginn habe ich die folgenden Parameter zur Anpassung der Risiken eingeführt:

                • DeltaBarPercent - Prozentsatz des DepositDeltaEquity,
                • DepositDeltaEquity - Einlage eines Bots zur Berechnung seines akzeptablen Kapital-Deltas für einen M1-Balken mit einer offenen Position.

                Die Begriffe mögen zunächst unklar erscheinen. Lassen Sie mich also klarstellen. Der Einfachheit halber geben wir eine Einlage an, mit der ein separater virtueller EA arbeitet, und geben dann in Prozenten an, welcher Teil dieser Einlage (in Form unseres Kapitals) steigen oder sinken soll, wenn wir eine Position eröffnen und wenn es eine Bewegung vom oberen Punkt „M1“ zum unteren Punkt oder umgekehrt gibt.

                Das Ziel unseres Codes ist es, das Eröffnungsvolumen unter Berücksichtigung unserer Anforderungen automatisch auszuwählen. Dazu benötigen wir zusätzliche mathematische Größen und darauf basierende Gleichungen. Ich werde keine Schlussfolgerungen ziehen, sondern sie Ihnen nur zur Verfügung stellen, um den Code zu erklären:

                • „Mb“ - durchschnittliche Größe der Balken in Punkten im ausgewählten Bereich der „Balken“-Größe auf dem Arbeitschart des EA,
                • „Mb1“ - durchschnittliche Größe der Balken in Punkten im ausgewählten Bereich der Balkengröße auf dem Arbeitschart des EA, neu berechnet auf M1,
                • „Kb“ - Verbindungsverhältnis zwischen der durchschnittlichen Größe der Balken des aktuellen Charts und seinem „M1“-Äquivalent,
                • „T“ - die Periode des ausgewählten Charts wird auf eine Minute reduziert (so wie wir es in unseren Dateien hatten),
                • „BasisI“ - erforderlicher durchschnittlicher Anstieg oder Rückgang der Aktienlinie in der Einzahlungswährung für die durchschnittliche Größe der M1-Kerze auf dem ausgewählten Instrumentenchart,
                • „Basisb“ - tatsächlicher durchschnittlicher Anstieg oder Rückgang der Kapitalkurve in der Einzahlungswährung für die durchschnittliche Größe der M1-Kerze auf dem ausgewählten Instrumentenchart für einen Handel mit der Losgröße „1“,
                • „Lot“ - ausgewähltes Losgröße (Volumen).

                Nachdem ich nun alle in der Berechnung verwendeten Größen aufgelistet habe, können wir mit der Analyse und dem Verständnis der Berechnung beginnen. Um die benötigte Losgröße zu berechnen, sollten wir zunächst verstehen, wie das Verhältnis zwischen den Balkengrößen auf höheren Zeitrahmen im Vergleich zu „M1“ zustande kommt. Die folgende Gleichung ist dabei hilfreich.

                Skalierungsfaktor für den Übergang zu M1

                Dies ist genau der Ausdruck, der es ermöglicht, ohne Daten von „M1“ zu laden, dieselbe Charakteristik zu berechnen, wenn auch für die dargestellte Periode des virtuellen Charts, mit dem die ausgewählte Instanz des virtuellen EA arbeitet. Nach der Multiplikation mit diesem Faktor erhalten wir fast die gleichen Daten, als ob wir sie für den Zeitraum „M1“ berechnet hätten. Das ist das erste, was zu tun ist. Die Methode zur Berechnung dieses Wertes sieht folgendermaßen aus:

                //+------------------------------------------------------------------+
                //|       timeframe to average movement adjustment coefficient       |
                //+------------------------------------------------------------------+
                double PeriodK(ENUM_TIMEFRAMES tf)
                   {
                   double ktemp;
                   switch(tf)
                      {
                      case  PERIOD_H1:
                          ktemp = MathSqrt(1.0/60.0);
                          break;
                      case  PERIOD_H4:
                          ktemp = MathSqrt(1.0/240.0);
                          break;
                      case PERIOD_M1:
                          ktemp = 1.0;
                          break;
                      case PERIOD_M5:
                          ktemp = MathSqrt(1.0/5.0);
                          break;
                      case PERIOD_M15:
                          ktemp = MathSqrt(1.0/15.0);
                          break;
                      case PERIOD_M30:
                          ktemp = MathSqrt(1.0/30.0);
                          break;
                      default: ktemp = 0;
                      }
                   return ktemp;
                   }

                Jetzt müssen wir natürlich verstehen, welchen Wert wir an „M1“ anpassen. Hier ist sie.

                Mit anderen Worten: Wir berechnen die durchschnittliche Größe der Kerzen in Punkten auf dem Chart, mit dem der virtuelle EA arbeitet. Nachdem wir den Wert berechnet haben, sollten wir ihn unter Verwendung des vorherigen Wertes wie folgt umrechnen.

                Diese beiden Aktionen werden in der folgenden Methode durchgeführt.

                //+------------------------------------------------------------------+
                //|     average candle size in points for M1 for the current chart   |
                //+------------------------------------------------------------------+
                double CalculateAverageBarPoints(Chart &Ch)
                   {
                   double SummPointsSize=0.0;
                   double MaxPointSize=0.0;
                   for (int j = 0; j < ArraySize(Ch.HighI); j++)
                      {
                      if (Ch.HighI[j]-Ch.LowI[j] > MaxPointSize) MaxPointSize= Ch.HighI[j]-Ch.LowI[j];
                      }
                        
                   for (int j = 0; j < ArraySize(Ch.HighI); j++)
                      {
                      if (Ch.HighI[j]-Ch.LowI[j] > 0) SummPointsSize+=(Ch.HighI[j]-Ch.LowI[j]);
                      else SummPointsSize+=MaxPointSize;
                      }  
                   SummPointsSize=(SummPointsSize/ArraySize(Ch.HighI))/Ch.ChartPoint;
                   return PeriodK(Ch.Timeframe)*SummPointsSize;//return the average size of candles reduced to a minute using the PeriodK() adjustment function
                   }

                Nun können wir den daraus resultierenden Wert, reduziert auf M1, zur Berechnung der Variable „Basisb“ verwenden. Sie sollte wie folgt berechnet werden.

                Die Tick-Größe ist der Betrag der Veränderung des Kapitals einer offenen Position mit einem Volumen von „1“ Lot, wenn sich der Preis um „1“ Punkt bewegt. Multipliziert man diesen Wert mit der durchschnittlichen Größe einer Minutenkerze, so erhält man den Betrag der Kapitalveränderung für eine Position mit einem einzigen Lot, wobei zu berücksichtigen ist, dass die Größe der Bewegung der durchschnittlichen Größe des Minutenbalkens entspricht. Als Nächstes sollten wir den verbleibenden Wert von „BasisI“ berechnen, und dies geschieht wie folgt.

                Der Prozentsatz und die Einlage darin sind genau die Steuerungsparameter, mit denen wir die Grundlage benötigen. Es bleibt nur noch, ein Verhältnis zu wählen, damit die Basen gleich sind, und dieses Verhältnis wird unser endgültiges Losgröße sein.

                Alle beschriebenen Vorgänge werden nach dem folgenden Verfahren durchgeführt.

                //+------------------------------------------------------------------+
                //|    calculate the optimal balanced lot for the selected chart     |
                //+------------------------------------------------------------------+
                double OptimalLot(Chart &Ch)
                   {
                   double BasisDX0 =  (DeltaBarPercent/100.0) * DepositDeltaEquityE;
                   double DY0=CalculateAverageBarPoints(Ch)*SymbolInfoDouble(Ch.CurrentSymbol,SYMBOL_TRADE_TICK_VALUE);
                   return BasisDX0/DY0;
                   }

                Auf diese Weise gleichen wir die Volumina aller Instrumente effektiv aus, um einen gleichen Beitrag zum Kapital aller virtuellen EAs zu leisten. Es stellt sich heraus, dass es so etwas wie ein fester Losgrößen-Modus ist, so als ob wir ihn für jeden EA separat eingestellt hätten, aber in diesem Fall sind wir diese Notwendigkeit losgeworden. Es handelt sich um ein völlig anderes Risikokontrollsystem, das jedoch an den diversifizierten Handel angepasst ist und das Sie auf jeden Fall anwenden müssen, wenn Sie eine optimale Stabilität der Gewinnkurve erreichen und sich von dieser Routine befreien wollen. Die Balance zwischen mehreren EAs ist in der Tat der heikelste Moment. Ich denke, diejenigen, die ähnliche Dinge getan haben, werden diese Entscheidung zu schätzen wissen. Wenn Ihnen diese Ausgleichsmethode nicht zusagt, können Sie Ihre Einstellungen jederzeit in die entsprechenden Dateien schreiben und dieses System ändern.


                Auto lot

                Die normalisierte Losgröße kann im Verhältnis zur Einlage erhöht werden. Um zu verstehen, wie das geht, werde ich die folgenden Begriffe einführen:

                • - Lot - normalisierte (ausgeglichene) Losgröße für einen bestimmten virtuellen EA;
                • - AutoLot - neu berechnet auf die „Lot“-Einlage für den Auto-Lot-Modus (wir müssen sie erhalten, wenn der Auto-Lot-Modus aktiviert ist);
                • - DepositPerOneBot - Teil des aktuellen Depots (Teil von Deposit), das nur von einem der virtuellen EAs kontrolliert werden kann;
                • - DepositDeltaEquity - Einlage, gegen die wir die Normalisierung durchgeführt haben (ausgeglichene Lose);
                • - Einlage - unsere derzeitige Bank;
                • - BotQuantity - aktuelle Anzahl der virtuellen EAs, die innerhalb des Multibots handeln.

                Dann können wir schreiben, wie unser „AutoLot“ berechnet wird:

                • AutoLot = Lot * (DepositPerOneBot / DepositDeltaEquity).

                Es stellt sich heraus, dass im Falle des üblichen normalisierten Volumens, wir unsere Einlage ignorieren und akzeptieren, dass die folgende Einlage für einen virtuellen EA zugeteilt wird - DepositDeltaEquity. Aber im Falle von Auto lot ist dieser Einlage nicht real, und wir sollten die normalisierten Volumina proportional ändern, damit sich unsere Risiken an die reale Einlage anpassen. Sie muss jedoch angepasst werden, wobei zu berücksichtigen ist, dass ein virtueller EA nur einen Teil der Einlage ausmacht.

                • DepositPerOneBot = Deposit / BotQuantity.

                So funktioniert das Auto lot in meiner Vorlage. Ich denke, dieser Ansatz ist recht bequem und bietet die notwendige Anpassung an die Steilheit der Kurve exponentiellem Wachstum. Den Quellcode finden Sie im Anhang. Schauen wir uns das Ergebnis der richtigen Anpassung dieser Werte an.

                automatische Losgröße mit normalisierten Volumina

                Bitte beachten Sie, dass die Gewinnkurve in diesem Modus ungefähr so aussieht wie hier gezeigt, vorausgesetzt, die Einstellungen sind korrekt und das Ausgangssignal ist profitabel. Diese Kurve wird umso glatter und exponentieller verlaufen, je höher der Gewinnfaktor Ihrer Strategie ist und je mehr Abschlüsse sie in Ihrem Testbereich hat. Genau das wird durch die Diversifizierung sehr gut erreicht. Unsere Vorlage enthält alle Grundlagen, um diesen Effekt zu maximieren. Achten Sie außerdem auf die Belastung der Einlage: Ihre Gleichmäßigkeit und Gleichförmigkeit ist ein indirekter Hinweis auf die korrekte Normalisierung und anschließende Skalierung der aktuellen Einlagenbelastung. Dieses Beispiel habe ich meinem Produkt entnommen, das auf der fraglichen Vorlage basiert.


                Synchronisierung mit API

                Diese Funktion ist optional und kann sehr leicht deaktiviert oder ganz aus der Vorlage entfernt werden, aber ich persönlich fand sie für mein Produkt sehr nützlich. Wie bereits erwähnt, wird die Synchronisierung auch durch den Timer ausgelöst.

                //+------------------------------------------------------------------+
                //|            used to read the settings every 5 minutes +           |
                //+------------------------------------------------------------------+
                void DownloadTimer()
                {
                    // Check if the passed time from the last download time is more than 5 minutes
                    if (  TimeCurrent() - LastDownloadTime > 5*60 + int((double(MathRand())/32767.0) * 60) )
                    {
                        // Set the last download time to the current time
                        LastDownloadTime=TimeCurrent();
                        // Download files again
                        DownloadFiles();
                     }
                } 

                Schauen wir uns nun die Hauptmethode DownloadFiles an.

                //+------------------------------------------------------------------+
                //|       used to download control files if they isn't present       |
                //+------------------------------------------------------------------+
                void DownloadFiles()
                {
                    string Files[];
                    // Initialize the response code by getting files from the signal directory
                    int res_code=GetFiles(SignalDirectoryE,Files); 
                
                    // Check if the list of files is successfully got
                    if (res_code == 200)
                    {
                        // Proceed if there is at least one file in the server
                        if (ArraySize(Files) > 0)
                        {
                            // Download each file individually
                            for (int i = 0; i < ArraySize(Files); i++)
                            {
                                string FileContent[];
                                // Get the content of the file
                                int resfile =  GetFileContent(SignalDirectoryE,Files[i],FileContent);
                
                                // Check if the file content is successfully got
                                if (resfile == 200)
                                {
                                    // Write the file content in our local file
                                    WriteData(FileContent,Files[i]);
                                }
                            }
                        }
                    }
                }

                Ich habe die gesamte Struktur so aufgebaut, dass der erste Schritt darin besteht, auf die API zuzugreifen, um die gesamte Liste der Dateien zu ermitteln, die sich in dem angegebenen Ordner auf unserem Server befinden. So werden die Einstellungen verteilt. Der Name des Ordners ist SignalDirectoryE. Dies ist auch der Name des Signals nach meiner Vorstellung. Nach Erhalt der Dateiliste wird jede Datei einzeln heruntergeladen. Meiner Meinung nach ist diese Konstruktionslogik sehr praktisch. Auf diese Weise können wir viele Signale (Ordner) erstellen, zwischen denen wir jederzeit wechseln können. Wie Sie dies tun und gestalten, bleibt Ihnen überlassen. Meine Aufgabe ist es, fertige Funktionen für eine einfache Verbindung bereitzustellen. Sehen wir uns nun die Methodenvorlage an, die eine Liste von Dateinamen von unserem Server abruft.

                //+------------------------------------------------------------------+
                //|              getting the list of files into an array             |
                //+------------------------------------------------------------------+
                int GetFiles(string directory,string &fileArray[])
                   {
                   //string for getting a list of files in the form of JSON via GET to API 
                   string urlList = ApiDomen+"/filelist/"+directory;//URL
                   char message[];//Body of the request
                   string headers = "Password_key: " + key;// We form the headers of the request
                   string resultheaders = "";//returning headers          
                   string cookie = "";//cookies
                   int timeout = 1500;//waiting for a response when requesting a file or json
                   char result[];
                   
                   // We send a GET request to the server to receive JSON with a list of files
                   int res_code =  WebRequest("GET", urlList, headers, timeout, message, result, resultheaders);
                   bool rez = extractFiles(CharArrayToString(result),fileArray);
                   if (rez) return res_code;
                   else return 400;
                   }

                Alles, was Sie hier tun müssen, ist, dass Sie die Zeichenkette Ihrer „URL“ auf die gleiche Weise bilden müssen. Die wichtigsten Teile sind dabei die folgenden Zeichenfolgen:

                • filelist
                • Password_key

                Die erste Zeichenfolge ist eine der Funktionen Ihrer API. Sie können den Namen ändern, wenn Sie möchten. Sie werden mehrere solcher Funktionen haben. Zum Beispiel, um die folgenden Vorgänge zu ermöglichen:

                1. Hochladen von Einstellungsdateien in Ihre API (aus Ihrer individuellen Anwendung oder Ihrem Programm).
                2. Löschen von Einstellungsdateien auf Ihrer API (von Ihrer individuellen Anwendung oder Ihrem Programm).
                3. Hochladen von Dateilisten aus einem bestehenden Verzeichnis (aus Ihrem EA).
                4. Hochladen von Dateiinhalten (Ihres EAs).

                Es kann sein, dass Sie noch andere Funktionen benötigen, aber im EA werden nur die letzten beiden benötigt. Die zweite Zeichenfolge ist eine der Kopfzeilen (headers). Sie müssen sie auch auf der Grundlage dessen, was Sie an die API übergeben, formen. In der Regel brauchen Sie nur einen Zugangsschlüssel, um zu verhindern, dass jemand einbricht. Sie können aber noch mehr hinzufügen, wenn Sie zusätzliche Daten übermitteln müssen. Der vom Server empfangene String sollte hier geparst werden.

                //+------------------------------------------------------------------+
                //|                  get file list from JSON string                  |
                //+------------------------------------------------------------------+
                bool extractFiles(string json, string &Files[])
                   {
                
                   return false;
                   }

                Wir empfangen JSON und parsen es in Namen. Leider gibt es keinen universellen Parsing-Code. Jeder Fall ist individuell. Ich persönlich würde nicht sagen, dass das Schreiben des Parsing-Codes zu schwierig ist. Natürlich ist es gut, die entsprechenden Bibliotheken zu haben, aber ich persönlich ziehe es vor, so viel Code wie möglich selbst zu schreiben. Sehen wir uns nun eine ähnliche Methode an, die den Inhalt der Datei abruft.

                //+------------------------------------------------------------------+
                //|                    getting the file content                      |
                //+------------------------------------------------------------------+
                int GetFileContent(string directory,string filename,string &OutContent[])
                   {
                   //string for getting a file content in the form of JSON via GET to API 
                   string urlList = ApiDomen+"/file_content/"+directory+"/"+filename;//
                   char message[];// Body of the request
                   string headers = "Password_key: " + key;// We form the headers of the request
                   string resultheaders = "";//returning headers             
                   string cookie = "";//cookies
                   int timeout = 1500;//waiting for a response when requesting a file or json
                   char result[];
                   
                   // We send a GET request to the server to receive JSON with a file content
                   int res_code =  WebRequest("GET", urlList, headers, timeout, message, result, resultheaders);
                   bool rez = extractContent(CharArrayToString(result),OutContent);
                   if (rez) return res_code;
                   else return 400;
                   } 

                Alles ist genauso wie in der vorigen Version, nur dass wir nicht eine Liste von Dateien, sondern eine Liste von Zeichenketten innerhalb der Datei erhalten. Natürlich wird das Parsen von JSON mit den Dateiinhalten Zeichenkette für Zeichenkette von einer separaten Logik in der folgenden Methode durchgeführt, die den gleichen Zweck hat wie ihr Bruder - „extractFiles“.

                //+------------------------------------------------------------------+
                //|   read the contents of the file from JSON each line separately   |
                //+------------------------------------------------------------------+ 
                bool extractContent(string json, string &FileLines[])
                   {
                   
                   return false;
                   }

                Natürlich müssen Sie nicht alles genau so machen, wie ich es sage, ich habe nur schon ein Produkt, das genau so aufgebaut ist. Die Verwendung eines konkreten Arbeitsbeispiels scheint mir viel einfacher zu sein, um all dies zu verstehen. Nachdem Sie den Inhalt der Datei erhalten haben, können Sie ihn mit der folgenden Methode, die bereits in die Vorlagenlogik integriert ist, sicher Zeichenkette für Zeichenkette schreiben.

                //+-----------------------------------------------------------------------+
                //|    fill the file with its lines, which are all contained in data      |
                //|  with a new line separator, and save in the corresponding directory   |
                //+-----------------------------------------------------------------------+
                void WriteData(string &data[],string FileName)
                   {
                   int fileHandle;
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; 
                   
                   if (!bCommonReadE)
                      {
                      fileHandle = FileOpen(tempsubfolder+FileName,FILE_REWRITE|FILE_WRITE|FILE_TXT|FILE_ANSI);
                      }
                   else
                      {
                      fileHandle = FileOpen(tempsubfolder+FileName,FILE_REWRITE|FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_COMMON);
                      } 
                      
                   if(fileHandle != INVALID_HANDLE) 
                       {
                       FileSeek(fileHandle,0,SEEK_SET);
                       for (int i=0; i < ArraySize(data) ; i++)
                          {
                          FileWriteString(fileHandle,data[i]+"\r\n");
                          }
                       FileClose(fileHandle);
                       }
                   }
                
                

                So wird eine Datei mit ihrem Namen erstellt. Und natürlich wird auch ihr Inhalt in Form von separaten Zeichenketten in die Datei geschrieben. Dies ist eine völlig ausreichende Lösung für kleine Dateien. Ich habe während der Arbeit keine Verlangsamungen festgestellt.


                Allgemeine Methode mit Handelslogik

                Ich habe dieses Thema bereits im vorherigen Artikel angesprochen, möchte es aber noch einmal hervorheben und Sie daran erinnern, dass diese Methode funktioniert, wenn in jedem virtuellen EA ein neuer Balken geöffnet wird. Wir können ihn als Handler des Typs OnTick betrachten, aber in unserem Fall wird es natürlich OnBar sein. Übrigens gibt es in MQL5 keinen solchen Handler. Es funktioniert nicht ganz so, wie wir es gerne hätten, aber es hat nicht wirklich einen signifikanten Einfluss auf den Balkenhandel, also ist das unser geringstes Problem.

                //+------------------------------------------------------------------+
                //|      the main trading function of individual robot instance      |
                //+------------------------------------------------------------------+
                void BotInstance::Trade() 
                {
                   //data access
                   
                   //Charts[chartindex].CloseI[0]//current bar (zero bar is current like in mql4)
                   //Charts[chartindex].OpenI[0]
                   //Charts[chartindex].HighI[0]
                   //Charts[chartindex].LowI[0]
                   //Charts[chartindex]. ???
                   
                   //close & open
                   
                   //CloseBuyF();
                   //CloseSellF();
                   //BuyF();
                   //SellF();   
                
                   // Here we can include operations such as closing the buying position, closing selling position and opening new positions.
                   // Other information from the chart can be used for making our buying/selling decisions.
                   
                   // Here is a simple trading logic example
                   if ( Charts[chartindex].CloseI[1] > Charts[chartindex].OpenI[1] )
                   {
                      CloseBuyF();
                      SellF();
                   }
                   if ( Charts[chartindex].CloseI[1] < Charts[chartindex].OpenI[1] )
                   {
                      CloseSellF();
                      BuyF();
                   }      
                } 

                Ich habe einige grundlegende Logik in der Vorlage implementiert, sodass Sie Ihre eigene erstellen können, indem Sie sie als Beispiel verwenden. Ich empfehle, Ihre eigene Logik und Variablen neben dieser Methode in der BotInstance-Klasse hinzuzufügen, um nicht durcheinander zu kommen. Ich empfehle Ihnen, Ihre Logik mit Hilfe von Methoden und Variablen aufzubauen, die Sie in der Hauptmethode Trade verwenden werden.


                Grafische Nutzeroberfläche

                Die Vorlage enthält, genau wie die vorherige Version, ein Beispiel für eine einfache Nutzeroberfläche, deren Farbschema und Inhalt geändert werden kann. Diese Schnittstelle ist für beide Vorlagen identisch: sowohl MetaTrader 4 als auch MetaTrader 5, und sie sieht besser aus.

                GUI

                Fragezeichen kennzeichnen Stellen, an denen Sie zusätzliche Daten hinzufügen oder unnötige Blöcke entfernen können. Dies ist sehr einfach zu bewerkstelligen. Es gibt zwei Methoden, um mit der Schnittstelle zu arbeiten: CreateSimpleInterface und UpdateStatus. Sie sind sehr einfach. Ich werde sie nicht in Aktion zeigen. Sie können sie anhand ihrer jeweiligen Namen finden.

                Ich habe drei sehr nützliche Felder zu dieser Schnittstelle hinzugefügt. Wenn Sie sich die letzten drei Zeichenfolgen ansehen, können Sie Ihren „reservierten Korridor der Magic-Nummern“ erkennen, der für die von Ihnen verwendete aktuelle Konfiguration relevant ist. Wenn wir Einstellungsdateien löschen oder hinzufügen, verengt oder erweitert sich dieser Korridor entsprechend. Darüber hinaus müssen wir verschiedene EAs irgendwie vor Konflikten schützen, und dieses Feld wird uns dabei helfen. Die beiden verbleibenden Felder geben an, wann die Einstellungen das letzte Mal gelesen wurden und wann die letzte Synchronisierung mit unserer API stattgefunden hat, sofern eine solche Synchronisierung überhaupt stattfindet.


                Schlussfolgerung

                In diesem Artikel haben wir ein komfortableres und funktionelleres Vorlagenmodell entwickelt, das sich unter anderem auch für weitere Erweiterungen und Änderungen eignet. Der Code ist noch weit davon entfernt, perfekt zu sein, und es gibt noch eine Menge zu optimieren und zu beheben, aber selbst wenn man all das berücksichtigt, habe ich einige klare Vorstellungen davon, was dort hinzugefügt werden soll und zu welchem Zweck. Für den nächsten Artikel möchte ich Positionen virtualisieren und auf der Grundlage dieser Daten einen einzigartigen, währungsübergreifenden Optimierer erstellen, der jeden unserer EAs individuell optimiert und fertige Dateien mit Einstellungen für unsere Strategien generiert.

                Der währungsübergreifende Optimierer wird es uns ermöglichen, alle virtuellen Instrumentenperioden gleichzeitig zu optimieren. Wenn wir alle diese Einstellungen zusammenführen, erhalten wir eine bessere und sicherere Gewinnkurve mit geringeren Risiken. Ich halte die automatische Diversifizierung und Gewinnsteigerung für eine absolute Priorität für weitere Verbesserungen. Daher würde ich gerne ein grundlegendes Add-on zu jeder Strategie erhalten, das es ermöglicht, den maximalen Gewinn daraus zu ziehen und gleichzeitig die maximale Funktionalität und Bequemlichkeit für den Endnutzer zu erhalten. Dies sollte so etwas wie ein Exoskelett für unser Handelssignal sein.

                Links

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

                Beigefügte Dateien |
                DynamicTemplate.zip (45.56 KB)
                Winkelbasierte Operationen für Händler Winkelbasierte Operationen für Händler
                Dieser Artikel behandelt winkelbasierte Operationen. Wir werden uns Methoden zur Konstruktion von Winkeln und deren Verwendung beim Handel ansehen.
                Algorithmen zur Optimierung mit Populationen: Widerstand gegen das Steckenbleiben in lokalen Extremen (Teil II) Algorithmen zur Optimierung mit Populationen: Widerstand gegen das Steckenbleiben in lokalen Extremen (Teil II)
                Wir setzen unser Experiment fort, das darauf abzielt, das Verhalten von Populationsoptimierungsalgorithmen im Zusammenhang mit ihrer Fähigkeit zu untersuchen, lokale Minima bei geringer Populationsvielfalt effizient zu umgehen und globale Maxima zu erreichen. Forschungsergebnisse werden vorgelegt.
                Die Basisklasse der Populationsalgorithmen als Rückgrat einer effizienten Optimierung Die Basisklasse der Populationsalgorithmen als Rückgrat einer effizienten Optimierung
                Der Artikel präsentiert einen einzigartigen Forschungsversuch, eine Vielzahl von Populationsalgorithmen in einer einzigen Klasse zu kombinieren, um die Anwendung von Optimierungsmethoden zu vereinfachen. Dieser Ansatz eröffnet nicht nur Möglichkeiten für die Entwicklung neuer Algorithmen, einschließlich hybrider Varianten, sondern schafft auch eine universelle Basis-Testumgebung. Dieser Stand wird zu einem wichtigen Instrument für die Auswahl des optimalen Algorithmus für eine bestimmte Aufgabe.
                Entwicklung eines Expertenberaters für mehrere Währungen (Teil 4): Schwebende, virtuelle Aufträge und Speicherstatus Entwicklung eines Expertenberaters für mehrere Währungen (Teil 4): Schwebende, virtuelle Aufträge und Speicherstatus
                Nachdem wir mit der Entwicklung eines Mehrwährungs-EAs begonnen haben, konnten wir bereits einige Ergebnisse erzielen und mehrere Iterationen zur Verbesserung des Codes durchführen. Unser EA war jedoch nicht in der Lage, mit schwebenden Aufträgen zu arbeiten und den Betrieb nach dem Neustart des Terminals wieder aufzunehmen. Fügen wir diese Funktionen hinzu.