Klassische Strategien neu interpretieren (Teil 19): Tiefes Eintauchen in das Kreuzen von gleitenden Durchschnitten
Dieser Artikel untersucht die klassische Strategie des Kreuzens von gleitenden Durchschnitten und bietet dem Leser mehrere alternative Lösungswege, die er beschreiten kann, um die herkömmlichen Probleme der Strategie zu überwinden. Neben vielen anderen bekannten Problemen ist die Strategie dafür bekannt, dass sie verrauscht ist, verzögerte Handelssignale liefert und in großem Umfang ausgenutzt wird. Vereinfacht ausgedrückt bedeutet dies, dass die Handelssignale der traditionellen Strategie des Kreuzens von gleitenden Durchschnitten zu schnell und zu häufig umgekehrt werden können, als dass Händler zuverlässig von dieser Strategie profitieren könnten. Hinzu kommt, dass viele falsche Ausbrüche zu einem zu frühen Einstieg führen.
Die Strategie in ihrer traditionellen Form scheint anfällig für Rauschen zu sein und verfügt über unzuverlässige Mechanismen zum Herausfiltern von schwachen und unrentablen Handelsgeschäften. Die hier vorgestellten Lösungen helfen, die Schwächen der traditionellen Strategie zu überwinden, indem sie Filter für Handelssignale entwickeln, die weitaus robuster gegenüber Marktstörungen sind. In diesem Artikel untersuchen wir insbesondere fünf verschiedene Varianten von Lösungen, die darauf abzielen, die schwachen Handelsgeschäfte aus den vom Kreuzen erzeugten Signalen herauszufiltern.
Das Kreuzen gleitender Durchschnitte scheinen für unsere statistischen Modelle ein fruchtbarer Boden zu sein, aus dem sie lernen können. Die von uns erstellten statistischen Modelle lernten die Fehler, die geblieben sind, wenn sich die gleitenden Durchschnitt kreuzen – Fehler, die wir selbst nicht manuell herausfiltern konnten, indem wir uns kreativ bessere Handelsregeln ausdachten. Natürlich gibt es eine natürliche Grenze dafür, wie weit unsere menschliche Intuition uns bei der Optimierung einer Strategie leiten kann, aber wo die Intuition versagt, können uns unsere statistischen Modelle helfen, den Rest der Arbeit zu erledigen.
Erste Schritte in MQL5
Da wir in dieser speziellen Diskussion mehrere Versionen derselben Strategie betrachten, ist es wichtig, dass wir Parameter verwenden, die in allen Backtests gleichbleiben, um zu vermeiden, dass dieselben Informationen wieder und wieder in jeder Iteration wiederholt werden. Daher werden alle fünf Versionen der Anwendungen, die wir testen werden, in einem Ordner aufbewahrt (siehe Abbildung 1).

Abbildung 1: Die Visualisierung aller Versionen der Strategie des Kreuzens von gleitenden Durchschnitten wird bewertet
Die Prüfungstermine, die wir für alle Prüfungen verwenden werden, werden von Januar 2022 bis zum Zeitpunkt der Erstellung dieses Berichts im Jahr 2025 festgelegt.

Abbildung 2: Die Testdaten, die wir für alle Versionen unserer Handelsstrategie ausgewählt haben

Abbildung 3: Die Einstellungen für den Backtest, die wir für
Festlegung einer Basislinie
Wie bei allen Prozessen beginnen wir mit der Festlegung eines Grundniveaus der Leistung. Zu diesem Zweck werden wir die Strategie in einer weithin akzeptierten Form umsetzen, auf die sich die meisten Händler einigen können. Daher hat die ursprüngliche Strategie zwei gleitende Durchschnitte: einen mit einer kürzeren Periodenlänge und einen zweiten mit einer längeren Periodenlänge. Wenn der schnellere gleitende Durchschnitt über den langsameren gleitenden Durchschnitt klettert, neigen wir dazu, Kaufpositionen einzunehmen, und wenn der umgekehrte Fall eintritt, sind wir bestrebt, Verkaufspositionen einzunehmen. Dies ist die einfachste mögliche Version der Strategie, mit der die meisten Händler einverstanden sind, und deshalb haben wir sie als Basis ausgewählt.
Sie bot uns Regeln für den Einstieg und den Ausstieg aus dem Handel, und unsere Ausstiegsregeln wurden dann durch die ATR definiert, um unsere Stop-Loss- und Take-Profit-Positionen festzulegen. Wir verwendeten gleichmäßig verteilte Take-Profits und Stop-Losses ohne Trailing-Einstellungen, um sicherzustellen, dass die Änderungen der Rentabilität auf verbesserte Entscheidungsregeln für unsere Handelseinträge zurückzuführen sind.
//+------------------------------------------------------------------+ //| MA Crossover V1.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Technical Indicators | //+------------------------------------------------------------------+ int ma_fast_handler,ma_slow_handler,atr_handler; double ma_fast_reading[],ma_slow_reading[],atr_reading[]; //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ double ask,bid; //+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our indicators ma_fast_handler = iMA("EURUSD",PERIOD_D1,30,0,MODE_SMA,PRICE_CLOSE); ma_slow_handler = iMA("EURUSD",PERIOD_D1,60,0,MODE_SMA,PRICE_CLOSE); atr_handler = iATR("EURUSD",PERIOD_D1,14); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Free up memory we are no longer using when the application is off IndicatorRelease(ma_fast_handler); IndicatorRelease(ma_slow_handler); IndicatorRelease(atr_handler); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- When price levels change datetime current_time = iTime("EURUSD",PERIOD_D1,0); static datetime time_stamp; //--- Update the time if(current_time != time_stamp) { time_stamp = current_time; //--- Fetch indicator current readings CopyBuffer(ma_fast_handler,0,0,1,ma_fast_reading); CopyBuffer(ma_slow_handler,0,0,1,ma_slow_reading); CopyBuffer(atr_handler,0,0,1,atr_reading); ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK); bid = SymbolInfoDouble("EURUSD",SYMBOL_BID); //--- If we have no open positions if(PositionsTotal() == 0) { //--- Trading rules if(ma_fast_reading[0] > ma_slow_reading[0]) { //--- Buy signal Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),""); } else if(ma_fast_reading[0] < ma_slow_reading[0]) { //--- Sell signal Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),""); } } } } //+------------------------------------------------------------------+
Wie in der Einleitung des Artikels erläutert, ist die ursprüngliche Version der Strategie nicht profitabel und sehr verrauscht, was die schlechte Kapitalkurve erklärt, die wir bei den Backtests über die Testdauer beobachtet haben.

Abbildung 4: Visualisierung der Kapitalkurven, die sich bei der klassischen Version der Handelsstrategie ergibt
Bei der detaillierten Analyse der Strategie stellen wir fest, dass die Mehrzahl der von der Strategie getätigten Abschlüsse unrentabel war, was nicht wünschenswert ist. Außerdem hat die Strategie einen Gewinnfaktor von weniger als 1 und eine erwartete Auszahlung von weniger als 0, was bedeutet, dass sie das Kapital der Anleger im Laufe der Zeit aufzehren dürfte. Unter den meisten Umständen würde eine solche Strategie aufgegeben und vollständig ersetzt werden. Wir sollten jedoch nach Wegen suchen, um alten Hunden neue Tricks beizubringen.
Wie bereits in unserem Artikel erwähnt, wollen wir die Wiederholung derselben Informationen auf ein Mindestmaß beschränken; daher konzentrieren wir uns nur auf die Segmente des Codes, die sich geändert haben, und ignorieren die anderen Segmente, die sich nicht geändert haben. Nach mehreren Tests, Änderungen und Iterationen erwiesen sich diese Anpassungen als die stabilsten Verbesserungen, die wir manuell vornehmen konnten. Indem wir prüfen, ob sich die extremen Dochte des Kurses oberhalb oder unterhalb der jeweiligen schnellen und langsamen Periode gebildet haben, können wir bessere Kauf oder Verkaufseröffnungen finden.

Abbildung 5: Die Backtest-Ergebnisse der ursprünglichen Version unserer Handelsstrategie
Erster Versuch zur Verbesserung der Ausgangssituation
Jetzt, da wir eine Basislinie festgelegt haben, können wir weiter an der Verbesserung unserer Handelsstrategie arbeiten und strengere Filter hinzufügen, um die Anzahl der durch Rauschen verursachten Abschlüsse zu reduzieren. Nachdem wir mehrere verschiedene Konfigurationen manuell ausgewertet hatten, fanden wir zuverlässige Ergebnisse, wenn die Extreme der Kerzendochte mit dem Indikator des gleitenden Durchschnitts selbst als Hinweis auf eine Marktverzerrung verglichen wurden. Wenn der untere Docht über dem schnellen gleitenden Durchschnitt lag, neigten wir dazu, Kaufpositionen einzugehen, und wenn der obere Docht unter dem langsamen gleitenden Durchschnitt lag, versuchten wir, Verkaufspositionen einzugehen.
//--- If we have no open positions if(PositionsTotal() == 0) { //--- Trading rules if((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0])) { //--- Buy signal Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),""); } else if((ma_fast_reading[0] < ma_slow_reading[0]) && (high < ma_slow_reading[0])) { //--- Sell signal Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),""); } }
Die mit dem neuen Regelwerk erhaltene Kapitalkurve ist größtenteils noch instabil. Er weist jedoch einen vorherrschenden Aufwärtstrend auf, verglichen mit dem vorherrschenden Abwärtstrend, der in der ursprünglichen Version unserer Strategie beobachtet wurde. Wenn man sich das Bild genauer ansieht, kann man feststellen, dass es Zeiten gab, in denen das schwebende Kapital (equity) des Kontos über dem Endsaldo lag, der bei Abschluss des Handelsgeschäfts verzeichnet wurde, was darauf hindeutet, dass es in unserer Handelsstrategie noch wertvolle Signale gibt, die wir noch nicht erfasst haben.

Abbildung 6: Die Änderungen, die wir an unserer Anwendung vorgenommen haben, brachten wünschenswerte Änderungen an der Kapitalkurve, die wir von der Handelslogik erhalten haben
Wenn wir die detaillierten Statistiken unserer Handelsstrategie betrachten, sehen wir erhebliche Verbesserungen. Zunächst einmal ist der Gesamtnettogewinn endlich positiv geworden, nachdem er zu Beginn deutlich negativ gewesen ist. Außerdem ist der über das gesamte Backtest-Fenster aufgelaufene Bruttoverlust gesunken, was bedeutet, dass unsere Handelsstrategie einem geringeren Risiko ausgesetzt ist als die ursprüngliche Version. Darüber hinaus wurden 135 Handelsgeschäfte von der verrauschten Version der Strategie platziert, während die profitable Version weniger – nämlich 107 Handelsgeschäfte – verwendete, um mehr Gewinn zu erzielen. Die Handelsgenauigkeit unserer Strategie ist von überwiegend unprofitabel auf marginal profitabel gestiegen, und unsere erwartete Auszahlung hat sich gegenüber der ursprünglichen Version verbessert.
Abbildung 7: Unsere manuellen Verbesserungen an der Strategie haben das Problem des negativen Kontosaldos aus dem ersten Test behoben
Zweiter Versuch, die Basislinie zu übertreffen
Mit diesen Ergebnissen in der Hand waren wir dann motiviert, es erneut zu versuchen, um noch bessere Ergebnisse zu erzielen. Zu diesem Zeitpunkt war unsere Intuition jedoch nicht mehr von großem Nutzen, und wir versuchten daher, bessere Handelsregeln direkt aus dem historischen Verhalten des Marktes zu lernen. Wir begannen damit, ein Skript zu erstellen, das historische Marktdaten abruft und sie in das CSV-Format schreibt.
//+------------------------------------------------------------------+
//| ProjectName |
//| Copyright 2020, CompanyName |
//| http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property script_show_inputs
//--- Define our moving average indicator
#define MA_PERIOD_FAST 30 //--- Moving Average Fast Period
#define MA_PERIOD_SLOW 60 //--- Moving Average Slow Period
#define MA_TYPE MODE_SMA //--- Type of moving average we have
#define HORIZON 5 //--- Forecast horizon
//--- Our handlers for our indicators
int ma_fast_handle,ma_slow_handle;
//--- Data structures to store the readings from our indicators
double ma_fast_reading[],ma_slow_reading[];
//--- File name
string file_name = Symbol() + " Cross Over Data.csv";
//--- Amount of data requested
input int size = 3000;
//+------------------------------------------------------------------+
//| Our script execution |
//+------------------------------------------------------------------+
void OnStart()
{
int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
ma_fast_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_FAST,0,MA_TYPE,PRICE_CLOSE);
ma_slow_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_SLOW,0,MA_TYPE,PRICE_OPEN);
//---Set the values as series
CopyBuffer(ma_fast_handle,0,0,fetch,ma_fast_reading);
ArraySetAsSeries(ma_fast_reading,true);
CopyBuffer(ma_slow_handle,0,0,fetch,ma_slow_reading);
ArraySetAsSeries(ma_slow_reading,true);
//---Write to file
int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
for(int i=size;i>=1;i--)
{
if(i == size)
{
FileWrite(file_handle,
//--- Time
"Time",
//--- OHLC
"Open",
"High",
"Low",
"Close",
"MA F",
"MA S"
);
}
else
{
FileWrite(file_handle,
iTime(_Symbol,PERIOD_CURRENT,i),
//--- OHLC
iOpen(_Symbol,PERIOD_CURRENT,i),
iHigh(_Symbol,PERIOD_CURRENT,i),
iLow(_Symbol,PERIOD_CURRENT,i),
iClose(_Symbol,PERIOD_CURRENT,i),
ma_fast_reading[i],
ma_slow_reading[i]
);
}
}
//--- Close the file
FileClose(file_handle);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Undefine system constants |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD_FAST
#undef MA_PERIOD_SLOW
#undef MA_TYPE
//+------------------------------------------------------------------+ Analyse unserer Marktdaten in Python
Danach haben wir das Skript mit Python ausgeführt, um die Daten zu analysieren und daraus Handelsregeln zu lernen. Wir begannen damit, die Standard-Python-Bibliotheken zu importieren.
#Import the standard python libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns
Dann lesen wir die vom Skript erzeugte CSV-Datei ein.
#Read in the data data = pd.read_csv("/content/EURUSD Cross Over Data.csv")
Wir legten fest, wie weit in die Zukunft wir mit unserem statistischen Modell voraussagen wollten.
#Label the data #Define the forecast horizon HORIZON = 5
Als Nächstes haben wir unsere Daten mit den Veränderungen in den interessierenden Preisfeeds gekennzeichnet. In diesem speziellen Beispiel haben wir festgestellt, dass es drei geeignete Ziele für unser Modell zum Lernen gibt – das Hauptziel ist die erwartete Marktrendite, und die beiden zusätzlichen Ziele stellen die Veränderungen des gleitenden Durchschnitts dar.
#Define targets data['Target'] = data['Close'].shift(-HORIZON) - data['Close'] data['Target 2'] = data['MA F'].shift(-HORIZON) - data['MA F'] data['Target 3'] = data['MA S'].shift(-HORIZON) - data['MA S'] #Drop missing rows of data data = data.iloc[:-HORIZON,:]Daraufhin wurden zwei getrennte Partitionen für Trainings- und Testzwecke erstellt.
#Separate the test dates train = data.iloc[:(-365*4),:] test = data.iloc[(-365*4):,:]Dann haben wir das statistische Modell unserer Wahl geladen.
from sklearn.linear_model import LinearRegressionLassen Sie uns nun unsere Zeitreihen-Kreuzvalidierungswerkzeuge definieren.
tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)Wir haben die Eingaben und Ziele getrennt. X = train.iloc[:,1:-3] y = train.iloc[:,-3:]In diesem Schritt waren wir fast bereit, unser Modell anzupassen und in das ONNX-Format zu exportieren. ONNX, die Abkürzung für Open Neural Network Exchange, ist eine weltweit anerkannte API, die es uns ermöglicht, Modelle für maschinelles Lernen in einem Rahmen zu erstellen und auszutauschen, der unabhängig von dem für die Erstellung des Modells verwendeten Trainingsrahmen ist.
import onnx from skl2onnx.common.data_types import FloatTensorType from skl2onnx import convert_sklearn
Anschließend haben wir das Modell initialisiert und an die historischen Marktdaten angepasst.
model = LinearRegression() model.fit(X,y)
Danach haben wir die Eingangsform des Modells definiert.
initial_types = [('float_input',FloatTensorType([1,X.shape[1]]))]
Und dann haben wir die Ausgabeform des Modells definiert.
final_types = [('float_output',FloatTensorType([1,3]))]
Zum Schluss haben wir das Modell als ONNX-Prototyp gespeichert.
onnx_proto = convert_sklearn(model,initial_types=initial_types,final_types=final_types,target_opset=12) onnx.save(onnx_proto,'EURUSD Detailed RF.onnx')
Realisierung der Verbesserungen in MQL5
Nachdem das Modell als ONNX-Prototyp gespeichert wurde, kann es nun in die Handelsanwendung importiert werden.
//+------------------------------------------------------------------+ //| Resources | //+------------------------------------------------------------------+ #resource "\\Files\\EURUSD MA.onnx" as const uchar onnx_proto[];
Wir beginnen mit der Definition neuer globaler Variablen, die mit unseren ONNX-Modellen verknüpft werden sollen. Diese neuen Variablen sind für den Umgang mit den Inputs und Outputs zuständig, die unser Modell uns liefert. Wir werden auch einen Handler definieren, mit dem wir die Vorhersagen unseres Modells ausführen können.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vectorf model_inputs,model_outputs; long model;
Nach der Initialisierung müssen einige wichtige Schritte unternommen werden, um unser Modell für den Einsatz auf dem Markt vorzubereiten. Zunächst müssen wir das Modell aus dem importierten ONNX-Puffer einrichten. Dann definieren wir die Eingangs- und Ausgangsformen unseres Modells. Unser Modell benötigt sechs Eingaben und liefert drei Vorhersagen. Von dort aus definieren wir die Eingabe- und Ausgabeformen des Modells und stellen dann sicher, dass das Modell erfolgreich erstellt wurde. Wenn unsere Anwendung nicht mehr verwendet wird, geben wir das ONNX-Modell frei, um Speicherplatz zu schaffen. Wann immer neue Preisniveaus erreicht werden, bleibt der Großteil unserer Handelslogik gleich. Die wenigen wichtigen Verbesserungen, die vorgenommen werden müssen, sind, dass wir zunächst alle Modelleingaben, die wir benötigen, im Float-Vektor speichern.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup the ONNX model model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT); //--- Define the model parameter shape ulong input_shape[] = {1,6}; ulong output_shape[] = {1,3}; OnnxSetInputShape(model,0,input_shape); OnnxSetOutputShape(model,0,output_shape); model_inputs = vectorf::Zeros(6); model_outputs = vectorf::Zeros(3); if(model != INVALID_HANDLE) { return(INIT_SUCCEEDED); } //--- return(INIT_FAILED); }
Wenn das ONNX-Modell nicht mehr verwendet wird, geben wir das Modell frei, um Speicherressourcen freizugeben.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Free up memory we are no longer using when the application is off OnnxRelease(model); }
Nachdem wir die benötigten Eingaben erfolgreich gespeichert haben, holen wir eine Vorhersage von unserem Modell ab, wenn wir keine Handelsgeschäfte offen haben. Die Vorhersage unseres Modells dient dann als zusätzlicher Filter für unsere Handelsregeln. Wenn also der prognostizierte Renditewert unseres Modells über 0 liegt, dürfen wir Kaufpositionen eröffnen, aber wenn unser Modell negative Renditen vorhersagt, dürfen wir verkaufen.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- When price levels change datetime current_time = iTime("EURUSD",PERIOD_D1,0); static datetime time_stamp; //--- Update the time if(current_time != time_stamp) { time_stamp = current_time; //--- Fetch indicator current readings CopyBuffer(ma_fast_handler,0,0,1,ma_fast_reading); CopyBuffer(ma_slow_handler,0,0,1,ma_slow_reading); CopyBuffer(atr_handler,0,0,1,atr_reading); double open = iOpen("EURUSD",PERIOD_D1,0); double close = iClose("EURUSD",PERIOD_D1,0); double high = iHigh("EURUSD",PERIOD_D1,0); double low = iLow("EURUSD",PERIOD_D1,0); model_inputs[0] = (float) open; model_inputs[1] = (float) high; model_inputs[2] = (float) low; model_inputs[3] = (float) close; model_inputs[4] = (float) ma_fast_reading[0]; model_inputs[5] = (float) ma_slow_reading[0]; ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK); bid = SymbolInfoDouble("EURUSD",SYMBOL_BID); //--- If we have no open positions if(PositionsTotal() == 0) { if(!(OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_outputs))) { Comment("Failed to obtain a forecast from our model: ",GetLastError()); } else { Comment("Forecast: ",model_outputs); //--- Trading rules if((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0]) && (model_outputs[0] > 0)) { //--- Buy signal Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),""); } else if((ma_fast_reading[0] < ma_slow_reading[0]) && (high < ma_slow_reading[0]) && (model_outputs[0] < 0)) { //--- Sell signal Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),""); } } } } } //+------------------------------------------------------------------+
Betrachtet man die Kapitalkurve, die unsere aktualisierte Version der Strategie erzeugt, so sind sofort deutliche Unterschiede zu erkennen. Die Volatilität des Kontosaldos wurde stark eingedämmt. Wir können sehen, dass diese neue Anwendung die Volatilität unseres Kontosaldos viel besser unter Kontrolle hat, und außerdem ist ein stärkerer und deutlicherer Aufwärtstrend des Kontosaldos zu erkennen, was bedeutet, dass unser Kontosaldo während der dreijährigen Praxis stetig gewachsen ist. Unsere neue Strategie scheint also durchaus solider zu sein als die ursprüngliche Strategie.

Abbildung 8: Unsere Handelsstrategie weist jetzt eine starke und gesunde Saldenkurve über die Zeit auf
Wenn wir uns die detaillierten Ergebnisse ansehen, können wir feststellen, dass sich die Gesamtnettogewinne gegenüber der ursprünglichen Version, die wir kurz zuvor hatten, verdoppelt haben. Der Nettogewinn beträgt jetzt 120,00 $, und außerdem hat sich der während dieses Testzeitraums aufgelaufene Bruttoverlust um einen Bruchteil verringert. Unsere ursprüngliche Version der Strategie verursachte einen Bruttoverlust von 900 $, während diese neue Version nur einen Verlust von 300 $ verursachte. Und dennoch hat sich unser Gesamtnettogewinn mehr als verdoppelt, was bedeutet, dass diese Version unserer Strategie definitiv viel effektiver ist. Unsere erwarteten Auszahlungs- und Gewinnfaktoren sind nun endlich gesünder als die ursprünglichen Werte. Es ist wirklich beeindruckend zu sehen, dass sich die Gesamtzahl der Handelsgeschäfte im Vergleich zu früher um fast die Hälfte reduziert hat, was bedeutet, dass wir mit wesentlich weniger Handelsgeschäften deutlich mehr Gewinn machen.
Es ist jedoch beunruhigend zu beobachten, dass die Verteilung der über die Anwendung getätigten Abschlüsse nicht auf eine solide Vorstellung davon schließen lässt, wie die Finanzmärkte gehandelt werden sollten. Unsere Anwendung platzierte während des dreijährigen Backtests 14 Verkäufe und 45 Käufe, was eine erhebliche Verzerrung darstellt und ein mögliches Zeichen dafür ist, dass mit dem statistischen Modell, das wir zur Steuerung unserer Strategie verwenden, etwas grundlegend falsch ist.

Abbildung 9: Das statistische Modell, das wir in unsere Strategie integriert haben, brachte eine Reihe eigener Probleme mit sich
Tiefer graben für Verbesserungen
Nach reiflicher Überlegung kam ich zu dem Schluss, dass eine praktische Lösung für das Problem, das wir haben, durch die Verwendung flexiblerer statistischer Modelle gefunden werden könnte. Wir haben ein lineares Regressionsmodell verwendet, das gut geeignet ist, um eine Basislinie zu erstellen, das aber sehr strenge Annahmen über die Struktur der Beziehung enthält. Daher werden wir das lineare Modell durch eine Random-Forest-Regression ersetzen, die in der Lage ist, nicht lineare Effekte in den Daten zu lernen, was unser typisches lineares Modell nicht konnte.
Im Python-Notebook waren nur wenige Änderungen erforderlich, und wir konzentrieren uns ausschließlich auf die Änderungen, die vorgenommen werden mussten. Unser neues Modell wird daher als Random-Forest-Regressor definiert. Anschließend passen wir unser Modell an und exportieren es in das ONNX-Format.
model = RandomForestRegressor() model.fit(X,y) onnx_proto = convert_sklearn(model,initial_types=initial_types,final_types=final_types,target_opset=12) onnx.save(onnx_proto,'EURUSD Detailed RF.onnx')
Raum für Wachstum in MQL5 kultivieren
Anschließend importieren wir das Modell in unsere MQL5-Anwendung, und wir können eine entscheidende Verbesserung erzielen, indem wir unsere alten Handelsregeln durch neue Regeln ersetzen, die vollständig von unserem neuen und flexibleren statistischen Modell gesteuert werden. Das von uns gewählte Random-Forest-Modell ist in der Lage, mehr Marktbeziehungen auszunutzen als das lineare Modell, mit dem wir begonnen haben, und sollte daher theoretisch in der Lage sein, alle Handelsentscheidungen für uns zu treffen, ohne dass wir die klassischen Handelsregeln anwenden müssen, die wir ursprünglich angewendet haben.
//--- Trading rules if(((model_outputs[0] > 0) && (model_outputs[1] > 0) && (model_outputs[2] > 0)) || ((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0]))) { //--- Buy signal Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),""); } else if(((model_outputs[0] < 0) && (model_outputs[1] < 0) && (model_outputs[2] < 0)) || ((ma_fast_reading[0] < ma_slow_reading[0]) && (low < ma_slow_reading[0]))) { //--- Sell signal Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),""); }
Wenn wir die neue Kapitalkurve betrachten, die durch das leistungsfähigere nichtlineare Modell erzeugt wird, können wir sehen, dass unser neuer Kontosaldo auf neue Höchststände steigt, die wir vor dem ersten Test nicht erreicht haben. Allerdings zeigen sich allmählich einige volatile Aspekte des Verhaltens der Handelsstrategie, denn wie wir sehen können, befand sich die Handelsstrategie von etwa Dezember 2022 bis etwa März 2024 in einem intensiven Drawdown. Obwohl er sich schließlich erholte, ist es ziemlich enttäuschend zu sehen, weil die ursprüngliche Version der Handelsstrategie gleich vor dieser nicht unter dieser großen volatilen Periode litt. Alles in allem weist das Konto aber immer noch einen positiven Saldo auf, was bedeutet, dass die Strategie gut ist.

Abbildung 10: Die Verwendung eines nichtlinearen statistischen Modells half uns, mit derselben Strategie neue Leistungsniveaus zu erreichen.
Wenn wir die detaillierten Ergebnisse betrachten, können wir die Auswirkungen der von uns vorgenommenen Änderungen erkennen. Der Gesamtnettogewinn ist mehr oder weniger derselbe wie am Anfang. Der Bruttoverlust ist jedoch wieder gestiegen und hat sich mehr als verdoppelt, und auch die Zahl der insgesamt getätigten Handelsgeschäfte ist gestiegen. Obwohl sich der Gesamtgewinn geringfügig verbessert, macht diese neue Version unserer Anwendung wesentlich mehr Arbeit, um mehr oder weniger die gleichen Ergebnisse wie zuvor zu erzielen. Die wichtigste Verbesserung besteht jedoch darin, dass die Verteilung der Abschlüsse nun die Natur des Marktes widerspiegelt, was in der vorherigen Version nicht der Fall war.

Abbildung 11: Das neue nichtlineare überwachte Modell korrigierte die Verzerrungen, die durch das klassische lineare Modell gelernt wurden
Letzte Versuche
Wir werden nun einen letzten Versuch unternehmen, unsere Handelsstrategie auf ein neues Leistungsniveau zu heben. Zu diesem Zweck habe ich mir überlegt, weitere Daten über denselben Markt zu beschaffen. Dies kann durch die Erstellung neuer Merkmale erreicht werden, die ursprünglich nicht im Datensatz enthalten waren. Daher haben wir viele Merkmale manuell definiert, die das Wachstum in den Primärmarkt-Feeds erfassen. Durch die Berechnung der Differenzen zwischen den gleitenden Durchschnittsindikatoren, zwischen dem Preis und diesen gleitenden Durchschnitten, zwischen dem Preis über alle vier seiner Kanäle usw. konnten wir etwa 20 Eingaben definieren, während wir ursprünglich nur 6 hatten.
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- Define our moving average indicator #define MA_PERIOD_FAST 30 //--- Moving Average Fast Period #define MA_PERIOD_SLOW 60 //--- Moving Average Slow Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 5 //--- Forecast horizon //--- Our handlers for our indicators int ma_fast_handle,ma_slow_handle; //--- Data structures to store the readings from our indicators double ma_fast_reading[],ma_slow_reading[]; //--- File name string file_name = Symbol() + " Cross Over Data.csv"; //--- Amount of data requested input int size = 3000; //+------------------------------------------------------------------+ //| Our script execution | //+------------------------------------------------------------------+ void OnStart() { int fetch = size + (HORIZON * 2); //---Setup our technical indicatorsa ma_fast_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_FAST,0,MA_TYPE,PRICE_CLOSE); ma_slow_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_SLOW,0,MA_TYPE,PRICE_OPEN); //---Set the values as series CopyBuffer(ma_fast_handle,0,0,fetch,ma_fast_reading); ArraySetAsSeries(ma_fast_reading,true); CopyBuffer(ma_slow_handle,0,0,fetch,ma_slow_reading); ArraySetAsSeries(ma_slow_reading,true); //---Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(int i=size;i>=1;i--) { if(i == size) { FileWrite(file_handle, //--- Time "Time", //--- OHLC "Open", "High", "Low", "Close", //--- Moving averages "MA F", "MA S", //--- Growth in OHLC channels "Delta O", "Delta H", "Delta L", "Delta C", //--- Growth in MA Channels "Delta MA F", "Delta MA S", //--- Growth Across OHLC Channels "Delta O - H", "Delta O - L", "Delta O - C", "Delta H - L", "Delta H - C", "Delta L - C", //--- Growth Between Price and the moving averages "Delta C - MA F", "Delta C - MA S" ); } else { FileWrite(file_handle, iTime(_Symbol,PERIOD_CURRENT,i), //--- OHLC iOpen(_Symbol,PERIOD_CURRENT,i), iHigh(_Symbol,PERIOD_CURRENT,i), iLow(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), //--- Moving Averages ma_fast_reading[i], ma_slow_reading[i], //--- Growth in OHLC channels iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON), iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON), iLow(_Symbol,PERIOD_CURRENT,i) - iLow(_Symbol,PERIOD_CURRENT,i+HORIZON), iClose(_Symbol,PERIOD_CURRENT,i) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON), //--- Growth in MA Channels ma_fast_reading[i] - ma_fast_reading[i+HORIZON], ma_slow_reading[i] - ma_slow_reading[i+HORIZON], //--- Growth across OHLC channels iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON) - iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON), iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,i+HORIZON), iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON), iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,i+HORIZON), iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON), iLow(_Symbol,PERIOD_CURRENT,i+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON), //--- Growth between price and the moving averages iClose(_Symbol,PERIOD_CURRENT,i+HORIZON) - ma_fast_reading[i+HORIZON], iClose(_Symbol,PERIOD_CURRENT,i+HORIZON) - ma_slow_reading[i+HORIZON] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef HORIZON #undef MA_PERIOD_FAST #undef MA_PERIOD_SLOW #undef MA_TYPE //+------------------------------------------------------------------+
Analysieren der Daten in Python
Danach laden wir unsere Standard-Python-Bibliotheken.
from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import TimeSeriesSplit,cross_val_score
Und dann erstellen wir unser Zeitreihen-Kreuzvalidierungsobjekt.
tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON) Anschließend erstellen wir eine neue Funktion namens get_model, die uns eine neue Instanz desselben Random-Forest-Modells liefert, das wir zuvor verwendet haben.
def get_model(): return(RandomForestRegressor())
Jetzt möchten wir die Verbesserungen durch die neuen Funktionen, die wir geschaffen haben, sorgfältig messen. Daher definieren wir zwei Eingaben: eine sind alle Eingaben, die wir haben, und die zweite sind die klassischen Eingaben – die Eröffnung, das Hoch, das Tief, der Schlusskurs und der gleitende Durchschnitt. Dann definieren wir die Ziele.
X = train.iloc[:,1:-3] X_classic = train.iloc[:,1:7] y = train.iloc[:,-3:]
Und nun wollen wir die Verbesserungen bei der Vorhersage der einzelnen Ziele messen. Daher erstellen wir ein Array, um unsere Leistung für jedes Ziel zu speichern, und addieren dann in jedem Array die ursprüngliche Leistung, die wir mit den klassischen Daten erzielt haben, und die neuen Leistungsniveaus, die wir durch die Verwendung aller abgefragten Daten erreicht haben.
target_1 = [] target_2 = [] target_3 = []
Wie wir sehen können, haben die detaillierten Marktdaten, die wir abgerufen haben, unsere Leistung bei der Vorhersage der zukünftigen EURUSD-Renditen erheblich verbessert. Unsere Fehlerquote scheint um mehr als die Hälfte zu sinken.
target_1.append(np.mean(np.abs(cross_val_score(get_model(),X_classic,y.iloc[:,0],cv=tscv,scoring='neg_mean_squared_error')))) target_1.append(np.mean(np.abs(cross_val_score(get_model(),X,y.iloc[:,0],cv=tscv,scoring='neg_mean_squared_error'))))

Abbildung 12: Unsere detaillierten Marktdaten halfen uns, zukünftige EURUSD-Renditen besser vorherzusagen.
Das Gleiche gilt auch für unseren schnellen gleitenden Durchschnitt, den mit der Periodenlänge von 30. Die neuen Marktdaten, die wir von Hand kuratiert haben, haben unsere Fehlerquote auf unglaublich niedrige Werte verbessert – es ist eine signifikante Verbesserung unseres mittleren quadratischen Fehlers.
target_2.append(np.mean(np.abs(cross_val_score(get_model(),X_classic,y.iloc[:,1],cv=tscv,scoring='neg_mean_squared_error')))) target_2.append(np.mean(np.abs(cross_val_score(get_model(),X,y.iloc[:,1],cv=tscv,scoring='neg_mean_squared_error'))))

Abbildung 13: Wir beobachteten auch geringere Fehlerquoten, wenn wir detailliertere Marktdaten zur Prognose des zukünftigen Wertes des gleitenden 30-Perioden-Durchschnitts verwendeten.
Leider profitierte der langsame gleitende Durchschnitt, d. h. der gleitende 60-Perioden-Durchschnitt, nicht so sehr von den detaillierten Daten, die wir von Hand erstellt haben. Es scheint also, dass nur die ersten beiden Ziele am meisten von unseren Bemühungen profitiert haben.
target_3.append(np.mean(np.abs(cross_val_score(get_model(),X_classic,y.iloc[:,2],cv=tscv,scoring='neg_mean_squared_error')))) target_3.append(np.mean(np.abs(cross_val_score(get_model(),X,y.iloc[:,2],cv=tscv,scoring='neg_mean_squared_error'))))

Abbildung 14: Der gleitende 60-Perioden-Durchschnitt blieb schwierig zu prognostizieren und profitierte nur geringfügig von den von uns erstellten detaillierten Marktdaten
Implementierung unserer Verbesserungen in MQL5
Von dort aus müssen wir nun einige Dinge in unserer Anwendung ändern. Zunächst einmal muss die Eingabeform unserer Anwendung geändert werden. Ursprünglich waren es 6, und jetzt müssen wir sie auf 20 setzen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our indicators ma_fast_handler = iMA("EURUSD",PERIOD_D1,30,0,MODE_SMA,PRICE_CLOSE); ma_slow_handler = iMA("EURUSD",PERIOD_D1,60,0,MODE_SMA,PRICE_CLOSE); atr_handler = iATR("EURUSD",PERIOD_D1,14); //--- Setup the ONNX model model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT); //--- Define the model parameter shape ulong input_shape[] = {1,20}; ulong output_shape[] = {1,3}; OnnxSetInputShape(model,0,input_shape); OnnxSetOutputShape(model,0,output_shape); model_inputs = vectorf::Zeros(20); model_outputs = vectorf::Zeros(3); if(model != INVALID_HANDLE) { return(INIT_SUCCEEDED); } //--- return(INIT_FAILED); }
Wenn wir aktualisierte Preisniveaus erhalten, haben wir jetzt viel mehr Merkmale, die wir in die Eingaben unseres Modells einspeisen können, aber die Art und Weise, wie wir die Ausgaben des Modells behandeln, wäre mehr oder weniger dieselbe.
//--- Update the time if(current_time != time_stamp) { time_stamp = current_time; //--- Fetch indicator current readings CopyBuffer(ma_fast_handler,0,0,10,ma_fast_reading); CopyBuffer(ma_slow_handler,0,0,10,ma_slow_reading); CopyBuffer(atr_handler,0,0,10,atr_reading); double open = iOpen("EURUSD",PERIOD_D1,0); double close = iClose("EURUSD",PERIOD_D1,0); double high = iHigh("EURUSD",PERIOD_D1,0); double low = iLow("EURUSD",PERIOD_D1,0); model_inputs[0] = (float) open; model_inputs[1] = (float) high; model_inputs[2] = (float) low; model_inputs[3] = (float) close; model_inputs[4] = (float) ma_fast_reading[0]; model_inputs[5] = (float) ma_slow_reading[0]; model_inputs[6] = (float) (iOpen(_Symbol,PERIOD_CURRENT,0) - iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[7] = (float) (iHigh(_Symbol,PERIOD_CURRENT,0) - iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[8] = (float) (iLow(_Symbol,PERIOD_CURRENT,0) - iLow(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[9] = (float) (iClose(_Symbol,PERIOD_CURRENT,0) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[10] = (float) (ma_fast_reading[0] - ma_fast_reading[0+HORIZON]); model_inputs[11] = (float) (ma_slow_reading[0] - ma_slow_reading[0+HORIZON]); model_inputs[12] = (float) (iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON) - iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[13] = (float) (iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[14] = (float) (iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[15] = (float) (iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[16] = (float) (iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[17] = (float) (iLow(_Symbol,PERIOD_CURRENT,0+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON)); model_inputs[18] = (float) (iClose(_Symbol,PERIOD_CURRENT,0+HORIZON) - ma_fast_reading[0+HORIZON]); model_inputs[19] = (float) (iClose(_Symbol,PERIOD_CURRENT,0+HORIZON) - ma_slow_reading[0+HORIZON]); ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK); bid = SymbolInfoDouble("EURUSD",SYMBOL_BID); //--- If we have no open positions if(PositionsTotal() == 0) { if(!(OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_outputs))) { Comment("Failed to obtain a forecast from our model: ",GetLastError()); } else { Comment("Forecast: ",model_outputs); //--- Trading rules if(((model_outputs[0] > 0) && (model_outputs[1] > 0) && (model_outputs[2] > 0)) || ((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0]))) { //--- Buy signal Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),""); } else if(((model_outputs[0] < 0) && (model_outputs[1] < 0) && (model_outputs[2] < 0)) || ((ma_fast_reading[0] < ma_slow_reading[0]) && (low < ma_slow_reading[0]))) { //--- Sell signal Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),""); } } } } }
Wenn wir schließlich die Kapitalkurve betrachten, die durch unseren neuen „Big-Data“-Ansatz zur Marktanalyse entstanden ist, können wir leider feststellen, dass viel Rauschen in unser System gekommen ist, und das System ist nicht mehr rentabel. Er ist unbeständig und hat seinen positiven Aufwärtstrend verloren.

Abbildung 15: Die neue Kapitalkurve, die wir erstellt haben, ist viel zu volatil
Wenn wir außerdem die detaillierte statistische Analyse unserer Leistung betrachten, stellen wir fest, dass sich unsere Leistung verschlechtert hat. Wir leiden nun unter dem gleichen Problem der voreingenommenen Handelseinträge, die zu Käufen tendieren, und zusätzlich ist der erwartete Gewinn nun wieder negativ, und unser gesamter Nettogewinn ist ebenfalls negativ. Daher können wir eindeutig feststellen, dass die vorherige Version – Version 4 unserer Anwendung – die beste Version war, die wir bisher in dieser Diskussion erstellt haben.

Abbildung 16: Eine detaillierte Analyse der Ergebnisse der endgültigen Version unserer Anwendung
Schlussfolgerung
Schlussendlich hat dieser Artikel dem Leser gezeigt, wie Strategien, die normalerweise als veraltet und zu stark ausgenutzt gelten, um rentabel zu sein, verbessert werden können. Entgegen der landläufigen Meinung können diese Strategien sorgfältig verbessert und neu konzipiert werden, um neue Leistungsniveaus zu erreichen. Indem wir die Schwachstellen der Strategie sorgfältig ermitteln, erhalten wir nützliche Hinweise auf die notwendigen Verbesserungen, die vorgenommen werden müssen. In diesem Artikel hat der Leser gelernt, wie er die Menge des Rauschens, das in seine Strategie eindringt, reduzieren kann, und hoffentlich einige Bereiche seiner privaten klassischen Strategien identifiziert, die auch heute noch genutzt werden können, wenn der Leser sich ein wenig mehr anstrengt.
Schließlich hat dieser Artikel auch eine der Tücken des klassischen überwachten maschinellen Lernens aufgezeigt. Wie wir in unserer Artikelserie „Die Grenzen des maschinellen Lernens überwinden“ erörtert haben, sind die Fehlermetriken, die wir zur Messung der Leistung statistischer Modelle verwenden, nicht unbedingt mit den Leistungsmetriken kompatibel, die uns als algorithmische Händler wichtig sind. Leser, die eine Auffrischung dieser früheren Diskussion benötigen, finden hier einen Link dazu.
Wiederkehrenden Lesern werden die Gefahren eines blinden Vertrauens in den RMSE deutlich. In unserer Analyse der detaillierten Marktdaten konnten wir bei der Vorhersage der EURUSD-Renditen und des 30-Perioden-SMA signifikante Verbesserungen des RMSE außerhalb der Stichprobe feststellen. Diese wesentlichen Verbesserungen des RMSE außerhalb der Stichprobe hatten jedoch unerwünschte Auswirkungen auf unsere Rentabilität. Daher sollte der Leser besser über die Grenzen des klassischen, überwachten statistischen Lernens informiert sein.
| Dateiname | Beschreibung der Datei |
|---|---|
| Fetch Data.mq5 | Das Skript, das wir geschrieben haben, um grundlegende Marktdaten zu den EURUSD-Wechselkursen abzurufen (4 OHLC-Preis-Feeds, 2 gleitende Durchschnitte). |
| Fetch Data 2.mq5 | Das Skript, das wir geschrieben haben, um detaillierte Marktdaten zu den EURUSD-Wechselkursen abzurufen (20 Spalten). |
| MA_Crossover_V1.mq5 | Die am weitesten verbreitete Version der Strategie des Kreuzens von gleitenden Durchschnitten haben wir implementiert, um die Basisleistung zu ermitteln. |
| MA_Crossover_V2.mq5 | Die besten manuellen Verbesserungen, die wir uns vorstellen konnten, um die langfristige Leistung der Strategie zu verbessern. |
| MA_Crossover_V3.mq5 | Diese Version unserer Strategie orientierte sich an einem einfachen statistischen Modell, lernte aber eine Vorliebe für Kaufpositionen. |
| MA_Crossover_V4.mq5 | Die beste Version unserer Handelsstrategie, die wir gemeinsam entwickelt haben. Sie hat die Verzerrungen der vorherigen Version korrigiert, ohne die Rentabilität zu beeinträchtigen. |
| MA_Crossover_V5.mq5 | Die endgültige Version unserer Handelsstrategie, die wir anhand umfangreicher und detaillierter Beobachtungen der EURUSD-Wechselkurse entwickelt haben. |
| Advanced_Moving_Averages.ipynb | Das Jupyternotebook, das wir für die Analyse unserer Marktdaten verwendet haben. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20488
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Der MQL5 Standard Library Explorer (Teil 5): Experte für mehrere Signale
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Automatisieren von Handelsstrategien in MQL5 (Teil 44): Erkennung des Change of Character (CHoCH) mit Durchbrechen der hohen und tiefen Umkehrpunkte
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.