English 日本語
preview
MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 70):  Verwendung der Muster von SAR und RVI mit einem Exponential-Kernel-Netzwerk

MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 70): Verwendung der Muster von SAR und RVI mit einem Exponential-Kernel-Netzwerk

MetaTrader 5Integration |
143 0
Stephen Njuki
Stephen Njuki

Einführung

Im letzten Artikel haben wir dieses komplementäre Paar aus dem Parabolic SAR Indikator (SAR) und dem Relative Vigour Index Oszillator (RVI) vorgestellt. Von den 10 Mustern, Pattern-n, die wir getestet haben, konnten drei keine sauberen Vorwärtstest durchführen, nämlich die Muster mit den Indizes 1, 2 und 6. Unsere Indizierung dieser Muster von 0 bis 9 ermöglicht es uns, auf einfache Weise den passenden Wert zu berechnen, der ihre ausschließliche Verwendung durch den Expert Advisor ermöglicht. Wenn zum Beispiel ein Muster mit dem Index 1 versehen ist, müssen wir den Parameter „PatternsUsed“ auf 2 hoch 1 setzen, was 2 ergibt. 

Wenn der Index 2 ist, dann ist das 2 hoch 2, was 4 ergibt, und so weiter. Der maximale Wert, der diesem Parameter sinnvollerweise zugewiesen werden kann, ist 1023, da wir nur 10 Parameter haben. Jede Zahl zwischen 0 und 1023, die kein reiner Exponent von 2 ist, würde eine Kombination dieser Muster darstellen, und der Leser könnte die Einrichtung des Expert Advisors zur Verwendung mehrerer Muster untersuchen. Auf der Grundlage unserer Argumente und Testergebnisse, die wir in früheren Artikeln dargelegt haben, haben wir uns jedoch entschieden, diesen Weg in dieser Serie vorerst nicht weiter zu verfolgen. 

Wie in einem der letzten Artikel versprochen, werden wir nun versuchen, die Signale der drei Pattern-1, 2 und 6, die im letzten Artikel nicht in der Lage waren, saubere Vorwärtsbewegungen durchzuführen, mit überwachtem Lernen wiederzubeleben. Bei der Anwendung des maschinellen Lernens auf diese MQL5-Indikatorsignale greifen wir auf Python zurück, um bei der Codierung und dem Training eines Netzwerkmodells zu helfen. Dies ist auf die Effizienz zurückzuführen, die er auch ohne einen Grafikprozessor bieten kann. Bei der Verwendung von Python stützen wir uns auf das Python-Modul von MetaTrader, das es uns ermöglicht, uns mit dem Server eines MetaTrader-Brokers zu verbinden, sobald wir einen Nutzernamen und ein Passwort angegeben haben. 

Sobald eine Verbindung über das MetaTrader 5 Python-Modul hergestellt ist, haben wir Zugriff auf die Kursdaten eines Brokers. Python verfügt auch über Bibliotheken für technische Indikatoren, die jedoch installiert werden müssen und oft in etwas esoterischen Formaten vorliegen. Glücklicherweise ist es relativ einfach, eine eigene Lösung auf der Grundlage der ersten Prinzipien zu entwickeln. Wir beginnen daher mit der Implementierung unserer beiden Indikatoren, des SAR und des RVI, in Python.


Parabolische SAR-Funktion (SAR)

Der SAR ist ein Trendfolge-Indikator, der verwendet wird, um mögliche Umkehr-Fraktalpunkte in der Kursrichtung zu erkennen. Er nutzt die Platzierung von Punkten, um einen vorherrschenden Trend zu markieren, wobei die Punkte auf der Seite potenzieller Stop-Loss-Punkte liegen. Bei einem Aufwärtstrend liegen die Punkte also unter den Tiefstständen, während sie bei einem Abwärtstrend über den Höchstständen liegen. Wir codieren die Funktion zur Berechnung der SAR wie folgt:

def SAR(df: pd.DataFrame, af: float = 0.02, af_max: float = 0.2) -> pd.DataFrame:
    """
    Calculate Parabolic SAR indicator and append it as 'SAR' column to the input DataFrame.
    
    Args:
        df (pd.DataFrame): DataFrame with 'high', 'low', 'close' columns
        af (float): Acceleration factor, default 0.02
        af_max (float): Maximum acceleration factor, default 0.2
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'SAR' column
    """
    if not all(col in df.columns for col in ['high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'high', 'low', 'close' columns")
    if af <= 0 or af_max <= 0 or af > af_max:
        raise ValueError("Invalid acceleration factors")
    if df.empty:
        raise ValueError("DataFrame is empty")

    result_df = df.copy()
    result_df['SAR'] = 0.0
    
    sar = df['close'].iloc[0]
    ep = df['high'].iloc[0]
    af_current = af
    trend = 1 if len(df) > 1 and df['close'].iloc[1] > df['close'].iloc[0] else -1
    
    for i in range(1, len(df)):
        prev_sar = sar
        high, low = df['high'].iloc[i], df['low'].iloc[i]
        
        if trend > 0:
            sar = prev_sar + af_current * (ep - prev_sar)
            if low < sar:
                trend = -1
                sar = ep
                ep = low
                af_current = af
            else:
                if high > ep:
                    ep = high
                    af_current = min(af_current + af, af_max)
        else:
            sar = prev_sar + af_current * (ep - prev_sar)
            if high > sar:
                trend = 1
                sar = ep
                ep = high
                af_current = af
            else:
                if low < ep:
                    ep = low
                    af_current = min(af_current + af, af_max)
        
        result_df.loc[i, 'SAR'] = sar
    
    return result_df

Unsere oben implementierte Funktion nimmt einen Pandas-Datenrahmen mit den Spalten Hoch-, Tief- und Schlusskurs sowie zwei Float-Parameter für den Beschleunigungsfaktor. Die Ausgabe fügt schließlich die berechneten Werte als Spalte an den Eingabedatenrahmen als neue Spalte mit der Bezeichnung „SAR“ an. Seine allgemeine Logik ist darauf ausgerichtet, die Trendrichtung zu verfolgen und den SAR auf der Grundlage des Extrempunkts und des Beschleunigungsfaktors zu aktualisieren.

Wenn wir uns die Details des Codes ansehen, beginnen wir mit der Erstellung einer Kopie des Eingabedatenrahmens, um eine Änderung des Originals zu vermeiden. Dies extrahiert die oberen und unteren Datenspalten als NumPy-Arrays für effiziente Berechnungen. Anschließend wird das Array „sar“ mit Nullen initialisiert, um „sar“-Werte zu speichern. Dies ist wichtig, weil dadurch die Datenintegrität gewährleistet und die Struktur für SAR-Berechnungen vorbereitet wird. Die Verwendung von .copy() verhindert unbeabsichtigte Nebeneffekte. NumPy-Arrays neigen auch dazu, bei iterativen Berechnungen eine bessere Leistung im Vergleich zu Pandas-Serien zu erzielen.

Danach setzen wir den Starttrend als Aufwärtstrend mit Trend = 1. Der Extrempunkt wird auch dem Anfangshoch zugeordnet, und der Anfangs-SAR erhält ebenfalls den Anfangstiefstkurs. Der Beschleunigungsfaktor beginnt mit dem Eingangswert „af-start“. Dies ist von entscheidender Bedeutung, weil damit ein Ausgangspunkt für die SAR-Berechnungen festgelegt wird, indem ein anfänglicher Aufwärtstrend angenommen wird. Die Wahl des Low[0]-Wertes für den Start-SAR steht im Einklang mit der Rolle des SAR als Stop-Loss für einen Aufwärtstrend, da er durch die Gewichtung des „af-start“-Parameters, der seine Subtraktion vom Low an die Marktvolatilität anpasst, unter dem Low-Preis liegt.

Anschließend aktualisieren wir die SAR-Formel. Die Aktualisierungen beziehen sich auf den aktuellen Zeitraum, indem er näher an den durch den Beschleunigungsfaktor skalierten Extrempunkt herangerückt wird. Dies ist wichtig, da es der Kern der SAR-Formel ist. Ein parabolischer Ausgleich des Preises bei der Entwicklung eines Trends. Bei der Umsetzung ist darauf zu achten, dass „af“ klein ist, um nicht zu aggressiv zu werden, wenn sich ein Trend abzeichnet. Außerdem ist es wichtig, die numerische Stabilität auf volatilen Märkten zu überwachen.

Nun können wir die Logik der Trendumkehr einstellen. Befindet sich der SAR in einem Aufwärtstrend und übersteigt das aktuelle Tief, wird eine Umkehr zum Abwärtstrend vollzogen. Der SAR wird auf den vorherigen Extrempunkt zurückgesetzt, und der neue Extrempunkt wird auf den aktuellen Tiefstand gesetzt. Der Beschleunigungsfaktor wird ebenfalls auf seinen Standardwert zurückgesetzt. 

Dies ist wichtig, da die Erkennung von Trendumkehrungen eine wichtige Funktion des SAR ist, um Einstiegs- und Ausstiegspunkte zu signalisieren. Diese Bedingung gewährleistet, dass der SAR in einem Aufwärtstrend unter dem Kurs bleibt. Die Prüfung auf Umkehrempfindlichkeit kann durch Anpassung des Parameters „af-increment“ erfolgen.

Wenn wir uns in einem anhaltenden Aufwärtstrend befinden, müssen wir unsere SAR-Pufferwerte weiterhin aktualisieren und vor allem das „af-increment“ erhöhen. Der SAR bleibt auf dem Minimum des aktuellen Wertes bzw. der letzten beiden Tiefststände begrenzt, um zu verhindern, dass er in die Preisspanne eingreift. Sobald der Höchstwert den Extrempunkt übersteigt, wird der Extrempunkt aktualisiert, während der Beschleunigungsfaktor ebenfalls eine Erhöhung erhält, sofern er die durch „af-max“ festgelegte Obergrenze nicht überschreitet. Dieser Wartungsschritt ist wichtig, da er sicherstellt, dass der SAR ein gültiges Stop-Loss-Niveau bleibt und sich nur im Einklang mit dem Trend beschleunigt. Die Verwendung von zwei früheren Tiefstständen bringt eine gewisse Robustheit mit sich, kann aber bei kleineren Zeiträumen eine Anpassung erfordern.

Die Logik bei einem Abwärtstrend des SAR wird ebenfalls als Nächstes festgelegt. In vielerlei Hinsicht spiegelt dies die gerade erwähnte Logik bei einem Aufwärtstrends wider. Zur Erinnerung: Fällt der SAR unter das aktuelle Hoch, kehrt er in einen Aufwärtstrend zurück. Kommt es nicht zu einer Umkehr, bleibt der SAR auf dem Maximum des aktuellen SAR und der vorherigen Höchststände gedeckelt. Sowohl der Extrempunkt als auch der Beschleunigungsfaktor erhalten eine Aktualisierung. Dieser Schritt vervollständigt die Fähigkeit des SAR, sowohl Aufwärts- als auch Abwärtstrends symmetrisch zu verfolgen. Es ist immer wichtig, die Symmetrie der Aufwärts- und Abwärtslogik sicherzustellen, um jegliche Verzerrung zu vermeiden. Die Validierung kann mit historischen Daten durchgeführt werden, um die Genauigkeit der Umkehr zu bestätigen.

Nachdem wir uns mit der SAR-Logik befasst haben, können wir mit der Zuweisung der Ausgänge fortfahren. Im Wesentlichen werden dabei die berechneten SAR-Werte in eine neue Spalte „SAR“ im Datenrahmen eingegeben und dann zurückgegeben. Dies ist wichtig, da der Indikator in den Datenrahmen integriert wird, um weitere Analysen oder Visualisierungen zu ermöglichen. Bei der Verwendung kann es sinnvoll sein, zu überprüfen, ob die SAR-Spalte mit den Preisdaten übereinstimmt. Dies kann mit Hilfe von Plotbibliotheken wie Matplotlib geschehen, um die SAR-Punkte zu visualisieren.

Zusammenfassend lässt sich sagen, dass der SAR am besten für Trendmärkte geeignet ist, wo er bei der Signalisierung von Stop-Loss-Niveaus und der Identifizierung von Umkehrpunkten helfen kann. In seitwärts tendierenden oder unruhigen Märkten würde er nicht gut funktionieren. Die Parameterabstimmung ist zwar für den SAR nicht so kritisch, kann aber für einige Anlagen wichtig sein, da der Start- und der Inkrement-Beschleunigungsfaktor gegenüber den Komfortwerten von 0,02 und 0,2 angepasst werden müssen. Eine Validierung durch Rückvergleiche mit historischen Maklerdaten kann sehr hilfreich sein. Die wichtigsten Einschränkungen des SAR sind seine Tendenz, bei sich schnell bewegenden Märkten hinterherzuhinken, was zu vielen Fehlsignalen führen kann.


Relative Vigour Index (RVI) Funktion

Das Hauptziel des RVI besteht darin, die Stärke eines Trends zu verfolgen, was wir als Synonym für Momentum verstehen. Dazu wird die Position des Schlusskurses im Verhältnis zur Handelsspanne verglichen und mit gleitenden Durchschnitten geglättet. Es handelt sich um einen Oszillator, der dazu dienen kann, den Trend zu bestätigen, indem er das Momentum überprüft oder Divergenzen aufspürt. Wir implementieren es in Python wie folgt:

def RVI(df: pd.DataFrame, period: int, signal_period: int) -> pd.DataFrame:
    """
    Calculate Relative Vigor Index (RVI) with signal line and append as 'RVI' and 'RVI_Signal' columns.
    
    Args:
        df (pd.DataFrame): DataFrame with 'open', 'high', 'low', 'close' columns
        period (int): Lookback period for RVI calculation
        signal_period (int): Lookback period for signal line calculation
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'RVI' and 'RVI_Signal' columns
    """
    # Input validation
    if not all(col in df.columns for col in ['open', 'high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'open', 'high', 'low', 'close' columns")
    if period < 1 or signal_period < 1:
        raise ValueError("Period and signal period must be positive")
        
    # Create a copy to avoid modifying the input DataFrame
    result_df = df.copy()
    
    # Calculate price change and range
    close_open = df['close'] - df['open']
    high_low = df['high'] - df['low']
    
    # Calculate SMA for numerator and denominator
    num = close_open.rolling(window=period).mean()
    denom = high_low.rolling(window=period).mean()
    
    # Calculate RVI
    result_df['RVI'] = num / denom * 100
    
    # Calculate signal line (SMA of RVI)
    result_df['RVI_Signal'] = result_df['RVI'].rolling(window=signal_period).mean()
    
    return result_df

Aus unserem obigen Code geht im Allgemeinen hervor, dass wir einen Pandas-Datenrahmen mit den Datenspalten „high“, „low“ und „close“ verwenden. Darüber hinaus enthalten die Eingänge „period“ für den gleitenden SMA-Durchschnitt des Signalpuffers. Die Ausgabe fügt den RVI, der zusammen mit einem RVI-Signal geglättet wurde, an die Spalten des Datenrahmens an. Im Grunde berechnet die Logik den RVI als close - open/high - low. Diese wird dann mit einem 4-Perioden-SMA geglättet, und wir erzeugen eine Signallinie mit einer nutzerdefinierten SMA-Periode.

Wenn wir dies nun Zeile für Zeile betrachten, erstellen wir in unserer Funktion zunächst eine Kopie des Eingabedatenrahmens. Auf diese Weise bleiben die Originaldaten erhalten. Dies ist wichtig, da so unbeabsichtigte Änderungen am Eingabedatenrahmen verhindert werden können. Die Verwendung der Kopierfunktion hilft uns dabei, dies zu erreichen und unbeabsichtigte „Nebeneffekte“ zu vermeiden.

Als Nächstes berechnen wir den RVI. Bei dieser Berechnung des rohen RVI wird dieser als Verhältnis zwischen der Preisänderung und der Handelsspanne festgelegt. Er ist wichtig, da er die Stärke der Kursbewegung im Verhältnis zur Tagesspanne erfasst, und dies bildet die Grundlage für den RVI. Es ist darauf zu achten, dass „high“ und „low“ nicht gleich sind, um Nulldurchgänge zu vermeiden. Dieses Problem wird jedoch im nächsten Schritt angegangen. Im Moment geht die Formel von einem Intraday-Momentum aus.

Um Fehler durch eine Division durch Null zu vermeiden, ersetzen wir unendliche Werte, die bei einer möglichen Null-Division entstehen, wenn der Hochwert gleich dem Tiefwert ist, durch NaN. Jedes Nan wird dann durch Null ersetzt. Dies ist wichtig, um die numerische Stabilität zu gewährleisten und Fehler bei späteren Berechnungen zu vermeiden. Es handelt sich hierbei um eine vereinfachte Lösung, da der Leser alternative Vorgehensweisen für den Fall in Betracht ziehen kann, dass die Nullwerte den RVI-Ausgangspuffer zu sehr verzerren.

Damit können wir den RVI glätten und auch die Signallinie definieren. Die Glättung wendet eine 4-Perioden-SMA auf den rohen RVI an, um die Anfälligkeit für Rauschen zu verringern und die ungeraden Nullwerte auszugleichen, die, wie oben erwähnt, vorkommen können. Dies hilft bei der Erstellung des Indikators RVI-Linie. Sobald dies festgelegt ist, wenden wir auch eine nutzerdefinierte Periodenlänge für den SMA an, um diesen bereits geglätteten RVI extra zu glätten, um die Signallinie zu erzeugen. Dieser gesamte Schritt ist von entscheidender Bedeutung, da die Glättung den RVI besser interpretierbar macht. Die Signallinie hilft uns ebenfalls, die Übergänge zwischen den einzelnen Signalen genauer zu erkennen. Bei der Umsetzung dieser Methode wird häufig die feste 4-Perioden-SMA zur Glättung des rohen RVI verwendet, aber es besteht immer die Möglichkeit, die SMA-Signalperiode anzupassen, wenn eine Feinabstimmung erforderlich ist. In Frage kommen Periodenlängen zwischen 6 und 14.

Anschließend wird die Ausgabe zugewiesen. Dem geglätteten RVI und dem Signalleitungs-RVI werden jeweils Datenspalten im Ausgabedatenrahmen zugewiesen. Auf dem Papier bedeutet dies, dass sowohl der RVI als auch sein Signalpuffer an weiteren Analysen oder Visualisierungen teilnehmen können. Die Aufzeichnung beider Puffer kann beispielsweise helfen, Überschneidungen zu erkennen. 

Zusammenfassend lässt sich sagen, dass der RVI zur Bestätigung der Trendstärke eingesetzt wird, indem er als Momentum-Proxy dient. Er kann auch frühzeitig Divergenzen aufzeigen, etwa wenn der Kurs neue Höchststände erreicht, der RVI aber nicht. Auch Überkreuzungen mit seiner Signallinie helfen, die steigenden oder fallenden Aussichten zu untermauern. Bei der Parametereinstellung für den RVI geht es in erster Linie um die Anpassung von „period“ für den SMA, der bei der Glättung des RVI für die Signalleitung verwendet wird. Kürzere Periodenlängen im Bereich von 6 können für schnellere/häufige Signale verwendet werden, während Periodenlängen, die etwa 14 betragen, für sanftere Signale dienen. Die Validierung durch das Testen von RVI-Kreuzungen und -Divergenzen anhand historischer Daten ist wichtig, um die Zuverlässigkeit der Signale zu ermitteln. Die wichtigsten Einschränkungen des RVI sind seine Fähigkeit, aufgrund der doppelten Glättung, die es verwendet, hinterherzuhinken. Außerdem kann es schwierig sein, auf Märkten mit geringer Volatilität aussagekräftige Signale zu erzeugen. 


SAR RVI Komplementarität

Wie bei den meisten Indikatoren, die wir für diese Reihe in Betracht gezogen haben, wurde dieses Indikatorpaar gewählt, weil jeder Indikator den anderen ergänzt. Der SAR ist ein Trendfolgeindikator, der auf einem Preisdiagramm dargestellt wird. Der RVI ist jedoch ein Oszillator, der in einem vom Kursdiagramm getrennten Fenster dargestellt wird und in erster Linie zur Messung des Momentums dient. Der SAR ist besser geeignet, um Stop-Loss zu setzen, während der RVI besser geeignet ist, Trends zu bestätigen oder Divergenzen zu erkennen.

Beide werden in Python implementiert, indem sie auf Pandas für die Datenverarbeitung zurückgreifen. Die Rolle von NumPy besteht in der Beschleunigung von Berechnungen. Die Logik des SAR, die iterativ ist, ist aufgrund von Trendumkehrungen tendenziell komplexer. Die vektorisierten Operationen von RVI sind dagegen einfacher, aber anfällig für eine Divisionen durch Null. Die Leistung dieses Indikator-Duos wird daher durch die Verwendung von NumPy-Arrays für SAR angesichts der Verwendung von iterativen Berechnungen und durch die Datenrahmen von Pandas bei der Handhabung des vektorisierten RVI gesteigert.

Weitere Maßnahmen, die der Leser für diese Paarung implementieren kann, sind Backtests in Python mit Bibliotheken wie zipline; Visualisierung, insbesondere für manuelle Trader, der SAR-Punktdiagramme sowie des RVI mit seiner Signallinie; Fehlerbehandlung durch Ergänzung des obigen Codes mit Eingabevalidierung, z. B. Überprüfung, ob die erforderlichen Datenspalten im Pandas-Datenrahmen vorhanden sind oder ob der Indikatorzeitraum gültig ist; und schließlich können Erweiterungen für jeden Indikator hinzugefügt werden. So könnte der RVI beispielsweise durch die Erkennung von Divergenzen ergänzt werden, oder es könnten alternative Glättungsmethoden eingesetzt werden.


Ausgewählte Signalmuster

Merkmal-1

Die meisten Muster im letzten Artikel waren in der Lage, eine Form des Vorwärtstest zu bieten, die Muster mit den Indizes 1, 2 und 6 jedoch nicht. In der Vergangenheit haben wir uns mit Pattern-n befasst, die durch maschinelles Lernen weiter geschärft werden können. In diesem und wahrscheinlich in vielen weiteren Artikeln werden wir jedoch mit Hilfe des maschinellen Lernens versuchen, das Schicksal von Pattern-n umzukehren, die nach den ersten Tests nicht in der Lage sind, einen Vorwärtstest zu bestehen.

In Python muss, ähnlich wie in MQL5, jedem dieser Muster eine Funktion zugewiesen werden, die das Signal entweder wahr oder falsch ausgibt. True, wenn das Signal eines Musters vorhanden ist, und false, wenn nicht. Diese wahren oder falschen Ausgaben werden als binäre Signale von 0 und 1 dargestellt, da sie Handelsstrategien von zwei Indikatoren und Preisdaten zusammenbringen. Jede Funktion erstellt ein Array für die Merkmale mit zwei Spalten. In der ersten Spalte, Spalte-0, werden Aufwärtssignale registriert. Die zweite Spalte, Spalte-1, protokolliert dann die Abwärtstrends. Für jede Spalte wird 0 eingetragen, wenn kein Signal vorhanden ist, und eine 1, wenn ein Signal vorhanden ist.

Wie bereits im letzten Artikel vorgestellt, erkennt Merkmal-1 Trends, wenn der Kurs über/unter dem SAR bleibt und gleichzeitig der RVI steigt/fällt. Er stützt sich auf eine anhaltende SAR-Kurs-Symbiose sowie eine RVI-Dynamik. Wir implementieren diese Funktion in Python wie folgt:

def feature_1(sar_df, rvi_df, price_df):
    """
    
    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) &
                     (price_df['low'] > sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI'].shift(1))).astype(int)
    
    feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) &
                     (price_df['high'] < sar_df['SAR']) &
                     (rvi_df['RVI'] < rvi_df['RVI'].shift(1))).astype(int)
    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Das erste, was wir in unserem obigen Code tun, ist, ein mit Nullen gefülltes Array zu initialisieren, das die Form [Länge der Eingabedatenrahmen, 2] hat. Die zwei zugewiesenen „Spalten“ am Ende sind dazu gedacht, Auf- bzw. Abwärtssignale zu erfassen. Dieser Schritt ist wichtig, da er eine saubere Weste für die Speicherung unserer binären Signale gewährleistet und verhindert, dass nicht initialisierte Werte vorhanden sind. Die Verwendung von np.zeros dient der Effizienz und verhindert unerwartete Ergebnisse bei den numerischen Operationen, da NaN-Werte automatisch auch zum Füllen eines leeren Arrays verwendet werden können.

Nachdem die Initialisierung abgeschlossen ist, legen wir den Wert für das Aufwärtssignal fest. In Spalte 0 vergeben wir eine 1, wenn das vorherige Tief über dem aktuellen SAR in einem anhaltenden Aufwärtstrend liegt und das aktuelle Tief ebenfalls über dem SAR liegt, wobei der aktuelle RVI über dem vorherigen Wert liegt, was auf ein steigendes Momentum hindeutet. Es ist wichtig, ALLE diese Signale zu nutzen, um einen starken Aufwärtstrend zu ergreifen, der sowohl durch den SAR für den Trend als auch durch den RVI für das Momentum bestätigt wird. Dabei ist darauf zu achten, dass „shift(1)“ die Daten korrekt ausrichtet. Fehlende Daten können zu erheblichen Fehlern führen. Die Verwendung von „astype(int)“ hilft bei der Umwandlung von booleschen Werten in binäre Werte, 0s und 1s.

Sobald die Aufwärts-Spalte gesetzt ist, gehen wir dazu über, die Abwärts-Spalte zu setzen. Die Anforderungen eines Abwärtstrends spiegeln die eines Aufwärtstrends wider, sodass es unwahrscheinlich ist, einen Merkmalsvektor mit Einsen in den Auf- und Abwärts-Spalten zu erhalten. Außerdem ist die Größe unseres Merkmalsvektors auf 2 beschränkt, um nur „vollständige“ Aufwärtstrends oder „vollständige“ Abwärtstrends zu erfassen. Wir haben über Vektoren gesprochen und sie auch tatsächlich implementiert, die viel länger sind und die einzelnen Indikatorsignale bei jedem Index verarbeiten. Tests mit diesen Bedingungen lieferten keine guten Vorwärtstest, ohne sehr begrenztes Testfenster, und auf dieser Basis bleiben wir bei der 2-Größen-Option von Auf- oder Abwärts.

In der zweiten Spalte setzen wir also ein Abwärtssignal, wenn das vorherige Hoch unter dem vorherigen SAR liegt und das aktuelle Hoch ebenfalls unter dem aktuellen SAR. Dies ist der Fall, wenn der aktuelle RVI niedriger ist als der vorherige RVI, ein Zeichen für eine nachlassende Dynamik. Alle diese Muster vereinen sich und definieren dank der Komplementarität von SAR und RVI einen starken Abwärtstrend. Ähnlich wie bei der obigen Aufwärtslogik müssen wir die Datenausrichtung überprüfen und auch NaN-Werte von „shift“ behandeln.

Nach der Definition der Auf- und Abwärtssignale nehmen wir Anpassungen an unserem Output-Array vor, indem wir die Shift-Vergleiche berücksichtigen. Dazu weisen wir den ersten beiden Zeilen Nullen zu, um ungültige Signale zu vermeiden. Sie ist auch wichtig, um Störsignale durch unvollständige Daten am Anfang zu vermeiden. In der Regel ist es immer gut, die ersten Zeilen zu annullieren, wenn verzögerte Daten verwendet werden, um die Robustheit zu erhalten. Die Testergebnisse, die sich auf das Training und den Vorwärtsgang erstrecken, geben uns folgenden Bericht:

r1

c1

Es scheint, dass wir mit der Einführung des überwachten Lernens bessere Ergebnisse erzielen. Nachfolgend sehen Sie das Diagramm, das wir für Pattern-1 ohne überwachtes Lernen erhalten haben:

c1_alt

Merkmal-2

Das dritte Muster, das wir getestet haben, konnte ebenfalls den Vorwärtstest nicht bestehen. Wie bereits im letzten Artikel erläutert, erkennt dieses Muster potenzielle Umkehrungen, wenn der Kurs den SAR kreuzt und der RVI eine gewisse Dynamik aufweist. Er berücksichtigt auch die Richtung der Preisbewegung, indem er den Schlusskurs als zusätzliche Richtung verwendet. Wir implementieren dies in Python wie folgt:

def feature_2(sar_df, rvi_df, price_df):
    """

    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['high'].shift(1) <= sar_df['SAR'].shift(1)) &
                     (price_df['low'] >= sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI'].shift(1)) &
                     (price_df['close'].shift(1) > price_df['close'])).astype(int)
    
    feature[:, 1] = ((price_df['low'].shift(1) >= sar_df['SAR'].shift(1)) &
                     (price_df['high'] <= sar_df['SAR']) &
                     (rvi_df['RVI'].shift(1) > rvi_df['RVI']) &
                     (price_df['close'] > price_df['close'].shift(1))).astype(int)
    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Wir beginnen auch hier mit der Initialisierung eines mit Nullen gefüllten Arrays, das so geformt ist, dass es sowohl Auf- als auch Abwärtssignale empfängt. Der Grund dafür ist, wie oben erwähnt, dass wir keine NaNs am Anfang haben und eine vorhersehbare Ausgabe von der Funktion erhalten können. Eine konsistente Initialisierung ist für die nachgelagerte Verarbeitung unerlässlich. 

Anschließend definieren wir die Bedingungen für ein Aufwärtssignal. Alle diese Bedingungen müssen erfüllt sein, damit wir eine 1 registrieren können, andernfalls wird eine Null zugewiesen. Deshalb ist die Arbeit mit Null gefüllten Arrays ein sicherer Weg, um falsche Einträge zu vermeiden. Wenn also das vorherige Hoch am oder unter dem vorherigen SAR liegt und das aktuelle Tief am oder über dem aktuellen SAR liegt, was bedeutet, dass wir einen Umschwung hatten, der RVI ansteigt und schließlich der Schlusskurs sinkt, um den Divergenzkontext zu bestätigen. Dies hilft dabei, eine Aufwärts-Umkehr zu erfassen, wenn der Kurs mit RVI-Momentum über den SAR ausbricht. Für dieses Muster gibt es mehrere Voraussetzungen, die alle bestätigt werden müssen, bevor wir den Wert wahr oder 1 zuweisen. 

Für die Abwärtsbedingungen setzen wir diese auf den zweiten Index des NumPy-Arrays der Merkmale. Für einen Abwärts-Wert liegt das vorherige Tief am oder unter dem vorherigen SAR; das aktuelle Hoch liegt am oder unter dem aktuellen SAR; der RVI fällt; und der Schlusskurs steigt als Bestätigung der Divergenzeinstellung. Signal von Pattern-2 zeigt eine Abwärts-Divergenz an, und es ist wichtig, die Datenkonsistenz zwischen den einzelnen Datenrahmen sicherzustellen. Anschließend werden die ersten Zeilen unseres Merkmals-Arrays durch Zuweisung von Nullen annulliert, um die Verwendung von Verschiebungsvergleichen zu berücksichtigen. Dies verhindert ungültige Signale aufgrund fehlender verzögerter Daten. Dies ist ein Standardverfahren für Zeitreihenmerkmale, die Verzögerungen aufweisen.

Dieses Merkmal 2 könnte theoretisch in Verbindung mit dem obigen Signal von Pattern-1 verwendet werden, da es eine gewisse komplementäre Beziehung gibt. Merkmal-1 ist ein reines Trendfolgesystem, während Merkmal-2 auf Umkehrungen an Divergenzpunkten abzielt und somit den Swing-Handel in ein robusteres System einbeziehen kann. Wie jedoch in früheren Artikeln dargelegt und gezeigt wurde, führt die Paarung verschiedener Muster zwangsläufig zu einer vorzeitigen Überkreuzung von Signalen, sodass die Tests für ein solches System mit einer umfangreichen Historie durchgeführt werden müssen und die Vorwärtsbewegungen ebenfalls endgültig sein sollten. Durch das Trainieren und Testen dieses Musters erhalten wir den nachstehenden Bericht, der sich sowohl auf den Trainings- als auch auf den Testzeitraum erstreckt. Auch hier zeigt sich, dass wir durch den Einsatz des maschinellen Lernens günstigere Ergebnisse erzielen:

r2

c2

Nachfolgend sehen Sie das Diagramm, das wir für Pattern-2 ohne überwachtes Lernen erhalten haben:

c2_alt

Merkmal-6

Unser letztes Testmerkmal aus den ursprünglichen 10 des letzten Artikels basiert auf dem Signal von Pattern-6. Dabei wird das Kreuzen des RVI mit seiner Signallinie anstelle des RVI Momentum zur Bestätigung herangezogen. Er konzentriert sich auf starke Trendsignale, die das Kreuzen von SAR und RVI aufweisen. Wir implementieren dies in Python wie folgt:

def feature_6(sar_df, rvi_df, price_df):
    """

    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) &
                     (price_df['low'] > sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI_Signal']) &
                     (rvi_df['RVI'].shift(1) < rvi_df['RVI_Signal'].shift(1))).astype(int)
    
    feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) &
                     (price_df['high'] < sar_df['SAR']) &
                     (rvi_df['RVI'] < rvi_df['RVI_Signal']) &
                     (rvi_df['RVI'].shift(1) > rvi_df['RVI_Signal'].shift(1))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Wir beginnen wie immer mit der Dimensionierung eines mit Null gefüllten NumPy-Arrays, das unsere geplanten Ausgaben aufnehmen soll. Diese Initialisierung ist wichtig und steht im Einklang mit den anderen Funktionen, um eine saubere NaN-freie Ausgabe zu gewährleisten. Dies trägt auch dazu bei, die vorhersehbare Array-Struktur für die spätere Verwendung zu erhalten.

Damit sind die Bedingungen für das Aufwärtssignal definiert, das beim ersten Index, also bei Null, erfasst wird. Wir registrieren eine 1, wenn das vorherige Tief über dem SAR liegt, das aktuelle Tief über dem aktuellen SAR liegt, der aktuelle RVI ebenfalls über seiner Signallinie liegt und schließlich der vorherige RVI unter seinem Signal lag.

Mit der Definition des Aufwärtsmusters legen wir auch das Abwärtsmuster fest, das vorliegt, wenn das vorherige Hoch unter dem vorherigen SAR liegt; und dies wird dadurch aufrechterhalten, dass das aktuelle Hoch ebenfalls unter dem aktuellen SAR liegt; und der aktuelle RVI liegt unter seiner Signallinie; wobei der vorherige RVI über der Nullgrenze lag. Das Trainieren und Testen dieses Musters wie bei den beiden anderen ergibt den folgenden Testbericht. Wieder einmal hat es den Anschein, dass wir einen günstigen Vorwärtstest haben.

r6

c6

Nachfolgend sehen Sie das Diagramm, das wir für Pattern-6 ohne überwachtes Lernen erhalten haben:

c6_alt


Das Netzwerk

Beim Testen der 3 oben ausgewählten Muster haben wir ein neuronales Netz eingesetzt, das ein 1D Convolutional Neural Network ist. Es besteht aus drei Faltungsschichten, deren Filteranzahl und Kernelgröße exponentiell ansteigt. Darauf folgt das Max-Pooling, eine Verflachung. Schließlich verwenden wir die vollständig verknüpften Schichten, um zur endgültigen Ausgabe zu gelangen. Dies wird in Python wie folgt codiert:

class ExpConv1DNetwork(nn.Module):
    def __init__(self, input_length, input_channels=1, base_filters=16, base_kernel_size=3, exp_base=2):
        super(ExpConv1DNetwork, self).__init__()
        self.conv_layers = nn.ModuleList()
        self.pool_layers = nn.ModuleList()
        
        for i in range(3):
            filters = int(base_filters * (exp_base ** i))
            kernel_size = int(base_kernel_size * (exp_base ** i)) | 1
            self.conv_layers.append(
                nn.Conv1d(
                    in_channels=input_channels if i == 0 else int(base_filters * (exp_base ** (i-1))),
                    out_channels=filters,
                    kernel_size=kernel_size,
                    padding='same'
                )
            )
            # Use smaller kernel size for pooling to prevent size reduction to 0
            self.pool_layers.append(nn.MaxPool1d(kernel_size=2, ceil_mode=True))
        
        self.flatten_size = self._get_flatten_size(input_length, input_channels, base_filters, exp_base)
        self.flatten = nn.Flatten()
        self.dense1 = nn.Linear(self.flatten_size, 128)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.5)
        self.dense2 = nn.Linear(128, 1)
    
    def _get_flatten_size(self, input_length, input_channels, base_filters, exp_base):
        current_length = input_length
        current_channels = input_channels
        
        for i in range(3):
            current_channels = int(base_filters * (exp_base ** i))
            # Update length after pooling
            current_length = (current_length + 1) // 2  # Ceiling division for ceil_mode=True
        
        return current_channels * current_length
    
    def forward(self, x):
        for conv, pool in zip(self.conv_layers, self.pool_layers):
            x = self.relu(conv(x))
            x = pool(x)
        x = self.flatten(x)
        x = self.relu(self.dense1(x))
        x = self.dropout(x)
        x = self.dense2(x)
        return x

Dieses Netz erwartet eine 1D-Eingabe in Form von Losgröße, Eingangskanälen und Eingabelänge. Das ist über Parameter wie base-filters, base-kernel-size und exp-base weiter anpassbar. Unser Ergebnis ist ein einzelner Skalar, der so trainiert wurde, dass er im Bereich von 0 bis 1 liegt, wobei 0 eine Abwärtsprognose und 1 eine Aufwärtsprognose darstellt. Eine genaue Durchsicht der Codezeilen dieser Klasse wäre sicherlich angebracht gewesen, da diese Klasse für die Leistung unserer neu gestalteten Signalmuster entscheidend ist. Angesichts der Länge unseres Artikels wird dies jedoch vorerst beiseite geschoben, aber wir werden in ähnlichen Artikeln in Zukunft darauf achten.


Schlussfolgerung

Wir haben die potenziellen Vorteile des Hinzufügens von überwachtem Lernen zu Signalmustern untersucht, die bei einem begrenzten Datenfenster des Paares GBP CHF von nur 2 Jahren nicht in der Lage waren, Vorwärtsbewegungen durchzuführen. Unsere Tests, wiederum unter sehr eingeschränkten Bedingungen, haben ergeben, dass es einen positiven Unterschied zu den Ergebnissen des letzten Artikels gibt. Dies ist ermutigend. Wie immer ist jedoch eine eigenständige und gründlichere Prüfung durch den Leser erforderlich, bevor die hier vorgestellten Ideen übernommen werden können. 


Name Beschreibung
WZ-70.mq5 Datei, deren Kopfzeile die in der Assistentengruppe verwendeten Dateien angibt
SignalWZ_70.mqh Nutzerdefinierte Signalklassendatei, die vom MQL5-Assistenten verwendet wird
70_1.onnx Exportiertes Netzmodell für Pattern-1
70_2.onnx Exportiertes Netzmodell für Pattern-2
70_6.onnx Exportiertes Netzmodell für Pattern-6

Für Leser, die neu sind, soll dieser beigefügte Code in einen Expert Advisor mit Hilfe des MQL5-Assistenten zusammengesetzt werden. Hier gibt es Anleitungen, wie man das macht.

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

Beigefügte Dateien |
WZ-70.mq5 (6.92 KB)
SignalWZ_70.mqh (14.34 KB)
70_1.onnx (153.86 KB)
70_2.onnx (153.86 KB)
70_6.onnx (153.86 KB)
Datenwissenschaft und ML (Teil 44): Forex OHLC Zeitreihenprognose mit Vektor-Autoregression (VAR) Datenwissenschaft und ML (Teil 44): Forex OHLC Zeitreihenprognose mit Vektor-Autoregression (VAR)
Entdecken Sie, wie Vektor-Autoregressions-Modelle (VAR) Forex OHLC (Open, High, Low und Close) Zeitreihendaten prognostizieren können. Dieser Artikel befasst sich mit der VAR-Implementierung, dem Modelltraining und der Echtzeitprognose in MetaTrader 5 und hilft Händlern, voneinander abhängige Währungsbewegungen zu analysieren und ihre Handelsstrategien zu verbessern.
Meistern der Log-Einträge (Teil 8): Fehlereinträge, die sich selbst übersetzen Meistern der Log-Einträge (Teil 8): Fehlereinträge, die sich selbst übersetzen
In diesem achten Teil der Serie Meistern der Log-Einträge untersuchen wir die Implementierung mehrsprachiger Fehlermeldungen in Logify, einer leistungsstarken Protokollierungsbibliothek für MQL5. Sie lernen, wie Sie Fehler mit Kontext strukturieren, Meldungen in mehrere Sprachen übersetzen und Protokolle dynamisch nach Schweregrad formatieren können. Und das alles in einem sauberen, erweiterbaren und produktionsreifen Design.
Erstellen eines Handelsadministrator-Panels in MQL5 (Teil XII): Integration eines Rechners für Forex-Werte Erstellen eines Handelsadministrator-Panels in MQL5 (Teil XII): Integration eines Rechners für Forex-Werte
Die genaue Berechnung der wichtigsten Handelswerte ist ein unverzichtbarer Bestandteil des Arbeitsablaufs eines jeden Händlers. In diesem Artikel werden wir die Integration eines leistungsstarken Dienstprogramms - des Forex-Rechners - in das Handelsverwaltungs-Panel besprechen, wodurch die Funktionalität unseres Multi-Panel-Handelsverwaltungssystems noch erweitert wird. Die effiziente Bestimmung von Risiko, Positionsgröße und potenziellem Gewinn ist bei der Platzierung von Handelsgeschäften von entscheidender Bedeutung, und diese neue Funktion wurde entwickelt, um diesen Prozess innerhalb des Panels schneller und intuitiver zu gestalten. Erforschen Sie mit uns die praktische Anwendung von MQL5 beim Aufbau fortgeschrittener Handelspanels.
Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 8): Analyse mehrerer Strategien Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 8): Analyse mehrerer Strategien
Wie können wir mehrere Strategien am besten kombinieren, um eine leistungsfähige Gesamtstrategie zu schaffen? Nehmen Sie an dieser Diskussion teil, in der wir drei verschiedene Strategien in unsere Handelsanwendung einbauen wollen. Händler verwenden oft spezielle Strategien für die Eröffnung und Schließung von Positionen, und wir wollen wissen, ob unsere Maschinen diese Aufgabe besser erfüllen können. In unserer einleitenden Diskussion machen wir uns mit den Fähigkeiten des Strategietesters und den Prinzipien der OOP vertraut, die wir für diese Aufgabe benötigen.