English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay-Systems — Marktsimulation (Teil 07): Erste Verbesserungen (II)

Entwicklung eines Replay-Systems — Marktsimulation (Teil 07): Erste Verbesserungen (II)

MetaTrader 5Beispiele | 4 Oktober 2023, 11:40
296 0
Daniel Jose
Daniel Jose

Einführung

Im vorigen Artikel Entwicklung eines Replay Systems - Marktsimulation (Teil 06): Erste Verbesserungen (I) haben wir einige Dinge korrigiert und Tests zu unserem Replay System hinzugefügt. Diese Tests zielen darauf ab, die höchstmögliche Stabilität zu gewährleisten. Gleichzeitig haben wir mit der Erstellung und Verwendung einer Konfigurationsdatei für das System begonnen.

Trotz unserer Bemühungen, ein intuitives und stabiles Replay System zu schaffen, gibt es noch einige ungelöste Probleme. Die Lösungen für einige dieser Probleme sind einfach, andere sind komplizierter.

Wenn Sie das Video am Ende des vorherigen Artikels gesehen haben, sind Ihnen wahrscheinlich einige Mängel im System aufgefallen, die noch verbessert werden müssen. Ich habe beschlossen, diese Mängel sichtbar zu machen, damit jeder versteht, dass es noch viele Bereiche gibt, die verbessert und korrigiert werden müssen, bevor eine intensivere Nutzung möglich ist. Das bedeutet nicht, dass das System nicht für die Praxis genutzt werden kann.

Ich möchte jedoch keine neuen Funktionen hinzufügen, ohne vorher alle Nuancen zu beseitigen, die zu einer Instabilität des Systems oder der Plattform führen könnten. Ich denke, jemand wird Replay und Praxis verwenden wollen, während der Markt geöffnet ist, warten auf ein echtes Geschäft zu erscheinen oder mit einem automatisierten EA in der Plattform.

Wenn das Replay System jedoch nicht stabil genug ist, ist es nicht praktikabel, es laufen zu lassen, während der Markt geöffnet ist, wenn die Möglichkeit besteht, dass ein echter Handel stattfinden könnte. Diese Warnung bezieht sich auf die Tatsache, dass das Replay System Ausfälle verursachen oder den normalen Betrieb anderer Plattformfunktionen beeinträchtigen kann.

Da wir weiterhin an der Verbesserung der Stabilität und des Komforts der Marktwiedergabe arbeiten, werden wir einige zusätzliche Ressourcen in den Code einfügen, um das System weiter zu verbessern.


Sicherstellen, dass der Kontrollindikator auf der Karte bleibt

Die erste Verbesserung, die wir einführen werden, wird der Kontrollindikator sein. Derzeit wird beim Start der Wiedergabe eine Vorlage geladen. Dieses Template muss den Indikator enthalten, der zur Steuerung des Replay System verwendet wird. Bis zu diesem Punkt ist alles in Ordnung: Fehlt dieser Indikator, sind Manipulationen mit dem Wiedergabedienst unmöglich. Daher ist dieser Indikator für den Dienst äußerst wichtig.

Nach dem Herunterladen und bis jetzt konnten wir jedoch nicht garantieren, dass dieser Indikator auf dem Chart bleibt. Jetzt wird sich die Situation ändern, und wir werden dafür sorgen, dass sie auf der Karte bleibt. Wenn der Indikator aus irgendeinem Grund entfernt wird, muss der Wiedergabedienst sofort beendet werden.

Aber wie können wir das tun? Wie kann man erreichen, dass der Indikator im Chart bleibt, oder wie kann man überprüfen, ob er tatsächlich vorhanden ist? Es gibt mehrere Möglichkeiten, aber die einfachste und eleganteste ist meiner Meinung nach die Verwendung der MetaTrader 5-Plattform selbst für die Überprüfung durch die MQL5-Sprache.

In der Regel gibt es keine spezifischen Ereignisse im Indikator, aber es gibt eines, das für uns besonders nützlich ist: das Ereignis DeInit

Dieses Ereignis wird ausgelöst, wenn etwas passiert und der Indikator geschlossen werden muss, und dann sofort durch das Ereignis OnInit ausgelöst. Können wir also beim Aufruf der Funktion OnDeInit dem Wiedergabedienst mitteilen, dass der Indikator entfernt wurde? Das ist möglich, aber es gibt einen wichtigen Punkt. Das Ereignis OnDeInit wird nicht nur aufgerufen, wenn ein Indikator gelöscht oder ein Chart geschlossen wird.

Er wird auch ausgelöst, wenn sich die Periode des Charts ändert oder die Parameter des Indikators geändert werden. Es sieht also so aus, als würden die Dinge wieder kompliziert werden. Wenn wir uns jedoch die Dokumentation für das Ereignis OnDeInit ansehen, werden wir sehen, welche Codes der Deinitialisierung wir verwenden können.

Wenn wir uns diese Codes ansehen, stellen wir fest, dass zwei von ihnen sehr nützlich sind, um anzuzeigen, dass der Kontrollindikator aus der Karte entfernt wurde. Lassen Sie uns also den folgenden Code erstellen:

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        break;
        }
}


Hier wird geprüft, ob das Entfernen des Indikators aus dem Chart das DeInit-Ereignis (und dementsprechend die OnDeInit-Funktion) ausgelöst hat. In diesem Fall müssen wir dem Wiedergabedienst mitteilen, dass der Indikator nicht mehr auf dem Chart läuft und dass der Dienst sofort beendet werden muss.

Dazu müssen Sie die globale Variable aus dem Terminal entfernen, die die Verbindung zwischen dem Kontrollindikator und dem Wiedergabedienst darstellt. Sobald wir diese Variable entfernen, versteht der Wiedergabedienst, dass die Aktivität beendet ist und schließt sie.

Wenn Sie sich den Servicecode ansehen, werden Sie feststellen, dass der Chart mit dem Symbol der Wiedergabe ebenfalls geschlossen wird, wenn es geschlossen wird. Dies geschieht genau dann, wenn der Dienst den folgenden Code ausführt:

void CloseReplay(void)
{
        ArrayFree(m_Ticks.Info);
        ChartClose(m_IdReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
}

Wenn Sie also den Kontrollindikator löschen, wird das Chart zusammen mit dem Indikator geschlossen, da der Wiedergabedienst ihn zwangsweise schließt. Es kann jedoch sein, dass der Dienst keine Zeit hat, das Chart mit dem Symbol der Wiedergabe zu schließen. In diesem Fall müssen wir sicherstellen, dass dieses Chart geschlossen wird, auch wenn das Symbol im Marktbeobachtungsfenster verbleibt und der Wiedergabedienst es aus irgendeinem Grund nicht löschen kann, trotz aller Versuche.

Dazu fügen wir eine weitere Zeile in die Funktion OnDeInit des Kontrollindikators ein.

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}


Nun geschieht Folgendes: Sobald der Kontrollindikator aus irgendeinem Grund aus dem Chart entfernt wird, aber der Wiedergabedienst das Chart nicht schließen kann, versucht der Indikator selbst, es zu schließen. Das mag vielleicht ein wenig kontraintuitiv erscheinen, aber ich möchte, dass das Chart wie der Wiedergabedienst die Plattform entlastet und keine Unannehmlichkeiten im Falle eines Ausfalls oder Fehlers verursacht.

Mit dieser Implementierung haben wir zumindest die Garantie, dass, wenn der Kontrollindikator aus dem Chart entfernt wird, wird der Dienst beendet und das Chart geschlossen. Wir haben jedoch ein weiteres Problem, das mit dem Indikator zusammenhängt.


Verhindern, dass der Kontrollindikator gelöscht wird

Dies ist ein schwerwiegendes Problem, da es passieren kann, dass der Indikator zwar im Chart verbleibt, aber seine Bestandteile einfach entfernt oder zerstört werden, wodurch der Indikator nicht mehr korrekt verwendet werden kann.

Glücklicherweise ist diese Situation recht einfach zu beheben. Dieser Moment erfordert jedoch besondere Sorgfalt, damit er in Zukunft nicht zu einer Quelle von Problemen wird. Indem wir die Zerstörung des Indikators oder die Entfernung seiner Elemente und Objekte verhindern, können wir ein unkontrollierbares Monster schaffen, das eine Menge Probleme verursacht. Um dieses Problem zu lösen, werden wir das Ereignis der Objektzerstörung im Chart abfangen und verarbeiten. Schauen wir uns an, wie das in der Praxis abläuft.

Beginnen wir mit dem Hinzufügen der folgenden Codezeile in die Klasse C_Control:

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


Indem wir diese Codezeile hinzufügen, bitten wir MetaTrader 5, uns ein Ereignis zu senden, wenn das Chart-Objekt vom Bildschirm entfernt wird. Die einfache Erfüllung dieser Bedingung garantiert nicht, dass Objekte nicht gelöscht werden, aber sie garantiert zumindest, dass die MetaTrader 5 Plattform uns benachrichtigt, wenn dies geschieht.

Um sicherzustellen, dass die Objekte gelöscht werden, wenn die Klasse C_Control gelöscht wird, müssen wir MetaTrader 5 mitteilen, wann kein Objektentfernungsereignis gesendet werden soll. Eine der Stellen, an denen diese Art von Funktion verwendet wird, ist unten dargestellt:

~C_Controls()
{
        m_id = (m_id > 0? m_id : ChartID());
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_PrefixObjectName);
}

Auf diese Weise teilen wir MetaTrader 5 mit, dass wir NICHT wollen, dass er uns ein Ereignis sendet, wenn ein Objekt aus dem Chart entfernt wird, und wir können die notwendigen Objekte ohne weitere Probleme entfernen.

Doch so einfach ist es nicht, und es gibt ein potenzielles Problem. Schauen wir uns den folgenden Code 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_OBJECT_CLICK:
                        if (sparam == m_szBtnPlay)
                        {
                                Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                {
                                        ObjectsDeleteAll(m_id, def_PrefixObjectName + "Slider");
                                        m_Slider.szBtnPin = NULL;
                                }
                                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.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;

// ... The rest of the code....

In dieser Zeile muss die Positionskontrollleiste entfernt werden, was das Ereignis zum Entfernen des Objekts auslöst.

Man könnte meinen, dass wir das Ereignis einfach ausschalten und dann das Bedienfeld entfernen und wieder einschalten können. Das ist richtig, aber bedenken Sie, dass diese Ein- und Ausschaltvorgänge mit zunehmender Menge an Code viel häufiger vorkommen, als es auf den ersten Blick scheint. Und noch etwas: Für eine korrekte Darstellung müssen Sie die Objekte in einer bestimmten Reihenfolge anordnen.

Daher garantiert das einfache Ein- und Ausschalten eines Löschereignisses nicht, dass das Ereignis korrekt behandelt wird. Wir müssen eine elegantere und robustere Lösung entwickeln, die die korrekte Reihenfolge der Objekte beibehält, sodass ihre Darstellung immer gleich ist und der Nutzer den Unterschied im Positionierungssystem nicht bemerkt.

Die einfachste Lösung besteht darin, eine Funktion zu erstellen, die das Ereignis „delete“ (lösachen) ausschaltet, Objekte in derselben Kette löscht und dann das Ereignis „delete“ wieder einschaltet. Dies lässt sich mit dem folgenden Code, der diese Aufgabe in der Kontrollleiste übernimmt, leicht umsetzen.

inline void RemoveCtrlSlider(void)
{                       
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_NameObjectsSlider);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
}


Jedes Mal, wenn wir nur das Bedienfeld entfernen müssen, rufen wir diese Funktion auf und erhalten das gewünschte Ergebnis.

Obwohl dies trivial erscheinen mag, wird dieses Verfahren beim derzeitigen Stand der Dinge nicht nur einmal, sondern zweimal in derselben Funktion verwendet, wie unten dargestellt:

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_OBJECT_DELETE:
                                if(StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
                                {
                                        if(StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
                                        {
                                                RemoveCtrlSlider();
                                                CreteCtrlSlider();
                                        }else
                                        {
                                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                                CreateBtnPlayPause(Info.s_Infos.isPlay);
                                        }
                                        ChartRedraw();
                                }
                                break;
                        case CHARTEVENT_OBJECT_CLICK:
                                if (sparam == m_szBtnPlay)
                                {
                                        Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                        if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                        {
                                                RemoveCtrlSlider();
                                                m_Slider.szBtnPin = NULL;
                                        }
                                        Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                        GlobalVariableSet(def_GlobalVariableReplay, Info.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:
                                x =(int)lparam;
                                y =(int)dparam;
                                px1 = m_Slider.posPinSlider + def_MinPosXPin - 14;
                                px2 = m_Slider.posPinSlider + def_MinPosXPin + 14;
                                if ((((uint)sparam & 0x01) == 1) && (m_Slider.szBtnPin != NULL))
                                {
                                        if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six ==-1))
                                        {
                                                6 = x;
                                                sps = m_Slider.posPinSlider;
                                                ChartSetInteger(m_id, CHART_MOUSE_SCROLL, false);
                                        }
                                        if (six > 0) PositionPinSlider(sps + x - six);
                                }else if (6 > 0)
                                {
                                        6 =-1;
                                        ChartSetInteger(m_id, CHART_MOUSE_SCROLL, true);
                                }
                                break;
                }
        }


Schauen wir uns den Teil, der für die Verarbeitung von Objektlöschungsereignissen zuständig ist, genauer an. Wenn wir der MetaTrader 5-Plattform mitteilen, dass wir Ereignisse erhalten möchten, wenn ein Objekt aus dem Chart entfernt wird, erzeugt sie für jedes gelöschte Objekt ein Löschereignis. Wir erfassen dann dieses Ereignis und können überprüfen, welches Objekt gelöscht wurde. 

Ein wichtiges Detail ist, dass Sie nicht sehen werden, welches Objekt gelöscht wird, sondern welches Objekt tatsächlich gelöscht wurde. In unserem Fall prüfen wir, ob es sich um einen der vom Kontrollindikator verwendeten Indikatoren handelt. Wenn ja, überprüfen wir noch einmal, ob es sich um eines der Objekte auf dem Bedienfeld oder um eine Schaltfläche handelt. Wenn es sich um eines der Objekte handelt, die Teil des Bedienfelds sind, wird das Bedienfeld vollständig entfernt und sofort neu erstellt. 

Wir brauchen dieser Erstellungsfunktion nichts mitzuteilen, da sie die ganze Arbeit selbst erledigt. Bei der Steuertaste ist die Situation etwas anders. In diesem Fall müssen wir die globale Variable des Terminals auslesen, um den aktuellen Zustand der Schaltfläche herauszufinden, bevor wir ihre Erstellung anfordern können.

Schließlich erzwingen wir, dass alle Objekte auf dem Chart platziert werden, sodass der Nutzer nicht einmal bemerkt, dass sie entfernt wurden.

Wir tun dies, um sicherzustellen, dass alles an seinem Platz ist. Sehen wir uns nun etwas anderes an, das ebenfalls für den Betrieb des Wiedergabedienstes wichtig ist.


Nur eine Wiederholungstafel

Bei der Arbeit mit dem System, das automatisch Charts öffnet, kommt es oft vor, dass es anfängt, Charts desselben Symbols zu öffnen, und nach einer Weile verstehen wir nicht mehr, womit wir es genau zu tun haben.

Um dies zu vermeiden, habe ich einen kleinen Test implementiert, der das Problem löst, wenn das Replay System einen Chart nach dem anderen öffnet, um den Markt erneut abzuspielen. Das Vorhandensein dieser Funktion garantiert auch eine gewisse Stabilität in Bezug auf die in der globalen Endvariablen enthaltenen Werte.

Wenn wir mehrere Charts haben, die dieselbe Idee widerspiegeln, in diesem Fall die Marktwiederholung, dann kann es sein, dass einer der Charts den angegebenen Wert hat, der vom Kontrollindikator erstellt wurde, und der andere einen völlig anderen Wert hat. Obwohl dieses Problem noch nicht vollständig gelöst ist, wird die einfache Tatsache, dass wir nicht mehr mehrere Charts haben, die gleichzeitig auf dasselbe Symbol verweisen, eine Menge Vorteile bringen.

Der folgende Code zeigt, wie Sie sicherstellen können, dass nur ein Chart für ein bestimmtes Instrument geöffnet wird:

long ViewReplay(ENUM_TIMEFRAMES arg1)
{
        if ((m_IdReplay = ChartFirst()) > 0) do
        {
                if(ChartSymbol(m_IdReplay) == def_SymbolReplay) ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}


Hier prüfen wir, ob ein offener Chart im MetaTrader 5 Plattform-Terminal vorhanden ist. Wenn es eine gibt, verwenden wir sie als Ausgangspunkt, um zu prüfen, welches Symbol offen ist. Wenn das Symbol dasjenige ist, das für die Marktwiedergabe verwendet wird, schließen wir diesen Chart.

Das Problem ist, dass wir zwei Möglichkeiten haben, wenn das Chart des Wiedergabesymbols geöffnet ist: Die erste ist, das Chart zu schließen, und genau das tun wir auch. Die zweite Möglichkeit besteht darin, die Schleife zu beenden, aber in diesem zweiten Fall kann es vorkommen, dass mehr als ein Chart desselben Assets geöffnet ist. Daher ziehe ich es vor, alle offenen Charts zu schließen; wir werden dies tun, bis der letzte Chart überprüft ist. Infolgedessen werden wir keinen Zeitplan für die Marktwiedergabe auf dem offenen Markt haben.

Als Nächstes müssen wir den Chart öffnen, der das Replay System des Marktes enthält, die Vorlage anwenden, damit der Kontrollindikator verwendet werden kann, die Anzeige des Charts erzwingen und den Index des geöffneten Charts zurückgeben.

Aber nichts hindert uns daran, ein neues Chart für das Wiedergabesymbol zu öffnen, nachdem das System bereits geladen ist. Wir könnten dem Dienst einen zusätzlichen Test hinzufügen, sodass nur ein Chart während des gesamten Wiederholungszeitraums geöffnet bleibt. Ich weiß jedoch, dass es Händler gibt, die gerne mehrere Charts desselben Symbols gleichzeitig verwenden. Jedes der Charts verwendet sein eigenes Zeitintervall.

Aus diesem Grund werden wir diesen zusätzlichen Test nicht hinzufügen, sondern etwas anderes tun. Das Vorhandensein und der Betrieb des Kontrollindikators auf einer anderen als der vom Dienst geöffneten Karte wird nicht zugelassen. Nun, wir könnten den Indikator im ursprünglichen Chart beenden, indem wir versuchen, ihn durch ein anderes Chart zu ersetzen. Das Chart würde jedoch geschlossen und der Wiedergabedienst angehalten, sodass die Änderung nicht vorgenommen werden könnte.

Im nächsten Thema werden wir uns ansehen, wie man sicherstellt, dass der Kontrollindikator nicht in einem anderen als dem ursprünglichen Chart geöffnet wird.


Nur eine Kontrollanzeige pro Sitzung

Dieser Teil ist recht interessant und kann Ihnen in einigen Fällen helfen. Schauen wir uns an, wie man sicherstellt, dass der Indikator nur zu einem Chart in einer MetaTrader 5-Arbeitssitzung gehört.

Um zu verstehen, wie das geht, schauen wir uns den folgenden Code an:

int OnInit()
{
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        if(GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}


Dieser Code prüft, ob eine globale Terminalvariable vorhanden ist. Wenn eine solche Variable vorhanden ist, werden wir sie zur späteren Verwendung erfassen. Wenn keine Variable vorhanden ist, wird sie initialisiert.

Es gibt jedoch ein wichtiges Detail: Die Funktion OnInit wird immer dann aufgerufen, wenn etwas passiert, entweder im Chart oder bei der Aktualisierung der Indikatorparameter. In diesem Fall enthält der Indikator keine Parameter und erhält sie auch nicht. Wir haben also nur Chartreignisse, die jedes Mal auftreten, wenn sich das Zeitintervall des Charts ändert, d.h. wenn wir von 5 Minuten auf 4 Minuten wechseln, wird OnInit aufgerufen. Wenn wir in diesem Fall einfach die Initialisierung des Indikators blockieren, wenn es eine globale Terminalvariable gibt, dann haben wir ein Problem. Der Grund dafür ist, dass das Chart geschlossen wird, was bedeutet, dass der Dienst beendet wird. Es ist schwierig, nicht wahr?

Aber die Lösung, die wir verwenden werden, wird recht einfach und gleichzeitig sehr elegant sein. Wir werden die globale Terminal-Variable verwenden, um zu wissen, ob ein Kontrollindikator bereits in einem Chart vorhanden ist oder nicht. Wenn er existiert, kann er nicht auf einem anderen Chart platziert werden, solange er auf einem offenen Chart in der aktuellen MetaTrader 5-Sitzung vorhanden ist.

Um dies zu realisieren, müssen wir den für die Kommunikation zwischen den Prozessen verwendeten Code bearbeiten.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableReplay "Replay Infos".
//+------------------------------------------------------------------+
#define def_MaxPosSlider 400
//+------------------------------------------------------------------+
union u_Interprocess
{
        double Value;
        struct st_0
        {
                bool isPlay; // Specifies in which mode we are - Play or Pause ...
                bool IsUsing; // Specifies if the indicator is running or not ...
                int iPosShift; // Value from 0 to 400 ...
        }s_Infos;
};


Denken Sie daran, dass wir der Struktur interne Variablen hinzufügen können, solange sie die 8-Byte-Grenze nicht überschreiten, d. h. die Größe, die eine Double-Variable im Speicher einnimmt. Aber da der boolesche Typ nur 1 Bit benötigt, das die Variable isPlay verwendet, und wir noch 7 freie Bits im Byte haben, können wir leicht 7 weitere boolesche Werte hinzufügen. Daher werden wir eines dieser 7 freien Bits verwenden, um herauszufinden, ob es einen Kontrollindikator in einem beliebigen Chart gibt oder nicht.

HINWEIS: Obwohl dieser Mechanismus durchaus angemessen ist, gibt es ein Problem. Aber darüber wollen wir vorerst nicht sprechen. Wir werden dieses Problem in einem anderen Artikel in der Zukunft behandeln, wenn es notwendig ist, Änderungen an der Struktur vorzunehmen.

Man könnte also meinen, dass dies ausreichend ist. Aber wir müssen dem Code etwas hinzufügen. Wir werden uns jedoch nicht um den Servicecode kümmern, sondern nur den Indikatorcode so ändern, dass die hinzugefügte Variable für uns tatsächlich nützlich ist.

Als Erstes müssen wir dem Indikatorcode ein paar zusätzliche Zeilen hinzufügen.

void Init(const bool state = false)
{
        u_Interprocess Info;
                                
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        Info.s_Infos.IsUsing = true;
        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
}


Hier teilen wir mit und vermerken in der globalen Terminalvariablen, dass der Kontrollindikator erstellt wurde. Aber warum brauchen wir den vorherigen Aufruf, um eine globale Terminalvariable zu erstellen? Können wir das nicht auslassen? Dieser erste Aufruf dient eigentlich dazu, der MetaTrader 5-Plattform mitzuteilen, dass die globale Variable temporär ist und nicht beibehalten werden soll. Auch wenn Sie die Plattform auffordern, die Daten globaler Terminalvariablen zu speichern, werden die Werte dieser Variablen, die als temporär gelten, nicht gespeichert und gehen verloren.

Genau das brauchen wir, denn wenn wir globale Terminalvariablen speichern und wieder zurücksetzen müssen, ist es nicht sinnvoll, eine Variable zu haben, die das Vorhandensein eines Kontrollindikators meldet, obwohl es in Wirklichkeit keinen gibt. Aus diesem Grund müssen wir die Dinge auf diese Weise tun.

Sie sollten in dieser Angelegenheit vorsichtig sein. Denn wenn die Plattform den Indikator auf dem Chart neu positioniert, kann der Wert der globalen Terminalvariable anders sein, weil wir in der Wiedergabe bereits weitergekommen sind. Und wenn wir diese Zeile nicht haben, wird das System das Replay System von Grund auf neu starten.

Wir müssen noch etwas mehr tun.

case CHARTEVENT_OBJECT_CLICK:
        if (sparam == m_szBtnPlay)
        {
                Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                {
                        RemoveCtrlSlider();
                        m_Slider.szBtnPin = NULL;
                }
                Info.s_Infos.IsUsing = true;
                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                GlobalVariableSet(def_GlobalVariableReplay, Info.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;


Jedes Mal, wenn die Pause/Play-Taste gedrückt wird, ändert sich der Wert der globalen Terminalvariablen. Aber mit dem vorherigen Code enthält der gespeicherte Wert beim Anklicken der Schaltfläche nicht mehr den Hinweis, dass der Kontrollindikator im Chart vorhanden ist. Aus diesem Grund müssen wir eine Codezeile hinzufügen. Damit erhalten wir eine korrekte Anzeige, da der Wechsel von der Pose zum Spiel und umgekehrt keine falsche Anzeige erzeugt.

Der Teil, der sich auf die Klasse C_Replay bezieht, ist abgeschlossen, aber wir haben noch ein wenig mehr Arbeit vor uns. Die bloße Schaffung eines Hinweises garantiert nichts anderes als die bloße Tatsache seiner Existenz. Kommen wir nun zum Code des Indikators. Hier sollten Sie ein wenig vorsichtiger sein, damit alles richtig funktioniert und nicht zu einem seltsamen Verhalten führt.

Achten wir also auf die Details. Das erste, worauf Sie achten sollten, ist der Code in OnInit:

int OnInit()
{
#define def_ShortName "Market Replay"
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if(GlobalVariableCheck(def_GlobalVariableReplay))
        {
                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                if (Info.s_Infos.IsUsing)
                {
                        ChartIndicatorDelete(ChartID(), 0, def_ShortName);
                        return INIT_FAILED;
                }
        } else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
#undef def_ShortName
}


Aus praktischen Gründen haben wir hier eine Definition erstellt, die den Namen des Indikators angibt: Dieser Name ermöglicht es uns, den Indikator aus der Liste der Indikatoren in dem Fenster zu entfernen, in dem wir sehen können, welche Indikatoren sich tatsächlich im Chart befinden. Auch diejenigen, die nicht visualisiert sind, werden in diesem Fenster angezeigt. Wir wollen nicht, dass dort irgendwelche nutzlosen Indikatoren übrig bleiben.

Nachdem wir den Indikator aus dem Chart entfernt haben, müssen wir daher herausfinden, ob er bereits in einem anderen Chart vorhanden ist. Dazu überprüfen wir den Wert, der in der globalen Variable terminal angelegt wurde. Dies macht die Kontrolle sehr einfach und effizient. Es gibt auch andere Möglichkeiten, diese Prüfung durchzuführen, aber da wir eine globale Terminalvariable verwenden, ist es einfacher, sie zu überprüfen.

Der Rest der Funktion bleibt unverändert, aber es wird nicht mehr möglich sein, einen Kontrollindikator zu mehr als einem Chart in einer einzigen MetaTrader 5-Sitzung hinzuzufügen. Hier und im beigefügten Code haben wir keine Warnung hinzugefügt, dass der Indikator bereits in einem anderen Chart existiert, aber Sie können diese Warnung hinzufügen, bevor die Funktion einen Initialisierungsfehler zurückgibt.

Das scheint genug zu sein, aber es gibt noch etwas, das wir in Ordnung bringen müssen. Es sollte daran erinnert werden, dass jedes Mal, wenn MetaTrader 5 eine Anfrage zur Änderung des Zeitrahmens erhält, was vielleicht das häufigste Ereignis auf der Plattform ist, alle Indikatoren, wie viele andere Dinge, entfernt und dann zurückgesetzt werden.

Denken Sie nun über Folgendes nach. Wenn der Indikator über die globale Variable mitteilt, dass eine Kopie davon auf einem beliebigen Chart ausgeführt wird, und Sie den Zeitrahmen dieses spezifischen Charts ändern, wird der Indikator entfernt. Aber wenn die MetaTrader 5-Plattform den Indikator wieder in das Chart einfügt, kann er nicht mehr im Chart platziert werden. Der Grund dafür ist genau das, was wir im Code der OnInit-Funktion gesehen haben. Wir müssen die globale Terminalvariable irgendwie bearbeiten, damit sie das Vorhandensein eines Kontrollindikators nicht mehr meldet.

Es gibt recht exotische Möglichkeiten, dieses Problem zu lösen, aber auch hier bietet die MetaTrader 5-Plattform zusammen mit der MQL5-Sprache recht einfache Mittel zur Lösung. Schauen wir uns den folgenden Code an:

void OnDeinit(const int reason)
{
        u_Interprocess Info;
        
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        if(GlobalVariableCheck(def_GlobalVariableReplay))
                        {
                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.IsUsing = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                        }
                        break;
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}


Wie wir uns erinnern, wird beim Löschen eines Indikators das Ereignis DeInit erzeugt, das die Funktion OnDeInit aufruft. Diese Funktion erhält als Parameter einen Wert, der den Grund des Anrufs angibt. Dies ist der Wert, den wir verwenden werden.

Dieser Wert ist in den Codes der Deinitialisierungsgründe zu sehen. Hier sehen wir, dass REASON_CHARTCHANGE anzeigt, dass sich der Zeitraum des Charts geändert hat. Also machen wir einen Check - es ist immer gut, die Dinge zu überprüfen. Bilden Sie sich nichts ein und nehmen Sie nichts an, sondern überprüfen Sie es immer, ob es eine globale Terminalvariable mit dem erwarteten Namen gibt. Wenn dies der Fall ist, wird der Wert der Variablen erfasst. Da der Dienst vielleicht gerade etwas tut und wir ihn nicht stören wollen, ändern wir hier die Information, dass der Kontrollindikator nicht mehr im Chart zu sehen sein wird. Anschließend werden die Informationen in die globale Terminalvariable zurückgeschrieben.

An dieser Stelle muss ich vor einer möglichen Schwachstelle dieses Systems warnen. Auch wenn die Wahrscheinlichkeit, dass etwas schief geht, gering ist, sollten Sie immer wissen, dass es einen Fehler in der Methode gibt, und sich daher auf mögliche Probleme vorbereiten.

Das Problem dabei ist, dass zwischen dem Lesen und Schreiben der Variablen eine kleine Lücke entsteht. Er ist zwar klein, aber er existiert, wenn der Dienst einen Wert in eine globale Terminalvariable schreiben kann, bevor der Indikator dies tut. Wenn ein solches Ereignis eintritt, weicht der Wert, den der Dienst beim Zugriff auf die globale Terminalvariable erwartet, von dem ab, der tatsächlich in der Variablen enthalten ist.

Es gibt Möglichkeiten, diese Schwachstelle zu umgehen, aber in diesem System, das mit Market Replay arbeitet, ist sie nicht entscheidend. Wir können diesen Fehler also ignorieren. Wenn Sie jedoch denselben Mechanismus in etwas Komplexerem verwenden möchten, bei dem die gespeicherten Werte kritisch sind, empfehle ich Ihnen, mehr darüber zu erfahren, wie man das Lesen und Schreiben des gemeinsamen Speichers sperrt und entsperrt. Nun, die globale Terminalvariable ist genau der gemeinsame Speicher.

Im folgenden Video können Sie sich einen Eindruck davon verschaffen, was bereits behoben wurde und was noch zu tun ist. Jetzt werden die Dinge ernster.




Schlussfolgerung

Obwohl das hier beschriebene System ideal zu sein scheint, um Störungen zu beseitigen, die durch den Missbrauch von Kontrollanzeigen verursacht werden, ist es dennoch keine wirklich wirksame Lösung, da es nur einen Teil der Probleme vermeidet, die tatsächlich auftreten können.

Ich denke, nachdem Sie das Video gesehen haben, werden Sie feststellen, dass wir ein weiteres Problem lösen müssen, das viel komplizierter ist, als es auf den ersten Blick scheint.


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

Beigefügte Dateien |
Market_Replay.zip (13057.92 KB)
Neuronale Netze leicht gemacht (Teil 38): Selbstüberwachte Erkundung bei Unstimmigkeit (Self-Supervised Exploration via Disagreement) Neuronale Netze leicht gemacht (Teil 38): Selbstüberwachte Erkundung bei Unstimmigkeit (Self-Supervised Exploration via Disagreement)
Eines der Hauptprobleme beim Verstärkungslernen ist die Erkundung der Umgebung. Zuvor haben wir bereits die Forschungsmethode auf der Grundlage der intrinsischen Neugier kennengelernt. Heute schlage ich vor, einen anderen Algorithmus zu betrachten: Erkundung bei Unstimmigkeit.
Kategorientheorie in MQL5 (Teil 16): Funktoren mit mehrschichtigen Perceptrons Kategorientheorie in MQL5 (Teil 16): Funktoren mit mehrschichtigen Perceptrons
In diesem Artikel, dem 16. in unserer Reihe, geht es weiter mit einem Blick auf Funktoren und wie sie mit künstlichen neuronalen Netzen implementiert werden können. Wir weichen von unserem bisherigen Ansatz der Volatilitätsprognose ab und versuchen, eine nutzerdefinierte Signalklasse zum Setzen von Ein- und Ausstiegssignalen zu implementieren.
Kategorientheorie in MQL5 (Teil 17): Funktoren und Monoide Kategorientheorie in MQL5 (Teil 17): Funktoren und Monoide
Dieser Artikel, der letzte in unserer Reihe zum Thema Funktoren, befasst sich erneut mit Monoiden als Kategorie. Monoide, die wir in dieser Serie bereits vorgestellt haben, werden hier zusammen mit mehrschichtigen Perceptrons zur Unterstützung der Positionsbestimmung verwendet.
Verbessern Sie Ihre Handelscharts mit interaktiven GUI's in MQL5 (Teil III): Ein einfaches, bewegliches Handels-GUI Verbessern Sie Ihre Handelscharts mit interaktiven GUI's in MQL5 (Teil III): Ein einfaches, bewegliches Handels-GUI
Begleiten Sie uns in Teil III der Serie „Verbessern Sie Ihre Handelscharts mit interaktiven GUIs in MQL5“, wenn wir die Integration interaktiver GUIs in bewegliche Handels-Dashboards in MQL5 untersuchen. Dieser Artikel baut auf den Grundlagen von Teil I und II auf und leitet die Leser an, statische Handels-Dashboards in dynamische, bewegliche Dashboards umzuwandeln.