English 日本語
preview
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 80): Verwendung von Ichimoku-Muster und des ADX-Wilder mit TD3 Reinforcement Learning

MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 80): Verwendung von Ichimoku-Muster und des ADX-Wilder mit TD3 Reinforcement Learning

MetaTrader 5Handelssysteme |
59 0
Stephen Njuki
Stephen Njuki

Einführung

Das Terrain des Handels mit Algorithmen hat sich stetig weiterentwickelt, von statischen, auf Indikatoren basierenden Strategien hin zu dynamischeren Methoden, die sich an veränderte Marktbedingungen anpassen. Eine der vielversprechendsten Methoden in diesem Grenzbereich ist das Reinforcement Learning. Wir haben bereits Implementierungen dieses Verfahrens sowie des Inferenz-Lernens in Prototypen von Expert Advisors mit Assistenten untersucht. Im Gegensatz zu klassischen maschinellen Lernmethoden, die Vorhersagen oder Klassifizierungen vornehmen, ermöglicht das Bestärkende Lernen (Reinforcement Learning, RL) einem Agenten, durch Interaktion mit seiner Umgebung zu lernen. Beim Handel bedeutet dies, dass man die Preismerkmale kontinuierlich beobachtet, sich für Aktionen wie Kaufen, Verkaufen oder Halten entscheidet und die Belohnung in Form von Gewinn oder Verlust erhält.

In der Familie der RL-Algorithmen zeichnet sich der Twin Delayed Deep Deterministic Policy Gradient (TD3) in einigen Kreisen als solider Kandidat für Finanzanwendungen ab. TD3 ist für kontinuierliche Aktionsräume konzipiert und eignet sich daher besonders gut für Handelsprobleme, bei denen Positionsgrößen und Timing nicht binär sind, sondern eine feinkörnige Kontrolle erfordern. Im Vergleich zu seinem Vorgänger DDPG bringt TD3 entscheidende Stabilitätsverbesserungen mit sich, wie z.B. die Verwendung von mehr als einem Kritiker, das Hinzufügen von Rauschglättung zu den Zielaktionen und das Verzögern von Politikaktualisierungen, um eine Überanpassung an vorübergehende Schwankungen zu verhindern. 

Unser Hauptziel mit diesem Artikel ist, wie immer, zu demonstrieren, wie TD3-Modelle, wenn sie in Python trainiert werden, in das Expert Advisor Framework von MQL5 integriert werden können, um Prototypen zu erstellen. Konkret soll gezeigt werden, wie ein TD3-Akteursnetzwerk, das in das ONNX-Format exportiert wird, in eine nutzerdefinierte Signalklasse verpackt und verwendet werden kann. Wie immer werden diese Signalklassen dann mit Hilfe des MQL5-Assistenten zu einem Handelsroboter zusammengestellt.

Um diese Diskussion mit etwas mehr „Praxisnähe“ zu untermauern, schließt der Artikel mit einem Überblick über einige Vorwärtstests, die mit dem MetaTrader Strategy Tester durchgeführt wurden. Es werden drei Berichte analysiert, die jeweils die 3 Signalmuster abdecken, die wir im Artikel Teil 74 zur weiteren Untersuchung ausgewählt hatten. Dies waren die Signalmuster 0, 1 und 5.


Hintergrund zu RL im Handel

RL basiert auf einem einfachen, aber wirkungsvollen Zyklus – ein Agent interagiert mit einer Umgebung, führt Aktionen aus und lernt aus den erhaltenen Belohnungen. Um zu verstehen, wie wir dies im Handel nutzen, kann es hilfreich sein, wenn wir die Rolle der verschiedenen Komponenten im RL rekapitulieren.

  • Agent: Für unsere Zwecke ist dies der Handelsalgorithmus, der entscheidet, ob wir kaufen, verkaufen oder halten.
  • Environment: Der Finanzmarkt, dargestellt durch Preisentwicklung, Indikatoren und abgeleitete Merkmale.
  • State: Eine numerische Darstellung der aktuellen Marktbedingungen, die in unserem Fall die Indikatorwerte der Muster 0, 1 und 5 sind. Wir verwenden nicht mehr die binär formatierte Eingabe, die wir bisher verwendet haben, sondern werden uns an ein kontinuierlicheres Format wagen.
  • Action: Ist die Handelsentscheidung. Dies kann diskret in Form von Kauf/Verkauf/Neutral oder kontinuierlich in Form von Volumen oder Positionsgrößen erfolgen, wobei negative Werte für Verkauf und positive Werte für Kauf stehen. In diesem Artikel verwenden wir den ersten, diskreten Begriff.
  • Reward: Ein numerisches Signal zur Bewertung der Qualität der Aktion. Im Handel wird dies häufig aus den Ergebnissen von Gewinn und Verlust abgeleitet. Häufig können diese jedoch über Auszahlungsgebühren oder Transaktionskosten an das Risiko angepasst werden.

Dieser Zyklus wiederholt sich, während der Agent verschiedene Strategien erprobt, mit dem Ziel, die kumulativen Belohnungen über die Zeit zu maximieren. Im Gegensatz zu Regressions- oder Klassifizierungsaufgaben, bei denen ein fester Datensatz das Problem definiert, lebt RL von sequentiellen Entscheidungen. Das Handeln von heute soll sich auf das Ergebnis von morgen auswirken. 

Eine der besonderen Herausforderungen der Finanzmärkte besteht darin, dass sie nicht stationär sind. Im Gegensatz zu Brettspielen, bei denen sich die Regeln nie ändern, entwickelt sich die Marktdynamik mit neuen Teilnehmern, Vorschriften und globalen Ereignissen weiter. Dies macht Off-Policy-RL-Methoden wie TD3 besonders attraktiv, denn sie können aus Replay-Puffern mit unterschiedlichen Marktzuständen lernen und sich flexibler anpassen als On-Policy-Methoden wie SARSA oder PPO.

Ein weiteres wichtiges Element ist die Trennung zwischen deterministischer und stochastischer Politik. Deterministische Strategien wie TD3 ordnen jeden Zustand direkt einer bestimmten Aktion zu, was sie rechnerisch effizient und einfacher in latenzempfindlichen Handelssystemen einsetzbar macht. Dies könnte Dinge wie Arbitrage usw. betreffen. Stochastische Richtlinien hingegen basieren auf Stichproben aus Wahrscheinlichkeitsverteilungen, was in stark explorativen Umgebungen nützlich sein kann, aber bei der Auftragsausführung, wo Konsistenz wichtig ist, weniger praktikabel ist.

Schließlich führt RL den bereits in früheren RL-Artikeln angesprochenen Begriff der Exploration gegenüber der Exploitation ein. Im Handel manifestiert sich dies oft als Abwägung zwischen dem Ausprobieren neuer Strategien, der Exploration, und dem Festhalten an einem profitablen Ansatz, der Exploitation. TD3 behebt dieses Problem, indem es während des Trainings kontrolliertes Rauschen in seine Strategie einbaut, um sicherzustellen, dass der Agent Variationen testet, ohne in ein „rücksichtsloses“ Verhalten abzugleiten.

Zusammenfassend lässt sich sagen, dass RL einen rationalen Rahmen für den Handel bietet, bei dem der sich wiederholende Prozess des Aufspürens, Auswählens und Gewinnens/Verlierens mit dem Kreislauf von Akteur-Umwelt-Belohnung in Einklang steht. Mit dem TD3 können diese Ideen zu einem nicht flüchtigen, kontinuierlichen Aktionsalgorithmus weiterentwickelt werden, der die Lücke zwischen Python-basiertem Training und „praktischer“ Ausführung innerhalb von MQL5 schließen kann.



Das Argument für TD3

Bei den ersten Experimenten der Händler mit RL hat sich DDPG (Deep Deterministic Policy Gradient) recht schnell als der bevorzugte Algorithmus für Probleme mit kontinuierlichen Aktionen durchgesetzt. Diese DDPG kombinierte das System aus Agent und Kritiker mit neuronalen Netzen, was deterministische Strategien in Umgebungen wie Portfolioallokation oder Positionsskalierung ermöglichte. Die DDPG litt jedoch unter zwei entscheidenden Schwächen. Überschätzungsfehler bei Q-Werten und Instabilität durch häufige Aktualisierungen der Politik. Bei Finanzzeitreihen, bei denen Unvorhersehbarkeit die Norm ist, führen diese Fehler dazu, dass das RL-Modell in der Simulation gut abschneidet, aber unter realen Bedingungen zusammenbricht.

Diese Herausforderungen bildeten die Grundlage für Twin Delayed Deep Deterministic Policy Gradient (TD3). TD3 glänzt, weil es entwickelt wurde, um die Unzulänglichkeiten von DDPG zu beheben und gleichzeitig dessen Stärken beizubehalten. Sie bringt drei Neuerungen mit sich, die unmittelbar den Handelsanwendungen zugute kommen. Wir gehen diese nacheinander durch:

  • Zwillings-Kritiker: Viele RL-Algorithmen stützen sich bei der Schätzung von Aktionswerten oft auf nur ein Kritiker-Netzwerk. TD3 verwendet zwei. Für jedes Paar von Zustandsaktionen liefern die beiden Kritiker beide Einschätzungen, und der Algorithmus nimmt das Minimum der beiden. Durch diesen bescheidenen/konservativen Ansatz wird die Verzerrung der Überschätzung deutlich verringert. Vom Standpunkt des Handels aus betrachtet, verhindert es, dass der Agent bei einem riskanten Handelsaufbau zu optimistisch ist, was oft zu einer vorsichtigeren und stabileren Entscheidungsfindung führt.
  • Zielpolitik Glättung: Die Finanzmärkte sind voller Rauschen, das sich in zufälligen Kurssprüngen, falschen Ausbrüchen und kurzfristigen Volatilitätsspitzen äußert. TD3 wirkt dem entgegen, indem es während der Kritiker-Aktualisierung den Zielaktionen abgeschnittenes Gaußsches Rauschen hinzufügt. Dieser Glättungseffekt zwingt die Netze der Kritiker dazu, Wertfunktionen zu lernen, die weniger empfindlich auf kleine Schwankungen reagieren. Das Ergebnis ist eine Politik, die nicht auf einzelne Tick-Anomalien überreagiert, sondern stattdessen auf robustere Muster reagiert.
  • Verspätete Aktualisierungen der Politik: Im Rahmen der DDPG sollen die Netzwerke der Akteure und Kritiker gleichzeitig aktualisiert werden. TD3 führt jedoch eine absichtliche Verzögerung ein. Das Politik-/Akteurs-Netzwerk wird weniger häufig aktualisiert als die Kritiker. Dies sorgt dafür, dass der Akteur sich von gut ausgebildeten Kritikern leiten lässt, anstatt instabilen, halb erlernten Signalen nachzujagen. In der Praxis führt diese Verzögerung zu gleichmäßigeren Verbesserungen und weniger untypischen Verschiebungen im Handelsverhalten.

Insgesamt handelt es sich um Änderungen, die TD3 unter den RL-Algorithmen für den Handel stabiler und zuverlässiger machen. In einem Bereich, in dem die Kontrolle des Drawdowns und die risikobereinigten Renditen von großer Bedeutung sind, ist die konservative Ausrichtung von TD3 von Vorteil, da die Hebelwirkung in Form von Investitionen im Namen anderer ein wachsender und florierender Bereich ist. Während andere Algorithmen wie PPO oder SAC für allgemeine RL-Aufgaben beliebt sind, sind sie aufgrund ihres stochastischen Charakters und ihrer Abhängigkeit von diskreten Stichproben bei der Verwendung in kontinuierlichen Echtzeit-Handelskontexten komplizierter oder weniger einfach.

Ein weiterer wichtiger Grund, warum TD3 bevorzugt werden sollte, ist seine Kompatibilität mit dem ONNX-Export. Das Akteursnetzwerk, das Zustände auf Handlungen bzw. Handelsentscheidungen abbildet, ist das einzige Netzwerk, das exportiert werden muss, um Projektionen für Händler zu erstellen. Es handelt sich um ein leichtgewichtiges, portables Setup, das sich gut für die Einbettung in MQL5-Umgebungen eignet, in denen die Ausführungsgeschwindigkeit sehr wichtig ist. Wir haben bereits dargelegt, warum eine direktes Training in MQL5 ineffizient und ressourcenintensiv ist. Der bevorzugte Ansatz ist, dass Python die schwere Arbeit beim Training übernimmt, während MQL5 die Inferenz und die Handelsausführung erledigt.

Zusammenfassend lässt sich sagen, dass TD3 durch die Kombination von Zwillingskritik, geglätteten Zielen und verzögerten Aktualisierungen eine gewisse Stabilität bietet, die notwendig ist, um die Zufälligkeit vieler Finanzmärkte zu überstehen. Sein deterministisches Design sorgt für konsistente Entscheidungen zur Ausführungszeit, und seine ONNX-Minimalanforderungen ermöglichen eine nahtlosere Bereitstellung für Expert Advisors in MQL5. Aus diesem Grund ist TD3 mehr als nur eine akademische Verbesserung gegenüber DDPG, es ist ein geringfügig robusteres Werkzeug für Handelsstrategien.



Training TD3 Modelle

Die Phase des Trainings eines TD3-Agenten findet vollständig in Python statt, wo Bibliotheken wie PyTorch die Flexibilität und Recheneffizienz bieten, die beim Umgang mit großen Mengen von Finanzdaten erforderlich sind. Der gesamte Prozess kann in vier zentrale Elemente unterteilt werden. Dabei handelt es sich um die Gestaltung der Umgebung, die Verwaltung des Wiedergabepuffers, die Abstimmung der Hyperparameter und die Trainingsschleife.

Im RL legt die Umgebung die Regeln der Interaktion fest. Für Handelsszenarien konstruieren wir daher eine zeilenformatierte Umgebung, wobei jede Zeile der historischen Daten einem Zeitschritt entspricht. Kursmerkmale wie Differenzen, Ichimoku-Spannen und ADX-Werte bilden schließlich den Zustandsvektor, den Input für das Akteursnetzwerk. Die Aktion wird dann zur entsprechenden Handelsentscheidung, die vom TD3-Akteursnetzwerk empfohlen wird, typischerweise skaliert im Bereich [-1,1]. Belohnungen sind, wie immer, sehr wichtig. Denn sie leiten den Agenten über Delta-Empfehlungen in den Rückwärtsdurchläufen des Akteursnetzwerks zu langfristig profitablen Strategien. In unserem Fall ist eine einfache, aber wirksame Wahl:

F1

wobei:

  • yt ist die gerichtete Marktbewegung
  • txn_cost sind eine Strafe im Verhältnis zu den Aktionsänderungen.

Anstatt also alte Umwelterfahrungen zu verwerfen, werden diese in einem großen Puffer gespeichert. Beim Training entnimmt der Agent nach dem Zufallsprinzip Mini-Batches aus diesem Puffer, um seine Netze zu aktualisieren. Diese Methode beseitigt zeitliche Korrelationen von Finanzdaten, stabilisiert das Lernen und verbessert die Generalisierung.

Für den Handel bedeutet dies, dass der Agent in der Lage ist, aus einer Vielzahl vergangener Marktbedingungen zu lernen, wie z. B. aus Trendphasen und Ausschlagssituationen, ohne auf die jüngste Vergangenheit beschränkt zu sein.

TD3 hat auch eine Reihe von Hyperparametern, die seine Leistung stark beeinflussen, insbesondere in Umgebungen wie den Finanzmärkten. Dazu gehören die Batch-Größe, bei der größere Batches die Gradientenschätzungen glätten, aber tendenziell mehr Speicherplatz erfordern; der Abzinsungsfaktor Gamma, der dafür sorgt, dass der Agent langfristige Gewinne fast genauso hoch einschätzt wie die unmittelbaren Gewinne – ein Schlüsselfaktor insbesondere beim Swing-Trading; dann gibt es noch die weiche Aktualisierungsrate tau, die steuert, wie langsam die Zielnetzwerke die Online-Netzwerke verfolgen, um Instabilität zu vermeiden; dann gibt es noch policy-noise und noise-clip, die die Zufälligkeit regulieren, die während der Aktualisierungen des Netzes der Kritiker in die Zielaktionen eingefügt wird, wodurch das Netzwerk daran gehindert wird, einzelne Ausreißer zu verfolgen; und schließlich haben wir policy-delay, das sicherstellt, dass wir das Akteursnetzwerk weniger häufig aktualisieren als die Kritiker, was die Verbesserungen der Politik stabilisiert.

Die Zuweisung von Werten für jeden dieser Hyperparameter ist teils eine Wissenschaft, teils eine Kunst. In der Praxis kann man jedoch mit den empfohlenen Standardwerten in TD3-Papieren beginnen und sie dann auf der Grundlage von Handelsdatenattributen anpassen, was am besten funktioniert. Die Trainingsschleife arbeitet in Epochen, in denen der Agent über Zyklen von Zeilen mit historischen Daten iteriert. Bei jedem Schritt beobachten wir den Zustand, in dem wir Merkmale aus dem Datensatz extrahieren, wobei die Merkmale die Ichimoku- und ADX-Signale sind, die in einen Vektor gezippt werden; wir wählen eine Aktion aus, da das Akteursnetzwerk diese mit dem beim Training hinzugefügten Explorationsrauschen ausgibt; wir verarbeiten dann die Umgebung, indem wir die Aktion anwenden, die Belohnung berechnen und unseren Index zum nächsten Zustand verschieben; als Nächstes speichern wir den Übergang, indem wir das oben abgebildete Tupel im Wiedergabepuffer speichern; schließlich lernen wir, indem wir in regelmäßigen Abständen einen Mini-Batch aus dem Wiedergabepuffer entnehmen und die Netze der Kritiker und schließlich auch das Akteursnetz aktualisieren, wenn auch mit einer verzögerten Frequenz.

Unsere Python-Implementierung dieses Trainings ist im folgenden Code-Schnipsel aus unserem Python-Quellcode zu sehen:

if __name__ == "__main__":
    # Example dummy data; replace with your real x_data, y_data arrays
    T = 5000
    state_dim = 6
    action_dim = 1

#----------------------------MT5 Connections-------------------------
    if not mt5.initialize(login = 5040189685, server = "MetaQuotes-Demo", password = "JmYp-u4v"):
        print("initialize() failed, error code =",mt5.last_error())
        quit()

    # set start and end dates for history data
    from datetime import timedelta, datetime
    #end_date = datetime.now()
    end_date = datetime(2024, 7, 1, 0)
    start_date = datetime(2023, 7, 1, 0)

    # print start and end dates
    print("data start date =",start_date)
    print("data end date =",end_date)

    # get rates
    eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H4, start_date, end_date)

    # create dataframe
    df = pd.DataFrame(eurusd_rates)

    df = Ichimoku(df)
    df = ADX_Wilder(df)

    index = 5
    scaler = 1    
    
    x_data, drops = GetFeatures(index, scaler, df)
    y_data = GetStates(df, drops)
    
    ##    min_rows = min(len(x_data), len(y_data))
    min_rows = min(x_data.shape[0], y_data.shape[0])
    x_data = x_data[:min_rows]
    y_data =  y_data[:min_rows]

    print(" x rows =",x_data.shape[0])
    print(" y rows =",y_data.shape[0])
    
    # Example usage (your style)
    state_dim = x_data.shape[1]           # Example state dimension
    action_dim = y_data.shape[1]          # Example action dimension
    agent = TD3Agent(state_dim, action_dim)  # alias to TD3Agent

    # Load the environment
    env = CustomDataEnv(x_data, y_data, txn_cost=TXN_COST)

    epochs = 10

    # Training loop
    for epoch in range(epochs):
        s_np = env.reset()
        for row in range(x_data.shape[0]):
            # Build torch state
            state = torch.tensor(s_np, dtype=torch.float32)
            # Policy action (with exploration noise)
            action = agent.act(state, noise_scale=0.1)
            next_state, reward, done, info = env.step(action)
            agent.replay_buffer.add(state, action, reward, next_state, done)

            # Periodic learning
            if row % BATCH_SIZE == 0:
                agent.learn(batch_size=BATCH_SIZE)

            # Advance
            s_np = next_state
            if done:
                env.render()
                s_np = env.reset()

        print(f"Epoch {epoch+1}/{epochs} done. Buffer size={agent.replay_buffer.size}")

    # Export actor ONNX post-training
    os.makedirs("exports", exist_ok=True)
    out_path = agent.export_actor_onnx(os.path.join("exports",str(index)+"_"+str(scaler)+"_actor.onnx"))
    print(f"Exported trained actor to: {out_path}")

Nach einer angemessenen Anzahl von Epochen, wir haben in unserem obigen Code 10 verwendet, sollte das TD3-Akteursnetzwerk lernen und eine stabile Zuordnung der Zustände zu Aktionen entwickeln. Zu diesem Zeitpunkt würden wir nur das Akteursnetzwerk exportieren. Dies ist die entscheidende Entscheidungskomponente der Umwelt. Die Netze der Kritiker, deren Aufgabe in erster Linie darin besteht, die Rückverfolgung des Akteursnetzes zu steuern, würden in Python verbleiben. Das Akteursnetzwerk kann dann in MQL5 als ONNX-Modell eingesetzt werden und liefert deterministischere Handelssignale. Diese Trennung ist sehr effektiv, da Python das aufwendige Training übernimmt, während MQL5 die Inferenz in Echtzeit mit wenig bis gar keinem Overhead ausführt.

In der Trainingsphase wird die Intelligenz des Systems eingebettet. Sobald dies geschehen ist, können wir zum nächsten Schritt übergehen, nämlich dem Export des Akteursnetzes in ein kompatibles ONNX-Format. ONNX ist die Brücke, die die Flexibilität von Python mit der Handelsumgebung von MQL5 verbindet. Auf diese Weise wird sichergestellt, dass der Agent lernt, übermäßiges Handeln zu vermeiden. Ein wichtiges zentrales Merkmal von Off-Policy-Algorithmen wie TD3 ist der Replay-Puffer. Jede Interaktion erzeugt ein einfaches Tupel:

F2


TD3 nach ONNX exportieren

Nachdem der TD3-Agent sein Training in Python absolviert hat, folgt nun der Export des Akteursnetzwerks für den Einsatz im MetaTrader. Der praktischste Weg, dies zu erreichen, ist das Open Neural Network Exchange-Format (ONNX). Dies ermöglicht ein rahmenunabhängiges Modellformat. ONNX ermöglicht es uns, mit PyTorch zu trainieren und dann über die ONNX-Laufzeitbindungen im MetaTrader effizient Inferenzen durchzuführen.

Warum exportieren wir nur das Netzwerk des Akteurs? Dies liegt daran, dass in TD3, und eigentlich in den meisten RL-Algorithmen, der Akteur für die Generierung der erforderlichen Aktionen für den aktuellen Zustand, der sich präsentiert, verantwortlich ist. Das Netze der Kritiker wird nur beim Training und nicht bei der Live-Inferenz benötigt. Indem wir nur den Akteur exportieren, stellen wir sicher, dass der „Laufzeit-Fußabdruck“ minimal ist, und wir vermeiden auch unnötige Komplexität beim Versuch des Trainings in MQL5. Unser Exportcode aus Python lautet wie folgt:

    # -------------------- ONNX Export --------------------
    def export_actor_onnx(self, path: str):
        self.actor.eval()
        dummy = torch.zeros(1, self.state_dim, dtype=torch.float32, device=DEVICE)
        torch.onnx.export(
            self.actor, dummy, path,
            export_params=True, opset_version=17, do_constant_folding=True,
            input_names=["state"], output_names=["action"], dynamic_axes=None
        )
        return path

Im obigen Code für unsere Exportfunktion ist die Eingabe mit „state“ und die Ausgabe mit „action“ gekennzeichnet, wobei die Form [1, action_dim] der Größe der Ausgabeschicht entspricht. Die Eingabe- und die Ausgabeschicht sind beide Tensoren vom Datentyp float32. Wir verwenden auch opset_version=17, um die Kompatibilität mit aktuellen ONNX-Laufzeiten zu gewährleisten, die die MQL5-Implementierung enthalten. Die Überprüfung des Exports vor dem Verschieben der Datei nach MQL5 ist immer wichtig, und dies erfordert Tests in Python. Neben der Überprüfung auf wohlgeformte Dateien können wir auch die Form der Eingabe- und Ausgabeschichten ablesen und überprüfen, eine wichtige Voraussetzung für die Initialisierung von ONNX-Ressourcen in MQL5.

Sobald die ONNX-Datei bestätigt und die trainierte TD3-Richtlinie in einen MQL5 Expert Advisor importiert ist, wird die Entscheidungsfähigkeit des Modells durch die Generierung von Handelssignalen in der MetaTrader-Umgebung konkretisiert. 



ONNX in MQL5

Nachdem das TD3-Akteursnetzwerk exportiert wurde, folgt nun der Import in den MetaTrader 5. Wie wir in früheren Artikeln gesehen haben, verfügt MQL5 über integrierte Unterstützung für die ONNX-Laufzeit, die es ermöglicht, in Python trainierte neuronale Netze während des Handels nativ auszuführen. Dadurch können RL-Agenten wie traditionelle Signalanbieter innerhalb eines Expert Advisors agieren. In MQL5 initialisieren wir ein ONNX als Ressource in der Funktion OnInit() der nutzerdefinierten Signalklasse wie folgt:

#resource "Python/0_1_actor.onnx" as uchar __81_0[]
#resource "Python/1_1_actor.onnx" as uchar __81_1[]
#resource "Python/5_1_actor.onnx" as uchar __81_5[]
#include <SRI\PipeLine.mqh>
#define __PATTERNS 3
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSignalRL_Ichimoku_ADXWilder::CSignalRL_Ichimoku_ADXWilder(void) : m_pattern_0(50),
   m_pattern_1(50),
   m_pattern_5(50)
//m_patterns_usage(255)
{
//--- initialization of protected data
   // 
   //-----omitted source
   //
//--- create model from static buffer
   m_handles[0] = OnnxCreateFromBuffer(__81_0, ONNX_DEFAULT);
   m_handles[1] = OnnxCreateFromBuffer(__81_1, ONNX_DEFAULT);
   m_handles[2] = OnnxCreateFromBuffer(__81_5, ONNX_DEFAULT);
   // 
   //-----omitted source
   //
}

Dadurch werden die drei exportierten Modelle unserer Signalmuster 0, 1 und 5 über Handles geladen, sodass sie für Inferenzaufrufe beim Handel bereit sind. Die TD3-Akteursnetzwerke erwarten für jedes der 3 Signalmuster, dass ihre Eingaben die gleiche Form haben, für die sie trainiert wurden. Dies ist [1, state-dim] und ist ein float32-Tensor. Um die Initialisierung abzuschließen, führen wir daher eine Validierungsprüfung in der Funktion ValidationSettings() durch. Dies wird wie folgt kodiert:

//+------------------------------------------------------------------+
//| Validation settings protected data.                              |
//+------------------------------------------------------------------+
bool CSignalRL_Ichimoku_ADXWilder::ValidationSettings(void)
{
//--- validation settings of additional filters
   if(!CExpertSignal::ValidationSettings())
      return(false);
//--- initial data checks
   const ulong _out_shape[] = {1, 1};
   for(int i = 0; i < __PATTERNS; i++)
   {  // Set input shapes
      int _in = 3;
      if(i == __PATTERNS - 1)
      {  _in = 7;
      }
      const ulong _in_shape[] = {1, _in};
      if(!OnnxSetInputShape(m_handles[i], ONNX_DEFAULT, _in_shape))
      {  Print("OnnxSetInputShape error ", GetLastError(), " for feature: ", i);
         return(false);
      }
      // Set output shapes
      if(!OnnxSetOutputShape(m_handles[i], 0, _out_shape))
      {  Print("OnnxSetOutputShape error ", GetLastError(), " for feature: ", i);
         return(false);
      }
   }
//--- ok
   return(true);
}

Wenn die Eingaben, die wir diesen ONNX-Modellen zur Verfügung stellen, nicht der Form entsprechen, die das Modell erwartet, schlägt die Initialisierung fehl. Auch die Form der Ausgabe muss in ähnlicher Weise überprüft werden. Sobald das Modell initialisiert ist und die Validierung ordnungsgemäß bestanden hat, ist die Durchführung der Inferenz ziemlich einfach. Dies wird in der unten aufgeführten Funktion RunModel() behandelt:

//+------------------------------------------------------------------+
//| Forward Feed Network, to Get Forecast State.                     |
//+------------------------------------------------------------------+
double CSignalRL_Ichimoku_ADXWilder::RunModel(int Index, ENUM_POSITION_TYPE T, vectorf &X)
{  vectorf _y(1);
   _y.Fill(0.0);
   //Print(" x: ", __FUNCTION__, X);
   ResetLastError();
   if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, X, _y))
   {  printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError());
      return(double(_y[0]));
   }
   //printf(__FUNCSIG__ + " pre y is: " + DoubleToString(_y[0], 5));
   if(T == POSITION_TYPE_BUY && _y[0] > 0.5f)
   {  _y[0] = 2.0f * (_y[0] - 0.5f);
   }
   else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f)
   {  _y[0] = 2.0f * (0.5f - _y[0]);
   }
   //printf(__FUNCSIG__ + " post y is: ", DoubleToString(_y[0], 5));
   return(double(_y[0]));
}

Die Ausgaben für solche RL-Modelle sind in hohem Maße anpassbar. Wenn das Modell zum Beispiel mehr als eine Aktion ausgibt, z. B. wenn die Positionsgröße und die prognostizierte Richtung die Ausgänge sind, dann ist der Ausgangsvektor ebenso mehrdimensional, in diesem Fall also 2. Die Ausgabe erfolgt in einer einzigen Dimension, wobei die Float-Ausgabe im Bereich [0,1] liegen soll. Das bedeutet, dass wir diesen Uni-Output in Handelsoperationen umsetzen müssen. Hierfür passen wir die folgende vereinfachte Interpretation an:

  • Wenn Action größer oder gleich 0,5f ist, eröffnen oder halten wir eine Kaufposition.
  • Wenn die Aktion weniger als 0,5f beträgt, eröffnen oder halten wir eine Verkaufsposition.

Beachten Sie, dass wir keine Neutralitätsschwelle zwischen Kauf und Verkauf haben, aber dies ist etwas, das von den Lesern durch die Verwendung eines gespiegelten Puffers zwischen dem Kauf und dem Verkauf erforscht werden könnte. Wenn dieser Puffer z. B. 0,2 beträgt, dann würde die Inaktivitätszone von -0,2f bis 0,2f reichen, und so weiter. Die Festlegung eines Schwellenwerts kann dazu beitragen, dass der Expert Advisor angesichts der Anfälligkeit für kleine Schwankungen um den Nullpunkt herum nicht zu viel handelt. Wenn der Expert Advisor mit einer nicht fixen, aber kontinuierlichen Positionsgröße optimiert wird, dann kann die Größe der Aktion auf eine Losgröße innerhalb der erlaubten Brokergrenzen skaliert werden. 

Die Vorverarbeitung von Daten gewinnt zunehmend an Bedeutung. Das Python-Modul von SCIKIT-LEARN hat dazu beigetragen, dies als einen sehr wichtigen Schritt zur Normalisierung/Standardisierung von Daten zu etablieren, bevor sie in ein Netzwerk/Modell eingespeist werden. Wann immer dies in Python während des Trainings geschieht, ist es wichtig, dass die exakte Skalierung auf die Eingabedaten angewendet wird, bevor die Inferenz durchgeführt wird. Wenn beim Training eine Min-Max-Skalierung oder eine Standard-Skalierung oder eine robuste Skalierung usw. verwendet wurde, müssen diese genauen Transformationen vor dem Aufruf der Funktion RunModel() angewendet werden. Jede Unstimmigkeit in der Skalierung führt zwangsläufig zu einem unvorhersehbaren Verhalten des Akteursnetzes.

Innerhalb der Funktion Direction(), die die wichtigen Funktionen LongCondition() und ShortCondition() aufruft, besteht unsere effektive Sequenz darin, einen Merkmalsvektor aus den letzten Bars zu erstellen, die Merkmale zu skalieren, um der Trainingstransformation zu entsprechen, die in unserem Fall der Standard-Scaler ist, diese Merkmale an das ONNX-Modell weiterzugeben, die Ausgabeaktion in eine Kauf-/Verkaufsentscheidung umzuwandeln, und schließlich den Handel unter Verwendung der Eröffnungs- und Schlussschwellen des nutzerdefinierten MQL5-Signals auszuführen.

Diese Sequenz macht das TD3-Akteursnetzwerk zum Entscheidungsgehirn des Expert Advisors. Die MQL5-Plattform ermöglicht die Zusammenstellung von Expert Advisors über den Assistenten, sodass Händler Handelsroboter aus modularen Codeblöcken entwerfen können. Anleitungen dazu finden neue Leser hier und hier. Wie wir hier in dieser Serie behandelt haben, enthalten diese Blöcke in erster Linie eine nutzerdefinierte Signalklasse, die in der Regel Indikatoren wie den RSI oder gleitende Durchschnitte verwendet und dieser Block wird mit Geldmanagement- und Trailing-Stop-Codeblöcken in Form von unabhängigen Klassen kombiniert. Unsere Integration von RL in dieses Ökosystem erfolgt über die Signalklasse in Form einer nutzerdefinierten Signalklasse, die vom MQL5-Assistenten erkannt wird.

Diese Implementierung für die langen und kurzen Bedingungen innerhalb dieser Klasse ist wie folgt:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalRL_Ichimoku_ADXWilder::LongCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
//--- if the model 0 is used
   if(((m_patterns_usage & 0x01) != 0))
   {  matrixf _raw = IsPattern_0();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         //Print(" buy x: ", __FUNCTION__, _x);
         double _y = RunModel(0, POSITION_TYPE_BUY, _x);
         if(_y > 0.0)
         {  result += m_pattern_0;
            results++;
         }
      }
   }
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0))
   {  matrixf _raw = IsPattern_1();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(1, POSITION_TYPE_BUY, _x);
         if(_y > 0.0)
         {  result += m_pattern_1;
            results++;
         }
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0))
   {  matrixf _raw = IsPattern_5();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(2, POSITION_TYPE_BUY, _x);
         if(_y > 0.0)
         {  result += m_pattern_5;
            results++;
         }
      }
   }
//--- return the result
//if(result > 0)printf(__FUNCSIG__+" result is: %i",result);
   if(results > 0 && result > 0)
   {  return(int(round(result / results)));
   }
   return(0);
}
//+------------------------------------------------------------------+
//| "Voting" that price will fall.                                   |
//+------------------------------------------------------------------+
int CSignalRL_Ichimoku_ADXWilder::ShortCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
//--- if the model 0 is used
   if(((m_patterns_usage & 0x01) != 0))
   {  matrixf _raw = IsPattern_0();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         //Print(" sell x: ", __FUNCTION__, _x);
         double _y = RunModel(0, POSITION_TYPE_SELL, _x);
         if(_y > 0.0)
         {  result += m_pattern_0;
            results++;
         }
      }
   }
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0))
   {  matrixf _raw = IsPattern_1();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(1, POSITION_TYPE_SELL, _x);
         if(_y > 0.0)
         {  result += m_pattern_1;
            results++;
         }
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0))
   {  matrixf _raw = IsPattern_5();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(2, POSITION_TYPE_SELL, _x);
         if(_y > 0.0)
         {  result += m_pattern_5;
            results++;
         }
      }
   }
//--- return the result
//if(result > 0)printf(__FUNCSIG__+" result is: %i",result);
   if(results > 0 && result > 0)
   {  return(int(round(result / results)));
   }
   return(0);
}



Ergebnisse der Tests

Es spielt keine Rolle, ob ein RL-Algorithmus im Training oder auf dem Papier vielversprechend ist, am Ende des Tages muss er eine sehr wichtige Hürde nehmen: den Vorwärtstest. Der Strategy Tester von MetaTrader bietet eine vernünftige Umgebung, um Live-Bedingungen zu simulieren, die den Expert Advisor bis zu einem gewissen Grad ungetesteten Daten aussetzt und gleichzeitig Ausführungsregeln, Spreads und Auftragsabwicklung berücksichtigt. Wie wir bereits in früheren Artikeln beschrieben haben, optimieren wir bei den Backtests die Parameter des Expert Advisors so, dass sie bei den Trainingsdaten/Kursen am besten funktionieren. Bei einem Vorwärtstest stellen wir uns einfach die Frage, ob dieselben Parameter bei unbekannten Kursdaten mit ähnlicher Leistung funktionieren.

Für diesen Artikel haben wir drei separate Tests durchgeführt, einen für jedes der drei Signalmuster 0, 1 und 5 aus dem Artikel Teil 74. Für jedes dieser Muster gab es ein eigenes Akteursnetzwerk, das in Python trainiert und dann von ONNX exportiert wurde. Alle drei wurden für das Devisensymbol EUR USD und den 4-Stunden-Zeitrahmen trainiert. Der Trainingszeitraum reichte von 2023.07.01 bis 2024.07.01, der anschließende Zeitraum des Vorwärtstests von 2024.07.01 bis 2025.07.01. Neben der Verwendung von Pipelines, die wir aus dem letzten Artikel übernommen haben, haben wir einige Änderungen am Eingabevektor für die Netzwerke vorgenommen. Wir verwenden kein binäres Format für die Eingabe in das Akteursnetzwerk. Erinnern Sie sich: Als wir in der Vergangenheit beispielsweise überlegten, das Signal durch Überwachtes Lernen zu ergänzen, um das Signal des Expert Advisors besser zu systematisieren, bestand der Eingangsvektor nur aus Nullen und Einsen. Das Vorhandensein einer Eins in einer der Dimensionen bedeutet, dass alle wichtigen Metriken für das ausgewählte Signalmuster mit dem Aufwärts- oder Abwärtssignal erfüllt wurden.

In diesem Artikel wollen wir die Möglichkeiten von Pipelines ausloten und uns wieder mit Fließkommazahlen oder Vektoren mit mehreren reellen Zahlen als Eingabe beschäftigen. Unsere Eingabevektoren folgen also der Größe der geprüften Indikatorwerte, und das bedeutet, dass wir keine Standardgröße für Eingabevektoren haben, wie die 2, die wir in früheren Artikeln hatten. Die Ergebnisse, die im Folgenden vorgestellt werden, sind weit entfernt von dem, was wir bei der letzten Verwendung von binären Eingangsvektoren erhalten haben. Lediglich das Signalmuster 5 deutet auf ein gewisses Potenzial, den Vorwärtstest erfolgreich zu bewältigen.

Muster-0:

r0r

c0r

Muster-1:

r1r

c1r

Muster-5:

r5r

c5r

Aus unseren obigen Berichten geht hervor, dass die Änderung des Eingabeformats von binären 0en und 1en zu einer kontinuierlichen Version unsere Leistung beeinträchtigt hat. Diese Änderungen wurden jedoch vorgenommen, um von unserer kürzlich eingeführten Datenvorverarbeitung zu profitieren, die zweifelsohne wichtig ist. Man kann argumentieren, dass unsere Wahl der Vorverarbeitungsimplementierung in diesem Artikel suboptimal sein könnte, daher werde ich in zukünftigen Artikeln prüfen, wie diese feiner abgestimmt werden kann.



Schlussfolgerung

Zusammenfassend haben wir hier gezeigt, dass TD3 in Python trainiert, über ONNX exportiert und als nutzerdefinierte Signalklasse in MQL5 integriert werden kann. Unser Ansatz hier ist jedoch etwas unorthodox, da wir das Bestärkende Lernen so wie Überwachtes Lernen verwenden, d.h. wir machen kein Trainieren bzw. Backpropation, wenn wir unsere Inferenz oder den Live-Handel durchführen. Auch wenn dies in der Simulation/Training in Python geschieht, verlangt/erwartet RL oft, dass dies auch im Live-Einsatz fortgesetzt wird.

Die Vorwärtstests, die wir mit den Signalmustern 0, 1 und 5 durchgeführt haben, haben gezeigt, dass der Übergang von binären Eingaben zu kontinuierlichen Merkmalsvektoren problematisch sein kann, da nur das Muster 5 mäßig ermutigende Ergebnisse lieferte. Auch wenn diese Ergebnisse uneinheitlich waren, verdeutlichen sie doch den iterativen Charakter der Forschung, der darin besteht, dass nachfolgende Tests zu Verfeinerungen führen, die uns näher an robuste Handelssysteme heranführen. In zukünftigen Artikeln werden wir uns mit der Optimierung dieser Vorverarbeitungspipelines, einer differenzierteren Merkmalsauswahl und wahrscheinlich aufwändigeren Vorwärtstest befassen, um Verbesserungen vorzunehmen.

NameBeschreibung
WZ-81.mq5Vom Assistenten erstellten Expert Advisor, dessen Header referenzierte Dateien auflistet
SignalWZ-81.mqhNutzerdefinierte Signalklassendatei, die in der vom Assistenten erstellten EA verwendet wird
0-1-actor.onnxMuster 0 exportiertes Netz
1-1-actor.onnxMuster 1 Netzwerk
5-1-actor.onnxMuster 5 Netzwerk
PipeLine.mqhKlassen für die verschiedenen Transformationsarten

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19627

Beigefügte Dateien |
WZ-81.mq5 (7.53 KB)
0_1_actor.onnx (519.96 KB)
1_1_actor.onnx (519.96 KB)
5_1_actor.onnx (523.96 KB)
PipeLine.mqh (15.01 KB)
SignalWZ_81.mqh (16.99 KB)
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 81):  Verwendung von Ichimoku-Mustern und des ADX-Wilder mit Beta-VAE-Inferenzlernen MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 81): Verwendung von Ichimoku-Mustern und des ADX-Wilder mit Beta-VAE-Inferenzlernen
Dieser Beitrag schließt an Teil 80 an, in dem wir die Paarung von Ichimoku und ADX im Rahmen eines Reinforcement Learning untersucht haben. Wir wenden uns nun dem Inferenzlernen zu. Ichimoku und ADX ergänzen sich, wie bereits erwähnt, jedoch werden wir die Schlussfolgerungen des letzten Artikels in Bezug auf die Verwendung von Pipelines wieder aufgreifen. Für unser Inferenzlernen verwenden wir den Beta-Algorithmus eines Variational Auto Encoders. Wir bleiben auch bei der Implementierung einer nutzerdefinierten Signalklasse für die Integration mit dem MQL5-Assistenten.
Aufbau eines Handelssystems (Teil 4): Wie zufällige Ausstiege die Handelserwartung beeinflussen Aufbau eines Handelssystems (Teil 4): Wie zufällige Ausstiege die Handelserwartung beeinflussen
Viele Händler haben diese Erfahrung gemacht, sie halten sich oft an ihre Einstiegskriterien, aber sie haben Probleme mit dem Handelsmanagement. Selbst bei den richtigen Setups können emotionale Entscheidungen – wie z. B. panische Ausstiege vor Erreichen des Take-Profit- oder Stop-Loss-Niveaus – zu einer fallenden Kapitalkurve führen. Wie können Händler dieses Problem lösen und ihre Ergebnisse verbessern? Dieser Artikel geht auf diese Fragen ein, indem er zufällige Gewinnraten untersucht und anhand von Monte-Carlo-Simulationen aufzeigt, wie Händler ihre Strategien verfeinern können, indem sie bei angemessenen Niveaus Gewinne mitnehmen, bevor das ursprüngliche Ziel erreicht ist.
Aufbau eines Handelssystems (Teil 5): Verwaltung von Gewinnen durch strukturierte Handelsausstiege Aufbau eines Handelssystems (Teil 5): Verwaltung von Gewinnen durch strukturierte Handelsausstiege
Für viele Händler ist es ein vertrauter Schmerzpunkt: zu sehen, wie ein Handel bis auf einen Hauch an Ihr Gewinnziel herankommt, nur um dann umzukehren und ihren Stop-Loss zu treffen. Oder noch schlimmer: Sie sehen, dass ein Trailing-Stop Sie an der Gewinnschwelle stoppt, bevor der Markt auf Ihr ursprüngliches Ziel zusteuert. Dieser Artikel befasst sich mit dem Einsatz mehrerer Einstiege zu unterschiedlichen Rendite-Risiko-Verhältnissen, um systematisch Gewinne zu sichern und das Gesamtrisiko zu reduzieren.
Entwicklung des Price Action Analysis Toolkit (Teil 42): Interaktive Chart-Prüfung mit Schaltflächenlogik und statistischen Ebenen Entwicklung des Price Action Analysis Toolkit (Teil 42): Interaktive Chart-Prüfung mit Schaltflächenlogik und statistischen Ebenen
In einer Welt, in der es auf Geschwindigkeit und Präzision ankommt, müssen die Analysetools so intelligent sein wie die Märkte, auf denen wir handeln. In diesem Artikel wird ein EA vorgestellt, der auf der Logik von Schaltflächen basiert – ein interaktives System, das rohe Kursdaten sofort in aussagekräftige statistische Werte umwandelt. Mit einem einzigen Klick werden Mittelwert, Abweichung, Perzentile und vieles mehr berechnet und angezeigt, sodass fortschrittliche Analysen zu klaren Signalen auf dem Chart werden. Es hebt die Zonen hervor, in denen der Preis am wahrscheinlichsten abprallen, zurückgehen oder durchbrechen wird, was die Analyse sowohl schneller als auch praktischer macht.