
Erstellen von selbstoptimierenden Expertenberatern in MQL5 (Teil 2): USDJPY Scalping Strategie
In unserer letzten Diskussion über den Aufbau von selbstoptimierenden Expert Advisors in MQL5 haben wir ein lineares Regressionsmodell erstellt, um Einstiegs- und Ausstiegssignale für unsere Handelsanwendung zu erzeugen; einen Link zum vorherigen Artikel finden Sie hier. Im Nachhinein betrachtet, brauchen wir vielleicht nicht alle beweglichen Teile, die in einem Modell für maschinelles Lernen vorhanden sind. Vielmehr können wir die Modelle des maschinellen Lernens als Beispiel dafür betrachten, wie man reale Probleme mit Hilfe dynamischer Regeln löst. Wir können dann dieselben einfachen Prinzipien des Denkens und der Logik nutzen, um unsere Handelsanwendungen zu höherer Rentabilität zu führen, ohne dass wir eine riesige Codebasis erstellen müssen.
Unser heutiges Thema ist der gewinnbringende Handel mit dem USDJPY-Paar im täglichen Zeitrahmen. Unsere Handelsstrategie wird auf Kerzenmustern beruhen. Insbesondere werden wir mit Umkehrmustern handeln, die durch „engulfing“ Kerzen entstehen. Unsere Regeln für eine Engulfing Aufwärtskerze sind erfüllt, wenn der Eröffnungskurs niedriger ist als der Schlusskurs des Vortages und der Schlusskurs höher ist als der Eröffnungskurs des Vortages. Ein Beispiel ist in Abb. 1 unten dargestellt. Es wird angenommen, dass diese Kerzenmuster zeigen, dass ein bestimmtes Kursniveau mit beträchtlicher Stärke abgelehnt wurde.
Abb. 1: Wir haben ein Beispiel für unser Aufwärtskerzenmuster identifiziert
Händler sind der Meinung, dass engulfing Kerzenmuster ein Zeichen dafür sind, dass sich ein neuer Trend auf dem Markt bildet. Wenn sie richtig erkannt werden, folgt ihnen in der Regel eine konsistente Kursentwicklung in Richtung des neuen Trends (siehe Abb. 2 unten). Dies bildet die Grundlage für die Handelsstrategie, die versucht, das Handelsmuster richtig zu erkennen. Engulfing Abwärtskerzen können verwendet werden, um den Beginn von Abwärtstrends zu erkennen. Wir verwenden dieselben Regeln, die wir soeben beschrieben haben, aber auf umgekehrte Weise.
Abb. 2: Unser Kerzenmuster war in diesem speziellen Beispiel zuverlässig
Normalerweise geht man davon aus, dass diese Strategien über alle Zeiträume hinweg gültig sind. Ich bin jedoch der Meinung, dass der tägliche Zeitrahmen am zuverlässigsten ist und habe ihn als Zeitrahmen für diese Übung ausgewählt. Versuchen wir, eine Strategie für den Handel mit Einstiegssignalen zu entwickeln, die sich aus unserem Verständnis dieser besonderen Marktmuster ergeben. Es wird uns auch interessieren, ob wir unsere Rentabilität durch Anpassungen der ursprünglichen Strategie steigern können.
Erste Schritte in MQL5
Unser Programm besteht aus 6 Hauptbestandteilen, die wir benötigen, um unser Ziel des profitablen Handels mit Kerzenmustern zu erreichen.
Bestandteil | Zweck |
---|---|
Initialisierung | Dieser Teil unseres Systems ist für das Laden und Setzen von globalen Variablen zuständig. |
DeInitialisierung | Freigeben von Ressourcen, die unsere Anwendung nicht mehr benötigt, um eine stabile Endnutzererfahrung zu gewährleisten. |
OnTick | Aktualisieren der Systemvariablen und die Suche im aktuellen Chart nach unseren Kerzenmustern. |
Nutzerdefinierte Funktionen | Sie führen spezielle Aufgaben aus, die zur Erreichung unseres Ziels erforderlich sind. |
Systemkonstanten | Konstanten, die nicht vom Endnutzer geändert werden sollen. |
Globale Variablen | Sie behalten den Überblick über die zuletzt erteilte Auftragsart, die aktuell angebotenen Marktpreise und die Volatilitätswerte. Zu diesem Zweck wurden Variablen wie „trade“ und „bid“ erstellt. Die Average True Range hilft bei der Platzierung von Stop Losses und Gewinnmitnahmen für unsere Positionen. |
Zu Beginn wird unsere Strategie nur Handelsgeschäfte platzieren, wenn unser Kerzenmuster gefunden wird. Wenn das Muster erkannt wird und wir keine offenen Positionen haben, nehmen wir das Signal auf und verwenden die ATR, um unseren Stop-Loss zu setzen und anzupassen. Andernfalls wird unser System alle von uns eröffneten Handelsgeschäfte verwalten. Daher werden die globalen Variablen unseres Systems sein:
Variabel | Beschreibung |
---|---|
trade | Diese Variable soll uns darüber informieren, welche Art von Position derzeit geöffnet sind. Dies erleichtert uns die Aktualisierung unserer Stopps und alle anderen Aufgaben, die uns in Zukunft einfallen könnten. |
atr_handler | Sie ist wichtig für die konsequente Aktualisierung unserer Stop-Losses. |
bid, ask | Damit behalten wir die Marktpreise im Auge. |
Um zu beginnen, werden wir zunächst eine Benchmark-Version unserer Handelsstrategie erstellen. Wir beginnen mit der Definition von Systemkonstanten. Diese Konstanten werden mit der Richtlinie #define erstellt. Die Direktive #define weist den in unseren MQL5-Editor eingebetteten Präprozessor an, jedes Vorkommen des von uns angegebenen Makrobezeichners zu ersetzen und an dessen Stelle das zu setzen, was wir rechts vom Makrobezeichner zugewiesen haben.
Die erste Systemkonstante, die wir definiert haben, ist „SYMBOL“. Wenn wir unsere Anwendung kompilieren, wird der Präprozessor alle Instanzen von „SYMBOL“ in unserem Code durch den Wert „USDJPY“ ersetzen. Diese einfache Funktion gibt uns die vollständige und vorhersehbare Kontrolle über das System und garantiert uns die Konsistenz unserer Tests - das sind Attribute, die wir attraktiv finden.
//+------------------------------------------------------------------+ //| Dynamic Stops Benchmark.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| This trading application is intended to serve as our benchmark. | //| Our goal is to learn what it will take to surpass the benchmark. | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define SYMBOL "USDJPY" //--- System pair #define DAILY PERIOD_D1 //--- Daily time frame #define VOL 0.1 //--- Trading volume #define ATR_PERIOD 14 //--- Technical Indicator ATR Period #define ATR_MULTIPLE 2 //--- Stop loss ATR multiple
Außerdem müssen wir die Handelsbibliothek einbinden.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade;
Nun werden wir unsere globalen Variablen definieren. Diese Variablen helfen uns, unsere offenen Positionen und die aktuellen Marktkurse zu verfolgen.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int trade = 0; int atr_handler; double atr[]; double bid,ask;
Bei der Initialisierung rufen wir eine spezielle Funktion auf, die für die Initialisierung unserer Systemvariablen zuständig ist.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); }
Wenn wir unsere Handelsanwendung nicht mehr nutzen, werden wir die technischen Indikatoren, die wir nicht mehr verwenden, freigeben.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicator release(); }
Wir werden unsere Systemvariablen einmal am Ende des Tages aktualisieren. Diese können Sie nach Belieben ändern, um das Risikomanagement zu verbessern. Ich habe mich dafür entschieden, die Systemvariablen nur einmal am Tag zu aktualisieren, damit die Backtests rechtzeitig abgeschlossen werden und wir die Änderungen, die wir vornehmen, besser vergleichen können.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); } //+------------------------------------------------------------------+
Die erste nutzerdefinierte Funktion, die wir erstellen werden, wird für die Freigabe von Systemressourcen verantwortlich sein, die wir nicht mehr verbrauchen.
//+------------------------------------------------------------------+ //| Custom Functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Release variables we don't need | //+------------------------------------------------------------------+ void release(void) { IndicatorRelease(atr_handler); }
Wir aktualisieren unsere Systemvariablen einmal pro Tag.
//+------------------------------------------------------------------+ //| Update system | //+------------------------------------------------------------------+ void update(void) { static datetime daily_timestamp; datetime daily_time = iTime(SYMBOL,DAILY,0); if(daily_timestamp != daily_time) { //--- Update the time daily_timestamp = daily_time; //--- Update system variables daily_update(); //--- Do we have an oppurtunity to trade? if((PositionsTotal() == 0)) find_setup(); //--- Do we have positions to manage? if(PositionsTotal() > 0) manage_setup(); } }
Wir verwalten unsere Handelsgeschäfte, indem wir Stop-Loss und Take-Profit nur dann aktualisieren, wenn die neue Position des Stop-Loss oder Take-Profits profitabler ist.
//+------------------------------------------------------------------+ //| Manage our trades | //+------------------------------------------------------------------+ void manage_setup(void) { //--- Select the position if(PositionSelect(SYMBOL)) { //--- Get ready to update the SL/TP double initial_sl = PositionGetDouble(POSITION_SL); double initial_tp = PositionGetDouble(POSITION_TP); double buy_sl = (ask - (ATR_MULTIPLE * atr[0])); double sell_sl = (bid + (ATR_MULTIPLE * atr[0])); double buy_tp = (ask + (ATR_MULTIPLE * atr[0])); double sell_tp = (bid - (ATR_MULTIPLE * atr[0])); double new_sl = ((trade == 1) && (initial_sl < buy_sl))? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl); double new_tp = ((trade == 1) && (initial_tp < buy_tp))? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp); //--- Update the position Trade.PositionModify(SYMBOL,new_sl,new_tp); } }
Einrichten der technischen Indikatoren. Bis jetzt haben wir nur 1 technischen Indikator zu verwalten.
//+------------------------------------------------------------------+ //| Get our technical indicators ready | //+------------------------------------------------------------------+ void setup(void) { atr_handler = iATR(SYMBOL,DAILY,ATR_PERIOD); }
Aktualisieren des Systemstatus.
//+------------------------------------------------------------------+ //| Daily update routine | //+------------------------------------------------------------------+ void daily_update(void) { //--- Get current prices ask = SymbolInfoDouble(SYMBOL,SYMBOL_ASK); bid = SymbolInfoDouble(SYMBOL,SYMBOL_BID); //--- Update Technical Indicators CopyBuffer(atr_handler,0,0,1,atr); //--- Check for engulfing candles. int candles_state = check_candles(); //--- Give feedback Comment("Candle State: ",candles_state); }
Prüfen unserer Kerzenmuster. Wenn das Muster gefunden wird, geben wir entweder 1 oder -1 zurück und erzeugen einen Kauf oder Verkauf. Andernfalls werden wir warten.
//+------------------------------------------------------------------+ //| Check if we have any engulfing candles | //+------------------------------------------------------------------+ int check_candles(void) { //--- Return 1 if we have a bullish engulfing candle if((iOpen(SYMBOL,DAILY,0) < iClose(SYMBOL,DAILY,1)) && (iClose(SYMBOL,DAILY,0) > iOpen(SYMBOL,DAILY,1))) return(1); //--- Return -1 if we have a bearish engulfing candle if((iOpen(SYMBOL,DAILY,0) > iClose(SYMBOL,DAILY,1)) && (iClose(SYMBOL,DAILY,0) < iOpen(SYMBOL,DAILY,1))) return(-1); //--- Otherwise return 0 return(0); }
Unser System weiß, dass es ein Handels-Setup gefunden hat, wenn der Kerzenstatus nicht 0 ist. Ansonsten gibt es im Moment nichts weiter zu tun.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Our sentiment is bullish int candles_state = check_candles(); if(candles_state == 1) { Trade.Buy(VOL,SYMBOL,ask,(ask - (ATR_MULTIPLE * atr[0])),(ask + (ATR_MULTIPLE * atr[0])),""); trade = 1; } //--- Our sentiment is bearish if(candles_state == -1) { Trade.Sell(VOL,SYMBOL,bid,(bid + (ATR_MULTIPLE * atr[0])),(bid - (ATR_MULTIPLE * atr[0])),""); trade = -1; } } //+------------------------------------------------------------------+
In Abbildung 3 sehen Sie, wie unser System aussieht. Unser System verfolgt das Vorhandensein oder Nichtvorhandensein unseres Kerzenmusters und veranlasst Handelsgeschäfte, wenn das Muster gefunden wird. Bei unserem derzeitigen Setup werden die Stop-Loss und Take-Profit der Positionen einmal pro Tag, am Ende des Tages, verschoben.
Abb. 3: Visualisierung unserer Handelsstrategie auf dem täglichen Zeitrahmen des USDJPY
Wir werden unsere neue Strategie mit historischer Daten von über 4 Jahren testen, vom 1. Januar 2020 bis Ende November 2024. Wenn Sie diese Einstellungen ändern möchten, müssen Sie auch die entsprechenden Änderungen an den Systemvariablen vornehmen. Andernfalls wird unser System weiterhin den USDJPY auf dem täglichen Zeitrahmen handeln, unabhängig davon, welche Symbole und Zeitrahmen wir angeben.
Abb. 4: Die wichtigsten Einstellungen für unseren Backtest
Die zufällige Verzögerung kommt den realen Handelsszenarien am nächsten und ermöglicht uns einen Stresstest unseres Systems. Stellen Sie sicher, dass Sie die „Einzahlung“ und die Hebelwirkung Ihres Kontos an die von Ihnen beabsichtigten Handelseinstellungen anpassen, wenn Sie die Strategie in der Praxis anwenden möchten.
Abb. 5: Auswahl der Modellierungsart und der Kontogröße für unseren Backtest
Die durch die Strategie erzeugte Saldenkurve ist vielversprechend. Unsere Scalping-Strategie ließ die Kontogröße bei diesem Backtest um etwa 4 % ansteigen. Wie bei jeder Handelsstrategie gab es auch hier anhaltende Verlustphasen. Das Bemerkenswerte an dieser Strategie ist jedoch ihre Fähigkeit, sich von Verlustphasen zu erholen.
Abb. 6: Visualisierung der Saldenkurve im Zeitverlauf
Schauen wir uns nun die Leistung unserer Strategie genauer an. In ihrer jetzigen Form hatte unsere Strategie eine Sharpe Ratio von 1,12 und eine Erfolgsquote von 44,68 %. Was ist nötig, um den durchschnittlichen Verlust von 133,22 $ zu verringern und ihn näher an 0 heranzuführen, während wir gleichzeitig die Auswirkungen auf den durchschnittlichen Gewinn minimieren?
Abb. 7: Eine detaillierte Analyse unserer Backtest-Leistung
Verbesserung unserer Ergebnisse
Unser System ist in seiner jetzigen Form profitabel. Gibt es irgendwelche Änderungen, die wir vornehmen können, um mehr Kontrolle über die Verlustgeschäfte auszuüben? Ich werde ein paar Änderungen an der ursprünglichen Strategie vorschlagen:
Vorgeschlagene Änderung | Verwendungszweck |
---|---|
Zusätzliche Bestätigungen | Indem wir eine zusätzliche Bestätigungsstrategie parallel zu unserer bereits profitablen Strategie einsetzen, können wir möglicherweise mehr unprofitable Geschäfte herausfiltern. |
Zusätzliches Polster für den Stop-Loss hinzufügen | Wir wollen die Anzahl der Stopp-Outs aus Handelsgeschäften im Gewinn minimieren. |
Berücksichtigung der Marktvolatilität | Jeder Markt hat potenziell ein einzigartiges Volatilitätsniveau. Unsere Handelsstrategie sollte versuchen, historische Volatilitätsniveaus zu berücksichtigen, um die aktuellen Kursniveaus mit einem gewissen Kontext zu analysieren, wie professionelle menschliche Händler. |
Wir hoffen, dass wir durch die Umsetzung dieser Änderungen den Anteil der unrentablen Handelsgeschäfte verringern können. Bei diesen Entscheidungen muss immer ein Kompromiss gefunden werden. Letztendlich wird unsere neue Strategie gelegentlich profitable Handelsgeschäfte verpassen, die unsere alte Strategie problemlos bewältigt hätte.
Es liegt auf der Hand, dass die Tatsache, dass wir keine Kontrolle über die Höhe unseres durchschnittlichen Verlustes haben, uns letztendlich den gesamten Gewinn kosten könnte, den wir uns erarbeitet haben. Um die gewünschten Änderungen zu realisieren, müssen wir Änderungen an unserer aktuellen Version der Anwendung vornehmen.
Systemveränderungen | Beschreibung |
---|---|
Neue Systemvariablen | Um die Marktvolatilität zu berücksichtigen, müssen wir zunächst entscheiden, wie viele Daten aus der Vergangenheit wir abrufen sollen. Dies wird für uns durch eine neue Systemvariable mit dem passenden Namen „fetch“ erledigt. Außerdem müssen wir die Parameter der technischen Indikatoren festlegen, die wir verwenden wollen. |
Technische Indikatoren | Eine zusätzliche Bestätigung können wir durch den Einsatz von Handelsstrategien mit technischen Indikatoren erhalten. Heute werden wir eine Strategie des gleitenden Durchschnittskanals anwenden. Daher werden wir neue Indikator-Handles und -Puffer erstellen, um diese neuen Informationen zu speichern. |
Zusammentreffen von Signalen | Wir werden eine neue globale Variable mit dem Namen „sentiment“ erstellen. Sein Wert ist 1 oder -1, wenn sowohl unser Kerzenmuster als auch die technischen Indikatoren entweder beide aufwärts (1) oder beide abwärts (-1) zeigen. Andernfalls wird der Wert 0 sein. Unser System platziert nur Handelsgeschäfte, wenn unser Sentiment-Wert ungleich 0 ist. |
Nutzerdefinierte Funktionen | Um das gewünschte Verhalten unseres Systems zu erreichen, müssen wir einige der nutzerdefinierten Funktionen, die wir bereits erstellt haben, erweitern und auch einige neue Funktionen erstellen. |
Überblick über die Bestätigungsstrategie
Unsere Bestätigungsstrategie wird auf Handelsstrategien mit gleitendem Durchschnittskanal basieren. Diese Strategie basiert auf 2 gleitenden Durchschnitten, die den Höchst- bzw. Tiefstkursen folgen. Die 2 gleitenden Durchschnitte bilden einen Kanal. Beachten Sie, dass sich die gleitenden Durchschnitte nicht überkreuzen. Daher werden unsere Einstiegssignale generiert, wenn sich eine Kerze vollständig außerhalb der Region zwischen den 2 gleitenden Durchschnitten bildet.
Der Grund für diese Strategie ist, dass Kursniveaus zwischen dem oberen und dem unteren gleitenden Durchschnitt als stabil angesehen werden. Bilden sich hingegen Kursniveaus jenseits der Region zwischen den beiden gleitenden Durchschnitten, so nehmen wir ein Ungleichgewicht auf dem Markt wahr. Die Strategie legt nahe, dass es sich dabei um die Bildung eines neuen Trends in Richtung des Ungleichgewichts handelt. Die folgende Abbildung 8 zeigt, wie wir die Strategie anwenden würden.
Der rote Pfeil stellt eine optimale Region dar, um eine Verkaufsposition gemäß der Strategie einzunehmen. Das Setup wird in der Regel so lange als gültig angesehen, bis die Kurse wieder in den Kanal zurückfinden. Zu diesem Zeitpunkt können unsere Positionen geschlossen werden, und wir warten auf das nächste Ungleichgewicht, das entdeckt wird. Dieses zweite Ungleichgewicht ist durch den blauen Pfeil gekennzeichnet. Da das Ungleichgewicht oberhalb unseres gleitenden Durchschnittskanals auftrat, hätten wir dies als Signal zum Kauf interpretiert.
Abb. 8: Unsere Strategie des Moving-Average-Channels zur Identifizierung von Einstiegs- und Ausstiegspunkten
Wir werden diesen Gedanken weiter ausbauen, indem wir auch historische Höchst- und Tiefststände des Marktes berücksichtigen. Wir berechnen den Mittelwert, der sich aus den historischen Höchst- und Tiefstpreisen des letzten Jahres auf dem Markt ergibt. Darüber hinaus werden wir die Informationen nutzen, um die Platzierung von Kaufpositionen einzuschränken, wenn der Schlusskurs über dem historischen Hoch-Tief-Mittelwert liegt, und das Gegenteil gilt für unsere Verkaufspositionen.
Die rote, waagerechte Linie in Abb. 9 symbolisiert den Durchschnitt der Höchst- und Tiefstpreise des letzten Jahres bis heute. Dieser Mittelwert wird täglich von unserem System aktualisiert und dient als Objektiv für unsere Anwendung, um die Preisniveaus darunter zu sehen.
Abb. 9: Der historische Mittelwert der Höchst- und Tiefstpreise des letzten Jahres, die auf dem Markt angeboten wurden
Typ der Position | Kriterien für neue Positionen |
---|---|
Kauf | Es hat sich eine Aufwärtskerze gebildet, die oberhalb des gleitenden Durchschnittskanals liegt, und die Preisniveaus liegen über den Niveaus der durchschnittlichen jährlichen Volatilität. |
Verkauf | Oberhalb des gleitenden Durchschnittskanals hat sich eine Abwärtskerze gebildet, und die Preisniveaus liegen über den durchschnittlichen jährlichen Volatilitätsniveaus. |
Wir hoffen, dass wir durch den gemeinsamen Einsatz unserer beiden Strategien die unrentablen Handelsgeschäfte herausfiltern können, die unser altes System gestört haben, während wir die rentablen Handelsgeschäfte, die wir behalten wollen, beibehalten können. Lassen Sie uns mit der Umsetzung dieser Änderungen beginnen, um zu sehen, wie wirksam sie sein werden. Zunächst müssen wir neue Systemvariablen definieren, die die Perioden unseres gleitenden Durchschnittskanals und die Menge der historischen Daten festlegen, die wir zur Berechnung unseres Mittelwerts abrufen.
//+------------------------------------------------------------------+ //| USDJPY Price Action Benchmark 2 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| This trading application is intended to surpass our benchmark. | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ //--- I have intentionally omitted parts of the system that remained unchanged #define FETCH 365 //--- How much should we fetch? #define MA_PERIOD 90 //--- Moving average period
Wir benötigen auch einige zusätzliche globale Variablen, um die von uns definierten Marktzustände zu verfolgen.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int sentiment = 0; int trade = 0; int ma_high_handler, ma_low_handler; double ma_high[],ma_low[];
Der Hauptteil unserer Bewerbung bleibt unverändert. Einige der Funktionen, die aufgerufen werden, haben sich jedoch geändert.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our system varaibles setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release any resources release(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update system variables update(); } //+------------------------------------------------------------------+
Betrachten wir nun die Änderungen, die an den nutzerdefinierten Funktionen vorgenommen wurden. Die ersten beiden Änderungen betreffen das Laden unserer technischen Indikatoren und die anschließende Freigabe derselben.
//+------------------------------------------------------------------+ //| Custom Functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Release our technical indicators | //+------------------------------------------------------------------+ void release(void) { IndicatorRelease(atr_handler); IndicatorRelease(ma_low_handler); IndicatorRelease(ma_high_handler); }
Diese Änderungen an unserer Codebasis gehen Hand in Hand und sind einfach zu verstehen.
//+------------------------------------------------------------------+ //| Get our technical indicators ready | //+------------------------------------------------------------------+ void setup(void) { atr_handler = iATR(SYMBOL,DAILY,ATR_PERIOD); ma_high_handler = iMA(SYMBOL,DAILY,MA_PERIOD,0,MODE_EMA,PRICE_HIGH); ma_low_handler = iMA(SYMBOL,DAILY,MA_PERIOD,0,MODE_EMA,PRICE_LOW); }
Auch unsere tägliche Aktualisierungsroutine muss erweitert werden. Uns interessiert nun auch, wie sich das aktuelle Preisniveau im Vergleich zu dem Ausmaß des historischen Rauschens verhält, das in diesem Markt erwartet werden muss. Wenn unsere Kerzenmuster und Kursniveaus uns eine passende Stimmung vermitteln, dann werden wir die Bestätigung durch unseren gleitenden Durchschnittskanal suchen, ob jetzt ein guter Zeitpunkt ist, um unseren Handel durchzuführen.
//+------------------------------------------------------------------+ //| Daily update routine | //+------------------------------------------------------------------+ void daily_update(void) { //--- Get current prices ask = SymbolInfoDouble(SYMBOL,SYMBOL_ASK); bid = SymbolInfoDouble(SYMBOL,SYMBOL_BID); //--- Update Technical Indicators CopyBuffer(atr_handler,0,0,1,atr); CopyBuffer(ma_high_handler,0,0,1,ma_high); CopyBuffer(ma_low_handler,0,0,1,ma_low); //--- Check for engulfing candles. int candles_state = check_candles(); //--- Compare current price levels to historical price levels in the market int price_state = check_price_levels(); //--- Check our tech //--- What is our sentiment? //--- Our sentiment is well defined. if(candles_state == price_state) sentiment = candles_state; //--- Wait. if(candles_state != price_state) sentiment = 0; //--- Give feedback Comment("Sentiment: ",sentiment,"\nCandle State: ",candles_state,"\nPrice State: ",price_state); }
Wir werden den Vektortyp von MQL5 verwenden, um unsere Marktstatistiken einfach und schnell zu berechnen und zu verfolgen.
//+------------------------------------------------------------------+ //| Check if we are closer to the all time high or low | //+------------------------------------------------------------------+ int check_price_levels(void) { //--- Get historical prices vector highs = vector::Zeros(FETCH); vector lows = vector::Zeros(FETCH); highs.CopyRates(SYMBOL,DAILY,COPY_RATES_HIGH,0,FETCH); lows.CopyRates(SYMBOL,DAILY,COPY_RATES_LOW,0,FETCH); //--- First we shall calculate the mid point between the all time high and low vector mid = ((highs + lows) / 2); //--- Return 1 if we are above the mid point if(iClose(SYMBOL,DAILY,0) > mid.Mean()) return(1); //--- Return -1 if we are above the mid point if(iClose(SYMBOL,DAILY,0) < mid.Mean()) return(-1); //--- Otherwise return 0 return(0); }Unsere neuen Regeln für die Suche nach einem Handelsaufbau werden 2 zusätzliche Filter berücksichtigen. Das Kursniveau in Bezug auf den Jahresmittelwert und das Kursniveau in Bezug auf den Kanal des gleitenden Durchschnitts. Wenn beide Strategien übereinstimmen, werden wir unseren Handel entsprechend platzieren.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Our sentiment is bullish if(sentiment == 1) { if((iOpen(SYMBOL,DAILY,0) > ma_high[0]) && (iClose(SYMBOL,DAILY,0) > ma_high[0])) { Trade.Buy(VOL,SYMBOL,ask,(ask - (ATR_MULTIPLE * atr[0])),(ask + (ATR_MULTIPLE * atr[0])),""); trade = 1; } } //--- Our sentiment is bearish if(sentiment == -1) { if((iOpen(SYMBOL,DAILY,0) < ma_low[0]) && (iClose(SYMBOL,DAILY,0) < ma_low[0])) { Trade.Sell(VOL,SYMBOL,bid,(bid + (ATR_MULTIPLE * atr[0])),(bid - (ATR_MULTIPLE * atr[0])),""); trade = -1; } } } //+------------------------------------------------------------------+
Wir können uns unsere Strategie in Aktion ansehen. Beachten Sie, dass unsere Strategie nun über 3 Bedingungen verfügt, die erfüllt sein müssen, bevor wir eine Position eingehen. Wir hoffen, dass durch eine sorgfältige Auswahl der richtigen Bedingungen nicht alle zufällig erfüllt werden.
Abb. 10: Wir testen unsere überarbeitete USDJPY-Scalping-Strategie erneut anhand historischer Marktdaten
Wie bereits erwähnt, werden die Einstellungen für die Dauer und den Zeitraum des Backtests beibehalten, um die Konsistenz zwischen unseren beiden Tests zu gewährleisten. Daher stimmen unsere Daten mit den Daten des vorherigen Tests überein.
Abb. 11: Unsere Einstellungen für den Backtest werden in beiden Tests beibehalten
Denken Sie daran, dass Sie diese Einstellungen an die Umgebung anpassen können, in der Sie sie verwenden möchten.
Abb. 12: Die zweite Gruppe von Einstellungen für unseren Backtest
Die von unserer neuen Strategie erzeugte Kapitalkurve weist im Vergleich zu unserem ersten Backtest weniger Perioden mit einem Drawdown auf. Im Zeitraum zwischen Januar 2020 und dem nahenden Dezember 2023 befand sich die von unserer ursprünglichen Strategie erzeugte Kapitalkurve beispielsweise an einer Stelle und oszillierte um das ursprüngliche Gleichgewicht. Unsere neue Kapitalkurve weist diese unerwünschte Eigenschaft nicht auf. Unsere Kapitalkurve entwickelte sich von September 2022 bis zum Ende des Backtests in einem weniger volatilen Trend.
Abb. 13: Die durch unsere überarbeitete Handelsstrategie erzeugte Kapitalkurve
Bei näherer Betrachtung stellen wir fest, dass wir unser Ziel erreicht haben, den durchschnittlichen Verlust und den Anteil der Verlustgeschäfte näher an 0 zu bringen. Allerdings konnten wir den Anteil der Verlustgeschäfte nur geringfügig reduzieren, nämlich von etwa 55 % auf etwa 54 %. Außerdem haben unsere Änderungen auch die Rentabilität unserer Handelsstrategie verringert. Dies ist kein wesentliches Problem, denn wir können es durch eine sichere Vergrößerung unserer Losgröße korrigieren. Marktplätze sind dynamische Umgebungen, und die neuen Sicherheitsmaßnahmen, die wir eingeführt haben, können sich in Zukunft als unschätzbar erweisen.
Abb. 14: Eine detaillierte Analyse unserer zweiten Handelsstrategie
Schlussfolgerung
In diesem Artikel haben wir uns mit dem Potenzial befasst, das sich aus dem Handel mit den von Kerzenmustern generierten Signalen ergibt. Obwohl es viele Kritikpunkte gegen solche Strategien gibt, wie z.B. die Tatsache, dass es möglich ist, die Form des Kerzenmusters zu beobachten, aber es wird nicht immer von der gleichen Preisaktion danach gefolgt, kann die Gültigkeit der Strategie in Frage stellen.
Wenn Sie jedoch die Anpassungen, die wir in dieser Strategie besprochen haben, befolgen und Ihr eigenes Verständnis des Marktes hinzufügen, glaube ich, dass alle Zweifel an der Rentabilität der Strategie vernünftig ausgeräumt werden können. Die Herausforderung besteht darin, dass uns nicht immer klar ist, wie sich die von uns vorgenommenen Änderungen auf die Rentabilität der Strategie auswirken werden.
Datei | Beschreibung |
---|---|
USDJPY Price Action Benchmark | Diese Anwendung war die ursprüngliche, volatile Version unserer Handelsstrategie, sie war profitabler, aber auch mit mehr Risiko verbunden. |
USDJPY Price Action Strategy 2 | Dies ist die verfeinerte Version der Strategie, die wir gemeinsam entwickelt haben. Sie ist genauso profitabel und versucht, ihre Verluste zu minimieren. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16643





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