English Русский Español 日本語 Português
preview
Entwicklung eines Replay Systems (Teil 35): Anpassungen vornehmen (I)

Entwicklung eines Replay Systems (Teil 35): Anpassungen vornehmen (I)

MetaTrader 5Beispiele | 14 Mai 2024, 09:34
70 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay Systems (Teil 34): Auftragssystem (III)“ habe ich erwähnt, dass das System einige ziemlich seltsame und sogar mysteriöse Fehler aufweist. Diese Fehler oder Ausfälle wurden durch eine Interaktion innerhalb des Systems verursacht. Trotz der Versuche, die Ursache für diese Ausfälle herauszufinden, um sie zu beseitigen, blieben alle Versuche erfolglos. Einige dieser Fälle machen keinen Sinn, z. B. wenn wir Zeiger oder Rekursion in C/C++ verwenden, stürzt das Programm ab. Einer der ersten Schritte besteht darin, diese Mechanismen zu überprüfen. In MQL5 geschieht dies jedoch nicht auf dieselbe Weise wie in C/C++. Nachdem ich einige kleine Änderungen vorgenommen hatte, gelang es mir, einen der Fehler zu beheben. Auch wenn die Lösung nicht elegant erscheint, so hat sie doch einen der Fehler vollständig beseitigt.

Allerdings müssen wir noch etwas radikalere Änderungen am Code vornehmen, um die Fehler, die den Betrieb des Systems beeinträchtigen, vollständig zu beseitigen. Wahrscheinlich gibt es sie schon lange, denn bestimmte Arten der Interaktion gab es in dem System früher nicht. Sobald diese Wechselwirkungen auftraten, machten sich diese Fehler bemerkbar.

Die vorhandenen Fehler wirken sich nicht negativ auf den Betrieb des Systems aus, ermöglichen aber keinen wirklich korrekten Betrieb. All dies macht die Erfahrung mit dem Programm ziemlich unangenehm und sogar inakzeptabel. Der erste dieser Fehler ist recht einfach zu beheben, also fangen wir mit ihm an.


Lösung für die Anzeige „Dienst ist beschäftigt“

Der erste dieser Fehler ist am einfachsten zu beheben. Sie ist für den folgenden Fall verantwortlich: Wenn wir bei der Auftragserteilung die STRG- oder SHIFT-Taste drücken, erhalten wir die Meldung, dass der Dienst beschäftigt ist. Das bedeutet, dass während der Ausführung, obwohl das System normal funktioniert, ein Hinweis darauf besteht, dass der Dienst eine andere Aufgabe ausführt. Diese Aufgabe ist zeitversetzt. Das bedeutet, dass Balken erstellt werden, bevor Sie irgendeine Art von Analyse auf dem Replay/Simulator-Chart durchführen können. Obwohl dieser Fehler nicht extrem schädlich ist, macht er die Nutzung von Replay/Simulator ziemlich unangenehm, da er eher verwirrend als informativ ist.

Manch einer mag denken, dass es besser ist, den Hinweis, beschäftigt zu sein, zu entfernen. Da es schon eine Weile her ist, dass wir die Anzeige der Balkenerstellung beim Scrollen durch die Zeit verwendet haben. Doch damit ist das Problem nicht gelöst. Dadurch wird es nur in den Hintergrund gedrängt und unter den Teppich gekehrt, aber die Lösung ist eigentlich ganz einfach und effektiv. Außerdem können wir einige Dinge aufbewahren, sodass wir in der Zukunft auf das Visualisierungssystem zurückkommen können, um die Erstellung der Balken zu sehen. Da es vorher möglich war, haben wir den Replay/Simulationsdienst deaktiviert. Um das Problem zu lösen, müssen wir also einige Änderungen vornehmen. Beginnen wir mit dem folgenden Code:

int OnInit()
{
#define macro_INIT_FAILED { ChartIndicatorDelete(m_id, 0, def_ShortName); return INIT_FAILED; }
        u_Interprocess Info;
        ulong ul = 1;

        m_id = ChartID();
        ul <<= def_BitShift;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED;
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
        if (Info.u_Value.IdGraphic != m_id) macro_INIT_FAILED;
        if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device");
        Info.u_Value.IdGraphic |= ul;
        GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
        
#undef macro_INIT_FAILED
}

Die erste Änderung sollte an der Indikator-Datei vorgenommen werden. Die genaue Stelle, an der die Änderung vorgenommen wird, ist oben dargestellt. Der durchgestrichene Teil wurde gelöscht und durch einen neuen Code ersetzt. Schauen Sie genau hin, denn dies ist die einzige Änderung, die hier wirklich vorgenommen werden muss. Sie betrifft den Wert des Parameters lparam, der an die Funktion EventChartCustom übergeben werden muss. Seltsamerweise trägt eine so einfache Änderung bereits Früchte.

In derselben Datei, aber in einer anderen Funktion, müssen wir etwas sehr Ähnliches tun:

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
  static bool bWait = false;
  u_Interprocess Info;
        
  Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
  if (!bWait)
  {
     if (Info.s_Infos.isWait)
     {
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 0, 0, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 1, 0, "");
        bWait = true;
     }
  }else if (!Info.s_Infos.isWait)
  {
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
     bWait = false;
  }
        
  return rates_total;
}

Dies ist ähnlich wie der obige OnInit-Code. Die durchgestrichenen Teile wurden durch neuen Code ersetzt. Der einzige Unterschied liegt wiederum im Wert des Parameters lparam. Die Lösung betrifft also wieder die Funktion EventChartCustom.

Warum geschieht dies, und funktioniert es wirklich? Tatsächlich werden diese Änderungen das Problem nicht lösen. Um das Problem wirklich zu lösen, müssen wir in die Klasse C_Control gehen und eine kleine Prüfung in die Nachrichtenbehandlungsfunktion einfügen. Sehen Sie sich den neuen Code unten an:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
//... The rest of the code ...
  }
}

Ich zeige hier nur die Fragmente, die wir wirklich brauchen, um das Problem zu verstehen. Beachten Sie, dass bei beiden nutzerdefinierten Ereignissen eine kleine Prüfung hinzugefügt wurde. Die Tatsache, dass die Nutzerinteraktion ein nutzerdefiniertes Ereignis im Programm auslöst, zwingt diese Prüfungen dazu, nur das Auslösen des nutzerdefinierten Ereignisses zuzulassen. Diese Ereignisse werden genau im Code des Kontrollindikators ausgelöst. Aus irgendeinem anderen Grund wird der Wert, der beim Handler ankommt, von dem abweichen, den wir erwartet haben. In diesem Fall wird der Wert des Parameters lparam berücksichtigt. Diese Art von Dingen wird in der Zukunft wieder auftauchen, wenn ich erkläre, wie man eine interessante Sache in der MetaTrader 5 Plattform nutzt. Wenn wir dazu kommen, werde ich die Gründe für diesen Fehler näher erläutern. Im Moment ist der Grund jedoch noch seltsam.

Aber Moment mal, das macht doch keinen Sinn! Tatsächlich habe ich anfangs auch nicht verstanden, warum. Aber irgendwie, wenn Sie SHIFT oder CTRL drücken, um das Auftragssystem zu nutzen, das wir entwickeln, erzeugt die Plattform ein Ereignis, das zwei vorherige Ereignisse auslöst. Konkret CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn, was dazu führt, dass das Bild „Dienst ist beschäftigt“ auf dem Chart erscheint. Sobald Sie SHIFT oder CTRL loslassen, wird das CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff Ereignis ausgelöst, und damit ist alles wieder im Lot.

Nachdem dieses Problem behoben ist, können wir uns dem nächsten zuwenden. Das zweite Problem ist jedoch wesentlich schwieriger zu lösen. Um Verwirrung zu vermeiden, müssen wir sie in einem separaten Thema behandeln. Gehen wir also zum nächsten Abschnitt über, um die tatsächliche Situation zu verstehen.


Eine tiefere Wiederverwendung der Klasse C_Mouse

Der nächste Fehler tritt auf, wenn wir eine Analyse durchführen. In diesem Fall kann es passieren, dass das System die Positionsanzeige verschiebt, ohne dass der Nutzer dies bemerkt. Die Lösung dieses Problems erfordert eine wesentlich tiefgreifendere Codebearbeitung. Man könnte meinen, dass es ausreicht, eine Prüfung durchzuführen, um sicherzustellen, dass der Nutzer tatsächlich versucht, den Schieberegler zu bewegen. Dieses Schieberegler-Objekt ist ein integraler Bestandteil der Kontrollindikators und dient dazu, dass der Nutzer weiß, in welchem Stadium sich die Wiedergabe oder Simulation befindet. Eine einfache Überprüfung reicht jedoch nicht aus, um diesen Mangel zu beheben. Wir müssen über eine einfache Überprüfung hinausgehen. Tatsächlich werden wir zu Beginn etwas viel Komplexeres machen, aber es wird das Problem auf eine viel definitivere Weise lösen und uns auch erlauben, später andere Dinge zu tun.

Wir werden die Klasse C_Mouse, die ursprünglich im EA verwendet wird, auch im Kontrollindikator einsetzen. Aber das ist nicht so einfach, wie es scheint. Wir werden an mehreren Stellen des Codes erhebliche Änderungen vornehmen müssen. Wir haben dies getan, damit die Klasse C_Mouse sowohl im Kontrollindikator als auch im EA vollständig harmonisch funktioniert. Alles muss ohne Konflikte funktionieren. Auf diese Weise öffnen wir auch eine Tür, die es uns ermöglicht, etwas anderes zu tun.

Schauen wir uns zunächst die Klasse C_Mouse und ihre Änderungen an. Um die Änderungen zu erklären, müssen wir uns ansehen, was dem Code für diese Klasse tatsächlich hinzugefügt wurde. Der erste Punkt betrifft private globale Variablen. Die Änderung ist unten dargestellt:

struct st_Mem
{
   bool     CrossHair,
            IsFull;
   datetime dt;
}m_Mem;

Diese hinzugefügte Variable wird verwendet, um die Prüfung innerhalb der Klasse durchzuführen. Vorrangig geht es darum, genau festzulegen, wie die Klasse ausgeführt werden soll: Dies bezieht sich auf die Objekte, mit denen die Klasse während ihrer Lebensdauer arbeiten soll. Sobald diese Variable deklariert ist, müssen wir sie nun richtig initialisieren. Dazu verwenden wir den Klassenkonstruktor. Aber jetzt kommt die erste Komplikation: Wie weiß man, ob man das System im FULL-Modus verwenden soll oder nicht? Ein wichtiges Detail: Bei der Verwendung des FULL-Modus werden tatsächlich grafische Objekte verwendet. Diese Objekte werden vom Expert Advisor verwendet. Wenn wir die Klasse nicht im EA verwenden, sollten solche Objekte NICHT erstellt werden. Aber wie können wir der Klasse mitteilen, ob wir den Expert Advisor oder etwas anderes verwenden? Es gibt mehrere Möglichkeiten. Wir brauchen diejenige, die durch den Klassenkonstruktor ausgeführt wird. Auf diese Weise lassen sich viele nachträgliche Überprüfungen und Risiken im Zusammenhang mit einer falschen Verwendung der Klasse vermeiden. Wir werden also den Konstruktor ändern. Es sieht dann wie folgt aus:

C_Mouse(C_Terminal *arg, color corH = clrNONE, color corP = clrNONE, color corN = clrNONE)
{
   Terminal = arg;
   if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   if (_LastError != ERR_SUCCESS) return;
   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);
   ZeroMemory(m_Info);
   m_Info.corLineH  = corH;
   m_Info.corTrendP = corP;
   m_Info.corTrendN = corN;
   m_Info.Study = eStudyNull;
   if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
}

Beachten Sie nun, was hier geschieht. Die Tatsache, dass wir hier in der Konstruktordeklaration die Farbdaten mit einem Standardwert definieren, ermöglicht es uns, den Konstruktor zu überladen. Diese Überladung bedeutet, dass wir keinen weiteren Konstruktorcode erstellen müssen, um die gleiche C_Mouse-Klasse zu verwenden. Dies ist notwendig, um zu unterscheiden, ob er in einem Kontrollindikator oder im Expert Advisor verwendet wird. Gäbe es eine solche Überladung nicht, müssten wir einen weiteren Konstruktor erstellen, um der Klasse mitzuteilen, ob sie im FULL-Modus verwendet werden soll oder nicht. An dieser Stelle des Konstruktorcodes wird der Wert der Variablen festgelegt. Zugleich nutzen wir seinen Wert zum ersten Mal. Wenn wir uns im FULL-Modus befinden, erstellen wir eine Kurslinie, die im Chart verwendet wird. Andernfalls wird diese Zeile nicht erstellt.

Der Code des Destruktors wurde ebenfalls geringfügig geändert. Hier ist der Code:

~C_Mouse()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, 0, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName);
}

Wenn die Konstruktion der Klasse fehlschlägt, weil der Zeiger auf die Klasse C_Terminal nicht korrekt initialisiert wurde, können wir diesen Zeiger im Destruktor nicht verwenden. Um die Verwendung eines ungültigen Zeigers zu vermeiden, führen wir diese Prüfung im Destruktor der Klasse durch. Dies war der einfachste Teil der anfänglichen Kodierung. Werfen wir nun einen kurzen Blick auf den alten Klassencode, um zu sehen, welche Änderungen wir vornehmen müssen.

void CreateStudy(void)
{
   if (m_Mem.IsFull)
   {
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
      CreateObjectInfo(0, 0, def_NameObjectStudy);
   }
   m_Info.Study = eStudyCreate;
}
//+------------------------------------------------------------------+
void ExecuteStudy(const double memPrice)
{
   double v1 = GetInfoMouse().Position.Price - memPrice;
   int w, h;
                                
   if (!CheckClick(eClickLeft))
   {
      m_Info.Study = eStudyNull;
      ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
      if (m_Mem.IsFull) ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName + "T");
   }else if (m_Mem.IsFull)
   {
      string sz1 = StringFormat(" %." + (string)def_InfoTerminal.nDigits + "f [ %d ] %02.02f%% ",
      MathAbs(v1), Bars(def_InfoTerminal.szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1/ memPrice) * 100.0)));
      GetDimensionText(sz1, w, h);
      ObjectSetString(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_TEXT, sz1);                                                                                                                           
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XSIZE, w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YSIZE, h);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X - w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - (v1 < 0 ? 1 : h));                            
      ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
   }
   m_Info.Data.ButtonStatus = eKeyNull;
}

Wir haben diese beiden Methoden geändert, um sie sehr einfach und unkompliziert zu gestalten. Wir fügen eine kleine Prüfung für das Erstellen und Arbeiten mit Objekten hinzu. Solche Manipulationen finden nur statt, wenn wir die Klasse im FULL-Modus verwenden. Aber wir haben auch eine andere Methode in der Klasse C_Mouse. Dies ist sehr wichtig, denn durch diese Methode werden wir mit der Klasse interagieren. Der Code ist unten zu finden:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   int w = 0;
   static double memPrice = 0;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + ev_HideMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
         break;
      case (CHARTEVENT_CUSTOM + ev_ShowMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         ChartXYToTimePrice(def_InfoTerminal.ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
         if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = def_AcessTerminal.AdjustPrice(m_Info.Data.Position.Price));
         m_Info.Data.Position.dt = def_AcessTerminal.AdjustTime(m_Info.Data.Position.dt);
         ChartTimePriceToXY(def_InfoTerminal.ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
         if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
         m_Info.Data.ButtonStatus = (uint) sparam;
         if (CheckClick(eClickMiddle))
            if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
                  if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
                  m_Info.Study = eStudyExecute;
               }
         if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
         m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
         break;
   }
}

Wie die vorherigen Methoden wurde auch diese geändert. Das Einzige, was wir getan haben, war zu prüfen, ob Objekte im Chart manipuliert werden müssen. Man könnte meinen, dass diese Überprüfungen nicht notwendig sind, da sich die Objekte in manchen Fällen nicht im Chart befinden, und wenn doch, müssen sie auf dieselbe Weise bearbeitet werden. Dies liegt daran, dass die MetaTrader 5-Plattform keine doppelten Objekte erstellt. Aber bedenken Sie Folgendes: Wenn wir diese Manipulationen mit Objekten vornehmen, tun wir das nicht nur einmal. Die Tatsache, dass der EA und der Kontrollindikator dieselbe Klasse verwenden, ohne zu wissen, dass beide sie gleichzeitig verwenden, zwingt die MetaTrader 5-Plattform zu doppelten Aufrufen der Objektbehandlung. Das ist nicht wirklich ein Problem, aber wir müssen bedenken, dass wir das System für die Marktwiederholung oder -simulation verwenden. Seit Beginn dieses Projekts habe ich immer wieder darauf hingewiesen, dass unser Hauptziel darin besteht, die Simulation oder das Replay so nah wie möglich an das reale Marktgeschehen heranzuführen. Es geht nicht nur um die Erstellung der Balken, sondern auch um die Zeit, die für die Erstellung benötigt wird. Dadurch, dass MetaTrader 5 zweimal auf Objekte zugreift, wo dies mit einem einzigen Aufruf möglich wäre, wird wertvolle Zeit verschwendet, unabhängig davon, welchen Prozessor oder welche Hardware Sie verwenden. Wenn wir anfangen, Millisekunden oder sogar Nanosekunden durch Code-Duplizierung für solche Aufrufe zu verlieren, dann werden wir bald anfangen, Sekunden zu verlieren oder die Erstellung von Balken zu sehr zu verzögern. Ja, wir sollten sicherstellen, dass MetaTrader 5 keine zusätzliche Arbeit leisten muss, um Objekte im Chart anzuzeigen.

Wir haben die Änderung des C_Mouse-Klassencodes abgeschlossen und müssen uns nicht mehr um den EA-Code kümmern. Die Verzeichnisstruktur hat sich geändert, aber das betrifft nur den Code von include. Ich glaube also nicht, dass es notwendig ist, sich damit zu befassen. Es ist eine kleine Erleichterung, dass der EA-Code keine Änderungen bei der Implementierung erfahren hat. Dies ist jedoch nur vorübergehend, da der Code des Kontrollindikators, der zur Steuerung der Wiedergabe/Simulation verwendet wird, viel tiefgreifendere Änderungen erfahren wird, die eine ausführliche und ruhige Erklärung verdienen. Lassen Sie uns also zum nächsten Thema übergehen.


Ändern des Kontrollindikators

Um diese Änderung am Kontrollindikator vorzunehmen, arbeiten wir zunächst an der Klasse, die es verwendet. Ich meine die Klasse C_Control. Schauen wir uns die ersten Codeänderungen an:

#include "..\Auxiliar\C_Terminal.mqh"
#include "..\Auxiliar\C_Mouse.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Controls : protected C_Mouse
{
        protected:
                enum EventCustom {ev_WaitOn, ev_WaitOff};
        private :
//+------------------------------------------------------------------+
                string  m_szBtnPlay;
                bool    m_bWait;
                struct st_00
                {
                        string  szBtnLeft,
                                szBtnRight,
                                szBtnPin,
                                szBarSlider,
                                szBarSliderBlock;
                        int     posPinSlider,
                                posY,
                                Minimal;
                }m_Slider;
                C_Terminal *Terminal;

Sie können sofort sehen, dass der Code jetzt zwei Aufrufe zu Include-Dateien enthält, sowohl die C_Terminal-Klasse als auch die C_Mouse-Klasse. Aber warum nicht die Klasse C_Study verwenden, die von der Klasse C_Mouse erbt? Der Grund dafür ist, dass wir den Kontrollindikator nicht dazu bringen werden, Analysen durchzuführen oder zu verwalten. Dies ist die Aufgabe des Expert Advisors, zumindest in diesem Stadium der Implementierung, und nicht die des Kontrollindikators. Daher werden wir die Klasse C_Mouse verwenden. Beachten Sie, dass die Klasse C_Control von der Klasse C_Mouse erbt. Trotz dieser Vererbung wird der Kontrollindikator nicht davon profitieren, zumindest nicht direkt. Aus diesem Grund könnte eine solche Vererbung sogar privat gemacht werden, aber ich behandle diese Art von Vererbung normalerweise als geschützt. Das letzte, was bei diesen Deklarationen zu beachten ist, ist der Zeiger, den die Klasse C_Terminal verwenden wird.

HINWEIS: Im Quellcode der Klasse C_Control gab es eine private globale Variable, die Zugriff auf den Index des Chartfensters bot. Dieser Index wird in dieser Version nicht mehr existieren. Der Grund dafür ist, dass wir die Klasse C_Terminal verwenden werden, um diese Arbeit zu erledigen, die zuvor durch den internen Index der Klasse C_Control erledigt wurde.

Aufgrund dieses Details wurden alle Stellen, an denen auf den Index verwiesen wurde, entfernt. Dort, wo es wirklich nötig war, wurde der Verweis durch eine Definition ersetzt, die uns den Zugriff auf den Index über die Klasse C_Terminal ermöglicht. Schauen wir uns nun den Code des Klassenkonstruktors an. Es ist unten aufgeführt:

C_Controls(C_Terminal *arg)
          :C_Mouse(arg),
           m_bWait(false)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      m_szBtnPlay             = NULL;
      m_Slider.szBarSlider    = NULL;
      m_Slider.szBtnPin       = NULL;
      m_Slider.szBtnLeft      = NULL;
      m_Slider.szBtnRight     = NULL;
   }

Der Konstruktor nimmt nun einen Parameter entgegen, der ein Zeiger auf die Klasse C_Terminal ist. Sie wird an die Klasse C_Mouse übergeben. Aber beachten Sie, dass jetzt C_Mouse NICHT im Modus FULL verwendet wird. Dann werden keine Objekte der Klasse C_Mouse erstellt. Dies liegt daran, dass es uns nur als Unterstützung für die Wiederverwendung von Code dienen wird. Wie auch immer, wir prüfen hier, ob dieser Zeiger gültig ist. Dies ist wichtig, um zu vermeiden, dass etwas verwendet wird, das auf einen ungültigen oder unbekannten Speicherplatz zeigt. Wir haben auch einen Klassendestruktor, den wir ein wenig verändert haben:

~C_Controls()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName);
}

Diese Prüfung verhindert den Versuch, einen ungültigen Zeiger zu verwenden, weil der Konstruktor möglicherweise fehlgeschlagen ist, der Destruktor dies aber nicht weiß. Durch diese Prüfung stellen wir sicher, dass der Destruktor auch weiß, warum der Konstruktor fehlgeschlagen ist. Denn es kann sein, dass der Destruktor nur aufgerufen wird, um die Verwendung der Klasse C_Controls zu beenden. Da wir nun das System verwenden, werden einige Elemente außerhalb dieser Klasse initialisiert, sodass wir eine zusätzliche Änderung am Quellcode vornehmen können.

void Init(const bool state)
{
   if (m_szBtnPlay != NULL) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, true);
   CreateBtnPlayPause(state);
   GlobalVariableTemp(def_GlobalVariableReplay);
   if (!state) CreteCtrlSlider();
   ChartRedraw();
}

Die obigen Zeilen wurden gestrichen, da ihre Funktion bereits an anderer Stelle umgesetzt wurde. Das liegt daran, dass die Klasse C_Mouse die Mausbewegung auslöst und die Klasse C_Terminal der MetaTrader 5-Plattform mitteilt, dass wir benachrichtigt werden wollen, wenn ein Objekt aus dem Chart entfernt wurde. Aus diesen Gründen wurden diese Zeilen entfernt, denn wenn sie im Code verbleiben würden, könnte es in manchen Situationen zu einem seltsamen Verhalten kommen. Denken Sie daran, dass wir niemals Code duplizieren sollten. Dies wird als Fehler angesehen, weil der Code dadurch in einigen Szenarien nicht korrekt ausgeführt werden kann und im Laufe der Zeit sehr schwer zu warten ist.

Der nächste Code, den wir ändern müssen, ist die Ereignisbehandlung in der Klasse. Er ist unten aufgeführt:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
         {
            if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
            {
               RemoveCtrlSlider();
               CreteCtrlSlider();
            }else
            {
               Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
               CreateBtnPlayPause(Info.s_Infos.isPlay);
            }
            ChartRedraw();
         }
         break;
      case CHARTEVENT_OBJECT_CLICK:
         if (m_bWait) break;
         if (sparam == m_szBtnPlay)
         {
            Info.s_Infos.isPlay = (bool) ObjectGetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE);
            if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
            {
               RemoveCtrlSlider();
               m_Slider.szBtnPin = NULL;
            }
            Info.s_Infos.iPosShift = (ushort) m_Slider.posPinSlider;
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            ChartRedraw();
         }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
         else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         if (GetInfoMouse().ExecStudy) return;
         if ((CheckClick(C_Mouse::eClickLeft)) && (m_Slider.szBtnPin != NULL))
         {
            x = GetInfoMouse().Position.X;
            y = GetInfoMouse().Position.Y;
            px1 = m_Slider.posPinSlider + def_PosXObjects + 86;
            px2 = m_Slider.posPinSlider + def_PosXObjects + 114;
            if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
            {
               six = x;
               sps = m_Slider.posPinSlider;
               ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
            }
            if (six > 0) PositionPinSlider(sps + x - six);
         }else if (six > 0)
         {
            six = -1;
            ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
         }
         break;
   }
}

Dieser Code wurde im Vergleich zu früher in einigen Punkten geändert. Was Sie sehen, ist die endgültige Fassung. Auf diese Weise wird es einfacher zu erklären, was hier vor sich geht. Für den Kontrollindikator müssen Sie das Verhalten der Maus kennen. Die alleinige Verwendung des Ereignisses CHARTEVENT_MOUSE_MOVE reicht dafür nicht aus. Wir müssen die Klasse C_Mouse so gestalten, dass sie ihre Aufgabe erfüllt. Denken Sie daran, dass der EA, wie auch der Indikator, die Klasse C_Mouse verwendet, aber keine Informationen darüber austauscht, was die Maus macht.

Ich habe darüber nachgedacht, diese Art des Austauschs zu implementieren, was auch tatsächlich möglich ist. Aber wir haben dabei einige Probleme.

  1. Wenn wir Mausdaten zwischen dem EA und dem Kontrollindikator austauschen, indem wir irgendeine Funktion der MetaTrader 5-Plattform verwenden, kann es zu Problemen bei der Verwendung des EA außerhalb des Replay/Simulationssystems kommen. Es kann auch sein, dass die Kontrollanzeige nicht versteht, was die Maus gerade macht.
  2. Eine andere Möglichkeit wäre die Verwendung eines gemeinsamen Speichers über eine DLL, aber dies würde eine Abhängigkeit von der Plattform oder dem Wiedergabe-/Simulationsdienst schaffen. Ehrlich gesagt, bin ich im Moment nicht daran interessiert, eine solche Abhängigkeit zu schaffen. Die Verwendung von Shared Memory, damit MetaTrader 5 die Klasse C_Mouse nicht selbst verwalten muss, ist also nicht mehr relevant.

Diese Gründe mögen nicht sehr überzeugend erscheinen, aber ich möchte die Vorteile der MQL5-Sprache und der MetaTrader 5-Plattform in vollem Umfang nutzen, um das Wiederholungs-/Simulationssystem aufzubauen. Dies wird zeigen, dass wir viel mehr tun können, als viele Leute denken, dass die Plattform oder die Sprache möglich ist. Daher benötigen wir die Klasse C_Mouse, um auch die Daten für die Maus zu aktualisieren. Dies geschieht, indem Sie von hier aus einen Anruf tätigen. Da wir Mausereignisse über CHARTEVENT_MOUSE_MOVE behandeln werden, können wir zu diesem speziellen Ereignis übergehen.

Als erstes prüfen wir, ob der Nutzer eine Analyse im Chart durchführt. In diesem Fall sollte alles, was mit der Behandlung von Mausereignissen in der Klasse C_Control zu tun hat, ignoriert werden. Von nun an werden wir Mausereignisse nicht mehr lokal behandeln. Stattdessen fragen wir die Klasse C_Mouse, was passiert ist, und treffen dann entsprechende Entscheidungen und Aktionen, um die Wünsche des Nutzers zu erfüllen. Dann prüfen wir, ob der Nutzer die linke Maustaste gedrückt hat und ob das Schiebereglerobjekt im Chart vorhanden ist (Pausenmodus). Wenn ja, wird die Position geprüft, an der der Klick stattgefunden hat. Wenn er auf dem Schieberegler-Objekt (slider) war, wird er entsprechend reagieren, solange die linke Taste gedrückt ist. Auf diese Weise kann der Nutzer das Schieberegler-Objekt wie bisher von einer Seite zur anderen ziehen.

Wir mussten also nur eine Möglichkeit für die C_Control-Klasse hinzufügen, um zu wissen, ob der Nutzer eine Analyse durchführt oder nicht. Ähnliche Elemente könnten auch anders gemacht werden, aber wie zu Beginn des Artikels erwähnt, bietet dies einige Vorteile, auch wenn sie jetzt nicht so offensichtlich sind.


Schlussfolgerung

In diesem Artikel haben wir gesehen, wie Sie Klassen oder Methoden, die ursprünglich für die Verwendung in einem Expert Advisor entwickelt wurden, in einem anderen Programm, in diesem Fall einem Indikator, verwenden können. Die hier geleistete Arbeit wird uns eine bessere Erfahrung bei der Nutzung des Wiederholungs-/Simulationssystems ermöglichen. So kann ein Nutzer, der eine Technik entwickelt oder eine bestimmte Arbeitsweise testen möchte, dies mit Hilfe des in dieser Sequenz entwickelten Systems tun. Es gibt noch ein weiteres Problem, das derzeit gelöst wird. Wir werden im nächsten Artikel darüber sprechen. Denn um die Situation zu verbessern, müssen wir einige Dinge ändern und andere hinzufügen. Und wie alles, was in diesem Artikel vorgestellt wird, kann es für diejenigen, die gerade erst mit dem Programmieren beginnen, schon sehr schwierig sein. Ich werde den Schwierigkeitsgrad nicht ohne Grund erhöhen.

In der Anlage finden Sie den vollständigen Systemcode. Außerdem finden Sie am Ende des Artikels drei Dateisätze, mit denen Sie das System in verschiedenen Szenarien testen können. Durch regelmäßiges Testen können Sie etwas mehr über die Entwicklung des Systems erfahren.


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

Beigefügte Dateien |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Alternative Risiko-Ertrags-Metriken in MQL5 Alternative Risiko-Ertrags-Metriken in MQL5
In diesem Artikel stellen wir die Umsetzung mehrere Risikorenditekennzahlen vor, die als Alternativen zur Sharpe-Ratio angepriesen werden, und untersuchen hypothetische Aktienkurven, um ihre Eigenschaften zu analysieren.
Entwicklung eines Replay System (Teil 34): Auftragssystem (III) Entwicklung eines Replay System (Teil 34): Auftragssystem (III)
In diesem Artikel werden wir die erste Phase der Konstruktion abschließen. Obwohl dieser Teil recht schnell erledigt ist, werde ich auf Details eingehen, die zuvor nicht besprochen wurden. Ich werde einige Punkte erklären, die viele nicht verstehen. Wissen Sie, warum Sie die Umschalttaste oder die Strg-Taste drücken müssen?
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.
Algorithmen zur Optimierung mit Populationen: Mikro-Künstliches Immunsystem (Mikro-AIS) Algorithmen zur Optimierung mit Populationen: Mikro-Künstliches Immunsystem (Mikro-AIS)
Der Artikel befasst sich mit einer Optimierungsmethode, die auf den Prinzipien des körpereigenen Immunsystems basiert - Mikro-Künstliches Immunsystem (Micro Artificial Immune System, Micro-AIS) - eine Modifikation von AIS. Micro-AIS verwendet ein einfacheres Modell des Immunsystems und einfache Informationsverarbeitungsprozesse des Immunsystems. In dem Artikel werden auch die Vor- und Nachteile von Mikro-AIS im Vergleich zu herkömmlichen AIS erörtert.