English Русский Español 日本語 Português
preview
Developing a Replay System (Part 36): Making Adjustments (II)

Developing a Replay System (Part 36): Making Adjustments (II)

MetaTrader 5Beispiele | 15 Mai 2024, 15:52
67 0
Daniel Jose
Daniel Jose

Einführung

Im vorigen Artikel „Entwicklung eines Replay Systems (Teil 35): Anpassungen vornehmen (I)“ haben wir einen EA entwickelt, der den Market Replay Service einfach verwalten kann. Die in diesem Artikel beschriebenen Korrekturen haben zwar die Nutzerfreundlichkeit verbessert, aber wir haben noch nicht alles, was mit dem Replay-/Simulationssystem zusammenhängt, komplett neu gestaltet.

Trotz der verbesserten Nutzerfreundlichkeit haben wir immer noch ein kleines Problem. In diesem Artikel werden wir dieses Problem lösen, das sich trotz seiner relativen Einfachheit (in der Theorie) als ziemlich schwierig erweist, um es korrekt zu lösen. So werden wir unser Wissen über MetaTrader 5 erweitern und lernen, wie man die Sprache MQL5 im Detail verwendet.


Anzeige des Markttyps

Wenn sich der EA auf dem Chart befindet, wird er über die Art des erkannten Kontos informieren. Dies ist wichtig, um zu wissen, wie der EA handeln sollte. Dies funktioniert zwar sehr gut, aber wenn das System auf einem Chart eines REAL- oder DEMO-Kontos ausgeführt wird, verwendet das System nicht das, was das Replay-/Simulationssystem steuert, und meldet nicht die Art des Kontos, zu dem der Vermögenswert gehört, sondern die Art des Kontos, auf dem die Plattform arbeitet. Dieses Problem ist zwar geringfügig, bereitet uns aber einige Unannehmlichkeiten.

Man könnte meinen, dass die Lösung für dieses Problem nicht komplex oder gar schwierig ist, sondern ganz einfach. Stellen wir sicher, dass das Replay-/Simulationssystem Ihnen irgendwie mitteilt, welcher Kontotyp der richtige ist. Dies hängt natürlich von der verwendeten Anlage ab. Die Idee ist im Grunde ganz einfach. Aber lassen Sie uns das in die Praxis umsetzen - das wird eine andere Geschichte sein. Die Wahrheit ist, dass es nicht so einfach ist, das Replay-/Simulationssystem dazu zu bringen, uns mitzuteilen, welchen Kontotyp wir verwenden sollen. Aber glücklicherweise bietet uns die MetaTrader 5 Plattform die Möglichkeit, eine Lösung zu implementieren, die für den tatsächlichen Einsatz angemessen und plausibel ist.

Wir werden dies jedoch nicht auf rücksichtslose Weise tun. Wir werden die Lösung auf eine bestimmte Art und Weise implementieren, die bestimmte Dinge bei bestimmten Kontotypen verhindern wird. Diese Informationen sind für uns wichtig, wenn wir ein Auftragssystem erstellen. Lassen Sie uns zunächst einmal darüber nachdenken, worüber wir hier sprechen. Das Replay-/Simulationssystem wird in der Lage sein, Vermögenswerte aus verschiedenen Märkten zu verwenden. Das bedeutet, dass wir Vermögenswerte verwenden können, die NETTING oder HEDGING Kontotypen implizieren.

Hinweis. Da der Typ EXCHANGE dem Typ NETTING sehr ähnlich ist, werden wir diesen Typ EXCHANGE nicht wirklich verwenden. Wir werden es als NETTING betrachten.

Als Nutzer des Replay-/Simulationssystems wissen Sie, welche Art von Markt, oder genauer gesagt, welche Art von Konto, der Vermögenswert verwendet. Wir können dann die Möglichkeit hinzufügen, dass der Nutzer dies dem Replay-/Simulationssystem mitteilt. Dies ist der einfachste Teil, denn wir müssen lediglich einen neuen Befehl in die Konfigurationsdatei des Handelssymbols einfügen. Dies ist jedoch keine Garantie dafür, dass die Informationen den Stellen zur Verfügung stehen, die sie wirklich benötigen. Wo werden diese Informationen also in unserem Replay-/Simulationssystem verwendet? Diese Informationen werden vom Expert Advisor verwendet, und zwar von der Klasse C_Manager zusammen mit der Klasse C_Orders. Hier werden diese spezifischen Informationen aktiv genutzt. Auch wenn dies derzeit noch nicht der Fall ist, ist es notwendig, globaler und allgemeiner zu denken. Wir können etwas von dem verwenden, was wir früher in der Serie gesehen haben Einen EA erstellen, der automatisch funktioniert

In dieser Artikelserie musste der EA wissen, welcher Kontotyp verwendet wird. Genau aus demselben Grund mussten wir sicherstellen, dass das Replay/Modeling-System den EA auch darüber informieren kann. Andernfalls gehen die in dieser Serie besprochenen Funktionen verloren, und wir können sie nicht in unser System übertragen. Nun, wir haben bereits eine Idee, wie wir dem Replay-/Modellierungsdienst mitteilen können, welche Art von Konto ein bestimmter Vermögenswert verwendet. Die Frage ist jedoch, wie man diese Informationen in den EA einspeist.

An dieser Stelle müssen wir wirklich innehalten und nachdenken. Wenn Sie sich den Code ansehen, können Sie die Art des Kontos erkennen. Schauen Sie ihn sich an:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                
      switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
         case ACCOUNT_MARGIN_MODE_EXCHANGE      : szInfo = "EXCHANGE";           break;
      }
      Print("Detected Account ", szInfo);
   }

So kann der EA die Art des verwendeten Kontos bestimmen. Aber hier gibt es ein Problem, und zwar die Funktion AccountInfoInteger. Nun, nicht die Funktion selbst ist ein Problem, da sie genau das meldet, was wir von ihr verlangen. Das Problem besteht darin, dass bei Verwendung des Replay-/Simulationsdienstes das Ergebnis der Funktion AccountInfoInteger Informationen über die Art des Kontos auf dem Handelsserver sind. Mit anderen Worten: Wenn wir mit einem NETTING-Server verbunden sind, laufen wir unter einem NETTING-Konto, auch wenn das Replay-/Simulations-Asset HEDGING ist.

Das ist das Problem. Nun zu den Ideen. Wir könnten den EA anweisen, die Konfigurationsdatei des Replay/Simulations-Assets zu lesen. In gewisser Weise wäre dies angemessen, wenn wir nur eine einzige Datei dafür verwenden würden. Wir könnten also den Replay-/Simulationsdienst auffordern, diese Kontotypinformationen zu übermitteln. Dann würde der EA den richtigen Typ erkennen. Ja, genau. Aber es gibt hier einen kleinen Punkt. Im Gegensatz zu C/C++ gibt es bei MQL5 keine speziellen Codekonstruktionsarten. Es ist nicht so, dass es nicht möglich wäre, einige Formen der Kodierung zu verwenden, um Informationen zwischen Programmen zu übertragen. Wir haben bereits mehrfach gesehen, dass dies möglich ist. Das Problem ist jedoch, dass unsere Informationen in einem 8-Byte-Block enthalten sein müssen. Dies ermöglicht die Verwendung von globalen Terminalvariablen zur Weitergabe von Informationen zwischen Programmen. Denken Sie daran, dass wir dies mit MQL5 tun werden. Es gibt andere Möglichkeiten, dies zu tun, aber hier möchte ich die Möglichkeiten der MQL5-Plattform und -Sprache nutzen.

Zurück zu unserem Problem: Wir können eine Methode verwenden, die seit langem angewandt wird. Hierfür haben wir die Datei InterProcess.mqh verwendet, um die benötigte Kommunikation zu gewährleisten. Aber diese Datei hat ein Problem, das wir in diesem Artikel lösen werden. Um das Problem zu verstehen, sollten wir uns den Code ansehen.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

Das Problem liegt bei diesen booleschen Werten. Im Gegensatz zu C/C++, wo jeder boolesche Wert in einem Bit enthalten sein kann, belegt in MQL5 jeder boolesche Wert acht Bits. Vielleicht verstehen Sie nicht, was das wirklich bedeutet, aber ein Integer-Byte wird verwendet, wenn wir nur ein Bit benötigen, um zu speichern, was wir brauchen. Die Unkenntnis dieser Tatsache stellt ein Problem dar. Denken Sie daran, dass ushort zwei Bytes benötigt, um Informationen zu übertragen. Die Struktur st_0 benötigt vier Bytes, nicht drei wie erwartet. Wenn wir dieser st_0-Struktur vier weitere und nur vier Boolesche Werte hinzufügen, sind wir an der Grenze von acht Bytes, die wir verwenden können.

Solche Dinge machen das Programmieren etwas komplizierter, sodass die Idee, alles ausschließlich mit MQL5 zu machen, etwas komplizierter ist, als es scheint. Wenn die Dinge komplizierter werden oder wir mehr Daten im booleschen Modus übergeben müssen, werden wir diese Struktur radikal ändern müssen. Das ist ein logistischer Albtraum. Doch bis jetzt können wir die gleiche Struktur beibehalten. Allerdings wäre es schön, wenn die Entwickler der MQL5-Sprache uns erlauben würden, den gleichen Programmiermodus wie in C/C++ zu verwenden, zumindest im Fall von booleschen Typen. Auf diese Weise können wir die Anzahl der Bits bestimmen, die von jeder Variablen verwendet werden, und der Compiler wird die gesamte Arbeit der Organisation, Trennung und Gruppierung der Variablen übernehmen. Dies erspart uns die Programmierung auf niedriger Ebene, um solche Ziele zu erreichen. Mit einem einzigen Unterschied: Wenn dies von MQL5-Entwicklern durchgeführt wird, wäre das Ergebnis viel effizienter, da es möglich wäre, mit Assemblercode oder etwas, das der Maschinensprache sehr ähnlich ist, zu organisieren und zu erstellen. Wenn nur wir, die Entwickler, diese Arbeit machen würden, wäre die Effizienz des Codes nicht so groß und würde viel mehr Arbeit erfordern.

Hier ist also ein Hinweis auf eine Verbesserung für MQL5. Etwas scheinbar Triviales, aber sehr Nützliches bei der Programmierung in dieser Sprache.

Kehren wir nun zu unserem Code zurück, da wir immer noch nur mit dem arbeiten können, was wir in der Hand haben. Der neue Code für die Datei „Interprocess.mqh“ ist unten dargestellt:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                bool    isHedging;      // If true we are in a Hedging account, if false the account is Netting...
                bool    isSync;         // If true indicates that the service is synchronized...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

Beachten Sie, dass wir hier lediglich zwei neue boolesche Variablen hinzugefügt haben. Dies reicht aus, um das unmittelbare Problem zu lösen, aber es bringt uns sehr nahe an die Acht-Byte-Grenze. Obwohl wir nur vier boolesche Zahlen verwenden, die vier Bits entsprechen, bedeutet die Tatsache, dass jede boolesche Zahl acht Bits benötigt, dass die st_0-Struktur sechs Bytes groß ist und nicht drei wie erwartet.

WICHTIGER HINWEIS: Für diejenigen, die nicht wissen, wie derselbe Code aussehen würde, wenn die MQL5-Sprache eine ähnliche C/C++-Modellierung zur Definition boolescher Strukturen verwenden würde, möchte ich nur sagen, dass das Lesen und Schreiben des Codes überhaupt keine Probleme bereiten würde. Derselbe Code, der sechs Bytes beansprucht, würde drei Bytes beanspruchen:

union u_Interprocess
{
        union u_0
        {
                double  df_Value;      // Value of the terminal global variable...
                ulong   IdGraphic;     // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {           
    		char  isPlay    : 1// Indicates whether we are in Play or Pause mode...
    		char  isWait    : 1// Tells the user to wait...                
    		char  isHedging : 1;   // If true we are in a Hedging account, if false the account is Netting...                
    		char  isSync    : 1// If true indicates that the service is synchronized...
                ushort  iPosShift;     // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};

Der MQL5-Sprachcompiler versteht den obigen Code nicht, zumindest zum Zeitpunkt des Schreibens, aber hier sagen wir dem Compiler, dass wir nur ein Bit in jeder Deklaration verwenden werden. Es ist die Aufgabe des Compilers, nicht die des Programmierers, die Variable entsprechend zu setzen, damit wir beim Zugriff auf ein Bit kontrollierter vorgehen können, und dafür ein Label zu verwenden und nicht irgendeine Kompilationsdefinitionsanweisung.

 Obwohl diese Konstruktion in nativem MQL5 immer noch möglich ist, ist die Verwendung von Kompilierungsanweisungen fehleranfällig und macht den Code außerdem sehr schwer zu pflegen. Ich möchte diesen Punkt klarstellen, damit Sie nicht denken, dass die Sprache uns bestimmte Dinge nicht erlaubt.

Aber wenn die Welt uns Zitronen gibt, sollten wir Limonade daraus machen. Und wir sollten uns nicht darüber beschweren, dass die Welt nicht so ist, wie wir sie uns vorstellen. Nachdem wir diese Änderungen an der Datei Interprocess.mqh vorgenommen haben, können wir zum nächsten Punkt übergehen. Jetzt werden wir C_ConfigService.mqh ändern. Wir werden einige Änderungen vornehmen, beginnend mit dem unten stehenden Code:

        private :
                enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
                enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
                struct st001
                {
                        C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
                        int     Line;
                }m_GlPrivate;
                string  m_szPath;
                bool    m_AccountHedging;

Wir haben der Klasse C_ConfigService eine private Variable hinzugefügt. So können wir uns daran erinnern, was wir als Nächstes bearbeiten werden. Zunächst müssen wir diese Variable initialisieren. Dies wird im Konstruktor der Klasse durchgeführt:

C_ConfigService()
        :m_szPath(NULL),
         m_ModelLoading(1),
         m_AccountHedging(false)
   {
   }

Der Bedarf an einer Variablen, die als Speicher dient, erklärt sich dadurch, dass wir den Kontrollindikator benötigen, um eine globale Terminalvariable zu erstellen, die die bereits konfigurierten Daten aufnehmen wird. Diese globale Terminalvariable wird jedoch nur angezeigt, wenn die Vorlage den Kontrollindikator in den Chart lädt. Und das Chart erscheint erst, wenn der Replay-/Simulationsdienst die Einrichtung des gesamten Systems abgeschlossen hat. Wenn der nutzerdefinierte Einstellungswert nicht irgendwo gespeichert wäre, könnten wir dem EA den Kontotyp nicht mitteilen. Aber mit sorgfältiger Programmierung können wir den im Replay/Simulationsdienst konfigurierten Kontotyp an den EA weitergeben. Auf diese Weise können wir wissen, wie wir arbeiten müssen. Voraussetzung dafür ist jedoch, dass Sie das Wissen über die Erstellung eines automatisierten EA nutzen.

Da die Variable privat ist und die Klasse C_Replay eine globale Terminalvariable setzt, benötigen wir eine Methode der Klasse C_Replay, um auf die Kontotypvariable zuzugreifen. Dies geschieht im folgenden Code:

inline const bool TypeAccountIsHedging(void) const
   {
      return m_AccountHedging;
   }

Dies gibt einen Wert zurück. Nun müssen wir dafür sorgen, dass die vom Nutzer angegebenen Einstellungen von unserem Code erfasst und verwendet werden können. Zu diesem Zweck müssen wir dem Erfassungssystem neuen Code hinzufügen. Es ist unten aufgeführt:

inline bool Configs(const string szInfo)
   {
      const string szList[] = {
                               "PATH",
                               "POINTSPERTICK",
                               "VALUEPERPOINTS",
                               "VOLUMEMINIMAL",
                               "LOADMODEL",
                               "ACCOUNT"
                              };
      string  szRet[];
      char    cWho;
                                
      if (StringSplit(szInfo, '=', szRet) == 2)
      {
         StringTrimRight(szRet[0]);
         StringTrimLeft(szRet[1]);
         for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
         switch (cWho)
         {
            case 0:
               m_szPath = szRet[1];
               return true;
            case 1:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
               return true;
            case 2:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
               return true;
            case 3:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
               return true;
            case 4:
               m_ModelLoading = StringInit(szRet[1]);
               m_ModelLoading = ((m_ModelLoading < 1) && (m_ModelLoading > 4) ? 1 : m_ModelLoading);
               return true;
            case 5:
               if (szRet[1] == "HEDGING") m_AccountHedging = true;
               else if (szRet[1] == "NETTING") m_AccountHedging = false;
               else
               {
                  Print("Entered account type is not invalid.");                                                          
                  return false;
               }
               return true;                    
         }
         Print("Variable >>", szRet[0], "<< not defined.");
      }else
         Print("Configuration definition >>", szInfo, "<< invalidates.");
                                        
      return false;
   }

Anhand dieses neuen Codes können wir erkennen, welches Konto zu verwenden ist. Es ist sehr einfach zu machen und gleichzeitig absolut funktional. Die Konfigurationsdatei könnte nun etwa wie folgt aussehen:

[Config]
Path = Forex\EURUSD
PointsPerTick = 0.00001
ValuePerPoints = 1.0
VolumeMinimal = 0.01
Account = HEDGING

oder so:

[Config]
Path = Petrobras PN
PointsPerTick = 0.01
ValuePerPoints = 1.0
VolumeMinimal = 100.0
Account = NETTING

Wir haben verschiedene Assets, die vom Replay-/Simulationsdienst verwendet werden können, aber als Nutzer können Sie die Art des zu verwendenden Kontos bestimmen. Es ist ganz einfach und unkompliziert. Auf diese Weise können wir unsere Analyse auf jede Art von Markt ausdehnen. Sie erlaubt uns, jede uns bekannte oder von uns entwickelte Methode anzuwenden. Sehen wir uns nun an, wie wir die Klasse C_Replay dazu bringen können, diese Daten für uns zu setzen, da alle Arbeiten im Zusammenhang mit der Klasse C_ConfigService abgeschlossen sind. Um dies zu ermöglichen und die nutzerdefinierte Konfiguration für das gesamte Replay-/Simulationssystem sichtbar zu machen, müssen wir nicht viel Arbeit leisten. Alles, was wir tun müssen, ist im folgenden Code dargestellt:

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
      u_Interprocess info;
                                
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
         macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
         macroError("Asset configuration is not complete, need to declare the ticket value.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
         macroError("Asset configuration not complete, need to declare the minimum volume.");
      if (m_IdReplay == -1) return false;
      if ((m_IdReplay = ChartFirst()) > 0) do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         {
            ChartClose(m_IdReplay);
            ChartRedraw();
         }
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      Print("Waiting for [Market Replay] indicator permission to start replay ...");
      info.ServerTime = ULONG_MAX;
      CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value);
      info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
      ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
      CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
      while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      info.s_Infos.isHedging = TypeAccountIsHedging();
      info.s_Infos.isSync = true;
      GlobalVariableSet(def_GlobalVariableReplay, info.u_Value.df_Value);

      return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
   }

Wir haben eine Zeile aus dem Code entfernt. Der Grund dafür ist, dass wir die Klasse C_Replay benötigen, um den Wert der Variablen zu laden, die durch den Kontrollindikator gesetzt wurde, damit wir wissen, womit wir es zu tun haben. Denken Sie daran, dass wir keine Vermutungen anstellen sollten. In Zukunft müssen wir möglicherweise Werte an den Dienst übergeben, sobald der Kontrollindikator zu laufen beginnt. Danach lesen wir die vom Nutzer angegebenen Werte und melden, dass das System synchronisiert ist. Danach senden wir den Wert an MetaTrader 5 und machen die Daten über eine globale Terminalvariable verfügbar. Damit ist der Teil über den Replay-/Simulationsdienst abgeschlossen, aber wir haben den Code noch nicht geschrieben. Nun müssen wir den EA so modifizieren, dass er weiß, wie die Wiedergabe/Simulation vom Nutzer konfiguriert wurde. Auch hier werden wir alle diesbezüglichen Erklärungen in einem separaten Thema behandeln:


Machen Sie dem Expert Advisor den Kontotyp bekannt

Um zu verstehen, wie der EA den nutzerdefinierten Kontotyp erkennen kann, müssen wir verstehen, wie das gesamte System funktioniert. Schauen wir uns Bild 01 an, das kurz beschreibt, wie der Initialisierungsprozess abläuft.

Abbildung 01

Abbildung 01 - Initialisierungsprozess des Replay-/Simulationssystems.

Warum ist es so wichtig, diese Zahl zu verstehen? Denn die Klasse C_Terminal ist eine gemeinsame Klasse für den Kontrollindikator und den Expert Advisor. Wenn Sie dies nicht verstehen, werden Sie nicht in der Lage sein zu verstehen, wie und warum das System den nutzerkonfigurierten Einstellungen im MetaTrader 5 folgen kann. Und wenn wir diese Initialisierung nicht verstehen, können wir etwas Dummes tun, indem wir die Datei bearbeiten, die den Replay-/Simulationsprozess konfiguriert. Dies führt dazu, dass das gesamte System nicht richtig funktioniert. Anstatt eine Erfahrung zu machen, die dem realen Markt nahe kommt, können wir also einen falschen Eindruck davon bekommen, wie wir uns verhalten sollten, wenn wir in der Praxis anwenden, was wir durch den Replay/Simulationsprozess gelernt haben. Man muss also wissen, was die erste Zahl ist.

  • Alle blauen Pfeile zeigen die erste Phase der Initialisierung an. Dies geschieht genau in dem Moment, in dem wir als Nutzer den Dienst in MetaTrader 5 starten.
  • Danach folgt die zweite Phase, in der der Dienst ein Chart erstellt und eine Vorlage verwendet, um den EA und den Kontrollindikator zu initialisieren. Dies ist durch einen gelben Pfeil gekennzeichnet.
  • Der nächste Schritt, der durch die GRÜNEN Pfeile angezeigt wird, besteht darin, eine globale Terminalvariable mit Hilfe des Steuerungsindikators zu erstellen und diese Variable mit dem Replay-/Simulationsdienst zu konfigurieren. An diesem Punkt sehen wir, dass die Klasse C_Terminal Daten an den Kontrollindikator sendet, aber nicht an die Klasse C_Manager, die gerade vom EA initialisiert wird.
  • Der letzte Schritt (BLAUE Pfeile) ist die Einrichtung anhand der vom Nutzer in der Datei bereitgestellten Daten, die angeben, wie die Wiedergabe/Simulation funktionieren soll. Der EA benötigt diese Informationen, um zu wissen, welche Art von Konto verwendet wird.

Auch wenn es verwirrend erscheinen mag, müssen wir äußerst vorsichtig sein, um sicherzustellen, dass die Klasse C_Terminal dem Kontrollindikator erlaubt, seine Arbeit zu tun. Gleichzeitig darf der EA nichts tun, bevor die Klasse C_Manager richtig konfiguriert ist. Warum sage ich, dass die Verwaltung in der Klasse C_Terminal und nicht in der Klasse C_Manager erfolgen sollte? Es ist vielleicht einfacher, dies in der Klasse C_Manager zu tun. Warum nicht? Der Grund dafür ist nicht einfach die Wahl der Klasse, in der die Dinge gespeichert werden sollen, der eigentliche Grund ist die Klasse C_Orders. Wenn sich das Kontrollsystem in der Klasse C_Manager befindet, kann die Klasse C_Orders nicht auf die benötigten Daten zugreifen. Da wir das Steuerelement in die Klasse C_Terminal eingefügt haben, kann es auch dann noch auf die benötigten Daten zugreifen, wenn wir später eine andere Klasse für die Auftragsverwaltung verwenden. Wie in Abbildung 01 zu sehen ist, können wir nicht einfach einen beliebigen Weg wählen, um dies umzusetzen. Wir müssen sicherstellen, dass alles richtig gemacht wird. Andernfalls kann das gesamte System unmittelbar nach dem Start ausfallen. Ich weiß, es ist schwer zu verstehen und mag sogar schockierend sein, aber glauben Sie mir, wenn die Programmierung nicht streng genug durchgeführt wird, wird das System versagen. Schauen wir uns also an, wie das System umgesetzt wird. Beginnen wir mit dem folgenden Code:

class C_Terminal
{
        protected:
                enum eErrUser {ERR_Unknown, ERR_PointerInvalid};
                enum eEvents {ev_Update};
//+------------------------------------------------------------------+
                struct st_Terminal
                {
                        ENUM_SYMBOL_CHART_MODE   ChartMode;
                        ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
                        long    ID;
                        string  szSymbol;
                        int     Width,
                                Height,
                                nDigits;
                        double  PointPerTick,
                                ValuePerPoint,
                                VolumeMinimal,
                                AdjustToTrade;
                };
//+------------------------------------------------------------------+
        private :
                st_Terminal m_Infos;
                struct mem
                {
                        long    Show_Descr,
                                Show_Date;
                        bool    AccountLock;
                }m_Mem;

Diese Variable ermöglicht den Zugriff auf den Kontotyp mit Hilfe der in diesem Artikel bereits implementierten Methoden. Wir müssen keinen neuen Code in die Klasse einfügen, um diese Informationen zu erhalten, was sehr gut ist. Aber es gibt noch eine weitere Variable. Sie dient als Sperre, aber Sie werden bald verstehen, warum und wie sie verwendet wird. Schauen wir uns nun den Konstruktor der Klasse an:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      m_Mem.AccountLock = false;
      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_EVENT_OBJECT_CREATE, 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.ValuePerPoint / m_Infos.PointPerTick;
      m_Infos.ChartMode     = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
      if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
      ResetLastError();
   }

Hier wird die Variable „lock“ initialisiert, um der Klasse C_Terminal anzuzeigen, dass der Wert des Kontotyps noch nicht implementiert wurde. Aber wir tun etwas dagegen, und das ist sehr wichtig. Wenn das System feststellt, dass ein Vermögenswert KEIN REPLAY-Vermögenswert ist, muss die Klasse C_Terminal die Kontodaten initialisieren. Wenn es sich um ein Replay-Asset handelt, werden die Daten nicht jetzt, sondern erst später in der Klasse C_Manager initialisiert. Das muss man verstehen, denn es wird sehr bald von großer Bedeutung sein. Die Klasse C_Manager initialisiert die Daten, wenn das Asset ein Replay-Asset ist. Handelt es sich bei dem Asset nicht um ein Wiedergabe-Asset, wird es in diesem Stadium initialisiert. Als Nächstes haben wir den folgenden Methodenaufruf:

inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
   {
      if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
      m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
   }

Diese Methode ist sehr wichtig. Ohne sie wäre die Art des verwendeten Kontos unbekannt. Denn in diesem Fall würden Sie davon ausgehen, dass dieser oder jener andere Typ verwendet wird. Und es ist wichtig, dass sich der Wert der Variablen nicht mehr ändert, sobald Sie mit einer bestimmten Kontoart begonnen haben. Aus diesem Grund verwenden wir diese Blockade. Ich betone dies: Nur wenn die blockerte Variable auf false gesetzt ist, können wir ihren Wert auf der Grundlage des an die Methode übergebenen Arguments initialisieren. Danach kann die Variable bis zum Ende der Codeausführung nicht mehr geändert werden. Ein weiteres Detail ist, dass wir uns auf die Typen NETTING und HEDGING beschränken werden. Das liegt daran, dass EXCHANGE genauso funktioniert wie der Typ NETTING. Da wir ein Handelssystem verwenden, das dem realen Markt sehr nahe kommt, sehe ich kein Problem darin, uns auf die Typen NETTING und HEDGING zu beschränken. Damit ist der Codeteil der Klasse C_Terminal abgeschlossen. Schauen wir uns nun an, was in der Klasse C_Manager geändert werden muss. In dieser C_Manager-Klasse mussten wir lediglich den Konstruktorcode ändern. Jetzt sieht es so aus:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
      u_Interprocess info;
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                                                                        
      if (def_InfoTerminal.szSymbol == def_SymbolReplay)
      {
         do
         {
            while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag)) Sleep(750);
         }while ((!info.s_Infos.isSync) && (!_StopFlag));
         def_AcessTerminal.SetTypeAccount(info.s_Infos.isHedging ? ACCOUNT_MARGIN_MODE_RETAIL_HEDGING : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
      };
      switch (def_InfoTerminal.TypeAccount)
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
      }
      Print("Detected Account ", szInfo);
   }

Sie werden vielleicht denken, dass sich nichts geändert hat. Oder schlimmer noch, Sie verstehen vielleicht nicht, was vor sich geht. Denn wir haben hier eine Schleife, die unter bestimmten Bedingungen in eine Endlosschleife übergehen kann. Aber der größte Teil des Codes bleibt derselbe, mit einem kleinen Unterschied, der genau in diesem Punkt liegt. Zuvor haben wir hier alle Kontotypen überprüft. Jetzt bleiben nur noch zwei übrig. Und die Informationen, mit denen Sie die Art des Kontos herausfinden können, befinden sich in der Variablen, die in der Klasse C_Terminal erstellt wurde. Aber sehen wir uns den wirklich neuen Teil des Codes einmal genauer an. Sie beginnt, wenn festgestellt wird, dass das verwendete Asset dasselbe ist wie das im Replay-/Simulationssystem. Wenn die Prüfung erfolgreich ist, landen wir in einer Doppelschleife, die aus einer äußeren Schleife und einer weiteren Schleife besteht, die in diese erste Schleife eingebettet ist.

In dieser verschachtelten Schleife warten wir darauf, dass der Kontrollindikator eine globale Terminalvariable erstellt, damit der Replay-/Simulationsdienst sie setzen kann. Wenn das Programm durch den Nutzer beendet wird oder der Kontrollindikator eine globale Terminalvariable erzeugt, verlassen wir diese geschachtelte Schleife und treten in die äußere Schleife ein. Diese äußere Schleife wird nur in zwei Fällen beendet: erstens, wenn der Replay-/Simulationsdienst eine globale Terminalvariable setzt, und zweitens, wenn der Nutzer das Programm schließt. Außer in diesen beiden Fällen wird die Schleife nicht beendet und die verschachtelte Schleife wird wieder aufgenommen. Wenn alles gut geht und die äußere Schleife endet, wird der Wert der Kontoart an die Klasse C_Terminal gesendet. 


Schlussfolgerung

In diesem Artikel haben wir ein Problem gelöst, das zwar klein ist, uns aber in Zukunft viel Kopfzerbrechen bereiten könnte. Ich hoffe, Sie verstehen die Bedeutung dieser Änderungen und vor allem, wie es uns gelungen ist, das Problem mit MQL5 zu lösen. Der Anhang enthält den Quellcode sowie aktualisierte Dateien zur Verwendung in der Wiedergabe/Simulation. Wenn Sie diesen Dienst bereits nutzen, auch wenn Sie ihn noch nicht für die Analyse verwenden können, müssen Sie auch die Dateien aktualisieren, die für die Einrichtung des Replay-/Simulationsdienstes zuständig sind, um den entsprechenden Kontotyp zu verwenden. Andernfalls verwendet das System die Standardkontenart HEDGING.

Dies kann in der Zukunft zu Problemen führen. Vergessen Sie also nicht, sie zu aktualisieren.



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

Beigefügte Dateien |
Files_-_FOREX.zip (3744 KB)
Files_-_BOLSA.zip (1358.28 KB)
Files_-_FUTUROS.zip (11397.55 KB)
Developing a Replay System (Part 37): Paving the Path (I) Developing a Replay System (Part 37): Paving the Path (I)
In this article, we will finally begin to do what we wanted to do much earlier. However, due to the lack of "solid ground", I did not feel confident to present this part publicly. Now I have the basis to do this. I suggest that you focus as much as possible on understanding the content of this article. I mean not simply reading it. I want to emphasize that if you do not understand this article, you can completely give up hope of understanding the content of the following ones.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 12): Das Newton-Polynom MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 12): Das Newton-Polynom
Das Newtonsche Polynom, bei dem aus einer Reihe von Punkten quadratische Gleichungen erstellt werden, ist ein archaischer, aber interessanter Ansatz für die Betrachtung einer Zeitreihe. In diesem Artikel versuchen wir zu untersuchen, welche Aspekte dieses Konzept für Händler von Nutzen sein könnten, und gehen auch auf seine Grenzen ein.
Developing an MQL5 Reinforcement Learning agent with RestAPI integration (Part 1): How to use RestAPIs in MQL5 Developing an MQL5 Reinforcement Learning agent with RestAPI integration (Part 1): How to use RestAPIs in MQL5
In this article we will talk about the importance of APIs (Application Programming Interface) for interaction between different applications and software systems. We will see the role of APIs in simplifying interactions between applications, allowing them to efficiently share data and functionality.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 11): Number Walls MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 11): Number Walls
Number Walls oder Zahlenwände sind eine Variante der Linear Shift Back Registers, die Sequenzen auf ihre Vorhersagbarkeit hin überprüfen, indem sie auf Konvergenz prüfen. Wir sehen uns an, wie diese Ideen in MQL5 von Nutzen sein könnten.