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

Entwicklung eines Replay-Systems — Marktsimulation (Teil 09): Nutzerdefinierte Ereignisse

MetaTrader 5Beispiele | 9 November 2023, 09:47
244 0
Daniel Jose
Daniel Jose

Einführung

Im vorigen Artikel „Entwicklung eines Wiedergabesystems — Marktsimulation (Teil 08): Sperren der Anzeige“, habe ich Ihnen gezeigt, wie Sie die Kontrollanzeige sperren können. Obwohl es uns gelungen ist, dieses Ziel zu erreichen, gibt es noch einige Aspekte, die angegangen werden müssen. Wenn Sie genau hingesehen haben, ist Ihnen wahrscheinlich aufgefallen, dass jedes Mal, wenn wir den Startpunkt der Wiedergabe/Simulation ändern, eine kurze Präsentation der Handelsbalken angezeigt wird, die gerade erstellt werden. Das ist in gewisser Weise nicht wirklich problematisch, für die einen mag es sogar interessant sein, für die anderen nicht so sehr. Jetzt werden wir versuchen, die Griechen und Trojaner zufrieden zu stellen. Schauen wir uns an, wie man den Replay/Simulator-Dienst so implementiert, dass er am besten für Sie funktioniert. Mit anderen Worten: Sie werden sehen können, ob die Balken erstellt werden oder nicht.


Stell wir die Griechen und die Trojaner zufrieden

Der erste Schritt besteht darin, eine neue Variable oder einen neuen Parameter in die Servicedatei aufzunehmen:

input string            user00 = "Config.txt";  //Replay configuration file
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Starting time
input bool              user02 = true;          //Visualize construction of bars


Hier beginnen wir mit dem Prozess, der es dem Nutzer ermöglicht, eine Entscheidung zu treffen. Wie wir bereits sagten, gibt es Menschen, die gerne zusehen, wie Balken erstellt werden, während es andere nicht interessiert.

Nachdem wir diesen Schritt abgeschlossen haben, werden wir diesen Parameter im nächsten Schritt an die Klasse C_Replay übergeben:

while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value)) && (!_StopFlag))
{
        if (!Info.s_Infos.isPlay)
        {
                if (!bTest) bTest = true;
        }else
        {
                if (bTest)
                {
                        delay = ((delay = Replay.AdjustPositionReplay(user02)) >= 0 ? 3 : delay);
                        bTest = false;
                        t1 = GetTickCount64();
                }else if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
}


Jetzt können wir mit der Klasse C_Replay fortfahren und mit der Arbeit daran beginnen. Obwohl diese Aufgabe auf den ersten Blick einfach erscheint, birgt sie Hindernisse und Herausforderungen. Bisher basierten die Marktwiedergabedaten auf gehandelten Ticks und der Chart wurde mit 1-Minuten-Balken dargestellt. Es geht also nicht nur darum, Balken hinzuzufügen oder zu entfernen. Wir müssen die verschiedenen Elemente so behandeln, dass sie einheitlich sind. Keine leichte Aufgabe, nicht wahr? Aber ich löse gerne Probleme, und dieses Problem scheint recht interessant zu sein.

Der erste Schritt besteht darin, Ein-Minuten-Balken zu erstellen, während wir die Datei mit den gehandelten Ticks lesen, aber es gibt noch einen weiteren Aspekt, der berücksichtigt werden muss. Wir sollten sehr vorsichtig sein. Wir werden diese Herausforderung folgendermaßen angehen. Von Anfang an werden wir einen neuen Satz von Variablen in das System einführen.

struct st00
{
        MqlTick  Info[];
        MqlRates Rate[];
        int      nTicks,
                 nRate;
}m_Ticks;


Dieser Satz wird 1-Minuten-Balken enthalten, die wir gleichzeitig mit dem Lesen der Tick-Datei darstellen werden. Betrachtet man den bisherigen Code, so stellt man fest, dass die Funktion Event_OnTime in der Klasse C_Replay in der Lage ist, einminütige Balken auf der Grundlage der Werte der gehandelten Ticks darzustellen. Wir können diese Funktion jedoch nicht aufrufen, um diese Aufgabe für uns zu erledigen. Wir könnten dies sogar mit einer gewissen Vorsicht tun: Am Ende des Prozesses könnten wir alle im Wiedergabedienst erstellten Takte löschen. Auf diese Weise ist das System einsatzbereit. Die Funktionsweise von Event_OnTime führt jedoch zu einer kleinen Verzögerung bei jedem Aufruf, während die Anzahl der Aufrufe, die mit gehandelten Ticks verbunden sind, normalerweise relativ groß ist. Wir werden einen etwas anderen Ansatz wählen müssen.

Wie bereits erwähnt, müssen wir nach einem etwas anderen Ansatz suchen. Wir haben also die folgende Funktion:

inline bool BuiderBar1Min(MqlRates &rate, const MqlTick &tick)
                {
                        if (rate.time != macroRemoveSec(tick.time))
                        {
                                rate.real_volume = (long) tick.volume_real;
                                rate.tick_volume = 0;
                                rate.time = macroRemoveSec(tick.time);
                                rate.open = rate.low = rate.high = rate.close = tick.last;
                
                                return true;
                        }
                        rate.close = tick.last;
                        rate.high = (rate.close > rate.high ? rate.close : rate.high);
                        rate.low = (rate.close < rate.low ? rate.close : rate.low);
                        rate.real_volume += (long) tick.volume_real;
        
                        return false;
                }


Was wir hier tun, ist im Wesentlichen dasselbe, was Event_OnTime tun würde. Wir werden dies jedoch Tick für Tick tun. Hier eine kurze Erklärung, was passiert: Wenn die auf dem Tick angegebene Zeit von der auf dem Balken aufgezeichneten Zeit abweicht, haben wir eine anfängliche Balkenkonstruktion. Wir geben „true“ zurück, um dem Aufrufer mitzuteilen, dass ein neuer Balken erstellt wird, sodass er alle notwendigen Änderungen vornehmen kann. Bei späteren Aufrufen werden wir die Werte entsprechend anpassen. In diesem Fall wird „false“ zurückgegeben, um anzuzeigen, dass kein neuer Balken erstellt wurde. Die Funktion selbst ist recht einfach, aber man muss vorsichtig sein, wenn man sie nutzt.

Stellen Sie zunächst sicher, dass Sie das Array richtig initialisieren. Schauen wir uns an, wo dies geschieht.

bool SetSymbolReplay(const string szFileConfig)
{
        int     file;
        string  szInfo;
        bool    isBars = true;
                                
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
                MessageBox("Failed to load the\nconfiguration file.", "Market Replay", MB_OK);
                return false;
        }
        Print("Loading data for replay. Please wait....");
        ArrayResize(m_Ticks.Rate, 540);
        m_Ticks.nRate = -1;
        m_Ticks.Rate[0].time = 0;
        while ((!FileIsEnding(file)) && (!_StopFlag))
        {
                szInfo = FileReadString(file);
                StringToUpper(szInfo);
                if (szInfo == def_STR_FilesBar) isBars = true; else
                if (szInfo == def_STR_FilesTicks) isBars = false; else
                if (szInfo != "") if (!(isBars ? LoadPrevBars(szInfo) : LoadTicksReplay(szInfo)))
                {
                        if (!_StopFlag)
                                MessageBox(StringFormat("File %s from%s\ncould not be loaded.", szInfo, (isBars ? def_STR_FilesBar : def_STR_FilesTicks), "Market Replay", MB_OK));
                        FileClose(file);
                        return false;
                }
        }
        FileClose(file);
        return (!_StopFlag);
}


Wenn dies nicht korrekt und im Voraus geschieht, können Sie die Funktion zur Erstellung von Takten nicht richtig nutzen. Dann stellt sich die nächste Frage: Warum geben wir beim Index des ersten Arrays den Wert -1 an? Sollte nicht 0 der Startwert sein? Ja, es ist 0, aber wir beginnen mit -1 für den ersten Aufruf, der immer wahr sein wird. Würde sie bei 0 beginnen, müssten wir unmittelbar nach dem Aufruf der Balkenerstellung einen zusätzlichen Test durchführen. Bei einem Wert von -1 ist diese zusätzliche Prüfung jedoch überflüssig. Es ist wichtig zu beachten, dass wir das Array mit 540 Positionen initialisieren, was der Anzahl der 1-Minuten-Balken entspricht, die an einem typischen Handelstag an der brasilianischen Börse (B3) üblich sind.

Sobald dieser Schritt abgeschlossen ist, können wir mit dem Ablesen der gehandelten Ticks fortfahren.

bool LoadTicksReplay(const string szFileNameCSV)
{
        int     file,
                old;
        string  szInfo = "";
        MqlTick tick;
        MqlRates rate;
                                
        if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                ArrayResize(m_Ticks.Rate, 540, 540);
                old = m_Ticks.nTicks;
                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
                if (szInfo != def_Header_Ticks)
                {
                        Print("File ", szFileNameCSV, ".csv is not a file a traded ticks.");
                        return false;
                }
                Print("Loading replay ticks. Please wait...");
                while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
                {
                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                        szInfo = FileReadString(file) + " " + FileReadString(file);
                        tick.time = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                        tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                        tick.bid = StringToDouble(FileReadString(file));
                        tick.ask = StringToDouble(FileReadString(file));
                        tick.last = StringToDouble(FileReadString(file));
                        tick.volume_real = StringToDouble(FileReadString(file));
                        tick.flags = (uchar)StringToInteger(FileReadString(file));
                        if ((m_Ticks.Info[old].last == tick.last) && (m_Ticks.Info[old].time == tick.time) && (m_Ticks.Info[old].time_msc == tick.time_msc))
                                m_Ticks.Info[old].volume_real += tick.volume_real;
                        else
                        {                                                       
                                m_Ticks.Info[m_Ticks.nTicks] = tick;
                                if (tick.volume_real > 0.0)
                                {
                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                        rate.spread = m_Ticks.nTicks;
                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        m_Ticks.nTicks++;
                                }
                                old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                        }
                }
                if ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        Print("Too many data in the tick file.\nCannot continue...");
                        return false;
                }
        }else
        {
                Print("Tick file ", szFileNameCSV,".csv not found...");
                return false;
        }
        return (!_StopFlag);
};


Ein wichtiges Detail: Die Anfangs- und Reservewerte müssen angepasst werden, wenn die Anzahl der Minutenbalken größer ist als hier angegeben. Dieser Wert eignet sich für den Handelszeitraum von 9:00 bis 18:00 Uhr, was 540 Minuten entspricht. Wenn dieser Zeitraum jedoch länger ist, müssen Sie ihn im Voraus erhöhen. Es ist jedoch wichtig zu beachten, dass es sich bei den zu berücksichtigenden Zeiten um die Eröffnungs- und Schlusszeiten der Börsensitzung handeln sollte. Dies bezieht sich auf die gehandelte Tick-Datei, nicht auf die Balken-Datei. Dies ist darauf zurückzuführen, dass die Balken auf der Grundlage einer Tick-Datei erzeugt werden, und wenn diese Sitzung in einer bestimmten Datei anders ist, können bei der Ausführung (RUN TIME) Probleme auftreten. Wir verwenden diesen Wert, da die Handelssitzung an der B3-Börse normalerweise 540 Minuten dauert.

Jetzt können wir eine Datei mit gehandelten Ticks vorlegen. Auf diese Weise werden wir jeweils einen Tick erfassen und 1-Minuten-Balken erstellen. Es ist jedoch wichtig, Folgendes zu beachten: Balken werden nur erzeugt, wenn ein gewisses Handelsvolumen vorhanden ist; andernfalls stellt der Tick eine Anpassung an den BID oder ASK des Vermögenswerts dar und wird daher nicht berücksichtigt. Hinweis: Wir werden uns in naher Zukunft mit solchen Situationen befassen, da wir beabsichtigen, das System für den Devisenmarkt anzupassen. Aber lassen wir das erst einmal beiseite.

Da wir den Spread-Wert in der Wiedergabe/Simulation nicht verwenden, wird er für einen sinnvolleren Zweck eingesetzt. Bitte beachten Sie jedoch, dass dies nicht der Spread ist. Wenn also ein Indikator einen korrekten Spread erfordert, müssen Sie einen anderen Ansatz wählen. Die Variable, die zur Speicherung der Spanne verwendet wird, kann zur Speicherung des Wertes der Position des Zählers verwendet werden. Dies wird sich in naher Zukunft als sehr nützlich erweisen.

Nun, da alles korrekt eingerichtet ist, können wir die Daten des 1-Minuten-Balkens speichern und zum nächsten Schritt übergehen: Dies ist darauf zurückzuführen, dass es keine weiteren Änderungen am Ablesesystem gibt. Daher erübrigt sich ein weiterer Kommentar zur Lesereihenfolge.

Schauen wir uns nun die Hauptfunktion an.

int AdjustPositionReplay(const bool bViewBuider)
{
        u_Interprocess Info;
        MqlRates       Rate[1];
        int            iPos = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks);
        datetime       dt_Local;
                                
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == iPos) return 0;
        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / def_MaxPosSlider));
        if (iPos < m_ReplayCount)
        {
                dt_Local = m_dtPrevLoading;
                m_ReplayCount = 0;
                if (!bViewBuider) for (int c0 = 1; (c0 < m_Ticks.nRate) && (m_Ticks.Rate[c0 - 1].spread < iPos); c0++)
                {
                        dt_Local = m_Ticks.Rate[c0].time;
                        m_ReplayCount = m_Ticks.Rate[c0 - 1].spread;
                }
                CustomRatesDelete(def_SymbolReplay, dt_Local, LONG_MAX);
                if (m_dtPrevLoading == 0)
                {
                        Rate[0].close = Rate[0].open = Rate[0].high = Rate[0].low = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].tick_volume = 0;
                        Rate[0].time = m_Ticks.Info[m_ReplayCount].time - 60;
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
        }
        for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag); m_ReplayCount++) Event_OnTime();
        return Event_OnTime();
}

Diese Funktion ist noch nicht ganz fertig und wird sich weiter verändern. Um jedoch Verwirrung bei künftigen Erklärungen zu vermeiden, werde ich erläutern, was bei der Entwicklung dieses Artikels hinzugefügt oder entfernt wurde. Auf diese Weise kann der Leser besser nachvollziehen, was geschieht, und wenn Sie Änderungen vornehmen möchten, ist dies leichter möglich. Sie müssen nur zu diesen Artikeln zurückkehren und überprüfen, was in jedem der besprochenen Punkte tatsächlich vor sich geht. Denken Sie daran, dass alles, was hier nicht erwähnt wird, bereits in früheren Artikeln behandelt wurde.

Der erste Schritt besteht darin, eine lokale Variable zu deklarieren, um die interne Zeitposition innerhalb der Funktion festzulegen. Mit dieser Einstellung müssen wir das Replay nicht wieder von vorne beginnen, wenn wir uns vorwärts bewegen und dann beschließen, ein Stück zurückzugehen. Zu diesem Punkt kommen wir bald. Nachdem wir einige Berechnungen durchgeführt haben, um festzustellen, ob sich die aktuelle Position vorwärts oder rückwärts bewegen soll, finden wir die erste Aktion, die wir durchführen müssen. Wenn die Position zurückgehen muss, leiten diese beiden Zeilen eine Wiederholung/Simulation zu Beginn der Aktion ein. Dies ist jedoch nicht unbedingt erforderlich. Wenn Sie oder der Nutzer angeben, dass Sie die Entstehung der Balken nicht beobachten wollen, wird eine kurze Schleife gestartet, um den Inhalt aller 1-Minuten-Balken zu überprüfen, die beim Lesen der gehandelten Ticks aufgezeichnet wurden. 

Nun gibt es ein Problem, das im Moment vielleicht nicht ganz klar erscheint. Wenn wir gehandelte Ticks in 1-Minuten-Balken umwandeln, erhalten wir die relative Position des Zählers und gleichzeitig die neue Öffnungszeit des Balkens. Diese Information ist nützlich und notwendig, da sie es uns ermöglicht, alle Balken zu löschen, die nach der angegebenen Zeit erscheinen. Es ist unwahrscheinlich, dass der Zählwert mit dem vom Nutzer angeforderten neuen relativen Positionswert identisch ist. Das System nimmt also eine kleine Anpassung vor, um die Positionen abzugleichen, aber diese Anpassung wird sehr schnell vorgenommen. Dadurch wird das Erstellen eines Balkens fast unsichtbar.

Wie bereits erwähnt, ist diese Funktion jedoch noch nicht vollständig. Die beschriebene Operation wird nur angewendet, wenn der Nutzer von der aktuellen Zählerposition aus einen Rückschritt macht. Wenn der Nutzer von der Gegenposition aus vorrückt, wird der Balken noch nicht erstellt. Da wir es allen recht machen wollen, sowohl den Griechen als auch den Trojanern, müssen wir dieses kleine Missgeschick korrigieren, damit die Entstehung der Balken nicht im Voraus sichtbar wird. Es ist nicht sehr kompliziert. Vergleichen wir den obigen Code, der das Beförderungssystem nicht enthält, mit dem folgenden Code, der es einschließt:

int AdjustPositionReplay(const bool bViewBuider)
{
#define macroSearchPosition     {                                                                                               \
                dt_Local = m_dtPrevLoading; m_ReplayCount = count = 0;                                                          \
                if (!bViewBuider) for (count = 1; (count < m_Ticks.nRate) && (m_Ticks.Rate[count - 1].spread < iPos); count++)  \
                        { dt_Local = m_Ticks.Rate[count].time;  m_ReplayCount = m_Ticks.Rate[count - 1].spread; }               \
                                }

        u_Interprocess  Info;
        MqlRates        Rate[def_BarsDiary];
        int             iPos = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks),
                        count;
        datetime        dt_Local;
                                
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == iPos) return 0;
        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
        if (iPos < m_ReplayCount)
        {
                macroSearchPosition;
                CustomRatesDelete(def_SymbolReplay, dt_Local, LONG_MAX);
                if (m_dtPrevLoading == 0)
                {
                        Rate[0].close = Rate[0].open = Rate[0].high = Rate[0].low = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].tick_volume = 0;
                        Rate[0].time = m_Ticks.Info[m_ReplayCount].time - 60;
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
        }if ((iPos > m_ReplayCount) && (!bViewBuider))
        {
                macroSearchPosition;                    
                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, count);
        }
        for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag); m_ReplayCount++) Event_OnTime();
        return Event_OnTime();
}


Sehen Sie den Unterschied? Wenn Sie an das Makro denken, vergessen Sie es, denn es ist nur dazu da, uns davor zu bewahren, denselben Code an zwei verschiedenen Stellen zu wiederholen. In der Tat gibt es praktisch keinen Unterschied. Das Einzige, was vielleicht anders ist, ist die Linie, die zusätzliche Balken hinzufügen wird. Wenn Sie das Replay-System anwenden, werden Sie feststellen, dass die Vorwärts- und Rückwärtspunkte wahrscheinlich nicht mit dem Schluss eines Balkens und der Eröffnung des nächsten zusammenfallen. Das liegt daran, dass es immer einen Rest geben wird, der dieser Linie entspricht. Aufgrund der Geschwindigkeit dieser Einstellung ist es jedoch unwahrscheinlich, dass Sie diese Verfeinerung bemerken werden.


Benachrichtigung des Nutzers

Unser Wiedergabesystem ist an einem Punkt angelangt, an dem wir damit beginnen sollten, einige Ergänzungen einzubauen, die bisher nicht notwendig waren. Eine dieser Ergänzungen besteht darin, den Nutzer zu benachrichtigen, wenn keine Daten mehr im System vorhanden sind, um die Wiedergabe zu simulieren oder fortzusetzen. Ohne diese Warnung könnte der Nutzer annehmen, dass das System einfach abgestürzt ist oder eine ungewöhnliche Situation aufgetreten ist. Um solche Annahmen zu vermeiden, sollten wir zunächst einige zusätzliche Informationen hinzufügen. Der erste Schritt ist eine Warnung, dass keine weiteren Daten zur Verfügung stehen. Um zu verstehen, wie man das macht, schauen wir uns den folgenden Code an:

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id = 0;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        if (!Replay.SetSymbolReplay(user00))
        {
                Finish();
                return;
        }
        Print("Wait for permission from [Market Replay] indicator to start replay ...");
        id = Replay.ViewReplay(user01);
        while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(id) != "")) Sleep(750);
        if ((_StopFlag) || (ChartSymbol(id) == ""))
        {
                Finish();
                return;
        }
        Print("Permission granted. The replay service can now be used...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value)) && (!_StopFlag))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest) bTest = true;
                }else
                {
                        if (bTest)
                        {
                                if ((delay = Replay.AdjustPositionReplay(user02)) < 0) AlertToUser(); else
                                {
                                        delay = (delay >= 0 ? 3 : delay);
                                        bTest = false;
                                        t1 = GetTickCount64();
                                }                               
                        }else if ((GetTickCount64() - t1) >= (uint)(delay))
                        {
                                if ((delay = Replay.Event_OnTime()) < 0) AlertToUser();
                                t1 = GetTickCount64();
                        }
                }
        }
        Finish();
}
//+------------------------------------------------------------------+
void AlertToUser(void)
{
        u_Interprocess Info;
        
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
        Info.s_Infos.isPlay = false;
        GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
        MessageBox("No more data to use in replay-simulation", "Service Replay", MB_OK);
}
//+------------------------------------------------------------------+
void Finish(void)
{
        Replay.CloseReplay();
        Print("The replay service completed...");
}
//+------------------------------------------------------------------+


Es gibt zwei Fälle, in denen Sie eine solche Warnung erstellen können. Der erste Fall tritt während der normalen Wiedergabe auf, was der häufigste Fall ist. Es gibt jedoch noch eine weitere Möglichkeit: wenn der Nutzer die Position auf das Ende der Bildlaufleiste einstellt.

int AdjustPositionReplay(const bool bViewBuider)
{

// ... Code ...

        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));

// ...  Rest of the code ...


Unabhängig davon wird die Antwort immer die gleiche sein. Wir nehmen den Wert der globalen Terminalvariablen und verwenden ihn, um anzuzeigen, dass wir uns im Pausenmodus befinden. Dann zeichnen wir das Ganze noch einmal auf und zeigen ein Fenster an, in dem wir berichten, was passiert ist. Im Grunde werden wir genau das tun, aber es wird sehr nützlich sein. Auf diese Weise wird der arme Nutzer herausfinden, was passiert ist.


Hinzufügen der Warnung „Please Wait“ (Bitte warten)

Nun, da unser Wiedergabesystem dem Nutzer die Möglichkeit gegeben hat, anzugeben, ob er den Prozess der Balkenbildung sehen möchte, gibt es ein kleines Problem, wenn er den Prozess der Balkenbildung tatsächlich überwachen möchte. Das ist der Grund für dieses Thema.

Wenn wir sehen wollen, wie sich die Balken aufbauen, während wir darauf warten, dass der Wiedergabedienst die richtige Position erreicht, haben wir den Eindruck, dass wir den Fortschritt jederzeit anhalten oder starten können. Das liegt daran, dass wir Tasten zum Abspielen und Anhalten haben. Wir können jedoch keines dieser Dinge tun, bis der Wiedergabedienst die richtige Position erreicht hat, um das System freizugeben. Und genau in diesen Situationen können wir ein wenig verwirrt werden, weil wir nicht genau wissen, was genau passiert. Ersetzt man jedoch diese Schaltfläche durch eine andere, die auf die Notwendigkeit des Wartens hinweist, ändert sich die Situation. Richtig?

Es reicht jedoch nicht aus, einfach eine Schaltfläche hinzuzufügen. Wir müssen einige zusätzliche Schritte durchführen, die es dem Dienst ermöglichen, dem Kontrollindikator mitzuteilen, was angezeigt werden soll und was nicht. Fügen wir zunächst eine neue Variable in die Header-Datei InterProcess.mqh ein.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableReplay        "Replay Infos"
#define def_GlobalVariableIdGraphics    "Replay ID"
#define def_SymbolReplay                "RePlay"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market Replay"
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // The value of the terminal's global variable...
                ulong   IdGraphic;      // Contains the asset chart ID....
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Specifies if we are in the play or pause mode ...
                bool    isWait;         // Asks the user to wait...
                ushort  iPosShift;      // A value between 0 and 400 ...
        }s_Infos;
};
//+------------------------------------------------------------------+


Dieser Wert, der zwischen dem Dienst und dem Indikator übertragen wird, hat Vorrang vor anderen Kontrollen. Wenn er also angezeigt werden muss, kann der Kontrollindikator nichts anderes tun. Wir haben die Variable bereits definiert, jetzt müssen wir zum Wiedergabedienst gehen und den erforderlichen Code für die Kommunikation mit dem Kontrollindikator hinzufügen. Zu diesem Zweck müssen wir der Klasse C_Replay etwas Code hinzufügen. Es ist nicht sehr schwierig.

int AdjustPositionReplay(const bool bViewBuider)
{
#define macroSearchPosition     {                                                                                               \
                dt_Local = m_dtPrevLoading; m_ReplayCount = count = 0;                                                          \
                if (!bViewBuider) for (count = 1; (count < m_Ticks.nRate) && (m_Ticks.Rate[count - 1].spread < iPos); count++)  \
                        { dt_Local = m_Ticks.Rate[count].time;  m_ReplayCount = m_Ticks.Rate[count - 1].spread; }               \
                                }

        u_Interprocess  Info;
        MqlRates        Rate[def_BarsDiary];
        int             iPos = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks),
                        count;
        datetime        dt_Local;
                                
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == iPos) return 0;
        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
        if (iPos < m_ReplayCount)
        {
                macroSearchPosition;
                CustomRatesDelete(def_SymbolReplay, dt_Local, LONG_MAX);
                if (m_dtPrevLoading == 0)
                {
                        Rate[0].close = Rate[0].open = Rate[0].high = Rate[0].low = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].tick_volume = 0;
                        Rate[0].time = m_Ticks.Info[m_ReplayCount].time - 60;
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
        }if ((iPos > m_ReplayCount) && (!bViewBuider))
        {
                macroSearchPosition;                    
                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, count);
        }
        if (bViewBuider)
        {
                Info.s_Infos.isWait = true;
                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
        }
        for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag); m_ReplayCount++) Event_OnTime();
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
        Info.s_Infos.isWait = false;
        GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
        return Event_OnTime();
}


Dieser Punkt wird in der Regel nicht erreicht und tritt erst in dem Moment ein, in dem wirklich etwas getan werden muss. Wenn der Nutzer die Balken auf dem Chart anzeigen lassen möchte, senden wir ein Signal, damit der Indikator anzeigt, dass der Dienst für einige Zeit nicht verfügbar ist. Wir halten dies in der globalen Terminalvariable fest, damit der Indikator diesen Wert interpretieren kann. Der Dienst wird dann die Aufgabe erfüllen, für die er eigentlich vorgesehen ist. Kurz darauf geben wir den Indikator vollständig und bedingungslos frei.

Danach können wir mit dem Code des Kontrollindikators fortfahren, um zu analysieren, was passiert. Manch einer mag denken, dass es eine Menge Code braucht, um hier zu funktionieren. Wie Sie jedoch sehen können, werde ich die gesamte Arbeit mit einem Minimum an Code erledigen. Wie wäre es zur Vereinfachung mit einer kleinen Abstraktion? Dazu fügen wir zunächst die folgende Zeile in die Header-Datei C_Control.mqh ein.

enum EventCustom {Ev_WAIT_ON, Ev_WAIT_OFF};


Eigentlich fügen wir eine zusätzliche Abstraktionsebene hinzu, um das weitere Vorgehen zu vereinfachen. Vergessen Sie nicht das Bild, das wir verwenden werden, es wird im folgenden Ausschnitt hinzugefügt:

#define def_ButtonPlay  "Images\\Market Replay\\Play.bmp"
#define def_ButtonPause "Images\\Market Replay\\Pause.bmp"
#define def_ButtonLeft  "Images\\Market Replay\\Left.bmp"
#define def_ButtonRight "Images\\Market Replay\\Right.bmp"
#define def_ButtonPin   "Images\\Market Replay\\Pin.bmp"
#define def_ButtonWait  "Images\\Market Replay\\Wait.bmp"
#resource "\\" + def_ButtonPlay
#resource "\\" + def_ButtonPause
#resource "\\" + def_ButtonLeft
#resource "\\" + def_ButtonRight
#resource "\\" + def_ButtonPin
#resource "\\" + def_ButtonWait


Die Verwendung eines Bildes vereinfacht die Dinge wirklich. Denken Sie daran, dass wir dem Nutzer nur anzeigen wollen, dass der Dienst läuft und dass er während dieses Vorgangs nicht in der Lage ist, auf andere Anfragen zu antworten.

Als Nächstes fügen wir in der Klassendatei eine private, interne Variable hinzu, um interne Aktionen zu steuern. 

class C_Controls
{
        private :
//+------------------------------------------------------------------+
                string  m_szBtnPlay;
                long    m_id;
                bool    m_bWait;
                struct st_00
                {
                        string  szBtnLeft,
                                szBtnRight,
                                szBtnPin,
                                szBarSlider;
                        int     posPinSlider,
                                posY;
                }m_Slider;
//+------------------------------------------------------------------+


Durch das Hinzufügen dieser Variable haben wir bereits eine Vorstellung vom Zustand des Replay/Simulationsdienstes. Sie muss jedoch an der richtigen Stelle initialisiert werden, und die beste Möglichkeit ist der Klassenkonstruktor.

C_Controls() : m_id(0), m_bWait(false)
        {
                m_szBtnPlay             = NULL;
                m_Slider.szBarSlider    = NULL;
                m_Slider.szBtnPin       = NULL;
                m_Slider.szBtnLeft      = NULL;
                m_Slider.szBtnRight     = NULL;
        }


Beachten Sie, dass wir den Wert mit „false“ initialisieren müssen, da der Wiedergabe-/Simulationsdienst immer frei starten und auf jeden Befehl reagieren kann. Auch wenn diese Initialisierung hier erfolgt, werden wir in anderen Aufrufen für den richtigen Zustand sorgen. Aber das reicht für unsere Zwecke erst einmal aus.

Jetzt müssen wir Folgendes analysieren: Welches Ereignis wollen wir wirklich sperren? Jedes Mal, wenn wir die Abspielposition vorwärts oder rückwärts bewegen, ändert sich die Schaltfläche von „play“ zu „pause“, und wir wollen den Zugriff des Nutzers auf diese Schaltfläche blockieren. Ein einfacher Klick veranlasst den Kontrollanzeiger, eine Aktion vom Wiedergabe-/Simulationsdienst anzufordern. Der Dienst antwortet jedoch nicht, wenn er sich in der Phase befindet, in der er sich auf die Wiedergabe/Simulation vorbereitet.

Wenn Sie sich den Code ansehen, können Sie feststellen, dass das System immer auf Ereignisse reagiert; mit anderen Worten, dies ist das ereignisbasierte System. Aus diesem Grund haben wir die EreignisCustom Enumeration geschaffen, um ein ereignisbasiertes System zu unterstützen. Daran werden wir nichts ändern. Tatsächlich sollten wir eine solche Änderung nicht einmal in Erwägung ziehen, da sie uns dazu zwingen würde, mehrere komplexere Ansätze als die Verwendung von Ereignissen zu verwenden. Das einfache Hinzufügen einer Enumeration, die das Vorhandensein von Ereignissen anzeigt, ist jedoch nicht die Lösung. Schauen wir mal, was wir tun müssen. Wir ändern die Prozedur DispatchMessage so, dass das Drücken der Taste Play/Pause kein Ereignis erzeugt, wenn der Dienst beschäftigt ist. Dies lässt sich leicht durch Hinzufügen der folgenden Prüfung erreichen:

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)
                {

// ... Internal code ...

                        case CHARTEVENT_OBJECT_CLICK:
                                if (m_bWait) break;
                                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 = (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;

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

Durch Hinzufügen dieser Testzeile wird verhindert, dass der Indikator Anfragen an den Dienst sendet, während dieser beschäftigt ist. Damit ist das Problem aber immer noch nicht vollständig gelöst, denn der Nutzer mag es vielleicht nicht, wenn er auf die Schaltfläche „Play/Pause“ klickt und sich nichts ändert. Wir müssen andere Maßnahmen ergreifen. Außerdem ist es uns immer noch nicht gelungen, den Wert der zu prüfenden Variablen korrekt einzustellen.

Dieser Teil mag ein wenig verwirrend erscheinen, aber alles, was wir wirklich tun werden, ist den Wert der Variablen m_bWait zu ändern und zu überprüfen. So können wir bestimmen, welche Bilder aufgezeichnet werden sollen. Ziel ist es, dass die Schaltfläche Play/Pause zu einem anderen Bild wechselt, wenn der Dienst aktiv ist, und zur herkömmlichen Abspiel-/Pause-Schaltfläche zurückkehrt, wenn der Dienst deaktiviert ist. Wir werden einen einfachen Ansatz verwenden:

void CreateBtnPlayPause(bool state)
{
        m_szBtnPlay = def_PrefixObjectName + "Play";
        CreateObjectBitMap(5, 25, m_szBtnPlay, (m_bWait ? def_ButtonWait : def_ButtonPause), (m_bWait ? def_ButtonWait : def_ButtonPlay));
        ObjectSetInteger(m_id, m_szBtnPlay, OBJPROP_STATE, state);
}


Beachten Sie, dass wir lediglich die Variable überprüfen. Je nach Wert wird eine Play/Pause-Taste oder eine Taste verwendet, die das Wartesignal darstellt. Aber, wie soll diese Taste funktionieren? Wird es ständig den Wert einer globalen Variablen aus dem Terminal lesen? Es wird etwas Ähnliches geben. Denken Sie daran: Jedes Mal, wenn der Dienst einen neuen Datensatz zu einem Marktwiedergabe-Asset hinzufügt, wird dies im Indikator angezeigt. MetaTrader 5 erzeugt also ein Ereignis, das die Funktion OnCalculate startet. An dieser Stelle kommen wir ins Spiel, aber wir werden den Indikator nicht ständig überwachen. Wir werden es auf elegantere Weise tun. Um den Ablauf zu verstehen, sehen Sie sich die nachstehende Abbildung an, die den Anruffluss im Code zeigt:

Dies ist genau die Abfolge von Aktionen, die durchgeführt werden, um die Taste auf dem Kontrollanzeiger korrekt zu bedienen. Die CreateBtnPlayPause Prozedur wurde bereits zuvor vorgestellt, daher denke ich, dass sie ziemlich selbsterklärend ist. Wir werden uns nun weitere Punkte dieses Charts ansehen. Die Funktion OnCalculate beinhaltet eine schwierigere Logik und erfordert das Verständnis der Schritte von DispatchMessage

Kommen wir also zum grundlegenden Code für die Behandlung nutzerdefinierter Ereignisse. 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_CUSTOM + Ev_WAIT_ON):
                        m_bWait = true;
                        CreateBtnPlayPause(true);
                        break;
                case (CHARTEVENT_CUSTOM + Ev_WAIT_OFF):
                        m_bWait = false;
                        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                        CreateBtnPlayPause(Info.s_Infos.isPlay);
                        break;

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

Wenn DispatchMessage durch den Kontrollindikator OnChartEvent aufgerufen wird, werden die Daten übergeben, um die Verarbeitung sowohl von Ereignismeldungen der MetaTrader 5-Plattform als auch von nutzerdefinierten Ereignissen, die von unserem Code an bestimmten Punkten ausgelöst werden, zu ermöglichen. Wir werden später über nutzerdefinierte Ereignisse sprechen. Die Funktion sucht nach passendem Code, wenn das nutzerdefinierte Ereignis Ev_WAIT_ON verwendet wird. Dadurch wird uns mitgeteilt, dass der Dienst beschäftigt ist, was die Variable m_bWait auf true gesetzt wird. Als Nächstes rufen wir die Erstellung der Schaltfläche „Play/Pause“ auf, mit der ein Bild gezeichnet wird, das den Belegtstatus anzeigt. Wenn die Ev_WAIT_OFF ausgelöst wird, soll der aktuelle Zustand des Dienstes angezeigt werden, d. h. ob er sich im Modus Play oder Pause befindet. Daher muss die m_bWait einen Wert erhalten, der anzeigt, dass der Dienst zur Annahme von Anfragen bereit ist. Außerdem müssen wir Daten aus der globalen Terminalvariable abrufen, die den aktuellen Zustand des Dienstes enthält. Als Nächstes rufen wir eine Funktion auf, die eine Schaltfläche für Play/Pause erstellt, damit der Nutzer mit dem System interagieren kann.

Dieser Ansatz ist recht intuitiv, und ich denke, jeder kann die Idee verstehen. Die große Frage ist: Wie werden diese Ereignisse ausgelöst? Werden wir einen extrem komplexen und schwer verständlichen Code haben? Nein, die Art und Weise, Ereignisse in MQL5 auszulösen, ist recht einfach, ebenso wie die Art und Weise, die genannten nutzerdefinierten Ereignisse zu analysieren und zu verarbeiten. Im obigen Code können Sie sehen, wie zwei nutzerdefinierte Ereignisse behandelt werden. Sehen wir uns nun an, wie diese Ereignisse ausgelöst werden können. Wenn wir ein nutzerdefiniertes Ereignis auslösen, rufen wir eigentlich die Funktion OnChartEvent auf. Diese Funktion wird immer dann aufgerufen, wenn ein Ereignis eintritt, entweder ein nutzerdefiniertes Ereignis oder eines, das vom MetaTrader 5 stammt. Die aufgerufene Funktion wird immer dieselbe sein. Sehen Sie sich nun den Code für diese Funktion in der Befehlszeile an:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Control.DispatchMessage(id, lparam, dparam, sparam);
}


Das heißt, wenn ein Ereignis ausgelöst wird, wird seine Verarbeitung an die Klasse C_Control delegiert, und die Funktion DispatchMessage wird ausgeführt. Haben Sie bemerkt, wie alles funktioniert? Wäre der in der Funktion DispatchMessage enthaltene Code innerhalb der Ereignisbehandlungsfunktion, wäre das Ergebnis dasselbe. Bitte beachten Sie jedoch, dass die Funktion OnChartEvent 4 Parameter benötigt, während die Funktion, die nutzerdefinierte Ereignisse auslöst, mehr Parameter verwendet. Tatsächlich gibt es 5 Parameter, die zur Auslösung von nutzerdefinierten Ereignissen verwendet werden. Auf diese Weise können wir nutzerdefinierte Ereignisse von Ereignissen aus MetaTrader 5 unterscheiden. Wenn Sie genau hinschauen, werden Sie feststellen, dass der Wert, der zum Zeitpunkt der Auswahl verwendet wird, die Summe der Werte ist, die in der Enumeration EreignisCustom angegebenen Wertes mit anderen Daten CHARTEVENT_CUSTOM ist. Auf diese Weise erhalten wir den richtigen Wert. 

Aber wie wird dieser Wert geschaffen? Wie können wir mit MQL5 nutzerdefinierte Ereignisse erzeugen? Um dies zu verstehen, sehen Sie sich den Hauptcode unseres Kontrollindikators an: die Funktion OnCalculate. Sie ist unten aufgeführt:

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, Ev_WAIT_ON, 0, 0, "");
                        bWait = true;
                }
        }else if (!Info.s_Infos.isWait)
        {
                EventChartCustom(m_id, Ev_WAIT_OFF, 0, Info.u_Value.df_Value, "");
                bWait = false;
        }
        
        return rates_total;
}


Lassen Sie uns verstehen, wie der obige Code funktioniert. Zunächst ist zu beachten, dass es sich bei diesem Code um eine Ereignisbehandlung handelt, die von MetaTrader 5 aufgerufen wird. Das heißt, jedes Mal, wenn sich der Preis des Vermögenswerts ändert oder der Vermögenswert einen neuen gehandelten Tick erhält, wird die Funktion OnCalculate automatisch von MetaTrader 5 aufgerufen. Wir brauchen also keinen Timer innerhalb des Indikators. In der Tat sollten Sie die Verwendung von Zeitgebern in Indikatoren (so weit wie möglich) vermeiden, da sie nicht nur den betreffenden Indikator, sondern auch alle anderen beeinflussen. Daher werden wir diesen Aufruf der MetaTrader 5-Plattform verwenden, um zu überprüfen, was mit dem Dienst geschieht. Beachten Sie, dass der Dienst Eingabedaten an die Wiedergabe-/Simulationsressource sendet und daher indirekt die Funktion OnCalculate aufruft.


Schlussfolgerung

Ich hoffe, Sie haben den Grundgedanken verstanden, denn er ist die Basis für alles andere. Jedes Mal, wenn wir OnCalculate aufrufen, schreiben wir den Wert in die globale Terminalvariable und prüfen, ob die lokale statische Variable wahr ist oder nicht. Wenn der Wert nicht true ist, wird geprüft, ob der Dienst beschäftigt ist. Wenn diese Bedingung erfüllt ist, erstellen wir ein spezielles Ereignis, um dies zu melden. Unmittelbar danach wird der Wert einer lokalen statischen Variablen geändert, um anzuzeigen, dass der Kontrollindikator weiß, dass der Wiedergabe-/Simulationsdienst beschäftigt ist. Wenn wir also das nächste Mal OnCalcule aufrufen, prüfen wir, ob der Wiedergabe-/Simulationsdienst frei ist, um seine Tätigkeit auszuüben. Sobald dies der Fall ist, wird ein spezielles Ereignis ausgelöst, das anzeigt, dass der Dienst bereit ist, Anforderungen für Kontrollanzeigen zu empfangen. Und die Schleife wird so lange wiederholt, wie die lokale, statische Variable wahr ist.

Beachten Sie nun, dass wir zum Auslösen von nutzerdefinierten Ereignissen etwas Allgemeines verwenden, nämlich die Funktion EventChartCustom. Hier sind wir nur durch den aktuellen Chart und den Kontrollindikator eingeschränkt. Wir können jedoch Ereignisse für jedes Chart, jeden Indikator und sogar für einen Expert Advisor auslösen. Dazu müssen Sie die Parameter der Funktion EventChartCustom korrekt ausfüllen. Wenn wir dies tun, dann wird alles andere der MetaTrader 5 Plattform anvertraut, und wir müssen nur das nutzerdefinierte Ereignis im Moment entweder im Indikator oder im Expert Advisor verarbeiten. Dies ist ein wenig erforschter Aspekt, und wie ich festgestellt habe, glauben die Leute manchmal, dass die MetaTrader 5-Plattform nicht in der Lage ist, bestimmte Aktionen durchzuführen. 

Im nächsten Video werde ich das System in seinem derzeitigen Entwicklungsstadium demonstrieren. Ich wünsche Ihnen viel Spaß mit dieser Artikelserie und hoffe, dass sie Ihnen hilft, die MetaTrader 5 Plattform und die Möglichkeiten der MQL5 Sprache besser kennenzulernen.



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

Beigefügte Dateien |
Market_Replay.zip (13060.83 KB)
Neuronale Netze leicht gemacht (Teil 44): Erlernen von Fertigkeiten mit Blick auf die Dynamik Neuronale Netze leicht gemacht (Teil 44): Erlernen von Fertigkeiten mit Blick auf die Dynamik
Im vorangegangenen Artikel haben wir die DIAYN-Methode vorgestellt, die einen Algorithmus zum Erlernen einer Vielzahl von Fertigkeiten (skills) bietet. Die erworbenen Fertigkeiten können für verschiedene Aufgaben genutzt werden. Aber solche Fertigkeiten können ziemlich unberechenbar sein, was ihre Anwendung schwierig machen kann. In diesem Artikel wird ein Algorithmus zum Erlernen vorhersehbarer Fertigkeiten vorgestellt.
Neuronale Netze leicht gemacht (Teil 43): Beherrschen von Fähigkeiten ohne Belohnungsfunktion Neuronale Netze leicht gemacht (Teil 43): Beherrschen von Fähigkeiten ohne Belohnungsfunktion
Das Problem des Verstärkungslernens liegt in der Notwendigkeit, eine Belohnungsfunktion zu definieren. Sie kann komplex oder schwer zu formalisieren sein. Um dieses Problem zu lösen, werden aktivitäts- und umweltbasierte Ansätze zum Erlernen von Fähigkeiten ohne explizite Belohnungsfunktion erforscht.
Neuronale Netze leicht gemacht (Teil 45): Training von Fertigkeiten zur Erkundung des Zustands Neuronale Netze leicht gemacht (Teil 45): Training von Fertigkeiten zur Erkundung des Zustands
Das Training nützlicher Fertigkeiten ohne explizite Belohnungsfunktion ist eine der größten Herausforderungen beim hierarchischen Verstärkungslernen. Zuvor haben wir bereits zwei Algorithmen zur Lösung dieses Problems kennengelernt. Die Frage nach der Vollständigkeit der Umweltforschung bleibt jedoch offen. In diesem Artikel wird ein anderer Ansatz für das Training von Fertigkeiten vorgestellt, dessen Anwendung direkt vom aktuellen Zustand des Systems abhängt.
Neuronale Netze leicht gemacht (Teil 42): Modell der Prokrastination, Ursachen und Lösungen Neuronale Netze leicht gemacht (Teil 42): Modell der Prokrastination, Ursachen und Lösungen
Im Kontext des Verstärkungslernens kann die Prokrastination (Zögern) eines Modells mehrere Ursachen haben. Der Artikel befasst sich mit einigen der möglichen Ursachen für Prokrastination bei Modellen und mit Methoden zu deren Überwindung.