Entwicklung eines Expertenberaters für mehrere Währungen (Teil 24): Hinzufügen einer neuen Strategie (I)
Einführung
Im letzten Artikel haben wir die Entwicklung eines Systems zur automatischen Optimierung von Handelsstrategien im MetaTrader 5 fortgesetzt. Das Herzstück des Systems ist eine Optimierungsdatenbank, die Informationen über Optimierungsprojekte enthält. Um Projekte zu erstellen, wurde ein Skript zur Projekterstellung geschrieben. Obwohl das Skript geschrieben wurde, um ein Projekt zur Optimierung einer bestimmten Handelsstrategie (SimpleVolumes) zu erstellen, kann es als Vorlage verwendet werden, die an andere Handelsstrategien angepasst werden kann.
Wir haben die Möglichkeit geschaffen, ausgewählte Gruppen von Handelsstrategien in der letzten Phase des Projekts automatisch zu exportieren. Der Export erfolgte in eine separate Datenbank, die sogenannte EA-Datenbank. Sie kann vom endgültigen EA verwendet werden, um die Einstellungen von Handelssystemen ohne Neukompilierung zu aktualisieren. Dies ermöglicht es uns, die Arbeit des EA im Tester über einen Zeitraum zu simulieren, in dem mehrmals neue Projektoptimierungsergebnisse auftreten können.
Wir sind auch endlich zu einer sinnvollen Struktur für die Organisation von Projektdateien übergegangen und haben alle Dateien in zwei Teile unterteilt. Der erste Teil, die sogenannte Advisor-Bibliothek, wurde in den Ordner MQL5/Include verschoben, während der Rest im Arbeitsordner innerhalb von MQL5/Experts verblieb. Wir haben alle Dateien, die das automatische Optimierungssystem unterstützen und unabhängig von der Art der zu optimierenden Handelsstrategien sind, in den Bibliotheksbereich verschoben. Der Arbeitsordner des Projekts enthält die EAs der einzelnen Phasen, einen endgültigen EA und ein Skript zur Erstellung eines Optimierungsprojekts.
Ich habe die Modellhandelsstrategie SimpleVolumes jedoch im Bibliotheksbereich belassen, da es für uns zu diesem Zeitpunkt wichtiger war zu testen, wie der Mechanismus zur automatischen Aktualisierung der Strategieparameter im endgültigen EA funktionieren würde. Es spielte keine Rolle, wohin genau die Datei mit dem Quellcode der Handelsstrategie während der Kompilierung verbunden wurde.
Stellen wir uns nun vor, dass wir eine neue Handelsstrategie mit einem automatischen Optimierungssystem verbinden und dafür einen Stage EA und einen Final EA erstellen wollen. Was brauchen wir dafür?
Der Weg ist vorgezeichnet
Nehmen wir zunächst eine einfache Strategie und setzen sie in Code um, den wir mit unserer Advisor-Bibliothek verwenden können. Legen wir den Code in den Arbeitsordner des Projekts. Sobald die Strategie erstellt ist, kann ein Expert Advisor der ersten Stufe erstellt werden, der zur Optimierung der Parameter einzelner Instanzen dieser Handelsstrategie verwendet wird. Hier werden wir auf einige Schwierigkeiten stoßen, die sich aus der Notwendigkeit ergeben, Bibliotheks- und Projektcodes zu trennen.
Wir können für die zweite und dritte Stufe praktisch dieselben EAs verwenden, die im vorherigen Teil geschrieben wurden, da der Code ihres Bibliotheksteils keinen Hinweis auf die Klassen der verwendeten Handelsstrategien enthält. Und wir müssen einen Befehl hinzufügen, um die neue Strategiedatei in den Code im Arbeitsordner des Projekts aufzunehmen.
Für die neue Strategie müssen wir einige Änderungen am EA-Skript zur Projekterstellung in der Optimierungsdatenbank vornehmen. Zumindest wirken sich die Änderungen auf die Eingabeparameter-Vorlage für den EA der ersten Stufe aus, da sich die Zusammensetzung der Eingabeparameter in der neuen Handelsstrategie von derjenigen der vorherigen Strategie unterscheidet.
Nachdem wir das Projekt zur Erstellung von EA in der Optimierungsdatenbank geändert haben, können wir es ausführen. Die Optimierungsdatenbank wird erstellt, und die für dieses Projekt erforderlichen Optimierungsaufgaben werden ihr hinzugefügt. Als Nächstes können wir den automatischen Optimierungsförderer starten und warten, bis er seine Arbeit beendet hat. Dies ist ein recht langwieriger Prozess. Die Dauer hängt vom gewählten Optimierungsintervall ab (je länger es ist, desto länger dauert es), von der Komplexität der Handelsstrategie selbst (je komplexer sie ist, desto länger dauert sie) und natürlich von der Anzahl der für die Optimierung verfügbaren Testagenten (je mehr, desto schneller).
Der letzte Schritt besteht darin, den endgültigen EA auszuführen oder ihn im Strategietester zu testen, um die Optimierungsergebnisse zu bewerten.
Fangen wir an!
SimpleCandles-Strategie
Wir erstellen einen neuen Ordner für das Projekt im Ordner MQL5/Experts. Nennen wir ihn zum Beispiel Artikel.17277. Es ist wahrscheinlich sinnvoll, gleich einen Haftungsausschluss zu machen, um in Zukunft Verwirrung zu vermeiden. Ich werde den Begriff „Projekt“ in zweierlei Hinsicht verwenden. In einem Fall handelt es sich einfach um einen Ordner mit Dateien von EAs, die zur automatischen Optimierung einer bestimmten Handelsstrategie verwendet werden. Der Code für diese EAs wird Include-Dateien aus der Advisor-Bibliothek verwenden. In diesem Zusammenhang ist ein Projekt also einfach ein Arbeitsordner im Expertenordner des Terminals. In einem anderen Fall bezeichnet das Wort „Projekt“ eine in der Optimierungsdatenbank erstellte Datenstruktur, die Optimierungsaufgaben beschreibt, die automatisch durchgeführt werden müssen, um Ergebnisse zu erhalten, die dann im endgültigen EA verwendet werden, der auf einem Handelskonto arbeiten soll. In diesem Zusammenhang ist ein Projekt im Wesentlichen die Befüllung einer Optimierungsdatenbank, bevor die eigentliche Optimierung beginnt.
Wir sprechen hier von einem Projekt im ersten Sinne. Erstellen wir also einen Unterordner namens Strategies im Arbeitsordner des Projekts. Darin werden wir Dateien mit verschiedenen Handelsstrategien ablegen. Vorerst werden wir dort nur eine neue Strategie erstellen.
Wiederholen wir den in Teil 1 eingeschlagenen Weg bei der Entwicklung der Handelsstrategie SimpleVolumes. Beginnen wir auch mit der Formulierung der Handelsidee.
Nehmen wir an, dass die Wahrscheinlichkeit, dass die nächste Kerze eine andere Richtung hat, etwas höher ist, wenn mehrere aufeinanderfolgende Kerzen in dieselbe Richtung für ein bestimmtes Symbol auftreten. Wenn wir dann nach solchen Kerzen eine Position in die entgegengesetzte Richtung eröffnen, können wir damit möglicherweise einen Gewinn erzielen.
Lassen Sie uns versuchen, diese Idee in eine Strategie umzusetzen. Dazu müssen wir eine Reihe von Regeln für das Öffnen und Schließen von Positionen formulieren, die keine unbekannten Parameter enthalten. Mit diesem Regelwerk sollte es möglich sein, zu jedem Zeitpunkt, an dem die Strategie läuft, zu bestimmen, ob und welche Positionen eröffnet werden sollen.
Lassen Sie uns zunächst den Begriff der Kerzenrichtung erläutern. Wir bezeichnen eine Kerze als aufsteigend, wenn der Schlusskurs der Kerze größer ist als der Eröffnungskurs. Eine Kerze, deren Schlusskurs niedriger ist als der Eröffnungskurs, wird als absteigend bezeichnet. Da wir die Richtung mehrerer aufeinanderfolgender Kerzen in der Vergangenheit bewerten wollen, werden wir das Konzept der Kerzenrichtung nur auf bereits geschlossene Kerzen anwenden. Daraus können wir schließen, dass der Zeitpunkt einer möglichen Positionseröffnung mit dem Erscheinen eines neuen Balkens, d.h. mit dem Erscheinen einer neuen Kerze, eintritt.
Wir haben uns also für den Zeitpunkt der Eröffnung von Positionen entschieden, aber wie sieht es mit deren Schließung aus? Wir werden die einfachste Option verwenden: Bei der Eröffnung einer Position werden StopLoss- und TakeProfit-Levels festgelegt, bei denen die Position geschlossen wird.
Nun können wir unsere Strategie wie folgt beschreiben:
Ein Signal zur Eröffnung einer Position liegt vor, wenn zu Beginn eines neuen Balkens (einer neuen Kerze) alle vorangegangenen Kerzen in dieselbe Richtung (aufwärts oder abwärts) gerichtet sind. Wenn die Kerzen aufwärts gerichtet sind, eröffnen wir eine VERKAUFS-Position. Andernfalls eröffnen wir eine KAUF-Position.
Jede Position verfügt über StopLoss- und TakeProfit-Levels und wird nur geschlossen, wenn diese Levels erreicht werden. Wenn es bereits eine offene Position gibt und erneut ein Signal zur Eröffnung einer Position empfangen wird, können weitere Positionen eröffnet werden, wenn ihre Anzahl nicht zu groß ist.
Dies ist eine ausführlichere, aber noch nicht vollständige Beschreibung. Deshalb lesen wir ihn noch einmal und markieren alle Stellen, an denen etwas unklar ist. Hier sind ausführlichere Erläuterungen erforderlich.
Hier sind die Fragen, die sich gestellt haben:
- „... der letzten Kerzen ...“ – Wie viele?
- „... zusätzliche Positionen können eröffnet werden...“ – Wie viele Positionen können insgesamt eröffnet werden?
- „... hat StopLoss und TakeProfit Levels ...“ – Wie werden diese Werte verwendet? Wie sind sie zu berechnen?
Wie viel „letzte“ Kerzen? Das ist die einfachste Frage. Diese Menge wird einfach einer der Strategieparameter sein, die geändert werden können, um den besten Wert zu finden. Es kann sich nur um eine ganze Zahl handeln, die nicht sehr groß sein darf, wahrscheinlich nicht mehr als 10, da den Charts nach zu urteilen lange Sequenzen von unidirektionalen Kerzen selten sind.
Wie viele Positionen können insgesamt eröffnet werden? Dies kann auch zu einem Strategieparameter gemacht werden, und die besten Werte können während der Optimierung ausgewählt werden.
Wie werden die Werte für StopLoss und TakeProfit verwendet? Wie sind sie zu berechnen? Diese Frage ist etwas komplizierter, aber im einfachsten Fall können wir sie auf die gleiche Weise beantworten wie die vorherigen Fragen: StopLoss und TakeProfit in Punkten werden als Strategieparameter festgelegt. Wenn wir eine Position eröffnen, entfernen wir uns vom Eröffnungskurs um die in diesen Parametern angegebene Anzahl von Punkten in die gewünschten Richtungen. Es kann jedoch auch ein etwas komplexerer Ansatz verwendet werden. Wir können diese Parameter nicht in Punkten, sondern als Prozentsatz eines Durchschnittswerts der Volatilität des Preises des Handelsinstruments (Symbol), ausgedrückt in Punkten, festlegen. Dies wirft die nächste Frage auf.
Wie findet man diesen Wert für die Volatilität? Es gibt verschiedene Möglichkeiten, dies zu tun. Sie können zum Beispiel den vorgefertigten Volatilitätsindikator ATR (Average True Range) verwenden oder eine eigene Methode zur Berechnung der Volatilität entwickeln und implementieren. Aber höchstwahrscheinlich kann einer der Parameter in solchen Berechnungen die Anzahl der Perioden sein, über die die Bandbreite der Preisschwankungen eines Handelsinstruments berücksichtigt wird, und die Größe einer Periode. Wenn wir diese Werte zu den Strategieparametern hinzufügen, können wir sie zur Berechnung der Volatilität verwenden.
Da wir keine Einschränkungen dahingehend machen, dass nach der Eröffnung der ersten Position weitere Positionen in derselben Richtung eröffnet werden müssen, können Situationen entstehen, in denen die Handelsstrategie Positionen in verschiedenen Richtungen offen hält. Bei einer normalen Umsetzung wären wir gezwungen, den Anwendungsbereich einer solchen Strategie darauf zu beschränken, dass sie nur auf Konten mit eigenständiger Bilanzierung („Hedging“) angewendet werden kann. Bei der Nutzung virtueller Stellen gibt es diese Einschränkung jedoch nicht.
Da nun alles klar ist, wollen wir alle Werte auflisten, die wir bereits als Strategieparameter genannt haben. Um ein Signal zur Eröffnung von Positionen zu erhalten, müssen wir das Symbol und den Zeitrahmen auswählen, in dem wir die Kerzen verfolgen wollen. Dann erhalten wir die folgende Beschreibung:
Der EA wird für ein bestimmtes Symbol und einen bestimmten Zeitrahmen (Timeframe) gestartet
Die Eingabeparameter:
- Symbol
- Zeitrahmen für die Zählung unidirektionaler Kerzen
- Anzahl der Kerzen in derselben Richtung (signalSeqLen)
- ATR-Zeitraum (periodATR)
- Stop Loss (in Punkten oder % ATR) (stopLevel)
- Take Profit (in Punkten oder % ATR) (takeLevel)
- Maximale Anzahl von gleichzeitig offenen Positionen (maxCountOfOrders)
- Positionsgröße
Wenn ein neuer Balken eintrifft, prüfen wir die Richtungen der letzten geschlossenen signalSeqLen-Kerzen.
Wenn die Richtungen gleich sind und die Anzahl der offenen Positionen kleiner ist als maxCountOfOrders, dann:
- Wir berechnen StopLoss und TakeProfit. Wenn periodATR = 0 ist, wird der aktuelle Kurs einfach um die Anzahl der Punkte aus den Parametern stopLevel und takeLevel erhöht. Wenn periodATR > 0 ist, berechnen wir den ATR-Wert unter Verwendung des periodATR-Parameters für den täglichen Zeitrahmen. Wir ziehen uns vom aktuellen Kurs um die Werte ATR * stopLevel und ATR * takeLevel zurück.
- Wir eröffnen eine VERKAUFS-Position, wenn die Kerzenrichtung aufwärts gerichtet ist, und eine KAUF-Position, wenn die Kerzenrichtung abwärts gerichtet ist. Wir setzen bei der Eröffnung die zuvor berechneten StopLoss- und TakeProfit-Niveaus.
Diese Beschreibung ist bereits völlig ausreichend, um mit der Umsetzung zu beginnen. Wir werden alle Probleme lösen, die auf dem Weg dorthin auftreten.
Ich möchte auch darauf hinweisen, dass wir bei der Beschreibung der Strategie die Größe der eröffneten Positionen nicht erwähnt haben. Wir haben zwar formal einen solchen Parameter in die Liste der Parameter aufgenommen, aber angesichts der Verwendung der entwickelten Strategie im Auto-Optimierungssystem können wir einfach das Mindestlos für die Tests verwenden. Während der automatischen Optimierung werden geeignete Positionsgrößen-Multiplikatorwerte ausgewählt, die einen vorgegebenen Drawdown von 10 % über das gesamte Testintervall sicherstellen. Daher müssen wir die Positionsgrößen nirgendwo manuell einstellen.
Umsetzung der Strategie
Wir verwenden die vorhandene Klasse CSimpleVolumesStrategy und erstellen darauf aufbauend die Klasse CSimpleCandlesStrategy. Sie muss von der Klasse CVirtualStrategy abgeleitet werden. Führen wir die erforderlichen Strategieparameter als Klassenfelder auf, wobei wir bedenken, dass unsere neue Klasse einige weitere Felder und Methoden von ihren Vorgängern erbt.
//+------------------------------------------------------------------+ //| Trading strategy using unidirectional candlesticks | //+------------------------------------------------------------------+ class CSimpleCandlesStrategy : public CVirtualStrategy { protected: string m_symbol; // Symbol (trading instrument) ENUM_TIMEFRAMES m_timeframe; // Chart period (timeframe) //--- Open signal parameters int m_signalSeqLen; // Number of unidirectional candles int m_periodATR; // ATR period //--- Position parameters double m_stopLevel; // Stop Loss (in points or % ATR) double m_takeLevel; // Take Profit (in points or % ATR) //--- Money management parameters int m_maxCountOfOrders; // Max number of simultaneously open positions CSymbolInfo *m_symbolInfo; // Object for getting information about the symbol properties // ... public: // Constructor CSimpleCandlesStrategy(string p_params); virtual string operator~() override; // Convert object to string virtual void Tick() override; // OnTick event handler };
Um zentral Informationen über die Eigenschaften eines Handelsinstruments (Symbol) zu erhalten, werden wir den Zeiger auf das Klassenobjekt CSymbolInfo in die Klassenfelder aufnehmen.
Die Klasse mit unserer neuen Handelsstrategie ist von der Klasse CFactorable abgeleitet worden. Auf diese Weise können wir in der neuen Klasse einen Konstruktor implementieren, der mit Hilfe der in der Klasse CFactorable implementierten Lesemethoden die Werte der Parameter aus dem Initialisierungsstring liest. Wenn beim Lesen keine Fehler aufgetreten sind, dann gibt die Methode IsValid() „true“ zurück.
Um mit virtuellen Positionen arbeiten zu können, wird im übergeordneten CVirtualStrategy das Array m_orders für die Speicherung von Zeigern auf die Objekte der Klasse CVirtualOrder, d. h. virtuelle Positionen, deklariert. Daher werden wir im Konstruktor so viele Instanzen von virtuellen Positionsobjekten erstellen, wie im Parameter m_maxCountOfOrders angegeben sind, und sie in das Array m_orders stellen. Die statische Methode CVirtualReceiver::Get() übernimmt diese Aufgabe.
Da unsere Strategie nur dann Positionen eröffnet, wenn ein neuer Balken in einem bestimmten Zeitrahmen geöffnet wird, erstellen wir ein Objekt zur Überprüfung des Auftretens eines neuen Balkens für ein bestimmtes Symbol und einen bestimmten Zeitrahmen.
Das letzte, was wir im Konstruktor tun müssen, ist, den Symbolmonitor aufzufordern, ein Informationsobjekt für unsere Klasse CSymbolInfo zu erstellen.
Der vollständige Code des Konstruktors sieht wie folgt aus:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSimpleCandlesStrategy::CSimpleCandlesStrategy(string p_params) { // Read parameters from the initialization string m_params = p_params; m_symbol = ReadString(p_params); m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params); m_signalSeqLen = (int) ReadLong(p_params); m_periodATR = (int) ReadLong(p_params); m_stopLevel = ReadDouble(p_params); m_takeLevel = ReadDouble(p_params); m_maxCountOfOrders = (int) ReadLong(p_params); if(IsValid()) { // Request the required number of objects for virtual positions CVirtualReceiver::Get(&this, m_orders, m_maxCountOfOrders); // Add tracking a new bar on the required timeframe IsNewBar(m_symbol, m_timeframe); // Create an information object for the desired symbol m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol]; } }
Als nächstes müssen wir den abstrakten virtuellen Tilde-Operator (~) implementieren, der die Initialisierungszeichenfolge des Strategieobjekts zurückgibt. Seine Umsetzung ist Standard:
//+------------------------------------------------------------------+ //| Convert an object to a string | //+------------------------------------------------------------------+ string CSimpleCandlesStrategy::operator~() { return StringFormat("%s(%s)", typename(this), m_params); }
Eine weitere benötigte, virtuelle Methode, die implementiert werden muss, ist die Methode Tick() zur Behandlung von Ticks. Bei der Methode wird geprüft, ob ein neuer Balken begonnen hat und ob die Anzahl der offenen Positionen noch nicht den Maximalwert erreicht hat. Wenn diese Bedingungen erfüllt sind, wird geprüft, ob ein Eröffnungssignal vorhanden ist. Liegt ein Signal vor, eröffnen wir eine Position in der entsprechenden Richtung. Die übrigen Methoden, die wir der Klasse hinzufügen, spielen eine unterstützende Rolle.
//+------------------------------------------------------------------+ //| "Tick" event handler function | //+------------------------------------------------------------------+ void CSimpleCandlesStrategy::Tick() override { // If a new bar has arrived for a given symbol and timeframe if(IsNewBar(m_symbol, m_timeframe)) { // If the number of open positions is less than the allowed number if(m_ordersTotal < m_maxCountOfOrders) { // Get an open signal int signal = SignalForOpen(); if(signal == 1) { // If there is a buy signal, then OpenBuy(); // open a BUY position } else if(signal == -1) { // If there is a sell signal, then OpenSell(); // open a SELL_STOP position } } } }
Wir haben die Prüfung auf das Vorhandensein eines Öffnungssignals in die separate Methode SignalForOpen() verschoben. Bei dieser Methode erhalten wir eine Reihe von Kursen der vorangegangenen Kerzen und prüfen der Reihe nach, ob sie alle nach unten oder nach oben gerichtet sind:
//+------------------------------------------------------------------+ //| Signal for opening pending orders | //+------------------------------------------------------------------+ int CSimpleCandlesStrategy::SignalForOpen() { // By default, there is no signal int signal = 0; MqlRates rates[]; // Copy the quote values (candles) to the destination array int res = CopyRates(m_symbol, m_timeframe, 1, m_signalSeqLen, rates); // If the required number of candles has been copied if(res == m_signalSeqLen) { signal = 1; // buy signal // Loop through all the candles for(int i = 0; i < m_signalSeqLen; i++) { // If at least one upward candle occurs, cancel the signal if(rates[i].open < rates[i].close ) { signal = 0; break; } } if(signal == 0) { signal = -1; // otherwise, sell signal // Loop through all the candles for(int i = 0; i < m_signalSeqLen; i++) { // If at least one downward candle occurs, cancel the signal if(rates[i].open > rates[i].close ) { signal = 0; break; } } } } return signal; }
Die erstellten Methoden OpenBuy() und OpenSell() sind für die Eröffnung von Positionen zuständig. Da sie sich sehr ähnlich sind, stellen wir den Code nur für eine von ihnen zur Verfügung. Die wichtigsten Punkte in dieser Methode sind der Aufruf der Methode zur Aktualisierung der StopLoss- und TakeProfit-Levels, die die Werte der beiden entsprechenden Klassenfelder – m_sl und m_tp – aktualisiert, sowie der Aufruf der Methode zur Eröffnung der ersten ungeöffneten virtuellen Position aus dem Array m_orders.
//+------------------------------------------------------------------+ //| Open BUY order | //+------------------------------------------------------------------+ void CSimpleCandlesStrategy::OpenBuy() { // Retrieve the necessary symbol and price data double point = m_symbolInfo.Point(); int digits = m_symbolInfo.Digits(); // Opening price double price = m_symbolInfo.Ask(); // Update SL and TP levels by calculating ATR UpdateLevels(); // StopLoss and TakeProfit levels double sl = NormalizeDouble(price - m_sl * point, digits); double tp = NormalizeDouble(price + m_tp * point, digits); bool res = false; for(int i = 0; i < m_maxCountOfOrders; i++) { // Iterate through all virtual positions if(!m_orders[i].IsOpen()) { // If we find one that is not open, then open it // Open a virtual SELL position res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot, 0, NormalizeDouble(sl, digits), NormalizeDouble(tp, digits)); break; // and exit } } if(!res) { PrintFormat(__FUNCTION__" | ERROR opening BUY virtual order", 0); } }
Die Methode der Pegelaktualisierung prüft zunächst, ob für den ATR-Berechnungszeitraum ein Wert ungleich Null eingestellt ist. Wenn ja, dann wird die ATR-Berechnungsfunktion aufgerufen. Das Ergebnis fließt in die Variable channelWidth ein. Wenn der Periodenwert 0 ist, wird dieser Variablen 1 zugewiesen. In diesem Fall werden die Werte aus den Eingängen m_stopLevel und m_takeLevel als Werte in Punkten interpretiert und unverändert in m_sl und m_tp übernommen. Andernfalls werden sie als ein Bruchteil des ATR-Wertes interpretiert und mit dem berechneten ATR-Wert multipliziert:
//+------------------------------------------------------------------+ //| Update SL and TP levels based on calculated ATR | //+------------------------------------------------------------------+ void CSimpleCandlesStrategy::UpdateLevels() { // Calculate ATR double channelWidth = (m_periodATR > 0 ? ChannelWidth() : 1); // Update SL and TP levels m_sl = m_stopLevel * channelWidth; m_tp = m_takeLevel * channelWidth; }
Die letzte Methode, die wir für unsere neue Handelsstrategie benötigen, ist die ATR-Berechnungsmethode. Wie bereits erwähnt, kann sie auf verschiedene Weise umgesetzt werden, auch mit Hilfe fertiger Lösungen. Der Einfachheit halber werden wir eine der möglichen Umsetzungsoptionen verwenden, die uns zur Verfügung stehen:
//+------------------------------------------------------------------+ //| Calculate the ATR value (non-standard implementation) | //+------------------------------------------------------------------+ double CSimpleCandlesStrategy::ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1) { int n = m_periodATR; // Number of bars for calculation MqlRates rates[]; // Array for quotes // Copy quotes from the daily (default) timeframe int res = CopyRates(m_symbol, p_tf, 1, n, rates); // If the required amount has been copied if(res == n) { double tr[]; // Array for price ranges ArrayResize(tr, n); // Change its size double s = 0; // Sum for calculating the average FOREACH(rates, { tr[i] = rates[i].high - rates[i].low; // Remember the bar size }); ArraySort(tr); // Sort the sizes // Sum the inner two quarters of the bar sizes for(int i = n / 4; i < n * 3 / 4; i++) { s += tr[i]; } // Return the average size in points return 2 * s / n / m_symbolInfo.Point(); } return 0.0; }
Speichern wir die an der Datei Strategies/SimpleCandlesStrategy.mqh vorgenommenen Änderungen im Arbeitsordner des Projekts.
Verknüpfung der Strategie
Die Strategie als Ganzes ist also fertig, und jetzt müssen wir sie mit der EA-Datei verbinden. Beginnen wir mit dem ersten Stage-EA. Wir möchten Sie daran erinnern, dass der Code jetzt in zwei Dateien aufgeteilt ist:
- MQL5/Experts/Article.17277/Stage1.mq5 – Datei des aktuellen Projekts zur Erforschung der SimpleCandles-Strategie;
- MQL5/Include/antekov/Advisor/Experts/Stage1.mqh – gemeinsame Bibliotheksdatei für alle Projekte.
In der aktuellen Projektdatei müssen wir Folgendes tun:
- Wir definieren die Konstante __NAME__, indem wir ihr einen eindeutigen Wert zuweisen, der sich von den Namen in anderen Projekten unterscheidet.
- Wir hängen eine Datei mit der entwickelten Handelsstrategieklasse an.
- Wir verbinden den gemeinsamen Teil mit dem ersten Stage-EA aus der Advisor-Bibliothek.
- Wir führen die Inputs für die Handelsstrategie auf.
- Wir erstellen eine Funktion namens GetStrategyParams(), die die Werte der Eingaben in einen Initialisierungsstring für das Strategieobjekt umwandelt.
// 1. Define a constant with the EA name #define __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME) // 2. Connect the required strategy #include "Strategies/SimpleCandlesStrategy.mqh"; // 3. Connect the general part of the first stage EA from the Advisor library #include <antekov/Advisor/Experts/Stage1.mqh> //+------------------------------------------------------------------+ //| 4. Strategy inputs | //+------------------------------------------------------------------+ sinput string symbol_ = "GBPUSD"; sinput ENUM_TIMEFRAMES period_ = PERIOD_H1; input group "=== Opening signal parameters" input int signalSeqLen_ = 5; // Number of unidirectional candles input int periodATR_ = 30; // ATR period input group "=== Pending order parameters" input double stopLevel_ = 3750; // Stop Loss (in points) input double takeLevel_ = 50; // Take Profit (in points) input group "=== Money management parameters" input int maxCountOfOrders_ = 3; // Maximum number of simultaneously open orders //+------------------------------------------------------------------+ //| 5. Strategy initialization string generation function | //| from the inputs | //+------------------------------------------------------------------+ string GetStrategyParams() { return StringFormat( "class CSimpleCandlesStrategy(\"%s\",%d,%d,%d,%.3f,%.3f,%d)", symbol_, period_, signalSeqLen_, periodATR_, stopLevel_, takeLevel_, maxCountOfOrders_ ); } //+------------------------------------------------------------------+
Wenn wir jedoch die EA-Datei der ersten Stufe kompilieren (die Kompilierung verläuft ohne Fehler), erhalten wir bei der Ausführung den folgenden Fehler in der Funktion OnInit(), der dazu führt, dass der EA anhält:
2018.01.01 00:00:00 CVirtualFactory::Create | ERROR: Constructor not found for: 2018.01.01 00:00:00 class CSimpleCandlesStrategy("GBPUSD",16385,5,30,2.95,3.92,3)
Der Grund dafür ist, dass wir zum Erstellen von Objekten aller von CFactorable abgeleiteten Klassen eine separate Funktion CVirtualFactory::Create() aus der Datei Virtual/VirtualFactory.mqh verwenden. Es wird in den in Base/Factorable.mqh deklarierten Makros NEW(C) und CREATE(C, O, P) aufgerufen.
Diese Funktion liest den Objektklassennamen aus dem Initialisierungsstring in die Variable className. Der Leseteil wird aus dem Initialisierungsstring entfernt. Es folgt eine einfache Iteration durch alle möglichen Klassennamen (CFactorable-Nachfahren ), bis eine Übereinstimmung mit dem gerade gelesenen Namen gefunden wird. In diesem Fall wird ein neues Objekt der gewünschten Klasse erstellt, und der Zeiger darauf wird über die Objektvariable als Ergebnis der Erstellungsfunktion zurückgegeben:
// Create an object from the initialization string static CFactorable* Create(string p_params) { // Read the object class name string className = CFactorable::ReadClassName(p_params); // Pointer to the object being created CFactorable* object = NULL; // Call the corresponding constructor depending on the class name if(className == "CVirtualAdvisor") { object = new CVirtualAdvisor(p_params); } else if(className == "CVirtualRiskManager") { object = new CVirtualRiskManager(p_params); } else if(className == "CVirtualStrategyGroup") { object = new CVirtualStrategyGroup(p_params); } else if(className == "CSimpleVolumesStrategy") { object = new CSimpleVolumesStrategy(p_params); } else if(className == "CHistoryStrategy") { object = new CHistoryStrategy(p_params); } // If the object is not created or is created in the invalid state, report an error if(!object) { ... } return object; }
Als sich unser gesamter Code in einem Ordner befand, fügten wir hier einfach zusätzliche bedingte Anweisungszweige für neue von CFactorable abgeleiteten Klassen hinzu, die wir verwendeten. So entstand zum Beispiel der Teil, der für die Erstellung der Objekte unserer ersten SimpleVolumes-Modellstrategie verantwortlich ist:
} else if(className == "CSimpleVolumesStrategy") { object = new CSimpleVolumesStrategy(p_params); }
Entsprechend dem vorherigen Ansatz sollten wir hier einen ähnlichen Block für unsere neue SimpleCandles-Modellstrategie hinzufügen:
} else if(className == "CSimpleCandlesStrategy") { object = new CSimpleCandlesStrategy(p_params); }
Dies verstößt aber bereits jetzt gegen das Prinzip der Trennung des Codes in Bibliotheks- und Projektteile. Der Bibliotheksteil des Codes braucht nicht zu wissen, welche anderen neuen Strategien bei seiner Verwendung erstellt werden. Jetzt sieht sogar die Erstellung von CSimpleVolumesStrategy auf diese Weise falsch aus.
Versuchen wir, einen Weg zu finden, der einerseits die Erstellung aller notwendigen Objekte und andererseits eine klare Trennung des Codes gewährleistet.
Verbesserung von CFactorable
Ich muss zugeben, dass diese Aufgabe nicht so einfach ist. Ich war gezwungen, intensiv über die Lösung nachzudenken und mehr als eine Umsetzungsoption auszuprobieren, bevor ich schließlich diejenige fand, die ich vorerst weiterverwenden werde. Wenn die MQL5-Sprache die Möglichkeit hätte, Code aus einer Zeichenkette in einem bereits kompilierten Programm auszuführen, dann wäre alles sehr einfach gelöst. Aber aus Sicherheitsgründen haben wir keine Funktion, die der Funktion eval() aus anderen Programmiersprachen ähnelt. Deshalb mussten wir uns mit den vorhandenen Möglichkeiten begnügen.
Im Allgemeinen ist die Idee die folgende: Jeder CFactorable-Nachkomme sollte eine statische Funktion haben, die ein Objekt der gegebenen Klasse erzeugt. Wir haben es also mit einer Art statischem Konstruktor zu tun. In diesem Fall kann der reguläre Konstruktor nicht öffentlich gemacht werden, und nur der statische Konstruktor kann zur Erstellung von Objekten verwendet werden. Als Nächstes müssen wir die Zeichenkettennamen der Klassen irgendwie mit diesen Funktionen verknüpfen, damit wir wissen, welche Konstruktorfunktion wir auf der Grundlage des Klassennamens aus der Initialisierungszeichenkette aufrufen müssen.
Um dieses Problem zu lösen, benötigen wir Funktionszeiger. Dies ist ein spezieller Variablentyp, der es uns ermöglicht, einen Zeiger auf einen Funktionscode in einer Variablen zu speichern und den Funktionscode mit diesem Zeiger aufzurufen. Wie Sie vielleicht bemerkt haben, können alle statischen Konstruktoren von Objekten verschiedener von CFactorable abstammender Klassen mit der folgenden Signatur deklariert werden:
static CFactorable* Create(string p_params)
Daher können wir ein statisches Array erstellen, in dem wir Zeiger auf solche Funktionen für alle Nachfolgeklassen ablegen. Die Klassen, die Teil der Advisor-Bibliothek sind (CVirtualAdvisor, CVirtualStrategyGroup, CVirtualRiskManager), werden diesem Array innerhalb des Bibliothekscodes hinzugefügt. Gleichzeitig werden die Handelsstrategieklassen aus dem Code im Arbeitsordner des Projekts zu diesem Array hinzugefügt. Auf diese Weise wird die gewünschte Codetrennung erreicht.
Die nächste Frage lautet: Wie erreichen wir das alles? In welcher Klasse sollte dieses statische Array deklariert werden und wie kann es wieder aufgefüllt werden? Wie kann man die Assoziation eines Klassennamens mit einem Array-Element beibehalten?
Zunächst schien es am sinnvollsten, dieses statische Array als Teil der Klasse CFactorable zu erstellen. Für die Bindung können wir ein weiteres statisches Array von Strings erstellen – Klassennamen. Wenn die Auffüllung gleichzeitig einen Klassennamen zu einem Array und einen Zeiger auf einen statischen Konstruktor von Objekten dieser Klasse zu einem anderen Array hinzufügt, erhalten wir eine Indexbeziehung zwischen den Elementen der beiden Arrays. Mit anderen Worten: Nachdem wir in einem Array den Index eines Elements gefunden haben, das dem gewünschten Klassennamen entspricht, können wir diesen Index verwenden, um den Zeiger auf eine Konstruktorfunktion aus einem anderen Array zu erhalten und sie dann unter Übergabe der Initialisierungszeichenfolge aufzurufen.
Aber wie füllen wir diese Felder? Ich wollte wirklich keine Funktionen erstellen, die von OnInit() aus aufgerufen werden müssten. Obwohl dieser Ansatz, wie sich herausstellt, durchaus praktikabel ist. Aber am Ende habe ich mich anders entschieden.
Die Grundidee war, dass wir in der Lage sein möchten, einen bestimmten Code nicht aus OnInit(), sondern direkt aus den Dateien aufzurufen, die die Klassen der CFactorable-Nachfolgeobjekte beschreiben. Wenn Sie den Code jedoch einfach außerhalb der Klassendefinition platzieren, wird er nicht ausgeführt. Wenn Sie aber eine globale Variable außerhalb der Klassendefinition deklarieren, die ein Objekt einer Klasse ist, dann wird ihr Konstruktor an dieser Stelle aufgerufen!
Daher erstellen wir eine eigene Klasse CFactorableCreator speziell für diesen Zweck erstellen. Ihre Objekte speichern den Klassennamen und einen Zeiger auf den statischen Konstruktor von Objekten der angegebenen Klasse. Diese Klasse wird auch ein statisches Array von Zeigern auf Objekte... derselben Klasse haben. Gleichzeitig sorgt der CFactorableCreator-Konstruktor dafür, dass jedes Objekt, das er erzeugt, in diesem Array landet:
// Preliminary class definition class CFactorable; // Type declaration - pointer to the function for creating objects of the CFactorable class typedef CFactorable* (*TCreateFunc)(string); //+------------------------------------------------------------------+ //| Class of creators that bind names and static | //| constructors of CFactorable descendant classes | //+------------------------------------------------------------------+ class CFactorableCreator { public: string m_className; // Class name TCreateFunc m_creator; // Static constructor for the class // Creator constructor CFactorableCreator(string p_className, TCreateFunc p_creator); // Static array of all created creator objects static CFactorableCreator* creators[]; }; // Static array of all created creator objects CFactorableCreator* CFactorableCreator::creators[]; //+------------------------------------------------------------------+ //| Creator constructor | //+------------------------------------------------------------------+ CFactorableCreator::CFactorableCreator(string p_className, TCreateFunc p_creator) : m_className(p_className), m_creator(p_creator) { // Add the current creator object to the static array APPEND(creators, &this); } //+------------------------------------------------------------------+
Schauen wir uns an, wie wir das Auffüllen des CFactorableCreator::creators-Arrays am Beispiel der Klasse CVirtualAdvisor organisieren können. Wir werden den CVirtualAdvisor-Konstruktor in den Abschnitt „protected“ übertragen und die statische Konstruktorfunktion Create() hinzufügen. Nachdem wir die Klasse beschrieben haben, erstellen wir das globale Objekt der Klasse CFactorableCreator mit dem Namen CVirtualAdvisorCreator. Genau dort, beim Aufruf des CFactorableCreator-Konstruktors, wird das Array CFactorableCreator::creators aufgefüllt.
//+------------------------------------------------------------------+ //| Class of the EA handling virtual positions (orders) | //+------------------------------------------------------------------+ class CVirtualAdvisor : public CAdvisor { protected: //... CVirtualAdvisor(string p_param); // Private constructor public: static CFactorable* Create(string p_params) { return new CVirtualAdvisor(p_params) }; //... }; CFactorableCreator CVirtualAdvisorCreator("CVirtualAdvisor", CVirtualAdvisor::Create);
Wir müssen die gleichen drei Änderungen an allen Klassen der CFactorable-Nachfolgeobjekte vornehmen. Um die Sache ein wenig zu vereinfachen, werden wir in der Datei mit der Klasse CFactorable zwei Hilfsmakros deklarieren:
// Declare a static constructor inside the class #define STATIC_CONSTRUCTOR(C) static CFactorable* Create(string p) { return new C(p); } // Add a static constructor for the new CFactorable descendant class // to a special array by creating a global object of the CFactorableCreator class #define REGISTER_FACTORABLE_CLASS(C) CFactorableCreator C##Creator(#C, C::Create);
Es wird einfach die Codevorlage wiederverwendet, die wir bereits für die Klasse CVirtualAdvisor entwickelt haben. Jetzt können wir Bearbeitungen wie diese vornehmen:
//+------------------------------------------------------------------+ //| Class of the EA handling virtual positions (orders) | //+------------------------------------------------------------------+ class CVirtualAdvisor : public CAdvisor { protected: // ... CVirtualAdvisor(string p_param); // Constructor public: STATIC_CONSTRUCTOR(CVirtualAdvisor); // ... }; REGISTER_FACTORABLE_CLASS(CVirtualAdvisor);
Ähnliche Änderungen müssen an den drei Klassendateien in der Advisor-Bibliothek (CVirtualAdvisor, CVirtualStrategyGroup, CVirtualRiskManager) vorgenommen werden, aber dies musste nur einmal geschehen. Da diese Änderungen nun in der Bibliothek enthalten sind, können wir sie vergessen.
In der/den Datei(en) der Handelsstrategieklasse(n), die sich im Arbeitsordner des Projekts befinden, sind solche Ergänzungen für jede neue Klasse obligatorisch. Fügen wir sie zu unserer neuen Strategie hinzu, dann wird der Code der Klassenbeschreibung wie folgt aussehen:
//+------------------------------------------------------------------+ //| Trading strategy using unidirectional candlesticks | //+------------------------------------------------------------------+ class CSimpleCandlesStrategy : public CVirtualStrategy { protected: string m_symbol; // Symbol (trading instrument) ENUM_TIMEFRAMES m_timeframe; // Chart period (timeframe) //--- Open signal parameters int m_signalSeqLen; // Number of unidirectional candles int m_periodATR; // ATR period //--- Position parameters double m_stopLevel; // Stop Loss (in points or % ATR) double m_takeLevel; // Take Profit (in points or % ATR) //--- Money management parameters int m_maxCountOfOrders; // Max number of simultaneously open positions CSymbolInfo *m_symbolInfo; // Object for getting information about the symbol properties double m_tp; // Stop Loss in points double m_sl; // Take Profit in points //--- Methods int SignalForOpen(); // Signal to open a position void OpenBuy(); // Open a BUY position void OpenSell(); // Open a SELL position double ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1); // Calculate the ATR value void UpdateLevels(); // Update SL and TP levels // Private constructor CSimpleCandlesStrategy(string p_params); public: // Static constructor STATIC_CONSTRUCTOR(CSimpleCandlesStrategy); virtual string operator~() override; // Convert object to string virtual void Tick() override; // OnTick event handler }; // Register the CFactorable descendant class REGISTER_FACTORABLE_CLASS(CSimpleCandlesStrategy);
Ich möchte noch einmal betonen, dass die hervorgehobenen Teile in jeder neuen Handelsstrategieklasse vorhanden sein sollten.
Alles, was bleibt, ist die Anwendung des gefüllten Arrays von Objekterzeugern in der allgemeinen Objekterzeugungsfunktion aus dem Initialisierungsstring von CVirtualFactory::Create(). Auch hier werden wir etwas ändern. Es hat sich herausgestellt, dass wir diese Funktion nicht mehr in einer separaten Klasse unterbringen müssen. Zuvor wurde dies getan, weil die Klasse CFactorable formal nicht verpflichtet ist, die Namen aller ihrer Nachkommen zu kennen. Nachdem die Änderungen bereits vorgenommen wurden, kennen wir möglicherweise nicht die Namen aller Nachkommen, aber wir können jeden von ihnen erstellen, indem wir über die Elemente eines einzigen Arrays CFactorableCreator::creators auf statische Konstruktoren zugreifen. Verschieben wir also den Code dieser Funktion in eine neue statische Methode der Klasse CFactorable::Create():
//+------------------------------------------------------------------+ //| Base class of objects created from a string | //+------------------------------------------------------------------+ class CFactorable { // ... public: // ... // Create an object from the initialization string static CFactorable* Create(string p_params); }; //+------------------------------------------------------------------+ //| Create an object from the initialization string | //+------------------------------------------------------------------+ CFactorable* CFactorable::Create(string p_params) { // Pointer to the object being created CFactorable* object = NULL; // Read the object class name string className = CFactorable::ReadClassName(p_params); // Find and call the corresponding constructor depending on the class name int i; SEARCH(CFactorableCreator::creators, className == CFactorableCreator::creators[i].m_className, i); if(i != -1) { object = CFactorableCreator::creators[i].m_creator(p_params); } // If the object is not created or is created in the invalid state, report an error if(!object) { PrintFormat(__FUNCTION__" | ERROR: Constructor not found for:\n%s", p_params); } else if(!object.IsValid()) { PrintFormat(__FUNCTION__ " | ERROR: Created object is invalid for:\n%s", p_params); delete object; // Remove the invalid object object = NULL; } return object; }
Wie Sie sehen, holen wir uns auch hier zuerst den Klassennamen aus dem Initialisierungsstring und suchen dann nach dem Index des Elements im Array der Ersteller, dessen Klassenname mit dem gewünschten übereinstimmt. Der gewünschte Index wird in die Variable i eingetragen. Wird der Index gefunden, so wird der statische Konstruktor des Objekts der gewünschten Klasse über den entsprechenden Zeiger auf die Funktion aufgerufen. In diesem Code gibt es keine Verweise mehr auf die Namen der CFactorable-Nachfolgeklassen. Die Datei, die die Klasse CVirtualFactory enthält, ist überflüssig geworden. Sie wird aus der Bibliothek ausgeschlossen.
Überprüfung der ersten Stufe EA
Kompilieren wir den EA der ersten Stufe und führen wir die Optimierung (vorerst) manuell durch. Nehmen wir zum Beispiel das Optimierungsintervall von 2018 bis einschließlich 2023, das GBPUSD-Symbol und den H4-Zeitrahmen. Die Optimierung wird erfolgreich gestartet, und nach einiger Zeit können wir die erzielten Ergebnisse betrachten:

Abb. 1. Optimierungseinstellungen und Visualisierung der Optimierungsergebnisse für den EA Stage1.mq5
Schauen wir uns ein paar einzelne Pässe an, die mehr oder weniger gut waren.


Abb. 2. Ergebnisse des Durchlaufs mit den folgenden Parametern: class CSimpleCandlesStrategy("GBPUSD",16388,4,23,2.380,4.950,19)
Bei den in Abb. 2 dargestellten Ergebnissen erfolgte die Eröffnung nach vier Kerzen in dieselbe Richtung, und das Verhältnis zwischen StopLoss- und TakeProfit-Niveau betrug ungefähr 1:2.


Abb. 3. Ergebnisse des Durchlaufs mit den folgenden Parametern: class CSimpleCandlesStrategy("GBPUSD",16388,7,9,0.090,3.840,1)
Abb. 3 zeigt die Ergebnisse eines Durchgangs, bei dem die Öffnung nach sieben Kerzen in derselben Richtung erfolgte. In diesem Fall wurden ein sehr kurzer StopLoss und ein großer TakeProfit verwendet. Dies ist deutlich auf dem Chart zu sehen, wo die überwiegende Mehrheit der Handelsgeschäfte mit einem kleinen Verlust abgeschlossen wird und nur ein Dutzend Handelsgeschäfte über 6 Jahre mit einem Gewinn, wenn auch einem großen, abgeschlossen wurden.
Auch wenn diese Handelsstrategie sehr einfach ist, können Sie versuchen, mit ihr zu arbeiten, um bessere Ergebnisse zu erzielen, nachdem Sie viele Instanzen zu einem endgültigen EA kombiniert haben.
Schlussfolgerung
Wir haben den Prozess der Verknüpfung der neuen Strategie mit dem Auto-Optimierungssystem noch nicht abgeschlossen, aber wir haben wichtige Schritte unternommen, die es uns ermöglichen werden, den eingeschlagenen Weg fortzusetzen. Erstens haben wir bereits eine neue Handelsstrategie als separate Klasse implementiert, die von CVirtualStrategy abstammt. Zweitens konnten wir ihn mit dem EA der ersten Stufe verbinden und überprüfen, ob es möglich war, den Optimierungsprozess dieses EA zu starten.
Die Optimierung einer einzelnen Instanz einer Handelsstrategie, die in der ersten Phase durchgeführt wird, beginnt, wenn die Optimierungsdatenbank noch keine Ergebnisse von Läufen enthält. Für die zweite und dritte Stufe ist es erforderlich, dass die Optimierungsergebnisse der ersten Stufe bereits in der Datenbank vorhanden sind. Daher ist es noch nicht möglich, die Strategie mit den EAs der zweiten und dritten Stufe zu verbinden und zu testen. Zunächst müssen wir ein Projekt in der Optimierungsdatenbank anlegen und es ausführen, um die Ergebnisse der ersten Phase zu sammeln. Im nächsten Teil werden wir die begonnene Arbeit fortsetzen, indem wir uns mit der Modifizierung des EA zur Projekterstellung befassen.
Vielen Dank für Ihre Aufmerksamkeit! Bis bald!
Wichtige Warnung
Alle in diesem Artikel und in allen vorangegangenen Artikeln dieser Reihe vorgestellten Ergebnisse beruhen lediglich auf historischen Testdaten und sind keine Garantie für zukünftige Gewinne. Die Arbeiten im Rahmen dieses Projekts haben Forschungscharakter. Alle veröffentlichten Ergebnisse können von jedermann auf eigenes Risiko verwendet werden.
Inhalt des Archivs
| # | Name | Version | Beschreibung | Jüngste Änderungen |
|---|---|---|---|---|
| MQL5/Experts/Article.17277 | Arbeitsordner des Projekts | |||
| 1 | CreateProject.mq5 | 1.01 | EA-Skript zur Erstellung eines Projekts mit Phasen, Aufträgen und Optimierungsaufgaben. | Teil 23 |
| 2 | Optimization.mq5 | 1.00 | EA für Projekte Auto-Optimierung | Teil 23 |
| 3 | SimpleCandles.mq5 | 1.00 | Endgültiger EA für den Parallelbetrieb mehrerer Gruppen von Modellstrategien. Die Parameter werden aus der integrierten Gruppenbibliothek übernommen. | Teil 24 |
| 4 | Stage1.mq5 | 1.22 | Handelsstrategie Einzelinstanzoptimierung EA (Phase 1) | Teil 24 |
| 5 | Stage2.mq5 | 1.00 | Handelsstrategien Instanzen Gruppe Optimierung EA (Phase 2) | Teil 23 |
| 6 | Stage3.mq5 | 1.00 | Der EA, der eine generierte standardisierte Gruppe von Strategien in einer EA-Datenbank mit einem bestimmten Namen speichert. | Teil 23 |
| MQL5/Experts/Article.17277/Strategies | Ordner Projektstrategien | |||
| 7 | SimpleCandlesStrategy.mqh | 1.01 | Teil 24 | |
| MQL5/Include/antekov/Advisor/Base | Basisklassen, von denen andere Projektklassen erben | |||
| 8 | Advisor.mqh | 1.04 | EA-Basisklasse | Teil 10 |
| 9 | Factorable.mqh | 1.05 | Basisklasse von Objekten, die aus einer Zeichenkette erstellt werden | Teil 24 |
| 10 | FactorableCreator.mqh | 1.00 | Teil 24 | |
| 11 | Interface.mqh | 1.01 | Basisklasse zur Visualisierung verschiedener Objekte | Teil 4 |
| 12 | Receiver.mqh | 1.04 | Basisklasse für die Umwandlung von offenen Volumina in Marktpositionen | Teil 12 |
| 13 | Strategy.mqh | 1.04 | Handelsstrategie-Basisklasse | Teil 10 |
| MQL5/Include/antekov/Advisor/Database | Dateien für den Umgang mit allen Arten von Datenbanken, die von Projekt-EAs verwendet werden | |||
| 14 | Database.mqh | 1.10 | Klasse für den Umgang mit der Datenbank | Teil 22 |
| 15 | db.adv.schema.sql | 1.00 | Endgültige Datenbankstruktur von EA | Teil 22 |
| 16 | db.cut.schema.sql | 1.00 | Struktur der verkürzten Optimierungsdatenbank | Teil 22 |
| 17 | db.opt.schema.sql | 1.05 | Optimierung der Datenbankstruktur | Teil 22 |
| 18 | Storage.mqh | 1.01 | Klasse zur Handhabung der Schlüssel-Wert-Speicherung für den endgültigen EA in der EA-Datenbank | Teil 23 |
| MQL5/Include/antekov/Advisor/Experts | Dateien mit gemeinsamen Teilen der verwendeten EAs verschiedener Typen | |||
| 19 | Expert.mqh | 1.22 | Die Bibliotheksdatei für den endgültigen EA. Gruppenparameter können aus der EA-Datenbank übernommen werden | Teil 23 |
| 20 | Optimization.mqh | 1.04 | Bibliotheksdatei für den EA, der den Start von Optimierungsaufgaben verwaltet | Teil 23 |
| 21 | Stage1.mqh | 1.19 | Bibliotheksdatei für die Einzelinstanz der Handelsstrategieoptimierung EA (Stage 1) | Teil 23 |
| 22 | Stage2.mqh | 1.04 | Bibliotheksdatei für den EA, der eine Gruppe von Handelsstrategieinstanzen optimiert (Stage 2) | Teil 23 |
| 23 | Stage3.mqh | 1.04 | Bibliotheksdatei für den EA, die eine generierte standardisierte Gruppe von Strategien in einer EA-Datenbank mit einem bestimmten Namen speichert. | Teil 23 |
| MQL5/Include/antekov/Advisor/Optimization | Für die automatische Optimierung zuständige Klassen | |||
| 24 | Optimizer.mqh | 1.03 | Klasse für den Projektautooptimierungsmanager | Teil 22 |
| 25 | OptimizerTask.mqh | 1.03 | Klasse der Optimierungsaufgaben | Teil 22 |
| MQL5/Include/antekov/Advisor/Strategies | Beispiele für Handelsstrategien, die die Funktionsweise des Projekts veranschaulichen | |||
| 26 | HistoryStrategy.mqh | 1.00 | Klasse der Handelsstrategie für die Wiederholung der Handelshistorie | Teil 16 |
| 27 | SimpleVolumesStrategy.mqh | 1.11 | Klasse der Handelsstrategie mit Tick-Volumen | Teil 22 |
| MQL5/Include/antekov/Advisor/Utils | Hilfsprogramme, Makros zur Code-Reduzierung | |||
| 28 | ExpertHistory.mqh | 1.00 | Klasse für den Export der Handelshistorie in eine Datei | Teil 16 |
| 29 | Macros.mqh | 1.05 | Nützliche Makros für Array-Operationen | Teil 22 |
| 30 | NewBarEvent.mqh | 1.00 | Klasse zur Definition eines neuen Balkens für ein bestimmtes Symbol | Teil 8 |
| 31 | SymbolsMonitor.mqh | 1.00 | Klasse zur Beschaffung von Informationen über Handelsinstrumente (Symbole) | Teil 21 |
| MQL5/Include/antekov/Advisor/Virtual | Klassen zur Erstellung verschiedener Objekte, die durch ein System virtueller Handelsaufträge und -positionen verbunden sind | |||
| 32 | Money.mqh | 1.01 | Basisklasse Geldmanagement | Teil 12 |
| 33 | TesterHandler.mqh | 1.07 | Klasse zur Behandlung von Optimierungsereignissen | Teil 23 |
| 34 | VirtualAdvisor.mqh | 1.10 | Klasse des EA, der virtuelle Positionen (Aufträge) bearbeitet | Teil 24 |
| 35 | VirtualChartOrder.mqh | 1.01 | Grafische virtuelle Positionsklasse | Teil 18 |
| 36 | VirtualHistoryAdvisor.mqh | 1.00 | Die Klasse des EA zur Wiederholung des Handelsverlaufs | Teil 16 |
| 37 | VirtualInterface.mqh | 1.00 | EA GUI-Klasse | Teil 4 |
| 38 | VirtualOrder.mqh | 1.09 | Klasse der virtuellen Aufträge und Positionen | Teil 22 |
| 39 | VirtualReceiver.mqh | 1.04 | Klasse für die Umwandlung von offenen Volumina in Marktpositionen (Empfänger) | Teil 23 |
| 40 | VirtualRiskManager.mqh | 1.05 | Klasse Risikomanagement (Risikomanager) | Teil 24 |
| 41 | VirtualStrategy.mqh | 1.09 | Klasse einer Handelsstrategie mit virtuellen Positionen | Teil 23 |
| 42 | VirtualStrategyGroup.mqh | 1.03 | Klasse der Handelsstrategien Gruppe(n) | Teil 24 |
| 43 | VirtualSymbolReceiver.mqh | 1.00 | Symbol-Empfängerklasse | Teil 3 |
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/17277
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Fibonacci am Devisenmarkt (Teil I): Prüfung des Verhältnisses zwischen Preis und Zeit
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Neuronale Netze im Handel: Multi-Task-Lernen auf der Grundlage des ResNeXt-Modells (letzter Teil)
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.