English Русский 中文 日本語
preview
Klassische Strategien neu interpretieren (Teil X): Kann KI den MACD verbessern?

Klassische Strategien neu interpretieren (Teil X): Kann KI den MACD verbessern?

MetaTrader 5Experten | 13 Januar 2025, 10:15
159 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Die sich kreuzenden gleitende Durchschnitte ist wahrscheinlich eine der ältesten existierenden Handelsstrategie. Der MACD (Moving Average Convergence Divergence) ist ein sehr beliebter Indikator, der auf dem Konzept sich kreuzender gleitende Durchschnitte basiert. Es gibt viele neue Mitglieder unserer Gemeinschaft, die sich für die Vorhersagekraft des MACD-Indikators interessieren, um die bestmögliche Handelsstrategie zu entwickeln. Darüber hinaus gibt es erfahrene technische Analysten, die den MACD in ihren Strategien verwenden und sich vielleicht die gleiche Frage stellen. In diesem Artikel finden Sie eine empirische Analyse der Vorhersagekraft des Indikators für das Währungspaar EURUSD. Darüber hinaus vermitteln wir Ihnen Modellierungstechniken, mit denen Sie Ihre technische Analyse durch KI verbessern können.


Überblick über die Handelsstrategie

Der MACD-Indikator wird in erster Linie verwendet, um Markttrends zu erkennen und die Trenddynamik zu messen. Der Indikator wurde in den 1970er Jahren von dem bereits verstorbenen Gerrald Appel entwickelt. Appel war ein Vermögensverwalter für seine Privatkunden, und sein Erfolg beruhte auf seinem Handelsansatz der technischen Analyse. Er erfand den MACD-Indikator vor etwa 50 Jahren.

Abb. 1: Gerald Appel, der Erfinder des MACD-Indikators

Technische Analysten verwenden den Indikator, um auf verschiedene Weise Ein- und Ausstiegspunkte zu identifizieren. Abb. 2 zeigt einen Screenshot des MACD-Indikators, der mit den Standardeinstellungen auf das Paar GBPUSD angewendet wird. Der Indikator ist standardmäßig in Ihrer Installation von MetaTrader 5 enthalten. Die rote Linie, der so genannte MACD-Hauptteil, wird aus der Differenz zwischen zwei gleitenden Durchschnitten, einem schnellen und einem langsamen, berechnet. Wenn die Hauptlinie unter 0 kreuzt, befindet sich der Markt höchstwahrscheinlich in einem Abwärtstrend, und das Gegenteil ist der Fall, wenn die Linie über 0 kreuzt.

Ebenso kann auch die Hauptlinie selbst zur Ermittlung der Marktstärke herangezogen werden. Nur ein steigendes Preisniveau führt zu einer Erhöhung des Wertes der Hauptlinie, und umgekehrt führt ein sinkendes Preisniveau zu einem Rückgang der Hauptlinie. Die Wendepunkte, an denen die Hauptlinie die Form einer Tasse annimmt, werden also durch eine Verschiebung der Marktdynamik verursacht. Um den MACD herum sind verschiedene Handelsstrategien entwickelt worden. Komplexere und ausgefeiltere Strategien versuchen, MACD-Divergenzen zu erkennen.

MACD-Divergenzen treten auf, wenn sich die Kurse in einem starken Trend erholen und neue Extremwerte erreichen. Andererseits befindet sich der MACD-Indikator in einem Trend, der nur noch flacher wird und in scharfem Kontrast zu den starken Kursbewegungen auf dem Chart zu fallen beginnt. In der Regel wird die MACD-Divergenz als frühzeitige Warnung vor einer Trendumkehr interpretiert, die es Händlern ermöglicht, ihre offenen Positionen zurückzunehmen, bevor die Märkte volatiler werden.

Abb. 2: Der MACD-Indikator mit seinen Standardeinstellungen auf dem M1-Chart des GBPUSD

Es gibt viele Skeptiker, die die Verwendung des MACD-Indikators insgesamt in Frage stellen. Lassen Sie uns damit beginnen, den Elefanten im Raum anzusprechen. Alle technischen Indikatoren sind der Gruppe von nachlaufende Indikatoren zugehörig. Das bedeutet, dass sich die technischen Indikatoren nur ändern, wenn sich die Preisniveaus ändern, sie können sich nicht vor den Preisniveaus ändern. Makroökonomische Indikatoren wie das weltweite Inflationsniveau und geopolitische Nachrichten wie der Ausbruch eines Krieges oder einer Naturkatastrophe können die Angebots- und Nachfrageniveaus ausgleichen. Sie gelten als Frühindikatoren, weil sie sich schnell ändern können, bevor die Preisniveaus diese Änderung widerspiegeln.

Viele Händler sind der Meinung, dass diese verzögerten Signale die Händler höchstwahrscheinlich dazu veranlassen werden, ihre Positionen erst dann einzugehen, wenn die Marktbewegung bereits erschöpft ist. Darüber hinaus ist es üblich, Trendumkehrungen zu beobachten, denen keine MACD-Divergenzen vorausgegangen sind. Im gleichen Sinne kann auch eine MACD-Divergenz beobachtet werden, auf die keine Trendumkehr folgt.

Diese Tatsachen werfen die Frage auf, wie zuverlässig der Indikator ist und ob er wirklich eine nennenswerte Vorhersagekraft hat. Wir wollen prüfen, ob es möglich ist, die dem Indikator innewohnende Verzögerung mit Hilfe von KI zu überwinden. Wenn sich der MACD-Indikator als belastbar erweist, werden wir ein KI-Modell integrieren, das entweder:

  1. Verwendet die Indikatorwerte zur Prognose zukünftiger Preisniveaus.
  2. Prognostiziert den MACD-Indikator selbst.

Je nachdem, welcher Modellierungsansatz einen geringeren Fehler ergibt. Andernfalls, wenn unsere Analyse ergibt, dass der MACD im Rahmen unserer derzeitigen Strategie keine Vorhersagekraft besitzt, werden wir uns bei der Vorhersage der Kursniveaus für das Modell mit der besten Leistung entscheiden.


Überblick über die Methodik

Unsere Analyse begann mit einem angepassten Skript, das in MQL5 geschrieben wurde, um genau 100 000 M1-Marktnotierungen für den EURUSD und die entsprechenden MACD-Signal- und Hauptwerte in eine CSV-Datei zu übertragen. Nach unseren Datenvisualisierungen zu urteilen, scheint der MACD-Indikator ein schlechter Indikator für zukünftige Kursniveaus zu sein. Die Änderungen des Preisniveaus sind höchstwahrscheinlich unabhängig vom Wert des Indikators, außerdem haben die Daten durch die Berechnung des Indikators eine nichtlineare und komplexe Struktur, die schwierig zu modellieren sein kann.

Die Daten, die wir von unserem MetaTrader 5 Terminal erhalten haben, wurden in 2 Hälften aufgeteilt. Wir haben die erste Hälfte verwendet, um die Genauigkeit unseres Modells mithilfe einer 5-fachen Kreuzvalidierung zu schätzen. Anschließend erstellten wir 3 identische Deep Neural Network-Modelle und trainierten sie auf 3 verschiedenen Teilmengen unserer Daten:

  1. Preismodell:  Prognostizieren von Preisniveaus mit OHLC-Marktkursen aus MetaTrader 5
  2. MACD-Modell: Prognostizieren der Werte des MACD-Indikators anhand von OHLC-Kursen und dem MACD-Wert
  3. Vollständiges Modell: Vorhersage von Kursniveaus anhand von OHLC-Kursen und dem MACD-Indikator

Die zweite Hälfte der Partition wurde zum Testen der Modelle verwendet. Das erste Modell erzielte mit 69 % die höchste Genauigkeit im Test. Unsere Algorithmen zur Auswahl von Merkmalen legten nahe, dass die Marktkurse, die wir aus dem MetaTrader 5 bezogen, informativer waren als die MACD-Werte.

So begannen wir mit der Optimierung des besten Modells, das uns zur Verfügung stand: ein Regressionsmodell, das den zukünftigen Kurs des EURUSD-Paares vorhersagt. Wir sind jedoch schnell auf Probleme gestoßen, weil wir das Rauschen in unseren Trainingsdaten gelernt haben. Es ist uns nicht gelungen, eine einfache lineare Regression in der Testgruppe zu übertreffen. Daher haben wir das überoptimierte Modell durch eine Support Vector Machine (SVM) ersetzt.

Anschließend exportierten wir unser SVM-Modell in das ONNX-Format und erstellten einen Expert Advisor, der einen kombinierten Ansatz zur Vorhersage künftiger EURUSD-Kurse und des MACD-Indikators verwendet.


Abrufen der benötigten Daten

Um den Ball ins Rollen zu bringen, war unsere erste Station die integrierte Entwicklungsumgebung (IDE) MetaEditor. Wir haben das nachstehende Skript erstellt, um unsere Marktdaten vom MetaTrader 5-Terminal abzurufen. Wir haben 100 000 Zeilen historischer M1-Daten angefordert und in das CSV-Format exportiert. Das folgende Skript füllt unsere CSV-Datei mit den Werten für Time, Open, High, Low, Close und den 2 MACD. Ziehen Sie das Skript einfach per Drag & Drop auf ein beliebiges Paar, das Sie analysieren möchten, wenn Sie uns folgen wollen.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| Script Inputs                                                    |
//+------------------------------------------------------------------+
input int size = 100000; //How much data should we fetch?

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int indicator_handler;
double indicator_buffer[];
double indicator_buffer_2[];

//+------------------------------------------------------------------+
//| On start function                                                |
//+------------------------------------------------------------------+
void OnStart()
  {

//--- Load indicator
   indicator_handler = iMACD(Symbol(),PERIOD_CURRENT,12,26,9,PRICE_CLOSE);
   CopyBuffer(indicator_handler,0,0,size,indicator_buffer);
   CopyBuffer(indicator_handler,1,0,size,indicator_buffer_2);
   ArraySetAsSeries(indicator_buffer,true);
   ArraySetAsSeries(indicator_buffer_2,true);

//--- File name
   string file_name = "Market Data " + Symbol() +" MACD " +  ".csv";

//--- Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i= size;i>=0;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","Open","High","Low","Close","MACD Main","MACD Signal");
        }

      else
        {
         FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                   iOpen(Symbol(),PERIOD_CURRENT,i),
                   iHigh(Symbol(),PERIOD_CURRENT,i),
                   iLow(Symbol(),PERIOD_CURRENT,i),
                   iClose(Symbol(),PERIOD_CURRENT,i),
                   indicator_buffer[i],
                   indicator_buffer_2[i]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Vorverarbeitung von Daten

Nachdem wir unsere Daten in das CSV-Format exportiert haben, können wir die Daten nun in unseren Python-Arbeitsbereich einlesen. Zuerst laden wir die benötigten Bibliotheken.

#Load libraries
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

Wir lesen die Daten ein

#Read in the data
data = pd.read_csv("Market Data EURUSD MACD .csv")

und legen fest, wie weit wir in die Zukunft vorausschauen sollen.

#Forecast horizon
look_ahead = 20

Fügen wir jetzt binäre Ziele hinzu, die anzeigen, ob der aktuelle Messwert größer ist als die 20 vorangegangenen, sowohl für den EURUSD-Schlusskurs als auch für die MACD-Hauptlinie.

#Let's add labels
data["Bull Bear"] = np.where(data["Close"] < data["Close"].shift(look_ahead),0,1)
data["MACD Bull"] = np.where(data["MACD Main"] < data["MACD Main"].shift(look_ahead),0,1)

data = data.loc[20:,:]

data

Abb. 3: Einige der Spalten in unserem Datenrahmen

Außerdem müssen wir unsere Zielwerte festlegen.

data["MACD Target"] = data["MACD Main"].shift(-look_ahead)
data["Price Target"] = data["Close"].shift(-look_ahead)

data["MACD Binary Target"] = np.where(data["MACD Main"] < data["MACD Target"],1,0)
data["Price Binary Target"] = np.where(data["Close"] < data["Price Target"],1,0)

data = data.iloc[:-20,:]


Explorative Datenanalyse

Streudiagramme helfen uns, die Beziehung zwischen einer abhängigen und einer unabhängigen Variable zu visualisieren. Die nachstehende Grafik zeigt, dass es definitiv eine Beziehung zwischen zukünftigen Kursniveaus und dem aktuellen MACD-Wert gibt. Die Herausforderung besteht darin, dass die Beziehung nicht linear ist und eine komplexe Struktur zu haben scheint. Es ist nicht sofort ersichtlich, welche Veränderungen des MACD-Indikators zu einer steigenden oder fallenden Kursentwicklung führen.

sns.scatterplot(data=data,x="MACD Main",y="MACD Signal",hue="Price Binary Target")

Abb. 4: Visualisierung der Beziehung zwischen dem MACD-Indikator und den Kursniveaus

Eine 3D-Darstellung macht nur noch deutlicher, wie verworren die Beziehung wirklich ist. Es gibt keine definierten Grenzen, sodass wir erwarten, dass die Daten schwer zu klassifizieren sein werden. Die einzigen intelligenten Schlussfolgerungen, die wir aus unserem Diagramm ziehen können, sind, dass sich die Märkte nach dem Durchschreiten extremer Niveaus auf dem MACD schnell wieder in der Mitte zu sammeln scheinen.

#Define the 3D Plot
fig = plt.figure(figsize=(7,7))
ax = plt.axes(projection="3d")
ax.scatter(data["MACD Main"],data["MACD Signal"],data["Close"],c=data["Price Binary Target"])
ax.set_xlabel("MACD Main")
ax.set_ylabel("MACD Signal")
ax.set_zlabel("EURUSD Close")

Abb. 5: Visualisierung der Interaktion zwischen dem MACD-Indikator und dem EURUSD-Markt

Mit Hilfe von Violinplots können wir gleichzeitig die Verteilung von Daten visualisieren und zwei Verteilungen vergleichen. Der blaue Umriss stellt eine Zusammenfassung der beobachteten Verteilung zukünftiger Kursniveaus dar, nachdem der MACD angestiegen oder gefallen ist. In der folgenden Abbildung 6 wollten wir herausfinden, ob das Steigen oder Fallen des MACD-Indikators mit unterschiedlichen Verteilungen in Bezug auf künftige Kursbewegungen verbunden ist. Wie wir sehen können, sind die beiden Verteilungen fast identisch. Darüber hinaus hat der Kern jeder Verteilung ein Boxplot. Die Mittelwerte beider Boxplots sind nahezu identisch, unabhängig davon, ob sich der Indikator in einer Hausse- oder Baisse-Phase befindet.

sns.violinplot(data=data,x="MACD Bull",y="Close",hue="Price Binary Target",split=True,fill=False)

Abb. 6: Die Visualisierung der Auswirkungen des MACD-Indikators auf künftige Kursniveaus



Vorbereiten der Datenmodellierung

Beginnen wir nun mit der Modellierung unserer Daten. Zuallererst müssen wir unsere Bibliotheken importieren.

#Perform train test splits
from sklearn.model_selection import train_test_split,TimeSeriesSplit
from sklearn.metrics import accuracy_score
train,test = train_test_split(data,test_size=0.5,shuffle=False)

Jetzt werden wir die Prädiktoren und das Ziel definieren.

#Let's scale the data
ohlc_predictors = ["Open","High","Low","Close","Bull Bear"]
macd_predictors = ["MACD Main","MACD Signal","MACD Bull"]
all_predictors  = ohlc_predictors + macd_predictors
cv_predictors   = [ohlc_predictors,macd_predictors,all_predictors]

#Define the targets
cv_targets = ["MACD Binary Target","Price Binary Target","All"]

Skalierung der Daten.

#Scaling the data
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(train[all_predictors])
train_scaled = pd.DataFrame(scaler.transform(train[all_predictors]),columns=all_predictors)
test_scaled = pd.DataFrame(scaler.transform(test[all_predictors]),columns=all_predictors)

Laden wir die benötigten Bibliotheken

#Import the models we will evaluate
from sklearn.neural_network import MLPClassifier,MLPRegressor
from sklearn.linear_model import LinearRegression

und jetzt erstellen wir das geteilte Zeitreihenobjekt.

tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead)

Die Indizes unseres Datenrahmens werden auf den Satz von Eingaben abgebildet, die wir ausgewertet haben.

err_indexes = ["MACD Train","Price Train","All Train","MACD Test","Price Test","All Test"]

Nun werden wir den Datenrahmen erstellen, der unsere Schätzungen der Genauigkeit des Modells aufzeichnet, wenn wir unsere Eingaben ändern.

#Now let us define a table to store our error levels
columns = ["Model Accuracy"]

cv_err = pd.DataFrame(columns=columns,index=err_indexes)

Wir setzen alle unsere Indizes zurück

#Reset index
train = train.reset_index(drop=True)
test = test.reset_index(drop=True)

und führe eine Kreuzvalidierung des Modells durch. Wir machen das auf der Trainingsmenge, um dann seine Genauigkeit auf der Testmenge zu erfassen, ohne es an die Testmenge anzupassen.

#Initailize the model
price_model = MLPClassifier(hidden_layer_sizes=(10,6))
macd_model  = MLPClassifier(hidden_layer_sizes=(10,6))
all_model   = MLPClassifier(hidden_layer_sizes=(10,6))

price_acc = []
macd_acc = []
all_acc = []

#Cross validate each model twice
for j,(train_index,test_index) in enumerate(tscv.split(train_scaled)):
  #Fit the models
  price_model.fit(train_scaled.loc[train_index,ohlc_predictors],train.loc[train_index,"Price Binary Target"])
  macd_model.fit(train_scaled.loc[train_index,all_predictors],train.loc[train_index,"MACD Binary Target"])
  all_model.fit(train_scaled.loc[train_index,all_predictors],train.loc[train_index,"Price Binary Target"])
  #Store the accuracy
  price_acc.append(accuracy_score(train.loc[test_index,"Price Binary Target"],price_model.predict(train_scaled.loc[test_index,ohlc_predictors])))
  macd_acc.append(accuracy_score(train.loc[test_index,cv_targets[0]],macd_model.predict(train_scaled.loc[test_index,all_predictors])))
  all_acc.append(accuracy_score(train.loc[test_index,cv_targets[1]],all_model.predict(train_scaled.loc[test_index,all_predictors])))

#Now we can store our estimates of the model's error
cv_err.iloc[0,0] = np.mean(price_acc)
cv_err.iloc[1,0] = np.mean(macd_acc)
cv_err.iloc[2,0] = np.mean(all_acc)
#Estimating test error
cv_err.iloc[3,0] = accuracy_score(test[cv_targets[1]],price_model.predict(test_scaled[ohlc_predictors]))
cv_err.iloc[4,0] = accuracy_score(test[cv_targets[0]],macd_model.predict(test_scaled[all_predictors]))
cv_err.iloc[5,0] = accuracy_score(test[cv_targets[1]],all_model.predict(test_scaled[all_predictors]))
 Eingabe Gruppe
 Modell-Genauigkeit
MACD Train
0.507129 
OHLC Train
0.690267
All Train
0.504577
MACD Test
0.48669
OHLC Test
0.684069
Alle Tests
0.487442


Die Bedeutung der Merkmale

Versuchen wir nun, die Wichtigkeit der Merkmale für unser tiefes neuronales Netz zu schätzen. Wir werden die Bedeutung der Permutation für die Interpretation unseres Modells wählen. Die Permutationsbedeutung definiert die Bedeutung jeder Eingabe, indem zunächst die Werte dieser Eingabespalte gemischt werden und dann die Änderungen der Modellgenauigkeit bewertet werden. Der Gedanke dahinter ist, dass wichtige Merkmale einen starken Rückgang des Fehlers bewirken, während unwichtige Merkmale Änderungen in der Genauigkeit des Modells verursachen, die nahe bei 0 liegen.

Es sind jedoch einige Überlegungen anzustellen. Zunächst mischt der Permutations-Bedeutungs-Algorithmus die einzelnen Eingaben des Modells nach dem Zufallsprinzip. Das bedeutet, dass der Algorithmus den Eröffnungskurs nach dem Zufallsprinzip mischen und ihn höher als den Höchstkurs setzen kann. Dies ist in der realen Welt natürlich nicht möglich. Daher sollten wir die Ergebnisse des Algorithmus mit Vorsicht interpretieren. Man könnte sagen, dass der Algorithmus voreingenommen ist, weil er die Bedeutung von Merkmalen unter simulierten Bedingungen bewertet, die möglicherweise nie eintreten werden, wodurch das Modell unnötig benachteiligt wird. Aufgrund der stochastischen Natur der Optimierungsalgorithmen, die zur Anpassung moderner neuronaler Netze verwendet werden, kann das Training derselben neuronalen Netze mit demselben Datensatz jedes Mal zu bemerkenswert unterschiedlichen Erklärungen führen.

#Let us try assess feature importance
from sklearn.inspection import permutation_importance
from sklearn.linear_model import RidgeClassifier

Wir werden nun unser Permutations-Bedeutungsobjekt an unser trainiertes tiefes neuronales Netzwerkmodell anpassen. Sie haben die Möglichkeit, die zu mischenden Trainings- oder Testdaten zu übergeben. Wir haben uns für die Testdaten entschieden. Anschließend ordneten wir die Daten nach der Reihenfolge des verursachten Genauigkeitsverlusts und stellten die Ergebnisse grafisch dar. Abb. 7 zeigt die beobachteten Werte für die Wichtigkeit von Permutationen. Wir können sehen, dass die Auswirkungen des Umschichtens der Eingaben in Bezug auf den MACD sehr nahe bei 0 liegen, was bedeutet, dass die MACD-Spalten nicht so wichtig für unser Modell sind.

#Let us fit the model
model   = MLPClassifier(hidden_layer_sizes=(10,6))
model.fit(train_scaled.loc[:,all_predictors],train.loc[:,"Price Binary Target"])

#Calculate permutation importance scores
pi = permutation_importance(
    model, test_scaled.loc[:,all_predictors], test.loc[:,"Price Binary Target"], n_repeats=10, random_state=42, n_jobs=-1
)

#Sort the importance scores
sorted_importances_idx = pi.importances_mean.argsort()
importances = pd.DataFrame(
    pi.importances[sorted_importances_idx].T,
    columns=test_scaled.columns[sorted_importances_idx],
)

#Create the plot
ax = importances.plot.box(vert=False, whis=10)
ax.set_title("Permutation Importances (test set)")
ax.axvline(x=0, color="k", linestyle="--")
ax.set_xlabel("Decrease in accuracy score")
ax.figure.tight_layout()

Abb. 7: Unsere Permutationsbewertung ergab, dass der Schlusskurs das wichtigste Merkmal ist.

Die Anpassung eines einfacheren Modells könnte uns auch Aufschluss über die Bedeutung der Inputs geben. Der Ridge Classifier ist ein lineares Modell, das seine Koeffizienten in der Richtung, in der es seinen Fehler minimiert, immer näher an 0 heranschiebt. Unter der Annahme, dass Ihre Daten standardisiert und skaliert wurden, haben unwichtige Merkmale daher die kleinsten Ridge-Koeffizienten. Falls es Sie interessiert: Der Ridge-Klassifikator kann dies erreichen, indem er das gewöhnliche lineare Modell um einen Strafterm erweitert, der proportional zur quadrierten Summe der Modellkoeffizienten ist. Dies ist allgemein als L2-Regularisierung bekannt.

#Let us fit the model
model   = RidgeClassifier()
model.fit(train_scaled.loc[:,all_predictors],train.loc[:,"Price Binary Target"])

Nun wollen wir die Koeffizienten des Modells aufzeichnen.

ridge_importance = pd.DataFrame(model.coef_.tolist(),columns=all_predictors)

#Prepare the plot
fig,ax = plt.subplots(figsize=(10,5))
sns.barplot(ridge_importance,ax=ax)

Abb. 8: Unsere Ridge-Koeffizienten deuten darauf hin, dass der hohe und der niedrige Preis die informativsten Merkmale sind, die wir haben



Einstellen der Parameter

Nun werden wir versuchen, unser leistungsstärkstes Modell zu optimieren. Doch wie wir bereits festgestellt haben, war unsere Optimierungsroutine in diesem Zug nicht erfolgreich. Leider liegt dies in der Natur von Optimierungsalgorithmen, wir haben keine Garantie, Lösungen zu finden. Die Optimierung der Parameter bedeutet nicht zwangsläufig, dass das Modell, das Sie am Ende erhalten, besser ist; wir versuchen lediglich, uns den optimalen Modellparametern zu nähern. Laden wir die benötigten Bibliotheken

#Let's tune our model further
from sklearn.model_selection import RandomizedSearchCV

Definition des Modells.

#Reinitialize the model
model  = MLPRegressor(max_iter=200)

Jetzt werden wir das Tuner-Objekt definieren. Das Objekt bewertet unser Modell unter verschiedenen Initialisierungsparametern und gibt ein Objekt zurück, das die besten gefundenen Eingaben enthält.

#Define the tuner
tuner = RandomizedSearchCV(
        model,
        {
        "activation" : ["relu","logistic","tanh","identity"],
        "solver":["adam","sgd","lbfgs"],
        "alpha":[0.1,0.01,0.001,0.0001,0.00001,0.00001,0.0000001],
        "tol":[0.1,0.01,0.001,0.0001,0.00001,0.000001,0.0000001],
        "learning_rate":['constant','adaptive','invscaling'],
        "learning_rate_init":[0.1,0.01,0.001,0.0001,0.00001,0.000001,0.0000001],
        "hidden_layer_sizes":[(2,4,8,2),(10,20),(5,10),(2,20),(6,8,10),(1,5),(20,10),(8,4),(2,4,8),(10,5)],
        "early_stopping":[True,False],
        "warm_start":[True,False],
        "shuffle": [True,False]
        },
        n_iter=100,
        cv=5,
        n_jobs=-1,
        scoring="neg_mean_squared_error"
)

Anpassen des „Tuner“-Objekts.

tuner.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])

Die besten Parameter, die wir gefunden haben.

tuner.best_params_
{'warm_start': False,
 'tol': 0.01,
 'solver': 'sgd',
 'shuffle': False,
 'learning_rate_init': 0.01,
 'learning_rate': 'constant',
 'hidden_layer_sizes': (20, 10),
 'early_stopping': True,
 'alpha': 1e-07,
 'activation': 'identity'}


Tiefergehende Optimierung

Mit Hilfe der SciPy-Bibliothek können wir noch tiefer nach besseren Eingabeeinstellungen suchen.  Wir werden die Bibliothek verwenden, um die Ergebnisse der globalen Optimierung der kontinuierlichen Parameter des Modells zu schätzen.
#Deeper optimization
from scipy.optimize import minimize
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import TimeSeriesSplit

Wir definieren das Objekt für die Aufteilung der Zeitserie

#Define the time series split object
tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead)

und erstellen Datenstrukturen, um unsere Genauigkeitsstufen zu speichern.

#Create a dataframe to store our accuracy
current_error_rate = pd.DataFrame(index = np.arange(0,5),columns=["Current Error"])
algorithm_progress = []

Unsere Kostenfunktion, die es zu minimieren gilt, sind die Fehlerwerte des Modells bei den Trainingsdaten.

#Define the objective function
def objective(x):
    #The parameter x represents a new value for our neural network's settings
    model = MLPRegressor(hidden_layer_sizes=tuner.best_params_["hidden_layer_sizes"],
                         early_stopping=tuner.best_params_["early_stopping"],
                         warm_start=tuner.best_params_["warm_start"],
                         max_iter=500,
                         activation=tuner.best_params_["activation"],
                         learning_rate=tuner.best_params_["learning_rate"],
                         solver=tuner.best_params_["solver"],
                         shuffle=tuner.best_params_["shuffle"],
                         alpha=x[0],
                         tol=x[1],
                         learning_rate_init=x[2]
                         )
    #Now we will cross validate the model
    for i,(train_index,test_index) in enumerate(tscv.split(train)):
        #Train the model
        model.fit(train.loc[train_index,ohlc_predictors],train.loc[train_index,"Price Target"])
        #Measure the RMSE
        current_error_rate.iloc[i,0] = mean_squared_error(train.loc[test_index,"Price Target"],model.predict(train.loc[test_index,ohlc_predictors]))
    #Store the algorithm's progress
    algorithm_progress.append(current_error_rate.iloc[:,0].mean())
    #Return the Mean CV RMSE
    return(current_error_rate.iloc[:,0].mean())

SciPy erwartet, dass wir es mit Anfangswerten versorgen, um das Optimierungsverfahren zu starten.

#Define the starting point
pt = [tuner.best_params_["alpha"],tuner.best_params_["tol"],tuner.best_params_["learning_rate_init"]]
bnds = ((10.00 ** -100,10.00 ** 100),
        (10.00 ** -100,10.00 ** 100),
        (10.00 ** -100,10.00 ** 100))

Wir wollen nun versuchen, das Modell zu optimieren.

#Searching deeper for parameters
result = minimize(objective,pt,method="L-BFGS-B",bounds=bnds)

Es scheint, dass der Algorithmus konvergieren konnte. Das bedeutet, dass er stabile Inputs mit geringer Varianz gefunden hat. Er kam daher zu dem Schluss, dass es keine besseren Lösungen gibt, da sich die Änderungen der Fehlerquoten gegen 0 bewegten.

#The result of our optimization
result

 message: CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH
  success: True
   status: 0
      fun: 3.730365831424036e-06
        x: [ 9.939e-08  9.999e-03  9.999e-03]
      nit: 3
      jac: [-7.896e+01 -1.133e+02  1.439e+03]
     nfev: 100
     njev: 25
 hess_inv: <3x3 LbfgsInvHessProduct with dtype=float64>

Veranschaulichen wir uns das Verfahren.

#Store the optimal coefficients
optimal_weights = result.x
optima_y = min(algorithm_progress)
optima_x = algorithm_progress.index(optima_y)
inputs = np.arange(0,len(algorithm_progress))

#Plot the performance of our optimization procedure
plt.scatter(inputs,algorithm_progress)
plt.plot(optima_x,optima_y,'ro',color='r')
plt.axvline(x=optima_x,ls='--',color='red')
plt.axhline(y=optima_y,ls='--',color='red')
plt.xlabel("Iterations")
plt.ylabel("Training MSE")
plt.title("Minimizing Training Error")

Abb. 9: Visualisierung der Optimierung eines tiefen neuronalen Netzes


Test auf Überanpassung

Überanpassung ist ein unerwünschter Effekt, bei dem unser Modell sinnlose Darstellungen aus den Daten lernt, die wir ihm gegeben haben. Dies ist unerwünscht, da ein Modell in diesem Zustand eine schlechte Genauigkeit aufweist. Wir können feststellen, ob unser Modell überangepasst ist, indem wir es mit schwächeren Lernern und Standardinstanzen eines ähnlichen neuronalen Netzes vergleichen. Wenn unser Modell das Rauschen lernt und das Signal in den Daten nicht erkennen kann, wird es von den schwächeren Lernern übertroffen. Aber selbst wenn unser Modell die schwächeren Lerner übertrifft, besteht immer noch die Möglichkeit, dass es sich zu gut anpasst.

#Testing for overfitting
#Benchmark
benchmark = LinearRegression()

#Default
default_nn = MLPRegressor(max_iter=500)

#Randomized NN
random_search_nn = MLPRegressor(hidden_layer_sizes=tuner.best_params_["hidden_layer_sizes"],
                         early_stopping=tuner.best_params_["early_stopping"],
                         warm_start=tuner.best_params_["warm_start"],
                         max_iter=500,
                         activation=tuner.best_params_["activation"],
                         learning_rate=tuner.best_params_["learning_rate"],
                         solver=tuner.best_params_["solver"],
                         shuffle=tuner.best_params_["shuffle"],
                         alpha=tuner.best_params_["alpha"],
                         tol=tuner.best_params_["tol"],
                         learning_rate_init=tuner.best_params_["learning_rate_init"]
                         )

#LBFGS NN
lbfgs_nn = MLPRegressor(hidden_layer_sizes=tuner.best_params_["hidden_layer_sizes"],
                         early_stopping=tuner.best_params_["early_stopping"],
                         warm_start=tuner.best_params_["warm_start"],
                         max_iter=500,
                         activation=tuner.best_params_["activation"],
                         learning_rate=tuner.best_params_["learning_rate"],
                         solver=tuner.best_params_["solver"],
                         shuffle=tuner.best_params_["shuffle"],
                         alpha=result.x[0],
                         tol=result.x[1],
                         learning_rate_init=result.x[2]
                         )

Wir passen die Modelle an und bewerten ihre Genauigkeit. Es ist deutlich zu erkennen, dass das lineare Regressionsmodell alle unsere tiefen neuronalen Netze in den Schatten stellt. Ich beschloss daher, stattdessen eine lineare SVM zu verwenden. Es schnitt besser ab als die neuronalen Netze, aber nicht besser als die lineare Regression.

#Fit the models on the training sets
benchmark = LinearRegression()
benchmark.fit(((train.loc[:,ohlc_predictors])),train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],benchmark.predict(((test.loc[:,ohlc_predictors]))))

#Test the default
default_nn.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],default_nn.predict(test.loc[:,ohlc_predictors]))

#Test the random search
random_search_nn.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],random_search_nn.predict(test.loc[:,ohlc_predictors]))

#Test the lbfgs nn
lbfgs_nn.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],lbfgs_nn.predict(test.loc[:,ohlc_predictors])
Lineare Regression
Standard NN
Zufällige Suche
LBFGS NN
2.609826e-07
1.996431e-05
0.00051
0.000398

Passen wir unsere LinearSVR an, so ist es wahrscheinlicher, dass sie die nichtlinearen Wechselwirkungen in unseren Daten erfasst.

#From experience, I'll try LSVR
from sklearn.svm import LinearSVR

Wir initialisieren das Modell und passen es an alle Daten an, die wir haben. Beachten Sie, dass die Fehlerwerte der SVR besser sind als die des neuronalen Netzes, aber nicht so gut wie die der linearen Regression.

#Initialize the model
lsvr = LinearSVR()

#Fit the Linear Support Vector
lsvr.fit(train.loc[:,["Open","High","Low","Close"]],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],lsvr.predict(test.loc[:,["Open","High","Low","Close"]]))

5.291875e-06


Exportieren nach ONNX

Open Neural Network Exchange (ONNX) ermöglicht es uns, Modelle für maschinelles Lernen in einer Sprache zu erstellen und sie dann mit jeder anderen Sprache, die die ONNX-API unterstützt, zu teilen. Das ONNX-Protokoll verändert rasch die Anzahl der Umgebungen, in denen maschinelles Lernen eingesetzt werden kann. ONNX ermöglicht uns die nahtlose Integration von AI in unseren MQL5 Expert Advisor.

#Let's export the LSVR to ONNX
import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Erstellen wir eine neue Instanz des Modells.

model = LinearSVR()

Passen wir das Modell an alle Daten an, die wir haben.

model.fit(data.loc[:,["Open","High","Low","Close"]],data.loc[:,"Price Target"])

Definieren wir jetzt die Eingabeform des Modells,

#Define the input type
initial_types = [("float_input",FloatTensorType([1,4]))]

erstellen eine ONNX-Darstellung des Modells

#Create the ONNX representation
onnx_model = convert_sklearn(model,initial_types=initial_types,target_opset=12)

und speichern das ONNX-Modell.

# Save the ONNX model
onnx.save_model(onnx_model,"EURUSD SVR M1.onnx")

unser LinearSVR

Abb. 10: Visualisierung unseres ONNX-Modells



Implementierung in MQL5

Wir können nun mit der Umsetzung unserer Strategie in MQL5 beginnen. Wir möchten eine Anwendung entwickeln, die immer dann kauft, wenn der Kurs über dem gleitenden Durchschnitt liegt und die KI einen Kursanstieg vorhersagt.

Um mit unserer Anwendung zu beginnen, werden wir zunächst die gerade erstellte ONNX-Datei in unseren Expert Advisor einbinden.

//+--------------------------------------------------------------+
//| EURUSD AI                                                    |
//+--------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link "https://metaquotes.com/en/users/gamuchiraindawa"
#property version "2.1"
#property description "Supports M1"

//+--------------------------------------------------------------+
//| Resources we need                                            |
//+--------------------------------------------------------------+
#resource "\\Files\\EURUSD SVR M1.onnx" as const uchar onnx_buffer[];

Jetzt müssen wir die Handelsbibliothek laden.

//+--------------------------------------------------------------+
//| Libraries                                                    |
//+--------------------------------------------------------------+
#include  <Trade\Trade.mqh>
CTrade trade;

Definieren Sie einige Konstanten, die sich nun ändern werden.

//+--------------------------------------------------------------+
//| Constants                                                    |
//+--------------------------------------------------------------+
const double  stop_percent = 1;
const int     ma_period_shift = 0;

Wir werden dem Nutzer die Kontrolle über die Parameter der technischen Indikatoren und das allgemeine Verhalten des Programms ermöglichen.

//+--------------------------------------------------------------+
//| User inputs                                                  |
//+--------------------------------------------------------------+
input group "TAs"
input double atr_multiple =2.5;             //How wide should the stop loss be?
input int    atr_period = 200;              //ATR Period
input int    ma_period = 1000;              //Moving average period

input group "Risk"
input double risk_percentage= 0.02;         //Risk percentage (0.01 - 1)
input double profit_target = 1.0;           //Profit target

Definieren wir nun alle globalen Variablen, die wir benötigen.

//+--------------------------------------------------------------+
//| Global variables                                             |
//+--------------------------------------------------------------+
double position_size = 2;
int lot_multiplier = 1;
bool  buy_break_even_setup = false;
bool  sell_break_even_setup = false;
double up_level = 0.03;
double down_level = -0.03;
double min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,ask, bid,atr_stop,mid_point,risk_equity;
double take_profit = 0;
double close_price[3];
double moving_average_low_array[],close_average_reading[],moving_average_high_array[],atr_reading[];
long   min_distance,login;
int    ma_high,ma_low,atr,close_average;
bool   authorized = false;
double tick_value,average_market_move,margin,mid_point_height,channel_width,lot_step;
string currency,server;
bool all_closed =true;
long onnx_model;
vectorf onnx_output = vectorf::Zeros(1);
ENUM_ACCOUNT_TRADE_MODE account_type;

Unser Experte prüft zunächst, ob der Nutzer den Expertenhandel für das Konto aktiviert hat, dann versucht er, das ONNX-Modell zu laden, und schließlich, wenn dies erfolgreich war, laden wir unsere technischen Indikatoren.

//+------------------------------------------------------------------+
//| On initialization                                                |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- Authorization
   if(!auth())
     {
      return(INIT_FAILED);
     }
     
//--- Load the ONNX model
if(!load_onnx())
   {
      return(INIT_FAILED);
   }

//--- Everything went fine
   else
     {
      load();      
      return(INIT_SUCCEEDED);
     }
  }

Wenn unser Advisor nicht nutzt wird, geben wir den dem ONNX-Modell zugewiesenen Speicher frei.

//+------------------------------------------------------------------+
//| On deinitialization                                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
      OnnxRelease(onnx_model);
  }
Immer wenn wir aktualisierte Kursdaten erhalten, aktualisieren wir unsere globalen Marktvariablen und suchen dann nach Handelssignalen, wenn wir keine offenen Positionen haben. Andernfalls aktualisieren wir unseren nachlaufenden (trailing) Stop-Loss.
//+------------------------------------------------------------------+
//| On every tick                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
//On Every Function Call
   update();
   static datetime time_stamp;
   datetime time = iTime(_Symbol,PERIOD_CURRENT,0);
   Comment("AI Forecast: ",onnx_output[0]);

//On Every Candle
   if(time_stamp != time)
     {

      //Mark the candle
      time_stamp = time;

      OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin);
      calculate_lot_size();
      if(PositionsTotal() == 0)
        {
         check_signal();
        }
     }

//--- If we have positions, manage them.
   if(PositionsTotal() > 0)
     {
      check_atr_stop();
      check_profit();
     }
  }


//+------------------------------------------------------------------+
//| Check if we have any valid setups, and execute them              |
//+------------------------------------------------------------------+
void check_signal(void)
  {
  //--- Get a prediction from our model
  model_predict();
     if(onnx_output[0] > iClose(Symbol(),PERIOD_CURRENT,0))
      {
         if(above_channel())
           {
               check_buy();
           }
      }
      
      else
         if(below_channel())
           {
             if(onnx_output[0] < iClose(Symbol(),PERIOD_CURRENT,0))
               {
                  check_sell();
                }
           }
  }

Diese Funktion ist für die Aktualisierung aller unserer globalen Marktvariablen zuständig.

//+------------------------------------------------------------------+
//| Update our global variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Important details that need to be updated everytick
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   buy_stop_loss = 0;
   sell_stop_loss = 0;
   check_price(3);
   CopyBuffer(ma_high,0,0,1,moving_average_high_array);
   CopyBuffer(ma_low,0,0,1,moving_average_low_array);
   CopyBuffer(atr,0,0,1,atr_reading);
   ArraySetAsSeries(moving_average_high_array,true);
   ArraySetAsSeries(moving_average_low_array,true);
   ArraySetAsSeries(atr_reading,true);
   risk_equity = AccountInfoDouble(ACCOUNT_BALANCE) * risk_percentage;
   atr_stop = (((min_distance + (atr_reading[0]* 1e5) * atr_multiple) * _Point));
   mid_point = (moving_average_high_array[0] + moving_average_low_array[0]) / 2;
   mid_point_height = close_price[0] - mid_point;
   channel_width = moving_average_high_array[0] - moving_average_low_array[0];
  }

Jetzt müssen wir die Funktion definieren, die sicherstellt, dass unsere Anwendung ausgeführt werden darf. Wenn sie nicht ausgeführt werden darf, gibt die Funktion dem Nutzer Anweisungen, was zu tun ist, und gibt false zurück, wodurch die Initialisierung abgebrochen wird.

//+------------------------------------------------------------------+
//| Check if the EA is allowed to be run                             |
//+------------------------------------------------------------------+
bool auth(void)
  {
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program");
      return(false);
     }

   else
      if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
        {
         Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading");
         return(false);
        }

   return(true);
  }

Während der Initialisierung benötigen wir eine Funktion, die für das Laden aller unserer technischen Indikatoren und das Abrufen wichtiger Marktdetails verantwortlich ist. Die Ladefunktion wird genau das für uns tun, und da sie auf globale Variablen verweist, ist ihr Rückgabetyp ungültig.

//+---------------------------------------------------------------------+
//| Load our needed variables                                           |
//+---------------------------------------------------------------------+
void load(void)
  {
//Account Info
   currency = AccountInfoString(ACCOUNT_CURRENCY);
   server = AccountInfoString(ACCOUNT_SERVER);
   login = AccountInfoInteger(ACCOUNT_LOGIN);

//Indicators
   atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);
   ma_high = iMA(_Symbol,PERIOD_CURRENT,ma_period,ma_period_shift,MODE_EMA,PRICE_HIGH);
   ma_low = iMA(_Symbol,PERIOD_CURRENT,ma_period,ma_period_shift,MODE_EMA,PRICE_LOW);

//Market Information
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   tick_value = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT) * min_volume;
   lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   average_market_move = NormalizeDouble(10000 * tick_value,_Digits);
  }

Unser ONNX-Modell hingegen wird durch einen separaten Funktionsaufruf geladen. Die Funktion erstellt unser ONNX-Modell aus dem zuvor definierten Puffer und validiert die Eingabe- und Ausgabeform.

//+------------------------------------------------------------------+
//| Load our ONNX model                                              |
//+------------------------------------------------------------------+
bool load_onnx(void)
   {
      onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
      ulong onnx_input [] = {1,4};
      ulong onnx_output[] = {1,1};
      if(!OnnxSetInputShape(onnx_model,0,onnx_input))
         {
            Comment("[INTERNAL ERROR] Failed to load AI modules. Relode the EA.");
            return(false);
         }
         
      if(!OnnxSetOutputShape(onnx_model,0,onnx_output))
         {
            Comment("[INTERNAL ERROR] Failed to load AI modules. Relode the EA.");
            return(false);
         }
         
     return(true);
   }

Definieren wir nun die Funktion, mit der wir Vorhersagen aus unserem Modell erhalten.

//+------------------------------------------------------------------+
//| Get a prediction from our model                                  |
//+------------------------------------------------------------------+
void model_predict(void)
   {
      vectorf onnx_inputs = {iOpen(Symbol(),PERIOD_CURRENT,0),iHigh(Symbol(),PERIOD_CURRENT,0),iLow(Symbol(),PERIOD_CURRENT,0),iClose(Symbol(),PERIOD_CURRENT,0)};
      OnnxRun(onnx_model,ONNX_DEFAULT,onnx_inputs,onnx_output);
   }

Unser Stop-Loss wird um den ATR-Wert angepasst. Je nachdem, ob es sich bei dem aktuellen Handel um einen Kauf- oder einen Verkaufshandel handelt, ist dies der entscheidende Faktor, der uns dabei hilft, festzustellen, ob wir unseren Stop-Loss nach oben aktualisieren sollten, indem wir den aktuellen ATR-Wert addieren, oder nach unten, indem wir den aktuellen ATR-Wert abziehen. Wir können auch ein Vielfaches des aktuellen ATR-Wertes verwenden, um dem Nutzer eine feinere Kontrolle über sein Risikoniveau zu geben.

//+------------------------------------------------------------------+
//| Update the ATR stop loss                                         |
//+------------------------------------------------------------------+
void check_atr_stop()
  {

   for(int i = PositionsTotal() -1; i >= 0; i--)
     {

      string symbol = PositionGetSymbol(i);
      if(_Symbol == symbol)
        {

         ulong ticket = PositionGetInteger(POSITION_TICKET);
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN);
         double type = PositionGetInteger(POSITION_TYPE);
         double current_stop_loss = PositionGetDouble(POSITION_SL);

         if(type == POSITION_TYPE_BUY)
           {
            double atr_stop_loss = (ask - (atr_stop));
            double atr_take_profit = (ask + (atr_stop));

            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
              }
           }

         else
            if(type == POSITION_TYPE_SELL)
              {
               double atr_stop_loss = (bid + (atr_stop));
               double atr_take_profit = (bid - (atr_stop));
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                 }
              }
        }
     }
  }

Schließlich müssen wir 2 Funktionen definieren, die für das Eröffnen von Kauf- und Verkaufspositionen zuständig sind, und ihre komplementären Paare für das Schließen der Position.

//+------------------------------------------------------------------+
//| Open buy positions                                               |
//+------------------------------------------------------------------+
void check_buy()
  {
   if(PositionsTotal() == 0)
     {
      for(int i=0; i < position_size;i++)
        {
         trade.Buy(min_volume * lot_multiplier,_Symbol,ask,buy_stop_loss,0,"BUY");
         Print("Position: ",i," has been setup");
        }
     }
  }

//+------------------------------------------------------------------+
//| Open sell positions                                              |
//+------------------------------------------------------------------+
void check_sell()
  {
   if(PositionsTotal() == 0)
     {
      for(int i=0; i < position_size;i++)
        {
         trade.Sell(min_volume * lot_multiplier,_Symbol,bid,sell_stop_loss,0,"SELL");
         Print("Position: ",i," has been setup");
        }
     }
  }

//+------------------------------------------------------------------+
//| Close all buy positions                                          |
//+------------------------------------------------------------------+
void close_buy()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_BUY)
              {
               trade.PositionClose(ticket);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Close all sell positions                                         |
//+------------------------------------------------------------------+
void close_sell()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_SELL)
              {
               trade.PositionClose(ticket);
              }
           }
        }
     }
  }

Lassen Sie uns die letzten 3 Kursniveaus verfolgen.

//+------------------------------------------------------------------+
//| Get the last 3 quotes                                            |
//+------------------------------------------------------------------+
void check_price(int candles)
  {
   for(int i = 0; i < candles;i++)
     {
      close_price[i] = iClose(_Symbol,PERIOD_CURRENT,i);
     }
  }

Diese boolesche Prüfung gibt true zurück, wenn wir über dem gleitenden Durchschnitt liegen.

//+------------------------------------------------------------------+
//| Are we completely above the MA?                                  |
//+------------------------------------------------------------------+
bool above_channel()
  {
   return (((close_price[0] - moving_average_high_array[0] > 0)) && ((close_price[0] - moving_average_low_array[0]) > 0));
  }

Prüfen Sie, ob wir unter dem gleitenden Durchschnitt liegen.

//+------------------------------------------------------------------+
//| Are we completely below the MA?                                  |
//+------------------------------------------------------------------+
bool below_channel()
  {
   return(((close_price[0] - moving_average_high_array[0]) < 0) && ((close_price[0] - moving_average_low_array[0]) < 0));
  }

Schließen wir alle Positionen, die wir haben.

//+------------------------------------------------------------------+
//| Close all positions we have                                      |
//+------------------------------------------------------------------+
void close_all()
  {
   if(PositionsTotal() > 0)
     {
      ulong ticket;
      for(int i =0;i < PositionsTotal();i++)
        {
         ticket = PositionGetTicket(i);
         trade.PositionClose(ticket);
        }
     }
  }

Berechnen wir die optimale Losgröße, damit unsere Marge dem Kapitalbetrag entspricht, den wir zu riskieren bereit sind.

//+------------------------------------------------------------------+
//| Calculate the lot size to be used                                |
//+------------------------------------------------------------------+
void calculate_lot_size()
  {
//--- This is the total percentage of the account we're willing to part with for margin, or to keep a position open in other words.
   Print("Risk Equity: ",risk_equity);

//--- Now that we're ready to part with a discrete amount for margin, how many positions can we afford under the current lot size?
//--- By default we always start from minimum lot
   position_size = risk_equity / margin;

//--- We need to keep the number of positions lower than 10
   if(position_size > 10)
     {
      //--- How many times is it greater than 10?
      int estimated_lot_size = (int)  MathFloor(position_size / 10);
      position_size = risk_equity / (margin * estimated_lot_size);
      Print("Position Size After Dividing By margin at new estimated lot size: ",position_size);
      int estimated_position_size = position_size;
      //--- Can we increase the lot size this many times?
      if(estimated_lot_size < max_volume_increase)
        {
         Print("Est Lot Size: ",estimated_lot_size," Position Size: ",estimated_position_size);
         lot_multiplier = estimated_lot_size;
         position_size = estimated_position_size;
        }
     }
  }

Schließen wir offene Positionen und prüfen, ob wir wieder handeln können.

//--- This function will help us keep track of which side we need to enter the market
void close_all_and_enter()
  {

   if(PositionSelect(Symbol()))
     {
      // Determine the type of position
      check_signal();
     }
   else
     {
      Print("No open position found.");
     }
  }

Wenn wir unser Gewinnziel erreicht haben, schließen wir alle Positionen, die wir haben, um den Gewinn zu realisieren, und prüfen dann, ob wir wieder einsteigen können.

//+------------------------------------------------------------------+
//| Chekc if we have reached our profit target                       |
//+------------------------------------------------------------------+
void check_profit()
  {
   double current_profit = (AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE)) / PositionsTotal();
   if(current_profit > profit_target)
     {
      close_all_and_enter();
     }

   if((current_profit * PositionsTotal()) < (risk_equity * -1))
     {
      Comment("We've breached our risk equity, consider closing all positions");
     }
  }

Und schließlich brauchen wir eine Funktion, die alle unrentablen Geschäfte schließt.

//+------------------------------------------------------------------+
//| Close all losing trades                                          |
//+------------------------------------------------------------------+
void close_profitable_trades()
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetDouble(POSITION_PROFIT)>profit_target)
           {
            ulong ticket;
            ticket = PositionGetTicket(i);
            trade.PositionClose(ticket);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Abb. 11: Unser Expert Advisor

Abb. 12: Die Parameter, die wir zum Testen der Anwendung verwenden

Abb. 13: Unser Programm in Aktion



Schlussfolgerung

Unsere Ergebnisse waren zwar nicht ermutigend, aber sie sind bei weitem nicht schlüssig. Es gibt noch andere Möglichkeiten, den MACD-Indikator zu interpretieren, die eine Bewertung wert sein können. So kreuzt die MACD-Signallinie bei einem Aufwärtstrend oberhalb der Hauptlinie, während sie bei einem Abwärtstrend unter die Hauptlinie fällt. Die Betrachtung des Indikators aus dieser Perspektive könnte zu unterschiedlichen Fehlermetriken führen. Wir können nicht einfach davon ausgehen, dass alle Strategien zur Interpretation des MACD zu einheitlichen Fehlerquoten führen. Es wäre nur vernünftig, die Wirksamkeit verschiedener Strategien auf der Grundlage des MACD zu testen, bevor wir uns ein Urteil über die Wirksamkeit des Indikators bilden können. 

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

Beigefügte Dateien |
EURUSD_MACD.ipynb (514.45 KB)
EURUSD_SVR_M1.onnx (0.28 KB)
EURUSD_AI.mq5 (18.01 KB)
MQL5 Handels-Toolkit (Teil 3): Entwicklung einer EX5-Bibliothek zur Verwaltung schwebenden Aufträge MQL5 Handels-Toolkit (Teil 3): Entwicklung einer EX5-Bibliothek zur Verwaltung schwebenden Aufträge
Lernen Sie, wie Sie eine umfassende EX5-Bibliothek für schwebende Aufträge in Ihrem MQL5-Code oder Ihren Projekten entwickeln und implementieren. Dieser Artikel zeigt Ihnen, wie Sie eine umfangreiche EX5-Bibliothek für die Verwaltung schwebender Aufträge erstellen können, und führt Sie durch den Import und die Implementierung dieser Bibliothek, indem er ein Handels-Panel oder eine grafische Nutzeroberfläche (GUI) erstellt. Das Expert Advisor-Order-Panel ermöglicht es den Nutzern, schwebende Aufträge, die mit einer bestimmten magischen Zahl verknüpft sind, direkt über die grafische Oberfläche im Chartfenster zu öffnen, zu überwachen und zu löschen.
Nachrichtenhandel leicht gemacht (Teil 4): Leistungsverbesserung Nachrichtenhandel leicht gemacht (Teil 4): Leistungsverbesserung
Dieser Artikel befasst sich mit Methoden zur Verbesserung der Laufzeit des Experten im Strategietester. Der Code wird so geschrieben, dass die Zeiten der Nachrichtenereignisse in stündliche Kategorien unterteilt werden. Der Zugriff auf diese Ereigniszeiten erfolgt innerhalb der angegebenen Stunde. Dadurch wird sichergestellt, dass der EA sowohl in Umgebungen mit hoher als auch mit niedriger Volatilität effizient ereignisgesteuerte Trades verwalten kann.
Erstellen eines Handelsadministrator-Panels in MQL5 Teil IV: Login-Sicherheitsschicht Erstellen eines Handelsadministrator-Panels in MQL5 Teil IV: Login-Sicherheitsschicht
Stellen Sie sich vor, ein bösartiger Akteur dringt in den Raum des Handelsadministrator ein und verschafft sich Zugang zu den Computern und dem Admin-Panel, über das Millionen von Händlern weltweit wertvolle Informationen erhalten. Ein solches Eindringen könnte katastrophale Folgen haben, z. B. das unbefugte Versenden irreführender Nachrichten oder zufällige Klicks auf Schaltflächen, die unbeabsichtigte Aktionen auslösen. In dieser Diskussion werden wir die Sicherheitsmaßnahmen in MQL5 und die neuen Sicherheitsfunktionen, die wir in unserem Admin-Panel zum Schutz vor diesen Bedrohungen implementiert haben, untersuchen. Durch die Verbesserung unserer Sicherheitsprotokolle wollen wir unsere Kommunikationskanäle schützen und das Vertrauen unserer weltweiten Handelsgemeinschaft erhalten. Weitere Informationen finden Sie in diesem Artikel.
Wie Smart-Money-Konzepte (SMC) zusammen mit dem Fibonacci-Indikator einen optimalen Handelseinstieg signalisieren. Wie Smart-Money-Konzepte (SMC) zusammen mit dem Fibonacci-Indikator einen optimalen Handelseinstieg signalisieren.
SMC (Orderblock) sind Schlüsselbereiche, in denen institutionelle Händler umfangreiche Käufe oder Verkäufe tätigen. Nach einer signifikanten Kursbewegung hilft Fibonacci dabei, ein potenzielles Retracement von einem kürzlichen Swing-Hoch zu einem Swing-Tief zu identifizieren, um einen optimalen Handelseinstieg zu finden.