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

Entwicklung eines Replay Systems — Marktsimulation (Teil 20): FOREX (I)

MetaTrader 5Tester | 12 Januar 2024, 10:09
325 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay-Systems — Marktsimulation (Teil 19): Notwendige Anpassungen“, haben wir bestimmte Dinge umgesetzt, deren Vorhandensein dringlicher war. Auch wenn der Schwerpunkt dieser Serie von Anfang an auf dem Aktienmarkt lag, möchte ich versuchen, auch den Devisenmarkt abzudecken. Der Grund für mein anfängliches Desinteresse an Forex ist die Tatsache, dass auf diesem Markt ständig Trades stattfinden, sodass es keinen Sinn macht, Replay/Simulationen zum Testen oder Trainieren zu haben.

Sie können dafür einfach ein Demokonto verwenden. Allerdings gibt es auf diesem Markt einige Besonderheiten, die auf dem Aktienmarkt nicht zu finden sind. Aus diesem Grund ist es interessant zu zeigen, wie die notwendigen Korrekturen am System vorgenommen werden können, um das System an andere Arten von Märkten, z. B. Krypto-Assets, anzupassen.

So wird deutlich, wie vielseitig und geeignet die MetaTrader 5-Plattform für viel mehr Anwendungen sein kann, als von ihren Schöpfern ursprünglich vorgesehen. Nur Ihre Vorstellungskraft und Ihr Wissen über einen bestimmten Markt setzen Ihren Möglichkeiten Grenzen.


Einige Dinge über Forex lernen

Das ursprüngliche Ziel dieses Artikels ist es nicht, alle Möglichkeiten des Forex-Handels abzudecken, sondern das System so anzupassen, dass Sie zumindest ein Replay des Marktes durchführen können. Wir lassen die Simulation noch einen Moment auf sich warten. Wenn wir jedoch keine Ticks, sondern nur Balken haben, können wir mit ein wenig Aufwand mögliche Abschlüsse simulieren, die auf dem Forex-Markt passieren könnten. Dies wird der Fall sein, bis wir uns mit der Anpassung des Simulators befassen. Der Versuch, mit Forex-Daten innerhalb des Systems zu arbeiten, ohne sie zu verändern, führt zu einer Reihe von Fehlern. Obwohl wir versuchen, solche Fehler zu vermeiden, werden sie immer wieder vorkommen. Es ist jedoch möglich, sie zu überwinden und so ein Wiedergabesystem für den Devisenmarkt zu schaffen. Aber dafür müssen wir einige Anpassungen vornehmen und einige der Konzepte ändern, an denen wir bisher gearbeitet haben. Ich denke, das ist es wert, denn dadurch wird das System viel flexibler und kann viel mehr exotische Daten verarbeiten.

Der Artikelanhang enthält ein Devisensymbol (Währungspaar). Dies werden echte Ticks sein, damit wir uns die Situation vorstellen können. Zweifellos ist der Forex-Markt kein einfacher Markt, wenn es um Simulation und Wiedergabe geht. Obwohl wir dieselbe grundlegende Art von Informationen sehen, hat der Forex seine spezifischen Merkmale. Deshalb ist es interessant, sie zu beobachten und zu analysieren.

Dieser Markt weist einige Besonderheiten auf. Um das Wiedergabesystem zu implementieren, muss ich einige dieser Funktionen erklären, damit Sie verstehen, wovon wir sprechen und wie interessant es sein kann, andere Märkte zu kennen.


Wie wird der Handel durchgeführt?

Auf dem Devisenmarkt erfolgt der Handel in der Regel ohne eine echte Spanne zwischen dem BID- und dem ASK-Wert. In den meisten Fällen können diese beiden Werte identisch sein. Aber wie ist das möglich? Wie können sie dasselbe sein? Anders als auf dem Aktienmarkt, wo es immer eine Spanne zwischen BID und ASK gibt, ist dies auf dem Devisenmarkt nicht der Fall. Obwohl es manchmal eine Spanne gibt und sie manchmal viel höher ist, können die BID- und ASK-Werte normalerweise gleich sein. Dies kann für diejenigen, die vom Aktienmarkt kommen und in den Devisenhandel einsteigen wollen, verwirrend sein, da die Handelsstrategien oft erhebliche Änderungen erfordern.

Es sei auch darauf hingewiesen, dass die Hauptakteure auf dem Devisenmarkt die Zentralbanken sind. Diejenigen, die an der B3 (brasilianische Börse) arbeiten, haben bereits gesehen und wissen sehr gut, was die Zentralbank manchmal mit dem Dollar macht. Aus diesem Grund meiden viele den Handel mit diesem Vermögenswert aus Angst vor möglichen Interventionen der Zentralbank in die Währung, da dies eine zuvor gewinnbringende Position schnell in einen großen Verlierer verwandeln könnte. Viele unerfahrene Händler gehen in dieser Zeit oft in Konkurs und werden in einigen Fällen sogar von der Börse und dem Börsenmakler verklagt. Dies könnte durch eine der Interventionen geschehen, die die Zentralbank ohne Vorwarnung und ohne Gnade für die Inhaber von Positionen durchführen kann.

Für uns spielt dies jedoch keine Rolle, denn wir sind an dem Programm selbst interessiert. Auf dem Devisenmarkt basiert die Anzeige der Preise auf dem BID-Kurswert, wie in Abbildung 01 dargestellt.


Abbildung 01 

Abbildung 01: Chartdarstellung des Devisenmarkt

Wie sich dies beispielsweise vom B3-Anzeigesystem unterscheidet, das den Preis des letzten ausgeführten Geschäfts verwendet, ist in Abbildung 02 unten zu sehen, wo wir Daten für einen Dollar-Futures-Kontrakt haben (zum Zeitpunkt der Erstellung dieses Artikels).

Abbildung 02 

Abbildung 02: Mini-Dollar-Kontrakt, der an der brasilianischen Börse (B3) gehandelt wird.

Das Replay/Simulations-System wurde entwickelt, um die Anwendung dieser Art von Analyse zu fördern. Das heißt, wenn wir den letzten gehandelten Preis verwenden, haben wir einen Unterschied in der Art und Weise, wie die Daten in der gehandelten Tick-Datei angeordnet sind. Nicht nur das, sondern wir können sogar einen großen Unterschied in der Art der Informationen haben, die tatsächlich in der Tick-Datei oder in den 1-Minuten-Balken zur Verfügung stehen werden. Aufgrund dieser Variationen werden wir uns jetzt ausschließlich auf die Durchführung der Wiedergabe konzentrieren, da die Simulation andere, noch komplexere Probleme beinhaltet. Wie zu Beginn dieses Artikels erwähnt, ist es jedoch möglich, Daten der 1-Minuten-Balken zu verwenden, um zu simulieren, was wahrscheinlich während des Handels passiert ist. Ohne nur über die Theorie zu sprechen, wir wollen den Informationsunterschied zwischen Forex und dem Aktienmarkt am Beispiel der brasilianischen Börse B3 untersuchen, für die das Replay/Simulationssystem ursprünglich entwickelt wurde. In Abbildung 03 finden Sie Informationen über eines der Forex-Währungspaare.

Abbildung 03

Abbildung 03: Informationen über reale Handelsgeschäfte auf dem Forex-Markt

Abbildung 04 zeigt die gleiche Art von Informationen, aber diesmal aus einem der Mini-Dollar-Futures-Kontrakte, die an der B3 (Brasilianische Börse) gehandelt werden.

Abbildung 04

Abbildung 04: Informationen von realen Ticks von B3

Das ist etwas völlig anderes. Auf dem Devisenmarkt gibt es keinen letzten Preis und kein letztes Handelsvolumen. Auf B3 sind diese Werte verfügbar, und viele Handelsmodelle verwenden das Handelsvolumen und den Preis des letzten Geschäfts. Alles, was bisher gesagt wurde, soll lediglich verdeutlichen, dass die Art und Weise, wie das System aufgebaut ist, es nicht zulässt, andere Arten von Märkten zu bedienen, ohne dass einige wesentliche Änderungen vorgenommen werden. Ich habe überlegt, das Thema aus der Marktperspektive aufzuteilen, aber aus irgendeinem Grund wäre das nicht praktikabel gewesen. Nicht unter dem Gesichtspunkt der Programmierung, denn eine solche Trennung würde die Programmierung stark vereinfachen, sondern unter dem Gesichtspunkt der Nutzerfreundlichkeit, denn wir werden uns immer an den einen oder anderen Markt anpassen müssen. Wir können nur versuchen, einen Mittelweg zu finden, aber das wird nicht ohne Anstrengung gehen. Ich werde versuchen, diese Schwierigkeiten so weit wie möglich zu minimieren, denn ich will und werde nicht das gesamte System von Grund auf neu erstellen.


Beginn der Umsetzung zur Abdeckung von Forex

Als Erstes müssen wir das Gleitkomma-Zahlensystem festlegen. Aber im vorigen Artikel haben wir bereits einige Änderungen am Gleitkommasystem vorgenommen. Und es ist nicht für den Devisenhandel geeignet. Das liegt daran, dass die Genauigkeit auf vier Dezimalstellen begrenzt ist und wir dem System mitteilen müssen, dass wir eine Menge mit mehr Dezimalstellen verwenden werden. Wir müssen dies beheben, um weitere Probleme in der Zukunft zu vermeiden. Diese Korrektur wird im folgenden Code vorgenommen:

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Serviço Market Replay **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

Genau hier teilen wir MetaTrader 5 mit, dass wir mehr Dezimalstellen in unserem Fließkommasystem benötigen. In diesem Fall werden wir 8 Dezimalstellen verwenden, was mehr als genug ist, um ein breites Spektrum an Bedingungen abzudecken. Ein wichtiges Detail: B3 kommt gut mit 4 Dezimalstellen zurecht, aber um in Forex zu arbeiten, brauchen wir 5 Dezimalstellen. Durch die Verwendung von 8 machen wir das System frei. Dies wird jedoch nicht immer der Fall sein. Wir müssen es später wegen eines Details ändern, das ich noch nicht erklären kann, aber für den Moment reicht es.

Sobald dies geschehen ist, werden wir damit beginnen, unser Leben auf irgendeine Weise zu erleichtern. Wir gehen von folgendem Szenario aus: Die Vorschaubalken, die auf unserem Chart angezeigt werden, sind 1-Minuten-Balken. Was die Ticks betrifft, so handelt es sich um echte Ticks, die in einer anderen Datei vorhanden sind. Wir werden also zum einfachsten System zurückkehren, uns aber schnell zu einem umfassenderen System hocharbeiten.


Arbeiten mit den Basics

Um den Nutzer nicht zu zwingen, die Art des zu analysierenden Marktes auszuwählen, d.h. die Art des Marktes, von dem die Daten für die Wiedergabe stammen, werden wir die Tatsache ausnutzen, dass wir in einigen Fällen den Wert des letzten Preises oder des Handelsvolumens nicht haben werden, und in anderen Fällen werden wir ihn haben. Damit das System dies für uns prüfen kann, müssen wir etwas in den Code einfügen.

Zunächst fügen wir Folgendes hinzu:

class C_FileTicks
{
    protected:
        enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
        struct st00
        {
            MqlTick   Info[];
            MqlRates  Rate[];
            int       nTicks,
                      nRate;
            bool      bTickReal;
            ePlotType ModePlot;
        }m_Ticks;

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

Diese Aufzählung wird uns helfen, Verwirrung in einigen Bereichen zu vermeiden. Im Wesentlichen lassen sich zwei Arten von Märkten unterscheiden. Sie werden den Grund dafür bald verstehen. Um unnötige Funktionsaufrufe zu vermeiden, sollten wir dem System eine neue Variable hinzufügen. Jetzt nehmen die Dinge allmählich Gestalt an, aber das System muss in der Lage sein zu erkennen, wann wir eine bestimmte Anzeigemethode verwenden. Ich möchte dem Nutzer das Leben nicht mit solchen Problemen erschweren, also nehmen wir eine kleine Änderung am unten stehenden Code vor:

inline bool ReadAllsTicks(const bool ToReplay)
        {
#define def_LIMIT (INT_MAX - 2)
#define def_Ticks m_Ticks.Info[m_Ticks.nTicks]

                string   szInfo;
                MqlRates rate;

                Print("Loading ticks for replay. Please wait...");
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                m_Ticks.ModePlot = PRICE_FOREX;
                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                {
                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                        def_Ticks.time = StringToTime(StringSubstr(szInfo, 0, 19));
                        def_Ticks.time_msc = (def_Ticks.time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                        def_Ticks.bid = StringToDouble(FileReadString(m_File));
                        def_Ticks.ask = StringToDouble(FileReadString(m_File));
                        def_Ticks.last = StringToDouble(FileReadString(m_File));
                        def_Ticks.volume_real = StringToDouble(FileReadString(m_File));
                        def_Ticks.flags = (uchar)StringToInteger(FileReadString(m_File));
                        m_Ticks.ModePlot = (def_Ticks.volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot);
                        if (def_Ticks.volume_real > 0.0)
                        {
                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                m_Ticks.nRate += (BuiderBar1Min(rate, def_Ticks) ? 1 : 0);
                                m_Ticks.Rate[m_Ticks.nRate] = rate;
                        }
                        m_Ticks.nTicks++;
                }
                FileClose(m_File);
                if (m_Ticks.nTicks == def_LIMIT)
                {
                        Print("Too much data in the tick file.\nCannot continue...");
                        return false;
                }
                return (!_StopFlag);
#undef def_Ticks
#undef def_LIMIT
        }

Zunächst legen wir fest, dass die Anzeigeart ein FOREX-Modell sein soll. Wird jedoch beim Lesen der Tick-Datei ein Tick gefunden, der ein gehandeltes Volumen enthält, wird dieses Modell in den Typ EXCHANGE geändert. Es ist wichtig zu verstehen, dass dies ohne jeglichen Eingriff des Nutzers geschieht. Hier ergibt sich jedoch ein wichtiger Punkt: Dieses System funktioniert nur in den Fällen, in denen das Lesen während des Starts der Wiedergabe durchgeführt wird. Im Falle der Simulation ist die Situation anders. Wir werden uns vorerst nicht mit der Simulation befassen.

Aus diesem Grund sollten Sie, bis wir den Simulationscode erstellen, KEINE Dateien mit Balken verwenden. Es ist unbedingt erforderlich, echte oder simulierte Tick-Dateien zu verwenden. Es gibt Möglichkeiten, simulierte Tick-Dateien zu erstellen, aber ich werde nicht ins Detail gehen, da dies den Rahmen dieses Artikels sprengen würde. Wir dürfen die Nutzer jedoch nicht völlig im Dunkeln lassen. Auch wenn das System die Daten analysieren kann, können wir dem Nutzer zeigen, welche Art von Anzeige verwendet wird. Indem wir das Fenster Symbol öffnen, können wir das Anzeigeformular überprüfen. Genau wie in Abbildung 01 und Abbildung 02 dargestellt.

Um dies zu ermöglichen, müssen wir unserem Code noch einige Dinge hinzufügen. Dazu gehören die unten aufgeführten Zeilen:

datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
    {
        int      MemNRates,
                 MemNTicks;
        datetime dtRet = TimeCurrent();
        MqlRates RatesLocal[];

        MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
        MemNTicks = m_Ticks.nTicks;
        if (!Open(szFileNameCSV)) return 0;
        if (!ReadAllsTicks(ToReplay)) return 0;
        if (!ToReplay)
        {
            ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
            ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
            CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
            dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
            m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
            m_Ticks.nTicks = MemNTicks;
            ArrayFree(RatesLocal);
        }else
        {
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
        }
        m_Ticks.bTickReal = true;

        return dtRet;
    };

Durch die Hinzufügung dieser hervorgehobenen Zeilen erhalten wir mehr Informationen über das Symbol. Aber ich möchte, dass Sie aufpassen, was ich Ihnen jetzt erkläre, denn wenn Sie keines der hier vorgestellten Konzepte verstehen, werden Sie denken, dass das System Ihnen einen Streich spielt. Abbildung 05 zeigt, was der obige Code bewirkt. Versuchen Sie dies mit anderen Symbolen, um die Sache zu verdeutlichen. Der erste Punkt bezieht sich auf diese spezielle Linie. Sie zeigt an, welche Art von Berechnung wir für das Symbol verwenden können oder werden. Es gibt zwar noch mehr Möglichkeiten, Symbole zu berechnen, aber da es hier darum geht, es so einfach wie möglich zu halten und trotzdem zu funktionieren, haben wir es auf diese beiden Arten von Berechnungen reduziert.

Wenn Sie weitere Einzelheiten erfahren oder andere Berechnungsarten implementieren möchten, können Sie in der Dokumentation nachschlagen und SYMBOL_TRADE_CALC_MODE anzeigen. Dort finden Sie eine detaillierte Beschreibung der einzelnen Berechnungsmodi. Hier werden wir nur auf die einfachste Weise arbeiten. Eine Sache, die Sie vielleicht verrückt macht, ist die zweite hervorgehobene Zeile. In dieser Zeile geben wir einfach die Art der Anzeige an. Im Grunde gibt es nur diese beiden Arten. Das Problem liegt hier nicht in dieser Zeile selbst, sondern in der Konfigurationsdatei, genauer gesagt in der Art und Weise, wie die Konfigurationsdatei im derzeitigen Entwicklungsstadium gelesen wird.

Derzeit ist das System so geschrieben, dass es Probleme gibt, wenn wir die Balken- und dann die Tick-Datei lesen. Das liegt nicht daran, dass wir etwas falsch gemacht haben, im Gegenteil, wir folgen der richtigen Logik. Die Tatsache, dass diese Zeile ausgeführt wird, nachdem die Balkendatei in das Chart geladen wurde, führt jedoch dazu, dass jeglicher Inhalt im Chart entfernt wird. Dieses Problem wird dadurch verursacht, dass die Daten nicht gepuffert werden, da sie direkt in das Chart gelangen. Diese Lösung wird jedoch später in diesem Artikel erörtert.

Meiner persönlichen Meinung nach würde ich alles anders machen, wenn das System nur für meinen persönlichen Gebrauch bestimmt wäre. Wir würden einfach eine Art von Warnung erzeugen, damit die Tick-Datei vor der Balken-Datei gelesen wird. Da das System aber häufig von Personen genutzt werden wird, die keine Programmierkenntnisse haben, erscheint es mir sinnvoll, ein solches Problem zu lösen. Das ist auch gut so, denn wir verwenden einen sehr interessanten und recht nützlichen Trick.

Abbildung 05

Abbildung 05: Anzeige der automatischen Tick-Erkennung

Da das System nun einige Dinge erkennen kann, müssen wir es irgendwie an unser Anzeigesystem anpassen.

class C_Replay : private C_ConfigService
{
    private :
        long         m_IdReplay;
        struct st01
        {
            MqlRates Rate[1];
            datetime memDT;
            int      delay;
        }m_MountBar;
        struct st02
        {
            bool     bInit;
        }m_Infos;

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

Um alles zu konfigurieren und die Einstellungen zu speichern, fügen wir zunächst diese Variable hinzu. Sie zeigt an, ob das System vollständig initialisiert wurde. Aber achten Sie darauf, wie wir es verwenden werden. Zunächst wird er mit dem entsprechenden Wert initialisiert. Dies geschieht in dem folgenden Codefragment:

C_Replay(const string szFileConfig)
{
    m_ReplayCount = 0;
    m_dtPrevLoading = 0;
    m_Ticks.nTicks = 0;
    m_Infos.bInit = false;
    Print("************** Serviço Market Replay **************");
    srand(GetTickCount());
    GlobalVariableDel(def_GlobalVariableReplay);
    SymbolSelect(def_SymbolReplay, false);
    CustomSymbolDelete(def_SymbolReplay);
    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
    CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
    CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
    SymbolSelect(def_SymbolReplay, true);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
    CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
    CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
    m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
}

Warum initialisieren wir die Variable mit false? Der Grund dafür ist, dass das System noch nicht initialisiert ist, aber sobald wir das Chart auf den Bildschirm laden, wird es initialisiert werden. Man könnte meinen, dass wir dies in der Funktion, die das Chart initialisiert, angeben würden. Richtig? Nein. Wir können dies nicht in der Funktion tun, die das Chart initialisiert. Wir müssen warten, bis diese Initialisierungsfunktion abgeschlossen ist, und sobald die nächste Funktion aufgerufen wird, können wir angeben, dass das System initialisiert ist. Aber warum wird diese Variable verwendet? Weiß das System wirklich nicht, ob es bereits initialisiert wurde oder nicht? Warum müssen wir die Variable verwenden? Das System weiß, dass sie initialisiert ist, aber wir brauchen diese Variable aus einem anderen Grund. Betrachten wir zur Verdeutlichung eine Funktion, die ihren Zustand ändert.

bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
        {
                u_Interprocess Info;
                int iPos, iTest;

                if (!m_Infos.bInit)
                {
                        ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
                        ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
                        ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
                        m_Infos.bInit = true;
                }
                iTest = 0;
                while ((iTest == 0) && (!_StopFlag))
                {
                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                        if (iTest == 0) Sleep(100);
                }
                if ((iTest < 0) || (_StopFlag)) return false;
                AdjustPositionToReplay(bViewBuider);
                m_MountBar.delay = 0;
                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                {
                        CreateBarInReplay(bViewMetrics, true);
                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                        if (m_MountBar.delay > 400)
                        {
                                if (ChartSymbol(m_IdReplay) == "") break;
                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                if (!Info.s_Infos.isPlay) return true;
                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                Sleep(m_MountBar.delay - 20);
                                m_MountBar.delay = 0;
                        }
                }                               
                return (m_ReplayCount == m_Ticks.nTicks);
        }

Die oben beschriebene Funktion bereitet uns einige Kopfschmerzen. Aus diesem Grund benötigen wir eine Variable, die angibt, ob das System initialisiert ist oder nicht. Bitte beachten Sie: Wenn Sie diese Funktion zum ersten Mal ausführen, zeigt die Variable noch an, dass das System NICHT vollständig initialisiert wurde. Zu diesem Zeitpunkt wird die Initialisierung abgeschlossen sein. Hier stellen wir sicher, dass die richtigen Kurslinien auf dem Bildschirm angezeigt werden. Wenn das System den Anzeigemodus als FOREX definiert, werden die BID- und ASK als Kurslinien angezeigt, und die letzte Kurslinie wird ausgeblendet. Das Gegenteil ist der Fall, wenn der Anzeigemodus EXCHANGE ist. In diesem Fall werden die BID- und ASK-Preislinien ausgeblendet und die letzte Preislinie wird angezeigt.

Alles wäre sehr schön und gut, wenn nicht einige Nutzer eine andere Einstellung bevorzugen würden. Auch wenn sie mit dem Anzeigestil EXCHANGE arbeiten, zeigen sie gerne die BID- oder ASK-Linien an, in manchen Fällen sogar beide Linien. Wenn also der Nutzer das System nach der Konfiguration nach seinen Wünschen pausiert und dann neu startet, ignoriert das System die Einstellungen des Nutzers und kehrt zu seinen internen Einstellungen zurück. Wenn Sie jedoch angeben, dass das System bereits initialisiert wurde (und dafür eine Variable verwenden), wird es nicht zu den internen Einstellungen zurückkehren, sondern so bleiben, wie der Nutzer es gerade konfiguriert hat.

Aber dann stellt sich die Frage: Warum wird diese Einstellung nicht in der Funktion ViewReplay vorgenommen? Der Grund dafür ist, dass das Chart eine solche Einstellung für die Linien nicht erhalten hat. Wir werden auch andere, eher unangenehme Probleme lösen müssen. Wir brauchen zusätzliche Variablen, die uns helfen. Eine einfache Programmierung wird nicht alle Probleme lösen.


Anzeige der Balken

Wir haben nun endlich den Punkt erreicht, an dem wir Balken im Chart anzeigen können. Wenn Sie dies jedoch zu diesem Zeitpunkt versuchen, werden Sie Bereichsfehler in den Arrays feststellen. Daher müssen wir, bevor wir die Balken im Chart darstellen können, einige Korrekturen am System vornehmen.

Die erste Lösung findet sich in der folgenden Funktion:

void AdjustPositionToReplay(const bool bViewBuider)
        {
                u_Interprocess Info;
                MqlRates       Rate[def_BarsDiary];
                int            iPos, nCount;

                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
                for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                if (iPos < m_ReplayCount)
                {
                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                        CustomTicksDelete(def_SymbolReplay, m_Ticks.Info[iPos].time_msc, LONG_MAX);
                        if ((m_dtPrevLoading == 0) && (iPos == 0)) FirstBarNULL(); else
                        {
                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                m_ReplayCount++;
                        }
                }else if (iPos > m_ReplayCount)
                {
                        if (bViewBuider)
                        {
                                Info.s_Infos.isWait = true;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }else
                        {
                                for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++);
                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                        }
                }
                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false, false);
                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                Info.s_Infos.isWait = false;
                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
        }

Mit dieser Prüfung kann das System feststellen, ob die ersten paar Ticks übersprungen werden sollen. Das Problem ist, dass die in der Prüfung enthaltene Schleife fehlschlägt, wenn nicht geprüft wird, ob es sich um eine börsenähnliche Anzeige handelt. Und das führt zu einem Bereichsfehler. Durch Hinzufügen einer zweiten Prüfung können wir diese Phase jedoch umgehen. Wenn der Anzeigemodus mit dem in FOREX verwendeten Typ übereinstimmt, wird die Schleife nicht ausgeführt. Bereiten wir uns also auf die nächste Etappe vor.

Im nächsten Schritt tragen wir Ticks auf das Chart auf. Hier müssen wir uns nur darum kümmern, dem System mitzuteilen, wie hoch der Schlusskurs des Balkens sein wird; der Rest wird von der Balkensimulationsfunktion erledigt. In diesem Fall ist sie sowohl für die Anzeige des Börsenmodus als auch für die FOREX-Anzeige gleich. Der Code für die Implementierung ist unten dargestellt:

inline void CreateBarInReplay(const bool bViewMetrics, const bool bViewTicks)
    {
#define def_Rate m_MountBar.Rate[0]

        bool bNew;
        MqlTick tick[1];
        static double PointsPerTick = 0.0;

        if (bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
        {
            PointsPerTick = (PointsPerTick == 0.0 ? SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) : PointsPerTick);                    
            if (bViewMetrics) Metrics();
            m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
            def_Rate.real_volume = 0;
            def_Rate.tick_volume = 0;
        }
        def_Rate.close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close) :
                                                               (m_Ticks.Info[m_ReplayCount].bid > 0.0 ? m_Ticks.Info[m_ReplayCount].bid : def_Rate.close));
        def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
        def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
        def_Rate.low = (bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
        def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
        def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
        def_Rate.time = m_MountBar.memDT;
        CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
        if (bViewTicks)
        {
            tick = m_Ticks.Info[m_ReplayCount];
            if (!m_Ticks.bTickReal)
            {
                static double BID, ASK;
                double  dSpread;
                int     iRand = rand();

                dSpread = PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? PointsPerTick : 0 ) : 0 );
                if (tick[0].last > ASK)
                {
                    ASK = tick[0].ask = tick[0].last;
                    BID = tick[0].bid = tick[0].last - dSpread;
                }
                if (tick[0].last < BID)
                {
                    ASK = tick[0].ask = tick[0].last + dSpread;
                    BID = tick[0].bid = tick[0].last;
                }
            }
            CustomTicksAdd(def_SymbolReplay, tick); 
        }
        m_ReplayCount++;

#undef def_Rate
    }

Diese Zeile tut genau das. Sie generiert den Schlusskurs des Balkens je nach Art der Anzeige. Ansonsten bleibt die Funktion dieselbe wie zuvor. Auf diese Weise können wir das FOREX-Mapping-System abdecken und mit den in der Tick-Datei enthaltenen Daten eine Wiedergabe durchführen. Wir haben noch nicht die Möglichkeit, Simulationen durchzuführen.

Man könnte meinen, dass das System bereits fertig ist, aber das ist es nicht. Wir haben immer noch zwei Probleme, die sehr wichtig sind, bevor wir überhaupt an die FOREX-Simulation denken.

Das erste Problem ist, dass die Konfigurationsdatei für die Wiedergabe/Simulation eine Erstellungslogik haben muss. In vielen Fällen wird der Nutzer dadurch gezwungen, sich an das System anzupassen, ohne dass dies wirklich notwendig ist. Außerdem haben wir ein weiteres Problem. Das Timer-System. Der Grund für das zweite Problem ist, dass wir es mit einem Symbol oder einer Handelszeit zu tun haben, bei dem das Symbol stundenlang inaktiv bleiben kann, oder weil es versteigert oder ausgesetzt wird oder aus einem anderen Grund. Aber das macht nichts, wir müssen auch das Problem mit dem Timer lösen.

Da das zweite Problem dringender und dringender ist, sollten wir mit ihm beginnen.


Festlegen des Timers

Das größte Problem mit dem System und dem Timer ist, dass das System nicht mit den Bedingungen umgehen kann, die manchmal in einigen Symbolen auftreten. Diese Bedingung kann eine extrem niedrige Liquidität, die Aussetzung von Transaktionen, Auktionen oder andere Gründe sein. Wenn die Tick-Datei dem Timer aus irgendeinem Grund mitteilt, dass das Symbol z. B. 15 Minuten lang schlafen soll, wird das System während dieser Zeit vollständig gesperrt.

Auf dem realen Markt wird dies auf eine besondere Weise gelöst. Normalerweise werden wir von der Plattform benachrichtigt, wenn ein Symbol nicht gehandelt wird. Aber auch wenn die Plattform uns diese Information nicht zur Verfügung stellt, erhalten wir eine Benachrichtigung vom Markt. Erfahrene Händler werden bei der Betrachtung eines Symbols feststellen, dass in dieser Zeit etwas passiert ist und nichts getan werden muss. Wenn jedoch die Marktwiedergabe verwendet wird, kann diese Situation problematisch sein. Der Nutzer muss die Möglichkeit haben, die Position, in der die Wiedergabe oder Simulation läuft, zu schließen oder zu ändern.

Diese Art von Lösung wurde schon früher verwendet, und zwar mit Hilfe eines Kontrollanzeigers. Es gab jedoch keine Präzedenzfälle, die uns zu einer solch drastischen Maßnahme gezwungen hätten, bis hin zur Wiederholung der Situation, in der ein Symbol zur Versteigerung angeboten wurde, mit sehr geringer Liquidität, oder sogar aufgrund eines relevanten Ereignisses ausgesetzt wurde. All dies schafft das so genannte Liquiditätsrisiko, aber mit dem Replay/Simulationssystem können wir dieses Risiko leicht vermeiden und unsere Forschung fortsetzen. Um dies effektiv zu tun, müssen wir jedoch die Funktionsweise des Timers ändern.

Als Nächstes haben wir eine neue Anzeigesystemschleife. Ich weiß, der Code mag auf den ersten Blick verwirrend erscheinen.

bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
    {
        u_Interprocess Info;
        int iPos, iTest;

        iTest = 0;
        while ((iTest == 0) && (!_StopFlag))
        {
            iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
            iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
            iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
            if (iTest == 0) Sleep(100);
        }
        if ((iTest < 0) || (_StopFlag)) return false;
        AdjustPositionToReplay(bViewBuider);
        iPos = 0;
        while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
        {
            iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
            m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
            iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0);
            CreateBarInReplay(bViewMetrics, true);
            if (m_MountBar.delay > 400)
            while ((iPos > 200) && (!_StopFlag))
            {
                if (ChartSymbol(m_IdReplay) == "") break;
                if (ChartSymbol(m_IdReplay) == "") return false;
                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                if (!Info.s_Infos.isPlay) return true;
                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                Sleep(195);
                iPos -= 200;
                Sleep(m_MountBar.delay - 20);
                m_MountBar.delay = 0;
            }
        }                               
        return (m_ReplayCount == m_Ticks.nTicks);
    }

Alle entfernten Teile wurden durch andere Codes ersetzt. So können wir das erste Problem im Zusammenhang mit der Anzeige von Balken lösen. Bisher haben wir die Rückwärtsrechnung verwendet, jetzt werden wir die Vorwärtsrechnung verwenden. Dadurch wird verhindert, dass im Idle-Modus seltsame Dinge auf dem Chart passieren. Bitte beachten Sie, dass das System beim Start immer eine gewisse Zeit wartet, bevor es den Tick anzeigt. Zuvor wurde genau das Gegenteil gemacht (MEINE SCHULD). Da die Zeit nun sehr lang sein kann, bis hin zu mehreren Stunden, haben wir eine weitere Möglichkeit, den Timer zu steuern. Zum besseren Verständnis sollten Sie wissen, dass das System bisher im Standby-Modus blieb, bis die Zeit vollständig abgelaufen war. Wenn wir versuchten, irgendwelche Änderungen vorzunehmen, z. B. das Chart zu schließen oder den Ausführungszeitpunkt zu ändern, reagierte das System einfach nicht wie erwartet. Dies geschah, weil bei den früher beigefügten Dateien kein Risiko bestand, einen Satz zu verwenden, bei dem der Vermögenswert für eine lange Zeit „nicht gehandelt“ wurde. Doch als ich diesen Artikel zu schreiben begann, zeigte das System diesen Fehler an. Es wurden daher Korrekturen vorgenommen.

Der Timer läuft nun so lange, bis die Periode größer oder gleich 200 Millisekunden ist. Sie können diesen Wert ändern, aber achten Sie darauf, ihn auch an anderen Stellen zu ändern. Wir haben begonnen, die Situation zu verbessern, aber wir müssen noch eine weitere Sache tun, bevor wir fertig sind. Wenn wir das Chart schließen, verlässt das System die Schleife. Gehen wir nun zurück zum aufrufenden Programm. Damit ist sichergestellt, dass alles funktioniert, zumindest in der Theorie. Der Grund dafür ist, dass der Nutzer während der Leerlaufzeit erneut mit dem System interagieren kann. Die übrigen Funktionen sind praktisch unangetastet geblieben, sodass alles weiter funktioniert wie bisher. Wenn Sie jedoch während einer bestimmten Zeitspanne den Kontrollanzeiger auffordern, seine Position zu ändern, ist dies nun möglich, während dies vorher nicht möglich war. Dies ist sehr wichtig, denn manche Vermögenswerte können über einen längeren Zeitraum hinweg ruhen. Dies ist im realen Handel akzeptabel, aber NICHT in einem Replay/Simulationssystem.


Schlussfolgerung

Trotz aller Misserfolge können Sie nun mit der Verwendung von FOREX-Daten im System experimentieren. Beginnen wir mit dieser Version des Replay/Simulationssystems. Um dies zu untermauern, stelle ich im Anhang einige FOREX-Daten zur Verfügung. Das System muss in einigen Bereichen noch verbessert werden. Da ich jetzt noch nicht ins Detail gehen möchte (da dies möglicherweise radikale Änderungen an einigen der in diesem Artikel gezeigten Dinge erfordert), beende ich die Änderungen hier.

Im nächsten Artikel werde ich einige weitere ungelöste Fragen ansprechen. Diese Probleme hindern uns zwar nicht daran, das System zu nutzen, aber wenn wir es mit FOREX-Marktdaten verwenden wollen, werden wir feststellen, dass einige Dinge nicht korrekt dargestellt werden. Wir müssen sie ein wenig anpassen, aber das werden wir im nächsten Artikel sehen.

Das Problem mit der Konfigurationsdatei und andere damit zusammenhängende Probleme müssen noch gelöst werden. Da das System ordnungsgemäß funktioniert und es Ihnen ermöglicht, Daten vom Devisenmarkt zu replizieren, wird die Lösung dieser Probleme auf den nächsten Artikel verschoben.


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

Beigefügte Dateien |
Market_Replay_yvg20.zip (14386.04 KB)
Brute-Force-Ansatz zur Mustersuche (Teil VI): Zyklische Optimierung Brute-Force-Ansatz zur Mustersuche (Teil VI): Zyklische Optimierung
In diesem Artikel zeige ich den ersten Teil der Verbesserungen, die es mir ermöglicht haben, nicht nur die gesamte Automatisierungskette für den Handel mit MetaTrader 4 und 5 zu schließen, sondern auch etwas viel Interessanteres zu tun. Von nun an ermöglicht mir diese Lösung, sowohl die Erstellung von EAs als auch die Optimierung vollständig zu automatisieren und die Arbeitskosten für das Finden effektiver Handelskonfigurationen zu minimieren.
Neuronale Netze leicht gemacht (Teil 52): Forschung mit Optimismus und Verteilungskorrektur Neuronale Netze leicht gemacht (Teil 52): Forschung mit Optimismus und Verteilungskorrektur
Da das Modell auf der Grundlage des Erfahrungswiedergabepuffers trainiert wird, entfernt sich die aktuelle Strategie oder Politik des Akteurs immer weiter von den gespeicherten Beispielen, was die Effizienz des Trainings des Modells insgesamt verringert. In diesem Artikel befassen wir uns mit einem Algorithmus zur Verbesserung der Effizienz bei der Verwendung von Stichproben in Algorithmen des verstärkten Lernens.
Neuronale Netze leicht gemacht (Teil 54): Einsatz von Random Encoder für eine effiziente Forschung (RE3) Neuronale Netze leicht gemacht (Teil 54): Einsatz von Random Encoder für eine effiziente Forschung (RE3)
Wann immer wir Methoden des Verstärkungslernens in Betracht ziehen, stehen wir vor dem Problem der effizienten Erkundung der Umgebung. Die Lösung dieses Problems führt häufig dazu, dass der Algorithmus komplizierter wird und zusätzliche Modelle trainiert werden müssen. In diesem Artikel werden wir einen alternativen Ansatz zur Lösung dieses Problems betrachten.
Entwicklung eines Replay Systems — Marktsimulation (Teil 19): Erforderliche Anpassungen Entwicklung eines Replay Systems — Marktsimulation (Teil 19): Erforderliche Anpassungen
Hier werden wir den Boden bereiten, damit wir, wenn wir neue Funktionen zum Code hinzufügen müssen, dies reibungslos und einfach tun können. Der derzeitige Kodex kann einige der Dinge, die notwendig sind, um sinnvolle Fortschritte zu erzielen, noch nicht abdecken oder behandeln. Wir müssen alles strukturieren, damit wir bestimmte Dinge mit minimalem Aufwand umsetzen können. Wenn wir alles richtig machen, erhalten wir ein wirklich universelles System, das sich sehr leicht an jede Situation anpassen lässt, die es zu bewältigen gilt.