English 日本語
preview
Klassische Strategien neu interpretieren (Teil II): Bollinger-Bänder Ausbrüche

Klassische Strategien neu interpretieren (Teil II): Bollinger-Bänder Ausbrüche

MetaTrader 5Beispiele | 20 September 2024, 10:25
173 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Einführung

Bollinger-Bänder sind ein vielseitiges Instrument für Handelsstrategien, das sowohl für die Trendverfolgung als auch für die Identifizierung potenzieller Wende- oder Umkehrpunkte geeignet ist. Technisch gesehen besteht der Indikator aus einem exponentiellen gleitenden Durchschnitt (EMA), der den Schlusskurs eines Wertpapiers glättet. Diese zentrale Linie wird von zwei weiteren Linien umschlossen, die in der Regel 2 Standardabweichungen über und unter dem EMA liegen.

In diesem Artikel wollen wir die Vorteile der Strategie von Grund auf empirisch analysieren, um den Lesern, die den Einsatz der Bollinger-Bänder in Erwägung ziehen, die Entscheidung zu erleichtern, ob die Strategie für sie besser geeignet ist. Darüber hinaus werden wir zeigen, wie technische Indikatoren verwendet werden können, um KI-Modelle zu steuern und hoffentlich stabilere Handelsstrategien zu entwickeln.

Dazu haben wir zwei gleichwertige KI-Modelle mit dem Algorithmus der linearen Diskriminanzanalyse trainiert und die Modelle mit Hilfe von Zeitreihen-Kreuzvalidierung verglichen, wobei wir uns für die Tests ausschließlich auf die scikit-learn-Bibliothek verlassen haben. Das erste Modell wurde so trainiert, dass es einfach vorhersagen konnte, ob der Preis steigen oder fallen würde, während das zweite Modell lernte zu prognostizieren, wie sich der Preis zwischen den vier vom Bollinger-Bänder umrissenen Zonen bewegt. Zum Leidwesen der Fans der Bollinger-Bänder haben uns unsere empirischen Beobachtungen zu dem Schluss gebracht, dass eine direkte Preisvorhersage effektiver sein kann als die Vorhersage des Übergangs zwischen den vier von den Bollinger-Bändern gebildeten Zonen. Es ist jedoch anzumerken, dass bei der Festlegung der Parameter des Indikators keine Optimierungstechniken eingesetzt wurden.

Dieser Artikel soll verdeutlichen:

  1. Wie kann man zwei mögliche Handelsstrategien analytisch vergleichen?
  2. Wie man die lineare Diskriminanzanalyse von Grund auf in MQL5 implementiert.
  3. Wie man stabile Handelsstrategien unter Einbezug der KI entwickelt.

    Überblick über die Strategie und unsere Beweggründe

    Der Begriff „Künstliche Intelligenz“ (KI) ist wohl eine der irreführendsten Namensgebungen der Geschichte. Nachdem Sie diesen Artikel gelesen haben, werden Sie vielleicht zustimmen, dass KI eine falsche Bezeichnung ist. Für mich als Autor liegt das Problem in dem Wort „Intelligenz“. KI-Modelle sind nicht intelligent im menschlichen Sinne. Vielmehr handelt es sich um intelligente Anwendungen von Optimierungsalgorithmen.

    KI-Modelle zielen in erster Linie auf die Minimierung von Fehlern oder die Maximierung von Gewinnen innerhalb eines Systems ab. Die aus diesen Modellen abgeleiteten Lösungen sind jedoch nicht immer praktikabel. Ein KI-System, das darauf ausgelegt ist, die Verluste auf einem Handelskonto zu minimieren, könnte beispielsweise zu dem Schluss kommen, dass es am besten ist, keine Geschäfte zu tätigen, da dies keine Verluste garantiert. Auch wenn diese Lösung mathematisch gesehen dem Problem gerecht wird, ist sie für den Handel unpraktisch.

    Als intelligente KI-Praktiker müssen wir unsere Modelle mit sorgfältig geplanten Beschränkungen steuern. In diesem Artikel werden wir unsere KI-Modelle mithilfe von Bollinger-Bändern steuern. Wir werden vier mögliche Zonen identifizieren, in denen sich der Preis jederzeit befinden könnte. Beachten Sie, dass sich der Preis zu einem bestimmten Zeitpunkt nur in einer dieser vier Zonen befinden kann:

    • Zone 1: Der Preis liegt vollständig oberhalb der Bollinger-Bänder.
    • Zone 2: Der Preis liegt oberhalb des mittleren Bandes, aber unterhalb des oberen Bandes.
    • Zone 3: Der Preis liegt oberhalb des unteren Bandes, aber unterhalb des mittleren Bandes.
    • Zone 4: Preis liegt unter dem unteren Band.

    Wir werden ein Modell trainieren, um zu verstehen, wie der Preis zwischen diesen vier Zonen übergeht, und die nächste Zone vorhersagen, in die sich der Preis bewegen wird. Handelssignale werden immer dann erzeugt, wenn sich der Preis von einer Zone in eine andere bewegt. Wenn unser Modell beispielsweise vorhersagt, dass sich der Kurs von Zone 2 nach Zone 1 bewegen wird, interpretieren wir dies als eine Aufwärtsbewegung und lösen einen Kaufauftrag aus. Unser Modell und unser Expert Advisor werden vollständig in nativem MQL5 implementiert sein.

    Die Bollinger-Bänder können in einer Reihe von Handelsstrategien eingesetzt werden, von der Trendverfolgung bis hin zur Identifizierung von Wendepunkten oder Umkehrpunkten. Technisch gesehen besteht dieser Indikator aus einem exponentiellen gleitenden Durchschnitt (EMA), der normalerweise den Schlusskurs eines Wertpapiers glättet. Er wird von zwei zusätzlichen Bändern flankiert: eines oberhalb und eines unterhalb des EMA, deren Abstand in der Regel jeweils auf 2 Standardabweichungen festgelegt ist.

    Traditionell werden Bollinger-Bänder verwendet, um überkaufte und überverkaufte Kursniveaus zu identifizieren. Wenn die Kurse das obere Bollinger-Bänder erreichen, neigen sie dazu, auf den Mittelwert zurückzufallen, und dieses Verhalten gilt oft auch für das untere Band. Dies kann so interpretiert werden, dass das Wertpapier um 2 Standardabweichungen abgewertet wird, wenn es die untere Bandbreite erreicht, was Anleger dazu verleiten könnte, den Vermögenswert mit einem attraktiven Abschlag zu kaufen. Es kann jedoch vorkommen, dass die Kurse heftig aus den Bollinger-Bändern ausbrechen und sich in einem starken Trend fortsetzen. Leider zeigt unsere statistische Analyse, dass die Vorhersage von Ausbrüchen aus dem Bollinger-Band schwieriger sein kann als die Vorhersage von Preisänderungen.


    Abrufen der Daten von unserem MetaTrader 5 Terminal

    Um zu beginnen, öffnen Sie Ihr MetaTrader5-Terminal und klicken Sie auf das Symbol-Symbol im Kontextmenü. Sie sollten eine Liste der auf Ihrem Terminal verfügbaren Symbole sehen.

    Exportieren der benötigten Daten

    Abb. 1: Vorbereitung des Abrufs von Daten von unserem MetaTrader5 Terminal.

    Klicken Sie dann auf das Fenster Balken und suchen Sie nach dem Symbol, das Sie modellieren möchten, und wählen Sie den gewünschten Zeitrahmen aus. Für unser Beispiel werde ich den täglichen GBPUSD-Wechselkurs modellieren.

    Holen Sie die Daten.

    Abb. 2: Wir bereiten den Export unserer Daten vor.

    Klicken Sie dann auf die Schaltfläche „Balken exportieren“, und wir werden unsere Analyse in Python fortsetzen.


    Explorative Datenanalyse

    Veranschaulichen wir uns die Wechselwirkungen zwischen den Bollinger-Bändern und den Veränderungen der Kursniveaus.

    Wir beginnen damit, die benötigten Bibliotheken zu importieren.

    #Import libraries
    import pandas as pd
    import numpy as np
    import seaborn as sns
    import pandas_ta as ta
    
    Dann lesen wir die csv-Datei ein, die wir für unseren empirischen Test erstellt haben. Beachten Sie, dass wir den Parameter sep="\t" übergeben haben, um anzugeben, dass unsere csv-Datei durch Tabulatoren getrennt ist. Dies ist die Standardausgabe des MetaTrader5 Terminals.
    #Read in the csv file
    csv = pd.read_csv("/home/volatily/market_data/GBPUSD_Daily_20160103_20240131.csv",sep="\t")
    
    Definieren wir nun unseren Prognosehorizont.
    #Define how far into the future we should forecast
    look_ahead = 20
    Jetzt werden wir die Bollinger-Bänder für die Daten berechnen, die wir mit der Bibliothek Pandas-Ta haben.
    #Add the Bollinger bands
    csv.ta.bbands(length=30,std=2,append=True)
    
    Als Nächstes benötigen wir eine Spalte, in der der zukünftige Schlusskurs gespeichert wird.
    #Add a column to show the future price
    csv["Future Close"] = csv["Close"].shift(-look_ahead)

    Jetzt werden wir unsere Daten kennzeichnen. Wir werden zwei Kennzeichnungen haben, von denen eines die Preisveränderung und das andere die Preisveränderung zwischen den Bollinger-Band-Zonen angibt. Preisänderungen werden mit 1 für einen Anstieg und mit 0 für einen Rückgang gekennzeichnet. Die Kennzeichnungen der Bollinger-Bänder wurden oben definiert.

    #Add the normal target, predicting changes in the close price
    csv["Price Target"] = 0
    csv["Price State"] = 0
    #Label the data our conditions
    #If price depreciated, our label is 0 
    csv.loc[csv["Close"] < csv["Close"].shift(look_ahead),"Price State"] = 0
    csv.loc[csv["Close"] > csv["Future Close"], "Price Target"] = 0
    #If price appreciated, our label is 1
    csv.loc[csv["Close"] > csv["Close"].shift(look_ahead),"Price State"] = 1
    csv.loc[csv["Close"] < csv["Future Close"], "Price Target"] = 1
    
    #Label the Bollinger bands
    #The label to store the current state of the market
    csv["Current State"] = -1
    #If price is above the upper-band, our label is 1
    csv.loc[csv["Close"] > csv["BBU_30_2.0"], "Current State"] = 1
    #If price is below the upper-band and still above the mid-band,our label is 2
    csv.loc[(csv["Close"] < csv["BBU_30_2.0"]) & (csv["Close"] > csv["BBM_30_2.0"]),"Current State"] = 2
    #If price is below the mid-band and still above the low-band,our label is 3
    csv.loc[(csv["Close"] < csv["BBM_30_2.0"]) & (csv["Close"] > csv["BBL_30_2.0"]),"Current State"] = 3
    #Finally, if price is beneath the low-band our label is 4
    csv.loc[csv["Close"] < csv["BBL_30_2.0"], "Current State"] = 4
    #Now we can add a column to denote the future state the market will be in
    csv["State Target"] = csv["Current State"].shift(-look_ahead)

    Lassen Sie uns alle ungültigen Einträge löschen.

    #Let's drop any NaN values 
    csv.dropna(inplace=True)
    

    Wir können nun mit der Visualisierung unserer Daten beginnen, zunächst mit den Veränderungen der Preisniveaus anhand von Boxplots. Auf der y-Achse werden die Schlusskurse und auf der x-Achse zwei Werte angezeigt. Der erste Wert auf der x-Achse steht für die Fälle in unseren Daten, in denen der Preis fiel, und ist mit 0 gekennzeichnet. Innerhalb des Wertes 0 finden Sie zwei Boxplots. Das erste, blau dargestellte Boxplot zeigt die Fälle, in denen der Kurs 20 Kerzen lang fiel und dann weitere 20 Kerzen lang weiter fiel. Das orangefarbene Kästchen stellt die Fälle dar, in denen der Preis 20 Kerzen lang fiel, dann aber in den nächsten 20 Kerzen anstieg. Die von uns erhobenen Daten zeigen, dass die Preise immer dann, wenn sie unter 1,1 fielen, wieder anstiegen. Umgekehrt hat der Wert 1 auf der x-Achse auch zwei Boxplots darüber. Die erste blaue Box fasst die Fälle zusammen, in denen der Preis anstieg und dann fiel, während die zweite orangefarbene Box die Fälle zusammenfasst, in denen der Preis stieg und weiter stieg.

    Beachten Sie, dass für den Wert 1, d. h. wenn der Kurs 20 Kerzen lang steigt, das Ende des blauen Box-Plots größer ist als das des orangen Boxplots. Dies könnte darauf hindeuten, dass der GBPUSD-Wechselkurs bei einem Anstieg in Richtung der 1,5-Marke zu einem Rückgang neigt, während in der 0-Spalte bei einem Rückgang des Wechselkurses auf etwa die 1,1-Marke eine Tendenz zur Umkehr und zum Anstieg zu beobachten ist.

    #Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries
    sns.boxplot(data=csv,x="Price State",y="Close",hue="Price Target")
    

    Visualisierung des Verhaltens der Preise

    Abb. 3: Visualisierung der Veränderungen im Preisniveau.

    Wir können ähnliche Visualisierungen auch mit den durch die Bollinger-Bänder definierten Zuständen durchführen. Wie zuvor wird der Schlusskurs auf der y-Achse angezeigt, und die aktuelle Position des Kurses innerhalb der Bollinger-Bänder wird durch die vier Werte auf der x-Achse markiert. Beachten Sie, dass die Dochte der Boxplots Bereiche aufweisen, in denen sie sich nicht überschneiden. Diese Regionen können möglicherweise als Klassifizierungsgrenzen dienen. Beobachten Sie beispielsweise, dass der Kurs immer dann, wenn er sich im Zustand 4 oder vollständig unter den Bollinger-Bändern befindet und sich der 1,1-Marke nähert, anscheinend immer wieder abprallen wird.

    #Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries
    sns.boxplot(data=csv,x="Current State",y="Close",hue="Price Target")
    


    Visualisierung des Kursverhaltens in den Zonen der Bollinger-Bänder

    Abb. 4: Visualisierung des Kursverhaltens innerhalb der 4 Zonen der Bollinger-Bänder.

    Außerdem können wir mit Hilfe von Boxplots visualisieren, wie der Kurs zwischen den vier Bollinger-Band-Zuständen wechselt. Das nachstehende Boxplot zeigt beispielsweise den Schlusskurs auf der y-Achse und vier Werte auf der x-Achse, die die vier von den Bollinger-Bändern gebildeten Zonen darstellen. Jede Box fasst zusammen, wohin der Preis nach dem Auftauchen in dieser Zone übergegangen ist. Lassen Sie uns die Daten gemeinsam interpretieren. Beachten Sie, dass der erste Wert, Zustand 1, nur drei Boxplots aufweist. Das bedeutet, dass der Preis von Zustand 1 aus nur in drei mögliche Zustände übergeht: Er bleibt entweder in Zustand 1 oder geht in Zustand 2 oder 3 über.

    #Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries
    sns.boxplot(data=csv,x="Current State",y="Close",hue="State Target")
    

    Visualisierung des Preisverhaltens innerhalb der 4 Zonen.

    Abb. 5: Visualisierung des Preisverhaltens innerhalb der 4 Zonen.

    Wir erstellen eine kategoriale Darstellung mit dem Schlusskurs auf der y-Achse und zwei Werten auf der x-Achse. Der erste Wert, Price State 0, zeigt an, dass der Preis in den letzten 10 Kerzen gefallen ist. Über dem Zustand 0 befindet sich eine Wolke aus blauen und orangefarbenen Punkten. Diese Punkte stehen für Fälle, in denen der Kurs nach einem Rückgang von 10 Kerzen entweder weiter fiel oder sich umkehrte und für die nächsten 10 Kerzen wieder anstieg. Beachten Sie, dass es keine klare Trennung zwischen den Fällen gibt, in denen der Preis weiter fällt, und den Fällen, in denen er sich umkehrt und zu steigen beginnt. Es scheint, dass der einzige klar definierte Trennungspunkt der ist, an dem sich der Preis extremen Werten nähert. Bei allen Preisniveaus unter 1,1 im Zustand 0 beispielsweise stieg der Preis durchweg wieder an.

    #we have very poor separation in the data
    sns.catplot(data=csv,x="Price State",y="Close",hue="Price Target")
    

    Visualisierung der Trennung von Daten innerhalb des Datensatzes.

    Abb. 6: Visualisierung der Trennung im Datensatz.

    Wir können die gleichen Visualisierungen mit den vier Zuständen durchführen, die durch die Bollinger-Bänder definiert sind. Auch hier ist zu beobachten, dass die blauen und orangenen Punkte am besten bei den extremen Preisniveaus getrennt sind.

    #Visualizing the separation of data in the Bollinger band zones
    sns.catplot(data=csv,x="Current State",y="Close",hue="Price Target")

    Visualisierung der Trennung von Daten in den Zuständen der Bollinger-Bänder

    Abb. 7: Visualisierung der Datentrennung in den Zonen der Bollinger-Bänder.

    Wir können nun ein Streudiagramm mit dem Schlusskurs auf der x-Achse und dem zukünftigen Schlusskurs auf der y-Achse erstellen. Wir färben die Punkte entweder orange oder blau, je nachdem, ob der Kurs in den letzten 20 Kerzen gestiegen oder gefallen ist. Stellen Sie sich vor, Sie legen eine goldene Linie von der linken unteren Ecke zur rechten oberen Ecke des Grundstücks. Alle Punkte oberhalb dieser goldenen Linie stellen Fälle dar, in denen der Kurs in den nächsten 20 Kerzen gestiegen ist, unabhängig davon, ob er in den vorangegangenen 20 Kerzen gefallen (blau) oder gestiegen (orange) ist. Beachten Sie, dass auf beiden Seiten der goldenen Linie eine Mischung aus blauen und orangefarbenen Punkten vorhanden ist.

    Wenn wir eine imaginäre rote Linie bei dem Schlusswert von 1,3 ziehen würden, gäbe es viele blaue und orangefarbene Punkte, die diese Linie berühren. Dies bedeutet, dass neben dem aktuellen Schlusskurs auch andere Variablen den zukünftigen Schlusskurs beeinflussen. Eine andere Möglichkeit, diese Beobachtungen zu interpretieren, besteht darin, dass derselbe Eingabewert zu unterschiedlichen Ausgabewerten führen kann, was darauf hindeutet, dass unser Datensatz verrauscht ist!

    #Notice that using the price target gives us beautiful separation in the data set
    sns.scatterplot(data=csv,x="Close",y="Future Close",hue="Price Target")

    Visualisierung der Daten

    Abb. 8: Unser Datensatz weist eine sehr geringe natürliche Trennung auf.

    Wir werden nun dieselbe Visualisierung durchführen, indem wir den Zielzustand der Bollinger-Bänder verwenden, um das Streudiagramm einzufärben. Beachten Sie, dass die Trennung innerhalb unseres Datensatzes bei Verwendung der Bollinger-Bänder sehr gering ist. Optisch sieht es noch schlechter aus als die Trennung, die wir erhalten haben, wenn wir einfach den Preis selbst verwendet haben.

    #Using the Bollinger bands to define states, however, gives us rather mixed separation
    sns.scatterplot(data=csv,x="Close",y="Future Close",hue="Current State")
    

    Visualisierung der Trennung im Datensatz

    Abb. 9: Visualisierung der durch die Zonen der Bollinger-Bänder erzeugten Trennung im Datensatz.

    Führen wir nun unsere analytischen Tests durch, um festzustellen, ob wir eine größere Genauigkeit bei der Vorhersage von Änderungen der Preisniveaus oder von Änderungen der Bollinger-Band-Zustände erreichen. Zunächst importieren wir die erforderlichen Bibliotheken.

    #Now let us compare our accuracy forecasting the original price target and the new Bollinger bands target
    from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
    from sklearn.model_selection import TimeSeriesSplit
    from sklearn.metrics import accuracy_score
    

    Als Nächstes werden wir die Parameter für die Kreuzvalidierung der Zeitreihen festlegen. Der erste Parameter, splits, gibt die Anzahl der Partitionen an, die aus unseren Daten erstellt werden sollen. Der zweite Parameter, gap, bestimmt die Größe der Lücke zwischen den einzelnen Partitionen. Diese Lücke sollte mindestens so groß sein wie unser Prognosehorizont.

    #Now let us define the cross validation parameters
    splits = 10
    gap = look_ahead
    

    Nun können wir unser Zeitreihenobjekt erstellen, das uns die entsprechenden Indizes für unseren Trainings- und Testsatz liefert. In unserem Beispiel werden 10 Indexpaare erzeugt, um unser Modell zu trainieren und zu bewerten.

    #Now create the cross validation object
    tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
    

    Als Nächstes erstellen wir einen DataFrame, um die Genauigkeit unseres Modells für die Vorhersage der einzelnen Ziele zu speichern.

    #We need a dataframe to store the accuracy associated with each target
    target_accuracy = pd.DataFrame(index=np.arange(0,splits),columns=["Price Target Accuracy","New Target Accuracy"])
    

    Nun werden wir unsere Modelleingaben definieren.

    #Define the inputs
    predictors = ["Open","High","Low","Close"]
    target = "Price Target"
    

    Nun werden wir den Kreuzvalidierungstest durchführen.

    #Now let us perform the cross validation
    for i,(train,test) in enumerate(tscv.split(csv)):
        #First initialize the model
        model = LinearDiscriminantAnalysis()
        #Now train the model
        model.fit(csv.loc[train[0]:train[-1],predictors],csv.loc[train[0]:train[-1],target])
        #Now record the accuracy
        target_accuracy.iloc[i,0] = accuracy_score(csv.loc[test[0]:test[-1],target],model.predict(csv.loc[test[0]:test[-1],predictors]))
    

    Jetzt können wir endlich das Ergebnis der Tests analysieren.

    target_accuracy

    Die neuen Genauigkeitsstufen, die wir erhalten haben

    Abb. 10: Unser Modell schnitt bei der direkten Vorhersage von Preisveränderungen besser ab.

    Wie bereits erwähnt, haben unsere Tests gezeigt, dass unser Modell bei der Vorhersage von Kursniveaus effektiver ist als die Übergänge der Bollinger-Bänder. Es ist jedoch zu beachten, dass sich die beiden Strategien im Durchschnitt nicht signifikant unterscheiden.

    Als Nächstes werden wir die Strategie in MQL5-Code implementieren, um sie zu testen und zu sehen, wie sie sich auf realen Marktdaten verhält

    Die Umsetzung der Strategie

    Zunächst importieren wir die notwendigen Bibliotheken, die wir in unserem Programm verwenden werden.

    //+------------------------------------------------------------------+
    //|                                           Target Engineering.mq5 |
    //|                                        Gamuchirai Zororo Ndawana |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Gamuchirai Zororo Ndawana"
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //+------------------------------------------------------------------+
    //| Libraries we need                                                |
    //+------------------------------------------------------------------+
    /*
       This Expert Advisor will implement the Linear Discriminant Anlysis
       algorithm to help us successfully trade Bollinger Band Breakouts.
    
       Gamuchirai Zororo Ndawana
       Selebi Phikwe
       Botswana
       Wednesday 10 July 2024 15:42
    */
    #include <Trade/Trade.mqh>//Trade class
    CTrade Trade;

    Als Nächstes werden wir nutzerkonfigurierbare Eingaben definieren, z. B. die Periode der Bollinger-Bänder und die Standardabweichung.

    //+------------------------------------------------------------------+
    //| Input variables                                                  |
    //+------------------------------------------------------------------+
    input double bband_deviation = 2.0;//Bollinger Bands standard deviation
    input int    bband_period = 60; //Bollinger Bands Period
    input int look_ahead = 10; //How far into the future should we forecast?
    int input  lot_multiple = 1; //How many times bigger than minimum lot?
    int input    fetch = 200;//How much data should we fetch?
    input double stop_loss_values = 1;//Stop loss values
    

    Anschließend werden wir die globalen Variablen definieren, die in unserer Anwendung verwendet werden sollen.

    //+------------------------------------------------------------------+
    //| Global variables                                                 |
    //+------------------------------------------------------------------+
    int bband_handler;//Technical Indicator Handlers
    vector bband_high_reading = vector::Ones(fetch);//Bollinger band high reading
    vector bband_mid_reading = vector::Ones(fetch);//Bollinger band mid reading
    vector bband_low_reading = vector::Ones(fetch);//Bollinger band low reading
    double minimum_volume;//The smallest contract size allowed
    double ask_price;//Ask
    double bid_price;//Bid
    vector input_data = vector::Zeros(fetch);//All our input data will be kept in vectors
    int training_output_array[];//Our output data will be stored in a vector
    vector output_data = vector::Zeros(fetch);
    double variance;//This is the variance of our input data
    int classes = 4;//The total number of output classes we have
    vector mean_values = vector::Zeros(classes);//This vector will store the mean value for each class
    vector probability_values = vector::Zeros(classes);//This vector will store the prior probability the target will belong each class
    vector total_class_count = vector::Zeros(classes);//This vector will count the number of times each class was the target
    bool model_trained = false;//Has our model been trained?
    bool training_procedure_running = false;//Have we started the training process?
    int forecast = 0;//Our model's forecast
    double discriminant_values[4];//The discriminant function
    int current_state = 0;//The current state of the system
    

    Als Nächstes müssen wir die Initialisierungsfunktion unseres Expert Advisors definieren. In dieser Funktion initialisieren wir den Indikator der Bollinger-Bänder und rufen wichtige Marktdaten ab.

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- Initialize the bollinger bands
       bband_handler = iBands(_Symbol,PERIOD_CURRENT,bband_period,0,bband_deviation,PRICE_CLOSE);
    //--- Market data
       minimum_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
    //--- End of initilization
       return(INIT_SUCCEEDED);
      }
    

    Im Anschluss daran werden wir wichtige Hilfsfunktionen definieren, um unseren Code in kleinere, besser handhabbare Segmente aufzuteilen. Die erste Funktion, die wir erstellen werden, wird für die Aktualisierung unserer Marktdaten zuständig sein.

    //+------------------------------------------------------------------+
    //|This function will update the price and other technical data      |
    //+------------------------------------------------------------------+
    void update_technical_data(void)
      {
    //--- Update the bid and ask prices
       ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
       bid_price = SymbolInfoDouble(_Symbol,SYMBOL_BID);
      }
    

    Anschließend müssen wir eine Funktion implementieren, die den Initialisierungsvorgang steuert. Diese Funktion stellt sicher, dass wir die Trainingsdaten abrufen, unser Modell anpassen und die Prognosen in der richtigen Reihenfolge erstellen.

    //+------------------------------------------------------------------+
    //|This function will start training our model                       |
    //+------------------------------------------------------------------+
    void model_initialize(void)
      {
    //--- First we have to fetch the input and output data
       Print("Initializing the model");
       int input_start = 1 + (look_ahead * 2);
       int output_start = 1+ look_ahead;
       fetch_input_data(input_start,fetch);
       fetch_output_data(output_start,fetch);
    //--- Fit the model
       fit_lda_model();
      }
    

    Anschließend definieren wir die Funktion, die für das Abrufen der Eingabedaten zum Trainieren unseres Modells verantwortlich ist. Es ist wichtig zu beachten, dass der Input des Modells aus dem aktuellen Zustand des Marktes besteht — insbesondere aus der Zone, in der sich der Markt derzeit befindet. Das Modell prognostiziert dann, in welche Zone sich der Markt als Nächstes bewegen wird.

    //+------------------------------------------------------------------+
    //|This function will fetch the inputs for our model                 |
    //+------------------------------------------------------------------+
    void fetch_input_data(int f_start,int f_fetch)
      {
    //--- This function will fetch input data for our model   Print("Fetching input data");
    //--- The input for our model will be the current state of the market
    //--- To know the current state of the market, we have to first update our indicator readings
       bband_mid_reading.CopyIndicatorBuffer(bband_handler,0,f_start,f_fetch);
       bband_high_reading.CopyIndicatorBuffer(bband_handler,1,f_start,f_fetch);
       bband_low_reading.CopyIndicatorBuffer(bband_handler,2,f_start,f_fetch);
       vector historical_prices;
       historical_prices.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_start,f_fetch);
    //--- Reshape the input data
       input_data.Resize(f_fetch);
    //--- Now we will input the state of the market
       for(int i = 0; i < f_fetch;i++)
         {
          //--- Are we above the bollinger bands entirely?
          if(historical_prices[i] > bband_high_reading[i])
            {
             input_data[i] = 1;
            }
    
          //--- Are we between the upper and mid band?
          else
             if((historical_prices[i]  < bband_high_reading[i]) && (historical_prices[i] > bband_mid_reading[i]))
               {
                input_data[i] = 2;
               }
    
             //--- Are we between the mid and lower band?
             else
                if((historical_prices[i]  < bband_mid_reading[i]) && (historical_prices[i]  > bband_low_reading[i]))
                  {
                   input_data[i] = 3;
                  }
    
                //--- Are we below the bollinger bands entirely?
                else
                   if(historical_prices[i]  < bband_low_reading[i])
                     {
                      input_data[i] = 4;
                     }
         }
    //--- Show the input data
       Print(input_data);
      }
    

    Nun benötigen wir eine Funktion zum Abrufen der Ausgabedaten für unser Modell. Diese Aufgabe ist komplizierter als das Abrufen der Eingabedaten. Wir müssen nicht nur die Endzone aufzeichnen, in der der Preis endete, sondern auch verfolgen, wie oft jede Zone der Ausgang war. Diese Zahl ist entscheidend für die spätere Schätzung der Parameter unseres LDA-Modells.

    Von diesem Punkt an sind wir bereit, unser LDA-Modell anzupassen. Für die Anpassung des Modells stehen verschiedene Methoden zur Verfügung; wir werden uns heute auf einen bestimmten Ansatz konzentrieren.

    //+---------------------------------------------------------------------+
    //|Fetch the output data for our model                                  |
    //+---------------------------------------------------------------------+
    void fetch_output_data(int f_start,int f_fetch)
      {
    //--- The output for our model will be the state of the market
    //--- To know the state of the market, we have to first update our indicator readings
       Print("Fetching output data");
       bband_mid_reading.CopyIndicatorBuffer(bband_handler,0,f_start,(f_fetch));
       bband_high_reading.CopyIndicatorBuffer(bband_handler,1,f_start,(f_fetch));
       bband_low_reading.CopyIndicatorBuffer(bband_handler,2,f_start,(f_fetch));
       vector historical_prices;
       historical_prices.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_start,f_fetch);
    //--- First we have to ensure that the class count has been reset
       total_class_count[0] = 0;
       total_class_count[1] = 0;
       total_class_count[2] = 0;
       total_class_count[3] = 0;
    //--- Now we need to resize the matrix
       ArrayResize(training_output_array,f_fetch);
    //--- Now we will input the state of the market to our output vector
       for(int i =0 ; i < f_fetch;i++)
         {
          //--- Are we above the bollinger bands entirely?
          if(historical_prices[i] > bband_high_reading[i])
            {
             training_output_array[i] = 1;
             total_class_count[0] += 1;
            }
    
          //--- Are we between the upper and mid band?
          else
             if((historical_prices[i] < bband_high_reading[i]) && (historical_prices[i] > bband_mid_reading[i]))
               {
                training_output_array[i] = 2;
                total_class_count[1] += 1;
               }
    
             //--- Are we between the mid and lower band?
             else
                if((historical_prices[i] < bband_mid_reading[i]) && (historical_prices[i] > bband_low_reading[i]))
                  {
                   training_output_array[i] = 3;
                   total_class_count[2] += 1;
                  }
    
                //--- Are we below the bollinger bands entirely?
                else
                   if(historical_prices[i] < bband_low_reading[i])
                     {
                      training_output_array[i] = 4;
                      total_class_count[3] += 1;
                     }
         }
    //--- Show the output data
       Print("Final state of output vector");
       ArrayPrint(training_output_array);
    //--- Show the total number of times each class appeared as the target.
       Print(total_class_count);
      }
    

    Das Verfahren ist etwas kompliziert und bedarf einer ausführlichen Erklärung. Zunächst berechnen wir die Gesamtsumme aller Eingabewerte, die den einzelnen Klassen in der Ausgabe entsprechen. Zum Beispiel berechnen wir für jeden Fall, bei dem das Ziel 1 war, die Summe aller Eingabewerte, die einer Ausgabe von 1 entsprechen, und so weiter für jede Ausgabeklasse.  Anschließend wird der Mittelwert von X für jede Klasse berechnet. Bei mehreren Eingaben würden wir den Mittelwert für jede Eingabe berechnen. Anschließend wird die Wahrscheinlichkeit bestimmt, mit der jede Klasse auf der Grundlage der Trainingsdaten als tatsächliches Ziel erscheint. Anschließend wird die Varianz von X für jede Klasse von y berechnet.  Schließlich aktualisieren wir unsere Flags, um den Abschluss des Trainingsverfahrens anzuzeigen.

    //+------------------------------------------------------------------+
    //|Fit the LDA model                                                 |
    //+------------------------------------------------------------------+
    void fit_lda_model(void)
      {
    
    //--- To fit the LDA model, we first need to know the mean value for each our inputs for each of our 4 classes
       double sum_class_one = 0;
       double sum_class_two = 0;
       double sum_class_three = 0;
       double sum_class_four = 0;
    
    //--- In this case we only have 1 input
       for(int i = 0; i < fetch;i++)
         {
          //--- Class 1
          if(training_output_array[i] == 1)
            {
             sum_class_one += input_data[i];
            }
          //--- Class 2
          else
             if(training_output_array[i] == 2)
               {
                sum_class_two += input_data[i];
               }
             //--- Class 3
             else
                if(training_output_array[i] == 3)
                  {
                   sum_class_three += input_data[i];
                  }
                //--- Class 4
                else
                   if(training_output_array[i] == 4)
                     {
                      sum_class_four += input_data[i];
                     }
         }
    //--- Show the sums
       Print("Class 1: ",sum_class_one," Class 2: ",sum_class_two," Class 3: ",sum_class_three," Class 4: ",sum_class_four);
    //--- Calculate the mean value for each class
       mean_values[0] = sum_class_one / fetch;
       mean_values[1] = sum_class_two / fetch;
       mean_values[2] = sum_class_three / fetch;
       mean_values[3] = sum_class_four / fetch;
       Print("Mean values");
       Print(mean_values);
    //--- Now we need to calculate class probabilities
       for(int i=0;i<classes;i++)
         {
          probability_values[i] = total_class_count[i] / fetch;
         }
       Print("Class probability values");
       Print(probability_values);
    //--- Calculating the variance
       Print("Calculating the variance");
    //--- Next we need to calculate the variance of the inputs within each class of y.
    //--- This process can be simplified into 2 steps
    //--- First we calculate the difference of each instance of x from the group mean.
       double squared_difference[4];
       for(int i =0; i < fetch;i++)
         {
          //--- If the output value was 1, find the input value that created the output
          //--- Calculate how far that value is from it's group mean and square the difference
          if(training_output_array[i] == 1)
            {
             squared_difference[0] = MathPow((input_data[i]-mean_values[0]),2);
            }
    
          else
             if(training_output_array[i] == 2)
               {
                squared_difference[1] = MathPow((input_data[i]-mean_values[1]),2);
               }
    
             else
                if(training_output_array[i] == 3)
                  {
                   squared_difference[2] = MathPow((input_data[i]-mean_values[2]),2);
                  }
    
                else
                   if(training_output_array[i] == 4)
                     {
                      squared_difference[3] = MathPow((input_data[i]-mean_values[3]),2);
                     }
         }
    
    //--- Show the squared difference values
       Print("Squared difference value for each output value of y");
       ArrayPrint(squared_difference);
    
    //--- Next we calculate the variance as the average squared difference from the mean
       variance = (1.0/(fetch - 4.0)) * (squared_difference[0] + squared_difference[1] + squared_difference[2] + squared_difference[3]);
       Print("Variance: ",variance);
    
    //--- Update our flags to denote the model has been trained
       model_trained = true;
       training_procedure_running = false;
      }
    //+------------------------------------------------------------------+
    

    Um mit unserem Modell eine Prognose zu erstellen, holen wir uns zunächst die neuesten Inputdaten vom Markt. Mit diesen Eingabedaten berechnen wir die Diskriminanzfunktion für jede mögliche Klasse. Die Klasse mit dem höchsten Diskriminanzfunktionswert ist die vorhergesagte Klasse.

    In MQL5 bieten Arrays eine nützliche Funktion namens ArrayMaximum(), die den Index des größten Wertes in einem 1D-Array zurückgibt. Da Arrays mit einem Null-Index versehen sind, addieren wir 1 zum Ergebnis von ArrayMaximum(), um die vorhergesagte Klasse zu erhalten.

    //+------------------------------------------------------------------+
    //|This function will obtain forecasts from our model                |
    //+------------------------------------------------------------------+
    int model_forecast(void)
      {
    //--- First we need to fetch the most recent input data
       fetch_input_data(0,1);
    //--- Update the current state of the system
       current_state = input_data[0];
    
    //--- We need to calculate the discriminant function for each class
    //--- The predicted class is the one with the largest discriminant function
       Print("Calculating discriminant values.");
       for(int i = 0; i < classes; i++)
         {
          discriminant_values[i] = (input_data[0] * (mean_values[i]/variance) - (MathPow(mean_values[i],2)/(2*variance)) + (MathLog(probability_values[i])));
         }
    
       ArrayPrint(discriminant_values);
       return(ArrayMaximum(discriminant_values) + 1);
      }
    
    

    Nachdem wir mit unserem Modell eine Prognose erhalten haben, müssen wir diese interpretieren und entsprechende Entscheidungen treffen. Wie bereits erwähnt, werden unsere Handelssignale generiert, wenn das Modell vorhersagt, dass sich der Preis in eine andere Zone bewegen wird:

    1. Zeigt die Prognose eine Bewegung von Zone 1 nach Zone 2 an, wird ein Verkaufssignal ausgelöst.
    2. Umgekehrt bedeutet eine Vorhersage des Übergangs von der Zone 4 zur Zone 3 ein Kaufsignal.
    3. Deutet die Prognose jedoch darauf hin, dass der Kurs in derselben Zone verbleibt (z. B. von Zone 1 nach Zone 1), so ist dies kein Einstiegssignal.

    //+--------------------------------------------------------------------+
    //|This function will interpret out model's forecast and execute trades|
    //+--------------------------------------------------------------------+
    void find_entry(void)
      {
    //--- If the model's forecast is not equal to the current state then we are interested
    //--- Otherwise whenever the model forecasts that the state will remain the same
    //--- We are uncertain whether price levels will rise or fall
       if(forecast != current_state)
         {
          //--- If the model forecasts that we will move from a small state to a greater state
          //--- That is from 1 to 2 or from 2 to 4 then that is a down move
          if(forecast > current_state)
            {
             Trade.Sell(minimum_volume * lot_multiple,_Symbol,bid_price,(bid_price + stop_loss_values),(bid_price - stop_loss_values));
            }
    
          //--- Otherwise we have a buy setup
          else
            {
             Trade.Buy(minimum_volume * lot_multiple,_Symbol,ask_price,(ask_price - stop_loss_values),(ask_price +stop_loss_values));
            }
         }
    //--- Otherwise we do not have an entry signal from our model
      }
    

    Schließlich ist die Ereignisbehandlung duch OnTick() für die Verwaltung des Ereignisflusses verantwortlich und stellt sicher, dass wir nur handeln, wenn unser Modell trainiert wurde und unsere anderen Handelsbedingungen erfüllt sind.

    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
    //--- We must always update market data
       update_technical_data();
    //--- First we must ensure our model has been trained
       switch(model_trained)
         {
    
          //--- Our model has been trained
          case(true):
             //--- If we have no open positions, let's obtain a forecast from our model
             if(PositionsTotal() == 0)
               {
                //--- Obtaining a forecast
                forecast = model_forecast();
                Comment("Model forecast: ",forecast);
                //--- Find an entry setup
                find_entry();
               }
             break;
          //--- End of case 1
    
          //--- Our model has not been trained
          default:
             //--- We haven't started the training procedure!
             if(!training_procedure_running)
               {
                Print("Our model has not been trained, starting the training procedure now.");
                //--- Initialize the model
                model_initialize();
               }
    
             break;
             //--- End of default case
         }
      }
    //+------------------------------------------------------------------+
    

    Unser System in Aktion

    Abb. 11: Unser Handelssystem in Aktion.

    Beschränkungen

    Bis zu diesem Punkt hat unsere Strategie eine wesentliche Einschränkung: Sie kann schwierig zu interpretieren sein. Wenn unser Modell vorhersagt, dass der Preis in der gleichen Zone bleiben wird, ist nicht klar, ob die Preise steigen oder fallen werden. Dieser Kompromiss ergibt sich aus unserer Entscheidung, die Marktzustände in vier verschiedene Zonen einzuteilen, was die Genauigkeit erhöht, aber die Transparenz im Vergleich zur direkten Vorhersage von Preisbewegungen beeinträchtigt. Außerdem erzeugt dieser Ansatz weniger Handelssignale, weil wir warten müssen, bis das Modell eine Zonenänderung vorhersagt, bevor wir handeln.


    Schlussfolgerung

    Zusammenfassend lässt sich sagen, dass unsere Strategie die Leistungsfähigkeit des maschinellen Lernens, insbesondere der linearen Diskriminanzanalyse (LDA), integriert mit Bollinger-Bändern für Handelssignale nutzt. Unser Ansatz bietet zwar eine höhere Genauigkeit, geht aber zu Lasten der Transparenz. Alles in allem sind Händler besser dran, wenn sie Preisänderungen vorhersagen, als wenn sie Durchbrüche von Bollinger-Bänder vorhersagen.

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

    Vom Neuling zum Experten: Die wesentliche Reise durch den MQL5-Handel Vom Neuling zum Experten: Die wesentliche Reise durch den MQL5-Handel
    Entfalten Sie Ihr Potenzial! Sie sind von Möglichkeiten umgeben. Entdecken Sie die 3 wichtigsten Geheimnisse, um Ihre MQL5-Reise in Gang zu bringen oder auf die nächste Stufe zu heben. Lassen Sie uns in die Diskussion über Tipps und Tricks für Anfänger und Profis gleichermaßen eintauchen.
    MQL5 Handels-Toolkit (Teil 2): Erweiterung und Implementierung der Positionsmanagement EX5-Bibliothek MQL5 Handels-Toolkit (Teil 2): Erweiterung und Implementierung der Positionsmanagement EX5-Bibliothek
    Erfahren Sie, wie Sie EX5-Bibliotheken in Ihren MQL5-Code oder Ihre Projekte importieren und verwenden können. In diesem Fortsetzungsartikel werden wir die EX5-Bibliothek erweitern, indem wir weitere Positionsmanagement-Funktionen zur bestehenden Bibliothek hinzufügen und zwei Expert Advisors erstellen. Im ersten Beispiel wird der Variable Index Dynamic Average Technical Indicator verwendet, um einen Expert Advisor für eine Trailing-Stop-Handelsstrategie zu entwickeln, während im zweiten Beispiel ein Handelspanel zum Überwachen, Öffnen, Schließen und Ändern von Positionen verwendet wird. Diese beiden Beispiele zeigen, wie die erweiterte EX5-Positionsmanagement-Bibliothek verwendet und implementiert werden kann.
    MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 28): GANs überarbeitet mit einer Anleitung zu Lernraten MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 28): GANs überarbeitet mit einer Anleitung zu Lernraten
    Die Lernrate ist eine Schrittgröße in Richtung eines Trainingsziels in den Trainingsprozessen vieler maschineller Lernalgorithmen. Wir untersuchen die Auswirkungen, die die vielen Zeitpläne und Formate auf die Leistung eines Generative Adversarial Network haben können, eine Art neuronales Netz, das wir in einem früheren Artikel untersucht haben.
    Datenwissenschaft und ML (Teil 27): Convolutional Neural Networks (CNNs) in MetaTrader 5 Trading Bots — funktioniert das? Datenwissenschaft und ML (Teil 27): Convolutional Neural Networks (CNNs) in MetaTrader 5 Trading Bots — funktioniert das?
    Faltende neuronale Netzwerke (Convolutional Neural Networks, CNN) sind für ihre Fähigkeiten bei der Erkennung von Mustern in Bildern und Videos bekannt und werden in den verschiedensten Bereichen eingesetzt. In diesem Artikel untersuchen wir das Potenzial von CNNs zur Erkennung wertvoller Muster auf den Finanzmärkten und zur Erzeugung effektiver Handelssignale für MetaTrader 5-Handelsroboter. Lassen Sie uns herausfinden, wie diese tiefgehende maschinelle Lerntechnik für intelligentere Handelsentscheidungen genutzt werden kann.