Kagi-Chart in MQL5 beherrschen (Teil 2): Implementierung des automatisierten Kagi-basierten Handels
Einführung
Im ersten Teil dieser Serie haben wir eine vollständige Kagi-Chart-Engine in MQL5 erstellt. Wir haben gelernt, wie man Preisdaten sammelt, die Kagi-Struktur aufbaut und die einzelnen Liniensegmente im Chart zeichnet. Am Ende von Teil 1 hatten wir ein voll funktionsfähiges Kagi-Chart, das bei jedem neuen Balken aktualisiert wird.
In diesem zweiten Teil gehen wir von der Chart-Konstruktion zum tatsächlichen Handel über. Unser Ziel ist es, den Kagi-Chart in einen funktionierenden Expert Advisor zu verwandeln, der auf Veränderungen der Marktstruktur reagieren kann. Wir werden neue Funktionen einführen, die es dem EA ermöglichen, Umkehrsignale zu erkennen, Handelsgeschäfte zu platzieren, Risiken zu verwalten und offene Positionen zu verwalten. Wir werden auch visuelle Marker hinzufügen, damit der Händler genau sehen kann, wann Signale auftreten.
Dieser Teil baut direkt auf der Kagi-Engine auf. Jede neue Funktion wird in einer klaren und einfachen Weise hinzugefügt, sodass die Leser der Logik ohne Schwierigkeiten folgen können. Am Ende dieses Artikels werden Sie ein vollständiges, auf Kagi basierendes Handelssystem haben, das für jedes Instrument in MetaTrader 5 verwendet werden kann.
Neue Funktionen für das Handelsmodul
In diesem Teil der Serie erweitern wir den Kagi-Chart zu einem kompletten Handelssystem. Bevor wir mit dem Programmieren beginnen, ist es wichtig, dass der Leser den MetaEditor 5 öffnet und die Quelldatei aus dem ersten Teil lädt. Die Datei heißt KagiTraderPart1.mq5 und ist beigefügt. Die gesamte neue Logik wird auf diesem Fundament aufsetzen.
In diesem Abschnitt stellen wir jede neue Funktion auf einer hohen Ebene vor. Wir fügen auch den kleinen vorbereitenden Code hinzu, den wir später benötigen werden. Das Ziel ist es, dem Leser zu helfen, zu verstehen, was hinzugefügt wird und warum es für die Handelsmaschine wichtig ist.
Visuelle Marker für Kauf- und Verkaufssignale
Das erste Merkmal ist die Einführung von visuellen Markierungen. Diese Markierungen zeigen genau an, wann Kauf- oder Verkaufssignale auftreten. Sie helfen dem Händler zu verstehen, wie der EA auf Kagi-Umkehrungen reagiert. Der EA zeigt einen kleinen Pfeil über oder unter dem Kursbalken an, um Kauf- oder Verkaufs-Signale deutlich zu markieren.
Möglichkeit, den Handel zu aktivieren oder zu deaktivieren
Einige Nutzer möchten vielleicht, dass der EA nur als visueller Indikator fungiert. Andere Nutzer möchten vielleicht, dass der EA automatisch Handelsgeschäfte platziert. Aus diesem Grund führen wir einen Eingabeparameter ein, der steuert, ob der Handel aktiv ist oder nicht. Der Parameter wird unter den vorhandenen Eingängen platziert.
input group "Trading" input bool enableTrading = true;
Wenn enableTrading true ist, wird der EA neue Positionen eröffnen. Wenn es false ist, platziert der EA keine Handelsgeschäfte, aber das Kagi-Chart wird weiterhin normal aktualisiert.
Kontrolle über die Richtung des Handels
Verschiedene Händler haben unterschiedliche Präferenzen. Einige bevorzugen ausschließlich Kaufpositionen, andere ausschließlich Verkaufspositionen. Viele wollen in beide Richtungen handeln. Um diese Flexibilität zu unterstützen, erstellen wir eine kleine Enumeration. Sie wird unterhalb der bestehenden Enumeration platziert.
enum ENUM_TRADE_DIRECTION
{
ONLY_LONG,
ONLY_SHORT,
TRADE_BOTH
};
Nach seiner Definition führen wir einen neuen Eingabeparameter ein.
input ENUM_TRADE_DIRECTION direction = TRADE_BOTH;
Dadurch wird ein einfacher Schalter geschaffen. Der Nutzer kann einen der drei Modi wählen. Der EA überprüft diesen Wert, bevor er einen neuen Handel eröffnet. Wenn der Nutzer ONLY_LONG auswählt, ignoriert der EA Verkaufssignale. Wenn der Nutzer ONLY_SHORT auswählt, ignoriert der EA Kaufsignale. Wenn TRADE_BOTH ausgewählt ist, handelt der EA in beide Richtungen.
Eröffnung von Kaufpositionen bei Umkehrung von Yin zu Yang

Eine große Stärke des Kagi-Chart ist seine Fähigkeit, klare Umkehrungen auf der Grundlage von Veränderungen der Marktstärke anzuzeigen. Ein Wechsel von einer Yin-Linie zu einer Yang-Linie zeigt, dass die Käufer die Kontrolle übernommen haben. Daher wird der EA eine Kaufposition eröffnen, wenn die Kagi-Struktur von Yin zu Yang wechselt. Damit ist der Beginn einer möglichen Aufwärtsbewegung eingefangen. Im weiteren Verlauf des Artikels werden wir die Logik kodieren, die diese Umkehrung in Echtzeit erkennt.
Eröffnung von Verkaufspositionen bei Umkehrung von Yang zu Yin

Eine Umkehrung von Yang zu Yin zeigt, dass die Verkäufer die Kontrolle übernommen haben. Dies ist häufig der Beginn einer Abwärtsbewegung. Der EA eröffnet eine Verkaufsposition, wenn die Kagi-Linie von Yang nach Yin wechselt. Dies ergänzt die Kauflogik. Diese beiden Merkmale bilden zusammen den Kern der Handelsstrategie. Im Abschnitt über die Umsetzung werden wir zeigen, wie man diesen Übergang auf zuverlässige Weise erfassen kann.
Die Wahl zwischen manueller und automatischer Losgröße
Händler gehen unterschiedlich mit Risiken um. Einige ziehen es vor, eine feste Losgröße festzulegen. Andere wollen, dass die Losgröße auf der Grundlage des Kontostands berechnet wird. Um diese beiden Stile zu unterstützen, führen wir eine einfache Enumeration ein.
enum ENUM_LOT_SIZE_INPUT_MODE
{
MODE_MANUAL,
MODE_AUTO
};
Diese Enumeration legt fest, wie die Losgröße erzeugt wird. Nach seiner Definition fügen wir den folgenden Eingabeparameter hinzu.
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO;
Wenn der Nutzer MODE_AUTO auswählt, wird die Losgröße auf der Grundlage eines gewählten Risikoprozentsatzes berechnet. Deswegen führen wir eine Eingabe ein, die den Prozentsatz des zu riskierenden Kontos definiert.
input double riskPerTradePercent = 1.0;
Wenn der Nutzer ein $10.000-Konto hat und riskPerTradePercent auf 1,0 setzt, wird der EA die Position so bemessen, dass ein Verlust nur $100 kostet. Dadurch wird das Risiko berechenbar und stabil.
Wenn der Nutzer MODE_MANUAL auswählt, ignoriert der EA den Risikoprozentsatz. Sie verwendet stattdessen eine feste Losgröße. Aus diesem Grund fügen wir den Parameter der manuellen Losgröße hinzu.
input double lotSize = 0.1;
Dadurch hat der Nutzer die vollständige Kontrolle über das Volumen jedes einzelnen Handelsgeschäfts. Beide Methoden sind unter Händlern weit verbreitet. Der EA wird beides unterstützen.
Stopp-Loss am vorherigen lokalen Extremwert setzen
Ein Kagi-Chart verdeutlicht Kursschwankungen. Jede Umkehrung erzeugt ein lokales Minimum oder Maximum. Diese Punkte sind wichtig, da ein Durchbruch über sie hinaus auf einen möglichen Trendwechsel hindeutet. Demzufolge wird der Stop-Loss für eine Kaufposition auf das vorherige lokale Minimum gesetzt. Bei einer Verkaufsposition wird der Stop-Loss auf das vorherige lokale Maximum gesetzt. Mit dieser Methode wird das Risiko kontrolliert und der Handel auf die zugrunde liegende Kagi-Struktur abgestimmt.
Gewinnmitnahme auf der Grundlage eines Risiko-/Ertragsverhältnisses
Das nächste Merkmal ist ein Risiko-Ertrags-System. Händler entscheiden sich oft für ein festes Verhältnis zwischen Risiko und Ertrag. Um dies zu unterstützen, führen wir eine weitere Enumeration ein.
enum ENUM_RISK_REWARD_RATIO
{
ONE_TO_ONE,
ONE_TO_ONEandHALF,
ONE_TO_TWO,
ONE_TO_THREE,
ONE_TO_FOUR,
ONE_TO_FIVE,
ONE_TO_SIX
};
Jeder Wert legt fest, wie groß Take-Profit im Verhältnis zu Stop-Loss sein wird. ONE_TO_THREE bedeutet zum Beispiel, dass der Ertrag dreimal so hoch ist wie das Risiko. Diese Art des Risikomanagements ist bei Trendfolgesystemen üblich. Nachdem wir die Enumeration definiert haben, fügen wir den Eingabeparameter hinzu.
input ENUM_RISK_REWARD_RATIO riskRewardRatio = ONE_TO_THREE;
So kann der Nutzer das Verhältnis wählen, das seinem Handelsstil entspricht.
Optionaler Trailing-Stop
Das letzte Merkmal ist ein Trailing-Stop. Wenn diese Funktion aktiviert ist, folgt der Stop-Loss dem Kurs, wenn er sich in die Handelsrichtung bewegt. Dies schützt die Gewinne bei starken Bewegungen. Im Moment fügen wir nur den Hauptsteuerungsparameter hinzu.
input bool enableTrailingStop = false;
Weitere Parameter können später hinzugefügt werden, aber dies reicht aus, um mit der Entwicklung fortzufahren.
Erkennen von Übergängen von Yin zu Yang und Yang zu Yin
Bevor wir einen Handel platzieren können, müssen wir feststellen, wann die Kagi-Linie tatsächlich ihre Richtung ändert. In Kagi-Chart wird diese Richtungsverschiebung durch eine Änderung der Dicke oder Farbe ausgedrückt:
- Yin zu Yang (Kaufsignal)
- Yang zu Yin (Verkaufssignal)
Diese Umkehrungen finden innerhalb unserer Funktion ConstructKagiInRealTime statt und sind bereits in drei Gruppen von Bedingungen geordnet. Jede Konstellation aktualisiert kagiData.isYin und kagiData.isYang, was sie zu perfekten Orten macht, um unsere Handelslogik unterzubringen.
1. Komplexe Umkehrung
Dies sind die klassischen Kagi-Umkehrungen. Wenn der Preis weit genug in die entgegengesetzte Richtung drängt, ändert sich der Trend vollständig, und die Linie wechselt die Dicke.
Yang zu Yin
Sie finden es hier:
void ConstructKagiInRealTime(double bidPr, double askPr){ ... //--- Handle a complex reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum)){ if(overlayKagi){ DrawBendTop (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMinimum, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMinimum = currentClosePrice; kagiData.isDowntrend = true; kagiData.isUptrend = false; kagiData.isYang = false; kagiData.isYin = true; } }
Yin zu Yang
void ConstructKagiInRealTime(double bidPr, double askPr){ ... //--- Handle a complex reversal if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum)){ if(overlayKagi){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMaximum, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMaximum = currentClosePrice; kagiData.isDowntrend = false; kagiData.isUptrend = true; kagiData.isYang = true; kagiData.isYin = false; } }
Dies sind die stärksten Kagi-Signaltypen.
2. Komplexe Fortführung nach Umkehrung
Diese treten auf, wenn sich der Kurs über das vorherige lokale Extrem hinaus ausdehnt, nachdem bereits eine Umkehrung stattgefunden hat. Die Kagi-Dicke kippt erneut, und der Trend setzt sich mit neuer Stärke fort.
Yang zu Yin
void ConstructKagiInRealTime(double bidPr, double askPr){ ... //--- Handle a complex continuation after reversal if(kagiData.isDowntrend && kagiData.isYang && (currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum))){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = false; kagiData.isYin = true; } }
Yin zu Yang
void ConstructKagiInRealTime(double bidPr, double askPr){ ... //--- Handle a complex continuation after reversal if(kagiData.isUptrend && kagiData.isYin && (currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum))){ if(overlayKagi){ DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = true; kagiData.isYin = false; } }
Diese Übergänge stellen immer noch bedeutsame Stimmungsumschwünge dar.
3. Seltenes (seltsames) Szenario
In dieser Kategorie wird ein ungewöhnliches Preisverhalten erfasst, das dennoch zu einer gültigen Polaritätsänderung führt. Sie sind zwar selten, müssen aber unbedingt berücksichtigt werden, damit kein gültiger Übergang übersehen wird.
Yin zu Yang
void ConstructKagiInRealTime(double bidPr, double askPr){ ... //--- Handle a weird scenario if(kagiData.isUptrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.isYin = false; kagiData.isYang = true; kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } }
Yang zu Yin
void ConstructKagiInRealTime(double bidPr, double askPr){ ... //--- Handle a weird scenario if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.isYang = false; kagiData.isYin = true; kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } }
In jedem dieser Fälle wechselt die Kagi-Linie ausdrücklich von einer Dicke zur anderen. Für unsere Strategie ist das alles, was wir brauchen. Im nächsten Abschnitt werden wir unsere Handelslogik direkt in diese Übergangspunkte einbauen. Hier berücksichtigen wir auch den Handelsmodus des Nutzers (nur Käufe, nur Verkäufe oder beides), wenden die gewählte Methode der Losgröße an und bereiten Stop-Loss und Take-Profits vor.
Integration der Handelslogik in Kagi-Übergänge
Da unsere Kagi-Struktur bereits vorhanden ist, ist es nun an der Zeit, die Chart-Übergänge (Yin zu Yang und Yang zu Yin) mit tatsächlichen Handelsaktionen zu verbinden. In diesem Abschnitt werden wir die Handelsumgebung vorbereiten, eine kleine Datenstruktur für die Speicherung von Handelsdetails einführen und dann die Funktionen zur Eröffnung von Kauf- und Verkaufspositionen erläutern. Jeder Schritt wird so dargestellt, dass Sie ihn direkt nachvollziehen und umsetzen können.
Der erste Schritt besteht darin, sicherzustellen, dass der EA Aufträge senden kann. Wir tun dies, indem wir die Standard-Handelsbibliothek einbeziehen. Platzieren Sie diese Zeile unmittelbar unter Ihren Direktiven von #property.
//+------------------------------------------------------------------+ //| Standard Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh>
Dies ermöglicht uns den Zugriff auf die Klasse CTrade, die alle Operationen zum Versenden von Aufträgen durchführt.
Als Nächstes benötigen wir eine einfache Struktur, die uns hilft, den Überblick über den letzten Handel zu behalten. Sie enthält die Ticketnummer, den Einstiegskurs, den Stop-Loss, den Take-Profit und andere nützliche Details. Platzieren Sie die folgende Struktur direkt unter Ihren bestehenden Strukturdefinitionen:
struct MqlTradeInfo { ulong orderTicket; ENUM_ORDER_TYPE type; ENUM_POSITION_TYPE posType; double entryPrice; double takeProfitLevel; double stopLossLevel; datetime openTime; double lotSize; };
Diese Struktur wird jedes Mal gefüllt, wenn eine neue Position eröffnet wird, sodass Ihr EA genau weiß, was er gerade ausgeführt hat.
Fügen Sie direkt nach der Struktur die folgenden zwei globalen Instanzen hinzu:
//--- Instantiate the trade information data structure MqlTradeInfo tradeInfo; //--- Create a CTrade object to handle trading operations CTrade Trade;
Das Objekt Trade sendet Aufträge, während tradeInfo als Container dient, um die Ergebnisse des letzten Handels zu speichern.
Unsere Handelsfunktion ist auf genaue Geld- und Briefkurse angewiesen, weshalb wir zwei globale Variablen deklarieren:
//--- Bid and Ask double askPrice; double bidPrice;
Wir aktualisieren diese Werte dann innerhalb der MQL5-Funktion OnTick, um sicherzustellen, dass sie immer den neuesten Marktzustand widerspiegeln.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- Scope variables askPrice = SymbolInfoDouble (_Symbol, SYMBOL_ASK); bidPrice = SymbolInfoDouble (_Symbol, SYMBOL_BID); }
Diese Preise werden später in unsere Kauf- und Verkaufsfunktionen übernommen.
Um den EA sauber und modular zu halten, werden wir die Handelslogik in zwei Hilfsfunktionen unterbringen: OpenBuy und OpenSel. Beide Funktionen führen fast die gleichen Schritte aus, jedoch in entgegengesetzter Richtung. Um die Wiederholung ähnlicher Erklärungen zu vermeiden, wollen wir die Logik einer Funktion aufschlüsseln. Die gleiche Logik gilt für die andere Seite, nur umgekehrt. Nachfolgend finden Sie die Funktion OpenBuy. Platzieren Sie sie in Ihrem Code, wo Sie Ihre Hilfsfunktionen aufbewahren:
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ENUM_ORDER_TYPE action = ORDER_TYPE_BUY; ENUM_POSITION_TYPE positionType = POSITION_TYPE_BUY; datetime currentTime = TimeCurrent(); double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double rewardValue = 1.0; switch(riskRewardRatio){ case ONE_TO_ONE: rewardValue = 1.0; break; case ONE_TO_ONEandHALF: rewardValue = 1.5; break; case ONE_TO_TWO: rewardValue = 2.0; break; case ONE_TO_THREE: rewardValue = 3.0; break; case ONE_TO_FOUR: rewardValue = 4.0; break; case ONE_TO_FIVE: rewardValue = 5.0; break; case ONE_TO_SIX: rewardValue = 6.0; break; default: rewardValue = 1.0; break; } double stopLevel = NormalizeDouble(kagiData.localMinimum, Digits()); double stopDistance = NormalizeDouble(askPr - stopLevel, Digits()); double targetLevel = NormalizeDouble(askPr + (rewardValue * stopDistance), Digits()); double volume = NormalizeDouble(lotSize, 2); if(lotSizeMode == MODE_AUTO){ double amountAtRisk = (riskPerTradePercent / 100.0) * accountBalance; volume = amountAtRisk / (contractSize * stopDistance); volume = NormalizeDouble(volume, 2); } if(!Trade.Buy(volume, _Symbol, askPr, stopLevel, targetLevel)){ Print("Error while opening a long position, ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; }else{ MqlTradeResult result = {}; Trade.Result(result); tradeInfo.orderTicket = result.order; tradeInfo.type = action; tradeInfo.posType = positionType; tradeInfo.entryPrice = result.price; tradeInfo.takeProfitLevel = targetLevel; tradeInfo.stopLossLevel = stopLevel; tradeInfo.openTime = currentTime; tradeInfo.lotSize = result.volume; return true; } return false; }
Damit Sie verstehen, was diese Funktion bewirkt, werden im Folgenden die wichtigsten Schritte beschrieben:
Zu Beginn teilen wir der Funktion mit, dass wir eine Kaufposition eröffnen und die aktuelle Uhrzeit erfassen.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ENUM_ORDER_TYPE action = ORDER_TYPE_BUY; ENUM_POSITION_TYPE positionType = POSITION_TYPE_BUY; datetime currentTime = TimeCurrent(); ... }
Dann erhalten wir die Kontraktgröße und den Kontostand. Diese Werte helfen uns bei der Berechnung der automatischen Losgrößen, wenn der Eingabeparameter lotSizeMode auf MODE_AUTO gesetzt ist.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ... double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); ... }
Der nächste Schritt ist die Bestimmung des Risiko-Ertrags-Multiplikators.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ... double rewardValue = 1.0; switch(riskRewardRatio){ case ONE_TO_ONE: rewardValue = 1.0; break; case ONE_TO_ONEandHALF: rewardValue = 1.5; break; case ONE_TO_TWO: rewardValue = 2.0; break; case ONE_TO_THREE: rewardValue = 3.0; break; case ONE_TO_FOUR: rewardValue = 4.0; break; case ONE_TO_FIVE: rewardValue = 5.0; break; case ONE_TO_SIX: rewardValue = 6.0; break; default: rewardValue = 1.0; break; } ... }
Die switch-Anweisung wandelt das vom Nutzer gewählte riskToRewardRatio in einen numerischen Wert um.
Anschließend berechnen wir die Niveaus von Stop-Loss und Take-Profit.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ... double stopLevel = NormalizeDouble(kagiData.localMinimum, Digits()); double stopDistance = NormalizeDouble(askPr - stopLevel, Digits()); double targetLevel = NormalizeDouble(askPr + (rewardValue * stopDistance), Digits()); ... }
Das Stop-Loss-Niveau wird auf das letzte lokale Minimum der Kagi-Struktur gesetzt. Anschließend messen wir den Abstand vom Einstieg bis zum Stop-Loss-Level und weisen diesen Wert der Variablen stopDistance zu. Das Niveau von Take-Profit wird anhand des Risiko-Ertrags-Multiplikators bestimmt.
Als Nächstes berechnen wir das Handelsvolumen.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ... double volume = NormalizeDouble(lotSize, 2); if(lotSizeMode == MODE_AUTO){ double amountAtRisk = (riskPerTradePercent / 100.0) * accountBalance; volume = amountAtRisk / (contractSize * stopDistance); volume = NormalizeDouble(volume, 2); } ... }
Wenn der Nutzer für den Eingabeparameter lotSizeMode den Wert MODE_MANUAL ausgewählt hat, verwenden wir den angegebenen Wert des Eingabeparameters lotSize. Wenn Sie AUTO_MODE gewählt haben, berechnen wir die Losgröße auf der Grundlage des angegebenen prozentualen Risikos.
Der nächste Schritt besteht darin, einen Auftrag zur sofortigen Eröffnung einer Kaufposition zum Marktpreis zu senden.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ... if(!Trade.Buy(volume, _Symbol, askPr, stopLevel, targetLevel)){ Print("Error while opening a long position, ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } ... }
Der Befehl Trade.Buy() sendet den Auftrag ab. Wenn er fehlschlägt, gibt die Funktion den Fehler zur Fehlersuche aus. Wenn der Handel erfolgreich ist, speichern wir das Ticket, den Einstiegskurs, den Stop-Loss, den Take-Profit und die Losgröße in der Struktur tradeInfo.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ... if(!Trade.Buy(volume, _Symbol, askPr, stopLevel, targetLevel)){ ... }else{ MqlTradeResult result = {}; Trade.Result(result); tradeInfo.orderTicket = result.order; tradeInfo.type = action; tradeInfo.posType = positionType; tradeInfo.entryPrice = result.price; tradeInfo.takeProfitLevel = targetLevel; tradeInfo.stopLossLevel = stopLevel; tradeInfo.openTime = currentTime; tradeInfo.lotSize = result.volume; return true; } ... }
Die Funktion OpenSel folgt genau demselben Ablauf, nur in umgekehrter Richtung. Sie verwendet das lokale Maximum aus der Kagi-Struktur und berechnet die Stop-Loss und Take-Profit in umgekehrter Weise.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenSel( const double bidPr){ ENUM_ORDER_TYPE action = ORDER_TYPE_SELL; ENUM_POSITION_TYPE positionType = POSITION_TYPE_SELL; datetime currentTime = TimeCurrent(); double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double rewardValue = 1.0; switch(riskRewardRatio){ case ONE_TO_ONE: rewardValue = 1.0; break; case ONE_TO_ONEandHALF: rewardValue = 1.5; break; case ONE_TO_TWO: rewardValue = 2.0; break; case ONE_TO_THREE: rewardValue = 3.0; break; case ONE_TO_FOUR: rewardValue = 4.0; break; case ONE_TO_FIVE: rewardValue = 5.0; break; case ONE_TO_SIX: rewardValue = 6.0; break; default: rewardValue = 1.0; break; } double stopLevel = NormalizeDouble(kagiData.localMaximum, Digits()); double stopDistance = NormalizeDouble(stopLevel - bidPr, Digits()); double targetLevel = NormalizeDouble(bidPr - (rewardValue * stopDistance), Digits()); double volume = NormalizeDouble(lotSize, 2); if(lotSizeMode == MODE_AUTO){ double amountAtRisk = (riskPerTradePercent / 100.0) * accountBalance; volume = amountAtRisk / (contractSize * stopDistance); volume = NormalizeDouble(volume, 2); } if(!Trade.Sell(volume, _Symbol, bidPr, stopLevel, targetLevel)){ Print("Error while opening a short position, ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; }else{ MqlTradeResult result = {}; Trade.Result(result); tradeInfo.orderTicket = result.order; tradeInfo.type = action; tradeInfo.posType = positionType; tradeInfo.entryPrice = result.price; tradeInfo.takeProfitLevel = targetLevel; tradeInfo.stopLossLevel = stopLevel; tradeInfo.openTime = currentTime; tradeInfo.lotSize = result.volume; return true; } return false; }
Bevor wir die Handelslogik in die Kagi-Übergänge integrieren, müssen wir zunächst ein paar Hilfsfunktionen vorbereiten. Diese Funktionen ermöglichen es unserem EA zu prüfen, ob bereits eine Kauf- oder Verkaufsposition besteht, und sie geben uns auch eine einfache Möglichkeit, alle offenen Handelsgeschäfte dieses EAs zu schließen. Sie helfen uns, eine sehr wichtige Regel durchzusetzen: Der EA sollte immer nur eine Position eröffnen. Wenn bereits ein Handelsgeschäft offen ist, sollte der EA kein weiteres Handelsgeschäft eröffnen, bis der aktuelle geschlossen ist.
Platzieren Sie diese Funktionen direkt unter den vorhandenen Hilfsfunktionen in Ihrem Projekt. Fügen Sie sie nacheinander hinzu, und nehmen Sie sich einen Moment Zeit, um zu verstehen, was jede einzelne Funktion ist, da sie später eine wichtige Rolle spielen wird.
Um zu prüfen, ob eine aktive Kaufposition vorliegt, definieren wir die folgende Funktion:
//+------------------------------------------------------------------+ //| To verify whether this EA currently has an active buy position. | | //+------------------------------------------------------------------+ bool IsThereAnActiveBuyPosition(ulong magic){ for(int i = PositionsTotal() - 1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(ticket == 0){ Print("Error while fetching position ticket ", _LastError); continue; }else{ if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ return true; } } } return false; }
Diese Funktion durchsucht alle derzeit offenen Positionen und prüft, ob es einen aktiven Kaufhandel gibt, der zu diesem EA gehört. Dazu wird die magische Zahl jeder Position mit der dem EA zugewiesenen magischen Zahl verglichen. Findet sie eine Kaufposition mit der passenden magischen Zahl, gibt sie true zurück. Wenn keine solche Position gefunden wird, wird false zurückgegeben. Wir werden diese Funktion später verwenden, um zu verhindern, dass der EA zwei Kaufpositionen gleichzeitig eröffnet.
Um zu prüfen, ob eine aktive Verkaufsposition vorliegt, definieren wir die folgende Funktion:
//+------------------------------------------------------------------+ //| To verify whether this EA currently has an active sell position. | | //+------------------------------------------------------------------+ bool IsThereAnActiveSellPosition(ulong magic){ for(int i = PositionsTotal() - 1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(ticket == 0){ Print("Error while fetching position ticket ", _LastError); continue; }else{ if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ return true; } } } return false; }
Diese Funktion funktioniert auf die gleiche Weise wie die vorherige, aber sie prüft, ob eine Verkaufsposition vorliegt. Sie durchläuft alle offenen Handelsgeschäfte, ruft die magische Zahl und den Positionstyp ab und gibt nur dann true zurück, wenn sie eine Verkaufsposition findet, die zu diesem EA gehört. Durch die Verwendung dieser Funktion bei Handelsentscheidungen vermeidet der EA zu verkaufen, da ein solches Handelsgeschäft bereits geöffnet worden ist.
Wir benötigen auch eine Möglichkeit, alle aktiven Positionen zu schließen, die von unserem EA eröffnet wurden. Aus diesem Grund definieren wir folgende Hilfsfunktion:
//+------------------------------------------------------------------+ //| To close all position with a specified magic number | //+------------------------------------------------------------------+ void ClosePositionsByMagic(ulong magic) { for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { if (PositionGetInteger(POSITION_MAGIC) == magic) { ulong positionType = PositionGetInteger(POSITION_TYPE); double volume = PositionGetDouble(POSITION_VOLUME); if (positionType == POSITION_TYPE_BUY) { Trade.PositionClose(ticket); } else if (positionType == POSITION_TYPE_SELL) { Trade.PositionClose(ticket); } } } } }
Diese Funktion bietet eine einfache und zuverlässige Möglichkeit, jede von diesem EA eröffnete Position zu schließen. Sie durchsucht alle aktiven Handelsgeschäfte und wählt jedes einzelne anhand seiner Ticketnummer aus. Wenn er eine Position findet, deren magische Zahl mit der von unserem EA verwendeten übereinstimmt, schließt er diese Position, unabhängig davon, ob es sich um einen Kauf- oder Verkaufshandel handelt. Diese Funktion ist sehr nützlich, wenn Sie die volle Kontrolle über das Positionsmanagement haben möchten, insbesondere beim Zurücksetzen des EA oder bei der Implementierung von Handelsmodi, die nur Käufe, nur Verkäufe oder beides zulassen.
Auch wenn der Nutzer den automatischen Handel deaktiviert, sollte der EA in der Lage sein, Kagi-Signale direkt auf dem Chart anzuzeigen. Diese Funktion hilft dem Händler, das Verhalten der Kagi-Linie visuell zu verfolgen und zu verstehen, wo ein möglicher Einstieg stattgefunden hätte. Um dies zu erreichen, werden wir zwei einfache Nutzenfunktionen erstellen. Der eine zeichnet eine Kaufmarke, der andere eine Verkaufsmarke. Jeder Marker wird genau zu dem Zeitpunkt und auf dem Kursniveau platziert, zu dem das Signal aufgetreten ist.
Um einen Marker für ein Kaufsignal zu zeichnen, fügen Sie die folgende Funktion direkt nach den Funktionen für das Positionsmanagement ein:
//+------------------------------------------------------------------+ //| Draw a Buy Signal Marker | //+------------------------------------------------------------------+ void DrawBuySignalMarker(const datetime time, const double price){ //--- Create a unique name for the object string name = "BuySignal_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(MathRand()); //--- Create the buy arrow if(!ObjectCreate(0, name, OBJ_ARROW_UP, 0, time, price)){ Print("Failed to create Buy Signal marker. Error: ", GetLastError()); return; } //--- Styling (optional) ObjectSetInteger(0, name, OBJPROP_COLOR, yangLineColor); ObjectSetInteger(0, name, OBJPROP_WIDTH, 3); }
Diese Funktion zeichnet einen Kaufsignalmarker auf dem Chart. Zunächst wird ein eindeutiger Name für das Objekt erzeugt. Dies ist wichtig, da jedes grafische Objekt in MetaTrader 5 einen eigenen Namen haben muss. Die Funktion erstellt dann einen Aufwärtspfeil zum ausgewählten Zeitpunkt und Preis. Wenn das Objekt erfolgreich erstellt wurde, wendet die Funktion eine Farbe und eine Breite an, damit die Markierung sichtbar ist und zu Ihrem Kagi-Thema passt. Diese Markierung hat keinen Einfluss auf den Handel. Ihr einziger Zweck ist es, dem Händler zu zeigen, wo ein Kaufsignal aufgetreten ist.
Um einen Marker für ein Verkaufssignal zu zeichnen, definieren wir die folgende Funktion:
//+------------------------------------------------------------------+ //| Draw a Sell Signal Marker | //+------------------------------------------------------------------+ void DrawSellSignalMarker(const datetime time, const double price){ //--- Create a unique name for the object string name = "SellSignal_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(MathRand()); //--- Create the sell arrow if(!ObjectCreate(0, name, OBJ_ARROW_DOWN, 0, time, price)){ Print("Failed to create Sell Signal marker. Error: ", GetLastError()); return; } //--- Styling (optional) ObjectSetInteger(0, name, OBJPROP_COLOR, yinLineColor); ObjectSetInteger(0, name, OBJPROP_WIDTH, 3); }
Diese Funktion funktioniert genauso wie der Kaufmarker, aber sie platziert stattdessen einen Abwärtspfeil. Der Objektname wird ebenfalls dynamisch generiert, sodass jedes Signal separat gespeichert wird. Sobald der Pfeil zur richtigen Zeit und zum richtigen Preis erstellt wurde, stellt die Funktion seine Farbe und Breite so ein, dass er dem Stil Ihrer Yin-Linie entspricht. Diese visuellen Tags ermöglichen es dem Händler, Abwärtssignale des Kagi-Indikators zu verfolgen, auch wenn der eigentliche Handel deaktiviert ist.
Nachdem wir unsere Hilfsfunktionen für die Signalerstellung eingerichtet haben, besteht der nächste Schritt darin, die Kagi-Signale mit den tatsächlichen Handelsoperationen zu verbinden. Das Ziel ist einfach: Wann immer der EA eine bestätigte Kagi-Umkehr oder -Fortsetzung erkennt, die sich als Kauf- oder Verkaufssignal qualifiziert, führen wir den entsprechenden Handelsblock aus und setzen dann eine Markierung auf dem Chart. Bei diesem Ansatz wird der gesamte Handelsablauf innerhalb der Funktion ConstructKagiInRealTime zentralisiert, sodass der Leser leicht nachvollziehen kann, wie Signale in Aktionen umgesetzt werden.
Um die Integration reibungsloser zu gestalten, ist es wichtig zu verstehen, dass die Kagi-Engine je nach Marktstruktur zu unterschiedlichen Zeitpunkten Signale erzeugt. Einige Signale ergeben sich aus eindeutigen Umkehrungen, während andere aus Fortsetzungsbewegungen nach einer Umkehrung resultieren. Unabhängig davon, woher das Signal kommt, muss der EA immer die gleichen drei Schritte durchführen:
- Schließen von jeder aktiven Position in die entgegengesetzte Richtung.
- Eröffnen von einem neuen Handelsgeschäft in der Richtung des Signals (wenn der Handel erlaubt ist).
- Setzen einer visuellen Marke auf dem Chart, um das Signal anzuzeigen.
Da diese Schritte mehrfach vorkommen, sehen die für Kaufsignale verwendeten Codeblöcke alle ähnlich aus, und die gleiche Konsistenz gilt für Verkaufssignale. Im Folgenden wird anhand von Beispielen aus den Kagi-Blöcken erläutert, wie diese Logik in die Funktion passt.
Behandlung der Kaufsignale
Sobald die Kagi-Struktur einen Übergang in eine Aufwärtsphase erkennt – ob es sich nun um eine vollständige Umkehr oder eine Fortsetzungsbewegung handelt –, lösen wir die Kauflogik aus. Jeder Kaufblock folgt dem gleichen Muster.
- Schließen aller aktiven Verkaufspositionen – Bevor eine Kaufposition eröffnet wird, wird sichergestellt, dass kein Verkaufsauftrag mehr offen ist. Ist eine solche vorhanden, wird sie sofort geschlossen.
- Eine neue Kaufposition eröffnen, wenn der Handel erlaubt ist – Wenn der automatische Handel aktiviert ist und der Nutzer Käufe erlaubt hat, prüft der EA, ob bereits ein Kauf offen ist. Ist dies nicht der Fall, wird ein neues Handelsgeschäft mit dem aktuellen Briefkurs eröffnet.
- Platzieren eines Kaufsignalmarkers auf dem Chart – Unabhängig davon, ob der Handel erlaubt ist oder nicht, platziert der EA immer einen visuellen Kagi-Kaufmarker, damit der Händler das Signal direkt auf dem Chart verfolgen kann.
So sieht ein Kaufsignalblock innerhalb der Funktion aus:
// Close a short position if it exists if(IsThereAnActiveSellPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a long position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_LONG){ if(!IsThereAnActiveBuyPosition(magicNumber)){ OpenBuy(askPrice); } } } //--- Render a buy signal (up) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawBuySignalMarker(lastBarOpenTime, lastBarClosePrice);
Derselbe Block wiederholt sich in allen Situationen, in denen die Kagi-Logik einen gültigen Ausbruch oder eine Umkehrung nach oben bewirkt.
Behandlung der Verkaufssignale
Verkaufssignale spiegeln die Kaufsignale wider, wirken aber genau umgekehrt. Wann immer die Preisstruktur eine Abwärtsumkehr oder -fortsetzung bestätigt, folgen wir diesen Schritten:
- Schließen einer aktiven Kaufposition – Der EA prüft zunächst, ob eine Kaufposition offen ist. Wird eine gefunden, wird sie geschlossen, um einen Konflikt zu vermeiden.
- Eine neue Verkaufsposition eröffnen, wenn der Handel erlaubt ist – Wenn der Handel aktiviert ist und der Nutzer nur Verkäufe erlaubt hat, eröffnet der EA eine Verkaufsposition zum aktuellen Geldkurs – vorausgesetzt, dass noch keine aktive Verkaufsposition besteht.
- Hinzufügen einer Verkaufsmarkierung auf dem Chart – Die Markierung wird auf dem letzten abgeschlossenen Balken gezeichnet, um genau zu zeigen, wo Kagi umkehrt oder seine Richtung fortsetzt.
Nachstehend finden Sie ein Beispiel für einen Verkaufssignalblock:
// Close a long position if it exists if(IsThereAnActiveBuyPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a short position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_SHORT){ if(!IsThereAnActiveSellPosition(magicNumber)){ OpenSel(bidPrice); } } } //--- Render a sell signal (down) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawSellSignalMarker(lastBarOpenTime, lastBarClosePrice);
Genau wie der Kaufblock erscheint diese Verkaufsvorlage in jedem Teil der Kagi-Engine, in dem ein Strukturbruch nach unten festgestellt wird.
Innerhalb der Funktion ConstructKagiInRealTime aktualisiert der Kagi-Algorithmus seinen Zustand jedes Mal, wenn sich ein neuer Balken bildet. Während dieser Aktualisierung wird unter verschiedenen Bedingungen geprüft, ob sich der Kurs weit genug bewegt hat, um die aktuelle Kagi-Linie zu durchbrechen oder den Trend umzukehren. Jede dieser Bedingungen stellt ein mögliches Kauf- oder Verkaufssignal dar.
Zum Beispiel:
- Wenn der Kurs in einem Aufwärtstrend unter eine Yang-Linie fällt, löst dies einen Verkauf aus.
- Wenn der Kurs in einem Abwärtstrend über eine Yin-Linie bricht, löst dies einen Kauf aus.
- Wenn sich der Kurs nach einer Umkehrung stark fortsetzt, kann er ein Fortsetzungssignal erzeugen, das genauso behandelt wird wie eine reguläre Umkehrung.
An jedem dieser Punkte fügen wir einfach den entsprechenden Handelsblock (Kauf oder Verkauf) ein.
//+------------------------------------------------------------------+ //| This function is used to construct Kagi in real time | //+------------------------------------------------------------------+ void ConstructKagiInRealTime(double bidPr, double askPr){ if(IsNewBar(_Symbol, kagiTimeframe, kagiData.lastBarOpenTime)){ ... //--- Handle a complex reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum)){ // Close a long position if it exists if(IsThereAnActiveBuyPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a short position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_SHORT){ if(!IsThereAnActiveSellPosition(magicNumber)){ OpenSel(bidPrice); } } } //--- Render a sell signal(down) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawSellSignalMarker(lastBarOpenTime, lastBarClosePrice); if(overlayKagi){ DrawBendTop (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMinimum, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMinimum = currentClosePrice; kagiData.isDowntrend = true; kagiData.isUptrend = false; kagiData.isYang = false; kagiData.isYin = true; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum)){ // Close a short position if it exists if(IsThereAnActiveSellPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a long position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_LONG){ if(!IsThereAnActiveBuyPosition(magicNumber)){ OpenBuy(askPrice); } } } //--- Render a sell signal(down) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawBuySignalMarker(lastBarOpenTime, lastBarClosePrice); if(overlayKagi){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMaximum, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMaximum = currentClosePrice; kagiData.isDowntrend = false; kagiData.isUptrend = true; kagiData.isYang = true; kagiData.isYin = false; } ... //--- Handle a complex continuation after reversal if(kagiData.isDowntrend && kagiData.isYang && (currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum))){ // Close a long position if it exists if(IsThereAnActiveBuyPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a short position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_SHORT){ if(!IsThereAnActiveSellPosition(magicNumber)){ OpenSel(bidPrice); } } } //--- Render a sell signal(down) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawSellSignalMarker(lastBarOpenTime, lastBarClosePrice); if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = false; kagiData.isYin = true; } if(kagiData.isUptrend && kagiData.isYin && (currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum))){ // Close a short position if it exists if(IsThereAnActiveSellPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a long position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_LONG){ if(!IsThereAnActiveBuyPosition(magicNumber)){ OpenBuy(askPrice); } } } //--- Render a sell signal(down) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawBuySignalMarker(lastBarOpenTime, lastBarClosePrice); if(overlayKagi){ DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = true; kagiData.isYin = false; } ... //--- Handle a weird scenario if(kagiData.isUptrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ // Close a short position if it exists if(IsThereAnActiveSellPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a long position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_LONG){ if(!IsThereAnActiveBuyPosition(magicNumber)){ OpenBuy(askPrice); } } } //--- Render a sell signal(down) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawBuySignalMarker(lastBarOpenTime, lastBarClosePrice); if(overlayKagi){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.isYin = false; kagiData.isYang = true; kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ // Close a long position if it exists if(IsThereAnActiveBuyPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(50); } //--- Open a short position if allowed if(enableTrading){ if(direction == TRADE_BOTH || direction == ONLY_SHORT){ if(!IsThereAnActiveSellPosition(magicNumber)){ OpenSel(bidPrice); } } } //--- Render a sell signal(down) arrow datetime lastBarOpenTime = iTime(_Symbol, kagiTimeframe, 1); double lastBarClosePrice = iClose(_Symbol, kagiTimeframe, 1); DrawSellSignalMarker(lastBarOpenTime, lastBarClosePrice); if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.isYang = false; kagiData.isYin = true; kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } } }
Entwurf und Implementierung von Trailing-Stops
Ein Trailing-Stop hilft, Gewinne zu sichern, wenn sich ein Handel günstig entwickelt. In diesem EA ist der Trailing-Stop optional. Der Nutzer kann sie mit dem Eingabeparameter enableTrailingStop aktivieren oder deaktivieren. Wenn diese Funktion aktiviert ist, passt der EA den Stop-Loss in drei progressiven Schritten an, wenn der Kurs vordefinierte Schwellenwerte zwischen Einstieg und Take-Profit erreicht. Das Ziel ist es, einen zusätzlichen Gewinn zu erzielen und gleichzeitig dem Handel einen gewissen Spielraum zu geben.
Die Trailing-Logik verwendet den Abstand zwischen Einstieg und Take-Profit als Basislinie. Wir teilen diesen Abstand durch vier, um die Stufen der Trailing Stops zu erhalten. Die Schwellenwerte werden dann anhand des Einstiegspreises berechnet. Für eine Kaufposition gelten folgende Schwellenwerte:
- Eröffnung plus eine Trailing-Stufe.
- Eröffnung plus zwei Trailing-Stufen.
- Eröffnung plus drei Trailing-Stufen.
Wenn der Kurs die erste Schwelle überschreitet, wird der Stopp um eine Stufe nach oben gesetzt. Wenn der Kurs die zweite Schwelle überschreitet, wird der Stopp erneut verschoben, und dasselbe gilt für die dritte Schwelle. Für Verkäufe gilt das Gleiche, nur in umgekehrter Richtung.
Um den Trailing-Status sauber zu halten, verwenden wir eine kleine Struktur, die die drei Ebenen und die entsprechenden Stopp-Levels enthält. Außerdem werden drei boolesche Flags gespeichert, die anzeigen, ob bereits eine Anpassung auf dieser Ebene vorgenommen wurde. Dies verhindert wiederholte Änderungen, wenn der Preis hin und her schwankt.
Platzieren Sie diese Struktur in der Nähe Ihrer anderen Datenstrukturen:
struct MqlTrailingStop { double level1; double level2; double level3; double stopLevel1; double stopLevel2; double stopLevel3; bool isLevel1Active; bool isLevel2Active; bool isLevel3Active; };
Instanziieren Sie sie als globale Variable:
//--- Instantiate the trailing stop structure
MqlTrailingStop trailingStop;
Wir benötigen auch ein Array, das die letzten einminütigen Schlusskurse enthält. Dies ist nützlich, um Pegelübergänge zuverlässig zu erkennen.
//--- To store minutes data double closePriceMinutesData [];
Wir definieren das Minuten-Array als Zeitreihe in OnInit.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Array Set As Series ... ArraySetAsSeries(closePriceMinutesData, true); ... return INIT_SUCCEEDED; }
Dann wird es bei jedem Tick wieder aufgefüllt. Fügen wir den Aufruf zum Kopieren hinzu, nachdem wir die Geld- und Briefkurse in OnTick aktualisiert haben.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ ... //--- Get some minutes data if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){ Print("Error while copying minutes datas ", GetLastError()); return; } ... }
Die Verwendung einiger aktueller Balken hilft, falsche Auslöser zu vermeiden, die durch das Rauschen einzelner Ticks verursacht werden.
Um festzustellen, ob der Preis eine Schwelle überschritten hat, fügen wir zwei kleine Helfer hinzu. Es werden die letzten beiden Schlusskurse im Minuten-Array verglichen und true zurückgegeben, wenn eine Kreuzung auftritt.
//+------------------------------------------------------------------+ //| To detect a crossover at a given price level | //+------------------------------------------------------------------+ bool IsCrossOver(const double price, const double &closePriceMinsData[]){ if(closePriceMinsData[1] <= price && closePriceMinsData[0] > price){ return true; } return false; } //+------------------------------------------------------------------+ //| To detect a crossunder at a given price level | //+------------------------------------------------------------------+ bool IsCrossUnder(const double price, const double &closePriceMinsData[]){ if(closePriceMinsData[1] >= price && closePriceMinsData[0] < price){ return true; } return false; }
Diese Funktionen sind einfach und zuverlässig. Sie sagen uns, dass sich der Markt von einer Seite des Niveaus zur anderen bewegt hat, indem sie die Schlusskurse der Minutenbalken verwenden.
Wenn der EA ein Handelsgeschäft eröffnet, berechnen wir die Trailing-Schwellenwerte und die Stop-Ziele und speichern sie in der Struktur trailingStop. Tun Sie dies sofort, nachdem der Auftrag erfolgreich abgeschlossen wurde.
Für eine Kaufposition berechnen wir:
- targetDistance ist gleich takeProfit minus Eröffnungskurs.
- trailingStep ist gleich targetDistance geteilt durch vier
- level1 ist gleich entry plus trailingStep
- level2 ist gleich level1 plus trailingStep
- level3 ist gleich level2 plus trailingStep
- stopLevel1 ist gleich ursprünglicher Stop plus trailingStep
- stopLevel2 ist gleich stopLevel1 plus trailingStep
- stopLevel3 ist gleich stopLevel2 plus trailingStep
Wir setzen die drei booleschen Flags auf „false“, damit die Niveaus zum Handeln zur Verfügung stehen.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenBuy(const double askPr){ ... if(!Trade.Buy(volume, _Symbol, askPr, stopLevel, targetLevel)){ Print("Error while opening a long position, ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; }else{ ... //--- Refill the trailing Stop struct double targetDistance = targetLevel - askPr; double trailingStep = NormalizeDouble(targetDistance / 4, Digits()); trailingStop.level1 = NormalizeDouble(askPr + trailingStep, Digits()); trailingStop.level2 = NormalizeDouble(trailingStop.level1 + trailingStep, Digits()); trailingStop.level3 = NormalizeDouble(trailingStop.level2 + trailingStep, Digits()); trailingStop.stopLevel1 = NormalizeDouble(stopLevel + trailingStep, Digits()); trailingStop.stopLevel2 = NormalizeDouble(trailingStop.stopLevel1 + trailingStep, Digits()); trailingStop.stopLevel3 = NormalizeDouble(trailingStop.stopLevel2 + trailingStep, Digits()); trailingStop.isLevel1Active = false; trailingStop.isLevel2Active = false; trailingStop.isLevel3Active = false; return true; } return false; }
Für einen Verkauf gilt die gleiche Logik, aber wir ziehen die Schritte vom Eröffnungskurs und Stop-Loss-Niveau ab.
//+------------------------------------------------------------------+ //| Function used to open a market buy order. | //+------------------------------------------------------------------+ bool OpenSel( const double bidPr){ ... if(!Trade.Sell(volume, _Symbol, bidPr, stopLevel, targetLevel)){ Print("Error while opening a short position, ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; }else{ ... //--- Refill the trailing Stop struct double targetDistance = bidPr - targetLevel; double trailingStep = NormalizeDouble(targetDistance / 4, Digits()); trailingStop.level1 = NormalizeDouble(bidPr - trailingStep, Digits()); trailingStop.level2 = NormalizeDouble(trailingStop.level1 - trailingStep, Digits()); trailingStop.level3 = NormalizeDouble(trailingStop.level2 - trailingStep, Digits()); trailingStop.stopLevel1 = NormalizeDouble(stopLevel - trailingStep, Digits()); trailingStop.stopLevel2 = NormalizeDouble(trailingStop.stopLevel1 - trailingStep, Digits()); trailingStop.stopLevel3 = NormalizeDouble(trailingStop.stopLevel2 - trailingStep, Digits()); trailingStop.isLevel1Active = false; trailingStop.isLevel2Active = false; trailingStop.isLevel3Active = false; return true; } return false; }
Diese Initialisierung stellt sicher, dass der EA genau weiß, wann und wohin er den Stopp bei einem Kursanstieg verschieben muss.
Nachdem nun die Trailing-Stufen und Hilfsfunktionen fertig sind, muss als nächster Schritt die Funktion ManageTrailingStop selbst hinzugefügt werden. Diese Funktion sollte in den Hauptteil Ihres EAs eingefügt werden, direkt unter den nachgestellten Hilfsfunktionen.
//+------------------------------------------------------------------+ //| To track price action and updates the trailing stop | //+------------------------------------------------------------------+ void ManageTrailingStop(){ int totalPositions = PositionsTotal(); //--- Loop through all open positions for(int i = totalPositions - 1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(ticket != 0){ // Get some useful position properties ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); string symbol = PositionGetString (POSITION_SYMBOL); ulong magic = PositionGetInteger(POSITION_MAGIC); double targetLevel = PositionGetDouble(POSITION_TP); if(positionType == POSITION_TYPE_BUY ){ if(symbol == _Symbol && magic == magicNumber){ if(IsCrossOver(trailingStop.level1, closePriceMinutesData) && !trailingStop.isLevel1Active){ if(!Trade.PositionModify(ticket, trailingStop.stopLevel1, targetLevel)){ Print("Error while trailing SL at level 1: ", GetLastError()); Print(Trade.ResultRetcodeDescription()); Print(Trade.ResultRetcode()); }else{ trailingStop.isLevel1Active = true; } } if(IsCrossOver(trailingStop.level2, closePriceMinutesData) && !trailingStop.isLevel2Active){ if(!Trade.PositionModify(ticket, trailingStop.stopLevel2, targetLevel)){ Print("Error while trailing SL at level 2: ", GetLastError()); Print(Trade.ResultRetcodeDescription()); Print(Trade.ResultRetcode()); }else{ trailingStop.isLevel2Active = true; } } if(IsCrossOver(trailingStop.level3, closePriceMinutesData) && !trailingStop.isLevel3Active){ if(!Trade.PositionModify(ticket, trailingStop.stopLevel3, targetLevel)){ Print("Error while trailing SL at level 3: ", GetLastError()); Print(Trade.ResultRetcodeDescription()); Print(Trade.ResultRetcode()); }else{ trailingStop.isLevel3Active = true; } } } } if(positionType == POSITION_TYPE_SELL){ if(symbol == _Symbol && magic == magicNumber){ if(IsCrossUnder(trailingStop.level1, closePriceMinutesData) && !trailingStop.isLevel1Active){ if(!Trade.PositionModify(ticket, trailingStop.stopLevel1, targetLevel)){ Print("Error while trailing SL at level 1: ", GetLastError()); Print(Trade.ResultRetcodeDescription()); Print(Trade.ResultRetcode()); }else{ trailingStop.isLevel1Active = true; } } if(IsCrossUnder(trailingStop.level2, closePriceMinutesData) && !trailingStop.isLevel2Active){ if(!Trade.PositionModify(ticket, trailingStop.stopLevel2, targetLevel)){ Print("Error while trailing SL at level 2: ", GetLastError()); Print(Trade.ResultRetcodeDescription()); Print(Trade.ResultRetcode()); }else{ trailingStop.isLevel2Active = true; } } if(IsCrossUnder(trailingStop.level3, closePriceMinutesData) && !trailingStop.isLevel3Active){ if(!Trade.PositionModify(ticket, trailingStop.stopLevel3, targetLevel)){ Print("Error while trailing SL at level 3: ", GetLastError()); Print(Trade.ResultRetcodeDescription()); Print(Trade.ResultRetcode()); }else{ trailingStop.isLevel3Active = true; } } } } } } }
Ihre Aufgabe ist einfach: Jedes Mal, wenn ein neuer Tick eintrifft, prüft er, ob eine offene Position einen der Trailing-Schwellenwerte überschritten hat, und passt dann den Stop-Loss entsprechend an.
Innerhalb dieser Funktion beginnt der EA mit einer Schleife durch alle derzeit offenen Positionen. Für jede Transaktion werden die wichtigsten Details abgerufen, wie z. B. die Ticketnummer, das Symbol, die magische Zahl und ob es sich um einen Kauf oder Verkauf handelt. Der EA verwaltet nur Positionen, die zum gleichen Symbol gehören wie der Chart und die mit der magischen Zahl des EAs eröffnet wurden. Dies verhindert eine versehentliche Änderung von manuell oder durch andere Experten eröffneten Handelsgeschäften.
Bei Kaufpositionen verwendet der EA den Helfer Crossover, um zu prüfen, ob der Preis über die Stufe eins, zwei oder drei gestiegen ist. Wenn der Preis das Niveau eins überschreitet und diese Anpassung noch nicht verwendet wurde, ruft der EA Trade.PositionModify auf, um den Stop-Loss auf stopLevel1 zu verschieben, und markiert Level eins sofort als aktiv. Die gleiche Logik gilt für Stufe zwei und Stufe drei. Jede Ebene kann nur einmal ausgelöst werden, sodass ein sauberer und vorhersehbarer Nachlauf gewährleistet ist.
Für Verkaufspositionen ist das Verfahren identisch, aber es wird der Helfer Crossunder verwendet. Wenn sich der Kurs unter das Niveau eins, zwei oder drei bewegt, aktualisiert der EA den Stop-Loss schrittweise und markiert jedes Level als abgeschlossen. Durch diese symmetrische Handhabung bleibt die Trailing-Logik unabhängig von der Handelsrichtung konsistent.
Jeder Aufruf von PositionModify wird auf Erfolg geprüft. Lehnt der Broker die Änderung ab, gibt der EA eine Meldung auf der Registerkarte Experten aus, die dem Nutzer hilft, Probleme wie den Mindeststoppabstand oder eine zu niedrige Marge zu erkennen.
Durch diese strukturierte Gestaltung der Trailing-Logik wird sichergestellt, dass jede Anpassung genau einmal und nur dann erfolgt, wenn der Preis tatsächlich den Schwellenwert überschreitet. Da sich die Funktion auf die Daten von Minutenschlusskursen stützt, reagiert sie nicht auf Rauschen und schützt den Handel auf kontrollierte Weise.
Sobald die Trailing-Funktion eingerichtet ist, müssen Sie sie nur noch innerhalb von OnTick aktivieren. Rufen Sie nach der Aktualisierung von Kursen und der Ausführung der Kagi-Echtzeitkonstruktion den Trailing-Manager auf, wenn der Nutzer die Funktion aktiviert hat:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ ... //--- Trigger the trailing stop functionality if(enableTrailingStop){ ManageTrailingStop(); } }
Da nun alle Hauptkomponenten vorhanden sind, haben wir den Aufbau der vollständigen Version unseres Expert Advisors für den Handel abgeschlossen. Im folgenden Abschnitt werden wir uns auf das Testen des Systems konzentrieren, und sobald dies abgeschlossen ist, werden wir bereit sein, den EA für den realen Einsatz vorzubereiten.
Testen des Expert Advisors
Da nun alle Komponenten des KagiTrader EA implementiert sind, besteht der nächste Schritt darin, zu überprüfen, wie sich das System unter realen Marktbedingungen verhält. Zu diesem Zweck habe ich einen vollständigen Backtest für den Nikkei-Index (JPN225) durchgeführt, der den Zeitraum von Januar 2024 bis Dezember 2024 abdeckt. Dieser Test ermöglicht es uns zu beobachten, wie der EA mit Live-Kursen, Richtungsänderungen in der Kagi-Struktur und Trailing-Stop-Anpassungen in verschiedenen Marktphasen umgeht. Für diesen Test wurden alle Funktionen des EA aktiviert, einschließlich der dynamischen Kagi-Konstruktion, der Regeln für das Positionsmanagement und des optionalen Trailing-Stop-Loss. Der Backtest wurde mit dem MetaTrader 5 Strategy Tester unter Standardeinstellungen durchgeführt.
Die daraus resultierende Kapitalkurve zeigte eine bescheidene, aber stetige Rentabilität, was ein gutes Zeichen für ein System ist, das in erster Linie auf Strukturverschiebungen und eine disziplinierte SL/TP-Logik setzt. Der EA zeigte ein stabiles Verhalten, eröffnete und schloss ordnungsgemäß Handelsgeschäfte, respektierte Trailing-Schwellenwerte und behielt die Konsistenz über verschiedene Volatilitätsperioden bei.
Nachstehend sehen Sie die aus dem Ganzjahrestest generierte Kapitalkurve:


Dieses Ergebnis bestätigt, dass der KagiTrader EA korrekt funktioniert und Trend- und Retracement-Zyklen ohne erratisches Verhalten durchlaufen kann. Die hier dargestellte Performance bezieht sich zwar nur auf ein Symbol und einen Zeitrahmen, bietet aber eine solide Grundlage, von der aus Händler die Tests auf weitere Märkte ausdehnen oder die Parameter weiter optimieren können.
Um es Ihnen zu erleichtern, diesen Test zu reproduzieren, habe ich die während des Backtests verwendete .set-Datei beigefügt. Sie können sie direkt in Ihren Strategietester laden, um die genauen Bedingungen und Parameter, die in dieser Auswertung verwendet werden, zu erfüllen.
Zusätzlich zu den Backtest-Ergebnissen habe ich die Signalausführung auch direkt im Chart visuell überprüft. Um dies zu veranschaulichen, habe ich zwei Screenshots beigefügt. Im ersten Bild sehen Sie, dass der EA sofort eine Kaufposition eröffnet, wenn die Kagi-Struktur von Yin zu Yang übergeht, was bestätigt, dass Aufwärtsumkehrungen korrekt erkannt werden.

Der zweite Screenshot zeigt das gegenteilige Szenario – eine Verkaufsposition, die ausgelöst wird, wenn der Kagi von Yang zurück zu Yin wechselt, wie es unser Regelwerk erwartet.

Diese Chart-Beispiele geben uns die Gewissheit, dass der EA wie vorgesehen auf Trendänderungen reagiert und die Handelslogik unter Echtzeitbedingungen ordnungsgemäß funktioniert.
Schlussfolgerung
In diesem Teil der Serie haben wir unseren KagiTrader von einem einfachen Signalinterpreter in einen vollwertigen Expert Advisor verwandelt. Wir haben visuelle Signalmarker, flexible Handelsmodi, intelligentere Positionsgrößen, dynamische Stop-Loss-Platzierung und ein strukturiertes dreistufiges Trailing-System hinzugefügt. Jede Funktion wurde Schritt für Schritt vorgestellt, sodass Sie die Logik nachvollziehen und verstehen konnten, wie sie sich in das Gesamtbild einfügt.
Als wir alles integriert hatten, konnte der EA Kagi-Umkehrungen in Echtzeit lesen, Handelsgeschäfte verantwortungsvoll eröffnen und verwalten und seine Stopps an die Marktentwicklung anpassen. Unser Backtest mit dem Nikkei-Index hat bestätigt, dass das System sich konsistent verhält und die Logik genau so ausführt, wie es konzipiert wurde.
Da der EA nun fertiggestellt und erfolgreich getestet ist, verfügen Sie über eine solide Grundlage, die Sie verfeinern, erweitern oder mit der Sie experimentieren können – sei es durch das Hinzufügen von Filtern, die Verbesserung des Money Managements oder sogar die Erkundung alternativer Chart-Stile. Das Ziel dieser Serie war es, praktische, reale Automatisierungsfähigkeiten zu vermitteln, und wenn Sie diesen Punkt erreicht haben, haben Sie ein funktionierendes Handelswerkzeug von Grund auf aufgebaut.
Der gesamte in diesem Artikel verwendete Quellcode wird unten bereitgestellt. In der folgenden Tabelle werden die einzelnen Dateien und ihr Zweck erläutert.
| Dateiname | Beschreibung |
|---|---|
| KagiTraderPart1.mq5 | Der ursprüngliche Code aus Teil 1, den wir in Teil 2 erweitert und verbessert haben. |
| KagiTrader.mq5 | Der Quellcode für diesen Teil. |
| KagiTrader.set | Die .set-Datei, die zur Durchführung des Backtests für Teil 2 verwendet wird. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20378
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.
Entwicklung einer Handelsstrategie: Verwendung eines volumenabhängigen Ansatzes
Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 05): Das Logging-Modul von Python, Log Like a Pro
Einführung in MQL5 (Teil 31): Beherrschung der API- und WebRequest-Funktion in MQL5 (V)
Einführung in MQL5 (Teil 30): Beherrschung der API- und WebRequest-Funktion in MQL5 (IV)
- 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.