
Klassische Strategien neu interpretieren (Teil 14): Hochwahrscheinliche Setups
In unseren vorangegangenen Diskussionen haben wir analysiert, wie der Handel des Kreuzens von gleitenden Durchschnitten neu konzipiert werden kann, indem die Perioden der beiden fraglichen gleitenden Durchschnittsindikatoren festgelegt werden. Wir haben gezeigt, dass wir auf diese Weise ein angemessenes Maß an Kontrolle über den Umfang der Verzögerung in unserer Handelsstrategie ausüben können. Wir haben dann festgestellt, dass wir durch die Anwendung eines gleitenden Durchschnitts auf den Eröffnungskurs und des anderen auf den Schlusskurs eine sehr viel empfindlichere Form der alten Strategie des Kreuzens von gleitendem Durchschnitt erhalten haben. Unser neuer Rahmen bietet einige Garantien, die wir mit der traditionellen Strategie nicht erreichen konnten. Leser, die die frühere Diskussion noch nicht gelesen haben, können den Artikel hier nachlesen.
Heute gehen wir der Frage nach, ob es sinnvoll ist, mit unserer neu konzipierten Version des gleitenden Durchschnitts-Kreuzes mehr Produktivität zu erzielen. Indem wir die Beziehung zwischen unserer Kreuzstrategie der gleitenden Durchschnitte und dem EURUSD-Markt sorgfältig modellieren, können wir hoffentlich herausfinden, worin der Unterschied zwischen den Marktbedingungen besteht, unter denen sich unsere Strategie auszeichnet, und den Marktbedingungen, die sich für unsere Strategie als zu schwierig erweisen. Unser Ziel ist es dann, eine Handelsstrategie zu entwickeln, die lernt, den Handel zu stoppen, wenn sie ungünstige Marktbedingungen feststellt.
Überblick über die Handelsstrategie
Die meisten Mitglieder unserer Gemeinschaft sind sich einig, dass Händler aktiv versuchen sollten, Setups mit hoher Wahrscheinlichkeit zu handeln. Es gibt jedoch nur wenige formale Definitionen dafür, was genau ein hochwahrscheinliches Handels-Setup ist. Wie lässt sich die Wahrscheinlichkeit eines bestimmten Handels-Setups empirisch messen? Je nachdem, wen Sie fragen, werden Sie unterschiedliche Definitionen dafür erhalten, wie Sie solche Chancen erkennen und verantwortungsvoll nutzen können.
Dieser Artikel versucht, diese Probleme anzugehen, indem er einen algorithmischen Rahmen vorschlägt, der es uns ermöglicht, von den alten Definitionen abzuweichen und uns auf numerische Definitionen zu stützen, die evidenzbasiert sind, sodass unsere Handelsstrategien in der Lage sein können, sie zu identifizieren und gewinnbringend zu handeln, und zwar auf konsistente Art und Weise ganz von selbst.
Wir möchten die Beziehung zwischen unserer speziellen Handelsstrategie und jedem Symbol, das wir für den Handel ausgewählt haben, modellieren. Wir können dies erreichen, indem wir zunächst Marktdaten, die den Markt vollständig beschreiben, und alle Parameter, aus denen unsere Handelsstrategie besteht, vom MetaTrader 5-Terminal abrufen.
Danach werden wir ein statistisches Modell anpassen, um zu klassifizieren, ob die Strategie Signale erzeugt, die profitabel sind, oder ob das von unserer Strategie erzeugte Signal höchstwahrscheinlich unprofitabel sein wird.
Die von unserem Modell geschätzten Wahrscheinlichkeiten werden zu den Wahrscheinlichkeiten, die wir mit diesem bestimmten Signal verbinden. Daher können wir jetzt anfangen, über „Hochwahrscheinlichkeits-Setups“ auf eine wissenschaftlichere und empirischere Weise zu sprechen, die sich auf Beweise und relevante Marktdaten stützt.
Dieser Rahmen ermöglicht es uns im Wesentlichen, Handelsstrategien zu entwickeln, die „zielbewusst“ sind und ausdrücklich angewiesen werden, nur Aktionen durchzuführen, von denen sie erwarten, dass sie vorteilhaft sind. Wir beginnen damit, die notwendigen Komponenten zu formalisieren, die für die Entwicklung von algorithmischen Handelsstrategien erforderlich sind, die versuchen, die wahrscheinlichsten Folgen ihrer Handlungen abzuschätzen. Dies lässt sich korrekt als Ideologie des Verstärkungslernens bezeichnen, die auf überwachte Weise angegangen wird.
Erste Schritte in MQL5
Unsere heutige Aufgabe besteht darin, die Beziehung zwischen unserer Handelsstrategie und dem Symbol, mit dem wir handeln wollen, zu verstehen. Um dieses Ziel zu erreichen, werden wir das Wachstum der 4 primären Kursdaten (Open, High, Low und Close) sowie die Veränderungen unserer beiden gleitenden Durchschnittsindikatoren abrufen.
Beachten Sie, dass wir für die Kennzeichnung der Daten auch die realen Originalwerte der beiden Indikatoren und den Schlusskurs benötigen. Alles in allem werden wir unsere Daten in eine CSV-Datei mit 10 Spalten schreiben und dann damit fortfahren, die Beziehung zwischen unserer Strategie und diesem bestimmten Symbol zu erlernen, und bei jedem Schritt werden wir dieses Ergebnis mit der Leistung eines identischen Modells vergleichen, das versucht, den Marktpreis direkt vorherzusagen.
Dies gibt uns Aufschluss darüber, welches Ziel leichter zu erlernen ist, und das Schöne an unserem Ansatz ist die einfache Tatsache, dass unabhängig davon, welches Ziel leichter zu prognostizieren ist, beide Aufschluss darüber geben, wohin sich der Preis entwickelt.
/+-------------------------------------------------------------------+ //| 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 3 //--- Moving Average Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 10 //--- Our handlers for our indicators int ma_handle,ma_o_handle; //--- Data structures to store the readings from our indicators double ma_reading[],ma_o_reading[]; //--- File name string file_name = Symbol() + " Reward Modelling.csv"; //--- Amount of data requested input int size = 3000; //+------------------------------------------------------------------+ //| Our script execution | //+------------------------------------------------------------------+ void OnStart() { int fetch = size + (HORIZON * 2); //---Setup our technical indicators ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE); ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN); //---Set the values as series CopyBuffer(ma_handle,0,0,fetch,ma_reading); ArraySetAsSeries(ma_reading,true); CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading); ArraySetAsSeries(ma_o_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","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2"); } else { FileWrite(file_handle, iTime(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), ma_reading[i], ma_o_reading[i], 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)), ma_reading[i] - ma_reading[(i + HORIZON)], ma_o_reading[i] - ma_o_reading[(i + HORIZON)] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
Analyse der Daten
Lesen wir zunächst unsere Marktdaten in ein Jupyter-Notebook ein, damit wir die numerische Analyse der Performance der Strategie durchführen können.
import pandas as pd
Legen wir fest, wie weit in die Zukunft wir unsere Gewinne oder Verluste prognostizieren möchten.
HORIZON = 10
Wir lesen die Daten ein und fügen die benötigten Spalten hinzu. Die erste Spalte, „Ziel“, ist die traditionelle Marktrendite, in diesem Fall die 10-Tage-Rendite des EURUSD-Marktes. Die Spalte „Class“ ist entweder 1 für Aufwärts-Tage oder 0. Drittens gibt die Spalte „Action“ an, welche Aktion unsere Handelsstrategie ausgelöst hätte, wobei 1 für Kauf und -1 für Verkauf steht. Die Spalte „Reward“ wird als elementweises Produkt aus der Spalte „Ziel“ und der Spalte „Action“ berechnet; diese Multiplikation führt nur zu positiven Belohnungen, wenn unsere Strategie funktioniert:
- Wir wählen die Aktion -1 und das klassische Ziel ist kleiner als 0 (dies bedeutet, dass unsere Strategie verkauft und zukünftige Preisniveaus fallen werden)
- Wir wählen die Aktion 1 und das klassische Ziel ist größer 0 (dies bedeutet, dass unsere Strategie kauft und zukünftige Preisniveaus steigen werden)
data = pd.read_csv("..\EURUSD Reward Modelling.csv") data['Target'] = 0 data['Class'] = 0 data['Action'] = 0 data['Reward'] = 0 data['Trade Signal'] = 0
Jetzt geben wir unser klassisches Ziel ein, nämlich die 10-Tage-Veränderung des EURUSD-Schlusskurses.
data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close'] data.dropna(inplace=True)
Wir müssen die Klassen kennzeichnen, damit wir in unseren Zeichnungen schnell zwischen Auf- und Abwärts-Tagen unterscheiden können.
data.loc[data['Target'] > 0,'Class'] = 1
Wir wollen nun herausfinden, welche Maßnahmen unsere Strategie ergriffen hätte.
data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1 data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1
Und der Gewinn oder Verlust, den unsere Strategie bei diesen Maßnahmen erzielt hätte.
data['Reward'] = data['Target'] * data['Action']
Geben wir nun das Handelssignal ein, das uns sagt, ob wir handeln oder abwarten sollten.
data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1 data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1
Wir können einen Blick auf die Daten werfen. Wir können schnell erkennen, dass es schwierig ist, das Marktgeschehen gut zu trennen. Die orangefarbenen Punkte stehen für Handelssignale, die wir hätten ergreifen sollen, während die blauen Punkte für Signale stehen, die wir hätten ignorieren sollen. Die Tatsache, dass die orangefarbenen und blauen Punkte in diesem Streudiagramm übereinander zu sehen sind, informiert den Leser mathematisch darüber, dass sich profitable und unprofitable Handelssignale unter fast identischen Bedingungen bilden können. Unsere statistischen Modelle können möglicherweise Ähnlichkeiten und Unterschiede erkennen, für die wir als Menschen nicht sensibel genug sind oder die nur mit erheblichem Aufwand zu erkennen wären.
import numpy as np import seaborn as sns import matplotlib.pyplot as plt sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal') plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market') plt.grid()
Abb. 1: Das Kreuzen unserer gleitenden Durchschnitte scheinen Probleme zu haben, die Preisbewegung zu trennen
Schätzen wir schnell ab, ob unser neues Ziel leichter zu prognostizieren ist als das traditionelle Ziel.
from sklearn.linear_model import RidgeClassifier from sklearn.model_selection import TimeSeriesSplit,cross_val_score
Lineare Modelle sind besonders nützlich, um zuverlässige und kostengünstige Näherungen zu erhalten. Erstellen wir ein geteiltes Zeitserienobjekt, um einen linearen Klassifikator kreuzvalidieren zu können.
tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = []
Wir führen eine Kreuzvalidierung des Modells durch, zunächst mit dem alten Ziel, dann mit unserem neuen Ziel, das den Gewinn/Verlust unserer Strategie zu schätzen versucht. Da es sich um eine Klassifizierungsaufgabe handelt, sollten wir die Bewertungsmethode auf „accuracy“ (Genauigkeit) einstellen.
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy')))) scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))
Die Erstellung eines Balkendiagramms der Ergebnisse zeigt schnell, dass das Modell, das den Gewinn/Verlust der Strategie vorhersagt, besser abschneidet als das Modell, das versucht, den Markt direkt vorherzusagen. Das Modell, das versuchte, das Modell direkt zu prognostizieren, fiel unter die 50 %-Schwelle, während unser neues Ziel uns hilft, die 50 %-Benchmark zu überschreiten, wenn auch ehrlich gesagt nur knapp über dieser Schwelle.
sns.barplot(scores,color='black') plt.axhline(np.max(scores),linestyle=':',color='red') plt.title('Forcasting Market Returns vs Forecasting Strategy Reward') plt.ylabel('Percentage Accuracy Levels %') plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')
Abb. 2: Unsere linearen Modelle deuten darauf hin, dass wir den Gewinn oder Verlust, der durch unsere Strategie entsteht, besser vorhersagen sollten
Wir können die genaue prozentuale Leistungssteigerung berechnen, und es zeigt sich, dass wir durch die Vorhersage des Verhältnisses zwischen der Strategie und dem Markt eine um 7,6 % höhere Genauigkeit erzielen als durch die direkte Vorhersage des Marktes.
scores = (((scores / scores[0]) - 1) * 100) scores[1]
7.595993322203687
Lassen wir alle Daten weg, die sich mit unserem Backtest-Zeitraum überschneiden, damit unser Backtest eine echte Simulation der realen Marktbedingungen darstellt.
#Drop all the data that overlaps with your backtest period data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:] data
Abb. 3: Vergewissern wir uns, dass sich die Daten in unserem Datenrahmen nicht mit den Daten überschneiden, die wir für den Backtest verwenden.
Das lineare Modell gab uns die Gewissheit, dass die Vorhersage von Gewinnen/Verlusten, die durch die Strategie generiert werden, für uns besser sein könnte als die direkte Vorhersage von Preisen. In unseren Backtests für den Handel werden wir jedoch einen flexibleren Lerner verwenden, um sicherzustellen, dass das Modell so viele nützliche Informationen wie möglich aufnimmt.
from sklearn.ensemble import GradientBoostingRegressor model = GradientBoostingRegressor()
Kennzeichnung der Eingänge und des Ziels.
X = ['Open','High','Low','Close','MA Close 2','MA Open 2'] y = 'Trade Signal'
Passen wir das Modell an.
model.fit(data.loc[:,X],data.loc[:,y])
Bereiten wir den Export des Modells nach ONNX vor.
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
Definieren wir die Eingabegröße des Modells.
initial_types = [("float input",FloatTensorType([1,6]))]
Speichern wir das Modell.
onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(onnx_proto,"EURUSD Reward Model.onnx")
Erste Schritte in MQL5
Wir sind nun bereit, mit der Entwicklung unserer Handelsanwendung zu beginnen. Wir werden zwei Versionen der Strategie entwickeln, um die Wirksamkeit unserer Änderungen am Trainingsverfahren für unser statistisches Modell zu bewerten. Beide Versionen des Handelsalgorithmus werden unter identischen Bedingungen einem Backtest unterzogen. Machen wir uns mit diesen besonderen Bedingungen vertraut, damit wir die gleichen Informationen nicht unnötig wiederholen müssen.
Die erste wichtige Einstellung ist das Symbol, wie wir bereits besprochen haben, handeln wir in diesem Beispiel das Paar EURUSD. Wir werden das Symbol über einen Zeitraum von 5 Jahren, vom 1. Januar 2020 bis zum 1. April 2025, im täglichen Zeitrahmen handeln. Damit haben wir einen langen Zeitraum, um die Begründetheit unseres Antrags zu prüfen. Der Leser sollte sich daran erinnern, dass wir in Abbildung 3 alle Marktdaten, die uns über den 29. Dezember 2019 hinaus vorlagen, gelöscht haben.
Abb. 4: Die für unseren Backtest benötigten Daten für beide Versionen unserer Handelsstrategie
Und schließlich werden die Bedingungen, unter denen wir den Markt modellieren, so festgelegt, dass sie die Unvorhersehbarkeit des realen Handels nachahmen. Daher haben wir uns dafür entschieden, unsere Verzögerung auf Random Delay zu setzen und jeden Tick auf echte Ticks zu beziehen, um eine realistische Wiedergabe des Marktes zu erhalten.
Abb. 5: Die Einstellungen, die wir zur Nachahmung der Marktbedingungen verwenden, sind von entscheidender Bedeutung
Beginnen wir nun damit, eine Anwendung zu erstellen, die wir einem Backtest unterziehen können, um den Nutzen der von uns vorgeschlagenen Änderungen an unserem Schulungsverfahren zu bewerten. Wir beginnen damit, die Leistung unserer Strategie zu messen, ohne den neuen Modellierungsansatz zu verwenden, den wir formuliert haben, um Setups mit hoher Wahrscheinlichkeit zu identifizieren. Die erste Version unserer Handelsstrategie besteht im Wesentlichen aus der Anwendung der diskretionären Handelsstrategie, bei der die Überkreuzungen des gleitenden Durchschnitts gehandelt werden, wenn sie auftreten. Dadurch erhalten wir eine Benchmark, mit der wir unsere vorgeschlagene Belohnungsmodellierungsstrategie vergleichen können. Für den Anfang importieren wir zunächst die Handelsbibliothek.
//+------------------------------------------------------------------+ //| Reward Modelling.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Libraries we need | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Definieren wir die nützlichen Systemkonstanten. Diese Konstanten entsprechen den anderen Konstanten, die wir sowohl im MQL5-Skript als auch im Python-Skript verwendet haben.
//+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define MA_PERIOD 3 //--- Moving Average Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 10 //--- How far into the future we should forecast
Wir richten unsere globalen Variablen und technischen Indikatoren ein.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int fetch = HORIZON + 1; //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ int ma_handle,ma_o_handle; double ma_reading[],ma_o_reading[]; int position_timer;
Jedem Ereignis wurde eine entsprechende Methode zugeordnet, die aufgerufen wird, wenn die Ereignisbehandlung ausgelöst wird. Mit diesem Designstil lässt sich Ihre Codebasis leichter pflegen und in Zukunft erweitern, wenn Ihnen neue Ideen einfallen. Wenn Sie Ihren Code direkt in die Ereignisbehandlung schreiben, muss der Entwickler zahlreiche Codezeilen sorgfältig analysieren, bevor er Änderungen vornimmt, um sicher zu sein, dass nichts unbrauchbar wird, während der Entwickler bei unserem Entwurfsmuster nur einen Aufruf der gewünschten Funktion über die von uns bereitgestellten Funktionen legen muss, wenn er die Funktionalität der Anwendung erweitern möchte.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if(!setup()) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- release(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); } //+------------------------------------------------------------------+
Wir werden nun jede Methode der Reihe nach betrachten. Die erste Funktion, die wir einrichten werden, ist die Funktion, die für das Laden unserer technischen Indikatoren und das Zurücksetzen unseres Positionstimers verantwortlich ist. Der Positionstimer wird benötigt, um sicherzustellen, dass wir jeden Handel so lange halten, wie unsere Horizont-Systemkonstante eingestellt ist.
//+------------------------------------------------------------------+ //| Setup the system | //+------------------------------------------------------------------+ bool setup(void) { //---Setup our technical indicators ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE); ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN); position_timer = 0; return(true); }
Die Freigabemethode gibt einfach die technischen Indikatoren frei, die wir nicht verwenden. Es ist eine gute Praxis in MQL5, hinter sich selbst aufzuräumen.
//+------------------------------------------------------------------+ //| Release system variables we are no longer using | //+------------------------------------------------------------------+ void release(void) { IndicatorRelease(ma_handle); IndicatorRelease(ma_o_handle); return; }
Die Aktualisierungsmethode holt die aktuellen Preisniveaus und kopiert sie in unsere Indikatorpuffer. Darüber hinaus wird es auch verfolgen, wie lange unsere aktuelle Position geöffnet war, um sie rechtzeitig zu schließen.
//+------------------------------------------------------------------+ //| Update system parameters | //+------------------------------------------------------------------+ void update(void) { //--- Time stamps static datetime time_stamp; datetime current_time = iTime(Symbol(),PERIOD_D1,0); //--- We are on a new day if(time_stamp != current_time) { time_stamp = current_time; if(PositionsTotal() == 0) { //--- Copy indicator values CopyBuffer(ma_handle,0,0,fetch,ma_reading); CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading); //---Set the values as series ArraySetAsSeries(ma_reading,true); ArraySetAsSeries(ma_o_reading,true); find_setup(); position_timer = 0; } //--- Forecasts are only valid for HORIZON days if(PositionsTotal() > 0) { position_timer += 1; } //--- Otherwise close the position if(position_timer == HORIZON) Trade.PositionClose(Symbol()); } return; }
Und schließlich die Funktion zur Einrichtung der Suche. Unsere Setups werden immer dann identifiziert, wenn sich die gleitenden Durchschnitte überschneiden. Wenn der Eröffnungskurs den Schlusskurs übersteigt, wird dies als Verkaufssignal registriert. Ansonsten haben wir ein Kaufsignal.
//+------------------------------------------------------------------+ //| Find a trading oppurtunity | //+------------------------------------------------------------------+ void find_setup(void) { double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); vector ma_o,ma_c; ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1); ma_c.CopyIndicatorBuffer(ma_handle,0,0,1); if(ma_o[0] > ma_c[0]) { Trade.Sell(vol,Symbol(),ask,0,0,""); } if(ma_o[0] < ma_c[0]) { Trade.Buy(vol,Symbol(),bid,0,0,""); } return; }
Vergessen wir nicht, die zuvor definierten Systemkonstanten zu deaktivieren.
//+------------------------------------------------------------------+ //| Undefine system constatns | //+------------------------------------------------------------------+ #undef HORIZON #undef MA_PERIOD #undef MA_TYPE
Wir haben die Einstellungen, die wir für unseren Backtest verwenden werden, bereits kurz zuvor behandelt. Unser Ziel ist es, zu beobachten, wie effektiv unsere Strategie ohne die von uns entwickelten, statistischen Modellierungstechniken ist. Wir laden einfach den Expert Advisor und starten den Backtest mit den Einstellungen, die wir in Abb. 4 und 5 besprochen haben.
Abb. 6: Festlegung einer Benchmark
Unsere diskretionäre Version der Handelsstrategie war profitabel, auch wenn sie nur geringfügig profitabel zu sein scheint. Der Unterschied zwischen dem durchschnittlichen Gewinn und Verlust beträgt nur 0,9 $ und nur 51 % aller von ihm getätigten Geschäfte sind profitabel, was nicht sehr ermutigend ist. Unsere Sharpe Ratio liegt bei 0,62 und könnte sich möglicherweise verbessern, wenn wir unser System überarbeiten würden.
Abb. 7: Eine detaillierte Analyse der Leistung unserer diskretionären Handelsstrategie
Wenn wir nun die Salden- und Kapitalkurve analysieren, die diese Version der Handelsstrategie erzeugt, können wir sofort offensichtliche Mängel erkennen. Die Strategie ist instabil und unbeständig. Tatsächlich hat die Strategie nach vier Jahren Backtest im Februar 2024 fast wieder ihren Anfangsbestand zu Beginn des Backtests erreicht. Das Unternehmen hatte Mühe, aus einer Reihe von unrentablen Handelsgeschäften auszubrechen, die Ende 2020 begannen und vier Jahre lang bis 2024 andauerten. Dies ist für uns als algorithmische Händler nicht attraktiv.
Abb. 8: Visualisierung der Gewinn- und Verlustkurve, die durch unsere diskretionäre Version der Handelsstrategie erzeugt wird
Die Leistung unseres Expert Advisors verbessern
Verbessern wir nun unsere Handelsstrategie, indem wir unserer Anwendung die Fähigkeit geben, den menschlichen Denkprozess zu imitieren, bei dem man die Konsequenzen seiner Handlungen bedenkt, bevor man sich festlegt.
Wir beginnen damit, dass wir unser ONNX-Modell als Ressource aus unserem Systemdateiverzeichnis in die Anwendung importieren.
//+------------------------------------------------------------------+ //| System resources | //+------------------------------------------------------------------+ #resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];
Für die Anwendung unseres Modells werden einige zusätzliche globale Variablen benötigt. In erster Linie benötigen wir Variablen, die den Model-Handler und die Datenmenge darstellen, die wir abrufen sollen.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ long onnx_model; int fetch = HORIZON + 1;
Nun müssen wir das ONNX-Modell in der Setup-Funktion einrichten. Im folgenden Codebeispiel haben wir absichtlich Codesegmente weggelassen, die sich in beiden Versionen der Anwendung nicht geändert haben. Wir erstellen einfach das ONNX-Modell aus seinem Puffer, validieren das Modell und legen dann seine Eingabe- und Ausgabegröße entsprechend fest. Sollte ein Schritt auf diesem Weg fehlschlagen, brechen wir die Initialisierungsprozedur vollständig ab.
//+------------------------------------------------------------------+ //| Setup the system | //+------------------------------------------------------------------+ bool setup(void) { //---Omitted code that hasn't changed //--- Setup the ONNX model onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT); //--- Validate the ONNX model if(onnx_model == INVALID_HANDLE) { Comment("Failed to create ONNX model"); return(false); } //--- Register the ONNX model I/O parameters ulong input_shape[] = {1,6}; ulong output_shape[] = {1,1}; if(!OnnxSetInputShape(onnx_model,0,input_shape)) { Comment("Failed to set input shape"); return(false); } if(!OnnxSetOutputShape(onnx_model,0,output_shape)) { Comment("Failed to set output shape"); return(false); } return(true); }
Außerdem müssen wir, wenn die Anwendung freigegeben wird, die nicht mehr benötigten Systemressourcen freigeben
//+------------------------------------------------------------------+ //| Release system variables we are no longer using | //+------------------------------------------------------------------+ void release(void) { //--- Omitted code segments that haven't changed OnnxRelease(onnx_model); return; }
Wir werden die Anwendung anweisen, zunächst eine Prognose von unserem Handelsmodell einzuholen, bevor sie entscheidet, ob sie ein Geschäft tätigen soll. Wenn die Vorhersage unseres Modells über 0,5 liegt, bedeutet dies, dass unser Algorithmus davon ausgeht, dass das von unserer Strategie erzeugte Signal gewinnbringend ist, und uns die Erlaubnis zum Handeln erteilt. Wenn das nicht der Fall ist, werden wir abwarten, bis die ungünstigen Marktbedingungen ihren Lauf nehmen.
//+------------------------------------------------------------------+ //| Find a trading oppurtunity | //+------------------------------------------------------------------+ void find_setup(void) { //--- Skipped parts of the code base that haven't changed //--- Prepare the model's inputs vectorf model_input(6); model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0) - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0) - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0) - iLow(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0) - iClose(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]); model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]); //--- Prepare the model's output vectorf model_output(1); //--- We failed to run the model if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output)) Comment("Failed to obtain a forecast"); //--- Everything went fine else { Comment("Forecast: ",model_output[0]); //--- Our model forecasts that our strategy is likely to be profitable if(model_output[0] > 0.5) { if(ma_o[0] > ma_c[0]) { Trade.Sell(vol,Symbol(),ask,0,0,""); } if(ma_o[0] < ma_c[0]) { Trade.Buy(vol,Symbol(),bid,0,0,""); } } } return; }
Führen wir die Anwendung in einem Backtest mit den in Abb. 4 und 5 angegebenen Einstellungen aus, um einen fairen Vergleich zwischen den beiden Strategien zu ermöglichen.
Abb. 9: Durchführung unseres zweiten Backtests mit unserer überarbeiteten Version der Handelsstrategie, die Gewinne und Verluste modelliert
Unsere Sharpe Ratio stieg von 0,62 in der diskretionären Benchmark, die dieselbe Strategie anwendet, auf 1,07 in unserer aktuellen Iteration, was einer Steigerung von 72 % entspricht. Unser Gesamtnettogewinn stieg um 38 % von 117,13 $ auf 162,75 $, wenn wir die neue, algorithmisch definierte Strategie des „High Probability Setup“ anwenden. Der Anteil der Verlustgeschäfte sank um 17 % von 48,78 % auf 40,48 %. Und schließlich sank die Gesamtzahl der von der Strategie platzierten Handelsgeschäfte von 164 auf 126, was bedeutet, dass unser neues System mit nur 76 % der Gesamtzahl der vom alten System verwendeten Handelsgeschäfte 38 % mehr Gewinn erzielt hat, was effektiv bedeutet, dass wir effektiver sind als zuvor, weil wir höhere Renditen erzielen, während wir weniger Risiko eingehen.
Abb. 10: Eine detaillierte Zusammenfassung der Ergebnisse unserer neuen Handelsstrategie
Ich habe dem Leser die Salden- und Kapitalkurve der überarbeiteten Version unserer Handelsstrategie zur Verfügung gestellt und die Kurve der ursprünglichen Version der Strategie darunter gelegt, damit der Leser Vergleiche anstellen kann, ohne hin und her blättern zu müssen. Es ist deutlich zu erkennen, dass die ursprüngliche Version unserer Handelsstrategie noch vor Ablauf des ersten getesteten Jahres fast an den Breakeven-Punkt zurückfiel, während unsere neue Strategie diesen Zeitraum gut bewältigte.
Interessant ist, dass beide Strategien in der Zeit von Mai bis Dezember 2022 nur mäßige Ergebnisse erzielten. Dies kann ein Anzeichen für besonders instabile Marktphasen sein, die mehr Anstrengungen erfordern, um wirksam angegangen zu werden.
Abb. 11: Die Gewinn- und Verlustkurve unserer neuen Handelsstrategie, die in der Lage ist, die Konsequenzen ihres Handelns zu berücksichtigen
Abb. 12: Die Kapitalkurve, die von der ursprünglichen Version unserer Handelsstrategie erzeugt wurde, wurde zum leichteren Vergleich mit den neuen Ergebnissen, die wir erzielt haben, kopiert
Schlussfolgerung
Nach der Lektüre dieses Artikels hat der Leser eine neuartige Methode kennengelernt, um die Aufgabe des algorithmischen Handels mit überwachten statistischen Modellen anzugehen. Der Leser verfügt nun über das notwendige Wissen, um die Beziehungen zwischen seinen privaten Handelsstrategien und den von ihm gehandelten Märkten zu modellieren. Dies verschafft dem Leser einen Wettbewerbsvorteil gegenüber gelegentlichen Marktteilnehmern, die versuchen, den Markt direkt zu prognostizieren, was, wie wir gezeigt haben, nicht immer die beste Option für den Leser ist.
Dateiname | Beschreibung der Datei |
---|---|
Reward Modelling Benchmark.mq5 | Dies ist die traditionelle Version unserer Handelsstrategie, die nicht versucht, die Konsequenzen ihres Handelns zu berücksichtigen. Sie gewichtet alle Handelsmöglichkeiten gleich und geht davon aus, dass jeder Handel profitabel sein muss. |
Reward Modelling.mq5 | Dies ist die verfeinerte Version unserer Handelsstrategie, die explizit versucht, die Konsequenzen ihrer Handlungen abzuschätzen, bevor sie einen Handel tätigt. |
EURUSD Reward Model.onnx | Unser statistisches ONNX-Modell, das die Wahrscheinlichkeit schätzt, dass das von unserer Strategie erzeugte Signal profitabel sein wird. |
Reward Modelling.ipynb | Das Jupyter Notebook, mit dem wir unsere historischen Marktdaten analysiert und unser statistisches Modell angepasst haben. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17756
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.





- 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.