English Русский Español 日本語 Português
preview
Entwicklung eines Replay System (Teil 32): Auftragssystem (I)

Entwicklung eines Replay System (Teil 32): Auftragssystem (I)

MetaTrader 5Beispiele | 21 März 2024, 15:59
135 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel Entwicklung eines Replay System (Teil 31): Expert Advisor Projekt — Die Klasse C_Mouse (V), haben wir den Basisteil für die Verwendung der Maus im Replay/Simulationssystem entwickelt. Wir haben das Problem mit dem Mausrad nicht gezeigt, weil ich anfangs nicht die Notwendigkeit sah, diese Funktion zu nutzen. Jetzt können wir uns an den anderen Teil machen, der zweifellos viel schwieriger ist. Was wir zu implementieren haben, sowohl im Code als auch in anderen damit zusammenhängenden Dingen, ist zweifellos das Schwierigste am gesamten Replay-/Modeling-System. Ohne diesen Teil ist es unmöglich, eine praktische und einfache Analyse durchzuführen. Wir sprechen über das Auftragssystem.

Von allen Dingen, die wir bisher entwickelt haben, ist dieses System, wie Sie wahrscheinlich bemerken und letztendlich zustimmen werden, das komplexeste. Nun müssen wir etwas sehr Einfaches tun: unser System soll den Betrieb eines Handelsservers simulieren. Die Notwendigkeit, die Funktionsweise des Handelsservers genau zu implementieren, scheint eine Selbstverständlichkeit zu sein. Zumindest in Worten. Aber wir müssen dies so tun, dass alles nahtlos und transparent für den Nutzer des Wiedergabe-/Simulationssystems ist. Bei der Nutzung des Systems kann der Nutzer nicht zwischen dem echten und dem simulierten System unterscheiden. Aber das ist nicht der schwierigste Teil. Das Schwierigste ist, den EA in jeder Situation gleich sein zu lassen.

Zu sagen, dass der EA derselbe sein muss, bedeutet, dass man ihn nicht für die Verwendung in einem Tester kompiliert und ihn dann für die Verwendung auf dem realen Markt erneut kompiliert. Dies wäre vom Standpunkt der Programmierung aus gesehen viel einfacher und leichter, würde aber Probleme für den Nutzer mit sich bringen, da der EA ständig neu kompiliert werden müsste. Um also eine optimale Nutzererfahrung zu gewährleisten, müssen wir einen EA erstellen, der nur eine Kompilierung erfordert. Sobald dies geschehen ist, kann es sowohl auf dem realen Markt als auch im Testgerät verwendet werden.

Der kritische Teil der Verwendung des EA auf dem realen Markt, selbst auf einem Demokonto, ist am einfachsten zu implementieren. Also fangen wir dort an. Bitte beachten Sie, dass wir mit einem sehr einfachen Modell beginnen werden. Wir werden die Komplexität des EA schrittweise erhöhen und seine Fähigkeiten verbessern, um schließlich einen EA zu schaffen, der sowohl für Replay/Simulation als auch für ein Demo- oder Live-Konto verwendet werden kann. Als Erstes werden wir uns den Großteil des Codes ausborgen, der bereits in anderen Artikeln hier in der Community erklärt wurde. Diese Artikel gehören mir, daher sehe ich kein Problem darin, diese Informationen zu verwenden. Wir werden einige Änderungen vornehmen, um das System flexibel, modular, zuverlässig und robust zu machen. Andernfalls könnten wir uns irgendwann in einer Sackgasse befinden, die eine Weiterentwicklung des Systems in jeder Hinsicht unmöglich macht.

Dies ist nur der Anfang, denn die Aufgabe ist sehr komplex. Zunächst einmal müssen wir wissen, wie das System der Klassenvererbung derzeit im EA implementiert ist. Wir haben dies bereits in anderen Artikeln dieser Reihe gesehen. Das aktuelle Vererbungsdiagramm ist in Abbildung 01 dargestellt:

Abbildung 01

Abbildung 01: Aktuelles EA-Klassenvererbungsdiagramm

Obwohl dieses Schema für das, was bisher gemacht wurde, sehr gut funktioniert, ist es noch weit von dem entfernt, was wir wirklich brauchen. Dies liegt nicht daran, dass es schwierig wäre, die Klasse C_Orders in dieses Vererbungsschema aufzunehmen. Dies kann erreicht werden, indem die Klasse C_Orders von der Klasse C_Study abgeleitet wird. Aber ich will das nicht tun. Der Grund liegt in einem sehr praktischen Problem, das von den meisten Programmierern, die mit OOP (Object Oriented Programming) arbeiten, manchmal ignoriert wird. Dieses Problem wird als Verkapselung bezeichnet. Das heißt, Sie sollten nur das wissen, was Sie für Ihre Arbeit brauchen. Wenn wir eine Hierarchie von Klassen erstellen, sollten wir nicht zulassen, dass einige Klassen mehr wissen, als sie wirklich wissen müssen. Wir sollten immer die Art der Programmierung bevorzugen, bei der jede Klasse nur das weiß, was sie wirklich wissen muss, um ihre Aufgabe zu erfüllen. Daher ist die Beibehaltung der Kapselung beim Hinzufügen der Klasse C_Orders zu dem in Abbildung 01 dargestellten Diagramm praktisch unmöglich. Daher besteht die beste Lösung für dieses Problem darin, die Klasse C_Terminal aus dem Vererbungsblock zu entfernen, wie in Abbildung 01 gezeigt, und sie als Argument oder Parameter in denselben Block zu übergeben, der auf eine viel geeignetere Weise verwendet werden kann. Die Kontrolle darüber, wer welche Informationen erhält, wird also durch den EA-Code ausgeübt, was zur Aufrechterhaltung der Informationskapselung beitragen wird.

Das neue Klassendiagramm, das wir in diesem Artikel verklagen werden, ist in Abbildung 02 dargestellt.

Abbildung 02

Abbildung 02: Neues Vererbungsdiagramm

In dem neuen Diagramm werden einzelne Klassen nur dann zugänglich, wenn der EA-Code dies zulässt. Wie Sie vielleicht schon erraten haben, müssen wir kleine Änderungen am bestehenden Code vornehmen. Aber diese Änderungen werden das gesamte System nicht wesentlich beeinflussen. Wir können diese Änderungen schnell berücksichtigen, um zu sehen, was neu ist.


Vorbereitung des Bodens

Als Erstes müssen wir eine Enumeration in der Klasse C_Terminal erstellen. Und zwar so:

class C_Terminal
{

       protected:
      enum eErrUser {ERR_Unknown, ERR_PointerInvalid};

// ... Internal code ...

};

Diese Enumeration ermöglicht es uns, die Variable _LastError zu verwenden, um uns zu benachrichtigen, wenn aus irgendeinem Grund ein Fehler im System auftritt. Im Folgenden werden nur diese beiden Arten von Fehlern definiert.

An dieser Stelle werden wir die Klasse C_Mouse ändern. Ich werde nicht ins Detail gehen, da die Änderungen keine Auswirkungen auf die Funktionsweise der Klasse haben. Sie leiten den Nachrichtenfluss einfach etwas anders als bei der Verwendung eines Vererbungssystems. Die Änderungen sind nachstehend aufgeführt:

#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Mouse : public C_Terminal
{
   protected:
//+------------------------------------------------------------------+
// ... Internal fragment ....
//+------------------------------------------------------------------+
   private :
//+------------------------------------------------------------------+
// ... Internal fragment ...
      C_Terminal *Terminal;
//+------------------------------------------------------------------+

Um die ständige Wiederholung von Code zu vermeiden, haben wir zwei neue Definitionen hinzugefügt. Dies ermöglicht umfangreiche Konfigurationsmöglichkeiten. Außerdem wurde eine private globale Variable hinzugefügt, die den korrekten Zugriff auf die Klasse C_Terminal ermöglicht. Wie im obigen Code zu sehen ist, wird auch die Vererbung der Klasse C_Terminal nicht mehr verwendet.

Da wir die Vererbung nicht verwenden, müssen zwei weitere Änderungen diskutiert werden. Die erste befindet sich im Konstruktor der Klasse C_Mouse:

C_Mouse(C_Terminal *arg, color corH, color corP, color corN)
	:C_Terminal()
   {
      Terminal = arg;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      ZeroMemory(m_Info);
      m_Info.corLineH  = corH;
      m_Info.corTrendP = corP;
      m_Info.corTrendN = corN;
      m_Info.Study = eStudyNull;
      m_Mem.CrossHair = (bool)ChartGetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL);
      ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
      ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, false);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
   }

Hier wird der Aufruf des Konstruktors der Klasse C_Terminal aus dem Konstruktor der Klasse C_Mouse entfernt. Jetzt müssen wir einen neuen Parameter erhalten, um den Zeiger auf die Klasse zu initialisieren. Da wir nicht wollen, dass unser Code in einer unpassenden Situation abbricht, führen wir aus Sicherheitsgründen einen Test durch, um zu überprüfen, ob der Zeiger, der uns die Verwendung der Klasse C_Terminal ermöglicht, korrekt initialisiert wurde. 

Dazu verwenden wir die Funktion CheckPointer, die jedoch wie der Konstruktor keine Rückgabe von Fehlerinformationen zulässt. Wir werden die Fehlerbedingung mit einem vordefinierten Wert in der Enumeration angeben, die in der Klasse C_Terminal vorhanden ist. Da wir jedoch den Wert der Variablen _LastError nicht direkt ändern können, müssen wir den Aufruf SetUserError verwenden. Danach können wir _LastError überprüfen, um herauszufinden, was passiert ist.

Wir müssen jedoch vorsichtig sein, wenn die Klasse C_Terminal nicht korrekt initialisiert wurde: Der Konstruktor der Klasse C_Mouse kehrt zurück, ohne etwas zu tun, da er die nicht initialisierte Klasse C_Terminal nicht verwenden kann.

Eine weitere Änderung bezieht sich auf die folgende Funktion:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      int w = 0;
      static double memPrice = 0;
                                
      C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
      switch (id)
      {
//....

Der angegebene Code muss dem EA hinzugefügt werden, um die von der MetaTrader 5-Plattform gemeldeten Ereignisse zu verarbeiten. Wie Sie sehen, treten sonst bei einigen Ereignissen Probleme auf, die zu einer Verletzung der Position des Elements im Chart führen können. Für den Moment werden wir nur den Code an dieser Stelle entfernen. Wir können sogar der Klasse C_Mouse erlauben, das Nachrichtensystem der Klasse C_Terminal aufzurufen. Da wir aber keine Vererbung verwenden, wird der Code dadurch eine eher ungewöhnliche Abhängigkeit aufweisen.

So wie wir es in der Klasse C_Mouse gemacht haben, werden wir es auch in der Klasse C_Study machen. Achten Sie auf den Klassenkonstruktor, der unten zu sehen ist:

C_Study(C_Terminal *arg, color corH, color corP, color corN)
        :C_Mouse(arg, corH, corP, corN)
   {
      Terminal = arg;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise);
      CreateObjectInfo(2, 53, def_ExpansionBtn2);
      CreateObjectInfo(58, 53, def_ExpansionBtn3);
   }

Wir nehmen einen Parameter, der auf den Zeiger der Klasse C_Terminal zeigt, und übergeben ihn an die Klasse C_Mouse. Da wir sie geerbt haben, müssen wir sie korrekt initialisieren, aber wie auch immer, wir werden die gleichen Prüfungen durchführen, die wir im Konstruktor der Klasse C_Mouse durchgeführt haben, um sicherzustellen, dass wir den richtigen Zeiger verwenden. Jetzt müssen wir auf eine Sache achten: In beiden Konstruktoren von C_Mouse und C_Study überprüfen wir den Wert von _LastError, um zu wissen, ob etwas nicht wie erwartet ist. Je nach verwendetem Handelsinstrument muss die Klasse C_Terminal jedoch möglicherweise ihren Namen initialisieren, damit der EA weiß, welcher Vermögenswert sich derzeit im Chart befindet.

Wenn dies zufällig geschieht, enthält die Variable _LastError den Wert 4301 (ERR_MARKET_UNKNOWN_SYMBOL), was bedeutet, dass das Asset nicht korrekt erkannt wurde. Dies ist jedoch nicht der Fall, da die Klasse C_Terminal in ihrem derzeitigen programmierten Zustand auf das gewünschte Asset zugreifen kann. Um zu vermeiden, dass EA aufgrund dieses Fehlers aus dem Chart entfernt wird, müssen Sie eine kleine Änderung am Konstruktor der Klasse C_Terminal vornehmen. Und zwar so:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      CurrentSymbol();
      m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
      m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
      m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
      m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
      m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
      m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
      m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
      m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
      m_Infos.AdjustToTrade = m_Infos.PointPerTick / m_Infos.ValuePerPoint;
      ResetLastError();
   }

Durch Hinzufügen dieses Codes wird angezeigt, dass kein Anfangsfehler vorliegt. Daher wird das Konstruktorsystem zur Initialisierung von Klassen im EA-Code verwendet. Wir müssen diese Zeile nicht hinzufügen. Denn in manchen Fällen kann es vorkommen, dass wir diesen Zusatz vergessen oder, schlimmer noch, ihn zum falschen Zeitpunkt vornehmen, wodurch der Code völlig instabil und unsicher wird.


Die Klasse C_Orders

Was wir bis jetzt gesehen haben, wird uns helfen, die Voraussetzungen für den nächsten Schritt zu schaffen. Wir müssen noch einige Änderungen an der Klasse C_Terminal vornehmen. Wir werden einige dieser Änderungen später in diesem Artikel vornehmen. Fahren wir mit der Erstellung der Klasse C_Orders fort, die die Interaktion mit dem Handelsserver ermöglichen wird. In diesem Fall handelt es sich um einen echten Server, zu dem der Zugang über einen Broker erfolgt. Sie können jedoch ein Demokonto verwenden, um das System zu testen. Eigentlich ist es nicht ratsam, das System direkt auf einem echten Konto zu verwenden

Der Code für diese Klasse beginnt wie folgt:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "..\C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Orders
{

Um die Codierung zu erleichtern, werden wir hier mehrere Dinge definieren, um auf die Klasse C_Terminal zuzugreifen. Diese Definitionen befinden sich nun nicht mehr am Ende der Klassendatei, sondern innerhalb des Klassencodes. Dies ist der Weg, um auf die Klasse C_Terminal zuzugreifen. Wenn wir nun in der Zukunft Änderungen vornehmen, müssen wir nicht den Klassencode, sondern nur diese Definition ändern. Beachten Sie, dass die Klasse nicht abgeleitet wird. Es ist wichtig, dies im Hinterkopf zu behalten, damit Sie beim Programmieren dieser Klasse und beim Schreiben anderer Klassen, die später erscheinen werden, nicht durcheinander kommen.

Als Nächstes deklarieren wir die ersten globalen und internen Klassenvariablen. Und zwar so:

   private :
//+------------------------------------------------------------------+
      MqlTradeRequest m_TradeRequest;
      ulong           m_MagicNumber;
      C_Terminal      *Terminal;

Beachten Sie, dass diese globalen Variablen als privat deklariert sind, d.h. auf sie kann außerhalb des Klassencodes nicht zugegriffen werden. Achten Sie darauf, wie die Variable deklariert ist, die den Zugriff auf die Klasse C_Terminal ermöglicht. Er ist eigentlich als Zeiger deklariert, obwohl die Verwendung von Zeigern in MQL5 anders ist als in C/C++.

ulong ToServer(void)
   {
      MqlTradeCheckResult     TradeCheck;
      MqlTradeResult          TradeResult;
      bool bTmp;
                                
      ResetLastError();
      ZeroMemory(TradeCheck);
      ZeroMemory(TradeResult);
      bTmp = OrderCheck(m_TradeRequest, TradeCheck);
      if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
      if (_LastError != ERR_SUCCESS) PrintFormat("Order System - Error Number: %d", _LastError);
      return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
   }

Die obige Funktion, die privat sein wird, dient der „Zentralisierung“ von Anrufen. Ich habe mich für die Zentralisierung der Anrufe entschieden, weil es in Zukunft einfacher sein wird, das System anzupassen. Dies ist notwendig, um dasselbe Diagramm sowohl mit einem realen Server als auch mit einem simulierten Server verwenden zu können. Die frühere Funktion wurde zusammen mit anderen, die wir in dem Artikel besprochen haben, entfernt: Erstellen eines automatisch arbeitenden EA (Teil 15): Automatisierung (VII). In diesem Artikel wurde erklärt, wie man einen automatisierten EA aus einem manuellen EA erstellt. Wir werden ein paar Funktionen aus diesem Artikel verwenden, um unsere Arbeit hier ein wenig zu beschleunigen. Auf diese Weise können wir, wenn wir die gleichen Konzepte verwenden wollen, einen automatisierten EA mit dem Replay/Simulationssystem testen, ohne den MetaTrader 5 Strategietester verwenden zu müssen.

Im Grunde prüft die obige Funktion mehrere Dinge auf dem Server des Brokers. Wenn alles in Ordnung ist, sendet er eine Anfrage an den Handelsserver, um die Anfrage des Nutzers oder des EAs (da wir in einem automatisierten Modus arbeiten können) zu erfüllen.

inline void CommonData(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      double Desloc;
                                
      ZeroMemory(m_TradeRequest);
      m_TradeRequest.magic        = m_MagicNumber;
      m_TradeRequest.symbol       = def_InfoTerminal.szSymbol;
      m_TradeRequest.volume       = NormalizeDouble(def_InfoTerminal.VolumeMinimal + (def_InfoTerminal.VolumeMinimal * (Leverage - 1)), def_InfoTerminal.nDigits);
      m_TradeRequest.price        = NormalizeDouble(Price, def_InfoTerminal.nDigits);
      Desloc = def_AcessTerminal.FinanceToPoints(FinanceStop, Leverage);
      m_TradeRequest.sl           = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), def_InfoTerminal.nDigits);
      Desloc = def_AcessTerminal.FinanceToPoints(FinanceTake, Leverage);
      m_TradeRequest.tp           = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), def_InfoTerminal.nDigits);
      m_TradeRequest.type_time    = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
      m_TradeRequest.stoplimit    = 0;
      m_TradeRequest.expiration   = 0;
      m_TradeRequest.type_filling = ORDER_FILLING_RETURN;
      m_TradeRequest.deviation    = 1000;
      m_TradeRequest.comment      = "Order Generated by Experts Advisor.";
   }

Die obige Funktion wurde ebenfalls aus der gleichen Artikelserie übernommen. In diesem Fall mussten wir sie jedoch an das neue System anpassen, das gerade eingeführt wird. Das Funktionsprinzip ist grundsätzlich dasselbe wie bei der Automatisierungsserie. Aber für diejenigen, die diese Serie nicht gelesen haben, sollten wir uns diese Funktion kurz ansehen. Erstens verfügt sie über einen Code, der finanzielle Werte in Punkte umrechnet. Dies geschieht, damit wir als Nutzer uns nicht um die Einstellung der Anzahl der Punkte für einen bestimmten Hebel kümmern müssen. So müssen wir uns nicht mehr mit den Finanzen befassen, als wir wollen. Die manuelle Durchführung dieses Vorgangs kann zu Fehlern und Ausfällen führen, aber mit dieser Funktion werden die Werte ganz einfach umgewandelt. Die Funktion funktioniert unabhängig von der Anlage. Unabhängig vom verwendeten Asset-Typ wird die Konvertierung immer korrekt und effizient durchgeführt.

Schauen wir uns nun die nächste Funktion an, die sich in der Klasse C_Terminal befindet. Der Code ist unten zu sehen:

inline double FinanceToPoints(const double Finance, const uint Leverage)
   {
      double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
                                
      return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
   };

Das Hauptgeheimnis dieser Funktion liegt in dem Wert, der wie im folgenden Fragment gezeigt berechnet wird:

m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;

Alle oben genannten Werte, die in FinanceToPoints verwendet werden, hängen von dem Vermögenswert ab, den wir verwalten und für den Handel verwenden. Wenn FinanceToPoints also die Konvertierung vornimmt, passt es sich tatsächlich an das Asset an, das wir im Chart verwenden. Daher ist es dem EA egal, mit welchem Vermögenswert und auf welchem Markt er gestartet wurde. Ebenso kann es mit jedem Nutzer funktionieren. Nachdem wir nun den privaten Teil der Klasse gesehen haben, wollen wir uns den öffentlichen Teil ansehen. Wir beginnen mit dem Konstruktor:

C_Orders(C_Terminal *arg, const ulong magic)
         :m_MagicNumber(magic)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   }

Wir werden auf einfache und effektive Weise sicherstellen, dass der Klassenkonstruktor Zugriff auf die Klasse C_Terminal hat. Beachten Sie, wie dies tatsächlich geschieht: Wenn der EA ein C_Terminal-Objekt für die zu verwendende Klasse erstellt, erstellt er auch ein Objekt, das an alle anderen Klassen weitergegeben wird, die dieses Objekt benötigen. Dies geschieht folgendermaßen: Die Klasse erhält einen vom EA erstellten Zeiger, um Zugriff auf die bereits initialisierte Klasse zu haben. Anschließend speichern wir diesen Wert in unserer privaten globalen Variablen, sodass wir bei Bedarf auf alle Daten oder Funktionen der Klasse C_Terminal zugreifen können. Wenn ein solches Objekt, in diesem Fall eine Klasse, nicht auf etwas Nützliches verweist, wird es als Fehler gemeldet. Da der Konstruktor keinen Wert zurückgeben kann, verwenden wir diese Methode, die einen geeigneten Wert für die Variable _LastError setzt. So können wir den Grund erkennen.

Kommen wir nun zu den letzten beiden Funktionen, die die Klasse in diesem Entwicklungsstadium aufweist. Die erste ist unten abgebildet:

ulong ToMarket(const ENUM_ORDER_TYPE type, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      CommonData(type, SymbolInfoDouble(def_InfoTerminal.szSymbol, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID)), FinanceStop, FinanceTake, Leverage, IsDayTrade);
      m_TradeRequest.action   = TRADE_ACTION_DEAL;
      m_TradeRequest.type     = type;

      return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
   };

Diese Funktion hat die Aufgabe, einen Auftrag auf Ausführung zum Marktpreis zu senden. Hier verwenden wir praktisch den gesamten Code, den wir zuvor betrachtet haben. Dies ist ein guter Fall für die Wiederverwendung. Eine solche Wiederverwendung erhöht die Sicherheit und die Leistung im Laufe der Zeit. Durch die Verbesserung eines beliebigen Teils des wiederverwendeten Systems wird der gesamte Code verbessert. Beachten Sie die folgenden Entgleisungen in Bezug auf den obigen Code:

  • Zunächst werden wir die Stop-Levels (Take-Profit und Stop-Loss) als finanzielle Werte und nicht als Punkte angeben.
  • Zweitens weisen wir den Server an, den Auftrag sofort auszuführen, und zwar zum bestmöglichen Preis, der zum Zeitpunkt der Ausführung des Auftrags verfügbar ist.
  • Drittens: Obwohl wir Zugang zu mehr Auftragsarten haben, können wir hier nur diese beiden Arten verwenden und angeben, ob wir kaufen oder verkaufen wollen. Ohne diese Angabe wird der Auftrag nicht abgeschickt.

Diese Details sind wichtig. Wir sollten uns um sie kümmern, sonst können wir dieses System nicht nutzen. Wenn Sie diese Punkte nicht kennen oder ignorieren, werden Sie in der nächsten Entwicklungsphase eine Menge Kopfschmerzen und Zweifel haben.

Hier kommen die letzten Funktionen der Klasse C_Orders. Im Folgenden wird der aktuelle Stand der Entwicklung dargestellt:

ulong CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      double  bid, ask;
                                
      bid = SymbolInfoDouble(def_InfoTerminal.szSymbol, (def_InfoTerminal.ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : SYMBOL_BID));
      ask = (def_InfoTerminal.ChartMode == SYMBOL_CHART_MODE_LAST ? bid : SymbolInfoDouble(def_InfoTerminal.szSymbol, SYMBOL_ASK));
      CommonData(type, def_AcessTerminal.AdjustPrice(Price), FinanceStop, FinanceTake, Leverage, IsDayTrade);
      m_TradeRequest.action   = TRADE_ACTION_PENDING;
      m_TradeRequest.type     = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
							  (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));                                
                                
      return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
   };

Hier sind einige Dinge, die der Marktausführungsfunktion sehr ähnlich sind. Zum Beispiel die Tatsache, dass die Stop-Levels in finanziellen Werten festgelegt sind und dass wir angeben müssen, ob wir nur mit einem dieser beiden Werte kaufen oder verkaufen wollen. Es gibt jedoch etwas, das sich von der großen Mehrheit der Codes im EA unterscheidet. Wenn wir einen Expert Advisor erstellen, ist er in der Regel für einen ganz bestimmten Markt gedacht, sei es der Devisenmarkt oder die Börse. Da MetaTrader 5 beide Arten von Märkten unterstützt, müssen wir einige Standardisierungen vornehmen, um uns das Leben zu erleichtern. Wäre es nicht dasselbe, auf dem Forex und an der Börse zu arbeiten? Aus der Sicht des Nutzers JA, aber aus der Sicht der Programmierung NEIN. Wenn Sie genau hinsehen, können Sie erkennen, dass wir prüfen, welcher Charttyp gerade verwendet wird. Daraus können wir schließen, ob das System mit dem letzten Kurs oder mit Geld- und Briefkursen arbeitet. Dies zu wissen ist nicht wichtig, um einen Auftrag aufzugeben, sondern um zu wissen, welche Art von Auftrag zu verwenden ist. Später werden wir solche Aufträge in das System implementieren müssen, um den Betrieb eines Handelsservers zu simulieren. Zu diesem Zeitpunkt müssen wir jedoch nur wissen, dass die Art des Auftrags ebenso wichtig ist wie der Preis, zu dem er ausgeführt wird. Wenn wir den Preis an die richtige Stelle setzen, aber die Auftragsart falsch angeben, dann gibt es ein Problem, weil der Auftrag zu einem anderen Zeitpunkt ausgeführt wird, als Sie erwartet haben, dass er vom Server ausgeführt wird.

Sehr oft machen die unerfahrenen Nutzer von MetaTrader 5 Fehler beim Ausfüllen von schwebende Aufträgen. Nicht auf jedem Markt, denn mit der Zeit gewöhnen sich die Nutzer an den Markt und machen nicht mehr so leicht Fehler. Wenn wir jedoch von einem Markt zum anderen wechseln, werden die Dinge komplizierter. Wenn das Chart-System auf BID-ASK basiert, unterscheidet sich die Methode zur Einstellung der Auftragsart von der des LAST-basierten Chart-Systems. Die Unterschiede sind subtil, aber sie sind vorhanden und führen dazu, dass der Auftrag nicht schwebend bleibt, sondern zum Marktpreis ausgeführt wird.


Schlussfolgerung

Trotz des besprochenen Materials wird diesem Artikel kein Code beigefügt. Der Grund dafür ist, dass wir kein Auftragssystem implementieren, sondern lediglich eine BASIC-Klasse zur Implementierung eines solchen Systems erstellen. Sie haben vielleicht bemerkt, dass im Vergleich zum hier gezeigten Code der Klasse C_Orders mehrere Funktionen und Methoden fehlen. Ich meine, im Vergleich zu den Codes, die in früheren Artikeln betrachtet wurden, in denen wir das Auftragssystem erörtert haben.

Die Tatsache, dass dies geschieht, ist auf meine Entscheidung zurückzuführen, dieses Auftragssystem in mehrere Teile aufzuteilen, einige größere und einige kleinere. Dies wird mir helfen, klar und einfach zu erklären, wie das System in den Replay-/Simulationsdienst integriert werden soll. Glauben Sie mir, das ist nicht die einfachste Aufgabe, im Gegenteil, sie ist ziemlich komplex und umfasst viele Konzepte, die Ihnen vielleicht noch nicht vertraut sind. Deshalb muss ich die Erklärungen nach und nach präsentieren, damit die Artikel verständlich sind und ihr Inhalt nicht zu einem völligen Chaos wird.

Im nächsten Artikel werden wir uns ansehen, wie man dieses Auftragssystem dazu bringt, mit dem Handelsserver zu interagieren. Zumindest auf physischer Ebene, damit wir den EA auf einem Demo- oder Echtgeldkonto verwenden können. Dort werden wir beginnen zu verstehen, wie Auftragsarten funktionieren, damit wir mit dem simulierten System beginnen können. Wenn wir das Gegenteil tun oder das simulierte und das reale System zusammenbringen, führt das zu einer totalen Verwirrung. Wir sehen uns im nächsten Artikel!


Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/11393

Beigefügte Dateien |
Anexo.zip (130.63 KB)
Neuronale Netze leicht gemacht (Teil 62): Verwendung des Entscheidungs-Transformer in hierarchischen Modellen Neuronale Netze leicht gemacht (Teil 62): Verwendung des Entscheidungs-Transformer in hierarchischen Modellen
In den letzten Artikeln haben wir verschiedene Optionen für die Verwendung der Entscheidungs-Transformer-Methode gesehen. Die Methode erlaubt es, nicht nur den aktuellen Zustand zu analysieren, sondern auch die Trajektorie früherer Zustände und die darin durchgeführten Aktionen. In diesem Artikel werden wir uns auf die Anwendung dieser Methode in hierarchischen Modellen konzentrieren.
Neuronale Netze leicht gemacht (Teil 61): Optimismusproblem beim Offline-Verstärkungslernen Neuronale Netze leicht gemacht (Teil 61): Optimismusproblem beim Offline-Verstärkungslernen
Während des Offline-Lernens optimieren wir die Strategie des Agenten auf der Grundlage der Trainingsdaten. Die daraus resultierende Strategie gibt dem Agenten Vertrauen in sein Handeln. Ein solcher Optimismus ist jedoch nicht immer gerechtfertigt und kann zu erhöhten Risiken während des Modellbetriebs führen. Heute werden wir uns mit einer der Methoden zur Verringerung dieser Risiken befassen.
Algorithmen zur Optimierung mit Populationen: Der Algorithmus Charged System Search (CSS) Algorithmen zur Optimierung mit Populationen: Der Algorithmus Charged System Search (CSS)
In diesem Artikel werden wir einen weiteren Optimierungsalgorithmus betrachten, der von der unbelebten Natur inspiriert ist - den CSS-Algorithmus (Charged System Search, Suche geladener Systeme). In diesem Artikel wird ein neuer Optimierungsalgorithmus vorgestellt, der auf den Prinzipien der Physik und Mechanik beruht.
Entwicklung eines Replay System (Teil 31): Expert Advisor Projekt — Die Klasse C_Mouse (V) Entwicklung eines Replay System (Teil 31): Expert Advisor Projekt — Die Klasse C_Mouse (V)
Wir brauchen einen Timer, der anzeigt, wie viel Zeit bis zum Ende der Wiedergabe/Simulation verbleibt. Dies mag auf den ersten Blick eine einfache und schnelle Lösung sein. Viele versuchen einfach, sich anzupassen und das gleiche System zu verwenden, das der Handelsserver verwendet. Aber es gibt eine Sache, die viele Leute nicht bedenken, wenn sie über diese Lösung nachdenken: Bei der Wiederholung und noch mehr bei der Simulation funktioniert die Uhr anders. All dies erschwert die Schaffung eines solchen Systems.